diff --git a/tripleoclient/constants.py b/tripleoclient/constants.py index 1166fcdcd..bc6c38617 100644 --- a/tripleoclient/constants.py +++ b/tripleoclient/constants.py @@ -170,6 +170,7 @@ EXPORT_PASSWORD_EXCLUDE_PATTERNS = [ # Package that need to be to the latest before undercloud # update/update/ffwd. UNDERCLOUD_EXTRA_PACKAGES = [ + "openstack-tripleo-common", "openstack-tripleo-heat-templates", "openstack-tripleo-validations", "tripleo-ansible" diff --git a/tripleoclient/tests/v1/undercloud/test_install_upgrade.py b/tripleoclient/tests/v1/undercloud/test_install_upgrade.py index 81d718bb7..6c29e3966 100644 --- a/tripleoclient/tests/v1/undercloud/test_install_upgrade.py +++ b/tripleoclient/tests/v1/undercloud/test_install_upgrade.py @@ -17,6 +17,7 @@ import fixtures import json import mock import os +import sys from jinja2 import Template @@ -563,6 +564,7 @@ class TestUndercloudUpgrade(TestPluginV1): app_args.verbose_level = 1 self.cmd = undercloud.UpgradeUndercloud(self.app, app_args) + @mock.patch.object(sys, 'executable', 'python2') # TODO(cjeanner) drop once we have proper oslo.privsep @mock.patch('getpass.getuser', return_value='stack') @mock.patch('shutil.copy') @@ -582,12 +584,64 @@ class TestUndercloudUpgrade(TestPluginV1): self.cmd.take_action(parsed_args) mock_run_command.assert_called_with( ['sudo', 'yum', 'upgrade', '-y', + 'python2-tripleoclient', + 'openstack-tripleo-common', 'openstack-tripleo-heat-templates', 'openstack-tripleo-validations', 'tripleo-ansible'], name='Update extra packages' ) + mock_subprocess.assert_called_with([ + 'openstack', 'undercloud', 'upgrade', '--skip-package-updates', + '--no-validations']) + @mock.patch.object(sys, 'executable', 'python3') + # TODO(cjeanner) drop once we have proper oslo.privsep + @mock.patch('os.geteuid', return_value=1001) + @mock.patch('getpass.getuser', return_value='stack') + @mock.patch('shutil.copy') + @mock.patch('os.mkdir') + @mock.patch('tripleoclient.utils.write_env_file', autospec=True) + @mock.patch('subprocess.check_call', autospec=True) + @mock.patch('tripleoclient.utils.run_command', autospec=True) + def test_undercloud_upgrade_all_opts(self, mock_run_command, + mock_subprocess, + mock_wr, + mock_os, mock_copy, mock_user, + mock_getuid): + arglist = ['--force-stack-update', '--no-validations', + '--inflight-validations', '--dry-run', '--yes'] + verifylist = [] + self.cmd.app_args.verbose_level = 2 + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) + mock_run_command.assert_not_called() + mock_subprocess.assert_called_with([ + 'openstack', 'undercloud', 'upgrade', '--skip-package-updates', + '--force-stack-update', '--no-validations', + '--inflight-validations', '--dry-run', '--yes', '--debug']) + + # TODO(cjeanner) drop once we have proper oslo.privsep + @mock.patch('os.geteuid', return_value=1001) + @mock.patch('getpass.getuser', return_value='stack') + @mock.patch('shutil.copy') + @mock.patch('os.mkdir') + @mock.patch('tripleoclient.utils.write_env_file', autospec=True) + @mock.patch('subprocess.check_call', autospec=True) + @mock.patch('tripleoclient.utils.run_command', autospec=True) + def test_undercloud_upgrade_no_pkgs(self, mock_run_command, + mock_subprocess, + mock_wr, + mock_os, mock_copy, mock_user, + mock_getuid): + arglist = ['--no-validations', '--skip-package-updates'] + verifylist = [] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + # DisplayCommandBase.take_action() returns two tuples + self.cmd.take_action(parsed_args) mock_subprocess.assert_called_with( ['sudo', '--preserve-env', 'openstack', 'tripleo', 'deploy', '--standalone', '--standalone-role', 'Undercloud', '--stack', @@ -648,21 +702,13 @@ class TestUndercloudUpgrade(TestPluginV1): mock_subprocess, mock_wr, mock_os, mock_copy, mock_user): - arglist = ['--no-validations'] + arglist = ['--no-validations', '--skip-package-updates'] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) - mock_run_command.assert_called_with( - ['sudo', 'yum', 'upgrade', '-y', - 'openstack-tripleo-heat-templates', - 'openstack-tripleo-validations', - 'tripleo-ansible'], - name='Update extra packages' - ) - mock_subprocess.assert_called_with( ['sudo', '--preserve-env', 'openstack', 'tripleo', 'deploy', '--standalone', '--standalone-role', 'Undercloud', '--stack', @@ -722,21 +768,13 @@ class TestUndercloudUpgrade(TestPluginV1): mock_subprocess, mock_wr, mock_os, mock_copy, mock_user): - arglist = ['--no-validations'] + arglist = ['--no-validations', '--skip-package-updates'] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) - mock_run_command.assert_called_with( - ['sudo', 'yum', 'upgrade', '-y', - 'openstack-tripleo-heat-templates', - 'openstack-tripleo-validations', - 'tripleo-ansible'], - name='Update extra packages' - ) - mock_subprocess.assert_called_with( ['sudo', '--preserve-env', 'openstack', 'tripleo', 'deploy', '--standalone', '--standalone-role', 'Undercloud', '--stack', @@ -795,22 +833,14 @@ class TestUndercloudUpgrade(TestPluginV1): def test_undercloud_upgrade_with_heat_and_yes(self, mock_run_command, mock_subprocess, mock_wr, mock_os, - mock_user, mock_copy): - arglist = ['--no-validations', '-y'] + mock_copy, mock_user): + arglist = ['--no-validations', '-y', '--skip-package-updates'] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) # DisplayCommandBase.take_action() returns two tuples self.cmd.take_action(parsed_args) - mock_run_command.assert_called_with( - ['sudo', 'yum', 'upgrade', '-y', - 'openstack-tripleo-heat-templates', - 'openstack-tripleo-validations', - 'tripleo-ansible'], - name='Update extra packages' - ) - mock_subprocess.assert_called_with( ['sudo', '--preserve-env', 'openstack', 'tripleo', 'deploy', '--standalone', '--standalone-role', 'Undercloud', '--stack', @@ -871,7 +901,7 @@ class TestUndercloudUpgrade(TestPluginV1): mock_subprocess, mock_wr, mock_os, mock_copy, mock_user): - arglist = ['--no-validations'] + arglist = ['--no-validations', '--skip-package-updates'] verifylist = [] parsed_args = self.check_parser(self.cmd, arglist, verifylist) @@ -881,14 +911,6 @@ class TestUndercloudUpgrade(TestPluginV1): self.cmd.take_action(parsed_args) self.cmd.app_args.verbose_level = old_verbose - mock_run_command.assert_called_with( - ['sudo', 'yum', 'upgrade', '-y', - 'openstack-tripleo-heat-templates', - 'openstack-tripleo-validations', - 'tripleo-ansible'], - name='Update extra packages' - ) - mock_subprocess.assert_called_with( ['sudo', '--preserve-env', 'openstack', 'tripleo', 'deploy', '--standalone', '--standalone-role', 'Undercloud', '--stack', diff --git a/tripleoclient/v1/undercloud.py b/tripleoclient/v1/undercloud.py index 3fc2f6aec..fa60bf085 100644 --- a/tripleoclient/v1/undercloud.py +++ b/tripleoclient/v1/undercloud.py @@ -19,6 +19,7 @@ import argparse import logging import os import subprocess +import sys from openstackclient.i18n import _ @@ -165,41 +166,55 @@ class UpgradeUndercloud(InstallUndercloud): log = logging.getLogger(__name__ + ".UpgradeUndercloud") osloconfig = cfg.CONF + def get_parser(self, prog_name): + parser = super(UpgradeUndercloud, self).get_parser(prog_name) + parser.add_argument('--skip-package-updates', + dest='skip_package_updates', + action='store_true', + default=False, + help=_("Flag to skip the package update when " + "performing upgrades and updates"), + ) + return parser + def _update_extra_packages(self, packages=[], dry_run=False): """Necessary packages to be updated before undercloud upgrade.""" - cmd = [] - if packages: - cmd = ['sudo', 'yum', 'upgrade', '-y'] + packages + if not packages: + return - if cmd: - if not dry_run: - self.log.warning( - "Updating necessary packages: {}\n{}".format( - " ".join(packages), - ("Note that tripleoclient and tripleo-common " - "still need to be updated manually.") - ), - ) - output = utils.run_command(cmd, name="Update extra packages") - self.log.warning("{}".format(output)) - else: - self.log.warning( - "Would update necessary packages: {}".format(" ".join(cmd)) - ) + cmd = ['sudo', 'yum', 'upgrade', '-y'] + packages - def take_action(self, parsed_args): - # Fetch configuration used to add logging to a file - utils.load_config(self.osloconfig, constants.UNDERCLOUD_CONF_PATH) - utils.configure_logging(self.log, self.app_args.verbose_level, - self.osloconfig['undercloud_log_file']) - self.log.debug("take action(%s)" % parsed_args) + if not dry_run: + self.log.warning("Updating necessary packages: {}".format( + " ".join(packages))) + output = utils.run_command(cmd, name="Update extra packages") + self.log.warning("{}".format(output)) + else: + self.log.warning("Would update necessary packages: {}".format( + " ".join(cmd))) - utils.ensure_run_as_normal_user() - - self._update_extra_packages(constants.UNDERCLOUD_EXTRA_PACKAGES, - parsed_args.dry_run) + def _invoke_self(self, parsed_args): + cmd = ['openstack', 'undercloud', 'upgrade', '--skip-package-updates'] + opts = {'force_stack_update': '--force-stack-update', + 'no_validations': '--no-validations', + 'inflight': '--inflight-validations', + 'dry_run': '--dry-run', + 'yes': '--yes'} + args = vars(parsed_args) + for k, v in opts.items(): + if args[k]: + cmd.append(v) + # handle --debug + if self.app_args.verbose_level > 1: + cmd.append('--debug') + try: + subprocess.check_call(cmd) + except Exception as e: + self.log.error(e) + raise exceptions.DeploymentError(e) + def _run_upgrade(self, parsed_args): cmd = undercloud_config.\ prepare_undercloud_deploy( upgrade=True, @@ -224,3 +239,27 @@ class UpgradeUndercloud(InstallUndercloud): self.log.error(UNDERCLOUD_FAILURE_MESSAGE) self.log.error(e) raise exceptions.DeploymentError(e) + + def take_action(self, parsed_args): + # Fetch configuration used to add logging to a file + utils.load_config(self.osloconfig, constants.UNDERCLOUD_CONF_PATH) + utils.configure_logging(self.log, self.app_args.verbose_level, + self.osloconfig['undercloud_log_file']) + self.log.debug("take action(%s)" % parsed_args) + + utils.ensure_run_as_normal_user() + + if not parsed_args.skip_package_updates: + print("executable is {}".format(sys.executable)) + if ('python3' in sys.executable): + pyver = '3' + else: + pyver = '2' + client_pkgs = [ + "python{}-tripleoclient".format(pyver), + ] + pkgs = client_pkgs + constants.UNDERCLOUD_EXTRA_PACKAGES + self._update_extra_packages(pkgs, parsed_args.dry_run) + self._invoke_self(parsed_args) + else: + self._run_upgrade(parsed_args)