diff --git a/tripleoclient/tests/test_utils.py b/tripleoclient/tests/test_utils.py index 2d6e111e2..d4626ae85 100644 --- a/tripleoclient/tests/test_utils.py +++ b/tripleoclient/tests/test_utils.py @@ -1440,3 +1440,73 @@ class TestAnsibleSymlink(TestCase): def test_ansible_symlink_not_needed(self, mock_path, mock_cmd): utils.ansible_symlink() mock_cmd.assert_not_called() + + +class TestParseExtraVars(TestCase): + def test_simple_case_text_format(self): + input_parameter = ['key1=val1', 'key2=val2 key3=val3'] + expected = { + 'key1': 'val1', + 'key2': 'val2', + 'key3': 'val3' + } + result = utils.parse_extra_vars(input_parameter) + self.assertEqual(result, expected) + + def test_simple_case_json_format(self): + input_parameter = ['{"key1": "val1", "key2": "val2"}'] + expected = { + 'key1': 'val1', + 'key2': 'val2' + } + result = utils.parse_extra_vars(input_parameter) + self.assertEqual(result, expected) + + def test_multiple_format(self): + input_parameter = [ + 'key1=val1', 'key2=val2 key3=val3', + '{"key4": "val4", "key5": "val5"}'] + expected = { + 'key1': 'val1', + 'key2': 'val2', + 'key3': 'val3', + 'key4': 'val4', + 'key5': 'val5' + } + result = utils.parse_extra_vars(input_parameter) + self.assertEqual(result, expected) + + def test_same_key(self): + input_parameter = [ + 'key1=val1', 'key2=val2 key3=val3', + '{"key1": "other_value", "key5": "val5"}'] + expected = { + 'key1': 'other_value', + 'key2': 'val2', + 'key3': 'val3', + 'key5': 'val5' + } + result = utils.parse_extra_vars(input_parameter) + self.assertEqual(result, expected) + + def test_with_multiple_space(self): + input_parameter = ['key1=val1', ' key2=val2 key3=val3 '] + expected = { + 'key1': 'val1', + 'key2': 'val2', + 'key3': 'val3' + } + result = utils.parse_extra_vars(input_parameter) + self.assertEqual(result, expected) + + def test_invalid_string(self): + input_parameter = [ + 'key1=val1', 'key2=val2 key3=val3', + '{"key1": "other_value", "key5": "val5'] + self.assertRaises( + ValueError, utils.parse_extra_vars, input_parameter) + + def test_invalid_format(self): + input_parameter = ['key1 val1'] + self.assertRaises( + ValueError, utils.parse_extra_vars, input_parameter) diff --git a/tripleoclient/tests/v1/overcloud_external_update/test_overcloud_external_update.py b/tripleoclient/tests/v1/overcloud_external_update/test_overcloud_external_update.py index 63c5c9755..fba4d76b5 100644 --- a/tripleoclient/tests/v1/overcloud_external_update/test_overcloud_external_update.py +++ b/tripleoclient/tests/v1/overcloud_external_update/test_overcloud_external_update.py @@ -61,5 +61,38 @@ class TestOvercloudExternalUpdateRun(fakes.TestOvercloudExternalUpdateRun): node_user='tripleo-admin', tags='ceph', skip_tags='', - verbosity=1 + verbosity=1, + extra_vars={} + ) + + @mock.patch('tripleoclient.workflows.package_update.update_ansible', + autospec=True) + @mock.patch('os.path.expanduser') + @mock.patch('oslo_concurrency.processutils.execute') + @mock.patch('six.moves.builtins.open') + def test_update_with_user_and_extra_vars(self, mock_open, mock_execute, + mock_expanduser, update_ansible): + mock_expanduser.return_value = '/home/fake/' + argslist = ['--ssh-user', 'tripleo-admin', + '--extra-vars', 'key1=val1', + '--extra-vars', 'key2=val2'] + verifylist = [ + ('ssh_user', 'tripleo-admin'), + ('extra_vars', ['key1=val1', 'key2=val2']) + ] + + parsed_args = self.check_parser(self.cmd, argslist, verifylist) + with mock.patch('os.path.exists') as mock_exists: + mock_exists.return_value = True + self.cmd.take_action(parsed_args) + update_ansible.assert_called_once_with( + self.app.client_manager, + nodes='all', + inventory_file=mock_open().read(), + playbook='external_update_steps_playbook.yaml', + node_user='tripleo-admin', + tags='', + skip_tags='', + verbosity=1, + extra_vars={'key1': 'val1', 'key2': 'val2'} ) diff --git a/tripleoclient/tests/v1/overcloud_external_upgrade/test_overcloud_external_upgrade.py b/tripleoclient/tests/v1/overcloud_external_upgrade/test_overcloud_external_upgrade.py index 80d8bf70c..dc3e76c9f 100644 --- a/tripleoclient/tests/v1/overcloud_external_upgrade/test_overcloud_external_upgrade.py +++ b/tripleoclient/tests/v1/overcloud_external_upgrade/test_overcloud_external_upgrade.py @@ -61,5 +61,38 @@ class TestOvercloudExternalUpgradeRun(fakes.TestOvercloudExternalUpgradeRun): node_user='tripleo-admin', tags='ceph', skip_tags='', - verbosity=1 + verbosity=1, + extra_vars={} + ) + + @mock.patch('tripleoclient.workflows.package_update.update_ansible', + autospec=True) + @mock.patch('os.path.expanduser') + @mock.patch('oslo_concurrency.processutils.execute') + @mock.patch('six.moves.builtins.open') + def test_upgrade_with_user_and_extra_vars(self, mock_open, mock_execute, + mock_expanduser, update_ansible): + mock_expanduser.return_value = '/home/fake/' + argslist = ['--ssh-user', 'tripleo-admin', + '--extra-vars', 'key1=val1', + '--extra-vars', 'key2=val2'] + verifylist = [ + ('ssh_user', 'tripleo-admin'), + ('extra_vars', ['key1=val1', 'key2=val2']) + ] + + parsed_args = self.check_parser(self.cmd, argslist, verifylist) + with mock.patch('os.path.exists') as mock_exists: + mock_exists.return_value = True + self.cmd.take_action(parsed_args) + update_ansible.assert_called_once_with( + self.app.client_manager, + nodes='all', + inventory_file=mock_open().read(), + playbook='external_upgrade_steps_playbook.yaml', + node_user='tripleo-admin', + tags='', + skip_tags='', + verbosity=1, + extra_vars={'key1': 'val1', 'key2': 'val2'} ) diff --git a/tripleoclient/tests/v1/overcloud_ffwd_upgrade/test_overcloud_ffwd_upgrade.py b/tripleoclient/tests/v1/overcloud_ffwd_upgrade/test_overcloud_ffwd_upgrade.py index 936d080ce..6064ad6ad 100644 --- a/tripleoclient/tests/v1/overcloud_ffwd_upgrade/test_overcloud_ffwd_upgrade.py +++ b/tripleoclient/tests/v1/overcloud_ffwd_upgrade/test_overcloud_ffwd_upgrade.py @@ -169,7 +169,8 @@ class TestFFWDUpgradeRun(fakes.TestFFWDUpgradeRun): node_user='heat-admin', tags='', skip_tags='', - verbosity=1 + verbosity=1, + extra_vars=None ) @mock.patch('tripleoclient.workflows.package_update.update_ansible', @@ -195,7 +196,8 @@ class TestFFWDUpgradeRun(fakes.TestFFWDUpgradeRun): node_user='my-user', tags='', skip_tags='', - verbosity=1 + verbosity=1, + extra_vars=None ) @mock.patch('tripleoclient.workflows.package_update.update_ansible', diff --git a/tripleoclient/tests/v1/overcloud_update/test_overcloud_update.py b/tripleoclient/tests/v1/overcloud_update/test_overcloud_update.py index 046e18674..700ee8c89 100644 --- a/tripleoclient/tests/v1/overcloud_update/test_overcloud_update.py +++ b/tripleoclient/tests/v1/overcloud_update/test_overcloud_update.py @@ -151,7 +151,8 @@ class TestOvercloudUpdateRun(fakes.TestOvercloudUpdateRun): node_user='tripleo-admin', tags='', skip_tags='', - verbosity=1 + verbosity=1, + extra_vars=None ) @mock.patch('tripleoclient.workflows.package_update.update_ansible', @@ -182,7 +183,8 @@ class TestOvercloudUpdateRun(fakes.TestOvercloudUpdateRun): node_user='tripleo-admin', tags='', skip_tags='', - verbosity=1 + verbosity=1, + extra_vars=None ) @mock.patch('tripleoclient.workflows.package_update.update_ansible', @@ -212,7 +214,8 @@ class TestOvercloudUpdateRun(fakes.TestOvercloudUpdateRun): node_user='tripleo-admin', tags='', skip_tags='', - verbosity=1 + verbosity=1, + extra_vars=None ) @mock.patch('tripleoclient.workflows.package_update.update_ansible', diff --git a/tripleoclient/tests/v1/overcloud_upgrade/test_overcloud_upgrade.py b/tripleoclient/tests/v1/overcloud_upgrade/test_overcloud_upgrade.py index 8b0b17b0e..bceddc135 100644 --- a/tripleoclient/tests/v1/overcloud_upgrade/test_overcloud_upgrade.py +++ b/tripleoclient/tests/v1/overcloud_upgrade/test_overcloud_upgrade.py @@ -171,7 +171,8 @@ class TestOvercloudUpgradeRun(fakes.TestOvercloudUpgradeRun): node_user='tripleo-admin', tags='', skip_tags='', - verbosity=1 + verbosity=1, + extra_vars=None ) @mock.patch('tripleoclient.workflows.package_update.update_ansible', @@ -204,7 +205,8 @@ class TestOvercloudUpgradeRun(fakes.TestOvercloudUpgradeRun): node_user='tripleo-admin', tags='', skip_tags='validation', - verbosity=1 + verbosity=1, + extra_vars=None ) @mock.patch('tripleoclient.workflows.package_update.update_ansible', @@ -238,6 +240,7 @@ class TestOvercloudUpgradeRun(fakes.TestOvercloudUpgradeRun): tags='validation', skip_tags='', verbosity=1, + extra_vars=None ) @mock.patch('tripleoclient.workflows.package_update.update_ansible', @@ -268,7 +271,8 @@ class TestOvercloudUpgradeRun(fakes.TestOvercloudUpgradeRun): node_user='tripleo-admin', tags='', skip_tags='', - verbosity=1 + verbosity=1, + extra_vars=None ) @mock.patch('tripleoclient.workflows.package_update.update_ansible', @@ -299,7 +303,8 @@ class TestOvercloudUpgradeRun(fakes.TestOvercloudUpgradeRun): node_user='tripleo-admin', tags='', skip_tags='', - verbosity=1 + verbosity=1, + extra_vars=None ) @mock.patch('tripleoclient.workflows.package_update.update_ansible', @@ -332,7 +337,8 @@ class TestOvercloudUpgradeRun(fakes.TestOvercloudUpgradeRun): node_user='tripleo-admin', tags='', skip_tags='pre-upgrade,validation', - verbosity=1 + verbosity=1, + extra_vars=None ) @mock.patch('tripleoclient.workflows.package_update.update_ansible', diff --git a/tripleoclient/utils.py b/tripleoclient/utils.py index c28129b49..bd4a09b5d 100644 --- a/tripleoclient/utils.py +++ b/tripleoclient/utils.py @@ -1042,7 +1042,7 @@ def process_multiple_environments(created_env_files, tht_root, def run_update_ansible_action(log, clients, nodes, inventory, playbook, all_playbooks, action, ssh_user, tags='', - skip_tags='', verbosity='1'): + skip_tags='', verbosity='1', extra_vars=None): playbooks = [playbook] if playbook == "all": playbooks = all_playbooks @@ -1050,7 +1050,51 @@ def run_update_ansible_action(log, clients, nodes, inventory, playbook, log.debug("Running ansible playbook %s " % book) action.update_ansible(clients, nodes=nodes, inventory_file=inventory, playbook=book, node_user=ssh_user, tags=tags, - skip_tags=skip_tags, verbosity=verbosity) + skip_tags=skip_tags, verbosity=verbosity, + extra_vars=extra_vars) + + +def parse_extra_vars(extra_var_strings): + """Parses extra variables like Ansible would. + + Each element in extra_var_strings is like the raw value of -e + parameter of ansible-playbook command. It can either be very + simple 'key=val key2=val2' format or it can be '{ ... }' + representing a YAML/JSON object. + + The 'key=val key2=val2' format gets processed as if it was + '{"key": "val", "key2": "val2"}' object, and all YAML/JSON objects + get shallow-merged together in the order as they appear in + extra_var_strings, latter objects taking precedence over earlier + ones. + + :param extra_var_strings: unparsed value(s) of -e parameter(s) + :type extra_var_strings: list of strings + + :returns dict representing a merged object of all extra vars + """ + result = {} + + for extra_var_string in extra_var_strings: + invalid_yaml = False + + try: + parse_vars = yaml.safe_load(extra_var_string) + except yaml.YAMLError: + invalid_yaml = True + + if invalid_yaml or not isinstance(parse_vars, dict): + try: + parse_vars = dict( + item.split('=') for item in extra_var_string.split()) + except ValueError: + raise ValueError( + 'Invalid format for {extra_var_string}'.format( + extra_var_string=extra_var_string)) + + result.update(parse_vars) + + return result def prepend_environment(environment_files, templates_dir, environment): diff --git a/tripleoclient/v1/overcloud_external_update.py b/tripleoclient/v1/overcloud_external_update.py index 7360dbcb0..a7770d1f0 100644 --- a/tripleoclient/v1/overcloud_external_update.py +++ b/tripleoclient/v1/overcloud_external_update.py @@ -79,6 +79,10 @@ class ExternalUpdateRun(command.Command): '(default=Env: OVERCLOUD_STACK_NAME)'), default=utils.env('OVERCLOUD_STACK_NAME', default='overcloud')) + parser.add_argument( + '-e', '--extra-vars', dest='extra_vars', action='append', + help='Set additional variables as key=value or yaml/json', + default=[]) return parser @@ -93,11 +97,13 @@ class ExternalUpdateRun(command.Command): parsed_args.static_inventory, parsed_args.ssh_user, stack) limit_hosts = 'all' playbook = 'all' + extra_vars = oooutils.parse_extra_vars(parsed_args.extra_vars) + oooutils.run_update_ansible_action( self.log, clients, limit_hosts, inventory, playbook, constants.EXTERNAL_UPDATE_PLAYBOOKS, package_update, parsed_args.ssh_user, tags=parsed_args.tags, skip_tags=parsed_args.skip_tags, - verbosity=verbosity) + verbosity=verbosity, extra_vars=extra_vars) self.log.info("Completed Overcloud External Update Run.") diff --git a/tripleoclient/v1/overcloud_external_upgrade.py b/tripleoclient/v1/overcloud_external_upgrade.py index f645c6c18..fbd24dba0 100644 --- a/tripleoclient/v1/overcloud_external_upgrade.py +++ b/tripleoclient/v1/overcloud_external_upgrade.py @@ -79,6 +79,10 @@ class ExternalUpgradeRun(command.Command): '(default=Env: OVERCLOUD_STACK_NAME)'), default=utils.env('OVERCLOUD_STACK_NAME', default='overcloud')) + parser.add_argument( + '-e', '--extra-vars', dest='extra_vars', action='append', + help='Set additional variables as key=value or yaml/json', + default=[]) return parser @@ -93,11 +97,13 @@ class ExternalUpgradeRun(command.Command): parsed_args.static_inventory, parsed_args.ssh_user, stack) limit_hosts = 'all' playbook = 'all' + extra_vars = oooutils.parse_extra_vars(parsed_args.extra_vars) + oooutils.run_update_ansible_action( self.log, clients, limit_hosts, inventory, playbook, constants.EXTERNAL_UPGRADE_PLAYBOOKS, package_update, parsed_args.ssh_user, tags=parsed_args.tags, skip_tags=parsed_args.skip_tags, - verbosity=verbosity) + verbosity=verbosity, extra_vars=extra_vars) self.log.info("Completed Overcloud External Upgrade Run.")