diff --git a/tripleo_common/actions/heat_capabilities.py b/tripleo_common/actions/heat_capabilities.py index 7cf93a480..71d1bfff9 100644 --- a/tripleo_common/actions/heat_capabilities.py +++ b/tripleo_common/actions/heat_capabilities.py @@ -193,6 +193,23 @@ class UpdateCapabilitiesAction(base.TripleOAction): self.cache_delete(context, self.container, "tripleo.parameters.get") + # get the capabilities-map content to perform the environment ordering + try: + swift = self.get_object_client(context) + map_file = swift.get_object( + self.container, 'capabilities-map.yaml') + capabilities = yaml.safe_load(map_file[1]) + except swiftexceptions.ClientException as err: + err_msg = ("Error retrieving capabilities-map.yaml for " + "plan %s: %s" % (self.container, err)) + LOG.exception(err_msg) + return actions.Result(error=err_msg) + + ordered_env = plan_utils.apply_environments_order( + capabilities, env.get('environments', [])) + + env['environments'] = ordered_env + try: plan_utils.put_env(swift, env) except swiftexceptions.ClientException as err: diff --git a/tripleo_common/tests/actions/test_heat_capabilities.py b/tripleo_common/tests/actions/test_heat_capabilities.py index 1a5e672ae..87d1b2b00 100644 --- a/tripleo_common/tests/actions/test_heat_capabilities.py +++ b/tripleo_common/tests/actions/test_heat_capabilities.py @@ -246,7 +246,9 @@ class UpdateCapabilitiesActionTest(base.TestCase): - path: /path/to/overcloud-default-env.yaml - path: /path/to/ceph-storage-env.yaml """ - swift.get_object.return_value = ({}, mocked_env) + swift.get_object.side_effect = ( + ({}, mocked_env), + ({}, MAPPING_YAML_CONTENTS)) get_object_client_mock.return_value = swift environments = { @@ -287,7 +289,9 @@ class UpdateCapabilitiesActionTest(base.TestCase): - path: /path/to/overcloud-default-env.yaml - path: /path/to/ceph-storage-env.yaml """ - swift.get_object.return_value = ({}, mocked_env) + swift.get_object.side_effect = ( + ({}, mocked_env), + ({}, MAPPING_YAML_CONTENTS)) get_object_client_mock.return_value = swift environments = { diff --git a/tripleo_common/tests/utils/test_plan.py b/tripleo_common/tests/utils/test_plan.py index 62c3e36fe..59d3f23dd 100644 --- a/tripleo_common/tests/utils/test_plan.py +++ b/tripleo_common/tests/utils/test_plan.py @@ -37,6 +37,41 @@ passwords: ZaqarPassword: zzzz """ +USER_ENV_CONTENTS = """ +resource_registry: + OS::TripleO::Foo: bar.yaml +""" + +UNORDERED_PLAN_ENV_LIST = [ + {'path': 'overcloud-resource-registry-puppet.yaml'}, + {'path': 'environments/docker-ha.yaml'}, + {'path': 'environments/containers-default-parameters.yaml'}, + {'path': 'environments/docker.yaml'} +] + +CAPABILITIES_DICT = { + 'topics': [{ + 'environment_groups': [{ + 'environments': [{ + 'file': 'overcloud-resource-registry-puppet.yaml'} + ]}, { + 'environments': [{ + 'file': 'environments/docker.yaml', + 'requires': ['overcloud-resource-registry-puppet.yaml'] + }, { + 'file': 'environments/containers-default-parameters.yaml', + 'requires': ['overcloud-resource-registry-puppet.yaml', + 'environments/docker.yaml'] + }]}, { + 'environments': [{ + 'file': 'environments/docker-ha.yaml', + 'requires': ['overcloud-resource-registry-puppet.yaml', + 'environments/docker.yaml'] + }]} + ] + }] +} + class PlanTest(base.TestCase): def setUp(self): @@ -90,3 +125,15 @@ class PlanTest(base.TestCase): self.swift.get_object.assert_called() self.swift.put_object.assert_called() + + def test_apply_env_order(self): + ordered_plan_env_list = [ + {'path': 'overcloud-resource-registry-puppet.yaml'}, + {'path': 'environments/docker.yaml'}, + {'path': 'environments/docker-ha.yaml'}, + {'path': 'environments/containers-default-parameters.yaml'} + ] + + ordered_env = plan_utils.apply_environments_order( + CAPABILITIES_DICT, UNORDERED_PLAN_ENV_LIST) + self.assertEqual(ordered_env, ordered_plan_env_list) diff --git a/tripleo_common/utils/plan.py b/tripleo_common/utils/plan.py index 460ac0c5c..40a7339f2 100644 --- a/tripleo_common/utils/plan.py +++ b/tripleo_common/utils/plan.py @@ -56,3 +56,42 @@ def put_env(swift, env): constants.PLAN_ENVIRONMENT, yaml.safe_dump(env, default_flow_style=False) ) + + +def apply_environments_order(capabilities, environments): + """traverses the capabilities and orders the environment files + + by dependency rules defined in capabilities-map, so that parent + environments are first and children environments override these + parents + + :param capabilities: dict representing capabilities-map.yaml file + :param environments: list representing the environments section of the + plan-environments.yaml file + :return: list containing ordered environments + + """ + # get ordering rules from capabilities-map file + order_rules = {} + for topic in capabilities.get('topics', []): + for group in topic.get('environment_groups', []): + for environment in group.get('environments', []): + order_rules[environment['file']] = [] + if 'requires' in environment: + order_rules[environment['file']] \ + = environment.get('requires', []) + + # apply ordering rules + for e in environments: + path = e.get('path', '') + if path not in order_rules: + continue + path_pos = environments.index(e) + for requirement in order_rules[path]: + if {'path': requirement} in environments: + requirement_pos = environments.index({'path': requirement}) + if requirement_pos > path_pos: + item = environments.pop(requirement_pos) + environments.insert(path_pos, item) + + return environments