171 lines
5.9 KiB
Python
171 lines
5.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
# Copyright 2014 Mirantis, Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
import glob
|
|
import logging
|
|
import os
|
|
|
|
import six
|
|
|
|
from fuel_upgrade import messages
|
|
from fuel_upgrade import utils
|
|
from fuel_upgrade.version_file import VersionFile
|
|
|
|
from fuel_upgrade.engines.host_system import HostSystemUpgrader
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class UpgradeManager(object):
|
|
"""Upgrade manager is used to orchestrate upgrading process.
|
|
|
|
:param upgraders: a list with upgrader classes to use; each upgrader
|
|
must inherit the :class:`BaseUpgrader`
|
|
:param no_rollback: call :meth:`BaseUpgrader.rollback` method
|
|
in case of exception during execution
|
|
"""
|
|
|
|
def __init__(self, upgraders, config, no_rollback=True):
|
|
#: an object with configuration context
|
|
self._config = config
|
|
#: a list of upgraders to use
|
|
self._upgraders = upgraders
|
|
#: a list of used upgraders (needs by rollback feature)
|
|
self._used_upgraders = []
|
|
#: should we make rollback in case of error?
|
|
self._rollback = not no_rollback
|
|
#: version.yaml manager
|
|
self._version_file = VersionFile(self._config)
|
|
self._version_file.save_current()
|
|
|
|
def run(self):
|
|
"""Runs consequentially all registered upgraders.
|
|
|
|
.. note:: in case of exception the `rollback` method will be called
|
|
"""
|
|
logger.info('*** START UPGRADING')
|
|
|
|
self._version_file.switch_to_new()
|
|
|
|
for upgrader in self._upgraders:
|
|
logger.debug('%s: backuping...', upgrader.__class__.__name__)
|
|
|
|
try:
|
|
upgrader.backup()
|
|
except Exception as exc:
|
|
logger.exception(
|
|
'%s: failed to backup: "%s"',
|
|
upgrader.__class__.__name__, exc)
|
|
|
|
logger.error('*** UPGRADE FAILED')
|
|
raise
|
|
|
|
for upgrader in self._upgraders:
|
|
logger.debug('%s: upgrading...', upgrader.__class__.__name__)
|
|
self._used_upgraders.append(upgrader)
|
|
|
|
try:
|
|
upgrader.upgrade()
|
|
except Exception as exc:
|
|
logger.exception(
|
|
'%s: failed to upgrade: "%s"',
|
|
upgrader.__class__.__name__, exc)
|
|
|
|
if self._rollback:
|
|
self.rollback()
|
|
|
|
logger.error('*** UPGRADE FAILED')
|
|
raise
|
|
|
|
try:
|
|
self._on_success()
|
|
except Exception as exc:
|
|
logger.exception(
|
|
'Could not complete on_success actions due to %s',
|
|
six.text_type(exc))
|
|
|
|
logger.info('*** UPGRADING MASTER NODE DONE SUCCESSFULLY')
|
|
logger.info('*** PLEASE REBOOT YOUR BOOTSTRAP NODES IN ORDER TO MAKE'
|
|
' SURE THAT THEY USE THE LATEST BOOTSTRAP IMAGE')
|
|
|
|
def _on_success(self):
|
|
"""Do some useful job if upgrade was done successfully
|
|
|
|
Remove saved version files for all upgrades
|
|
|
|
NOTE(eli): It solves several problems:
|
|
|
|
1. user runs upgrade 5.0 -> 5.1 which fails
|
|
upgrade system saves version which we upgrade
|
|
from in file working_dir/5.1/version.yaml.
|
|
Then user runs upgrade 5.0 -> 5.0.1 which
|
|
successfully upgraded. Then user runs again
|
|
upgrade 5.0.1 -> 5.1, but there is saved file
|
|
working_dir/5.1/version.yaml which contains
|
|
5.0 version, and upgrade system thinks that
|
|
it's upgrading from 5.0 version, as result
|
|
it tries to make database dump from wrong
|
|
version of container.
|
|
|
|
2. without this hack user can run upgrade
|
|
second time and loose his data, this hack
|
|
prevents this case because before upgrade
|
|
checker will use current version instead
|
|
of saved version to determine version which
|
|
we run upgrade from.
|
|
"""
|
|
for version_file in glob.glob(self._config.version_files_mask):
|
|
utils.remove(version_file)
|
|
|
|
self._setup_update_repos()
|
|
|
|
def _setup_update_repos(self):
|
|
"""Setup updates/security repos on master node."""
|
|
template = os.path.join(
|
|
os.path.dirname(os.path.abspath(__file__)),
|
|
'templates', 'nailgun.repo')
|
|
|
|
for repo in self._config.master_node_repos:
|
|
destination = '{0}.repo'.format(
|
|
os.path.join('/etc/yum.repos.d', repo['name']))
|
|
utils.render_template_to_file(template, destination, repo)
|
|
|
|
logger.warning(messages.update_your_master_node)
|
|
|
|
def rollback(self):
|
|
logger.debug('Run rollback')
|
|
|
|
# because of issue #1452378 [1], we have to perform HostSystem's
|
|
# rollback before others. so, move it to the end of list.
|
|
#
|
|
# [1]: https://bugs.launchpad.net/fuel/+bug/1452378
|
|
hostsystem = next((
|
|
upgrader for upgrader in self._used_upgraders
|
|
if isinstance(upgrader, HostSystemUpgrader)),
|
|
None)
|
|
if hostsystem is not None:
|
|
self._used_upgraders.remove(hostsystem)
|
|
self._used_upgraders.append(hostsystem)
|
|
|
|
# do rollback in reverse order for all the upgraders
|
|
while self._used_upgraders:
|
|
upgrader = self._used_upgraders.pop()
|
|
logger.debug('%s: rollbacking...', upgrader.__class__.__name__)
|
|
upgrader.rollback()
|
|
|
|
self._version_file.switch_to_previous()
|