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

View File

@ -27,16 +27,19 @@ Why python based config?
and it's hard to create variables nesting more than 1 and it's hard to create variables nesting more than 1
""" """
import six
import glob import glob
import logging import logging
import yaml
from os.path import basename from os.path import basename
from os.path import exists from os.path import exists
from os.path import join from os.path import join
import six
import yaml
from fuel_upgrade.utils import normversion
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -196,19 +199,23 @@ def get_host_system(update_path, new_version):
:returns: a host-system upgrade settings :returns: a host-system upgrade settings
""" """
openstack_versions = glob.glob( 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_versions = [basename(v) for v in openstack_versions]
openstack_version = sorted(openstack_versions, reverse=True)[0] openstack_version = sorted(openstack_versions, reverse=True)[0]
centos_repo_path = join( centos_repo_path = join(
update_path, 'repos', openstack_version, 'centos/x86_64') update_path, 'repos', openstack_version, 'centos/x86_64')
return { return {
'install_packages': [
'fuel-release-{0}'.format(normversion(new_version)),
],
'manifest_path': join( 'manifest_path': join(
update_path, 'puppet', openstack_version, '/etc/puppet', openstack_version,
'modules/nailgun/examples/host-upgrade.pp'), 'modules/nailgun/examples/host-upgrade.pp'),
'puppet_modules_path': join( 'puppet_modules_path': join(
update_path, 'puppet', openstack_version, 'modules'), '/etc/puppet', openstack_version, 'modules'),
'repo_config_path': join( 'repo_config_path': join(
'/etc/yum.repos.d', '/etc/yum.repos.d',
@ -291,7 +298,7 @@ def config(update_path, admin_password):
image_prefix = 'fuel/' image_prefix = 'fuel/'
# Path to the Docker images to be loaded # 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 # Docker containers description section
container_prefix = 'fuel-core-' container_prefix = 'fuel-core-'
@ -589,71 +596,4 @@ def config(update_path, admin_password):
# Config for host system upgarde engine # Config for host system upgarde engine
host_system = get_host_system(update_path, new_version) 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() 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 #: dst repository path
self.repo_dst = self.host_system_config['repo_path']['dst'] 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 @property
def required_free_space(self): def required_free_space(self):
"""Required free space to run upgrade """Required free space to run upgrade
@ -79,6 +82,7 @@ class HostSystemUpgrader(UpgradeEngine):
""" """
self.copy_repo() self.copy_repo()
self.update_repo() self.update_repo()
self.install_packages()
self.run_puppet() self.run_puppet()
def rollback(self): def rollback(self):
@ -107,6 +111,13 @@ class HostSystemUpgrader(UpgradeEngine):
self.repo_config_path, self.repo_config_path,
{'version': self.version, {'version': self.version,
'repo_path': self.repo_dst}) '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): def run_puppet(self):
"""Run puppet to upgrade host system """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 pass
class UnsupportedActionTypeError(FuelUpgradeException):
pass
class NailgunIsNotRunningError(FuelUpgradeException): class NailgunIsNotRunningError(FuelUpgradeException):
pass 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') '/var/www/nailgun/2014.1.1-5.1/centos/x86_64')
self.called_once(run_puppet_mock) self.called_once(run_puppet_mock)
self.called_once(update_repo_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') @mock.patch('fuel_upgrade.engines.host_system.utils')
def test_update_repo(self, utils_mock): def test_update_repo(self, utils_mock):
@ -58,9 +60,9 @@ class TestHostSystemUpgrader(BaseTestCase):
self.upgrader.run_puppet() self.upgrader.run_puppet()
utils_mock.exec_cmd.assert_called_once_with( utils_mock.exec_cmd.assert_called_once_with(
'puppet apply -d -v ' '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 ' '/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( @mock.patch(
'fuel_upgrade.engines.host_system.' '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(), self.versioned_file.sorted_files(),
['/tmp/path.ext.10', '/tmp/path.ext.6']) ['/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): class TestSanitizer(BaseTestCase):
original = { original = {

View File

@ -31,6 +31,7 @@ from copy import deepcopy
from distutils.version import StrictVersion from distutils.version import StrictVersion
from mako.template import Template from mako.template import Template
from six.moves import range
import yaml import yaml
from fuel_upgrade import errors from fuel_upgrade import errors
@ -752,6 +753,21 @@ def iterfiles_filter(dir_path, file_pattern):
yield file_path 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): class VersionedFile(object):
"""Set of methods for versioned files. """Set of methods for versioned files.
If `basename` is '/tmp/file.ext' it allows If `basename` is '/tmp/file.ext' it allows