http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/definitions.py ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/simple_v1_0/definitions.py b/extensions/aria_extension_tosca/simple_v1_0/definitions.py index b60a797..8564249 100644 --- a/extensions/aria_extension_tosca/simple_v1_0/definitions.py +++ b/extensions/aria_extension_tosca/simple_v1_0/definitions.py @@ -15,7 +15,7 @@ from aria.utils.collections import FrozenDict from aria.utils.caching import cachedmethod -from aria.parser import dsl_specification +from aria.parser import implements_specification from aria.parser.presentation import (has_fields, short_form_field, allow_unknown_fields, primitive_field, primitive_list_field, object_field, object_list_field, object_dict_field, @@ -35,7 +35,7 @@ from .modeling.interfaces import (get_and_override_input_definitions_from_type, get_and_override_operation_definitions_from_type) @has_fields -@dsl_specification('3.5.8', 'tosca-simple-1.0') +@implements_specification('3.5.8', 'tosca-simple-1.0') class PropertyDefinition(ExtensiblePresentation): """ A property definition defines a named, typed value and related data that can be associated with @@ -86,7 +86,7 @@ class PropertyDefinition(ExtensiblePresentation): @primitive_field(str, default='supported', allowed=('supported', 'unsupported', 'experimental', 'deprecated')) - @dsl_specification(section='3.5.8.3', spec='tosca-simple-1.0') + @implements_specification(section='3.5.8.3', spec='tosca-simple-1.0') def status(self): """ The optional status of the property relative to the specification or implementation. @@ -121,7 +121,7 @@ class PropertyDefinition(ExtensiblePresentation): return get_property_constraints(context, self) @has_fields -@dsl_specification('3.5.10', 'tosca-simple-1.0') +@implements_specification('3.5.10', 'tosca-simple-1.0') class AttributeDefinition(ExtensiblePresentation): """ An attribute definition defines a named, typed value that can be associated with an entity @@ -190,7 +190,7 @@ class AttributeDefinition(ExtensiblePresentation): return get_data_type(context, self, 'type') @has_fields -@dsl_specification('3.5.12', 'tosca-simple-1.0') +@implements_specification('3.5.12', 'tosca-simple-1.0') class ParameterDefinition(PropertyDefinition): """ A parameter definition is essentially a TOSCA property definition; however, it also allows a @@ -225,7 +225,7 @@ class ParameterDefinition(PropertyDefinition): @short_form_field('implementation') @has_fields -@dsl_specification('3.5.13-1', 'tosca-simple-1.0') +@implements_specification('3.5.13-1', 'tosca-simple-1.0') class OperationDefinition(ExtensiblePresentation): """ An operation definition defines a named function or procedure that can be bound to an @@ -266,7 +266,7 @@ class OperationDefinition(ExtensiblePresentation): @allow_unknown_fields @has_fields -@dsl_specification('3.5.14-1', 'tosca-simple-1.0') +@implements_specification('3.5.14-1', 'tosca-simple-1.0') class InterfaceDefinition(ExtensiblePresentation): """ An interface definition defines a named interface that can be associated with a Node or @@ -352,7 +352,7 @@ class RelationshipDefinition(ExtensiblePresentation): @short_form_field('capability') @has_fields -@dsl_specification('3.6.2', 'tosca-simple-1.0') +@implements_specification('3.6.2', 'tosca-simple-1.0') class RequirementDefinition(ExtensiblePresentation): """ The Requirement definition describes a named requirement (dependencies) of a TOSCA Node Type or @@ -418,7 +418,7 @@ class RequirementDefinition(ExtensiblePresentation): @short_form_field('type') @has_fields -@dsl_specification('3.6.1', 'tosca-simple-1.0') +@implements_specification('3.6.1', 'tosca-simple-1.0') class CapabilityDefinition(ExtensiblePresentation): """ A capability definition defines a named, typed set of data that can be associated with Node Type
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/filters.py ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/simple_v1_0/filters.py b/extensions/aria_extension_tosca/simple_v1_0/filters.py index 617ce7a..838b505 100644 --- a/extensions/aria_extension_tosca/simple_v1_0/filters.py +++ b/extensions/aria_extension_tosca/simple_v1_0/filters.py @@ -14,7 +14,7 @@ # limitations under the License. from aria.utils.caching import cachedmethod -from aria.parser import dsl_specification +from aria.parser import implements_specification from aria.parser.presentation import (has_fields, object_sequenced_list_field, field_validator) from .misc import ConstraintClause @@ -45,7 +45,7 @@ class CapabilityFilter(ExtensiblePresentation): return None @has_fields -@dsl_specification('3.5.4', 'tosca-simple-1.0') +@implements_specification('3.5.4', 'tosca-simple-1.0') class NodeFilter(ExtensiblePresentation): """ A node filter definition defines criteria for selection of a TOSCA Node Template based upon the @@ -58,7 +58,7 @@ class NodeFilter(ExtensiblePresentation): @field_validator(node_filter_properties_validator) @object_sequenced_list_field(ConstraintClause) - @dsl_specification('3.5.3', 'tosca-simple-1.0') + @implements_specification('3.5.3', 'tosca-simple-1.0') def properties(self): """ An optional sequenced list of property filters that would be used to select (filter) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/functions.py ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/simple_v1_0/functions.py b/extensions/aria_extension_tosca/simple_v1_0/functions.py deleted file mode 100644 index 2f77420..0000000 --- a/extensions/aria_extension_tosca/simple_v1_0/functions.py +++ /dev/null @@ -1,536 +0,0 @@ -# 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. - -from cStringIO import StringIO - -from aria.utils.collections import FrozenList -from aria.utils.formatting import as_raw, safe_repr -from aria.parser import dsl_specification -from aria.parser.exceptions import InvalidValueError -from aria.parser.validation import Issue -from aria.modeling.exceptions import CannotEvaluateFunctionException -from aria.modeling.functions import Function - -# -# Intrinsic -# - -@dsl_specification('4.3.1', 'tosca-simple-1.0') -class Concat(Function): - """ - The :code:`concat` function is used to concatenate two or more string values within a TOSCA - service template. - """ - - def __init__(self, context, presentation, argument): - self.locator = presentation._locator - - if not isinstance(argument, list): - raise InvalidValueError( - 'function "concat" argument must be a list of string expressions: %s' - % safe_repr(argument), - locator=self.locator) - - string_expressions = [] - for index, an_argument in enumerate(argument): - string_expressions.append(parse_string_expression(context, presentation, 'concat', - index, None, an_argument)) - self.string_expressions = FrozenList(string_expressions) - - @property - def as_raw(self): - string_expressions = [] - for string_expression in self.string_expressions: - if hasattr(string_expression, 'as_raw'): - string_expression = as_raw(string_expression) - string_expressions.append(string_expression) - return {'concat': string_expressions} - - def _evaluate(self, context, container): - value = StringIO() - for e in self.string_expressions: - if hasattr(e, '_evaluate'): - e = e._evaluate(context, container) - value.write(str(e)) - return value.getvalue() - -@dsl_specification('4.3.2', 'tosca-simple-1.0') -class Token(Function): - """ - The :code:`token` function is used within a TOSCA service template on a string to parse out - (tokenize) substrings separated by one or more token characters within a larger string. - """ - - def __init__(self, context, presentation, argument): - self.locator = presentation._locator - - if (not isinstance(argument, list)) or (len(argument) != 3): - raise InvalidValueError('function "token" argument must be a list of 3 parameters: %s' - % safe_repr(argument), - locator=self.locator) - - self.string_with_tokens = parse_string_expression(context, presentation, 'token', 0, - 'the string to tokenize', argument[0]) - self.string_of_token_chars = parse_string_expression(context, presentation, 'token', 1, - 'the token separator characters', - argument[1]) - self.substring_index = parse_int(context, presentation, 'token', 2, - 'the 0-based index of the token to return', argument[2]) - - @property - def as_raw(self): - string_with_tokens = self.string_with_tokens - if hasattr(string_with_tokens, 'as_raw'): - string_with_tokens = as_raw(string_with_tokens) - string_of_token_chars = self.string_with_tokens - if hasattr(string_of_token_chars, 'as_raw'): - string_of_token_chars = as_raw(string_of_token_chars) - return {'token': [string_with_tokens, string_of_token_chars, self.substring_index]} - - def _evaluate(self, context, container): - string_with_tokens = self.string_with_tokens - if hasattr(string_with_tokens, '_evaluate'): - string_with_tokens = string_with_tokens._evaluate(context, container) # pylint: disable=no-member - -# -# Property -# - -@dsl_specification('4.4.1', 'tosca-simple-1.0') -class GetInput(Function): - """ - The :code:`get_input` function is used to retrieve the values of properties declared within the - inputs section of a TOSCA Service Template. - """ - - def __init__(self, context, presentation, argument): - self.locator = presentation._locator - - self.input_property_name = parse_string_expression(context, presentation, 'get_input', - None, 'the input property name', - argument) - - if isinstance(self.input_property_name, basestring): - the_input = context.presentation.get_from_dict('service_template', 'topology_template', - 'inputs', self.input_property_name) - if the_input is None: - raise InvalidValueError( - 'function "get_input" argument is not a valid input name: %s' - % safe_repr(argument), - locator=self.locator) - - @property - def as_raw(self): - return {'get_input': as_raw(self.input_property_name)} - - def _evaluate(self, context, container): # pylint: disable=unused-argument - if not context.modeling.instance: - raise CannotEvaluateFunctionException() - the_input = context.modeling.instance.inputs.get( - self.input_property_name, - context.modeling.template.inputs.get(self.input_property_name)) - return as_raw(the_input.value) if the_input is not None else None - -@dsl_specification('4.4.2', 'tosca-simple-1.0') -class GetProperty(Function): - """ - The :code:`get_property` function is used to retrieve property values between modelable entities - defined in the same service template. - """ - - def __init__(self, context, presentation, argument): - self.locator = presentation._locator - - if (not isinstance(argument, list)) or (len(argument) < 2): - raise InvalidValueError( - 'function "get_property" argument must be a list of at least 2 string expressions: ' - '%s' - % safe_repr(argument), - locator=self.locator) - - self.modelable_entity_name = parse_modelable_entity_name(context, presentation, - 'get_property', 0, argument[0]) - # The first of these will be tried as a req-or-cap name: - self.nested_property_name_or_index = argument[1:] - - @property - def as_raw(self): - return {'get_property': [self.modelable_entity_name] + self.nested_property_name_or_index} - - def _evaluate(self, context, container): - modelable_entities = get_modelable_entities(context, container, self.locator, - self.modelable_entity_name) - req_or_cap_name = self.nested_property_name_or_index[0] - - for modelable_entity in modelable_entities: - properties = None - - if hasattr(modelable_entity, 'requirement_templates') \ - and modelable_entity.requirement_templates \ - and (req_or_cap_name in [v.name for v in modelable_entity.requirement_templates]): - for requirement_template in modelable_entity.requirement_templates: - if requirement_template.name == req_or_cap_name: - # First argument refers to a requirement - # TODO: should follow to matched capability in other node... - raise CannotEvaluateFunctionException() - # break - nested_property_name_or_index = self.nested_property_name_or_index[1:] - elif hasattr(modelable_entity, 'capability_templates') \ - and modelable_entity.capability_templates \ - and (req_or_cap_name in modelable_entity.capability_templates): - # First argument refers to a capability - properties = modelable_entity.capability_templates[req_or_cap_name].properties - nested_property_name_or_index = self.nested_property_name_or_index[1:] - else: - properties = modelable_entity.properties - nested_property_name_or_index = self.nested_property_name_or_index - - if properties: - found = True - value = properties - for name in nested_property_name_or_index: - if (isinstance(value, dict) and (name in value)) \ - or (isinstance(value, list) and name < len(list)): - value = value[name] - if hasattr(value, '_evaluate'): - value = value._evaluate(context, modelable_entity) - else: - found = False - break - if found: - return as_raw(value) - - raise InvalidValueError( - 'function "get_property" could not find "%s" in modelable entity "%s"' \ - % ('.'.join(self.nested_property_name_or_index), self.modelable_entity_name), - locator=self.locator) - -# -# Attribute -# - -@dsl_specification('4.5.1', 'tosca-simple-1.0') -class GetAttribute(Function): - """ - The :code:`get_attribute` function is used to retrieve the values of named attributes declared - by the referenced node or relationship template name. - """ - - def __init__(self, context, presentation, argument): - self.locator = presentation._locator - - if (not isinstance(argument, list)) or (len(argument) < 2): - raise InvalidValueError( - 'function "get_attribute" argument must be a list of at least 2 string expressions:' - ' %s' - % safe_repr(argument), - locator=self.locator) - - self.modelable_entity_name = parse_modelable_entity_name(context, presentation, - 'get_attribute', 0, argument[0]) - # The first of these will be tried as a req-or-cap name: - self.nested_property_name_or_index = argument[1:] - - @property - def as_raw(self): - return {'get_attribute': [self.modelable_entity_name] + self.nested_property_name_or_index} - - def _evaluate(self, context, container): # pylint: disable=no-self-use,unused-argument - raise CannotEvaluateFunctionException() - -# -# Operation -# - -@dsl_specification('4.6.1', 'tosca-simple-1.0') # pylint: disable=abstract-method -class GetOperationOutput(Function): - """ - The :code:`get_operation_output` function is used to retrieve the values of variables exposed / - exported from an interface operation. - """ - - def __init__(self, context, presentation, argument): - self.locator = presentation._locator - - if (not isinstance(argument, list)) or (len(argument) != 4): - raise InvalidValueError( - 'function "get_operation_output" argument must be a list of 4 parameters: %s' - % safe_repr(argument), - locator=self.locator) - - self.modelable_entity_name = parse_string_expression(context, presentation, - 'get_operation_output', 0, - 'modelable entity name', argument[0]) - self.interface_name = parse_string_expression(context, presentation, 'get_operation_output', - 1, 'the interface name', argument[1]) - self.operation_name = parse_string_expression(context, presentation, 'get_operation_output', - 2, 'the operation name', argument[2]) - self.output_variable_name = parse_string_expression(context, presentation, - 'get_operation_output', 3, - 'the output name', argument[3]) - - @property - def as_raw(self): - interface_name = self.interface_name - if hasattr(interface_name, 'as_raw'): - interface_name = as_raw(interface_name) - operation_name = self.operation_name - if hasattr(operation_name, 'as_raw'): - operation_name = as_raw(operation_name) - output_variable_name = self.output_variable_name - if hasattr(output_variable_name, 'as_raw'): - output_variable_name = as_raw(output_variable_name) - return {'get_operation_output': [self.modelable_entity_name, interface_name, operation_name, - output_variable_name]} - -# -# Navigation -# - -@dsl_specification('4.7.1', 'tosca-simple-1.0') -class GetNodesOfType(Function): - """ - The :code:`get_nodes_of_type` function can be used to retrieve a list of all known instances of - nodes of the declared Node Type. - """ - - def __init__(self, context, presentation, argument): - self.locator = presentation._locator - - self.node_type_name = parse_string_expression(context, presentation, 'get_nodes_of_type', - None, 'the node type name', argument) - - if isinstance(self.node_type_name, basestring): - node_types = context.presentation.get('service_template', 'node_types') - if (node_types is None) or (self.node_type_name not in node_types): - raise InvalidValueError( - 'function "get_nodes_of_type" argument is not a valid node type name: %s' - % safe_repr(argument), - locator=self.locator) - - @property - def as_raw(self): - node_type_name = self.node_type_name - if hasattr(node_type_name, 'as_raw'): - node_type_name = as_raw(node_type_name) - return {'get_nodes_of_type': node_type_name} - - def _evaluate(self, context, container): - pass - -# -# Artifact -# - -@dsl_specification('4.8.1', 'tosca-simple-1.0') # pylint: disable=abstract-method -class GetArtifact(Function): - """ - The :code:`get_artifact` function is used to retrieve artifact location between modelable - entities defined in the same service template. - """ - - def __init__(self, context, presentation, argument): - self.locator = presentation._locator - - if (not isinstance(argument, list)) or (len(argument) < 2) or (len(argument) > 4): - raise InvalidValueError( - 'function "get_artifact" argument must be a list of 2 to 4 parameters: %s' - % safe_repr(argument), - locator=self.locator) - - self.modelable_entity_name = parse_string_expression(context, presentation, 'get_artifact', - 0, 'modelable entity name', - argument[0]) - self.artifact_name = parse_string_expression(context, presentation, 'get_artifact', 1, - 'the artifact name', argument[1]) - self.location = parse_string_expression(context, presentation, 'get_artifact', 2, - 'the location or "LOCAL_FILE"', argument[2]) - self.remove = parse_bool(context, presentation, 'get_artifact', 3, 'the removal flag', - argument[3]) - - @property - def as_raw(self): - artifact_name = self.artifact_name - if hasattr(artifact_name, 'as_raw'): - artifact_name = as_raw(artifact_name) - location = self.location - if hasattr(location, 'as_raw'): - location = as_raw(location) - return {'get_artifacts': [self.modelable_entity_name, artifact_name, location, self.remove]} - -# -# Utils -# - -def get_function(context, presentation, value): - functions = context.presentation.presenter.functions - if isinstance(value, dict) and (len(value) == 1): - key = value.keys()[0] - if key in functions: - try: - return True, functions[key](context, presentation, value[key]) - except InvalidValueError as e: - context.validation.report(issue=e.issue) - return True, None - return False, None - -def parse_string_expression(context, presentation, name, index, explanation, value): # pylint: disable=unused-argument - is_function, func = get_function(context, presentation, value) - if is_function: - return func - else: - value = str(value) - return value - -def parse_int(context, presentation, name, index, explanation, value): # pylint: disable=unused-argument - if not isinstance(value, int): - try: - value = int(value) - except ValueError: - raise invalid_value(name, index, 'an integer', explanation, value, - presentation._locator) - return value - -def parse_bool(context, presentation, name, index, explanation, value): # pylint: disable=unused-argument - if not isinstance(value, bool): - raise invalid_value(name, index, 'a boolean', explanation, value, presentation._locator) - return value - -def parse_modelable_entity_name(context, presentation, name, index, value): - value = parse_string_expression(context, presentation, name, index, 'the modelable entity name', - value) - if value == 'SELF': - the_self, _ = parse_self(presentation) - if the_self is None: - raise invalid_modelable_entity_name(name, index, value, presentation._locator, - 'a node template or a relationship template') - elif value == 'HOST': - _, self_variant = parse_self(presentation) - if self_variant != 'node_template': - raise invalid_modelable_entity_name(name, index, value, presentation._locator, - 'a node template') - elif (value == 'SOURCE') or (value == 'TARGET'): - _, self_variant = parse_self(presentation) - if self_variant != 'relationship_template': - raise invalid_modelable_entity_name(name, index, value, presentation._locator, - 'a relationship template') - elif isinstance(value, basestring): - node_templates = \ - context.presentation.get('service_template', 'topology_template', 'node_templates') \ - or {} - relationship_templates = \ - context.presentation.get('service_template', 'topology_template', - 'relationship_templates') \ - or {} - if (value not in node_templates) and (value not in relationship_templates): - raise InvalidValueError( - 'function "%s" parameter %d is not a valid modelable entity name: %s' - % (name, index + 1, safe_repr(value)), - locator=presentation._locator, level=Issue.BETWEEN_TYPES) - return value - -def parse_self(presentation): - from .templates import NodeTemplate, RelationshipTemplate - from .types import NodeType, RelationshipType - - if presentation is None: - return None, None - elif isinstance(presentation, NodeTemplate) or isinstance(presentation, NodeType): - return presentation, 'node_template' - elif isinstance(presentation, RelationshipTemplate) \ - or isinstance(presentation, RelationshipType): - return presentation, 'relationship_template' - else: - return parse_self(presentation._container) - -@dsl_specification('4.1', 'tosca-simple-1.0') -def get_modelable_entities(context, container, locator, modelable_entity_name): - """ - The following keywords MAY be used in some TOSCA function in place of a TOSCA Node or - Relationship Template name. - """ - - if modelable_entity_name == 'SELF': - return get_self(context, container) - elif modelable_entity_name == 'HOST': - return get_host(context, container) - elif modelable_entity_name == 'SOURCE': - return get_source(context, container) - elif modelable_entity_name == 'TARGET': - return get_target(context, container) - elif isinstance(modelable_entity_name, basestring): - node_templates = \ - context.presentation.get('service_template', 'topology_template', 'node_templates') \ - or {} - if modelable_entity_name in node_templates: - return [node_templates[modelable_entity_name]] - relationship_templates = \ - context.presentation.get('service_template', 'topology_template', - 'relationship_templates') \ - or {} - if modelable_entity_name in relationship_templates: - return [relationship_templates[modelable_entity_name]] - - raise InvalidValueError('function "get_property" could not find modelable entity "%s"' - % modelable_entity_name, - locator=locator) - -def get_self(context, container): # pylint: disable=unused-argument - """ - A TOSCA orchestrator will interpret this keyword as the Node or Relationship Template instance - that contains the function at the time the function is evaluated. - """ - - return [container] - -def get_host(context, container): # pylint: disable=unused-argument - """ - A TOSCA orchestrator will interpret this keyword to refer to the all nodes that "host" the node - using this reference (i.e., as identified by its HostedOn relationship). - - Specifically, TOSCA orchestrators that encounter this keyword when evaluating the get_attribute - or :code:`get_property` functions SHALL search each node along the "HostedOn" relationship chain - starting at the immediate node that hosts the node where the function was evaluated (and then - that node's host node, and so forth) until a match is found or the "HostedOn" relationship chain - ends. - """ - - return [] - -def get_source(context, container): # pylint: disable=unused-argument - """ - A TOSCA orchestrator will interpret this keyword as the Node Template instance that is at the - source end of the relationship that contains the referencing function. - """ - - return [] - -def get_target(context, container): # pylint: disable=unused-argument - """ - A TOSCA orchestrator will interpret this keyword as the Node Template instance that is at the - target end of the relationship that contains the referencing function. - """ - -def invalid_modelable_entity_name(name, index, value, locator, contexts): - return InvalidValueError('function "%s" parameter %d can be "%s" only in %s' - % (name, index + 1, value, contexts), - locator=locator, level=Issue.FIELD) - -def invalid_value(name, index, the_type, explanation, value, locator): - return InvalidValueError( - 'function "%s" %s is not %s%s: %s' - % (name, ('parameter %d' % (index + 1)) if index is not None else 'argument', - the_type, (', %s' % explanation) if explanation is not None else '', safe_repr(value)), - locator=locator, level=Issue.FIELD) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/misc.py ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/simple_v1_0/misc.py b/extensions/aria_extension_tosca/simple_v1_0/misc.py index 42fc1ad..74eba18 100644 --- a/extensions/aria_extension_tosca/simple_v1_0/misc.py +++ b/extensions/aria_extension_tosca/simple_v1_0/misc.py @@ -16,7 +16,7 @@ from aria.utils.caching import cachedmethod from aria.utils.console import puts from aria.utils.formatting import as_raw -from aria.parser import dsl_specification +from aria.parser import implements_specification from aria.parser.presentation import (AsIsPresentation, has_fields, allow_unknown_fields, short_form_field, primitive_field, primitive_list_field, primitive_dict_unknown_fields, object_field, @@ -36,7 +36,7 @@ from .presentation.field_validators import (constraint_clause_field_validator, from .presentation.types import (convert_shorthand_to_full_type_name, get_type_by_full_or_shorthand_name) -@dsl_specification('3.5.1', 'tosca-simple-1.0') +@implements_specification('3.5.1', 'tosca-simple-1.0') class Description(AsIsPresentation): """ See the `TOSCA Simple Profile v1.0 cos01 specification <http://docs.oasis-open.org/tosca @@ -53,10 +53,10 @@ class Description(AsIsPresentation): @allow_unknown_fields @has_fields -@dsl_specification('3.9.3.2', 'tosca-simple-1.0') +@implements_specification('3.9.3.2', 'tosca-simple-1.0') class MetaData(ExtensiblePresentation): @primitive_field(str) - @dsl_specification('3.9.3.3', 'tosca-simple-1.0') + @implements_specification('3.9.3.3', 'tosca-simple-1.0') def template_name(self): """ This optional metadata keyname can be used to declare the name of service template as a @@ -64,7 +64,7 @@ class MetaData(ExtensiblePresentation): """ @primitive_field(str) - @dsl_specification('3.9.3.4', 'tosca-simple-1.0') + @implements_specification('3.9.3.4', 'tosca-simple-1.0') def template_author(self): """ This optional metadata keyname can be used to declare the author(s) of the service template @@ -72,7 +72,7 @@ class MetaData(ExtensiblePresentation): """ @primitive_field(str) - @dsl_specification('3.9.3.5', 'tosca-simple-1.0') + @implements_specification('3.9.3.5', 'tosca-simple-1.0') def template_version(self): """ This optional metadata keyname can be used to declare a domain specific version of the @@ -87,7 +87,7 @@ class MetaData(ExtensiblePresentation): @short_form_field('url') @has_fields -@dsl_specification('3.5.5', 'tosca-simple-1.0') +@implements_specification('3.5.5', 'tosca-simple-1.0') class Repository(ExtensiblePresentation): """ A repository definition defines a named external repository which contains deployment and @@ -128,7 +128,7 @@ class Repository(ExtensiblePresentation): @short_form_field('file') @has_fields -@dsl_specification('3.5.7', 'tosca-simple-1.0') +@implements_specification('3.5.7', 'tosca-simple-1.0') class Import(ExtensiblePresentation): """ An import definition is used within a TOSCA Service Template to locate and uniquely name another @@ -177,7 +177,7 @@ class Import(ExtensiblePresentation): """ @has_fields -@dsl_specification('3.5.2', 'tosca-simple-1.0') +@implements_specification('3.5.2-1', 'tosca-simple-1.0') class ConstraintClause(ExtensiblePresentation): """ A constraint clause defines an operation along with one or more compatible values that can be @@ -376,7 +376,7 @@ class SubstitutionMappingsCapability(AsIsPresentation): validate_subtitution_mappings_capability(context, self) @has_fields -@dsl_specification('2.10', 'tosca-simple-1.0') +@implements_specification('2.10', 'tosca-simple-1.0') class SubstitutionMappings(ExtensiblePresentation): @field_validator(type_validator('node type', convert_shorthand_to_full_type_name, 'node_types')) @primitive_field(str, required=True) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py index 3bda7e2..99389e4 100644 --- a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py +++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py @@ -34,6 +34,8 @@ from aria.modeling.models import (Type, ServiceTemplate, NodeTemplate, SubstitutionTemplateMapping, InterfaceTemplate, OperationTemplate, ArtifactTemplate, Metadata, Parameter, PluginSpecification) +from .constraints import (Equal, GreaterThan, GreaterOrEqual, LessThan, LessOrEqual, InRange, + ValidValues, Length, MinLength, MaxLength, Pattern) from ..data_types import coerce_value @@ -166,6 +168,8 @@ def create_node_template_model(context, service_template, node_template): create_parameter_models_from_values(model.properties, node_template._get_property_values(context)) + create_parameter_models_from_values(model.attributes, + node_template._get_attribute_default_values(context)) create_interface_template_models(context, service_template, model.interface_templates, node_template._get_interfaces(context)) @@ -181,10 +185,10 @@ def create_node_template_model(context, service_template, node_template): model.capability_templates[capability_name] = \ create_capability_template_model(context, service_template, capability) - if model.target_node_template_constraints: + if node_template.node_filter: model.target_node_template_constraints = [] - create_node_filter_constraint_lambdas(context, node_template.node_filter, - model.target_node_template_constraints) + create_node_filter_constraints(context, node_template.node_filter, + model.target_node_template_constraints) return model @@ -273,10 +277,10 @@ def create_requirement_template_model(context, service_template, requirement): model = RequirementTemplate(**model) - if model.target_node_template_constraints: + if requirement.node_filter: model.target_node_template_constraints = [] - create_node_filter_constraint_lambdas(context, requirement.node_filter, - model.target_node_template_constraints) + create_node_filter_constraints(context, requirement.node_filter, + model.target_node_template_constraints) relationship = requirement.relationship if relationship is not None: @@ -348,7 +352,7 @@ def create_interface_template_model(context, service_template, interface): inputs = interface.inputs if inputs: for input_name, the_input in inputs.iteritems(): - model.inputs[input_name] = Parameter(name=input_name, + model.inputs[input_name] = Parameter(name=input_name, # pylint: disable=unexpected-keyword-arg type_name=the_input.value.type, value=the_input.value.value, description=the_input.value.description) @@ -395,7 +399,7 @@ def create_operation_template_model(context, service_template, operation): inputs = operation.inputs if inputs: for input_name, the_input in inputs.iteritems(): - model.inputs[input_name] = Parameter(name=input_name, + model.inputs[input_name] = Parameter(name=input_name, # pylint: disable=unexpected-keyword-arg type_name=the_input.value.type, value=the_input.value.value, description=the_input.value.description) @@ -491,7 +495,7 @@ def create_workflow_operation_template_model(context, service_template, policy): elif prop_name == 'dependencies': model.dependencies = prop.value else: - model.inputs[prop_name] = Parameter(name=prop_name, + model.inputs[prop_name] = Parameter(name=prop_name, # pylint: disable=unexpected-keyword-arg type_name=prop.type, value=prop.value, description=prop.description) @@ -536,7 +540,7 @@ def create_types(context, root, types): def create_parameter_models_from_values(properties, source_properties): if source_properties: for property_name, prop in source_properties.iteritems(): - properties[property_name] = Parameter(name=property_name, + properties[property_name] = Parameter(name=property_name, # pylint: disable=unexpected-keyword-arg type_name=prop.type, value=prop.value, description=prop.description) @@ -545,7 +549,7 @@ def create_parameter_models_from_values(properties, source_properties): def create_parameter_models_from_assignments(properties, source_properties): if source_properties: for property_name, prop in source_properties.iteritems(): - properties[property_name] = Parameter(name=property_name, + properties[property_name] = Parameter(name=property_name, # pylint: disable=unexpected-keyword-arg type_name=prop.value.type, value=prop.value.value, description=prop.value.description) @@ -559,17 +563,13 @@ def create_interface_template_models(context, service_template, interfaces, sour interfaces[interface_name] = interface -def create_node_filter_constraint_lambdas(context, node_filter, target_node_template_constraints): - if node_filter is None: - return - +def create_node_filter_constraints(context, node_filter, target_node_template_constraints): properties = node_filter.properties if properties is not None: for property_name, constraint_clause in properties: - func = create_constraint_clause_lambda(context, node_filter, constraint_clause, - property_name, None) - if func is not None: - target_node_template_constraints.append(func) + constraint = create_constraint(context, node_filter, constraint_clause, property_name, + None) + target_node_template_constraints.append(constraint) capabilities = node_filter.capabilities if capabilities is not None: @@ -577,129 +577,64 @@ def create_node_filter_constraint_lambdas(context, node_filter, target_node_temp properties = capability.properties if properties is not None: for property_name, constraint_clause in properties: - func = create_constraint_clause_lambda(context, node_filter, constraint_clause, - property_name, capability_name) - if func is not None: - target_node_template_constraints.append(func) + constraint = create_constraint(context, node_filter, constraint_clause, + property_name, capability_name) + target_node_template_constraints.append(constraint) -def create_constraint_clause_lambda(context, node_filter, constraint_clause, property_name, # pylint: disable=too-many-return-statements - capability_name): +def create_constraint(context, node_filter, constraint_clause, property_name, capability_name): # pylint: disable=too-many-return-statements constraint_key = constraint_clause._raw.keys()[0] - the_type = constraint_clause._get_type(context) - def coerce_constraint(constraint, container): - constraint = coerce_value(context, node_filter, the_type, None, None, constraint, - constraint_key) if the_type is not None else constraint - if hasattr(constraint, '_evaluate'): - constraint = constraint._evaluate(context, container) - return constraint - - def get_value(node_type): - if capability_name is not None: - capability = node_type.capability_templates.get(capability_name) - prop = capability.properties.get(property_name) if capability is not None else None - return prop.value if prop is not None else None - value = node_type.properties.get(property_name) - return value.value if value is not None else None + the_type = constraint_clause._get_type(context) - if constraint_key == 'equal': - def equal(node_type, container): - constraint = coerce_constraint(constraint_clause.equal, container) - value = get_value(node_type) - return value == constraint + def coerce_constraint(constraint): + if the_type is not None: + return coerce_value(context, node_filter, the_type, None, None, constraint, + constraint_key) + else: + return constraint - return equal + def coerce_constraints(constraints): + if the_type is not None: + return tuple(coerce_constraint(constraint) for constraint in constraints) + else: + return constraints + if constraint_key == 'equal': + return Equal(property_name, capability_name, + coerce_constraint(constraint_clause.equal)) elif constraint_key == 'greater_than': - def greater_than(node_type, container): - constraint = coerce_constraint(constraint_clause.greater_than, container) - value = get_value(node_type) - return value > constraint - - return greater_than - + return GreaterThan(property_name, capability_name, + coerce_constraint(constraint_clause.greater_than)) elif constraint_key == 'greater_or_equal': - def greater_or_equal(node_type, container): - constraint = coerce_constraint(constraint_clause.greater_or_equal, container) - value = get_value(node_type) - return value >= constraint - - return greater_or_equal - + return GreaterOrEqual(property_name, capability_name, + coerce_constraint(constraint_clause.greater_or_equal)) elif constraint_key == 'less_than': - def less_than(node_type, container): - constraint = coerce_constraint(constraint_clause.less_than, container) - value = get_value(node_type) - return value < constraint - - return less_than - + return LessThan(property_name, capability_name, + coerce_constraint(constraint_clause.less_than)) elif constraint_key == 'less_or_equal': - def less_or_equal(node_type, container): - constraint = coerce_constraint(constraint_clause.less_or_equal, container) - value = get_value(node_type) - return value <= constraint - - return less_or_equal - + return LessOrEqual(property_name, capability_name, + coerce_constraint(constraint_clause.less_or_equal)) elif constraint_key == 'in_range': - def in_range(node_type, container): - lower, upper = constraint_clause.in_range - lower, upper = coerce_constraint(lower, container), coerce_constraint(upper, container) - value = get_value(node_type) - if value < lower: - return False - if (upper != 'UNBOUNDED') and (value > upper): - return False - return True - - return in_range - + return InRange(property_name, capability_name, + coerce_constraints(constraint_clause.in_range)) elif constraint_key == 'valid_values': - def valid_values(node_type, container): - constraint = tuple(coerce_constraint(v, container) - for v in constraint_clause.valid_values) - value = get_value(node_type) - return value in constraint - - return valid_values - + return ValidValues(property_name, capability_name, + coerce_constraints(constraint_clause.valid_values)) elif constraint_key == 'length': - def length(node_type, container): # pylint: disable=unused-argument - constraint = constraint_clause.length - value = get_value(node_type) - return len(value) == constraint - - return length - + return Length(property_name, capability_name, + coerce_constraint(constraint_clause.length)) elif constraint_key == 'min_length': - def min_length(node_type, container): # pylint: disable=unused-argument - constraint = constraint_clause.min_length - value = get_value(node_type) - return len(value) >= constraint - - return min_length - + return MinLength(property_name, capability_name, + coerce_constraint(constraint_clause.min_length)) elif constraint_key == 'max_length': - def max_length(node_type, container): # pylint: disable=unused-argument - constraint = constraint_clause.max_length - value = get_value(node_type) - return len(value) >= constraint - - return max_length - + return MaxLength(property_name, capability_name, + coerce_constraint(constraint_clause.max_length)) elif constraint_key == 'pattern': - def pattern(node_type, container): # pylint: disable=unused-argument - constraint = constraint_clause.pattern - # Note: the TOSCA 1.0 spec does not specify the regular expression grammar, so we will - # just use Python's - value = node_type.properties.get(property_name) - return re.match(constraint, str(value)) is not None - - return pattern - - return None + return Pattern(property_name, capability_name, + coerce_constraint(constraint_clause.pattern)) + else: + raise ValueError('malformed node_filter: {0}'.format(constraint_key)) def split_prefix(string): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/modeling/constraints.py ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/constraints.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/constraints.py new file mode 100644 index 0000000..7c99eab --- /dev/null +++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/constraints.py @@ -0,0 +1,144 @@ +# 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.modeling.contraints import NodeTemplateConstraint +from aria.modeling.utils import NodeTemplateContainerHolder +from aria.modeling.functions import evaluate +from aria.parser import implements_specification + + +@implements_specification('3.5.2-2', 'tosca-simple-1.0') +class EvaluatingNodeTemplateConstraint(NodeTemplateConstraint): + """ + A version of :class:`NodeTemplateConstraint` with boilerplate initialization for TOSCA + constraints. + """ + + def __init__(self, property_name, capability_name, constraint, as_list=False): + self.property_name = property_name + self.capability_name = capability_name + self.constraint = constraint + self.as_list = as_list + + def matches(self, source_node_template, target_node_template): + # TOSCA node template constraints can refer to either capability properties or node + # template properties + if self.capability_name is not None: + # Capability property + capability = target_node_template.capability_templates.get(self.capability_name) + value = capability.properties.get(self.property_name) \ + if capability is not None else None # Parameter + else: + # Node template property + value = target_node_template.properties.get(self.property_name) # Parameter + + value = value.value if value is not None else None + + container_holder = NodeTemplateContainerHolder(source_node_template) + + if self.as_list: + constraints = [] + for constraint in self.constraint: + evaluation = evaluate(constraint, container_holder) + if evaluation is not None: + constraints.append(evaluation.value) + else: + constraints.append(constraint) + constraint = constraints + else: + evaluation = evaluate(self.constraint, container_holder) + if evaluation is not None: + constraint = evaluation.value + else: + constraint = self.constraint + + return self.matches_evaluated(value, constraint) + + def matches_evaluated(self, value, constraint): + raise NotImplementedError + + +class Equal(EvaluatingNodeTemplateConstraint): + def matches_evaluated(self, value, constraint): + return value == constraint + + +class GreaterThan(EvaluatingNodeTemplateConstraint): + def matches_evaluated(self, value, constraint): + return value > constraint + + +class GreaterOrEqual(EvaluatingNodeTemplateConstraint): + def matches_evaluated(self, value, constraint): + return value >= constraint + + +class LessThan(EvaluatingNodeTemplateConstraint): + def matches_evaluated(self, value, constraint): + return value < constraint + + +class LessOrEqual(EvaluatingNodeTemplateConstraint): + def matches_evaluated(self, value, constraint): + return value <= constraint + + +class InRange(EvaluatingNodeTemplateConstraint): + def __init__(self, property_name, capability_name, constraint): + super(InRange, self).__init__(property_name, capability_name, constraint, as_list=True) + + def matches_evaluated(self, value, constraints): + lower, upper = constraints + if value < lower: + return False + if (upper != 'UNBOUNDED') and (value > upper): + return False + return True + + +class ValidValues(EvaluatingNodeTemplateConstraint): + def __init__(self, property_name, capability_name, constraint): + super(ValidValues, self).__init__(property_name, capability_name, constraint, as_list=True) + + def matches_evaluated(self, value, constraints): + return value in constraints + + +class Length(EvaluatingNodeTemplateConstraint): + def matches_evaluated(self, value, constraint): + return len(value) == constraint + + +class MinLength(EvaluatingNodeTemplateConstraint): + def matches_evaluated(self, value, constraint): + return len(value) >= constraint + + +class MaxLength(EvaluatingNodeTemplateConstraint): + def matches_evaluated(self, value, constraint): + return len(value) <= constraint + + +class Pattern(EvaluatingNodeTemplateConstraint): + def matches_evaluated(self, value, constraint): + # 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. + return re.match(constraint, unicode(value)) is not None http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py index 99dcfea..3952785 100644 --- a/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py +++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/data_types.py @@ -16,13 +16,14 @@ import re from aria.utils.collections import OrderedDict -from aria.utils.formatting import full_type_name, safe_repr +from aria.utils.formatting import safe_repr +from aria.utils.type import full_type_name from aria.utils.imports import import_fullname -from aria.parser import dsl_specification +from aria.parser import implements_specification from aria.parser.presentation import (get_locator, validate_primitive) from aria.parser.validation import Issue -from ..functions import get_function +from .functions import get_function from ..presentation.types import get_type_by_full_or_shorthand_name # @@ -295,8 +296,12 @@ def apply_constraint_to_value(context, presentation, constraint_clause, value): elif constraint_key == 'pattern': constraint = constraint_clause.pattern try: - # Note: the TOSCA 1.0 spec does not specify the regular expression grammar, so we will - # just use Python's + # 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. if re.match(constraint, str(value)) is None: report('does not match regular expression', constraint) return False @@ -327,20 +332,20 @@ def get_data_type_value(context, presentation, field_name, type_name): PRIMITIVE_DATA_TYPES = { # YAML 1.2: - 'tag:yaml.org,2002:str': str, + 'tag:yaml.org,2002:str': unicode, 'tag:yaml.org,2002:integer': int, 'tag:yaml.org,2002:float': float, 'tag:yaml.org,2002:bool': bool, 'tag:yaml.org,2002:null': None.__class__, # TOSCA aliases: - 'string': str, + 'string': unicode, 'integer': int, 'float': float, 'boolean': bool, 'null': None.__class__} -@dsl_specification('3.2.1', 'tosca-simple-1.0') +@implements_specification('3.2.1-3', 'tosca-simple-1.0') def get_primitive_data_type(type_name): """ Many of the types we use in this profile are built-in types from the YAML 1.2 specification @@ -371,6 +376,8 @@ def coerce_value(context, presentation, the_type, entry_schema, constraints, val If the extension is present, we will delegate to that hook. """ + # TODO: should support models as well as presentations + is_function, func = get_function(context, presentation, value) if is_function: return func http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/60ea3ebb/extensions/aria_extension_tosca/simple_v1_0/modeling/functions.py ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/functions.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/functions.py new file mode 100644 index 0000000..7089ed9 --- /dev/null +++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/functions.py @@ -0,0 +1,677 @@ +# 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. + +from cStringIO import StringIO +import re + +from aria.utils.collections import FrozenList +from aria.utils.formatting import (as_raw, safe_repr) +from aria.utils.type import full_type_name +from aria.parser import implements_specification +from aria.parser.exceptions import InvalidValueError +from aria.parser.validation import Issue +from aria.modeling.exceptions import CannotEvaluateFunctionException +from aria.modeling.models import (Node, NodeTemplate, Relationship, RelationshipTemplate) +from aria.modeling.functions import (Function, Evaluation) + + +# +# Intrinsic +# + +@implements_specification('4.3.1', 'tosca-simple-1.0') +class Concat(Function): + """ + The :code:`concat` function is used to concatenate two or more string values within a TOSCA + service template. + """ + + def __init__(self, context, presentation, argument): + self.locator = presentation._locator + + if not isinstance(argument, list): + raise InvalidValueError( + 'function "concat" argument must be a list of string expressions: {0}' + .format(safe_repr(argument)), + locator=self.locator) + + string_expressions = [] + for index, an_argument in enumerate(argument): + string_expressions.append(parse_string_expression(context, presentation, 'concat', + index, None, an_argument)) + self.string_expressions = FrozenList(string_expressions) + + @property + def as_raw(self): + string_expressions = [] + for string_expression in self.string_expressions: + if hasattr(string_expression, 'as_raw'): + string_expression = as_raw(string_expression) + string_expressions.append(string_expression) + return {'concat': string_expressions} + + def __evaluate__(self, container_holder): + final = True + value = StringIO() + for e in self.string_expressions: + e, final = evaluate(e, final, container_holder) + if e is not None: + value.write(unicode(e)) + value = value.getvalue() + return Evaluation(value, final) + + +@implements_specification('4.3.2', 'tosca-simple-1.0') +class Token(Function): + """ + The :code:`token` function is used within a TOSCA service template on a string to parse out + (tokenize) substrings separated by one or more token characters within a larger string. + """ + + def __init__(self, context, presentation, argument): + self.locator = presentation._locator + + if (not isinstance(argument, list)) or (len(argument) != 3): + raise InvalidValueError('function "token" argument must be a list of 3 parameters: {0}' + .format(safe_repr(argument)), + locator=self.locator) + + self.string_with_tokens = parse_string_expression(context, presentation, 'token', 0, + 'the string to tokenize', argument[0]) + self.string_of_token_chars = parse_string_expression(context, presentation, 'token', 1, + 'the token separator characters', + argument[1]) + self.substring_index = parse_int(context, presentation, 'token', 2, + 'the 0-based index of the token to return', argument[2]) + + @property + def as_raw(self): + string_with_tokens = self.string_with_tokens + if hasattr(string_with_tokens, 'as_raw'): + string_with_tokens = as_raw(string_with_tokens) + string_of_token_chars = self.string_of_token_chars + if hasattr(string_of_token_chars, 'as_raw'): + string_of_token_chars = as_raw(string_of_token_chars) + return {'token': [string_with_tokens, string_of_token_chars, self.substring_index]} + + def __evaluate__(self, container_holder): + final = True + string_with_tokens, final = evaluate(self.string_with_tokens, final, container_holder) + string_of_token_chars, final = evaluate(self.string_of_token_chars, final, container_holder) + + if string_of_token_chars: + regex = '[' + ''.join(re.escape(c) for c in string_of_token_chars) + ']' + split = re.split(regex, string_with_tokens) + if self.substring_index < len(split): + return Evaluation(split[self.substring_index], final) + + raise CannotEvaluateFunctionException() + + +# +# Property +# + +@implements_specification('4.4.1', 'tosca-simple-1.0') +class GetInput(Function): + """ + The :code:`get_input` function is used to retrieve the values of properties declared within the + inputs section of a TOSCA Service Template. + """ + + def __init__(self, context, presentation, argument): + self.locator = presentation._locator + + self.input_property_name = parse_string_expression(context, presentation, 'get_input', + None, 'the input property name', + argument) + + if isinstance(self.input_property_name, basestring): + the_input = context.presentation.get_from_dict('service_template', 'topology_template', + 'inputs', self.input_property_name) + if the_input is None: + raise InvalidValueError( + 'function "get_input" argument is not a valid input name: {0}' + .format(safe_repr(argument)), + locator=self.locator) + + @property + def as_raw(self): + return {'get_input': as_raw(self.input_property_name)} + + def __evaluate__(self, container_holder): + service = container_holder.service + if service is None: + raise CannotEvaluateFunctionException() + + value = service.inputs.get(self.input_property_name) + if value is not None: + value = value.value + return Evaluation(value, False) # We never return final evaluations! + + raise InvalidValueError( + 'function "get_input" argument is not a valid input name: {0}' + .format(safe_repr(self.input_property_name)), + locator=self.locator) + + +@implements_specification('4.4.2', 'tosca-simple-1.0') +class GetProperty(Function): + """ + The :code:`get_property` function is used to retrieve property values between modelable entities + defined in the same service template. + """ + + def __init__(self, context, presentation, argument): + self.locator = presentation._locator + + if (not isinstance(argument, list)) or (len(argument) < 2): + raise InvalidValueError( + 'function "get_property" argument must be a list of at least 2 string expressions: ' + '{0}'.format(safe_repr(argument)), + locator=self.locator) + + self.modelable_entity_name = parse_modelable_entity_name(context, presentation, + 'get_property', 0, argument[0]) + # The first of these will be tried as a req-or-cap name: + self.nested_property_name_or_index = argument[1:] + + @property + def as_raw(self): + return {'get_property': [self.modelable_entity_name] + self.nested_property_name_or_index} + + def __evaluate__(self, container_holder): + modelable_entities = get_modelable_entities(container_holder, 'get_property', self.locator, + self.modelable_entity_name) + req_or_cap_name = self.nested_property_name_or_index[0] + + for modelable_entity in modelable_entities: + properties = None + + if hasattr(modelable_entity, 'requirement_templates') \ + and modelable_entity.requirement_templates \ + and (req_or_cap_name in [v.name for v in modelable_entity.requirement_templates]): + for requirement_template in modelable_entity.requirement_templates: + if requirement_template.name == req_or_cap_name: + # First argument refers to a requirement + # TODO: should follow to matched capability in other node... + raise CannotEvaluateFunctionException() + # break + nested_property_name_or_index = self.nested_property_name_or_index[1:] + elif hasattr(modelable_entity, 'capability_templates') \ + and modelable_entity.capability_templates \ + and (req_or_cap_name in modelable_entity.capability_templates): + # First argument refers to a capability + properties = modelable_entity.capability_templates[req_or_cap_name].properties + nested_property_name_or_index = self.nested_property_name_or_index[1:] + else: + properties = modelable_entity.properties + nested_property_name_or_index = self.nested_property_name_or_index + + evaluation = get_modelable_entity_parameter(modelable_entity, properties, + nested_property_name_or_index) + if evaluation is not None: + return evaluation + + raise InvalidValueError( + 'function "get_property" could not find "{0}" in modelable entity "{1}"' + .format('.'.join(self.nested_property_name_or_index), self.modelable_entity_name), + locator=self.locator) + + +# +# Attribute +# + +@implements_specification('4.5.1', 'tosca-simple-1.0') +class GetAttribute(Function): + """ + The :code:`get_attribute` function is used to retrieve the values of named attributes declared + by the referenced node or relationship template name. + """ + + def __init__(self, context, presentation, argument): + self.locator = presentation._locator + + if (not isinstance(argument, list)) or (len(argument) < 2): + raise InvalidValueError( + 'function "get_attribute" argument must be a list of at least 2 string expressions:' + ' {0}'.format(safe_repr(argument)), + locator=self.locator) + + self.modelable_entity_name = parse_modelable_entity_name(context, presentation, + 'get_attribute', 0, argument[0]) + # The first of these will be tried as a req-or-cap name: + self.nested_attribute_name_or_index = argument[1:] + + @property + def as_raw(self): + return {'get_attribute': [self.modelable_entity_name] + self.nested_attribute_name_or_index} + + def __evaluate__(self, container_holder): + modelable_entities = get_modelable_entities(container_holder, 'get_attribute', self.locator, + self.modelable_entity_name) + for modelable_entity in modelable_entities: + attributes = modelable_entity.attributes + nested_attribute_name_or_index = self.nested_attribute_name_or_index + evaluation = get_modelable_entity_parameter(modelable_entity, attributes, + nested_attribute_name_or_index) + if evaluation is not None: + evaluation.final = False # We never return final evaluations! + return evaluation + + raise InvalidValueError( + 'function "get_attribute" could not find "{0}" in modelable entity "{1}"' + .format('.'.join(self.nested_attribute_name_or_index), self.modelable_entity_name), + locator=self.locator) + + +# +# Operation +# + +@implements_specification('4.6.1', 'tosca-simple-1.0') # pylint: disable=abstract-method +class GetOperationOutput(Function): + """ + The :code:`get_operation_output` function is used to retrieve the values of variables exposed / + exported from an interface operation. + """ + + def __init__(self, context, presentation, argument): + self.locator = presentation._locator + + if (not isinstance(argument, list)) or (len(argument) != 4): + raise InvalidValueError( + 'function "get_operation_output" argument must be a list of 4 parameters: {0}' + .format(safe_repr(argument)), + locator=self.locator) + + self.modelable_entity_name = parse_string_expression(context, presentation, + 'get_operation_output', 0, + 'modelable entity name', argument[0]) + self.interface_name = parse_string_expression(context, presentation, 'get_operation_output', + 1, 'the interface name', argument[1]) + self.operation_name = parse_string_expression(context, presentation, 'get_operation_output', + 2, 'the operation name', argument[2]) + self.output_variable_name = parse_string_expression(context, presentation, + 'get_operation_output', 3, + 'the output name', argument[3]) + + @property + def as_raw(self): + interface_name = self.interface_name + if hasattr(interface_name, 'as_raw'): + interface_name = as_raw(interface_name) + operation_name = self.operation_name + if hasattr(operation_name, 'as_raw'): + operation_name = as_raw(operation_name) + output_variable_name = self.output_variable_name + if hasattr(output_variable_name, 'as_raw'): + output_variable_name = as_raw(output_variable_name) + return {'get_operation_output': [self.modelable_entity_name, interface_name, operation_name, + output_variable_name]} + + +# +# Navigation +# + +@implements_specification('4.7.1', 'tosca-simple-1.0') +class GetNodesOfType(Function): + """ + The :code:`get_nodes_of_type` function can be used to retrieve a list of all known instances of + nodes of the declared Node Type. + """ + + def __init__(self, context, presentation, argument): + self.locator = presentation._locator + + self.node_type_name = parse_string_expression(context, presentation, 'get_nodes_of_type', + None, 'the node type name', argument) + + if isinstance(self.node_type_name, basestring): + node_types = context.presentation.get('service_template', 'node_types') + if (node_types is None) or (self.node_type_name not in node_types): + raise InvalidValueError( + 'function "get_nodes_of_type" argument is not a valid node type name: {0}' + .format(safe_repr(argument)), + locator=self.locator) + + @property + def as_raw(self): + node_type_name = self.node_type_name + if hasattr(node_type_name, 'as_raw'): + node_type_name = as_raw(node_type_name) + return {'get_nodes_of_type': node_type_name} + + def __evaluate__(self, container): + pass + + +# +# Artifact +# + +@implements_specification('4.8.1', 'tosca-simple-1.0') # pylint: disable=abstract-method +class GetArtifact(Function): + """ + The :code:`get_artifact` function is used to retrieve artifact location between modelable + entities defined in the same service template. + """ + + def __init__(self, context, presentation, argument): + self.locator = presentation._locator + + if (not isinstance(argument, list)) or (len(argument) < 2) or (len(argument) > 4): + raise InvalidValueError( + 'function "get_artifact" argument must be a list of 2 to 4 parameters: {0}' + .format(safe_repr(argument)), + locator=self.locator) + + self.modelable_entity_name = parse_string_expression(context, presentation, 'get_artifact', + 0, 'modelable entity name', + argument[0]) + self.artifact_name = parse_string_expression(context, presentation, 'get_artifact', 1, + 'the artifact name', argument[1]) + self.location = parse_string_expression(context, presentation, 'get_artifact', 2, + 'the location or "LOCAL_FILE"', argument[2]) + self.remove = parse_bool(context, presentation, 'get_artifact', 3, 'the removal flag', + argument[3]) + + @property + def as_raw(self): + artifact_name = self.artifact_name + if hasattr(artifact_name, 'as_raw'): + artifact_name = as_raw(artifact_name) + location = self.location + if hasattr(location, 'as_raw'): + location = as_raw(location) + return {'get_artifacts': [self.modelable_entity_name, artifact_name, location, self.remove]} + + +# +# Utils +# + +def get_function(context, presentation, value): + functions = context.presentation.presenter.functions + if isinstance(value, dict) and (len(value) == 1): + key = value.keys()[0] + if key in functions: + try: + return True, functions[key](context, presentation, value[key]) + except InvalidValueError as e: + context.validation.report(issue=e.issue) + return True, None + return False, None + + +def parse_string_expression(context, presentation, name, index, explanation, value): # pylint: disable=unused-argument + is_function, func = get_function(context, presentation, value) + if is_function: + return func + else: + value = str(value) + return value + + +def parse_int(context, presentation, name, index, explanation, value): # pylint: disable=unused-argument + if not isinstance(value, int): + try: + value = int(value) + except ValueError: + raise invalid_value(name, index, 'an integer', explanation, value, + presentation._locator) + return value + + +def parse_bool(context, presentation, name, index, explanation, value): # pylint: disable=unused-argument + if not isinstance(value, bool): + raise invalid_value(name, index, 'a boolean', explanation, value, presentation._locator) + return value + + +def parse_modelable_entity_name(context, presentation, name, index, value): + value = parse_string_expression(context, presentation, name, index, 'the modelable entity name', + value) + if value == 'SELF': + the_self, _ = parse_self(presentation) + if the_self is None: + raise invalid_modelable_entity_name(name, index, value, presentation._locator, + 'a node template or a relationship template') + elif value == 'HOST': + _, self_variant = parse_self(presentation) + if self_variant != 'node_template': + raise invalid_modelable_entity_name(name, index, value, presentation._locator, + 'a node template') + elif (value == 'SOURCE') or (value == 'TARGET'): + _, self_variant = parse_self(presentation) + if self_variant != 'relationship_template': + raise invalid_modelable_entity_name(name, index, value, presentation._locator, + 'a relationship template') + elif isinstance(value, basestring): + node_templates = \ + context.presentation.get('service_template', 'topology_template', 'node_templates') \ + or {} + relationship_templates = \ + context.presentation.get('service_template', 'topology_template', + 'relationship_templates') \ + or {} + if (value not in node_templates) and (value not in relationship_templates): + raise InvalidValueError( + 'function "{0}" parameter {1:d} is not a valid modelable entity name: {2}' + .format(name, index + 1, safe_repr(value)), + locator=presentation._locator, level=Issue.BETWEEN_TYPES) + return value + + +def parse_self(presentation): + from ..types import (NodeType, RelationshipType) + from ..templates import ( + NodeTemplate as NodeTemplatePresentation, + RelationshipTemplate as RelationshipTemplatePresentation + ) + + if presentation is None: + return None, None + elif isinstance(presentation, NodeTemplatePresentation) or isinstance(presentation, NodeType): + return presentation, 'node_template' + elif isinstance(presentation, RelationshipTemplatePresentation) \ + or isinstance(presentation, RelationshipType): + return presentation, 'relationship_template' + else: + return parse_self(presentation._container) + + +def evaluate(value, final, container_holder): + """ + Calls ``__evaluate__`` and passes on ``final`` state. + """ + + if hasattr(value, '__evaluate__'): + value = value.__evaluate__(container_holder) + if not value.final: + final = False + return value.value, final + else: + return value, final + + +@implements_specification('4.1', 'tosca-simple-1.0') +def get_modelable_entities(container_holder, name, locator, modelable_entity_name): + """ + The following keywords MAY be used in some TOSCA function in place of a TOSCA Node or + Relationship Template name. + """ + + if modelable_entity_name == 'SELF': + return get_self(container_holder, name, locator) + elif modelable_entity_name == 'HOST': + return get_hosts(container_holder, name, locator) + elif modelable_entity_name == 'SOURCE': + return get_source(container_holder, name, locator) + elif modelable_entity_name == 'TARGET': + return get_target(container_holder, name, locator) + elif isinstance(modelable_entity_name, basestring): + modelable_entities = [] + + service = container_holder.service + if service is not None: + for node in service.nodes.itervalues(): + if node.node_template.name == modelable_entity_name: + modelable_entities.append(node) + else: + service_template = container_holder.service_template + if service_template is not None: + for node_template in service_template.node_templates.itervalues(): + if node_template.name == modelable_entity_name: + modelable_entities.append(node_template) + + if not modelable_entities: + raise CannotEvaluateFunctionException() + + return modelable_entities + + raise InvalidValueError('function "{0}" could not find modelable entity "{1}"' + .format(name, modelable_entity_name), + locator=locator) + + +def get_self(container_holder, name, locator): + """ + A TOSCA orchestrator will interpret this keyword as the Node or Relationship Template instance + that contains the function at the time the function is evaluated. + """ + + container = container_holder.container + if (not isinstance(container, Node)) and \ + (not isinstance(container, NodeTemplate)) and \ + (not isinstance(container, Relationship)) and \ + (not isinstance(container, RelationshipTemplate)): + raise InvalidValueError('function "{0}" refers to "SELF" but it is not contained in ' + 'a node or a relationship: {1}'.format(name, + full_type_name(container)), + locator=locator) + + return [container] + + +def get_hosts(container_holder, name, locator): + """ + A TOSCA orchestrator will interpret this keyword to refer to the all nodes that "host" the node + using this reference (i.e., as identified by its HostedOn relationship). + + Specifically, TOSCA orchestrators that encounter this keyword when evaluating the get_attribute + or :code:`get_property` functions SHALL search each node along the "HostedOn" relationship chain + starting at the immediate node that hosts the node where the function was evaluated (and then + that node's host node, and so forth) until a match is found or the "HostedOn" relationship chain + ends. + """ + + container = container_holder.container + if (not isinstance(container, Node)) and (not isinstance(container, NodeTemplate)): + raise InvalidValueError('function "{0}" refers to "HOST" but it is not contained in ' + 'a node: {1}'.format(name, full_type_name(container)), + locator=locator) + + if not isinstance(container, Node): + # NodeTemplate does not have "host"; we'll wait until instantiation + raise CannotEvaluateFunctionException() + + host = container.host + if host is None: + # We might have a host later + raise CannotEvaluateFunctionException() + + return [host] + + +def get_source(container_holder, name, locator): + """ + A TOSCA orchestrator will interpret this keyword as the Node Template instance that is at the + source end of the relationship that contains the referencing function. + """ + + container = container_holder.container + if (not isinstance(container, Relationship)) and \ + (not isinstance(container, RelationshipTemplate)): + raise InvalidValueError('function "{0}" refers to "SOURCE" but it is not contained in ' + 'a relationship: {1}'.format(name, full_type_name(container)), + locator=locator) + + if not isinstance(container, RelationshipTemplate): + # RelationshipTemplate does not have "source_node"; we'll wait until instantiation + raise CannotEvaluateFunctionException() + + return [container.source_node] + + +def get_target(container_holder, name, locator): + """ + A TOSCA orchestrator will interpret this keyword as the Node Template instance that is at the + target end of the relationship that contains the referencing function. + """ + + container = container_holder.container + if (not isinstance(container, Relationship)) and \ + (not isinstance(container, RelationshipTemplate)): + raise InvalidValueError('function "{0}" refers to "TARGET" but it is not contained in ' + 'a relationship: {1}'.format(name, full_type_name(container)), + locator=locator) + + if not isinstance(container, RelationshipTemplate): + # RelationshipTemplate does not have "target_node"; we'll wait until instantiation + raise CannotEvaluateFunctionException() + + return [container.target_node] + + +def get_modelable_entity_parameter(modelable_entity, parameters, nested_parameter_name_or_index): + if not parameters: + return False, True, None + + found = True + final = True + value = parameters + + for name_or_index in nested_parameter_name_or_index: + if (isinstance(value, dict) and (name_or_index in value)) \ + or ((isinstance(value, list) and (name_or_index < len(value)))): + value = value[name_or_index] # Parameter + # We are not using Parameter.value, but rather Parameter._value, because we want to make + # sure to get "final" (it is swallowed by Parameter.value) + value, final = evaluate(value._value, final, value) + else: + found = False + break + + return Evaluation(value, final) if found else None + + +def invalid_modelable_entity_name(name, index, value, locator, contexts): + return InvalidValueError('function "{0}" parameter {1:d} can be "{2}" only in {3}' + .format(name, index + 1, value, contexts), + locator=locator, level=Issue.FIELD) + + +def invalid_value(name, index, the_type, explanation, value, locator): + return InvalidValueError( + 'function "{0}" {1} is not {2}{3}: {4}' + .format(name, + 'parameter {0:d}'.format(index + 1) if index is not None else 'argument', + the_type, + ', {0}'.format(explanation) if explanation is not None else '', + safe_repr(value)), + locator=locator, level=Issue.FIELD)