diff --git a/heat/engine/resources/openstack/neutron/rbac_policy.py b/heat/engine/resources/openstack/neutron/rbac_policy.py index de67ecf540..451fcb0c51 100644 --- a/heat/engine/resources/openstack/neutron/rbac_policy.py +++ b/heat/engine/resources/openstack/neutron/rbac_policy.py @@ -13,6 +13,7 @@ from heat.common import exception from heat.common.i18n import _ +from heat.engine import constraints from heat.engine import properties from heat.engine.resources.openstack.neutron import neutron from heat.engine import support @@ -54,13 +55,17 @@ class RBACPolicy(neutron.NeutronResource): # Change it when neutron supports more function in the future. SUPPORTED_TYPES_ACTIONS = { OBJECT_NETWORK: [ACCESS_AS_SHARED, ACCESS_AS_EXTERNAL], - OBJECT_QOS_POLICY: [ACCESS_AS_SHARED]} + OBJECT_QOS_POLICY: [ACCESS_AS_SHARED], + } properties_schema = { OBJECT_TYPE: properties.Schema( properties.Schema.STRING, _('Type of the object that RBAC policy affects.'), required=True, + constraints=[ + constraints.AllowedValues(OBJECT_TYPE_KEYS) + ] ), TARGET_TENANT: properties.Schema( properties.Schema.STRING, @@ -70,8 +75,14 @@ class RBACPolicy(neutron.NeutronResource): ), ACTION: properties.Schema( properties.Schema.STRING, - _('Action for the RBAC policy.'), + _('Action for the RBAC policy. The allowed actions differ for ' + 'different object types - only %(network)s objects can have an ' + '%(external)s action.') % {'network': OBJECT_NETWORK, + 'external': ACCESS_AS_EXTERNAL}, required=True, + constraints=[ + constraints.AllowedValues(ACTION_KEYS) + ] ), OBJECT_ID: properties.Schema( properties.Schema.STRING, @@ -123,25 +134,26 @@ class RBACPolicy(neutron.NeutronResource): self.client().delete_rbac_policy(self.resource_id) def validate(self): - """Validate the provided params.""" + """Validate the provided properties.""" super(RBACPolicy, self).validate() action = self.properties[self.ACTION] obj_type = self.properties[self.OBJECT_TYPE] # Validate obj_type and action per SUPPORTED_TYPES_ACTIONS. - if obj_type not in self.SUPPORTED_TYPES_ACTIONS: - msg = (_("Invalid object_type: %(obj_type)s. " - "Valid object_type :%(value)s") % - {'obj_type': obj_type, - 'value': self.SUPPORTED_TYPES_ACTIONS.keys()}) - raise exception.StackValidationFailed(message=msg) if action not in self.SUPPORTED_TYPES_ACTIONS[obj_type]: - msg = (_("Invalid action %(action)s for object type " - "%(obj_type)s. Valid actions :%(value)s") % + valid_actions = ', '.join(self.SUPPORTED_TYPES_ACTIONS[obj_type]) + msg = (_('Invalid action "%(action)s" for object type ' + '%(obj_type)s. Valid actions: %(valid_actions)s') % {'action': action, 'obj_type': obj_type, - 'value': self.SUPPORTED_TYPES_ACTIONS[obj_type]}) - raise exception.StackValidationFailed(message=msg) + 'valid_actions': valid_actions}) + properties_section = self.properties.error_prefix[0] + path = [self.stack.t.RESOURCES, self.t.name, + self.stack.t.get_section_name(properties_section), + self.ACTION] + raise exception.StackValidationFailed(error='Property error', + path=path, + message=msg) def resource_mapping(): diff --git a/heat/tests/openstack/neutron/test_neutron_rbac_policy.py b/heat/tests/openstack/neutron/test_neutron_rbac_policy.py index a806c16216..b67c3b65f7 100644 --- a/heat/tests/openstack/neutron/test_neutron_rbac_policy.py +++ b/heat/tests/openstack/neutron/test_neutron_rbac_policy.py @@ -57,16 +57,13 @@ class RBACPolicyTest(common.HeatTestCase): def test_create_qos_policy_rbac(self): self._test_create(obj_type='qos_policy') - def _test_validate_invalid_action(self, + def _test_validate_invalid_action(self, msg, invalid_action='invalid', obj_type='network'): tpl = yaml.safe_load(inline_templates.RBAC_TEMPLATE) tpl['resources']['rbac']['properties']['action'] = invalid_action tpl['resources']['rbac']['properties']['object_type'] = obj_type self._create_stack(tmpl=yaml.safe_dump(tpl)) - msg = ("Invalid action %(action)s for object type %(type)s." % - {'action': invalid_action, - 'type': obj_type}) self.patchobject(type(self.rbac), 'is_service_available', return_value=(True, None)) @@ -75,21 +72,29 @@ class RBACPolicyTest(common.HeatTestCase): self.rbac.validate) def test_validate_action_for_network(self): - self._test_validate_invalid_action() + msg = ('Property error: resources.rbac.properties.action: ' + '"invalid" is not an allowed value ' + r'\[access_as_shared, access_as_external\]') + self._test_validate_invalid_action(msg) def test_validate_action_for_qos_policy(self): - self._test_validate_invalid_action( - obj_type='qos_policy') + msg = ('Property error: resources.rbac.properties.action: ' + '"invalid" is not an allowed value ' + r'\[access_as_shared, access_as_external\]') + self._test_validate_invalid_action(msg, obj_type='qos_policy') # we dont support access_as_external for qos_policy - self._test_validate_invalid_action( - obj_type='qos_policy', - invalid_action='access_as_external') + msg = ('Property error: resources.rbac.properties.action: ' + 'Invalid action "access_as_external" for object type ' + 'qos_policy. Valid actions: access_as_shared') + self._test_validate_invalid_action(msg, + obj_type='qos_policy', + invalid_action='access_as_external') def test_validate_invalid_type(self): tpl = yaml.safe_load(inline_templates.RBAC_TEMPLATE) tpl['resources']['rbac']['properties']['object_type'] = 'networks' self._create_stack(tmpl=yaml.safe_dump(tpl)) - msg = "Invalid object_type: networks. " + msg = '"networks" is not an allowed value' self.patchobject(type(self.rbac), 'is_service_available', return_value=(True, None))