fuel_upgrade: install rpm packages with fuel 6.1

We don't need to install bootstrap images, provisioning images and new
puppet manifests, because we have them packaged now. Since now, the
fuel_upgrade is supposed to install the "fuel-release" package, run
puppet and create new Docker containers.

Depends-On: I417f8e9495e7dc8b22fc33b3c88408b879abaec2
Implements: blueprint package-fuel-components

Change-Id: I379749673c67c459c7070d986094ec6236d7ca6e
Signed-off-by: Igor Kalnitsky <igor@kalnitsky.org>
This commit is contained in:
Igor Kalnitsky 2015-04-16 18:27:26 +03:00
parent de04ee015c
commit dbb9b9f384
13 changed files with 59 additions and 660 deletions

View File

@ -1,180 +0,0 @@
# -*- 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 abc
import os
import six
from fuel_upgrade.utils import copy
from fuel_upgrade.utils import remove
from fuel_upgrade.utils import rename
from fuel_upgrade.utils import symlink
from fuel_upgrade.utils import symlink_if_src_exists
@six.add_metaclass(abc.ABCMeta)
class Action(object):
"""An action interface.
The interface is clear and no need to be commented.
"""
def __init__(self, *args, **kwargs):
# declare custom undo action, not a default one
if 'undo' in kwargs:
self.undo = ActionManager(kwargs['undo']).do
@abc.abstractmethod
def do(self):
"""Performs an action.
"""
@abc.abstractmethod
def undo(self):
"""Rollbacks an action.
"""
class Copy(Action):
"""Copy action provides a way to copy stuff from one place to another.
The source and destination could be either a file or directory.
:param from: copy from
:param to: copy to
:param overwrite: overwrite a destination if True
:param symlinks: resolve symlinks if True
"""
def __init__(self, **kwargs):
super(Copy, self).__init__(**kwargs)
self._from = kwargs['from']
self._to = kwargs['to']
self._overwrite = kwargs.get('overwrite', True)
self._symlinks = kwargs.get('symlinks', True)
def do(self):
copy(self._from, self._to, self._overwrite, self._symlinks)
def undo(self):
# destination should be a path/to/file in case source was a file
destination = self._to
if not os.path.isdir(self._from) and os.path.isdir(self._to):
basename = os.path.basename(self._from)
destination = os.path.join(self._to, basename)
# do nothing if destination doesn't exist
remove(destination, ignore_errors=True)
class Move(Action):
"""Move action provides a way to move stuff from one place to another.
:param from: a move source
:param to: a move destination
:param overwrite: overwrite a destination if True
"""
def __init__(self, **kwargs):
super(Move, self).__init__(**kwargs)
self._from = kwargs['from']
self._to = kwargs['to']
self._overwrite = kwargs.get('overwrite', True)
def do(self):
rename(self._from, self._to, self._overwrite)
def undo(self):
rename(self._to, self._from, self._overwrite)
class Symlink(Action):
"""Symlink action provides a way to make a symbolic link to some resource.
:param from: a path to origin resource
:param to: a path to link
:param overwrite: overwrite link if True
"""
def __init__(self, **kwargs):
super(Symlink, self).__init__(**kwargs)
self._from = kwargs['from']
self._to = kwargs['to']
self._overwrite = kwargs.get('overwrite', True)
def do(self):
symlink(self._from, self._to, self._overwrite)
def undo(self):
remove(self._to)
class SymlinkIfSrcExists(Symlink):
"""SymlinkIfSrcExists action provides a way
to make a symbolic link to some resource but only if
source exists. I.e. link 1 -> 2 will be created only if 2 exists.
:param from: a path to origin resource
:param to: a path to link
:param overwrite: overwrite link if True
"""
def do(self):
symlink_if_src_exists(self._from, self._to, self._overwrite)
class ActionManager(Action):
"""The action manager is designed to manage actions, run it or
rollback based on action description.
:param actions: a list with action descriptions
:param context: a dict with some context that's passed to actions
"""
#: a list of supported actions
supported_actions = {
'copy': Copy,
'move': Move,
'symlink': Symlink,
'symlink_if_src_exists': SymlinkIfSrcExists
}
def __init__(self, actions):
#: a list of actions to execute
self._actions = []
#: a list of executed actions (we need it to rollback feature)
self._history = []
# convert some input action description to class instances
for action in actions:
action_class = self.supported_actions[action['name']]
self._actions.append(action_class(**action))
def do(self):
"""Performs actions saving in history."""
for action in self._actions:
action.do()
self._history.append(action)
def undo(self):
"""Rollbacks completed actions."""
while self._history:
action = self._history.pop()
action.undo()

View File

@ -29,13 +29,11 @@ from fuel_upgrade.checker_manager import CheckerManager
from fuel_upgrade.config import build_config
from fuel_upgrade.upgrade import UpgradeManager
from fuel_upgrade.engines.bootstrap import BootstrapUpgrader
from fuel_upgrade.engines.docker_engine import DockerInitializer
from fuel_upgrade.engines.docker_engine import DockerUpgrader
from fuel_upgrade.engines.host_system import HostSystemUpgrader
from fuel_upgrade.engines.openstack import OpenStackUpgrader
from fuel_upgrade.engines.raise_error import RaiseErrorUpgrader
from fuel_upgrade.engines.targetimages import TargetImagesUpgrader
from fuel_upgrade.pre_upgrade_hooks import PreUpgradeHookManager
@ -48,9 +46,7 @@ SUPPORTED_SYSTEMS = {
'host-system': HostSystemUpgrader,
'docker-init': DockerInitializer,
'docker': DockerUpgrader,
'bootstrap': BootstrapUpgrader,
'openstack': OpenStackUpgrader,
'targetimages': TargetImagesUpgrader,
'raise-error': RaiseErrorUpgrader,
}

View File

@ -27,16 +27,19 @@ Why python based config?
and it's hard to create variables nesting more than 1
"""
import six
import glob
import logging
import yaml
from os.path import basename
from os.path import exists
from os.path import join
import six
import yaml
from fuel_upgrade.utils import normversion
logger = logging.getLogger(__name__)
@ -196,19 +199,23 @@ def get_host_system(update_path, new_version):
:returns: a host-system upgrade settings
"""
openstack_versions = glob.glob(
join(update_path, 'puppet', '[0-9.-]*{0}'.format(new_version)))
join(update_path, 'repos', '[0-9.-]*{0}'.format(new_version)))
openstack_versions = [basename(v) for v in openstack_versions]
openstack_version = sorted(openstack_versions, reverse=True)[0]
centos_repo_path = join(
update_path, 'repos', openstack_version, 'centos/x86_64')
return {
'install_packages': [
'fuel-release-{0}'.format(normversion(new_version)),
],
'manifest_path': join(
update_path, 'puppet', openstack_version,
'/etc/puppet', openstack_version,
'modules/nailgun/examples/host-upgrade.pp'),
'puppet_modules_path': join(
update_path, 'puppet', openstack_version, 'modules'),
'/etc/puppet', openstack_version, 'modules'),
'repo_config_path': join(
'/etc/yum.repos.d',
@ -291,7 +298,7 @@ def config(update_path, admin_password):
image_prefix = 'fuel/'
# Path to the Docker images to be loaded
images = join(update_path, 'images', 'fuel-images.tar')
images = '/var/www/nailgun/docker/images/fuel-images.tar'
# Docker containers description section
container_prefix = 'fuel-core-'
@ -589,71 +596,4 @@ def config(update_path, admin_password):
# Config for host system upgarde engine
host_system = get_host_system(update_path, new_version)
# Config for bootstrap upgrade
bootstrap = {
'actions': [
{
'name': 'move',
'from': '/var/www/nailgun/bootstrap',
'to': '/var/www/nailgun/{0}_bootstrap'.format(from_version),
# Don't overwrite backup files
'overwrite': False,
'undo': [
{
# NOTE(eli): Rollback bootstrap files
# with copy, because in 5.0 version
# we have volumes linking in container
# which doesn't work correctly with symlinks
'name': 'copy',
'from': '/var/www/nailgun/{0}_bootstrap'.format(
from_version),
'to': '/var/www/nailgun/bootstrap',
'undo': [],
'overwrite': True
}
]
},
{
'name': 'symlink',
'from': '/var/www/nailgun/{0}_bootstrap'.format(from_version),
'to': '/var/www/nailgun/bootstrap',
'undo': []
},
{
'name': 'copy',
'from': join(update_path, 'bootstrap'),
'to': '/var/www/nailgun/{0}_bootstrap'.format(new_version),
},
{
'name': 'symlink',
'from': '/var/www/nailgun/{0}_bootstrap'.format(new_version),
'to': '/var/www/nailgun/bootstrap',
'undo': []
}
]}
targetimages = {
'actions': [
{
'name': 'copy',
'from': join(update_path, 'targetimages'),
'to': '/var/www/nailgun/{0}_targetimages'.format(new_version),
},
{
'name': 'symlink',
'from': '/var/www/nailgun/{0}_targetimages'.format(
new_version),
'to': '/var/www/nailgun/targetimages',
'undo': [
{
'name': 'symlink_if_src_exists',
'from': '/var/www/nailgun/{0}_targetimages'.format(
from_version),
'to': '/var/www/nailgun/targetimages'
}
]
}
]
}
return locals()

View File

@ -1,58 +0,0 @@
# -*- 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 logging
from fuel_upgrade.actions import ActionManager
from fuel_upgrade.engines.base import UpgradeEngine
from fuel_upgrade.utils import get_required_size_for_actions
logger = logging.getLogger(__name__)
class BootstrapUpgrader(UpgradeEngine):
"""Bootstrap Upgrader.
"""
def __init__(self, *args, **kwargs):
super(BootstrapUpgrader, self).__init__(*args, **kwargs)
#: an action manager instance
self._action_manager = ActionManager(self.config.bootstrap['actions'])
def upgrade(self):
logger.info('bootstrap upgrader: starting...')
self._action_manager.do()
logger.info('bootstrap upgrader: done')
def rollback(self):
logger.info('bootstrap upgrader: rollbacking...')
self._action_manager.undo()
logger.info('bootstrap upgrader: rollbacked')
@property
def required_free_space(self):
return get_required_size_for_actions(
self.config.bootstrap['actions'], self.config.update_path)
def on_success(self):
"""Do nothing for this engine
"""

View File

@ -62,6 +62,9 @@ class HostSystemUpgrader(UpgradeEngine):
#: dst repository path
self.repo_dst = self.host_system_config['repo_path']['dst']
#: packages to be installed before running puppet
self.packages = self.host_system_config['install_packages']
@property
def required_free_space(self):
"""Required free space to run upgrade
@ -79,6 +82,7 @@ class HostSystemUpgrader(UpgradeEngine):
"""
self.copy_repo()
self.update_repo()
self.install_packages()
self.run_puppet()
def rollback(self):
@ -107,6 +111,13 @@ class HostSystemUpgrader(UpgradeEngine):
self.repo_config_path,
{'version': self.version,
'repo_path': self.repo_dst})
utils.exec_cmd('yum clean all')
def install_packages(self):
"""Install packages for new release
"""
for package in self.packages:
utils.exec_cmd('yum install -v -y {0}'.format(package))
def run_puppet(self):
"""Run puppet to upgrade host system

View File

@ -1,58 +0,0 @@
# -*- 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 logging
from fuel_upgrade.actions import ActionManager
from fuel_upgrade.engines.base import UpgradeEngine
from fuel_upgrade.utils import get_required_size_for_actions
logger = logging.getLogger(__name__)
class TargetImagesUpgrader(UpgradeEngine):
"""TargetImagesUpgrader.
"""
def __init__(self, *args, **kwargs):
super(TargetImagesUpgrader, self).__init__(*args, **kwargs)
#: an action manager instance
self._action_manager = ActionManager(
self.config.targetimages['actions'])
def upgrade(self):
logger.info('targetimages upgrader: starting...')
self._action_manager.do()
logger.info('targetimages upgrader: done')
def rollback(self):
logger.info('targetimages upgrader: rollbacking...')
self._action_manager.undo()
logger.info('targetimages upgrader: rollbacked')
@property
def required_free_space(self):
return get_required_size_for_actions(
self.config.targetimages['actions'], self.config.update_path)
def on_success(self):
"""Do nothing for this engine
"""

View File

@ -77,10 +77,6 @@ class WrongVersionError(FuelUpgradeException):
pass
class UnsupportedActionTypeError(FuelUpgradeException):
pass
class NailgunIsNotRunningError(FuelUpgradeException):
pass

View File

@ -1,180 +0,0 @@
import mock
from fuel_upgrade import actions
from fuel_upgrade.tests.base import BaseTestCase
class TestActionManager(BaseTestCase):
def setUp(self):
self.manager = actions.ActionManager([
{
'name': 'copy',
'from': 'from_path',
'to': 'to_path',
},
{
'name': 'move',
'from': 'from_path',
'to': 'to_path',
},
{
'name': 'symlink',
'from': 'from_path',
'to': 'to_path',
}
])
def test_constructor(self):
self.assertEqual(len(self.manager._actions), 3)
self.assertEqual(len(self.manager._history), 0)
self.assertTrue(
isinstance(self.manager._actions[0], actions.Copy))
self.assertTrue(
isinstance(self.manager._actions[1], actions.Move))
self.assertTrue(
isinstance(self.manager._actions[2], actions.Symlink))
def test_do(self):
self.assertEqual(len(self.manager._history), 0)
for action in self.manager._actions:
action.do = mock.Mock()
self.manager.do()
for action in self.manager._actions:
self.called_once(action.do)
self.assertEqual(len(self.manager._history), 3)
def test_undo(self):
self.manager._history = self.manager._actions
for action in self.manager._history:
action.undo = mock.Mock()
self.manager.undo()
for action in self.manager._history:
self.called_once(action.undo)
self.assertEqual(len(self.manager._history), 0)
def test_complex_undo(self):
self.action = actions.Copy(**{
'from': 'path/to/src',
'to': 'path/to/dst',
'undo': [
{
'name': 'move',
'from': 'one',
'to': 'two',
},
{
'name': 'copy',
'from': 'one',
'to': 'two',
}
]
})
self.assertTrue(
isinstance(self.action.undo.__self__, actions.ActionManager))
self.assertEqual(len(self.action.undo.im_self._actions), 2)
mocks = [mock.Mock(), mock.Mock()]
self.action.undo.im_self._actions = mocks
self.action.undo()
for action in mocks:
self.called_once(action.do)
class TestCopyAction(BaseTestCase):
def setUp(self):
self.action = actions.Copy(**{
'from': 'path/to/src',
'to': 'path/to/dst',
})
@mock.patch('fuel_upgrade.actions.copy')
def test_do(self, copy):
self.action.do()
copy.assert_called_once_with('path/to/src', 'path/to/dst', True, True)
@mock.patch('fuel_upgrade.actions.copy')
def test_do_with_overwrite_false(self, copy):
self.action._overwrite = False
self.action.do()
copy.assert_called_once_with('path/to/src', 'path/to/dst', False, True)
@mock.patch('fuel_upgrade.actions.copy')
def test_do_with_symlink_false(self, copy):
self.action._symlinks = False
self.action.do()
copy.assert_called_once_with('path/to/src', 'path/to/dst', True, False)
@mock.patch('fuel_upgrade.actions.remove')
def test_undo(self, remove):
self.action.undo()
remove.assert_called_once_with('path/to/dst', ignore_errors=True)
class TestMoveAction(BaseTestCase):
def setUp(self):
self.action = actions.Move(**{
'from': 'path/to/src',
'to': 'path/to/dst',
'overwrite': False
})
@mock.patch('fuel_upgrade.actions.rename')
def test_do(self, rename):
self.action.do()
rename.assert_called_once_with('path/to/src', 'path/to/dst', False)
@mock.patch('fuel_upgrade.actions.rename')
def test_undo(self, rename):
self.action.undo()
rename.assert_called_once_with('path/to/dst', 'path/to/src', False)
class TestSymlinkAction(BaseTestCase):
def setUp(self):
self.action = actions.Symlink(**{
'from': 'path/to/src',
'to': 'path/to/dst',
})
@mock.patch('fuel_upgrade.actions.symlink')
def test_do(self, symlink):
self.action.do()
symlink.assert_called_once_with('path/to/src', 'path/to/dst', True)
@mock.patch('fuel_upgrade.actions.remove')
def test_undo(self, remove):
self.action.undo()
remove.assert_called_once_with('path/to/dst')
class TestSymlinkIfSrcExistsAction(BaseTestCase):
def setUp(self):
self.action = actions.SymlinkIfSrcExists(**{
'from': 'path/to/src',
'to': 'path/to/dst',
})
@mock.patch('fuel_upgrade.actions.symlink_if_src_exists')
def test_do(self, symlink):
self.action.do()
symlink.assert_called_once_with('path/to/src', 'path/to/dst', True)
@mock.patch('fuel_upgrade.actions.remove')
def test_undo(self, remove):
self.action.undo()
remove.assert_called_once_with('path/to/dst')

View File

@ -1,50 +0,0 @@
# -*- 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 mock
from fuel_upgrade.engines.bootstrap import BootstrapUpgrader
from fuel_upgrade.tests.base import BaseTestCase
class TestBootstrapUpgrader(BaseTestCase):
def setUp(self):
self.upgrader = BootstrapUpgrader(self.fake_config)
def test_constructor(self):
self.assertEqual(len(self.upgrader._action_manager._actions), 4)
def test_upgrade(self):
self.upgrader._action_manager.do = mock.Mock()
self.upgrader.upgrade()
self.called_once(self.upgrader._action_manager.do)
def test_rollback(self):
self.upgrader._action_manager.undo = mock.Mock()
self.upgrader.rollback()
self.called_once(self.upgrader._action_manager.undo)
def test_on_success_does_not_raise_exceptions(self):
self.upgrader.on_success()
@mock.patch('fuel_upgrade.utils.os.path.isdir', return_value=True)
@mock.patch('fuel_upgrade.utils.dir_size', return_value=42)
def test_required_free_space(self, _, __):
result = self.upgrader.required_free_space
self.assertEqual(result, {'/var/www/nailgun/9999_bootstrap': 42})

View File

@ -41,6 +41,8 @@ class TestHostSystemUpgrader(BaseTestCase):
'/var/www/nailgun/2014.1.1-5.1/centos/x86_64')
self.called_once(run_puppet_mock)
self.called_once(update_repo_mock)
mock_utils.exec_cmd.assert_called_with(
'yum install -v -y fuel-release-9999.0.0')
@mock.patch('fuel_upgrade.engines.host_system.utils')
def test_update_repo(self, utils_mock):
@ -58,9 +60,9 @@ class TestHostSystemUpgrader(BaseTestCase):
self.upgrader.run_puppet()
utils_mock.exec_cmd.assert_called_once_with(
'puppet apply -d -v '
'/tmp/upgrade_path/puppet/2014.1.1-5.1/modules/nailgun/examples'
'/etc/puppet/2014.1.1-5.1/modules/nailgun/examples'
'/host-upgrade.pp '
'--modulepath=/tmp/upgrade_path/puppet/2014.1.1-5.1/modules')
'--modulepath=/etc/puppet/2014.1.1-5.1/modules')
@mock.patch(
'fuel_upgrade.engines.host_system.'

View File

@ -1,50 +0,0 @@
# -*- 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 mock
from fuel_upgrade.engines.targetimages import TargetImagesUpgrader
from fuel_upgrade.tests.base import BaseTestCase
class TestTargetImagesUpgrader(BaseTestCase):
def setUp(self):
self.upgrader = TargetImagesUpgrader(self.fake_config)
def test_constructor(self):
self.assertEqual(len(self.upgrader._action_manager._actions), 2)
def test_upgrade(self):
self.upgrader._action_manager.do = mock.Mock()
self.upgrader.upgrade()
self.called_once(self.upgrader._action_manager.do)
def test_rollback(self):
self.upgrader._action_manager.undo = mock.Mock()
self.upgrader.rollback()
self.called_once(self.upgrader._action_manager.undo)
def test_on_success_does_not_raise_exceptions(self):
self.upgrader.on_success()
@mock.patch('fuel_upgrade.utils.os.path.isdir', return_value=True)
@mock.patch('fuel_upgrade.utils.dir_size', return_value=42)
def test_required_free_space(self, _, __):
result = self.upgrader.required_free_space
self.assertEqual(result, {'/var/www/nailgun/9999_targetimages': 42})

View File

@ -593,6 +593,20 @@ class TestVersionedFile(BaseTestCase):
self.versioned_file.sorted_files(),
['/tmp/path.ext.10', '/tmp/path.ext.6'])
def test_normversion(self):
cases = [
# (input, output)
('6', '6.0.0'),
('6.0', '6.0.0'),
('6.1', '6.1.0'),
('6.1.0', '6.1.0'),
('6.1.1', '6.1.1'),
('6.1.1.1', '6.1.1.1'),
]
for input_, output in cases:
self.assertEqual(utils.normversion(input_), output)
class TestSanitizer(BaseTestCase):
original = {

View File

@ -31,6 +31,7 @@ from copy import deepcopy
from distutils.version import StrictVersion
from mako.template import Template
from six.moves import range
import yaml
from fuel_upgrade import errors
@ -752,6 +753,21 @@ def iterfiles_filter(dir_path, file_pattern):
yield file_path
def normversion(version):
"""Normalize a given version to have exactly three components.
:param version: a version to be normalized
:returns: a normalized version
"""
components = version.split('.')
if len(components) < 3:
for _ in range(0, 3 - len(components)):
components.append('0')
return '.'.join(components)
class VersionedFile(object):
"""Set of methods for versioned files.
If `basename` is '/tmp/file.ext' it allows