Add immutable property attribute

Currently resources support only in-place update or (default)
UpdateReplace. In order to support AWS "Updates are not supported"
feature, this new property attribute is added, with default value False.
It is going to be used for denying an UpdateReplace
behavior of the resource (similar to update_allowed currently).

Change-Id: Ie94241630d9595b58ff6b84bcb94459b0785fc03
Implements: blueprint implement-aws-updates-not-supported
This commit is contained in:
Pavlo Shchelokovskyy 2014-07-11 17:02:25 +03:00
parent 8249ed170f
commit c4aec27bda
3 changed files with 58 additions and 5 deletions

View File

@ -24,10 +24,12 @@ SCHEMA_KEYS = (
REQUIRED, IMPLEMENTED, DEFAULT, TYPE, SCHEMA, REQUIRED, IMPLEMENTED, DEFAULT, TYPE, SCHEMA,
ALLOWED_PATTERN, MIN_VALUE, MAX_VALUE, ALLOWED_VALUES, ALLOWED_PATTERN, MIN_VALUE, MAX_VALUE, ALLOWED_VALUES,
MIN_LENGTH, MAX_LENGTH, DESCRIPTION, UPDATE_ALLOWED, MIN_LENGTH, MAX_LENGTH, DESCRIPTION, UPDATE_ALLOWED,
IMMUTABLE,
) = ( ) = (
'Required', 'Implemented', 'Default', 'Type', 'Schema', 'Required', 'Implemented', 'Default', 'Type', 'Schema',
'AllowedPattern', 'MinValue', 'MaxValue', 'AllowedValues', 'AllowedPattern', 'MinValue', 'MaxValue', 'AllowedValues',
'MinLength', 'MaxLength', 'Description', 'UpdateAllowed', 'MinLength', 'MaxLength', 'Description', 'UpdateAllowed',
'Immutable',
) )
@ -42,10 +44,10 @@ class Schema(constr.Schema):
KEYS = ( KEYS = (
TYPE, DESCRIPTION, DEFAULT, SCHEMA, REQUIRED, CONSTRAINTS, TYPE, DESCRIPTION, DEFAULT, SCHEMA, REQUIRED, CONSTRAINTS,
UPDATE_ALLOWED UPDATE_ALLOWED, IMMUTABLE,
) = ( ) = (
'type', 'description', 'default', 'schema', 'required', 'constraints', 'type', 'description', 'default', 'schema', 'required', 'constraints',
'update_allowed' 'update_allowed', 'immutable',
) )
def __init__(self, data_type, description=None, def __init__(self, data_type, description=None,
@ -53,11 +55,13 @@ class Schema(constr.Schema):
required=False, constraints=None, required=False, constraints=None,
implemented=True, implemented=True,
update_allowed=False, update_allowed=False,
immutable=False,
support_status=support.SupportStatus()): support_status=support.SupportStatus()):
super(Schema, self).__init__(data_type, description, default, super(Schema, self).__init__(data_type, description, default,
schema, required, constraints) schema, required, constraints)
self.implemented = implemented self.implemented = implemented
self.update_allowed = update_allowed self.update_allowed = update_allowed
self.immutable = immutable
self.support_status = support_status self.support_status = support_status
# validate structural correctness of schema itself # validate structural correctness of schema itself
self.validate() self.validate()
@ -119,7 +123,8 @@ class Schema(constr.Schema):
required=schema_dict.get(REQUIRED, False), required=schema_dict.get(REQUIRED, False),
constraints=list(constraints()), constraints=list(constraints()),
implemented=schema_dict.get(IMPLEMENTED, True), 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 @classmethod
def from_parameter(cls, param): def from_parameter(cls, param):
@ -145,11 +150,14 @@ class Schema(constr.Schema):
description=param.description, description=param.description,
required=param.required, required=param.required,
constraints=param.constraints, constraints=param.constraints,
update_allowed=True) update_allowed=True,
immutable=False)
def __getitem__(self, key): def __getitem__(self, key):
if key == self.UPDATE_ALLOWED: if key == self.UPDATE_ALLOWED:
return self.update_allowed return self.update_allowed
elif key == self.IMMUTABLE:
return self.immutable
else: else:
return super(Schema, self).__getitem__(key) return super(Schema, self).__getitem__(key)
@ -182,6 +190,9 @@ class Property(object):
def update_allowed(self): def update_allowed(self):
return self.schema.update_allowed return self.schema.update_allowed
def immutable(self):
return self.schema.immutable
def has_default(self): def has_default(self):
return self.schema.default is not None return self.schema.default is not None
@ -317,6 +328,16 @@ class Properties(collections.Mapping):
def validate(self, with_value=True): def validate(self, with_value=True):
for (key, prop) in self.props.items(): 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: if with_value:
try: try:
self._get_property_value(key, validate=True) self._get_property_value(key, validate=True)

View File

@ -1975,6 +1975,7 @@ class StackServiceTest(HeatTestCase):
'type': 'string', 'type': 'string',
'required': False, 'required': False,
'update_allowed': False, 'update_allowed': False,
'immutable': False,
}, },
}, },
'attributes': { 'attributes': {

View File

@ -31,6 +31,7 @@ class PropertySchemaTest(testtools.TestCase):
'default': 'wibble', 'default': 'wibble',
'required': True, 'required': True,
'update_allowed': False, 'update_allowed': False,
'immutable': False,
'constraints': [ 'constraints': [
{'length': {'min': 4, 'max': 8}}, {'length': {'min': 4, 'max': 8}},
] ]
@ -51,13 +52,15 @@ class PropertySchemaTest(testtools.TestCase):
'default': 'wibble', 'default': 'wibble',
'required': True, 'required': True,
'update_allowed': False, 'update_allowed': False,
'immutable': False,
'constraints': [ 'constraints': [
{'length': {'min': 4, 'max': 8}}, {'length': {'min': 4, 'max': 8}},
] ]
} }
}, },
'required': False, 'required': False,
'update_allowed': False 'update_allowed': False,
'immutable': False,
} }
s = properties.Schema(properties.Schema.STRING, 'A string', s = properties.Schema(properties.Schema.STRING, 'A string',
default='wibble', required=True, default='wibble', required=True,
@ -76,6 +79,7 @@ class PropertySchemaTest(testtools.TestCase):
'default': 'wibble', 'default': 'wibble',
'required': True, 'required': True,
'update_allowed': False, 'update_allowed': False,
'immutable': False,
'constraints': [ 'constraints': [
{'length': {'min': 4, 'max': 8}}, {'length': {'min': 4, 'max': 8}},
] ]
@ -83,6 +87,7 @@ class PropertySchemaTest(testtools.TestCase):
}, },
'required': False, 'required': False,
'update_allowed': False, 'update_allowed': False,
'immutable': False,
} }
s = properties.Schema(properties.Schema.STRING, 'A string', s = properties.Schema(properties.Schema.STRING, 'A string',
default='wibble', required=True, default='wibble', required=True,
@ -106,6 +111,7 @@ class PropertySchemaTest(testtools.TestCase):
'default': 'wibble', 'default': 'wibble',
'required': True, 'required': True,
'update_allowed': False, 'update_allowed': False,
'immutable': False,
'constraints': [ 'constraints': [
{'length': {'min': 4, 'max': 8}}, {'length': {'min': 4, 'max': 8}},
] ]
@ -113,10 +119,12 @@ class PropertySchemaTest(testtools.TestCase):
}, },
'required': False, 'required': False,
'update_allowed': False, 'update_allowed': False,
'immutable': False,
} }
}, },
'required': False, 'required': False,
'update_allowed': False, 'update_allowed': False,
'immutable': False,
} }
s = properties.Schema(properties.Schema.STRING, 'A string', s = properties.Schema(properties.Schema.STRING, 'A string',
default='wibble', required=True, default='wibble', required=True,
@ -164,6 +172,7 @@ class PropertySchemaTest(testtools.TestCase):
self.assertEqual('wibble', s.default) self.assertEqual('wibble', s.default)
self.assertTrue(s.required) self.assertTrue(s.required)
self.assertEqual(3, len(s.constraints)) self.assertEqual(3, len(s.constraints))
self.assertFalse(s.immutable)
def test_from_legacy_min_length(self): def test_from_legacy_min_length(self):
s = properties.Schema.from_legacy({ s = properties.Schema.from_legacy({
@ -1136,6 +1145,7 @@ class PropertiesTest(testtools.TestCase):
"description": "The WordPress database admin account username", "description": "The WordPress database admin account username",
"required": False, "required": False,
'update_allowed': True, 'update_allowed': True,
'immutable': False,
"constraints": [ "constraints": [
{"length": {"min": 1, "max": 16}, {"length": {"min": 1, "max": 16},
"description": "must begin with a letter and contain " "description": "must begin with a letter and contain "
@ -1150,6 +1160,7 @@ class PropertiesTest(testtools.TestCase):
"description": "Distribution of choice", "description": "Distribution of choice",
"required": False, "required": False,
'update_allowed': True, 'update_allowed': True,
'immutable': False,
"constraints": [ "constraints": [
{"allowed_values": ["F18", "F17", "U10", {"allowed_values": ["F18", "F17", "U10",
"RHEL-6.1", "RHEL-6.2", "RHEL-6.3"]} "RHEL-6.1", "RHEL-6.2", "RHEL-6.3"]}
@ -1160,6 +1171,7 @@ class PropertiesTest(testtools.TestCase):
"description": "WebServer EC2 instance type", "description": "WebServer EC2 instance type",
"required": False, "required": False,
'update_allowed': True, 'update_allowed': True,
'immutable': False,
"constraints": [ "constraints": [
{"allowed_values": ["t1.micro", {"allowed_values": ["t1.micro",
"m1.small", "m1.small",
@ -1179,6 +1191,7 @@ class PropertiesTest(testtools.TestCase):
"description": "Root password for MySQL", "description": "Root password for MySQL",
"required": False, "required": False,
'update_allowed': True, 'update_allowed': True,
'immutable': False,
"constraints": [ "constraints": [
{"length": {"min": 1, "max": 41}, {"length": {"min": 1, "max": 41},
"description": "must contain only alphanumeric " "description": "must contain only alphanumeric "
@ -1194,12 +1207,14 @@ class PropertiesTest(testtools.TestCase):
"SSH access to the instances"), "SSH access to the instances"),
"required": True, "required": True,
'update_allowed': True, 'update_allowed': True,
'immutable': False,
}, },
"DBPassword": { "DBPassword": {
"type": "string", "type": "string",
"description": "The WordPress database admin account password", "description": "The WordPress database admin account password",
"required": False, "required": False,
'update_allowed': True, 'update_allowed': True,
'immutable': False,
"constraints": [ "constraints": [
{"length": {"min": 1, "max": 41}, {"length": {"min": 1, "max": 41},
"description": "must contain only alphanumeric " "description": "must contain only alphanumeric "
@ -1214,6 +1229,7 @@ class PropertiesTest(testtools.TestCase):
"description": "The WordPress database name", "description": "The WordPress database name",
"required": False, "required": False,
'update_allowed': True, 'update_allowed': True,
'immutable': False,
"constraints": [ "constraints": [
{"length": {"min": 1, "max": 64}, {"length": {"min": 1, "max": 64},
"description": "must begin with a letter and contain " "description": "must begin with a letter and contain "
@ -1319,12 +1335,14 @@ class PropertiesTest(testtools.TestCase):
"SSH access to the instances"), "SSH access to the instances"),
"required": True, "required": True,
'update_allowed': True, 'update_allowed': True,
'immutable': False,
}, },
"InstanceType": { "InstanceType": {
"type": "string", "type": "string",
"description": "WebServer EC2 instance type", "description": "WebServer EC2 instance type",
"required": False, "required": False,
'update_allowed': True, 'update_allowed': True,
'immutable': False,
"constraints": [ "constraints": [
{"allowed_values": ["t1.micro", "m1.small", "m1.large", {"allowed_values": ["t1.micro", "m1.small", "m1.large",
"m1.xlarge", "m2.xlarge", "m2.2xlarge", "m1.xlarge", "m2.xlarge", "m2.2xlarge",
@ -1338,6 +1356,7 @@ class PropertiesTest(testtools.TestCase):
"description": "Distribution of choice", "description": "Distribution of choice",
"required": False, "required": False,
'update_allowed': True, 'update_allowed': True,
'immutable': False,
"constraints": [ "constraints": [
{"allowed_values": ["F18", "F17", "U10", {"allowed_values": ["F18", "F17", "U10",
"RHEL-6.1", "RHEL-6.2", "RHEL-6.3"], "RHEL-6.1", "RHEL-6.2", "RHEL-6.3"],
@ -1349,6 +1368,7 @@ class PropertiesTest(testtools.TestCase):
"description": "The WordPress database name", "description": "The WordPress database name",
"required": False, "required": False,
'update_allowed': True, 'update_allowed': True,
'immutable': False,
"constraints": [ "constraints": [
{"length": {"min": 1, "max": 64}, {"length": {"min": 1, "max": 64},
"description": "Length must be between 1 and 64"}, "description": "Length must be between 1 and 64"},
@ -1362,6 +1382,7 @@ class PropertiesTest(testtools.TestCase):
"description": "The WordPress database admin account username", "description": "The WordPress database admin account username",
"required": False, "required": False,
'update_allowed': True, 'update_allowed': True,
'immutable': False,
"constraints": [ "constraints": [
{"length": {"min": 1, "max": 16}, {"length": {"min": 1, "max": 16},
"description": "Length must be between 1 and 16"}, "description": "Length must be between 1 and 16"},
@ -1375,6 +1396,7 @@ class PropertiesTest(testtools.TestCase):
"description": "The WordPress database admin account password", "description": "The WordPress database admin account password",
"required": False, "required": False,
'update_allowed': True, 'update_allowed': True,
'immutable': False,
"constraints": [ "constraints": [
{"length": {"min": 1, "max": 41}, {"length": {"min": 1, "max": 41},
"description": "Length must be between 1 and 41"}, "description": "Length must be between 1 and 41"},
@ -1388,6 +1410,7 @@ class PropertiesTest(testtools.TestCase):
"description": "Root password for MySQL", "description": "Root password for MySQL",
"required": False, "required": False,
'update_allowed': True, 'update_allowed': True,
'immutable': False,
"constraints": [ "constraints": [
{"length": {"min": 1, "max": 41}, {"length": {"min": 1, "max": 41},
"description": "Length must be between 1 and 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) properties.Properties.schema_to_parameters_and_properties(schema)
self.assertEqual({}, parameters) self.assertEqual({}, parameters)
self.assertEqual({}, props) 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)