Update project maturity status
[multicloud/azure.git] / azure / aria / aria-extension-cloudify / src / aria / extensions / aria_extension_tosca / simple_v1_0 / presentation / field_validators.py
1 # Licensed to the Apache Software Foundation (ASF) under one or more
2 # contributor license agreements.  See the NOTICE file distributed with
3 # this work for additional information regarding copyright ownership.
4 # The ASF licenses this file to You under the Apache License, Version 2.0
5 # (the "License"); you may not use this file except in compliance with
6 # the License.  You may obtain a copy of the License at
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15
16 import re
17
18 from aria.utils.formatting import safe_repr
19 from aria.parser import implements_specification
20 from aria.parser.presentation import (report_issue_for_unknown_type, derived_from_validator)
21 from aria.parser.validation import Issue
22
23 from ..modeling.data_types import (get_primitive_data_type, get_data_type_name, coerce_value,
24                                    get_container_data_type)
25 from .types import (get_type_by_name, convert_name_to_full_type_name)
26
27
28
29 #
30 # NodeTemplate, RelationshipTemplate
31 #
32
33 @implements_specification('3.7.3.3', 'tosca-simple-1.0')
34 def copy_validator(template_type_name, templates_dict_name):
35     """
36     Makes sure that the field refers to an existing template defined in the root presenter.
37
38     Use with the :func:`field_validator` decorator for the ``copy`` field in
39     :class:`NodeTemplate` and :class:`RelationshipTemplate`.
40     """
41
42     def validator_fn(field, presentation, context):
43         field.default_validate(presentation, context)
44
45         # Make sure type exists
46         value = getattr(presentation, field.name)
47         if value is not None:
48             copy = context.presentation.get_from_dict('service_template', 'topology_template',
49                                                       templates_dict_name, value)
50             if copy is None:
51                 report_issue_for_unknown_type(context, presentation, template_type_name, field.name)
52             else:
53                 if copy.copy is not None:
54                     context.validation.report(
55                         '"copy" field refers to a %s that itself is a copy in "%s": %s'
56                         % (template_type_name, presentation._fullname, safe_repr(value)),
57                         locator=presentation._locator, level=Issue.BETWEEN_TYPES)
58
59     return validator_fn
60
61
62 #
63 # PropertyDefinition, AttributeDefinition, ParameterDefinition, EntrySchema
64 #
65
66 def data_type_validator(type_name='data type'):
67     """
68     Makes sure that the field refers to a valid data type, whether complex or primitive.
69
70     Used with the :func:`field_validator` decorator for the ``type`` fields in
71     :class:`PropertyDefinition`, :class:`AttributeDefinition`, :class:`ParameterDefinition`,
72     and :class:`EntrySchema`.
73
74     Extra behavior beyond validation: generated function returns true if field is a complex data
75     type.
76     """
77
78     def validator(field, presentation, context):
79         field.default_validate(presentation, context)
80
81         value = getattr(presentation, field.name)
82         if value is not None:
83             # Test for circular definitions
84             container_data_type = get_container_data_type(presentation)
85             if (container_data_type is not None) and (container_data_type._name == value):
86                 context.validation.report(
87                     'type of property "%s" creates a circular value hierarchy: %s'
88                     % (presentation._fullname, safe_repr(value)),
89                     locator=presentation._get_child_locator('type'), level=Issue.BETWEEN_TYPES)
90
91             # Can be a complex data type
92             if get_type_by_name(context, value, 'data_types') is not None:
93                 return True
94
95             # Can be a primitive data type
96             if get_primitive_data_type(value) is None:
97                 report_issue_for_unknown_type(context, presentation, type_name, field.name)
98
99         return False
100
101     return validator
102
103
104 #
105 # PropertyDefinition, AttributeDefinition
106 #
107
108 def entry_schema_validator(field, presentation, context):
109     """
110     According to whether the data type supports ``entry_schema`` (e.g., it is or inherits from
111     list or map), make sure that we either have or don't have a valid data type value.
112
113     Used with the :func:`field_validator` decorator for the ``entry_schema`` field in
114     :class:`PropertyDefinition` and :class:`AttributeDefinition`.
115     """
116
117     field.default_validate(presentation, context)
118
119     def type_uses_entry_schema(the_type):
120         use_entry_schema = the_type._get_extension('use_entry_schema', False) \
121             if hasattr(the_type, '_get_extension') else False
122         if use_entry_schema:
123             return True
124         parent = the_type._get_parent(context) if hasattr(the_type, '_get_parent') else None
125         if parent is None:
126             return False
127         return type_uses_entry_schema(parent)
128
129     value = getattr(presentation, field.name)
130     the_type = presentation._get_type(context)
131     if the_type is None:
132         return
133     use_entry_schema = type_uses_entry_schema(the_type)
134
135     if use_entry_schema:
136         if value is None:
137             context.validation.report(
138                 '"entry_schema" does not have a value as required by data type "%s" in "%s"'
139                 % (get_data_type_name(the_type), presentation._container._fullname),
140                 locator=presentation._locator, level=Issue.BETWEEN_TYPES)
141     else:
142         if value is not None:
143             context.validation.report(
144                 '"entry_schema" has a value but it is not used by data type "%s" in "%s"'
145                 % (get_data_type_name(the_type), presentation._container._fullname),
146                 locator=presentation._locator, level=Issue.BETWEEN_TYPES)
147
148
149 def data_value_validator(field, presentation, context):
150     """
151     Makes sure that the field contains a valid value according to data type and constraints.
152
153     Used with the :func:`field_validator` decorator for the ``default`` field in
154     :class:`PropertyDefinition` and :class:`AttributeDefinition`.
155     """
156
157     field.default_validate(presentation, context)
158
159     value = getattr(presentation, field.name)
160     if value is not None:
161         the_type = presentation._get_type(context)
162         entry_schema = presentation.entry_schema
163         # AttributeDefinition does not have this:
164         constraints = presentation._get_constraints(context) \
165             if hasattr(presentation, '_get_constraints') else None
166         coerce_value(context, presentation, the_type, entry_schema, constraints, value, field.name)
167
168
169 #
170 # DataType
171 #
172
173 _data_type_validator = data_type_validator()
174 _data_type_derived_from_validator = derived_from_validator(convert_name_to_full_type_name,
175                                                            'data_types')
176
177
178 def data_type_derived_from_validator(field, presentation, context):
179     """
180     Makes sure that the field refers to a valid parent data type (complex or primitive).
181
182     Used with the :func:`field_validator` decorator for the ``derived_from`` field in
183     :class:`DataType`.
184     """
185
186     if _data_type_validator(field, presentation, context):
187         # Validate derivation only if a complex data type (primitive types have no derivation
188         # hierarchy)
189         _data_type_derived_from_validator(field, presentation, context)
190
191
192 def data_type_constraints_validator(field, presentation, context):
193     """
194     Makes sure that we do not have constraints if we are a complex type (with no primitive
195     ancestor).
196     """
197
198     field.default_validate(presentation, context)
199
200     value = getattr(presentation, field.name)
201     if value is not None:
202         if presentation._get_primitive_ancestor(context) is None:
203             context.validation.report(
204                 'data type "%s" defines constraints but does not have a primitive ancestor'
205                 % presentation._fullname,
206                 locator=presentation._get_child_locator(field.name), level=Issue.BETWEEN_TYPES)
207
208
209 def data_type_properties_validator(field, presentation, context):
210     """
211     Makes sure that we do not have properties if we have a primitive ancestor.
212
213     Used with the :func:`field_validator` decorator for the ``properties`` field in
214     :class:`DataType`.
215     """
216
217     field.default_validate(presentation, context)
218
219     values = getattr(presentation, field.name)
220     if values is not None:
221         if presentation._get_primitive_ancestor(context) is not None:
222             context.validation.report(
223                 'data type "%s" defines properties even though it has a primitive ancestor'
224                 % presentation._fullname,
225                 locator=presentation._get_child_locator(field.name), level=Issue.BETWEEN_TYPES)
226
227
228 #
229 # ConstraintClause
230 #
231
232 def constraint_clause_field_validator(field, presentation, context):
233     """
234     Makes sure that field contains a valid value for the container type.
235
236     Used with the :func:`field_validator` decorator for various field in :class:`ConstraintClause`.
237     """
238
239     field.default_validate(presentation, context)
240
241     value = getattr(presentation, field.name)
242     if value is not None:
243         the_type = presentation._get_type(context)
244         constraints = the_type._get_constraints(context) \
245             if hasattr(the_type, '_get_constraints') else None
246         coerce_value(context, presentation, the_type, None, constraints, value, field.name)
247
248
249 def constraint_clause_in_range_validator(field, presentation, context):
250     """
251     Makes sure that the value is a list with exactly two elements, that both lower bound contains a
252     valid value for the container type, and that the upper bound is either "UNBOUNDED" or a valid
253     value for the container type.
254
255     Used with the :func:`field_validator` decorator for the ``in_range`` field in
256     :class:`ConstraintClause`.
257     """
258
259     field.default_validate(presentation, context)
260
261     values = getattr(presentation, field.name)
262     if isinstance(values, list):
263         # Make sure list has exactly two elements
264         if len(values) == 2:
265             lower, upper = values
266             the_type = presentation._get_type(context)
267
268             # Lower bound must be coercible
269             lower = coerce_value(context, presentation, the_type, None, None, lower, field.name)
270
271             if upper != 'UNBOUNDED':
272                 # Upper bound be coercible
273                 upper = coerce_value(context, presentation, the_type, None, None, upper, field.name)
274
275                 # Second "in_range" value must be greater than first
276                 if (lower is not None) and (upper is not None) and (lower >= upper):
277                     context.validation.report(
278                         'upper bound of "in_range" constraint is not greater than the lower bound'
279                         ' in "%s": %s <= %s'
280                         % (presentation._container._fullname, safe_repr(lower), safe_repr(upper)),
281                         locator=presentation._locator, level=Issue.FIELD)
282         else:
283             context.validation.report(
284                 'constraint "%s" is not a list of exactly 2 elements in "%s"'
285                 % (field.name, presentation._fullname),
286                 locator=presentation._get_child_locator(field.name), level=Issue.FIELD)
287
288
289 def constraint_clause_valid_values_validator(field, presentation, context):
290     """
291     Makes sure that the value is a list of valid values for the container type.
292
293     Used with the :func:`field_validator` decorator for the ``valid_values`` field in
294     :class:`ConstraintClause`.
295     """
296
297     field.default_validate(presentation, context)
298
299     values = getattr(presentation, field.name)
300     if isinstance(values, list):
301         the_type = presentation._get_type(context)
302         for value in values:
303             coerce_value(context, presentation, the_type, None, None, value, field.name)
304
305
306 def constraint_clause_pattern_validator(field, presentation, context):
307     """
308     Makes sure that the value is a valid regular expression.
309
310     Used with the :func:`field_validator` decorator for the ``pattern`` field in
311     :class:`ConstraintClause`.
312     """
313
314     field.default_validate(presentation, context)
315
316     value = getattr(presentation, field.name)
317     if value is not None:
318         try:
319             # From TOSCA 1.0 3.5.2.1:
320             #
321             # "Note: Future drafts of this specification will detail the use of regular expressions
322             # and reference an appropriate standardized grammar."
323             #
324             # So we will just use Python's.
325             re.compile(value)
326         except re.error as e:
327             context.validation.report(
328                 'constraint "%s" is not a valid regular expression in "%s"'
329                 % (field.name, presentation._fullname),
330                 locator=presentation._get_child_locator(field.name), level=Issue.FIELD, exception=e)
331
332
333 #
334 # RequirementAssignment
335 #
336
337 def node_template_or_type_validator(field, presentation, context):
338     """
339     Makes sure that the field refers to either a node template or a node type.
340
341     Used with the :func:`field_validator` decorator for the ``node`` field in
342     :class:`RequirementAssignment`.
343     """
344
345     field.default_validate(presentation, context)
346
347     value = getattr(presentation, field.name)
348     if value is not None:
349         node_templates = \
350             context.presentation.get('service_template', 'topology_template', 'node_templates') \
351             or {}
352         if (value not in node_templates) and \
353             (get_type_by_name(context, value, 'node_types') is None):
354             report_issue_for_unknown_type(context, presentation, 'node template or node type',
355                                           field.name)
356
357
358 def capability_definition_or_type_validator(field, presentation, context):
359     """
360     Makes sure refers to either a capability assignment name in the node template referred to by the
361     ``node`` field or a general capability type.
362
363     If the value refers to a capability type, make sure the ``node`` field was not assigned.
364
365     Used with the :func:`field_validator` decorator for the ``capability`` field in
366     :class:`RequirementAssignment`.
367     """
368
369     field.default_validate(presentation, context)
370
371     value = getattr(presentation, field.name)
372     if value is not None:
373         node, node_variant = presentation._get_node(context)
374         if node_variant == 'node_template':
375             capabilities = node._get_capabilities(context)
376             if value in capabilities:
377                 return
378
379         if get_type_by_name(context, value, 'capability_types') is not None:
380             if node is not None:
381                 context.validation.report(
382                     '"%s" refers to a capability type even though "node" has a value in "%s"'
383                     % (presentation._name, presentation._container._fullname),
384                     locator=presentation._get_child_locator(field.name), level=Issue.BETWEEN_FIELDS)
385             return
386
387         if node_variant == 'node_template':
388             context.validation.report(
389                 'requirement "%s" refers to an unknown capability definition name or capability'
390                 ' type in "%s": %s'
391                 % (presentation._name, presentation._container._fullname, safe_repr(value)),
392                 locator=presentation._get_child_locator(field.name), level=Issue.BETWEEN_TYPES)
393         else:
394             context.validation.report(
395                 'requirement "%s" refers to an unknown capability type in "%s": %s'
396                 % (presentation._name, presentation._container._fullname, safe_repr(value)),
397                 locator=presentation._get_child_locator(field.name), level=Issue.BETWEEN_TYPES)
398
399
400 def node_filter_validator(field, presentation, context):
401     """
402     Makes sure that the field has a value only if "node" refers to a node type.
403
404     Used with the :func:`field_validator` decorator for the ``node_filter`` field in
405     :class:`RequirementAssignment`.
406     """
407
408     field.default_validate(presentation, context)
409
410     value = getattr(presentation, field.name)
411     if value is not None:
412         _, node_type_variant = presentation._get_node(context)
413         if node_type_variant != 'node_type':
414             context.validation.report(
415                 'requirement "%s" has a node filter even though "node" does not refer to a node'
416                 ' type in "%s"'
417                 % (presentation._fullname, presentation._container._fullname),
418                 locator=presentation._locator, level=Issue.BETWEEN_FIELDS)
419
420
421 #
422 # RelationshipAssignment
423 #
424
425 def relationship_template_or_type_validator(field, presentation, context):
426     """
427     Makes sure that the field refers to either a relationship template or a relationship type.
428
429     Used with the :func:`field_validator` decorator for the ``type`` field in
430     :class:`RelationshipAssignment`.
431     """
432
433     field.default_validate(presentation, context)
434
435     value = getattr(presentation, field.name)
436     if value is not None:
437         relationship_templates = \
438             context.presentation.get('service_template', 'topology_template',
439                                      'relationship_templates') \
440             or {}
441         if (value not in relationship_templates) and \
442             (get_type_by_name(context, value, 'relationship_types') is None):
443             report_issue_for_unknown_type(context, presentation,
444                                           'relationship template or relationship type', field.name)
445
446
447 #
448 # PolicyType
449 #
450
451 def list_node_type_or_group_type_validator(field, presentation, context):
452     """
453     Makes sure that the field's elements refer to either node types or a group types.
454
455     Used with the :func:`field_validator` decorator for the ``targets`` field in
456     :class:`PolicyType`.
457     """
458
459     field.default_validate(presentation, context)
460
461     values = getattr(presentation, field.name)
462     if values is not None:
463         for value in values:
464             if (get_type_by_name(context, value, 'node_types') is None) and \
465                     (get_type_by_name(context, value, 'group_types') is None):
466                 report_issue_for_unknown_type(context, presentation, 'node type or group type',
467                                               field.name, value)
468
469
470 #
471 # PolicyTemplate
472 #
473
474 def policy_targets_validator(field, presentation, context):
475     """
476     Makes sure that the field's elements refer to either node templates or groups, and that
477     they match the node types and group types declared in the policy type.
478
479     Used with the :func:`field_validator` decorator for the ``targets`` field in
480     :class:`PolicyTemplate`.
481     """
482
483     field.default_validate(presentation, context)
484
485     values = getattr(presentation, field.name)
486     if values is not None:
487         for value in values:
488             node_templates = \
489                 context.presentation.get('service_template', 'topology_template',
490                                          'node_templates') \
491                 or {}
492             groups = context.presentation.get('service_template', 'topology_template', 'groups') \
493                 or {}
494             if (value not in node_templates) and (value not in groups):
495                 report_issue_for_unknown_type(context, presentation, 'node template or group',
496                                               field.name, value)
497
498             policy_type = presentation._get_type(context)
499             if policy_type is None:
500                 break
501
502             node_types, group_types = policy_type._get_targets(context)
503
504             is_valid = False
505
506             if value in node_templates:
507                 our_node_type = node_templates[value]._get_type(context)
508                 for node_type in node_types:
509                     if node_type._is_descendant(context, our_node_type):
510                         is_valid = True
511                         break
512
513             elif value in groups:
514                 our_group_type = groups[value]._get_type(context)
515                 for group_type in group_types:
516                     if group_type._is_descendant(context, our_group_type):
517                         is_valid = True
518                         break
519
520             if not is_valid:
521                 context.validation.report(
522                     'policy definition target does not match either a node type or a group type'
523                     ' declared in the policy type in "%s": %s'
524                     % (presentation._name, safe_repr(value)),
525                     locator=presentation._locator, level=Issue.BETWEEN_TYPES)
526
527
528 #
529 # NodeFilter
530 #
531
532 def node_filter_properties_validator(field, presentation, context):
533     """
534     Makes sure that the field's elements refer to defined properties in the target node type.
535
536     Used with the :func:`field_validator` decorator for the ``properties`` field in
537     :class:`NodeFilter`.
538     """
539
540     field.default_validate(presentation, context)
541
542     values = getattr(presentation, field.name)
543     if values is not None:
544         node_type = presentation._get_node_type(context)
545         if node_type is not None:
546             properties = node_type._get_properties(context)
547             for name, _ in values:
548                 if name not in properties:
549                     context.validation.report(
550                         'node filter refers to an unknown property definition in "%s": %s'
551                         % (node_type._name, name),
552                         locator=presentation._locator, level=Issue.BETWEEN_TYPES)
553
554
555 def node_filter_capabilities_validator(field, presentation, context):
556     """
557     Makes sure that the field's elements refer to defined capabilities and properties in the target
558     node type.
559
560     Used with the :func:`field_validator` decorator for the ``capabilities`` field in
561     :class:`NodeFilter`.
562     """
563
564     field.default_validate(presentation, context)
565
566     values = getattr(presentation, field.name)
567     if values is not None: # pylint: disable=too-many-nested-blocks
568         node_type = presentation._get_node_type(context)
569         if node_type is not None:
570             capabilities = node_type._get_capabilities(context)
571             for name, value in values:
572                 capability = capabilities.get(name)
573                 if capability is not None:
574                     properties = value.properties
575                     capability_properties = capability.properties
576                     if (properties is not None) and (capability_properties is not None):
577                         for property_name, _ in properties:
578                             if property_name not in capability_properties:
579                                 context.validation.report(
580                                     'node filter refers to an unknown capability definition'
581                                     ' property in "%s": %s'
582                                     % (node_type._name, property_name),
583                                     locator=presentation._locator, level=Issue.BETWEEN_TYPES)
584                 else:
585                     context.validation.report(
586                         'node filter refers to an unknown capability definition in "%s": %s'
587                         % (node_type._name, name),
588                         locator=presentation._locator, level=Issue.BETWEEN_TYPES)