diff --git a/heat/engine/properties.py b/heat/engine/properties.py index e241e994b..b6ad8bf72 100644 --- a/heat/engine/properties.py +++ b/heat/engine/properties.py @@ -24,10 +24,12 @@ SCHEMA_KEYS = ( REQUIRED, IMPLEMENTED, DEFAULT, TYPE, SCHEMA, ALLOWED_PATTERN, MIN_VALUE, MAX_VALUE, ALLOWED_VALUES, MIN_LENGTH, MAX_LENGTH, DESCRIPTION, UPDATE_ALLOWED, + IMMUTABLE, ) = ( 'Required', 'Implemented', 'Default', 'Type', 'Schema', 'AllowedPattern', 'MinValue', 'MaxValue', 'AllowedValues', 'MinLength', 'MaxLength', 'Description', 'UpdateAllowed', + 'Immutable', ) @@ -42,10 +44,10 @@ class Schema(constr.Schema): KEYS = ( TYPE, DESCRIPTION, DEFAULT, SCHEMA, REQUIRED, CONSTRAINTS, - UPDATE_ALLOWED + UPDATE_ALLOWED, IMMUTABLE, ) = ( 'type', 'description', 'default', 'schema', 'required', 'constraints', - 'update_allowed' + 'update_allowed', 'immutable', ) def __init__(self, data_type, description=None, @@ -53,11 +55,13 @@ class Schema(constr.Schema): required=False, constraints=None, implemented=True, update_allowed=False, + immutable=False, support_status=support.SupportStatus()): super(Schema, self).__init__(data_type, description, default, schema, required, constraints) self.implemented = implemented self.update_allowed = update_allowed + self.immutable = immutable self.support_status = support_status # validate structural correctness of schema itself self.validate() @@ -119,7 +123,8 @@ class Schema(constr.Schema): required=schema_dict.get(REQUIRED, False), constraints=list(constraints()), implemented=schema_dict.get(IMPLEMENTED, True), - update_allowed=schema_dict.get(UPDATE_ALLOWED, False)) + update_allowed=schema_dict.get(UPDATE_ALLOWED, False), + immutable=schema_dict.get(IMMUTABLE, False)) @classmethod def from_parameter(cls, param): @@ -145,11 +150,14 @@ class Schema(constr.Schema): description=param.description, required=param.required, constraints=param.constraints, - update_allowed=True) + update_allowed=True, + immutable=False) def __getitem__(self, key): if key == self.UPDATE_ALLOWED: return self.update_allowed + elif key == self.IMMUTABLE: + return self.immutable else: return super(Schema, self).__getitem__(key) @@ -182,6 +190,9 @@ class Property(object): def update_allowed(self): return self.schema.update_allowed + def immutable(self): + return self.schema.immutable + def has_default(self): return self.schema.default is not None @@ -317,6 +328,16 @@ class Properties(collections.Mapping): def validate(self, with_value=True): for (key, prop) in self.props.items(): + # check that update_allowed and immutable + # do not contradict each other + if prop.update_allowed() and prop.immutable(): + msg = _("Property %(prop)s: %(ua)s and %(im)s " + "cannot both be True") % { + 'prop': key, + 'ua': prop.schema.UPDATE_ALLOWED, + 'im': prop.schema.IMMUTABLE} + raise exception.InvalidSchemaError(message=msg) + if with_value: try: self._get_property_value(key, validate=True) diff --git a/heat/tests/test_engine_service.py b/heat/tests/test_engine_service.py index 4fbb64247..2d6a44f89 100644 --- a/heat/tests/test_engine_service.py +++ b/heat/tests/test_engine_service.py @@ -1975,6 +1975,7 @@ class StackServiceTest(HeatTestCase): 'type': 'string', 'required': False, 'update_allowed': False, + 'immutable': False, }, }, 'attributes': { diff --git a/heat/tests/test_properties.py b/heat/tests/test_properties.py index b11ec3ea2..63a47b9e7 100644 --- a/heat/tests/test_properties.py +++ b/heat/tests/test_properties.py @@ -31,6 +31,7 @@ class PropertySchemaTest(testtools.TestCase): 'default': 'wibble', 'required': True, 'update_allowed': False, + 'immutable': False, 'constraints': [ {'length': {'min': 4, 'max': 8}}, ] @@ -51,13 +52,15 @@ class PropertySchemaTest(testtools.TestCase): 'default': 'wibble', 'required': True, 'update_allowed': False, + 'immutable': False, 'constraints': [ {'length': {'min': 4, 'max': 8}}, ] } }, 'required': False, - 'update_allowed': False + 'update_allowed': False, + 'immutable': False, } s = properties.Schema(properties.Schema.STRING, 'A string', default='wibble', required=True, @@ -76,6 +79,7 @@ class PropertySchemaTest(testtools.TestCase): 'default': 'wibble', 'required': True, 'update_allowed': False, + 'immutable': False, 'constraints': [ {'length': {'min': 4, 'max': 8}}, ] @@ -83,6 +87,7 @@ class PropertySchemaTest(testtools.TestCase): }, 'required': False, 'update_allowed': False, + 'immutable': False, } s = properties.Schema(properties.Schema.STRING, 'A string', default='wibble', required=True, @@ -106,6 +111,7 @@ class PropertySchemaTest(testtools.TestCase): 'default': 'wibble', 'required': True, 'update_allowed': False, + 'immutable': False, 'constraints': [ {'length': {'min': 4, 'max': 8}}, ] @@ -113,10 +119,12 @@ class PropertySchemaTest(testtools.TestCase): }, 'required': False, 'update_allowed': False, + 'immutable': False, } }, 'required': False, 'update_allowed': False, + 'immutable': False, } s = properties.Schema(properties.Schema.STRING, 'A string', default='wibble', required=True, @@ -164,6 +172,7 @@ class PropertySchemaTest(testtools.TestCase): self.assertEqual('wibble', s.default) self.assertTrue(s.required) self.assertEqual(3, len(s.constraints)) + self.assertFalse(s.immutable) def test_from_legacy_min_length(self): s = properties.Schema.from_legacy({ @@ -1136,6 +1145,7 @@ class PropertiesTest(testtools.TestCase): "description": "The WordPress database admin account username", "required": False, 'update_allowed': True, + 'immutable': False, "constraints": [ {"length": {"min": 1, "max": 16}, "description": "must begin with a letter and contain " @@ -1150,6 +1160,7 @@ class PropertiesTest(testtools.TestCase): "description": "Distribution of choice", "required": False, 'update_allowed': True, + 'immutable': False, "constraints": [ {"allowed_values": ["F18", "F17", "U10", "RHEL-6.1", "RHEL-6.2", "RHEL-6.3"]} @@ -1160,6 +1171,7 @@ class PropertiesTest(testtools.TestCase): "description": "WebServer EC2 instance type", "required": False, 'update_allowed': True, + 'immutable': False, "constraints": [ {"allowed_values": ["t1.micro", "m1.small", @@ -1179,6 +1191,7 @@ class PropertiesTest(testtools.TestCase): "description": "Root password for MySQL", "required": False, 'update_allowed': True, + 'immutable': False, "constraints": [ {"length": {"min": 1, "max": 41}, "description": "must contain only alphanumeric " @@ -1194,12 +1207,14 @@ class PropertiesTest(testtools.TestCase): "SSH access to the instances"), "required": True, 'update_allowed': True, + 'immutable': False, }, "DBPassword": { "type": "string", "description": "The WordPress database admin account password", "required": False, 'update_allowed': True, + 'immutable': False, "constraints": [ {"length": {"min": 1, "max": 41}, "description": "must contain only alphanumeric " @@ -1214,6 +1229,7 @@ class PropertiesTest(testtools.TestCase): "description": "The WordPress database name", "required": False, 'update_allowed': True, + 'immutable': False, "constraints": [ {"length": {"min": 1, "max": 64}, "description": "must begin with a letter and contain " @@ -1319,12 +1335,14 @@ class PropertiesTest(testtools.TestCase): "SSH access to the instances"), "required": True, 'update_allowed': True, + 'immutable': False, }, "InstanceType": { "type": "string", "description": "WebServer EC2 instance type", "required": False, 'update_allowed': True, + 'immutable': False, "constraints": [ {"allowed_values": ["t1.micro", "m1.small", "m1.large", "m1.xlarge", "m2.xlarge", "m2.2xlarge", @@ -1338,6 +1356,7 @@ class PropertiesTest(testtools.TestCase): "description": "Distribution of choice", "required": False, 'update_allowed': True, + 'immutable': False, "constraints": [ {"allowed_values": ["F18", "F17", "U10", "RHEL-6.1", "RHEL-6.2", "RHEL-6.3"], @@ -1349,6 +1368,7 @@ class PropertiesTest(testtools.TestCase): "description": "The WordPress database name", "required": False, 'update_allowed': True, + 'immutable': False, "constraints": [ {"length": {"min": 1, "max": 64}, "description": "Length must be between 1 and 64"}, @@ -1362,6 +1382,7 @@ class PropertiesTest(testtools.TestCase): "description": "The WordPress database admin account username", "required": False, 'update_allowed': True, + 'immutable': False, "constraints": [ {"length": {"min": 1, "max": 16}, "description": "Length must be between 1 and 16"}, @@ -1375,6 +1396,7 @@ class PropertiesTest(testtools.TestCase): "description": "The WordPress database admin account password", "required": False, 'update_allowed': True, + 'immutable': False, "constraints": [ {"length": {"min": 1, "max": 41}, "description": "Length must be between 1 and 41"}, @@ -1388,6 +1410,7 @@ class PropertiesTest(testtools.TestCase): "description": "Root password for MySQL", "required": False, 'update_allowed': True, + 'immutable': False, "constraints": [ {"length": {"min": 1, "max": 41}, "description": "Length must be between 1 and 41"}, @@ -1671,3 +1694,11 @@ class PropertiesValidationTest(testtools.TestCase): properties.Properties.schema_to_parameters_and_properties(schema) self.assertEqual({}, parameters) self.assertEqual({}, props) + + def test_update_allowed_and_immutable_contradict(self): + schema = {'foo': properties.Schema( + properties.Schema.STRING, + update_allowed=True, + immutable=True)} + props = properties.Properties(schema, {}) + self.assertRaises(exception.InvalidSchemaError, props.validate)