| # Licensed to the Apache Software Foundation (ASF) under one or more |
| # contributor license agreements. See the NOTICE file distributed with |
| # this work for additional information regarding copyright ownership. |
| # The ASF licenses this file to You 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. |
| |
| import re |
| |
| from aria.utils.formatting import safe_repr |
| from aria.parser import implements_specification |
| from aria.parser.presentation import (report_issue_for_unknown_type, derived_from_validator) |
| from aria.parser.validation import Issue |
| |
| from ..modeling.data_types import (get_primitive_data_type, get_data_type_name, coerce_value, |
| get_container_data_type) |
| from .types import get_type_by_full_or_shorthand_name, convert_shorthand_to_full_type_name |
| |
| # |
| # NodeTemplate, RelationshipTemplate |
| # |
| |
| @implements_specification('3.7.3.3', 'tosca-simple-1.0') |
| def copy_validator(template_type_name, templates_dict_name): |
| """ |
| Makes sure that the field refers to an existing template defined in the root presenter. |
| |
| Use with the :func:`field_validator` decorator for the :code:`copy` field in |
| :class:`NodeTemplate` and :class:`RelationshipTemplate`. |
| """ |
| |
| def validator_fn(field, presentation, context): |
| field.default_validate(presentation, context) |
| |
| # Make sure type exists |
| value = getattr(presentation, field.name) |
| if value is not None: |
| copy = context.presentation.get_from_dict('service_template', 'topology_template', |
| templates_dict_name, value) |
| if copy is None: |
| report_issue_for_unknown_type(context, presentation, template_type_name, field.name) |
| else: |
| if copy.copy is not None: |
| context.validation.report( |
| '"copy" field refers to a %s that itself is a copy in "%s": %s' |
| % (template_type_name, presentation._fullname, safe_repr(value)), |
| locator=presentation._locator, level=Issue.BETWEEN_TYPES) |
| |
| return validator_fn |
| |
| # |
| # PropertyDefinition, AttributeDefinition, ParameterDefinition, EntrySchema |
| # |
| |
| def data_type_validator(type_name='data type'): |
| """ |
| Makes sure that the field refers to a valid data type, whether complex or primitive. |
| |
| Used with the :func:`field_validator` decorator for the :code:`type` fields in |
| :class:`PropertyDefinition`, :class:`AttributeDefinition`, :class:`ParameterDefinition`, |
| and :class:`EntrySchema`. |
| |
| Extra behavior beyond validation: generated function returns true if field is a complex data |
| type. |
| """ |
| |
| def validator(field, presentation, context): |
| field.default_validate(presentation, context) |
| |
| value = getattr(presentation, field.name) |
| if value is not None: |
| # Test for circular definitions |
| container_data_type = get_container_data_type(presentation) |
| if (container_data_type is not None) and (container_data_type._name == value): |
| context.validation.report( |
| 'type of property "%s" creates a circular value hierarchy: %s' |
| % (presentation._fullname, safe_repr(value)), |
| locator=presentation._get_child_locator('type'), level=Issue.BETWEEN_TYPES) |
| |
| # Can be a complex data type |
| if get_type_by_full_or_shorthand_name(context, value, 'data_types') is not None: |
| return True |
| |
| # Can be a primitive data type |
| if get_primitive_data_type(value) is None: |
| report_issue_for_unknown_type(context, presentation, type_name, field.name) |
| |
| return False |
| |
| return validator |
| |
| # |
| # PropertyDefinition, AttributeDefinition |
| # |
| |
| def entry_schema_validator(field, presentation, context): |
| """ |
| According to whether the data type supports :code:`entry_schema` (e.g., it is or inherits from |
| list or map), make sure that we either have or don't have a valid data type value. |
| |
| Used with the :func:`field_validator` decorator for the :code:`entry_schema` field in |
| :class:`PropertyDefinition` and :class:`AttributeDefinition`. |
| """ |
| |
| field.default_validate(presentation, context) |
| |
| def type_uses_entry_schema(the_type): |
| use_entry_schema = the_type._get_extension('use_entry_schema', False) \ |
| if hasattr(the_type, '_get_extension') else False |
| if use_entry_schema: |
| return True |
| parent = the_type._get_parent(context) if hasattr(the_type, '_get_parent') else None |
| if parent is None: |
| return False |
| return type_uses_entry_schema(parent) |
| |
| value = getattr(presentation, field.name) |
| the_type = presentation._get_type(context) |
| if the_type is None: |
| return |
| use_entry_schema = type_uses_entry_schema(the_type) |
| |
| if use_entry_schema: |
| if value is None: |
| context.validation.report( |
| '"entry_schema" does not have a value as required by data type "%s" in "%s"' |
| % (get_data_type_name(the_type), presentation._container._fullname), |
| locator=presentation._locator, level=Issue.BETWEEN_TYPES) |
| else: |
| if value is not None: |
| context.validation.report( |
| '"entry_schema" has a value but it is not used by data type "%s" in "%s"' |
| % (get_data_type_name(the_type), presentation._container._fullname), |
| locator=presentation._locator, level=Issue.BETWEEN_TYPES) |
| |
| def data_value_validator(field, presentation, context): |
| """ |
| Makes sure that the field contains a valid value according to data type and constraints. |
| |
| Used with the :func:`field_validator` decorator for the :code:`default` field in |
| :class:`PropertyDefinition` and :class:`AttributeDefinition`. |
| """ |
| |
| field.default_validate(presentation, context) |
| |
| value = getattr(presentation, field.name) |
| if value is not None: |
| the_type = presentation._get_type(context) |
| entry_schema = presentation.entry_schema |
| # AttributeDefinition does not have this: |
| constraints = presentation._get_constraints(context) \ |
| if hasattr(presentation, '_get_constraints') else None |
| coerce_value(context, presentation, the_type, entry_schema, constraints, value, field.name) |
| |
| # |
| # DataType |
| # |
| |
| _data_type_validator = data_type_validator() |
| _data_type_derived_from_validator = derived_from_validator(convert_shorthand_to_full_type_name, |
| 'data_types') |
| |
| def data_type_derived_from_validator(field, presentation, context): |
| """ |
| Makes sure that the field refers to a valid parent data type (complex or primitive). |
| |
| Used with the :func:`field_validator` decorator for the :code:`derived_from` field in |
| :class:`DataType`. |
| """ |
| |
| if _data_type_validator(field, presentation, context): |
| # Validate derivation only if a complex data type (primitive types have no derivation |
| # hierarchy) |
| _data_type_derived_from_validator(field, presentation, context) |
| |
| def data_type_constraints_validator(field, presentation, context): |
| """ |
| Makes sure that we do not have constraints if we are a complex type (with no primitive |
| ancestor). |
| """ |
| |
| field.default_validate(presentation, context) |
| |
| value = getattr(presentation, field.name) |
| if value is not None: |
| if presentation._get_primitive_ancestor(context) is None: |
| context.validation.report( |
| 'data type "%s" defines constraints but does not have a primitive ancestor' |
| % presentation._fullname, |
| locator=presentation._get_child_locator(field.name), level=Issue.BETWEEN_TYPES) |
| |
| def data_type_properties_validator(field, presentation, context): |
| """ |
| Makes sure that we do not have properties if we have a primitive ancestor. |
| |
| Used with the :func:`field_validator` decorator for the :code:`properties` field in |
| :class:`DataType`. |
| """ |
| |
| field.default_validate(presentation, context) |
| |
| values = getattr(presentation, field.name) |
| if values is not None: |
| if presentation._get_primitive_ancestor(context) is not None: |
| context.validation.report( |
| 'data type "%s" defines properties even though it has a primitive ancestor' |
| % presentation._fullname, |
| locator=presentation._get_child_locator(field.name), level=Issue.BETWEEN_TYPES) |
| |
| # |
| # ConstraintClause |
| # |
| |
| def constraint_clause_field_validator(field, presentation, context): |
| """ |
| Makes sure that field contains a valid value for the container type. |
| |
| Used with the :func:`field_validator` decorator for various field in :class:`ConstraintClause`. |
| """ |
| |
| field.default_validate(presentation, context) |
| |
| value = getattr(presentation, field.name) |
| if value is not None: |
| the_type = presentation._get_type(context) |
| constraints = the_type._get_constraints(context) \ |
| if hasattr(the_type, '_get_constraints') else None |
| coerce_value(context, presentation, the_type, None, constraints, value, field.name) |
| |
| def constraint_clause_in_range_validator(field, presentation, context): |
| """ |
| Makes sure that the value is a list with exactly two elements, that both lower bound contains a |
| valid value for the container type, and that the upper bound is either "UNBOUNDED" or a valid |
| value for the container type. |
| |
| Used with the :func:`field_validator` decorator for the :code:`in_range` field in |
| :class:`ConstraintClause`. |
| """ |
| |
| field.default_validate(presentation, context) |
| |
| values = getattr(presentation, field.name) |
| if isinstance(values, list): |
| # Make sure list has exactly two elements |
| if len(values) == 2: |
| lower, upper = values |
| the_type = presentation._get_type(context) |
| |
| # Lower bound must be coercible |
| lower = coerce_value(context, presentation, the_type, None, None, lower, field.name) |
| |
| if upper != 'UNBOUNDED': |
| # Upper bound be coercible |
| upper = coerce_value(context, presentation, the_type, None, None, upper, field.name) |
| |
| # Second "in_range" value must be greater than first |
| if (lower is not None) and (upper is not None) and (lower >= upper): |
| context.validation.report( |
| 'upper bound of "in_range" constraint is not greater than the lower bound' |
| ' in "%s": %s <= %s' |
| % (presentation._container._fullname, safe_repr(lower), safe_repr(upper)), |
| locator=presentation._locator, level=Issue.FIELD) |
| else: |
| context.validation.report( |
| 'constraint "%s" is not a list of exactly 2 elements in "%s"' |
| % (field.name, presentation._fullname), |
| locator=presentation._get_child_locator(field.name), level=Issue.FIELD) |
| |
| def constraint_clause_valid_values_validator(field, presentation, context): |
| """ |
| Makes sure that the value is a list of valid values for the container type. |
| |
| Used with the :func:`field_validator` decorator for the :code:`valid_values` field in |
| :class:`ConstraintClause`. |
| """ |
| |
| field.default_validate(presentation, context) |
| |
| values = getattr(presentation, field.name) |
| if isinstance(values, list): |
| the_type = presentation._get_type(context) |
| for value in values: |
| coerce_value(context, presentation, the_type, None, None, value, field.name) |
| |
| def constraint_clause_pattern_validator(field, presentation, context): |
| """ |
| Makes sure that the value is a valid regular expression. |
| |
| Used with the :func:`field_validator` decorator for the :code:`pattern` field in |
| :class:`ConstraintClause`. |
| """ |
| |
| field.default_validate(presentation, context) |
| |
| value = getattr(presentation, field.name) |
| if value is not None: |
| try: |
| # From TOSCA 1.0 3.5.2.1: |
| # |
| # "Note: Future drafts of this specification will detail the use of regular expressions |
| # and reference an appropriate standardized grammar." |
| # |
| # So we will just use Python's. |
| re.compile(value) |
| except re.error as e: |
| context.validation.report( |
| 'constraint "%s" is not a valid regular expression in "%s"' |
| % (field.name, presentation._fullname), |
| locator=presentation._get_child_locator(field.name), level=Issue.FIELD, exception=e) |
| |
| # |
| # RequirementAssignment |
| # |
| |
| def node_template_or_type_validator(field, presentation, context): |
| """ |
| Makes sure that the field refers to either a node template or a node type. |
| |
| Used with the :func:`field_validator` decorator for the :code:`node` field in |
| :class:`RequirementAssignment`. |
| """ |
| |
| field.default_validate(presentation, context) |
| |
| value = getattr(presentation, field.name) |
| if value is not None: |
| node_templates = \ |
| context.presentation.get('service_template', 'topology_template', 'node_templates') \ |
| or {} |
| if (value not in node_templates) and \ |
| (get_type_by_full_or_shorthand_name(context, value, 'node_types') is None): |
| report_issue_for_unknown_type(context, presentation, 'node template or node type', |
| field.name) |
| |
| def capability_definition_or_type_validator(field, presentation, context): |
| """ |
| Makes sure refers to either a capability assignment name in the node template referred to by the |
| :code:`node` field or a general capability type. |
| |
| If the value refers to a capability type, make sure the :code:`node` field was not assigned. |
| |
| Used with the :func:`field_validator` decorator for the :code:`capability` field in |
| :class:`RequirementAssignment`. |
| """ |
| |
| field.default_validate(presentation, context) |
| |
| value = getattr(presentation, field.name) |
| if value is not None: |
| node, node_variant = presentation._get_node(context) |
| if node_variant == 'node_template': |
| capabilities = node._get_capabilities(context) |
| if value in capabilities: |
| return |
| |
| if get_type_by_full_or_shorthand_name(context, value, 'capability_types') is not None: |
| if node is not None: |
| context.validation.report( |
| '"%s" refers to a capability type even though "node" has a value in "%s"' |
| % (presentation._name, presentation._container._fullname), |
| locator=presentation._get_child_locator(field.name), level=Issue.BETWEEN_FIELDS) |
| return |
| |
| if node_variant == 'node_template': |
| context.validation.report( |
| 'requirement "%s" refers to an unknown capability definition name or capability' |
| ' type in "%s": %s' |
| % (presentation._name, presentation._container._fullname, safe_repr(value)), |
| locator=presentation._get_child_locator(field.name), level=Issue.BETWEEN_TYPES) |
| else: |
| context.validation.report( |
| 'requirement "%s" refers to an unknown capability type in "%s": %s' |
| % (presentation._name, presentation._container._fullname, safe_repr(value)), |
| locator=presentation._get_child_locator(field.name), level=Issue.BETWEEN_TYPES) |
| |
| def node_filter_validator(field, presentation, context): |
| """ |
| Makes sure that the field has a value only if "node" refers to a node type. |
| |
| Used with the :func:`field_validator` decorator for the :code:`node_filter` field in |
| :class:`RequirementAssignment`. |
| """ |
| |
| field.default_validate(presentation, context) |
| |
| value = getattr(presentation, field.name) |
| if value is not None: |
| _, node_type_variant = presentation._get_node(context) |
| if node_type_variant != 'node_type': |
| context.validation.report( |
| 'requirement "%s" has a node filter even though "node" does not refer to a node' |
| ' type in "%s"' |
| % (presentation._fullname, presentation._container._fullname), |
| locator=presentation._locator, level=Issue.BETWEEN_FIELDS) |
| |
| # |
| # RelationshipAssignment |
| # |
| |
| def relationship_template_or_type_validator(field, presentation, context): |
| """ |
| Makes sure that the field refers to either a relationship template or a relationship type. |
| |
| Used with the :func:`field_validator` decorator for the :code:`type` field in |
| :class:`RelationshipAssignment`. |
| """ |
| |
| field.default_validate(presentation, context) |
| |
| value = getattr(presentation, field.name) |
| if value is not None: |
| relationship_templates = \ |
| context.presentation.get('service_template', 'topology_template', |
| 'relationship_templates') \ |
| or {} |
| if (value not in relationship_templates) and \ |
| (get_type_by_full_or_shorthand_name(context, value, 'relationship_types') is None): |
| report_issue_for_unknown_type(context, presentation, |
| 'relationship template or relationship type', field.name) |
| |
| # |
| # PolicyType |
| # |
| |
| def list_node_type_or_group_type_validator(field, presentation, context): |
| """ |
| Makes sure that the field's elements refer to either node types or a group types. |
| |
| Used with the :func:`field_validator` decorator for the :code:`targets` field in |
| :class:`PolicyType`. |
| """ |
| |
| field.default_validate(presentation, context) |
| |
| values = getattr(presentation, field.name) |
| if values is not None: |
| for value in values: |
| if \ |
| (get_type_by_full_or_shorthand_name(context, value, 'node_types') is None) and \ |
| (get_type_by_full_or_shorthand_name(context, value, 'group_types') is None): |
| report_issue_for_unknown_type(context, presentation, 'node type or group type', |
| field.name, value) |
| |
| # |
| # PolicyTemplate |
| # |
| |
| def policy_targets_validator(field, presentation, context): |
| """ |
| Makes sure that the field's elements refer to either node templates or groups, and that |
| they match the node types and group types declared in the policy type. |
| |
| Used with the :func:`field_validator` decorator for the :code:`targets` field in |
| :class:`PolicyTemplate`. |
| """ |
| |
| field.default_validate(presentation, context) |
| |
| values = getattr(presentation, field.name) |
| if values is not None: |
| for value in values: |
| node_templates = \ |
| context.presentation.get('service_template', 'topology_template', |
| 'node_templates') \ |
| or {} |
| groups = context.presentation.get('service_template', 'topology_template', 'groups') \ |
| or {} |
| if (value not in node_templates) and (value not in groups): |
| report_issue_for_unknown_type(context, presentation, 'node template or group', |
| field.name, value) |
| |
| policy_type = presentation._get_type(context) |
| if policy_type is None: |
| break |
| |
| node_types, group_types = policy_type._get_targets(context) |
| |
| is_valid = False |
| |
| if value in node_templates: |
| our_node_type = node_templates[value]._get_type(context) |
| for node_type in node_types: |
| if node_type._is_descendant(context, our_node_type): |
| is_valid = True |
| break |
| |
| elif value in groups: |
| our_group_type = groups[value]._get_type(context) |
| for group_type in group_types: |
| if group_type._is_descendant(context, our_group_type): |
| is_valid = True |
| break |
| |
| if not is_valid: |
| context.validation.report( |
| 'policy definition target does not match either a node type or a group type' |
| ' declared in the policy type in "%s": %s' |
| % (presentation._name, safe_repr(value)), |
| locator=presentation._locator, level=Issue.BETWEEN_TYPES) |
| |
| # |
| # NodeFilter |
| # |
| |
| def node_filter_properties_validator(field, presentation, context): |
| """ |
| Makes sure that the field's elements refer to defined properties in the target node type. |
| |
| Used with the :func:`field_validator` decorator for the :code:`properties` field in |
| :class:`NodeFilter`. |
| """ |
| |
| field.default_validate(presentation, context) |
| |
| values = getattr(presentation, field.name) |
| if values is not None: |
| node_type = presentation._get_node_type(context) |
| if node_type is not None: |
| properties = node_type._get_properties(context) |
| for name, _ in values: |
| if name not in properties: |
| context.validation.report( |
| 'node filter refers to an unknown property definition in "%s": %s' |
| % (node_type._name, name), |
| locator=presentation._locator, level=Issue.BETWEEN_TYPES) |
| |
| def node_filter_capabilities_validator(field, presentation, context): |
| """ |
| Makes sure that the field's elements refer to defined capabilities and properties in the target |
| node type. |
| |
| Used with the :func:`field_validator` decorator for the :code:`capabilities` field in |
| :class:`NodeFilter`. |
| """ |
| |
| field.default_validate(presentation, context) |
| |
| values = getattr(presentation, field.name) |
| if values is not None: # pylint: disable=too-many-nested-blocks |
| node_type = presentation._get_node_type(context) |
| if node_type is not None: |
| capabilities = node_type._get_capabilities(context) |
| for name, value in values: |
| capability = capabilities.get(name) |
| if capability is not None: |
| properties = value.properties |
| capability_properties = capability.properties |
| if (properties is not None) and (capability_properties is not None): |
| for property_name, _ in properties: |
| if property_name not in capability_properties: |
| context.validation.report( |
| 'node filter refers to an unknown capability definition' |
| ' property in "%s": %s' |
| % (node_type._name, property_name), |
| locator=presentation._locator, level=Issue.BETWEEN_TYPES) |
| else: |
| context.validation.report( |
| 'node filter refers to an unknown capability definition in "%s": %s' |
| % (node_type._name, name), |
| locator=presentation._locator, level=Issue.BETWEEN_TYPES) |