1 # Licensed under the Apache License, Version 2.0 (the "License"); you may
2 # not use this file except in compliance with the License. You may obtain
3 # a copy of the License at
5 # http://www.apache.org/licenses/LICENSE-2.0
7 # Unless required by applicable law or agreed to in writing, software
8 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 # License for the specific language governing permissions and limitations
15 from testtools.testcase import skip
16 from toscaparser.common import exception
17 from toscaparser.dataentity import DataEntity
18 from toscaparser.elements.datatype import DataType
19 from toscaparser.parameters import Input
20 from toscaparser.tests.base import TestCase
21 from toscaparser.tosca_template import ToscaTemplate
22 from toscaparser.utils.gettextutils import _
23 from toscaparser.utils import yamlparser
26 class DataTypeTest(TestCase):
28 custom_type_schema = '''
29 tosca.my.datatypes.PeopleBase:
40 tosca.my.datatypes.People:
41 derived_from: tosca.my.datatypes.PeopleBase
52 type: tosca.my.datatypes.ContactInfo
54 tosca.my.datatypes.ContactInfo:
55 description: simple contact information
67 tosca.my.datatypes.TestLab:
73 - in_range: [-256, INFINITY]
78 - in_range: [-256, UNBOUNDED]
83 - in_range: [UNBOUNDED, 256]
85 custom_type_def = yamlparser.simple_parse(custom_type_schema)
87 def test_empty_template(self):
89 value = yamlparser.simple_parse(value_snippet)
90 self.assertEqual(value, {})
92 def test_built_in_datatype(self):
96 network_id: 3e54214f-5c09-1bc9-9999-44100326da1b
97 addresses: [ 10.111.128.10 ]
99 value = yamlparser.simple_parse(value_snippet)
100 data = DataEntity('tosca.datatypes.network.NetworkInfo',
101 value.get('private_network'))
102 self.assertIsNotNone(data.validate())
108 value = yamlparser.simple_parse(value_snippet)
109 data = DataEntity('tosca.datatypes.network.PortSpec',
110 value.get('portspec_valid'))
111 self.assertIsNotNone(data.validate())
117 value = yamlparser.simple_parse(value_snippet)
118 data = DataEntity('tosca.datatypes.network.PortSpec',
119 value.get('portspec_invalid'))
120 err = self.assertRaises(exception.ValidationError, data.validate)
121 self.assertEqual(_('The value "xyz" of property "protocol" is not '
122 'valid. Expected a value from "[udp, tcp, igmp]".'
126 def test_built_in_datatype_with_short_name(self):
130 port_id: 2c0c7a37-691a-23a6-7709-2d10ad041467
131 network_id: 3e54214f-5c09-1bc9-9999-44100326da1b
132 mac_address: f1:18:3b:41:92:1e
133 addresses: [ 172.24.9.102 ]
135 value = yamlparser.simple_parse(value_snippet)
136 data = DataEntity('PortInfo', value.get('ethernet_port'))
137 self.assertIsNotNone(data.validate())
139 # Test normative PortSpec datatype's additional requirements
140 # TODO(Matt) - opened as bug 1555300
141 # Need a test for PortSpec normative data type
142 # that tests the spec. requirement: "A valid PortSpec
143 # must have at least one of the following properties:
144 # target, target_range, source or source_range."
145 # TODO(Matt) - opened as bug 1555310
146 # test PortSpec value for source and target
147 # against the source_range and target_range
149 def test_port_spec_addl_reqs(self):
154 target_range: [ 1, 65535 ]
156 source_range: [ 1, 65535 ]
159 value = yamlparser.simple_parse(value_snippet)
160 data = DataEntity('tosca.datatypes.network.PortSpec',
161 value.get('test_port'))
162 self.assertIsNotNone(data.validate())
164 def test_built_in_datatype_without_properties(self):
168 value = yamlparser.simple_parse(value_snippet)
169 datatype = DataType('PortDef')
170 self.assertEqual('integer', datatype.value_type)
171 data = DataEntity('PortDef', value)
172 self.assertIsNotNone(data.validate())
174 @skip('The example in TOSCA spec may have some problem.')
175 def test_built_in_nested_datatype(self):
182 value = yamlparser.simple_parse(value_snippet)
183 data = DataEntity('PortSpec', value.get('user_port'))
184 self.assertIsNotNone(data.validate())
186 def test_built_in_nested_datatype_portdef(self):
191 description: Port for the MySQL database
193 inputs = yamlparser.simple_parse(tpl_snippet)['inputs']
194 name, attrs = list(inputs.items())[0]
195 input = Input(name, attrs)
196 self.assertIsNone(input.validate(3360))
197 err = self.assertRaises(exception.ValidationError, input.validate,
199 self.assertEqual(_('The value "336000" of property "None" is out of '
200 'range "(min:1, max:65535)".'),
203 def test_custom_datatype(self):
208 value = yamlparser.simple_parse(value_snippet)
209 data = DataEntity('tosca.my.datatypes.PeopleBase', value,
210 DataTypeTest.custom_type_def)
211 self.assertIsNotNone(data.validate())
213 def test_custom_datatype_with_parent(self):
218 - {contact_name: Tom,
219 contact_email: tom@email.com,
220 contact_phone: '123456789'}
221 - {contact_name: Jerry,
222 contact_email: jerry@email.com,
223 contact_phone: '321654987'}
225 value = yamlparser.simple_parse(value_snippet)
226 data = DataEntity('tosca.my.datatypes.People', value,
227 DataTypeTest.custom_type_def)
228 self.assertIsNotNone(data.validate())
230 # [Tom, Jerry] is not a dict, it can't be a value of datatype PeopleBase
231 def test_non_dict_value_for_datatype(self):
235 value = yamlparser.simple_parse(value_snippet)
236 data = DataEntity('tosca.my.datatypes.PeopleBase', value,
237 DataTypeTest.custom_type_def)
238 error = self.assertRaises(exception.TypeMismatchError, data.validate)
239 self.assertEqual(_('[\'Tom\', \'Jerry\'] must be of type '
240 '"tosca.my.datatypes.PeopleBase".'),
243 # 'nema' is an invalid field name
244 def test_field_error_in_dataentity(self):
249 value = yamlparser.simple_parse(value_snippet)
250 data = DataEntity('tosca.my.datatypes.PeopleBase', value,
251 DataTypeTest.custom_type_def)
252 error = self.assertRaises(exception.UnknownFieldError, data.validate)
253 self.assertEqual(_('Data value of type '
254 '"tosca.my.datatypes.PeopleBase" contains unknown '
255 'field "nema". Refer to the definition to verify '
259 def test_default_field_in_dataentity(self):
263 value = yamlparser.simple_parse(value_snippet)
264 data = DataEntity('tosca.my.datatypes.PeopleBase', value,
265 DataTypeTest.custom_type_def)
266 data = data.validate()
267 self.assertEqual('unknown', data.get('gender'))
269 # required field 'name' is missing
270 def test_missing_field_in_dataentity(self):
274 value = yamlparser.simple_parse(value_snippet)
275 data = DataEntity('tosca.my.datatypes.PeopleBase', value,
276 DataTypeTest.custom_type_def)
277 error = self.assertRaises(exception.MissingRequiredFieldError,
279 self.assertEqual(_('Data value of type '
280 '"tosca.my.datatypes.PeopleBase" is missing '
281 'required field "[\'name\']".'),
284 # the value of name field is not a string
285 def test_type_error_in_dataentity(self):
290 value = yamlparser.simple_parse(value_snippet)
291 data = DataEntity('tosca.my.datatypes.PeopleBase', value,
292 DataTypeTest.custom_type_def)
293 error = self.assertRaises(ValueError, data.validate)
294 self.assertEqual(_('"123" is not a string.'), error.__str__())
296 # the value of name doesn't meet the defined constraint
297 def test_value_error_in_dataentity(self):
302 value = yamlparser.simple_parse(value_snippet)
303 data = DataEntity('tosca.my.datatypes.PeopleBase', value,
304 DataTypeTest.custom_type_def)
305 error = self.assertRaises(exception.ValidationError, data.validate)
306 self.assertEqual(_('Length of value "M" of property "name" must be '
307 'at least "2".'), error.__str__())
309 # value of addresses doesn't fit the entry_schema
310 def test_validation_in_collection_entry(self):
314 addresses: {Home: 1, Office: 9 bar avenue}
316 value = yamlparser.simple_parse(value_snippet)
317 data = DataEntity('tosca.my.datatypes.People', value,
318 DataTypeTest.custom_type_def)
319 error = self.assertRaises(ValueError, data.validate)
320 self.assertEqual(_('"1" is not a string.'), error.__str__())
322 # 'contact_pone' is an invalid attribute name in nested datatype below
323 def test_validation_in_nested_datatype(self):
328 - {contact_name: Tom,
329 contact_email: tom@email.com,
330 contact_pone: '123456789'}
331 - {contact_name: Jerry,
332 contact_email: jerry@email.com,
333 contact_phone: '321654987'}
335 value = yamlparser.simple_parse(value_snippet)
336 data = DataEntity('tosca.my.datatypes.People', value,
337 DataTypeTest.custom_type_def)
338 error = self.assertRaises(exception.UnknownFieldError, data.validate)
339 self.assertEqual(_('Data value of type '
340 '"tosca.my.datatypes.ContactInfo" contains unknown '
341 'field "contact_pone". Refer to the definition to '
342 'verify valid values.'),
345 def test_datatype_in_current_template(self):
346 tpl_path = os.path.join(
347 os.path.dirname(os.path.abspath(__file__)),
348 "data/datatypes/test_custom_datatypes_in_current_template.yaml")
349 self.assertIsNotNone(ToscaTemplate(tpl_path))
351 def test_datatype_in_template_positive(self):
352 tpl_path = os.path.join(
353 os.path.dirname(os.path.abspath(__file__)),
354 "data/datatypes/test_custom_datatypes_positive.yaml")
355 self.assertIsNotNone(ToscaTemplate(tpl_path))
357 def test_datatype_in_template_invalid_value(self):
358 tpl_path = os.path.join(
359 os.path.dirname(os.path.abspath(__file__)),
360 "data/datatypes/test_custom_datatypes_value_error.yaml")
361 self.assertRaises(exception.ValidationError, ToscaTemplate, tpl_path)
362 exception.ExceptionCollector.assertExceptionMessage(
364 _('"[\'1 foo street\', \'9 bar avenue\']" is not a map.'))
366 def test_datatype_in_template_nested_datatype_error(self):
367 tpl_path = os.path.join(
368 os.path.dirname(os.path.abspath(__file__)),
369 "data/datatypes/test_custom_datatypes_nested_datatype_error.yaml")
370 self.assertRaises(exception.ValidationError, ToscaTemplate, tpl_path)
371 exception.ExceptionCollector.assertExceptionMessage(
372 ValueError, _('"123456789" is not a string.'))
374 def test_valid_range_type(self):
378 target_range: [20000, 60000]
379 source_range: [1000, 3000]
381 value = yamlparser.simple_parse(value_snippet)
382 data = DataEntity('PortSpec', value.get('user_port'))
383 self.assertIsNotNone(data.validate())
385 def test_invalid_range_datatype(self):
390 target_range: [20000]
392 value = yamlparser.simple_parse(value_snippet)
393 data = DataEntity('PortSpec', value.get('user_port'))
394 err = self.assertRaises(ValueError, data.validate)
395 self.assertEqual(_('"[20000]" is not a valid range.'
403 target_range: [20000, 3000]
405 value = yamlparser.simple_parse(value_snippet)
406 data = DataEntity('PortSpec', value.get('user_port'))
407 err = self.assertRaises(ValueError, data.validate)
408 self.assertEqual(_('"[20000, 3000]" is not a valid range.'
413 humidity: [-100, 100]
415 value = yamlparser.simple_parse(value_snippet)
416 data = DataEntity('tosca.my.datatypes.TestLab',
417 value, DataTypeTest.custom_type_def)
418 err = self.assertRaises(exception.InvalidSchemaError,
419 lambda: data.validate())
420 self.assertEqual(_('The property "in_range" expects comparable values.'
424 def test_range_unbounded(self):
426 humidity: [-100, 100]
428 value = yamlparser.simple_parse(value_snippet)
429 data = DataEntity('tosca.my.datatypes.TestLab',
430 value, DataTypeTest.custom_type_def)
431 err = self.assertRaises(exception.InvalidSchemaError,
432 lambda: data.validate())
433 self.assertEqual(_('The property "in_range" expects comparable values.'
437 def test_invalid_ranges_against_constraints(self):
438 # The TestLab range type has min=-256, max=UNBOUNDED
440 temperature1: [-257, 999999]
442 value = yamlparser.simple_parse(value_snippet)
443 data = DataEntity('tosca.my.datatypes.TestLab', value,
444 DataTypeTest.custom_type_def)
445 err = self.assertRaises(exception.ValidationError, data.validate)
446 self.assertEqual(_('The value "-257" of property "temperature1" is '
447 'out of range "(min:-256, max:UNBOUNDED)".'),
451 temperature2: [-999999, 257]
453 value = yamlparser.simple_parse(value_snippet)
454 data = DataEntity('tosca.my.datatypes.TestLab', value,
455 DataTypeTest.custom_type_def)
456 err = self.assertRaises(exception.ValidationError, data.validate)
457 self.assertEqual(_('The value "257" of property "temperature2" is '
458 'out of range "(min:UNBOUNDED, max:256)".'),
461 def test_valid_ranges_against_constraints(self):
463 # The TestLab range type has max=UNBOUNDED
465 temperature1: [-255, 999999]
467 value = yamlparser.simple_parse(value_snippet)
468 data = DataEntity('tosca.my.datatypes.TestLab', value,
469 DataTypeTest.custom_type_def)
470 self.assertIsNotNone(data.validate())
472 # The TestLab range type has min=UNBOUNDED
474 temperature2: [-999999, 255]
476 value = yamlparser.simple_parse(value_snippet)
477 data = DataEntity('tosca.my.datatypes.TestLab', value,
478 DataTypeTest.custom_type_def)
479 self.assertIsNotNone(data.validate())
481 def test_incorrect_field_in_datatype(self):
483 tosca_definitions_version: tosca_simple_yaml_1_0
487 type: tosca.nodes.Compute
490 type: tosca.nodes.WebServer
499 tpl = yamlparser.simple_parse(tpl_snippet)
500 err = self.assertRaises(exception.ValidationError, ToscaTemplate,
501 None, None, None, tpl)
502 self.assertIn(_('The pre-parsed input failed validation with the '
503 'following error(s): \n\n\tUnknownFieldError: Data '
504 'value of type "tosca.datatypes.Credential" contains'
505 ' unknown field "some_field". Refer to the definition'
506 ' to verify valid values'), err.__str__())
508 def test_functions_datatype(self):
512 token: { get_input: password }
514 value = yamlparser.simple_parse(value_snippet)
515 data = DataEntity('tosca.datatypes.Credential',
516 value.get('admin_credential'))
517 self.assertIsNotNone(data.validate())