diff --git a/heat/engine/resources/software_config/software_component.py b/heat/engine/resources/software_config/software_component.py index a63bf6f6f..952c3e8c1 100644 --- a/heat/engine/resources/software_config/software_component.py +++ b/heat/engine/resources/software_config/software_component.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +from heat.common import exception from heat.engine import constraints as constr from heat.engine import properties from heat.engine import resource @@ -135,6 +136,23 @@ class SoftwareComponent(sc.SoftwareConfig): if self.client_plugin().is_not_found(ex): return None + def validate(self): + '''Validate SoftwareComponent properties consistency.''' + super(SoftwareComponent, self).validate() + + # One lifecycle action (e.g. CREATE) can only be associated with one + # config; otherwise a way to define ordering would be required. + configs = self.properties.get(self.CONFIGS, []) + config_actions = set() + for config in configs: + actions = config.get(self.CONFIG_ACTIONS) + if any(action in config_actions for action in actions): + msg = _('Defining more than one configuration for the same ' + 'action in SoftwareComponent "%s" is not allowed.')\ + % self.name + raise exception.StackValidationFailed(message=msg) + config_actions.update(actions) + def resource_mapping(): return { diff --git a/heat/tests/test_software_component.py b/heat/tests/test_software_component.py index e63332cb0..b1af38a42 100644 --- a/heat/tests/test_software_component.py +++ b/heat/tests/test_software_component.py @@ -217,6 +217,44 @@ class SoftwareComponentValidationTest(HeatTestCase): err_msg='actions length (0) is out of range ' '(min: 1, max: None)') ), + ( + 'multiple_configs_per_action_single', + dict(snippet=''' + component: + type: OS::Heat::SoftwareComponent + properties: + configs: + - actions: [CREATE] + config: #!/bin/bash + tool: script + - actions: [CREATE] + config: #!/bin/bash + tool: script + ''', + err=exception.StackValidationFailed, + err_msg='Defining more than one configuration for the same ' + 'action in SoftwareComponent "component" is not ' + 'allowed.') + ), + ( + 'multiple_configs_per_action_overlapping_list', + dict(snippet=''' + component: + type: OS::Heat::SoftwareComponent + properties: + configs: + - actions: [CREATE, UPDATE, RESUME] + config: #!/bin/bash + tool: script + - actions: [UPDATE] + config: #!/bin/bash + tool: script + ''', + err=exception.StackValidationFailed, + err_msg='Defining more than one configuration for the same ' + 'action in SoftwareComponent "component" is not ' + 'allowed.') + ), ] def setUp(self):