blob: be80702e4b816cdfe594922fce0899d056439ace [file] [log] [blame]
# 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 ``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 ``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 ``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 ``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 ``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 ``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 ``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 ``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 ``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 ``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 ``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
``node`` field or a general capability type.
If the value refers to a capability type, make sure the ``node`` field was not assigned.
Used with the :func:`field_validator` decorator for the ``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 ``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 ``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 ``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 ``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 ``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 ``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)