# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from oslo_serialization import jsonutils import six from heat.common import exception from heat.engine.cfn import functions as cfn_funcs from heat.engine import constraints from heat.engine.hot import parameters as hot_param from heat.engine import parameters from heat.engine import properties from heat.engine import resources from heat.engine import support from heat.tests import common class PropertySchemaTest(common.HeatTestCase): def test_schema_all(self): d = { 'type': 'string', 'description': 'A string', 'default': 'wibble', 'required': False, 'update_allowed': False, 'immutable': False, 'constraints': [ {'length': {'min': 4, 'max': 8}}, ] } s = properties.Schema(properties.Schema.STRING, 'A string', default='wibble', constraints=[constraints.Length(4, 8)]) self.assertEqual(d, dict(s)) def test_schema_list_schema(self): d = { 'type': 'list', 'description': 'A list', 'schema': { '*': { 'type': 'string', 'description': 'A string', 'default': 'wibble', 'required': False, 'update_allowed': False, 'immutable': False, 'constraints': [ {'length': {'min': 4, 'max': 8}}, ] } }, 'required': False, 'update_allowed': False, 'immutable': False, } s = properties.Schema(properties.Schema.STRING, 'A string', default='wibble', constraints=[constraints.Length(4, 8)]) l = properties.Schema(properties.Schema.LIST, 'A list', schema=s) self.assertEqual(d, dict(l)) def test_schema_map_schema(self): d = { 'type': 'map', 'description': 'A map', 'schema': { 'Foo': { 'type': 'string', 'description': 'A string', 'default': 'wibble', 'required': False, 'update_allowed': False, 'immutable': False, 'constraints': [ {'length': {'min': 4, 'max': 8}}, ] } }, 'required': False, 'update_allowed': False, 'immutable': False, } s = properties.Schema(properties.Schema.STRING, 'A string', default='wibble', constraints=[constraints.Length(4, 8)]) m = properties.Schema(properties.Schema.MAP, 'A map', schema={'Foo': s}) self.assertEqual(d, dict(m)) def test_schema_nested_schema(self): d = { 'type': 'list', 'description': 'A list', 'schema': { '*': { 'type': 'map', 'description': 'A map', 'schema': { 'Foo': { 'type': 'string', 'description': 'A string', 'default': 'wibble', 'required': False, 'update_allowed': False, 'immutable': False, 'constraints': [ {'length': {'min': 4, 'max': 8}}, ] } }, 'required': False, 'update_allowed': False, 'immutable': False, } }, 'required': False, 'update_allowed': False, 'immutable': False, } s = properties.Schema(properties.Schema.STRING, 'A string', default='wibble', constraints=[constraints.Length(4, 8)]) m = properties.Schema(properties.Schema.MAP, 'A map', schema={'Foo': s}) l = properties.Schema(properties.Schema.LIST, 'A list', schema=m) self.assertEqual(d, dict(l)) def test_all_resource_schemata(self): for resource_type in resources.global_env().get_types(): for schema in six.itervalues(getattr(resource_type, 'properties_schema', {})): properties.Schema.from_legacy(schema) def test_from_legacy_idempotency(self): s = properties.Schema(properties.Schema.STRING) self.assertTrue(properties.Schema.from_legacy(s) is s) def test_from_legacy_minimal_string(self): s = properties.Schema.from_legacy({ 'Type': 'String', }) self.assertEqual(properties.Schema.STRING, s.type) self.assertIsNone(s.description) self.assertIsNone(s.default) self.assertFalse(s.required) self.assertEqual(0, len(s.constraints)) def test_from_legacy_string(self): s = properties.Schema.from_legacy({ 'Type': 'String', 'Description': 'a string', 'Default': 'wibble', 'Implemented': False, 'MinLength': 4, 'MaxLength': 8, 'AllowedValues': ['blarg', 'wibble'], 'AllowedPattern': '[a-z]*', }) self.assertEqual(properties.Schema.STRING, s.type) self.assertEqual('a string', s.description) self.assertEqual('wibble', s.default) self.assertFalse(s.required) self.assertEqual(3, len(s.constraints)) self.assertFalse(s.immutable) def test_from_legacy_min_length(self): s = properties.Schema.from_legacy({ 'Type': 'String', 'MinLength': 4, }) self.assertEqual(1, len(s.constraints)) c = s.constraints[0] self.assertIsInstance(c, constraints.Length) self.assertEqual(4, c.min) self.assertIsNone(c.max) def test_from_legacy_max_length(self): s = properties.Schema.from_legacy({ 'Type': 'String', 'MaxLength': 8, }) self.assertEqual(1, len(s.constraints)) c = s.constraints[0] self.assertIsInstance(c, constraints.Length) self.assertIsNone(c.min) self.assertEqual(8, c.max) def test_from_legacy_minmax_length(self): s = properties.Schema.from_legacy({ 'Type': 'String', 'MinLength': 4, 'MaxLength': 8, }) self.assertEqual(1, len(s.constraints)) c = s.constraints[0] self.assertIsInstance(c, constraints.Length) self.assertEqual(4, c.min) self.assertEqual(8, c.max) def test_from_legacy_minmax_string_length(self): s = properties.Schema.from_legacy({ 'Type': 'String', 'MinLength': '4', 'MaxLength': '8', }) self.assertEqual(1, len(s.constraints)) c = s.constraints[0] self.assertIsInstance(c, constraints.Length) self.assertEqual(4, c.min) self.assertEqual(8, c.max) def test_from_legacy_min_value(self): s = properties.Schema.from_legacy({ 'Type': 'Integer', 'MinValue': 4, }) self.assertEqual(1, len(s.constraints)) c = s.constraints[0] self.assertIsInstance(c, constraints.Range) self.assertEqual(4, c.min) self.assertIsNone(c.max) def test_from_legacy_max_value(self): s = properties.Schema.from_legacy({ 'Type': 'Integer', 'MaxValue': 8, }) self.assertEqual(1, len(s.constraints)) c = s.constraints[0] self.assertIsInstance(c, constraints.Range) self.assertIsNone(c.min) self.assertEqual(8, c.max) def test_from_legacy_minmax_value(self): s = properties.Schema.from_legacy({ 'Type': 'Integer', 'MinValue': 4, 'MaxValue': 8, }) self.assertEqual(1, len(s.constraints)) c = s.constraints[0] self.assertIsInstance(c, constraints.Range) self.assertEqual(4, c.min) self.assertEqual(8, c.max) def test_from_legacy_minmax_string_value(self): s = properties.Schema.from_legacy({ 'Type': 'Integer', 'MinValue': '4', 'MaxValue': '8', }) self.assertEqual(1, len(s.constraints)) c = s.constraints[0] self.assertIsInstance(c, constraints.Range) self.assertEqual(4, c.min) self.assertEqual(8, c.max) def test_from_legacy_allowed_values(self): s = properties.Schema.from_legacy({ 'Type': 'String', 'AllowedValues': ['blarg', 'wibble'], }) self.assertEqual(1, len(s.constraints)) c = s.constraints[0] self.assertIsInstance(c, constraints.AllowedValues) self.assertEqual(('blarg', 'wibble'), c.allowed) def test_from_legacy_allowed_pattern(self): s = properties.Schema.from_legacy({ 'Type': 'String', 'AllowedPattern': '[a-z]*', }) self.assertEqual(1, len(s.constraints)) c = s.constraints[0] self.assertIsInstance(c, constraints.AllowedPattern) self.assertEqual('[a-z]*', c.pattern) def test_from_legacy_list(self): l = properties.Schema.from_legacy({ 'Type': 'List', 'Default': ['wibble'], 'Schema': { 'Type': 'String', 'Default': 'wibble', 'MaxLength': 8, } }) self.assertEqual(properties.Schema.LIST, l.type) self.assertEqual(['wibble'], l.default) ss = l.schema[0] self.assertEqual(properties.Schema.STRING, ss.type) self.assertEqual('wibble', ss.default) def test_from_legacy_map(self): l = properties.Schema.from_legacy({ 'Type': 'Map', 'Schema': { 'foo': { 'Type': 'String', 'Default': 'wibble', } } }) self.assertEqual(properties.Schema.MAP, l.type) ss = l.schema['foo'] self.assertEqual(properties.Schema.STRING, ss.type) self.assertEqual('wibble', ss.default) def test_from_legacy_invalid_key(self): self.assertRaises(exception.InvalidSchemaError, properties.Schema.from_legacy, {'Type': 'String', 'Foo': 'Bar'}) def test_from_string_param(self): description = "WebServer EC2 instance type" allowed_values = ["t1.micro", "m1.small", "m1.large", "m1.xlarge", "m2.xlarge", "m2.2xlarge", "m2.4xlarge", "c1.medium", "c1.xlarge", "cc1.4xlarge"] constraint_desc = "Must be a valid EC2 instance type." param = parameters.Schema.from_dict('name', { "Type": "String", "Description": description, "Default": "m1.large", "AllowedValues": allowed_values, "ConstraintDescription": constraint_desc, }) schema = properties.Schema.from_parameter(param) self.assertEqual(properties.Schema.STRING, schema.type) self.assertEqual(description, schema.description) self.assertIsNone(schema.default) self.assertFalse(schema.required) self.assertEqual(1, len(schema.constraints)) allowed_constraint = schema.constraints[0] self.assertEqual(tuple(allowed_values), allowed_constraint.allowed) self.assertEqual(constraint_desc, allowed_constraint.description) def test_from_string_allowed_pattern(self): description = "WebServer EC2 instance type" allowed_pattern = "[A-Za-z0-9.]*" constraint_desc = "Must contain only alphanumeric characters." param = parameters.Schema.from_dict('name', { "Type": "String", "Description": description, "Default": "m1.large", "AllowedPattern": allowed_pattern, "ConstraintDescription": constraint_desc, }) schema = properties.Schema.from_parameter(param) self.assertEqual(properties.Schema.STRING, schema.type) self.assertEqual(description, schema.description) self.assertIsNone(schema.default) self.assertFalse(schema.required) self.assertEqual(1, len(schema.constraints)) allowed_constraint = schema.constraints[0] self.assertEqual(allowed_pattern, allowed_constraint.pattern) self.assertEqual(constraint_desc, allowed_constraint.description) def test_from_string_multi_constraints(self): description = "WebServer EC2 instance type" allowed_pattern = "[A-Za-z0-9.]*" constraint_desc = "Must contain only alphanumeric characters." param = parameters.Schema.from_dict('name', { "Type": "String", "Description": description, "Default": "m1.large", "MinLength": "7", "AllowedPattern": allowed_pattern, "ConstraintDescription": constraint_desc, }) schema = properties.Schema.from_parameter(param) self.assertEqual(properties.Schema.STRING, schema.type) self.assertEqual(description, schema.description) self.assertIsNone(schema.default) self.assertFalse(schema.required) self.assertEqual(2, len(schema.constraints)) len_constraint = schema.constraints[0] allowed_constraint = schema.constraints[1] self.assertEqual(7, len_constraint.min) self.assertIsNone(len_constraint.max) self.assertEqual(allowed_pattern, allowed_constraint.pattern) self.assertEqual(constraint_desc, allowed_constraint.description) def test_from_param_string_min_len(self): param = parameters.Schema.from_dict('name', { "Description": "WebServer EC2 instance type", "Type": "String", "Default": "m1.large", "MinLength": "7", }) schema = properties.Schema.from_parameter(param) self.assertFalse(schema.required) self.assertEqual(1, len(schema.constraints)) len_constraint = schema.constraints[0] self.assertEqual(7, len_constraint.min) self.assertIsNone(len_constraint.max) def test_from_param_string_max_len(self): param = parameters.Schema.from_dict('name', { "Description": "WebServer EC2 instance type", "Type": "String", "Default": "m1.large", "MaxLength": "11", }) schema = properties.Schema.from_parameter(param) self.assertFalse(schema.required) self.assertEqual(1, len(schema.constraints)) len_constraint = schema.constraints[0] self.assertIsNone(len_constraint.min) self.assertEqual(11, len_constraint.max) def test_from_param_string_min_max_len(self): param = parameters.Schema.from_dict('name', { "Description": "WebServer EC2 instance type", "Type": "String", "Default": "m1.large", "MinLength": "7", "MaxLength": "11", }) schema = properties.Schema.from_parameter(param) self.assertFalse(schema.required) self.assertEqual(1, len(schema.constraints)) len_constraint = schema.constraints[0] self.assertEqual(7, len_constraint.min) self.assertEqual(11, len_constraint.max) def test_from_param_no_default(self): param = parameters.Schema.from_dict('name', { "Description": "WebServer EC2 instance type", "Type": "String", }) schema = properties.Schema.from_parameter(param) self.assertTrue(schema.required) self.assertIsNone(schema.default) self.assertEqual(0, len(schema.constraints)) self.assertFalse(schema.allow_conversion) def test_from_number_param_min(self): default = "42" param = parameters.Schema.from_dict('name', { "Type": "Number", "Default": default, "MinValue": "10", }) schema = properties.Schema.from_parameter(param) self.assertEqual(properties.Schema.NUMBER, schema.type) self.assertIsNone(schema.default) self.assertFalse(schema.required) self.assertEqual(1, len(schema.constraints)) value_constraint = schema.constraints[0] self.assertEqual(10, value_constraint.min) self.assertIsNone(value_constraint.max) def test_from_number_param_max(self): default = "42" param = parameters.Schema.from_dict('name', { "Type": "Number", "Default": default, "MaxValue": "100", }) schema = properties.Schema.from_parameter(param) self.assertEqual(properties.Schema.NUMBER, schema.type) self.assertIsNone(schema.default) self.assertFalse(schema.required) self.assertEqual(1, len(schema.constraints)) value_constraint = schema.constraints[0] self.assertIsNone(value_constraint.min) self.assertEqual(100, value_constraint.max) def test_from_number_param_min_max(self): default = "42" param = parameters.Schema.from_dict('name', { "Type": "Number", "Default": default, "MinValue": "10", "MaxValue": "100", }) schema = properties.Schema.from_parameter(param) self.assertEqual(properties.Schema.NUMBER, schema.type) self.assertIsNone(schema.default) self.assertFalse(schema.required) self.assertEqual(1, len(schema.constraints)) value_constraint = schema.constraints[0] self.assertEqual(10, value_constraint.min) self.assertEqual(100, value_constraint.max) def test_from_number_param_allowed_vals(self): default = "42" constraint_desc = "The quick brown fox jumps over the lazy dog." param = parameters.Schema.from_dict('name', { "Type": "Number", "Default": default, "AllowedValues": ["10", "42", "100"], "ConstraintDescription": constraint_desc, }) schema = properties.Schema.from_parameter(param) self.assertEqual(properties.Schema.NUMBER, schema.type) self.assertIsNone(schema.default) self.assertFalse(schema.required) self.assertEqual(1, len(schema.constraints)) self.assertFalse(schema.allow_conversion) allowed_constraint = schema.constraints[0] self.assertEqual(('10', '42', '100'), allowed_constraint.allowed) self.assertEqual(constraint_desc, allowed_constraint.description) def test_from_list_param(self): param = parameters.Schema.from_dict('name', { "Type": "CommaDelimitedList", "Default": "foo,bar,baz" }) schema = properties.Schema.from_parameter(param) self.assertEqual(properties.Schema.LIST, schema.type) self.assertIsNone(schema.default) self.assertFalse(schema.required) self.assertFalse(schema.allow_conversion) def test_from_json_param(self): param = parameters.Schema.from_dict('name', { "Type": "Json", "Default": {"foo": "bar", "blarg": "wibble"} }) schema = properties.Schema.from_parameter(param) self.assertEqual(properties.Schema.MAP, schema.type) self.assertIsNone(schema.default) self.assertFalse(schema.required) self.assertTrue(schema.allow_conversion) class PropertyTest(common.HeatTestCase): def test_required_default(self): p = properties.Property({'Type': 'String'}) self.assertFalse(p.required()) def test_required_false(self): p = properties.Property({'Type': 'String', 'Required': False}) self.assertFalse(p.required()) def test_required_true(self): p = properties.Property({'Type': 'String', 'Required': True}) self.assertTrue(p.required()) def test_implemented_default(self): p = properties.Property({'Type': 'String'}) self.assertTrue(p.implemented()) def test_implemented_false(self): p = properties.Property({'Type': 'String', 'Implemented': False}) self.assertFalse(p.implemented()) def test_implemented_true(self): p = properties.Property({'Type': 'String', 'Implemented': True}) self.assertTrue(p.implemented()) def test_no_default(self): p = properties.Property({'Type': 'String'}) self.assertFalse(p.has_default()) def test_default(self): p = properties.Property({'Type': 'String', 'Default': 'wibble'}) self.assertEqual('wibble', p.default()) def test_type(self): p = properties.Property({'Type': 'String'}) self.assertEqual('String', p.type()) def test_bad_type(self): self.assertRaises(exception.InvalidSchemaError, properties.Property, {'Type': 'Fish'}) def test_bad_key(self): self.assertRaises(exception.InvalidSchemaError, properties.Property, {'Type': 'String', 'Foo': 'Bar'}) def test_string_pattern_good(self): schema = {'Type': 'String', 'AllowedPattern': '[a-z]*'} p = properties.Property(schema) self.assertEqual('foo', p.get_value('foo', True)) def test_string_pattern_bad_prefix(self): schema = {'Type': 'String', 'AllowedPattern': '[a-z]*'} p = properties.Property(schema) self.assertRaises(exception.StackValidationFailed, p.get_value, '1foo', True) def test_string_pattern_bad_suffix(self): schema = {'Type': 'String', 'AllowedPattern': '[a-z]*'} p = properties.Property(schema) self.assertRaises(exception.StackValidationFailed, p.get_value, 'foo1', True) def test_string_value_list_good(self): schema = {'Type': 'String', 'AllowedValues': ['foo', 'bar', 'baz']} p = properties.Property(schema) self.assertEqual('bar', p.get_value('bar', True)) def test_string_value_list_bad(self): schema = {'Type': 'String', 'AllowedValues': ['foo', 'bar', 'baz']} p = properties.Property(schema) self.assertRaises(exception.StackValidationFailed, p.get_value, 'blarg', True) def test_string_maxlength_good(self): schema = {'Type': 'String', 'MaxLength': '5'} p = properties.Property(schema) self.assertEqual('abcd', p.get_value('abcd', True)) def test_string_exceeded_maxlength(self): schema = {'Type': 'String', 'MaxLength': '5'} p = properties.Property(schema) self.assertRaises(exception.StackValidationFailed, p.get_value, 'abcdef', True) def test_string_length_in_range(self): schema = {'Type': 'String', 'MinLength': '5', 'MaxLength': '10'} p = properties.Property(schema) self.assertEqual('abcdef', p.get_value('abcdef', True)) def test_string_minlength_good(self): schema = {'Type': 'String', 'MinLength': '5'} p = properties.Property(schema) self.assertEqual('abcde', p.get_value('abcde', True)) def test_string_smaller_than_minlength(self): schema = {'Type': 'String', 'MinLength': '5'} p = properties.Property(schema) self.assertRaises(exception.StackValidationFailed, p.get_value, 'abcd', True) def test_int_good(self): schema = {'Type': 'Integer', 'MinValue': 3, 'MaxValue': 3} p = properties.Property(schema) self.assertEqual(3, p.get_value(3, True)) def test_int_bad(self): schema = {'Type': 'Integer'} p = properties.Property(schema) ex = self.assertRaises(TypeError, p.get_value, [1]) self.assertEqual("int() argument must be a string or a number, " "not 'list'", six.text_type(ex)) def test_str_from_int(self): schema = {'Type': 'String'} p = properties.Property(schema) self.assertEqual('3', p.get_value(3)) def test_str_from_bool(self): schema = {'Type': 'String'} p = properties.Property(schema) self.assertEqual('True', p.get_value(True)) def test_int_from_str_good(self): schema = {'Type': 'Integer'} p = properties.Property(schema) self.assertEqual(3, p.get_value('3')) def test_int_from_str_bad(self): schema = {'Type': 'Integer'} p = properties.Property(schema) ex = self.assertRaises(TypeError, p.get_value, '3a') self.assertEqual("Value '3a' is not an integer", six.text_type(ex)) def test_integer_low(self): schema = {'Type': 'Integer', 'MinValue': 4} p = properties.Property(schema) self.assertRaises(exception.StackValidationFailed, p.get_value, 3, True) def test_integer_high(self): schema = {'Type': 'Integer', 'MaxValue': 2} p = properties.Property(schema) self.assertRaises(exception.StackValidationFailed, p.get_value, 3, True) def test_integer_value_list_good(self): schema = {'Type': 'Integer', 'AllowedValues': [1, 3, 5]} p = properties.Property(schema) self.assertEqual(5, p.get_value(5), True) def test_integer_value_list_bad(self): schema = {'Type': 'Integer', 'AllowedValues': [1, 3, 5]} p = properties.Property(schema) self.assertRaises(exception.StackValidationFailed, p.get_value, 2, True) def test_number_good(self): schema = {'Type': 'Number', 'MinValue': '3', 'MaxValue': '3'} p = properties.Property(schema) self.assertEqual(3, p.get_value(3, True)) def test_numbers_from_strings(self): """Numbers can be converted from strings.""" schema = {'Type': 'Number', 'MinValue': '3', 'MaxValue': '3'} p = properties.Property(schema) self.assertEqual(3, p.get_value('3')) def test_number_value_list_good(self): schema = {'Type': 'Number', 'AllowedValues': [1, 3, 5]} p = properties.Property(schema) self.assertEqual(5, p.get_value('5', True)) def test_number_value_list_bad(self): schema = {'Type': 'Number', 'AllowedValues': ['1', '3', '5']} p = properties.Property(schema) self.assertRaises(exception.StackValidationFailed, p.get_value, '2', True) def test_number_low(self): schema = {'Type': 'Number', 'MinValue': '4'} p = properties.Property(schema) self.assertRaises(exception.StackValidationFailed, p.get_value, '3', True) def test_number_high(self): schema = {'Type': 'Number', 'MaxValue': '2'} p = properties.Property(schema) self.assertRaises(exception.StackValidationFailed, p.get_value, '3', True) def test_boolean_true(self): p = properties.Property({'Type': 'Boolean'}) self.assertIs(True, p.get_value('True')) self.assertIs(True, p.get_value('true')) self.assertIs(True, p.get_value(True)) def test_boolean_false(self): p = properties.Property({'Type': 'Boolean'}) self.assertIs(False, p.get_value('False')) self.assertIs(False, p.get_value('false')) self.assertIs(False, p.get_value(False)) def test_boolean_invalid(self): p = properties.Property({'Type': 'Boolean'}) self.assertRaises(ValueError, p.get_value, 'fish') def test_list_string(self): p = properties.Property({'Type': 'List'}) self.assertRaises(TypeError, p.get_value, 'foo') def test_list_good(self): p = properties.Property({'Type': 'List'}) self.assertEqual(['foo', 'bar'], p.get_value(['foo', 'bar'])) def test_list_dict(self): p = properties.Property({'Type': 'List'}) self.assertRaises(TypeError, p.get_value, {'foo': 'bar'}) def test_list_maxlength_good(self): schema = {'Type': 'List', 'MaxLength': '3'} p = properties.Property(schema) self.assertEqual(['1', '2'], p.get_value(['1', '2'], True)) def test_list_exceeded_maxlength(self): schema = {'Type': 'List', 'MaxLength': '2'} p = properties.Property(schema) self.assertRaises(exception.StackValidationFailed, p.get_value, ['1', '2', '3'], True) def test_list_length_in_range(self): schema = {'Type': 'List', 'MinLength': '2', 'MaxLength': '4'} p = properties.Property(schema) self.assertEqual(['1', '2', '3'], p.get_value(['1', '2', '3'], True)) def test_list_minlength_good(self): schema = {'Type': 'List', 'MinLength': '3'} p = properties.Property(schema) self.assertEqual(['1', '2', '3'], p.get_value(['1', '2', '3'], True)) def test_list_smaller_than_minlength(self): schema = {'Type': 'List', 'MinLength': '4'} p = properties.Property(schema) self.assertRaises(exception.StackValidationFailed, p.get_value, ['1', '2', '3'], True) def test_map_string(self): p = properties.Property({'Type': 'Map'}) self.assertRaises(TypeError, p.get_value, 'foo') def test_map_list(self): p = properties.Property({'Type': 'Map'}) self.assertRaises(TypeError, p.get_value, ['foo']) def test_map_allow_conversion(self): p = properties.Property({'Type': 'Map'}) p.schema.allow_conversion = True self.assertEqual('foo', p.get_value('foo')) self.assertEqual(jsonutils.dumps(['foo']), p.get_value(['foo'])) def test_map_schema_good(self): map_schema = {'valid': {'Type': 'Boolean'}} p = properties.Property({'Type': 'Map', 'Schema': map_schema}) self.assertEqual({'valid': True}, p.get_value({'valid': 'TRUE'})) def test_map_schema_bad_data(self): map_schema = {'valid': {'Type': 'Boolean'}} p = properties.Property({'Type': 'Map', 'Schema': map_schema}) ex = self.assertRaises(exception.StackValidationFailed, p.get_value, {'valid': 'fish'}, True) self.assertEqual('Property error : valid: "fish" is not a ' 'valid boolean', six.text_type(ex)) def test_map_schema_missing_data(self): map_schema = {'valid': {'Type': 'Boolean'}} p = properties.Property({'Type': 'Map', 'Schema': map_schema}) self.assertEqual({'valid': None}, p.get_value({})) def test_map_schema_missing_required_data(self): map_schema = {'valid': {'Type': 'Boolean', 'Required': True}} p = properties.Property({'Type': 'Map', 'Schema': map_schema}) ex = self.assertRaises(exception.StackValidationFailed, p.get_value, {}, True) self.assertEqual('Property error : Property valid not assigned', six.text_type(ex)) def test_list_schema_good(self): map_schema = {'valid': {'Type': 'Boolean'}} list_schema = {'Type': 'Map', 'Schema': map_schema} p = properties.Property({'Type': 'List', 'Schema': list_schema}) self.assertEqual([{'valid': True}, {'valid': False}], p.get_value([{'valid': 'TRUE'}, {'valid': 'False'}])) def test_list_schema_bad_data(self): map_schema = {'valid': {'Type': 'Boolean'}} list_schema = {'Type': 'Map', 'Schema': map_schema} p = properties.Property({'Type': 'List', 'Schema': list_schema}) ex = self.assertRaises(exception.StackValidationFailed, p.get_value, [{'valid': 'True'}, {'valid': 'fish'}], True) self.assertEqual('Property error : [1].valid: "fish" is not ' 'a valid boolean', six.text_type(ex)) def test_list_schema_int_good(self): list_schema = {'Type': 'Integer'} p = properties.Property({'Type': 'List', 'Schema': list_schema}) self.assertEqual([1, 2, 3], p.get_value([1, 2, 3])) def test_list_schema_int_bad_data(self): list_schema = {'Type': 'Integer'} p = properties.Property({'Type': 'List', 'Schema': list_schema}) ex = self.assertRaises(exception.StackValidationFailed, p.get_value, [42, 'fish'], True) self.assertEqual("Property error : [1]: Value 'fish' is not " "an integer", six.text_type(ex)) class PropertiesTest(common.HeatTestCase): def setUp(self): super(PropertiesTest, self).setUp() schema = { 'int': {'Type': 'Integer'}, 'string': {'Type': 'String'}, 'required_int': {'Type': 'Integer', 'Required': True}, 'bad_int': {'Type': 'Integer'}, 'missing': {'Type': 'Integer'}, 'defaulted': {'Type': 'Integer', 'Default': 1}, 'default_override': {'Type': 'Integer', 'Default': 1}, 'default_bool': {'Type': 'Boolean', 'Default': 'false'}, } data = { 'int': 21, 'string': 'foo', 'bad_int': 'foo', 'default_override': 21, } double = lambda d: d * 2 self.props = properties.Properties(schema, data, double, 'wibble') def test_integer_good(self): self.assertEqual(42, self.props['int']) def test_string_good(self): self.assertEqual('foofoo', self.props['string']) def test_bool_not_str(self): self.assertEqual(False, self.props['default_bool']) def test_missing_required(self): self.assertRaises(ValueError, self.props.get, 'required_int') def test_integer_bad(self): self.assertRaises(ValueError, self.props.get, 'bad_int') def test_missing(self): self.assertIsNone(self.props['missing']) def test_default(self): self.assertEqual(1, self.props['defaulted']) def test_default_override(self): self.assertEqual(42, self.props['default_override']) def test_bad_key(self): self.assertEqual('wibble', self.props.get('foo', 'wibble')) def test_none_string(self): schema = {'foo': {'Type': 'String'}} props = properties.Properties(schema, {'foo': None}) self.assertEqual('', props['foo']) def test_none_integer(self): schema = {'foo': {'Type': 'Integer'}} props = properties.Properties(schema, {'foo': None}) self.assertEqual(0, props['foo']) def test_none_number(self): schema = {'foo': {'Type': 'Number'}} props = properties.Properties(schema, {'foo': None}) self.assertEqual(0, props['foo']) def test_none_boolean(self): schema = {'foo': {'Type': 'Boolean'}} props = properties.Properties(schema, {'foo': None}) self.assertIs(False, props['foo']) def test_none_map(self): schema = {'foo': {'Type': 'Map'}} props = properties.Properties(schema, {'foo': None}) self.assertEqual({}, props['foo']) def test_none_list(self): schema = {'foo': {'Type': 'List'}} props = properties.Properties(schema, {'foo': None}) self.assertEqual([], props['foo']) def test_none_default_string(self): schema = {'foo': {'Type': 'String', 'Default': 'bar'}} props = properties.Properties(schema, {'foo': None}) self.assertEqual('bar', props['foo']) def test_none_default_integer(self): schema = {'foo': {'Type': 'Integer', 'Default': 42}} props = properties.Properties(schema, {'foo': None}) self.assertEqual(42, props['foo']) schema = {'foo': {'Type': 'Integer', 'Default': 0}} props = properties.Properties(schema, {'foo': None}) self.assertEqual(0, props['foo']) schema = {'foo': {'Type': 'Integer', 'Default': -273}} props = properties.Properties(schema, {'foo': None}) self.assertEqual(-273, props['foo']) def test_none_default_number(self): schema = {'foo': {'Type': 'Number', 'Default': 42.0}} props = properties.Properties(schema, {'foo': None}) self.assertEqual(42.0, props['foo']) schema = {'foo': {'Type': 'Number', 'Default': 0.0}} props = properties.Properties(schema, {'foo': None}) self.assertEqual(0.0, props['foo']) schema = {'foo': {'Type': 'Number', 'Default': -273.15}} props = properties.Properties(schema, {'foo': None}) self.assertEqual(-273.15, props['foo']) def test_none_default_boolean(self): schema = {'foo': {'Type': 'Boolean', 'Default': True}} props = properties.Properties(schema, {'foo': None}) self.assertIs(True, props['foo']) def test_none_default_map(self): schema = {'foo': {'Type': 'Map', 'Default': {'bar': 'baz'}}} props = properties.Properties(schema, {'foo': None}) self.assertEqual({'bar': 'baz'}, props['foo']) def test_none_default_list(self): schema = {'foo': {'Type': 'List', 'Default': ['one', 'two']}} props = properties.Properties(schema, {'foo': None}) self.assertEqual(['one', 'two'], props['foo']) def test_resolve_returns_none(self): schema = {'foo': {'Type': 'String', "MinLength": "5"}} def test_resolver(prop): return None self.patchobject(properties.Properties, '_find_deps_any_in_init').return_value = True props = properties.Properties(schema, {'foo': 'get_attr: [db, value]'}, test_resolver) try: self.assertIsNone(props.validate()) except exception.StackValidationFailed: self.fail("Constraints should not have been evaluated.") def test_resolve_ref_with_constraints(self): # create test custom constraint class IncorrectConstraint(constraints.BaseCustomConstraint): expected_exceptions = (Exception,) def validate_with_client(self, client, value): raise Exception("Test exception") class TestCustomConstraint(constraints.CustomConstraint): @property def custom_constraint(self): return IncorrectConstraint() # create schema with test constraint schema = { 'foo': properties.Schema( properties.Schema.STRING, constraints=[TestCustomConstraint('test_constraint')] ) } # define parameters for function def test_resolver(prop): return 'None' class rsrc(object): action = INIT = "INIT" class DummyStack(dict): pass stack = DummyStack(another_res=rsrc()) # define properties with function and constraint props = properties.Properties( schema, {'foo': cfn_funcs.ResourceRef( stack, 'get_resource', 'another_res')}, test_resolver) try: self.assertIsNone(props.validate()) except exception.StackValidationFailed: self.fail("Constraints should not have been evaluated.") def test_schema_from_params(self): params_snippet = { "DBUsername": { "Type": "String", "Description": "The WordPress database admin account username", "Default": "admin", "MinLength": "1", "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", "NoEcho": "true", "MaxLength": "16", "ConstraintDescription": ("must begin with a letter and " "contain only alphanumeric " "characters.") }, "KeyName": { "Type": "String", "Description": ("Name of an existing EC2 KeyPair to enable " "SSH access to the instances") }, "LinuxDistribution": { "Default": "F17", "Type": "String", "Description": "Distribution of choice", "AllowedValues": [ "F18", "F17", "U10", "RHEL-6.1", "RHEL-6.2", "RHEL-6.3" ] }, "DBPassword": { "Type": "String", "Description": "The WordPress database admin account password", "Default": "admin", "MinLength": "1", "AllowedPattern": "[a-zA-Z0-9]*", "NoEcho": "true", "MaxLength": "41", "ConstraintDescription": ("must contain only alphanumeric " "characters.") }, "DBName": { "AllowedPattern": "[a-zA-Z][a-zA-Z0-9]*", "Type": "String", "Description": "The WordPress database name", "MaxLength": "64", "Default": "wordpress", "MinLength": "1", "ConstraintDescription": ("must begin with a letter and " "contain only alphanumeric " "characters.") }, "InstanceType": { "Default": "m1.large", "Type": "String", "ConstraintDescription": "must be a valid EC2 instance type.", "Description": "WebServer EC2 instance type", "AllowedValues": [ "t1.micro", "m1.small", "m1.large", "m1.xlarge", "m2.xlarge", "m2.2xlarge", "m2.4xlarge", "c1.medium", "c1.xlarge", "cc1.4xlarge" ] }, "DBRootPassword": { "Type": "String", "Description": "Root password for MySQL", "Default": "admin", "MinLength": "1", "AllowedPattern": "[a-zA-Z0-9]*", "NoEcho": "true", "MaxLength": "41", "ConstraintDescription": ("must contain only alphanumeric " "characters.") } } expected = { "DBUsername": { "type": "string", "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 " "only alphanumeric characters."}, {"allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*", "description": "must begin with a letter and contain " "only alphanumeric characters."}, ] }, "LinuxDistribution": { "type": "string", "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"]} ] }, "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", "m2.4xlarge", "c1.medium", "c1.xlarge", "cc1.4xlarge"], "description": "must be a valid EC2 instance type."}, ] }, "DBRootPassword": { "type": "string", "description": "Root password for MySQL", "required": False, 'update_allowed': True, 'immutable': False, "constraints": [ {"length": {"min": 1, "max": 41}, "description": "must contain only alphanumeric " "characters."}, {"allowed_pattern": "[a-zA-Z0-9]*", "description": "must contain only alphanumeric " "characters."}, ] }, "KeyName": { "type": "string", "description": ("Name of an existing EC2 KeyPair to enable " "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 " "characters."}, {"allowed_pattern": "[a-zA-Z0-9]*", "description": "must contain only alphanumeric " "characters."}, ] }, "DBName": { "type": "string", "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 " "only alphanumeric characters."}, {"allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*", "description": "must begin with a letter and contain " "only alphanumeric characters."}, ] }, } params = dict((n, parameters.Schema.from_dict(n, s)) for n, s in params_snippet.items()) props_schemata = properties.Properties.schema_from_params(params) self.assertEqual(expected, dict((n, dict(s)) for n, s in props_schemata.items())) def test_schema_from_hot_params(self): params_snippet = { "KeyName": { "type": "string", "description": ("Name of an existing EC2 KeyPair to enable " "SSH access to the instances") }, "InstanceType": { "default": "m1.large", "type": "string", "description": "WebServer EC2 instance type", "constraints": [ {"allowed_values": ["t1.micro", "m1.small", "m1.large", "m1.xlarge", "m2.xlarge", "m2.2xlarge", "m2.4xlarge", "c1.medium", "c1.xlarge", "cc1.4xlarge"], "description": "Must be a valid EC2 instance type."} ] }, "LinuxDistribution": { "default": "F17", "type": "string", "description": "Distribution of choice", "constraints": [ {"allowed_values": ["F18", "F17", "U10", "RHEL-6.1", "RHEL-6.2", "RHEL-6.3"], "description": "Must be a valid Linux distribution"} ] }, "DBName": { "type": "string", "description": "The WordPress database name", "default": "wordpress", "constraints": [ {"length": {"min": 1, "max": 64}, "description": "Length must be between 1 and 64"}, {"allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*", "description": ("Must begin with a letter and contain " "only alphanumeric characters.")} ] }, "DBUsername": { "type": "string", "description": "The WordPress database admin account username", "default": "admin", "hidden": "true", "constraints": [ {"length": {"min": 1, "max": 16}, "description": "Length must be between 1 and 16"}, {"allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*", "description": ("Must begin with a letter and only " "contain alphanumeric characters")} ] }, "DBPassword": { "type": "string", "description": "The WordPress database admin account password", "default": "admin", "hidden": "true", "constraints": [ {"length": {"min": 1, "max": 41}, "description": "Length must be between 1 and 41"}, {"allowed_pattern": "[a-zA-Z0-9]*", "description": ("Must contain only alphanumeric " "characters")} ] }, "DBRootPassword": { "type": "string", "description": "Root password for MySQL", "default": "admin", "hidden": "true", "constraints": [ {"length": {"min": 1, "max": 41}, "description": "Length must be between 1 and 41"}, {"allowed_pattern": "[a-zA-Z0-9]*", "description": ("Must contain only alphanumeric " "characters")} ] } } expected = { "KeyName": { "type": "string", "description": ("Name of an existing EC2 KeyPair to enable " "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", "m2.4xlarge", "c1.medium", "c1.xlarge", "cc1.4xlarge"], "description": "Must be a valid EC2 instance type."}, ] }, "LinuxDistribution": { "type": "string", "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"], "description": "Must be a valid Linux distribution"} ] }, "DBName": { "type": "string", "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"}, {"allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*", "description": ("Must begin with a letter and contain " "only alphanumeric characters.")}, ] }, "DBUsername": { "type": "string", "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"}, {"allowed_pattern": "[a-zA-Z][a-zA-Z0-9]*", "description": ("Must begin with a letter and only " "contain alphanumeric characters")}, ] }, "DBPassword": { "type": "string", "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"}, {"allowed_pattern": "[a-zA-Z0-9]*", "description": ("Must contain only alphanumeric " "characters")}, ] }, "DBRootPassword": { "type": "string", "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"}, {"allowed_pattern": "[a-zA-Z0-9]*", "description": ("Must contain only alphanumeric " "characters")}, ] } } params = dict((n, hot_param.HOTParamSchema.from_dict(n, s)) for n, s in params_snippet.items()) props_schemata = properties.Properties.schema_from_params(params) self.assertEqual(expected, dict((n, dict(s)) for n, s in props_schemata.items())) def test_compare_same(self): schema = {'foo': {'Type': 'Integer'}} props_a = properties.Properties(schema, {'foo': 1}) props_b = properties.Properties(schema, {'foo': 1}) self.assertFalse(props_a != props_b) def test_compare_different(self): schema = {'foo': {'Type': 'Integer'}} props_a = properties.Properties(schema, {'foo': 0}) props_b = properties.Properties(schema, {'foo': 1}) self.assertTrue(props_a != props_b) class PropertiesValidationTest(common.HeatTestCase): def test_required(self): schema = {'foo': {'Type': 'String', 'Required': True}} props = properties.Properties(schema, {'foo': 'bar'}) self.assertIsNone(props.validate()) def test_missing_required(self): schema = {'foo': {'Type': 'String', 'Required': True}} props = properties.Properties(schema, {}) self.assertRaises(exception.StackValidationFailed, props.validate) def test_missing_unimplemented(self): schema = {'foo': {'Type': 'String', 'Implemented': False}} props = properties.Properties(schema, {}) self.assertIsNone(props.validate()) def test_present_unimplemented(self): schema = {'foo': {'Type': 'String', 'Implemented': False}} props = properties.Properties(schema, {'foo': 'bar'}) self.assertRaises(exception.StackValidationFailed, props.validate) def test_missing(self): schema = {'foo': {'Type': 'String'}} props = properties.Properties(schema, {}) self.assertIsNone(props.validate()) def test_unknown_typo(self): schema = {'foo': {'Type': 'String'}} props = properties.Properties(schema, {'food': 42}) self.assertRaises(exception.StackValidationFailed, props.validate) def test_list_instead_string(self): schema = {'foo': {'Type': 'String'}} props = properties.Properties(schema, {'foo': ['foo', 'bar']}) ex = self.assertRaises(exception.StackValidationFailed, props.validate) self.assertEqual('Property error : foo: Value must be a string', six.text_type(ex)) def test_dict_instead_string(self): schema = {'foo': {'Type': 'String'}} props = properties.Properties(schema, {'foo': {'foo': 'bar'}}) ex = self.assertRaises(exception.StackValidationFailed, props.validate) self.assertEqual('Property error : foo: Value must be a string', six.text_type(ex)) def test_none_string(self): schema = {'foo': {'Type': 'String'}} props = properties.Properties(schema, {'foo': None}) self.assertIsNone(props.validate()) def test_none_integer(self): schema = {'foo': {'Type': 'Integer'}} props = properties.Properties(schema, {'foo': None}) self.assertIsNone(props.validate()) def test_none_number(self): schema = {'foo': {'Type': 'Number'}} props = properties.Properties(schema, {'foo': None}) self.assertIsNone(props.validate()) def test_none_boolean(self): schema = {'foo': {'Type': 'Boolean'}} props = properties.Properties(schema, {'foo': None}) self.assertIsNone(props.validate()) def test_none_map(self): schema = {'foo': {'Type': 'Map'}} props = properties.Properties(schema, {'foo': None}) self.assertIsNone(props.validate()) def test_none_list(self): schema = {'foo': {'Type': 'List'}} props = properties.Properties(schema, {'foo': None}) self.assertIsNone(props.validate()) def test_none_default_string(self): schema = {'foo': {'Type': 'String', 'Default': 'bar'}} props = properties.Properties(schema, {'foo': None}) self.assertIsNone(props.validate()) def test_none_default_integer(self): schema = {'foo': {'Type': 'Integer', 'Default': 42}} props = properties.Properties(schema, {'foo': None}) self.assertIsNone(props.validate()) def test_none_default_number(self): schema = {'foo': {'Type': 'Number', 'Default': 42.0}} props = properties.Properties(schema, {'foo': None}) self.assertIsNone(props.validate()) def test_none_default_boolean(self): schema = {'foo': {'Type': 'Boolean', 'Default': True}} props = properties.Properties(schema, {'foo': None}) self.assertIsNone(props.validate()) def test_none_default_map(self): schema = {'foo': {'Type': 'Map', 'Default': {'bar': 'baz'}}} props = properties.Properties(schema, {'foo': None}) self.assertIsNone(props.validate()) def test_none_default_list(self): schema = {'foo': {'Type': 'List', 'Default': ['one', 'two']}} props = properties.Properties(schema, {'foo': None}) self.assertIsNone(props.validate()) def test_schema_to_template_nested_map_map_schema(self): nested_schema = {'Key': {'Type': 'String', 'Required': True}, 'Value': {'Type': 'String', 'Default': 'fewaf'}} schema = {'foo': {'Type': 'Map', 'Schema': nested_schema}} prop_expected = {'foo': {'Ref': 'foo'}} param_expected = {'foo': {'Type': 'Json'}} (parameters, props) = properties.Properties.schema_to_parameters_and_properties( schema) self.assertEqual(param_expected, parameters) self.assertEqual(prop_expected, props) def test_schema_to_template_nested_map_list_map_schema(self): key_schema = {'bar': {'Type': 'Number'}} nested_schema = {'Key': {'Type': 'Map', 'Schema': key_schema}, 'Value': {'Type': 'String', 'Required': True}} schema = {'foo': {'Type': 'List', 'Schema': {'Type': 'Map', 'Schema': nested_schema}}} prop_expected = {'foo': {'Fn::Split': [",", {'Ref': 'foo'}]}} param_expected = {'foo': {'Type': 'CommaDelimitedList'}} (parameters, props) = properties.Properties.schema_to_parameters_and_properties( schema) self.assertEqual(param_expected, parameters) self.assertEqual(prop_expected, props) def test_schema_object_to_template_nested_map_list_map_schema(self): key_schema = {'bar': properties.Schema(properties.Schema.NUMBER)} nested_schema = { 'Key': properties.Schema(properties.Schema.MAP, schema=key_schema), 'Value': properties.Schema(properties.Schema.STRING, required=True) } schema = { 'foo': properties.Schema(properties.Schema.LIST, schema=properties.Schema( properties.Schema.MAP, schema=nested_schema)) } prop_expected = {'foo': {'Fn::Split': [",", {'Ref': 'foo'}]}} param_expected = {'foo': {'Type': 'CommaDelimitedList'}} (parameters, props) = properties.Properties.schema_to_parameters_and_properties( schema) self.assertEqual(param_expected, parameters) self.assertEqual(prop_expected, props) def test_schema_invalid_parameters_stripped(self): schema = {'foo': {'Type': 'String', 'Required': True, 'Implemented': True}} prop_expected = {'foo': {'Ref': 'foo'}} param_expected = {'foo': {'Type': 'String'}} (parameters, props) = properties.Properties.schema_to_parameters_and_properties( schema) self.assertEqual(param_expected, parameters) self.assertEqual(prop_expected, props) def test_schema_support_status(self): schema = { 'foo_sup': properties.Schema( properties.Schema.STRING, default='foo' ), 'bar_dep': properties.Schema( properties.Schema.STRING, default='bar', support_status=support.SupportStatus( support.DEPRECATED, 'Do not use this ever') ) } props = properties.Properties(schema, {}) self.assertEqual(support.SUPPORTED, props.props['foo_sup'].support_status().status) self.assertEqual(support.DEPRECATED, props.props['bar_dep'].support_status().status) self.assertEqual('Do not use this ever', props.props['bar_dep'].support_status().message) def test_nested_properties_schema_invalid_property_in_list(self): child_schema = {'Key': {'Type': 'String', 'Required': True}, 'Value': {'Type': 'Boolean', 'Default': True}} list_schema = {'Type': 'Map', 'Schema': child_schema} schema = {'foo': {'Type': 'List', 'Schema': list_schema}} valid_data = {'foo': [{'Key': 'Test'}]} props = properties.Properties(schema, valid_data) self.assertIsNone(props.validate()) invalid_data = {'foo': [{'Key': 'Test', 'bar': 'baz'}]} props = properties.Properties(schema, invalid_data) ex = self.assertRaises(exception.StackValidationFailed, props.validate) self.assertEqual('Property error : foo[0]: Unknown Property bar', six.text_type(ex)) def test_nested_properties_schema_invalid_property_in_map(self): child_schema = {'Key': {'Type': 'String', 'Required': True}, 'Value': {'Type': 'Boolean', 'Default': True}} map_schema = {'boo': {'Type': 'Map', 'Schema': child_schema}} schema = {'foo': {'Type': 'Map', 'Schema': map_schema}} valid_data = {'foo': {'boo': {'Key': 'Test'}}} props = properties.Properties(schema, valid_data) self.assertIsNone(props.validate()) invalid_data = {'foo': {'boo': {'Key': 'Test', 'bar': 'baz'}}} props = properties.Properties(schema, invalid_data) ex = self.assertRaises(exception.StackValidationFailed, props.validate) self.assertEqual('Property error : foo.boo: Unknown Property bar', six.text_type(ex)) def test_more_nested_properties_schema_invalid_property_in_list(self): nested_child_schema = {'Key': {'Type': 'String', 'Required': True}} child_schema = {'doo': {'Type': 'Map', 'Schema': nested_child_schema}} list_schema = {'Type': 'Map', 'Schema': child_schema} schema = {'foo': {'Type': 'List', 'Schema': list_schema}} valid_data = {'foo': [{'doo': {'Key': 'Test'}}]} props = properties.Properties(schema, valid_data) self.assertIsNone(props.validate()) invalid_data = {'foo': [{'doo': {'Key': 'Test', 'bar': 'baz'}}]} props = properties.Properties(schema, invalid_data) ex = self.assertRaises(exception.StackValidationFailed, props.validate) self.assertEqual('Property error : foo[0].doo: Unknown Property bar', six.text_type(ex)) def test_more_nested_properties_schema_invalid_property_in_map(self): nested_child_schema = {'Key': {'Type': 'String', 'Required': True}} child_schema = {'doo': {'Type': 'Map', 'Schema': nested_child_schema}} map_schema = {'boo': {'Type': 'Map', 'Schema': child_schema}} schema = {'foo': {'Type': 'Map', 'Schema': map_schema}} valid_data = {'foo': {'boo': {'doo': {'Key': 'Test'}}}} props = properties.Properties(schema, valid_data) self.assertIsNone(props.validate()) invalid_data = {'foo': {'boo': {'doo': {'Key': 'Test', 'bar': 'baz'}}}} props = properties.Properties(schema, invalid_data) ex = self.assertRaises(exception.StackValidationFailed, props.validate) self.assertEqual('Property error : foo.boo.doo: Unknown Property bar', six.text_type(ex)) def test_schema_to_template_empty_schema(self): schema = {} (parameters, props) = 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)