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:
parent
8249ed170f
commit
c4aec27bda
@ -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)
|
||||||
|
@ -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': {
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user