0e613b2a798c1ce12cc69f5d6a86502d9f09cfe7
[parser.git] / tosca2heat / tosca-parser / toscaparser / tests / test_datatypes.py
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
4 #
5 #         http://www.apache.org/licenses/LICENSE-2.0
6 #
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
11 #    under the License.
12
13 import os
14
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
24
25
26 class DataTypeTest(TestCase):
27
28     custom_type_schema = '''
29     tosca.my.datatypes.PeopleBase:
30       properties:
31         name:
32           type: string
33           required: true
34           constraints:
35             - min_length: 2
36         gender:
37           type: string
38           default: unknown
39
40     tosca.my.datatypes.People:
41       derived_from: tosca.my.datatypes.PeopleBase
42       properties:
43         addresses:
44           type: map
45           required: false
46           entry_schema:
47             type: string
48         contacts:
49           type: list
50           required: false
51           entry_schema:
52             type: tosca.my.datatypes.ContactInfo
53
54     tosca.my.datatypes.ContactInfo:
55       description: simple contact information
56       properties:
57         contact_name:
58           type: string
59           required: true
60           constraints:
61             - min_length: 2
62         contact_email:
63           type: string
64         contact_phone:
65           type: string
66
67     tosca.my.datatypes.TestLab:
68       properties:
69         temperature:
70           type: range
71           required: false
72           constraints:
73             - in_range: [-256, UNBOUNDED]
74         humidity:
75           type: range
76           required: false
77           constraints:
78             - in_range: [-256, INFINITY]
79     '''
80     custom_type_def = yamlparser.simple_parse(custom_type_schema)
81
82     def test_empty_template(self):
83         value_snippet = ''
84         value = yamlparser.simple_parse(value_snippet)
85         self.assertEqual(value, {})
86
87     # TODO(Matt) - opened as bug 1555300
88     # Need a test for PortSpec normative data type
89     # that tests the spec. requirement: "A valid PortSpec
90     # must have at least one of the following properties:
91     # target, target_range, source or source_range."
92     # TODO(Matt) - opened as bug 1555310
93     # test PortSpec value for source and target
94     # against the source_range and target_range
95     # when specified.
96     def test_built_in_datatype(self):
97         value_snippet = '''
98         private_network:
99           network_name: private
100           network_id: 3e54214f-5c09-1bc9-9999-44100326da1b
101           addresses: [ 10.111.128.10 ]
102         '''
103         value = yamlparser.simple_parse(value_snippet)
104         data = DataEntity('tosca.datatypes.network.NetworkInfo',
105                           value.get('private_network'))
106         self.assertIsNotNone(data.validate())
107
108         value_snippet = '''
109         portspec_valid:
110           protocol: tcp
111         '''
112         value = yamlparser.simple_parse(value_snippet)
113         data = DataEntity('tosca.datatypes.network.PortSpec',
114                           value.get('portspec_valid'))
115         self.assertIsNotNone(data.validate())
116
117         value_snippet = '''
118         portspec_invalid:
119           protocol: xyz
120         '''
121         value = yamlparser.simple_parse(value_snippet)
122         data = DataEntity('tosca.datatypes.network.PortSpec',
123                           value.get('portspec_invalid'))
124         err = self.assertRaises(exception.ValidationError, data.validate)
125         self.assertEqual(_('The value "xyz" of property "protocol" is not '
126                            'valid. Expected a value from "[udp, tcp, igmp]".'
127                            ),
128                          err.__str__())
129
130     def test_built_in_datatype_with_short_name(self):
131         value_snippet = '''
132         ethernet_port:
133           port_name: port1
134           port_id: 2c0c7a37-691a-23a6-7709-2d10ad041467
135           network_id: 3e54214f-5c09-1bc9-9999-44100326da1b
136           mac_address: f1:18:3b:41:92:1e
137           addresses: [ 172.24.9.102 ]
138         '''
139         value = yamlparser.simple_parse(value_snippet)
140         data = DataEntity('PortInfo', value.get('ethernet_port'))
141         self.assertIsNotNone(data.validate())
142
143     def test_built_in_datatype_without_properties(self):
144         value_snippet = '''
145         2
146         '''
147         value = yamlparser.simple_parse(value_snippet)
148         datatype = DataType('PortDef')
149         self.assertEqual('integer', datatype.value_type)
150         data = DataEntity('PortDef', value)
151         self.assertIsNotNone(data.validate())
152
153     @skip('The example in TOSCA spec may have some problem.')
154     def test_built_in_nested_datatype(self):
155         value_snippet = '''
156         user_port:
157           protocol: tcp
158           target: [50000]
159           source: [9000]
160         '''
161         value = yamlparser.simple_parse(value_snippet)
162         data = DataEntity('PortSpec', value.get('user_port'))
163         self.assertIsNotNone(data.validate())
164
165     def test_built_in_nested_datatype_portdef(self):
166         tpl_snippet = '''
167         inputs:
168           db_port:
169             type: PortDef
170             description: Port for the MySQL database
171         '''
172         inputs = yamlparser.simple_parse(tpl_snippet)['inputs']
173         name, attrs = list(inputs.items())[0]
174         input = Input(name, attrs)
175         self.assertIsNone(input.validate(3360))
176         err = self.assertRaises(exception.ValidationError, input.validate,
177                                 336000)
178         self.assertEqual(_('The value "336000" of property "None" is out of '
179                            'range "(min:1, max:65535)".'),
180                          err.__str__())
181
182     def test_custom_datatype(self):
183         value_snippet = '''
184         name: Mike
185         gender: male
186         '''
187         value = yamlparser.simple_parse(value_snippet)
188         data = DataEntity('tosca.my.datatypes.PeopleBase', value,
189                           DataTypeTest.custom_type_def)
190         self.assertIsNotNone(data.validate())
191
192     def test_custom_datatype_with_parent(self):
193         value_snippet = '''
194         name: Mike
195         gender: male
196         contacts:
197           - {contact_name: Tom,
198             contact_email: tom@email.com,
199             contact_phone: '123456789'}
200           - {contact_name: Jerry,
201             contact_email: jerry@email.com,
202             contact_phone: '321654987'}
203         '''
204         value = yamlparser.simple_parse(value_snippet)
205         data = DataEntity('tosca.my.datatypes.People', value,
206                           DataTypeTest.custom_type_def)
207         self.assertIsNotNone(data.validate())
208
209     # [Tom, Jerry] is not a dict, it can't be a value of datatype PeopleBase
210     def test_non_dict_value_for_datatype(self):
211         value_snippet = '''
212         [Tom, Jerry]
213         '''
214         value = yamlparser.simple_parse(value_snippet)
215         data = DataEntity('tosca.my.datatypes.PeopleBase', value,
216                           DataTypeTest.custom_type_def)
217         error = self.assertRaises(exception.TypeMismatchError, data.validate)
218         self.assertEqual(_('[\'Tom\', \'Jerry\'] must be of type '
219                            '"tosca.my.datatypes.PeopleBase".'),
220                          error.__str__())
221
222     # 'nema' is an invalid field name
223     def test_field_error_in_dataentity(self):
224         value_snippet = '''
225         nema: Mike
226         gender: male
227         '''
228         value = yamlparser.simple_parse(value_snippet)
229         data = DataEntity('tosca.my.datatypes.PeopleBase', value,
230                           DataTypeTest.custom_type_def)
231         error = self.assertRaises(exception.UnknownFieldError, data.validate)
232         self.assertEqual(_('Data value of type '
233                            '"tosca.my.datatypes.PeopleBase" contains unknown '
234                            'field "nema". Refer to the definition to verify '
235                            'valid values.'),
236                          error.__str__())
237
238     def test_default_field_in_dataentity(self):
239         value_snippet = '''
240         name: Mike
241         '''
242         value = yamlparser.simple_parse(value_snippet)
243         data = DataEntity('tosca.my.datatypes.PeopleBase', value,
244                           DataTypeTest.custom_type_def)
245         data = data.validate()
246         self.assertEqual('unknown', data.get('gender'))
247
248     # required field 'name' is missing
249     def test_missing_field_in_dataentity(self):
250         value_snippet = '''
251         gender: male
252         '''
253         value = yamlparser.simple_parse(value_snippet)
254         data = DataEntity('tosca.my.datatypes.PeopleBase', value,
255                           DataTypeTest.custom_type_def)
256         error = self.assertRaises(exception.MissingRequiredFieldError,
257                                   data.validate)
258         self.assertEqual(_('Data value of type '
259                            '"tosca.my.datatypes.PeopleBase" is missing '
260                            'required field "[\'name\']".'),
261                          error.__str__())
262
263     # the value of name field is not a string
264     def test_type_error_in_dataentity(self):
265         value_snippet = '''
266         name: 123
267         gender: male
268         '''
269         value = yamlparser.simple_parse(value_snippet)
270         data = DataEntity('tosca.my.datatypes.PeopleBase', value,
271                           DataTypeTest.custom_type_def)
272         error = self.assertRaises(ValueError, data.validate)
273         self.assertEqual(_('"123" is not a string.'), error.__str__())
274
275     # the value of name doesn't meet the defined constraint
276     def test_value_error_in_dataentity(self):
277         value_snippet = '''
278         name: M
279         gender: male
280         '''
281         value = yamlparser.simple_parse(value_snippet)
282         data = DataEntity('tosca.my.datatypes.PeopleBase', value,
283                           DataTypeTest.custom_type_def)
284         error = self.assertRaises(exception.ValidationError, data.validate)
285         self.assertEqual(_('Length of value "M" of property "name" must be '
286                            'at least "2".'), error.__str__())
287
288     # value of addresses doesn't fit the entry_schema
289     def test_validation_in_collection_entry(self):
290         value_snippet = '''
291         name: Mike
292         gender: male
293         addresses: {Home: 1, Office: 9 bar avenue}
294         '''
295         value = yamlparser.simple_parse(value_snippet)
296         data = DataEntity('tosca.my.datatypes.People', value,
297                           DataTypeTest.custom_type_def)
298         error = self.assertRaises(ValueError, data.validate)
299         self.assertEqual(_('"1" is not a string.'), error.__str__())
300
301     # 'contact_pone' is an invalid attribute name in nested datatype below
302     def test_validation_in_nested_datatype(self):
303         value_snippet = '''
304         name: Mike
305         gender: male
306         contacts:
307           - {contact_name: Tom,
308             contact_email: tom@email.com,
309             contact_pone: '123456789'}
310           - {contact_name: Jerry,
311             contact_email: jerry@email.com,
312             contact_phone: '321654987'}
313         '''
314         value = yamlparser.simple_parse(value_snippet)
315         data = DataEntity('tosca.my.datatypes.People', value,
316                           DataTypeTest.custom_type_def)
317         error = self.assertRaises(exception.UnknownFieldError, data.validate)
318         self.assertEqual(_('Data value of type '
319                            '"tosca.my.datatypes.ContactInfo" contains unknown '
320                            'field "contact_pone". Refer to the definition to '
321                            'verify valid values.'),
322                          error.__str__())
323
324     def test_datatype_in_current_template(self):
325         tpl_path = os.path.join(
326             os.path.dirname(os.path.abspath(__file__)),
327             "data/datatypes/test_custom_datatypes_in_current_template.yaml")
328         self.assertIsNotNone(ToscaTemplate(tpl_path))
329
330     def test_datatype_in_template_positive(self):
331         tpl_path = os.path.join(
332             os.path.dirname(os.path.abspath(__file__)),
333             "data/datatypes/test_custom_datatypes_positive.yaml")
334         self.assertIsNotNone(ToscaTemplate(tpl_path))
335
336     def test_datatype_in_template_invalid_value(self):
337         tpl_path = os.path.join(
338             os.path.dirname(os.path.abspath(__file__)),
339             "data/datatypes/test_custom_datatypes_value_error.yaml")
340         self.assertRaises(exception.ValidationError, ToscaTemplate, tpl_path)
341         exception.ExceptionCollector.assertExceptionMessage(
342             ValueError,
343             _('"[\'1 foo street\', \'9 bar avenue\']" is not a map.'))
344
345     def test_datatype_in_template_nested_datatype_error(self):
346         tpl_path = os.path.join(
347             os.path.dirname(os.path.abspath(__file__)),
348             "data/datatypes/test_custom_datatypes_nested_datatype_error.yaml")
349         self.assertRaises(exception.ValidationError, ToscaTemplate, tpl_path)
350         exception.ExceptionCollector.assertExceptionMessage(
351             ValueError, _('"123456789" is not a string.'))
352
353     def test_valid_range_type(self):
354         value_snippet = '''
355         user_port:
356           protocol: tcp
357           target_range:  [20000, 60000]
358           source_range:  [1000, 3000]
359         '''
360         value = yamlparser.simple_parse(value_snippet)
361         data = DataEntity('PortSpec', value.get('user_port'))
362         self.assertIsNotNone(data.validate())
363
364     def test_invalid_range_datatype(self):
365         value_snippet = '''
366         user_port:
367           protocol: tcp
368           target_range: [20000]
369         '''
370         value = yamlparser.simple_parse(value_snippet)
371         data = DataEntity('PortSpec', value.get('user_port'))
372         err = self.assertRaises(ValueError, data.validate)
373         self.assertEqual(_('"[20000]" is not a valid range.'
374                            ),
375                          err.__str__())
376
377         value_snippet = '''
378         user_port:
379           protocol: tcp
380           target_range: [20000, 3000]
381         '''
382         value = yamlparser.simple_parse(value_snippet)
383         data = DataEntity('PortSpec', value.get('user_port'))
384         err = self.assertRaises(ValueError, data.validate)
385         self.assertEqual(_('"[20000, 3000]" is not a valid range.'
386                            ),
387                          err.__str__())
388
389         value_snippet = '''
390         humidity: [-100, 100]
391         '''
392         value = yamlparser.simple_parse(value_snippet)
393         data = DataEntity('tosca.my.datatypes.TestLab',
394                           value, DataTypeTest.custom_type_def)
395         err = self.assertRaises(exception.InvalidSchemaError,
396                                 lambda: data.validate())
397         self.assertEqual(_('The property "in_range" expects comparable values.'
398                            ),
399                          err.__str__())
400
401     def test_range_unbounded(self):
402         value_snippet = '''
403         temperature: [-100, 999999]
404         '''
405         value = yamlparser.simple_parse(value_snippet)
406         data = DataEntity('tosca.my.datatypes.TestLab', value,
407                           DataTypeTest.custom_type_def)
408         self.assertIsNotNone(data.validate())