diff --git a/setup.cfg b/setup.cfg index 4704ec4b9..a0b59cb50 100644 --- a/setup.cfg +++ b/setup.cfg @@ -70,5 +70,6 @@ openstack.tripleoclient.v1 = overcloud_profiles_match = tripleoclient.v1.overcloud_profiles:MatchProfiles overcloud_profiles_list = tripleoclient.v1.overcloud_profiles:ListProfiles overcloud_update_stack = tripleoclient.v1.overcloud_update:UpdateOvercloud + overcloud_upgrade = tripleoclient.v1.overcloud_upgrade:UpgradeOvercloud undercloud_install = tripleoclient.v1.undercloud:InstallUndercloud undercloud_upgrade = tripleoclient.v1.undercloud:UpgradeUndercloud diff --git a/tripleoclient/tests/v1/overcloud_upgrade/__init__.py b/tripleoclient/tests/v1/overcloud_upgrade/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tripleoclient/tests/v1/overcloud_upgrade/fakes.py b/tripleoclient/tests/v1/overcloud_upgrade/fakes.py new file mode 100644 index 000000000..eb2bc5722 --- /dev/null +++ b/tripleoclient/tests/v1/overcloud_upgrade/fakes.py @@ -0,0 +1,40 @@ +# Copyright 2015 Red Hat, 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 openstackclient.tests import utils + + +class FakeClientWrapper(object): + + def __init__(self): + self._instance = mock.Mock() + self._orchestration = None + + def orchestration(self): + + if self._orchestration is None: + self._orchestration = mock.Mock() + + return self._orchestration + + +class TestOvercloudUpgrade(utils.TestCommand): + + def setUp(self): + super(TestOvercloudUpgrade, self).setUp() + + self.app.client_manager.auth_ref = mock.Mock(auth_token="TOKEN") + self.app.client_manager.tripleoclient = FakeClientWrapper() diff --git a/tripleoclient/tests/v1/overcloud_upgrade/test_overcloud_upgrade.py b/tripleoclient/tests/v1/overcloud_upgrade/test_overcloud_upgrade.py new file mode 100644 index 000000000..0077c7f80 --- /dev/null +++ b/tripleoclient/tests/v1/overcloud_upgrade/test_overcloud_upgrade.py @@ -0,0 +1,103 @@ +# Copyright 2015 Red Hat, 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 tripleoclient.tests.v1.overcloud_upgrade import fakes +from tripleoclient.v1 import overcloud_upgrade + + +class TestOvercloudUpgrade(fakes.TestOvercloudUpgrade): + + def setUp(self): + super(TestOvercloudUpgrade, self).setUp() + + # Get the command object to test + self.cmd = overcloud_upgrade.UpgradeOvercloud(self.app, None) + + @mock.patch('tripleo_common.upgrade.StackUpgradeManager') + def test_upgrade_out(self, upgrade_manager): + upgrade_manager.return_value.get_status.return_value = ( + 'UPDATE_COMPLETE', {}) + argslist = ['start', '--stack', 'overcloud', '--templates'] + verifylist = [ + ('stage', 'start'), + ('stack', 'overcloud'), + ('templates', '/usr/share/openstack-tripleo-heat-templates/') + ] + parsed_args = self.check_parser(self.cmd, argslist, verifylist) + self.cmd.take_action(parsed_args) + upgrade_manager.get_status.called_once() + upgrade_manager.upgrade.called_once() + + @mock.patch('tripleo_common.upgrade.StackUpgradeManager') + def test_upgrade_answerfile(self, upgrade_manager): + answers = ("templates: {templates}\n" + "environments:\n" + " - {environment}\n") + + mock_open = mock.mock_open(read_data=answers.format( + templates='/tmp/tht', environment='/tmp/env')) + + with mock.patch('six.moves.builtins.open', mock_open): + upgrade_manager.return_value.get_status.return_value = ( + 'UPDATE_COMPLETE', {}) + arglist = [ + 'start', + '--stack', 'overcloud', + '--answers-file', 'answerfile' + ] + verifylist = [ + ('stage', 'start'), + ('stack', 'overcloud'), + ('answers_file', 'answerfile') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + upgrade_manager.get_status.called_once() + upgrade_manager.upgrade.called_once() + + called_args = upgrade_manager.call_args[1] + self.assertEqual('/tmp/tht', called_args['tht_dir']) + self.assertEqual(['/tmp/env'], called_args['environment_files']) + + @mock.patch('tripleo_common.upgrade.StackUpgradeManager') + def test_upgrade_answerfile_just_environments(self, upgrade_manager): + mock_open = mock.mock_open(read_data="environments:\n - /tmp/env\n") + + with mock.patch('six.moves.builtins.open', mock_open): + upgrade_manager.return_value.get_status.return_value = ( + 'UPDATE_COMPLETE', {}) + arglist = [ + 'start', + '--stack', 'overcloud', + '--answers-file', 'answerfile' + ] + verifylist = [ + ('stage', 'start'), + ('stack', 'overcloud'), + ('answers_file', 'answerfile') + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + self.cmd.take_action(parsed_args) + + upgrade_manager.get_status.called_once() + upgrade_manager.upgrade.called_once() + + called_args = upgrade_manager.call_args[1] + self.assertEqual('/usr/share/openstack-tripleo-heat-templates/', + called_args['tht_dir']) + self.assertEqual(['/tmp/env'], called_args['environment_files']) diff --git a/tripleoclient/v1/overcloud_upgrade.py b/tripleoclient/v1/overcloud_upgrade.py new file mode 100644 index 000000000..c0d94f2eb --- /dev/null +++ b/tripleoclient/v1/overcloud_upgrade.py @@ -0,0 +1,102 @@ +# Copyright 2015 Red Hat, 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 +import yaml + +from cliff import command +from openstackclient.common import utils +from openstackclient.i18n import _ +from tripleo_common import upgrade + +from tripleoclient import constants + + +class UpgradeOvercloud(command.Command): + """Performs a major upgrade on overcloud nodes""" + + log = logging.getLogger(__name__ + ".UpgradeOvercloud") + + def get_parser(self, prog_name): + parser = super(UpgradeOvercloud, self).get_parser(prog_name) + parser.add_argument( + 'stage', + metavar="", + choices=['start'], + help=_('Stage of upgrade to perform.') + ) + parser.add_argument( + '--stack', + dest='stack', + help=_('Name or ID of heat stack to upgrade ' + '(default=Env: OVERCLOUD_STACK_NAME)'), + default=utils.env('OVERCLOUD_STACK_NAME')) + parser.add_argument( + '-e', '--environment-file', metavar='', + action='append', dest='environment_files', + help=_('Environment files to be passed to the heat stack-update ' + 'command. (Can be specified more than once.)') + ) + template_group = parser.add_mutually_exclusive_group(required=True) + template_group.add_argument( + '--templates', nargs='?', const=constants.TRIPLEO_HEAT_TEMPLATES, + help=_("The directory containing the Heat templates used for " + "the upgraded deployment. Cannot be specified with " + "--answers-file."), + ) + template_group.add_argument( + '--answers-file', + help=_('Path to a YAML file with arguments and parameters. Cannot ' + 'be used with --templates.') + ) + parser.add_argument + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + if parsed_args.answers_file is not None: + with open(parsed_args.answers_file, 'r') as answers_file: + answers = yaml.load(answers_file) + + parsed_args.templates = (constants.TRIPLEO_HEAT_TEMPLATES if + answers.get('templates') is None else + answers.get('templates')) + if 'environments' in answers: + if parsed_args.environment_files is not None: + answers.environments.extend( + parsed_args.environment_files) + parsed_args.environment_files = answers['environments'] + + osc_plugin = self.app.client_manager.tripleoclient + + orchestration = osc_plugin.orchestration + upgrade_manager = upgrade.StackUpgradeManager( + heatclient=orchestration, + stack_id=parsed_args.stack, + tht_dir=parsed_args.templates, + environment_files=parsed_args.environment_files) + status = upgrade_manager.get_status() + if status not in ['IN_PROGRESS', 'WAITING']: + print("Starting stack upgrade on stack {0}".format( + parsed_args.stack)) + stage_func = { + "start": upgrade_manager.upgrade + } + stage_func[parsed_args.stage]() + else: + print("Could not start upgrade. Stack operation already in " + "progress on stack {0}".format(parsed_args.stack)) + + print("stack {0} status: {1}".format(parsed_args.stack, status))