You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ariatosca.apache.org by em...@apache.org on 2017/05/11 16:31:50 UTC
[5/6] incubator-ariatosca git commit: ARIA-139 Support attributes
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)