Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 54A04200C56 for ; Fri, 14 Apr 2017 23:44:18 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id 53113160B8A; Fri, 14 Apr 2017 21:44:18 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 0272C160B8C for ; Fri, 14 Apr 2017 23:44:15 +0200 (CEST) Received: (qmail 23635 invoked by uid 500); 14 Apr 2017 21:44:15 -0000 Mailing-List: contact dev-help@ariatosca.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@ariatosca.incubator.apache.org Delivered-To: mailing list dev@ariatosca.incubator.apache.org Received: (qmail 23624 invoked by uid 99); 14 Apr 2017 21:44:15 -0000 Received: from pnap-us-west-generic-nat.apache.org (HELO spamd1-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 14 Apr 2017 21:44:15 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd1-us-west.apache.org (ASF Mail Server at spamd1-us-west.apache.org) with ESMTP id 98CD0C23C3 for ; Fri, 14 Apr 2017 21:44:14 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd1-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: -4.222 X-Spam-Level: X-Spam-Status: No, score=-4.222 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, RCVD_IN_DNSWL_HI=-5, RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01, RP_MATCHES_RCVD=-0.001, SPF_PASS=-0.001] autolearn=disabled Received: from mx1-lw-eu.apache.org ([10.40.0.8]) by localhost (spamd1-us-west.apache.org [10.40.0.7]) (amavisd-new, port 10024) with ESMTP id UWZetNrxQlrp for ; Fri, 14 Apr 2017 21:44:09 +0000 (UTC) Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx1-lw-eu.apache.org (ASF Mail Server at mx1-lw-eu.apache.org) with SMTP id D235F5FC3A for ; Fri, 14 Apr 2017 21:44:06 +0000 (UTC) Received: (qmail 23554 invoked by uid 99); 14 Apr 2017 21:44:06 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 14 Apr 2017 21:44:06 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id DF0BCDFFD7; Fri, 14 Apr 2017 21:44:05 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: emblemparade@apache.org To: dev@ariatosca.incubator.apache.org Date: Fri, 14 Apr 2017 21:44:06 -0000 Message-Id: In-Reply-To: <91f39e023b6f4ec89b9ffe8940b19309@git.apache.org> References: <91f39e023b6f4ec89b9ffe8940b19309@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: [2/2] incubator-ariatosca git commit: ARIA-92 Automatic operation task configuration archived-at: Fri, 14 Apr 2017 21:44:18 -0000 ARIA-92 Automatic operation task configuration Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/a7e7826e Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/a7e7826e Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/a7e7826e Branch: refs/heads/master Commit: a7e7826ed2d8b940b9e74ca15cb0284c39b01001 Parents: 8e1d059 Author: Tal Liron Authored: Fri Mar 24 16:33:11 2017 -0500 Committer: Tal Liron Committed: Fri Apr 14 13:54:21 2017 -0500 ---------------------------------------------------------------------- aria/cli/dry.py | 9 +- aria/modeling/models.py | 11 +- aria/modeling/orchestration.py | 92 +++++---- aria/modeling/service_common.py | 59 +----- aria/modeling/service_instance.py | 199 +++++++++++++------ aria/modeling/service_template.py | 133 ++++++++++++- aria/orchestrator/execution_plugin/__init__.py | 2 + aria/orchestrator/execution_plugin/common.py | 2 +- .../execution_plugin/instantiation.py | 191 ++++++++++++++++++ .../execution_plugin/ssh/operations.py | 4 +- aria/orchestrator/workflows/api/task.py | 131 +++++------- aria/orchestrator/workflows/api/task_graph.py | 2 +- aria/orchestrator/workflows/builtin/utils.py | 6 +- aria/orchestrator/workflows/core/engine.py | 4 +- .../workflows/core/events_handler.py | 8 +- aria/orchestrator/workflows/core/task.py | 9 +- aria/orchestrator/workflows/exceptions.py | 10 +- aria/orchestrator/workflows/executor/process.py | 2 +- aria/parser/consumption/modeling.py | 22 +- aria/storage/instrumentation.py | 4 +- .../custom_types/elasticsearch.yaml | 2 + .../multi-tier-1/custom_types/kibana.yaml | 2 + .../multi-tier-1/custom_types/logstash.yaml | 2 + .../paypalpizzastore_nodejs_app.yaml | 2 +- .../webserver-dbms-2/webserver-dbms-2.yaml | 6 +- .../profiles/aria-1.0/aria-1.0.yaml | 10 + .../profiles/tosca-simple-1.0/capabilities.yaml | 2 + .../profiles/tosca-simple-1.0/interfaces.yaml | 16 ++ .../profiles/tosca-simple-1.0/nodes.yaml | 1 + .../simple_v1_0/assignments.py | 49 +++-- .../simple_v1_0/modeling/__init__.py | 104 +++++++--- .../simple_v1_0/modeling/capabilities.py | 5 + tests/modeling/test_models.py | 65 +++--- tests/orchestrator/context/test_operation.py | 3 +- tests/orchestrator/context/test_serialize.py | 3 +- tests/orchestrator/execution_plugin/test_ssh.py | 17 +- tests/orchestrator/workflows/api/test_task.py | 24 +-- .../orchestrator/workflows/builtin/__init__.py | 3 - .../workflows/builtin/test_execute_operation.py | 3 +- tests/orchestrator/workflows/core/test_task.py | 29 +-- .../test_task_graph_into_exececution_graph.py | 3 +- .../workflows/executor/test_executor.py | 1 + .../workflows/executor/test_process_executor.py | 1 + .../node-cellar/node-cellar.yaml | 36 +++- .../node-cellar/types/nginx.yaml | 15 +- 45 files changed, 886 insertions(+), 418 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/cli/dry.py ---------------------------------------------------------------------- diff --git a/aria/cli/dry.py b/aria/cli/dry.py index 098638f..fc6c0c5 100644 --- a/aria/cli/dry.py +++ b/aria/cli/dry.py @@ -43,14 +43,19 @@ def convert_to_dry(service): for oper in interface.operations.itervalues(): convert_operation_to_dry(oper) + for group in service.groups.itervalues(): + for interface in group.interfaces.itervalues(): + for oper in interface.operations.itervalues(): + convert_operation_to_dry(oper) + def convert_operation_to_dry(oper): """ Converts a single :class:`Operation` to run dryly. """ - plugin = oper.plugin_specification.name \ - if oper.plugin_specification is not None else None + plugin = oper.plugin.name \ + if oper.plugin is not None else None if oper.inputs is None: oper.inputs = OrderedDict() oper.inputs['_implementation'] = models.Parameter(name='_implementation', http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/modeling/models.py ---------------------------------------------------------------------- diff --git a/aria/modeling/models.py b/aria/modeling/models.py index a01783b..170efb2 100644 --- a/aria/modeling/models.py +++ b/aria/modeling/models.py @@ -48,6 +48,7 @@ __all__ = ( 'InterfaceTemplate', 'OperationTemplate', 'ArtifactTemplate', + 'PluginSpecification', # Service instance models 'Service', @@ -71,7 +72,6 @@ __all__ = ( 'Parameter', 'Type', 'Metadata', - 'PluginSpecification', # Orchestration models 'Execution', @@ -131,6 +131,9 @@ class OperationTemplate(aria_declarative_base, service_template.OperationTemplat class ArtifactTemplate(aria_declarative_base, service_template.ArtifactTemplateBase): pass +class PluginSpecification(aria_declarative_base, service_template.PluginSpecificationBase): + pass + # endregion @@ -211,10 +214,6 @@ class Type(aria_declarative_base, service_common.TypeBase): class Metadata(aria_declarative_base, service_common.MetadataBase): pass - -class PluginSpecification(aria_declarative_base, service_common.PluginSpecificationBase): - pass - # endregion @@ -253,6 +252,7 @@ models_to_register = [ InterfaceTemplate, OperationTemplate, ArtifactTemplate, + PluginSpecification, # Service instance models Service, @@ -276,7 +276,6 @@ models_to_register = [ Parameter, Type, Metadata, - PluginSpecification, # Orchestration models Execution, http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/modeling/orchestration.py ---------------------------------------------------------------------- diff --git a/aria/modeling/orchestration.py b/aria/modeling/orchestration.py index f0bd4b2..b32a8a1 100644 --- a/aria/modeling/orchestration.py +++ b/aria/modeling/orchestration.py @@ -67,13 +67,13 @@ class ExecutionBase(ModelMixin): CANCELLING = 'cancelling' FORCE_CANCELLING = 'force_cancelling' - STATES = [TERMINATED, FAILED, CANCELLED, PENDING, STARTED, CANCELLING, FORCE_CANCELLING] - END_STATES = [TERMINATED, FAILED, CANCELLED] + STATES = (TERMINATED, FAILED, CANCELLED, PENDING, STARTED, CANCELLING, FORCE_CANCELLING) + END_STATES = (TERMINATED, FAILED, CANCELLED) VALID_TRANSITIONS = { - PENDING: [STARTED, CANCELLED], - STARTED: END_STATES + [CANCELLING], - CANCELLING: END_STATES + [FORCE_CANCELLING] + PENDING: (STARTED, CANCELLED), + STARTED: END_STATES + (CANCELLING,), + CANCELLING: END_STATES + (FORCE_CANCELLING,) } @orm.validates('status') @@ -219,7 +219,44 @@ class PluginBase(ModelMixin): class TaskBase(ModelMixin): """ - A Model which represents an task + Represents the smallest unit of stateful execution in ARIA. The task state includes inputs, + outputs, as well as an atomic status, ensuring that the task can only be running once at any + given time. + + Tasks may be "one shot" or may be configured to run repeatedly in the case of failure. + + Tasks are often based on :class:`Operation`, and thus act on either a :class:`Node` or a + :class:`Relationship`, however this is not required. + + :ivar node: The node actor (optional) + :vartype node: :class:`Node` + :ivar relationship: The relationship actor (optional) + :vartype relationship: :class:`Relationship` + :ivar plugin: The implementing plugin (set to None for default execution plugin) + :vartype plugin: :class:`Plugin` + :ivar inputs: Parameters that can be used by this task + :vartype inputs: {basestring: :class:`Parameter`} + :ivar implementation: Python path to an ``@operation`` function + :vartype implementation: basestring + :ivar max_attempts: Maximum number of retries allowed in case of failure + :vartype max_attempts: int + :ivar retry_interval: Interval between retries (in seconds) + :vartype retry_interval: int + :ivar ignore_failure: Set to True to ignore failures + :vartype ignore_failure: bool + :ivar due_at: Timestamp to start the task + :vartype due_at: datetime + :ivar execution: Assigned execution + :vartype execution: :class:`Execution` + :ivar status: Current atomic status ('pending', 'retrying', 'sent', 'started', 'success', + 'failed') + :vartype status: basestring + :ivar started_at: Timestamp for when task started + :vartype started_at: datetime + :ivar ended_at: Timestamp for when task ended + :vartype ended_at: datetime + :ivar retry_count: How many retries occurred + :vartype retry_count: int """ __tablename__ = 'task' @@ -227,7 +264,7 @@ class TaskBase(ModelMixin): __private_fields__ = ['node_fk', 'relationship_fk', 'plugin_fk', - 'execution_fk', + 'execution_fk' 'node_name', 'relationship_name', 'execution_name'] @@ -247,11 +284,6 @@ class TaskBase(ModelMixin): FAILED, ) - RUNS_ON_SOURCE = 'source' - RUNS_ON_TARGET = 'target' - RUNS_ON_NODE = 'node' - RUNS_ON = (RUNS_ON_NODE, RUNS_ON_SOURCE, RUNS_ON_TARGET) - INFINITE_RETRIES = -1 @declared_attr @@ -278,37 +310,25 @@ class TaskBase(ModelMixin): def inputs(cls): return relationship.many_to_many(cls, 'parameter', prefix='inputs', dict_key='name') - status = Column(Enum(*STATES, name='status'), default=PENDING) + implementation = Column(String) + max_attempts = Column(Integer, default=1) + retry_interval = Column(Float, default=0) + ignore_failure = Column(Boolean, default=False) + # State + status = Column(Enum(*STATES, name='status'), default=PENDING) due_at = Column(DateTime, nullable=False, index=True, default=datetime.utcnow()) started_at = Column(DateTime, default=None) ended_at = Column(DateTime, default=None) - max_attempts = Column(Integer, default=1) retry_count = Column(Integer, default=0) - retry_interval = Column(Float, default=0) - ignore_failure = Column(Boolean, default=False) - - # Operation specific fields - implementation = Column(String) - _runs_on = Column(Enum(*RUNS_ON, name='runs_on'), name='runs_on') @property def has_ended(self): - return self.status in [self.SUCCESS, self.FAILED] + return self.status in (self.SUCCESS, self.FAILED) @property def is_waiting(self): - return self.status in [self.PENDING, self.RETRYING] - - @property - def runs_on(self): - if self._runs_on == self.RUNS_ON_NODE: - return self.node - elif self._runs_on == self.RUNS_ON_SOURCE: - return self.relationship.source_node # pylint: disable=no-member - elif self._runs_on == self.RUNS_ON_TARGET: - return self.relationship.target_node # pylint: disable=no-member - return None + return self.status in (self.PENDING, self.RETRYING) @property def actor(self): @@ -366,12 +386,12 @@ class TaskBase(ModelMixin): # endregion @classmethod - def for_node(cls, instance, runs_on, **kwargs): - return cls(node=instance, _runs_on=runs_on, **kwargs) + def for_node(cls, actor, **kwargs): + return cls(node=actor, **kwargs) @classmethod - def for_relationship(cls, instance, runs_on, **kwargs): - return cls(relationship=instance, _runs_on=runs_on, **kwargs) + def for_relationship(cls, actor, **kwargs): + return cls(relationship=actor, **kwargs) @staticmethod def abort(message=None): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/modeling/service_common.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_common.py b/aria/modeling/service_common.py index 48c3170..1fcbc5f 100644 --- a/aria/modeling/service_common.py +++ b/aria/modeling/service_common.py @@ -101,7 +101,8 @@ class ParameterBase(TemplateModelMixin): from . import models return models.Parameter(name=name, - type_name=formatting.full_type_name(value), + type_name=formatting.full_type_name(value) + if value is not None else None, value=value, description=description) @@ -248,59 +249,3 @@ class MetadataBase(TemplateModelMixin): console.puts('{0}: {1}'.format( context.style.property(self.name), context.style.literal(self.value))) - - -class PluginSpecificationBase(TemplateModelMixin): - """ - Plugin specification. - - :ivar name: Required plugin name - :vartype name: basestring - :ivar version: Minimum plugin version - :vartype version: basestring - """ - - __tablename__ = 'plugin_specification' - - __private_fields__ = ['service_template_fk'] - - version = Column(Text, nullable=True) - - # region foreign keys - - @declared_attr - def service_template_fk(cls): - """For ServiceTemplate one-to-many to PluginSpecification""" - return relationship.foreign_key('service_template', nullable=True) - - # endregion - - @declared_attr - def service_template(cls): - return relationship.many_to_one(cls, 'service_template') - - @property - def as_raw(self): - return collections.OrderedDict(( - ('name', self.name), - ('version', self.version))) - - def coerce_values(self, container, report_issues): - pass - - def instantiate(self, container): - from . import models - return models.PluginSpecification(name=self.name, - version=self.version) - - def find_plugin(self, plugins): - matching_plugins = [] - for plugin in plugins: - # TODO: we need to use a version comparator - if (plugin.name == self.name) and \ - ((self.version is None) or (plugin.package_version >= self.version)): - matching_plugins.append(plugin) - if matching_plugins: - # Return highest version of plugin - return sorted(matching_plugins, key=lambda plugin: plugin.package_version)[-1] - return None http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/modeling/service_instance.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_instance.py b/aria/modeling/service_instance.py index d15aa7e..40d43fa 100644 --- a/aria/modeling/service_instance.py +++ b/aria/modeling/service_instance.py @@ -20,12 +20,14 @@ from sqlalchemy import ( Text, Integer, Enum, + Boolean ) from sqlalchemy import DateTime from sqlalchemy.ext.associationproxy import association_proxy from sqlalchemy.ext.declarative import declared_attr from .mixins import InstanceModelMixin +from ..orchestrator import execution_plugin from ..parser import validation from ..parser.consumption import ConsumptionContext from ..utils import collections, formatting, console @@ -65,8 +67,8 @@ class ServiceBase(InstanceModelMixin): :vartype outputs: {basestring: :class:`Parameter`} :ivar workflows: Custom workflows that can be performed on the service :vartype workflows: {basestring: :class:`Operation`} - :ivar plugin_specifications: Plugins used by the service - :vartype plugin_specifications: {basestring: :class:`PluginSpecification`} + :ivar plugins: Plugins used by the service + :vartype plugins: {basestring: :class:`Plugin`} :ivar created_at: Creation timestamp :vartype created_at: :class:`datetime.datetime` :ivar updated_at: Update timestamp @@ -176,13 +178,12 @@ class ServiceBase(InstanceModelMixin): return relationship.many_to_many(cls, 'parameter', prefix='outputs', dict_key='name') @declared_attr - def plugin_specifications(cls): - return relationship.many_to_many(cls, 'plugin_specification', dict_key='name') + def plugins(cls): + return relationship.many_to_many(cls, 'plugin', dict_key='name') # endregion description = Column(Text) - created_at = Column(DateTime, nullable=False, index=True) updated_at = Column(DateTime) @@ -207,6 +208,18 @@ class ServiceBase(InstanceModelMixin): satisfied = False return satisfied + def find_hosts(self): + for node in self.nodes.itervalues(): + node.find_host() + + def configure_operations(self): + for node in self.nodes.itervalues(): + node.configure_operations() + for group in self.groups.itervalues(): + group.configure_operations() + for operation in self.workflows.itervalues(): + operation.configure() + def is_node_a_target(self, target_node): for node in self.nodes.itervalues(): if self._is_node_a_target(node, target_node): @@ -215,11 +228,11 @@ class ServiceBase(InstanceModelMixin): def _is_node_a_target(self, source_node, target_node): if source_node.outbound_relationships: - for the_relationship in source_node.outbound_relationships: - if the_relationship.target_node.name == target_node.name: + for relationship_model in source_node.outbound_relationships: + if relationship_model.target_node.name == target_node.name: return True else: - node = the_relationship.target_node + node = relationship_model.target_node if node is not None: if self._is_node_a_target(node, target_node): return True @@ -282,38 +295,25 @@ class ServiceBase(InstanceModelMixin): if not self.is_node_a_target(node): self._dump_graph_node(node) - def _dump_graph_node(self, node): + def _dump_graph_node(self, node, capability=None): context = ConsumptionContext.get_thread_local() console.puts(context.style.node(node.name)) + if capability is not None: + console.puts('{0} ({1})'.format(context.style.property(capability.name), + context.style.type(capability.type.name))) if node.outbound_relationships: with context.style.indent: - for the_relationship in node.outbound_relationships: - relationship_name = context.style.property(the_relationship.name) - if the_relationship.type is not None: - relationship_type = context.style.type(the_relationship.type.name) + for relationship_model in node.outbound_relationships: + relationship_name = context.style.property(relationship_model.name) + if relationship_model.type is not None: + console.puts('-> {0} ({1})'.format(relationship_name, + context.style.type( + relationship_model.type.name))) else: - relationship_type = None - if the_relationship.target_capability is not None: - capability_name = \ - context.style.node(the_relationship.target_capability.name) - else: - capability_name = None - if capability_name is not None: - if relationship_type is not None: - console.puts('-> {0} ({1}) {2}'.format(relationship_name, - relationship_type, - capability_name)) - else: - console.puts('-> {0} {1}'.format(relationship_name, capability_name)) - else: - if relationship_type is not None: - console.puts('-> {0} ({1})'.format(relationship_name, - relationship_type)) - else: - console.puts('-> {0}'.format(relationship_name)) - target_node = the_relationship.target_node + console.puts('-> {0}'.format(relationship_name)) with console.indent(3): - self._dump_graph_node(target_node) + self._dump_graph_node(relationship_model.target_node, + relationship_model.target_capability) class NodeBase(InstanceModelMixin): @@ -360,8 +360,10 @@ class NodeBase(InstanceModelMixin): :vartype policies: [:class:`Policy`] :ivar substitution_mapping: Our contribution to service substitution :vartype substitution_mapping: :class:`SubstitutionMapping` - :ivar tasks: Tasks on this node + :ivar tasks: Tasks for this node :vartype tasks: [:class:`Task`] + :ivar hosted_tasks: Tasks on this node + :vartype hosted_tasks: [:class:`Task`] """ __tablename__ = 'node' @@ -417,7 +419,7 @@ class NodeBase(InstanceModelMixin): @property def is_available(self): - return self.state not in [self.INITIAL, self.DELETED, self.ERROR] + return self.state not in (self.INITIAL, self.DELETED, self.ERROR) # region foreign_keys @@ -455,7 +457,7 @@ class NodeBase(InstanceModelMixin): # region one_to_one relationships @declared_attr - def host(cls): + def host(cls): # pylint: disable=method-hidden return relationship.one_to_one_self(cls, 'host_fk') # endregion @@ -522,17 +524,9 @@ class NodeBase(InstanceModelMixin): __mapper_args__ = {'version_id_col': version} # Enable SQLAlchemy automatic version counting @property - def ip(self): - # TODO: totally broken - if not self.host_fk: - return None - host_node = self.host - if 'ip' in host_node.runtime_properties: # pylint: disable=no-member - return host_node.runtime_properties['ip'] # pylint: disable=no-member - host_node = host_node.node_template # pylint: disable=no-member - host_ip_property = host_node.properties.get('ip') - if host_ip_property: - return host_ip_property.value + def host_address(self): + if self.host and self.host.runtime_properties: + return self.host.runtime_properties.get('ip') return None def satisfy_requirements(self): @@ -567,9 +561,10 @@ class NodeBase(InstanceModelMixin): if target_node_capability is not None: # Relate to the first target node that has capacity for node in target_nodes: - target_capability = node.capabilities.get(target_node_capability.name) - if target_capability.relate(): + a_target_capability = node.capabilities.get(target_node_capability.name) + if a_target_capability.relate(): target_node = node + target_capability = a_target_capability break else: # Use first target node @@ -577,14 +572,15 @@ class NodeBase(InstanceModelMixin): if target_node is not None: if requirement_template.relationship_template is not None: - the_relationship = \ + relationship_model = \ requirement_template.relationship_template.instantiate(self) else: - the_relationship = models.Relationship(target_capability=target_capability) - the_relationship.name = requirement_template.name - the_relationship.requirement_template = requirement_template - the_relationship.target_node = target_node - self.outbound_relationships.append(the_relationship) + relationship_model = models.Relationship() + relationship_model.name = requirement_template.name + relationship_model.requirement_template = requirement_template + relationship_model.target_node = target_node + relationship_model.target_capability = target_capability + self.outbound_relationships.append(relationship_model) return True else: context.validation.report('requirement "{0}" of node "{1}" targets node ' @@ -619,6 +615,32 @@ class NodeBase(InstanceModelMixin): satisfied = False return satisfied + def find_host(self): + def _find_host(node): + if node.type.role == 'host': + return node + for the_relationship in node.outbound_relationships: + if (the_relationship.target_capability is not None) and \ + the_relationship.target_capability.type.role == 'host': + host = _find_host(the_relationship.target_node) + if host is not None: + return host + for the_relationship in node.inbound_relationships: + if (the_relationship.target_capability is not None) and \ + the_relationship.target_capability.type.role == 'feature': + host = _find_host(the_relationship.source_node) + if host is not None: + return host + return None + + self.host = _find_host(self) + + def configure_operations(self): + for interface in self.interfaces.itervalues(): + interface.configure_operations() + for the_relationship in self.outbound_relationships: + the_relationship.configure_operations() + @property def as_raw(self): return collections.OrderedDict(( @@ -761,6 +783,10 @@ class GroupBase(InstanceModelMixin): description = Column(Text) + def configure_operations(self): + for interface in self.interfaces.itervalues(): + interface.configure_operations() + @property def as_raw(self): return collections.OrderedDict(( @@ -1146,7 +1172,7 @@ class RelationshipBase(InstanceModelMixin): :vartype source_node: :class:`Node` :ivar target_node: Target node :vartype target_node: :class:`Node` - :ivar tasks: Tasks on this node + :ivar tasks: Tasks for this relationship :vartype tasks: [:class:`Task`] """ @@ -1266,6 +1292,10 @@ class RelationshipBase(InstanceModelMixin): source_position = Column(Integer) # ??? target_position = Column(Integer) # ??? + def configure_operations(self): + for interface in self.interfaces.itervalues(): + interface.configure_operations() + @property def as_raw(self): return collections.OrderedDict(( @@ -1552,6 +1582,10 @@ class InterfaceBase(InstanceModelMixin): description = Column(Text) + def configure_operations(self): + for operation in self.operations.itervalues(): + operation.configure() + @property def as_raw(self): return collections.OrderedDict(( @@ -1592,10 +1626,16 @@ class OperationBase(InstanceModelMixin): :vartype operation_template: :class:`OperationTemplate` :ivar description: Human-readable description :vartype description: string - :ivar plugin_specification: Associated plugin - :vartype plugin_specification: :class:`PluginSpecification` - :ivar implementation: Implementation string (interpreted by the plugin) + :ivar plugin: Associated plugin + :vartype plugin: :class:`Plugin` + :ivar relationship_edge: When true specified that the operation is on the relationship's + target edge instead of its source (only used by relationship + operations) + :vartype relationship_edge: bool + :ivar implementation: Implementation (interpreted by the plugin) :vartype implementation: basestring + :ivar configuration: Configuration (interpreted by the plugin) + :vartype configuration: {basestring, object} :ivar dependencies: Dependency strings (interpreted by the plugin) :vartype dependencies: [basestring] :ivar inputs: Parameters that can be used by this operation @@ -1632,9 +1672,9 @@ class OperationBase(InstanceModelMixin): return relationship.foreign_key('interface', nullable=True) @declared_attr - def plugin_specification_fk(cls): - """For Operation one-to-one to PluginSpecification""" - return relationship.foreign_key('plugin_specification', nullable=True) + def plugin_fk(cls): + """For Operation one-to-one to Plugin""" + return relationship.foreign_key('plugin', nullable=True) @declared_attr def operation_template_fk(cls): @@ -1650,9 +1690,8 @@ class OperationBase(InstanceModelMixin): # region one_to_one relationships @declared_attr - def plugin_specification(cls): - return relationship.one_to_one( - cls, 'plugin_specification', back_populates=relationship.NO_BACK_POP) + def plugin(cls): + return relationship.one_to_one(cls, 'plugin', back_populates=relationship.NO_BACK_POP) # endregion @@ -1685,12 +1724,32 @@ class OperationBase(InstanceModelMixin): # endregion description = Column(Text) + relationship_edge = Column(Boolean) implementation = Column(Text) + configuration = Column(modeling_types.StrictDict(key_cls=basestring)) dependencies = Column(modeling_types.StrictList(item_cls=basestring)) executor = Column(Text) max_retries = Column(Integer) retry_interval = Column(Integer) + def configure(self): + from . import models + # Note: for workflows (operations attached directly to the service) "interface" will be None + if (self.implementation is None) or (self.interface is None): + return + + if self.plugin is None: + arguments = execution_plugin.instantiation.configure_operation(self) + else: + # In the future plugins may be able to add their own "configure_operation" hook that + # can validate the configuration and otherwise return specially derived arguments + arguments = self.configuration + + # Note: the arguments will *override* operation inputs of the same name + if arguments: + for k, v in arguments.iteritems(): + self.inputs[k] = models.Parameter.wrap(k, v) + @property def as_raw(self): return collections.OrderedDict(( @@ -1716,9 +1775,17 @@ class OperationBase(InstanceModelMixin): if self.description: console.puts(context.style.meta(self.description)) with context.style.indent: + if self.plugin is not None: + console.puts('Plugin: {0}'.format( + context.style.literal(self.plugin.name))) if self.implementation is not None: console.puts('Implementation: {0}'.format( context.style.literal(self.implementation))) + if self.configuration: + with context.style.indent: + for k, v in self.configuration.iteritems(): + console.puts('{0}: {1}'.format(context.style.property(k), + context.style.literal(v))) if self.dependencies: console.puts( 'Dependencies: {0}'.format( http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/modeling/service_template.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py index 8355521..51fea2f 100644 --- a/aria/modeling/service_template.py +++ b/aria/modeling/service_template.py @@ -24,6 +24,7 @@ from sqlalchemy import ( Column, Text, Integer, + Boolean, DateTime ) from sqlalchemy.ext.declarative import declared_attr @@ -291,6 +292,16 @@ class ServiceTemplateBase(TemplateModelMixin): context.modeling.instance = service + for plugin_specification in self.plugin_specifications.itervalues(): + if plugin_specification.enabled: + if plugin_specification.resolve(): + plugin = plugin_specification.plugin + service.plugins[plugin.name] = plugin + else: + context = ConsumptionContext.get_thread_local() + context.validation.report('specified plugin not found: {0}'.format( + plugin_specification.name), level=validation.Issue.EXTERNAL) + utils.instantiate_dict(self, service.meta_data, self.meta_data) for node_template in self.node_templates.itervalues(): @@ -301,7 +312,6 @@ class ServiceTemplateBase(TemplateModelMixin): utils.instantiate_dict(self, service.groups, self.group_templates) utils.instantiate_dict(self, service.policies, self.policy_templates) utils.instantiate_dict(self, service.workflows, self.workflow_templates) - utils.instantiate_dict(self, service.plugin_specifications, self.plugin_specifications) if self.substitution_template is not None: service.substitution = self.substitution_template.instantiate(container) @@ -1740,8 +1750,14 @@ class OperationTemplateBase(TemplateModelMixin): :vartype description: basestring :ivar plugin_specification: Associated plugin :vartype plugin_specification: :class:`PluginSpecification` - :ivar implementation: Implementation string (interpreted by the plugin) + :ivar relationship_edge: When true specified that the operation is on the relationship's + target edge instead of its source (only used by relationship + operations) + :vartype relationship_edge: bool + :ivar implementation: Implementation (interpreted by the plugin) :vartype implementation: basestring + :ivar configuration: Configuration (interpreted by the plugin) + :vartype configuration: {basestring, object} :ivar dependencies: Dependency strings (interpreted by the plugin) :vartype dependencies: [basestring] :ivar inputs: Parameters that can be used by this operation @@ -1766,8 +1782,6 @@ class OperationTemplateBase(TemplateModelMixin): 'interface_template_fk', 'plugin_fk'] - description = Column(Text) - # region foreign keys @declared_attr @@ -1828,7 +1842,10 @@ class OperationTemplateBase(TemplateModelMixin): # endregion + description = Column(Text) + relationship_edge = Column(Boolean) implementation = Column(Text) + configuration = Column(modeling_types.StrictDict(key_cls=basestring)) dependencies = Column(modeling_types.StrictList(item_cls=basestring)) executor = Column(Text) max_retries = Column(Integer) @@ -1848,11 +1865,23 @@ class OperationTemplateBase(TemplateModelMixin): def instantiate(self, container): from . import models + if self.plugin_specification and self.plugin_specification.enabled: + plugin = self.plugin_specification.plugin + implementation = self.implementation if plugin is not None else None + # "plugin" would be none if a match was not found. In that case, a validation error + # should already have been reported in ServiceTemplateBase.instantiate, so we will + # continue silently here + else: + # If the plugin is disabled, the operation should be disabled, too + plugin = None + implementation = None operation = models.Operation(name=self.name, description=deepcopy_with_locators(self.description), - implementation=self.implementation, + relationship_edge=self.relationship_edge, + plugin=plugin, + implementation=implementation, + configuration=self.configuration, dependencies=self.dependencies, - plugin_specification=self.plugin_specification, executor=self.executor, max_retries=self.max_retries, retry_interval=self.retry_interval, @@ -1872,9 +1901,17 @@ class OperationTemplateBase(TemplateModelMixin): if self.description: console.puts(context.style.meta(self.description)) with context.style.indent: + if self.plugin_specification is not None: + console.puts('Plugin specification: {0}'.format( + context.style.literal(self.plugin_specification.name))) if self.implementation is not None: console.puts('Implementation: {0}'.format( context.style.literal(self.implementation))) + if self.configuration: + with context.style.indent: + for k, v in self.configuration.iteritems(): + console.puts('{0}: {1}'.format(context.style.property(k), + context.style.literal(v))) if self.dependencies: console.puts('Dependencies: {0}'.format( ', '.join((str(context.style.literal(v)) for v in self.dependencies)))) @@ -2023,3 +2060,87 @@ class ArtifactTemplateBase(TemplateModelMixin): console.puts('Repository credential: {0}'.format( context.style.literal(self.repository_credential))) utils.dump_dict_values(self.properties, 'Properties') + + +class PluginSpecificationBase(TemplateModelMixin): + """ + Plugin specification. + + :ivar name: Required plugin name + :vartype name: basestring + :ivar version: Minimum plugin version + :vartype version: basestring + :ivar enabled: Whether the plugin is enabled + :vartype enabled: bool + :ivar plugin: The matching plugin (or None if not matched) + :vartype plugin: :class:`Plugin` + """ + + __tablename__ = 'plugin_specification' + + __private_fields__ = ['service_template_fk', + 'plugin_fk'] + + version = Column(Text) + enabled = Column(Boolean, nullable=False, default=True) + + # region foreign keys + + @declared_attr + def service_template_fk(cls): + """For ServiceTemplate one-to-many to PluginSpecification""" + return relationship.foreign_key('service_template', nullable=True) + + @declared_attr + def plugin_fk(cls): + """For PluginSpecification many-to-one to Plugin""" + return relationship.foreign_key('plugin', nullable=True) + + # endregion + + # region many_to_one relationships + + @declared_attr + def service_template(cls): + return relationship.many_to_one(cls, 'service_template') + + @declared_attr + def plugin(cls): # pylint: disable=method-hidden + return relationship.many_to_one(cls, 'plugin', back_populates=relationship.NO_BACK_POP) + + # endregion + + @property + def as_raw(self): + return collections.OrderedDict(( + ('name', self.name), + ('version', self.version), + ('enabled', self.enabled))) + + def coerce_values(self, container, report_issues): + pass + + def resolve(self): + # TODO: we are planning a separate "instantiation" module where this will be called or + # moved to. There, we will probably have a context with a storage manager. Until then, + # this is the only potentially available context, which of course will only be available + # if we're in a workflow. + from ..orchestrator import context + try: + workflow_context = context.workflow.current.get() + plugins = workflow_context.model.plugin.list() + except context.exceptions.ContextException: + plugins = None + + matching_plugins = [] + if plugins: + for plugin in plugins: + # TODO: we need to use a version comparator + if (plugin.name == self.name) and \ + ((self.version is None) or (plugin.package_version >= self.version)): + matching_plugins.append(plugin) + self.plugin = None + if matching_plugins: + # Return highest version of plugin + self.plugin = sorted(matching_plugins, key=lambda plugin: plugin.package_version)[-1] + return self.plugin is not None http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/orchestrator/execution_plugin/__init__.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/execution_plugin/__init__.py b/aria/orchestrator/execution_plugin/__init__.py index 372022f..3624264 100644 --- a/aria/orchestrator/execution_plugin/__init__.py +++ b/aria/orchestrator/execution_plugin/__init__.py @@ -14,6 +14,8 @@ # limitations under the License. from contextlib import contextmanager +from . import instantiation + # Populated during execution of python scripts ctx = None http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/orchestrator/execution_plugin/common.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/execution_plugin/common.py b/aria/orchestrator/execution_plugin/common.py index 7915c47..32e4575 100644 --- a/aria/orchestrator/execution_plugin/common.py +++ b/aria/orchestrator/execution_plugin/common.py @@ -34,7 +34,7 @@ def download_script(ctx, script_path): file_descriptor, dest_script_path = tempfile.mkstemp(suffix='-{0}'.format(suffix)) os.close(file_descriptor) try: - if schema in ['http', 'https']: + if schema in ('http', 'https'): response = requests.get(script_path) if response.status_code == 404: ctx.task.abort('Failed to download script: {0} (status code: {1})' http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/orchestrator/execution_plugin/instantiation.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/execution_plugin/instantiation.py b/aria/orchestrator/execution_plugin/instantiation.py new file mode 100644 index 0000000..960835c --- /dev/null +++ b/aria/orchestrator/execution_plugin/instantiation.py @@ -0,0 +1,191 @@ +# 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. + +# TODO: this module will eventually be moved to a new "aria.instantiation" package + +from ...utils.formatting import full_type_name +from ...utils.collections import OrderedDict +from ...parser import validation +from ...parser.consumption import ConsumptionContext + + +def configure_operation(operation): + configuration = OrderedDict(operation.configuration) if operation.configuration else {} + + arguments = OrderedDict() + arguments['script_path'] = operation.implementation + arguments['process'] = _get_process(configuration.pop('process')) \ + if 'process' in configuration else None + + host = None + interface = operation.interface + if interface.node is not None: + host = interface.node.host + elif interface.relationship is not None: + if operation.relationship_edge is True: + host = interface.relationship.target_node.host + else: # either False or None + host = interface.relationship.source_node.host + + if host is None: + _configure_local(operation) + else: + _configure_remote(operation, configuration, arguments) + + # Any remaining unhandled configuration values will become extra arguments, available as kwargs + # in either "run_script_locally" or "run_script_with_ssh" + arguments.update(configuration) + + return arguments + +def _configure_local(operation): + """ + Local operation. + """ + from . import operations + operation.implementation = '{0}.{1}'.format(operations.__name__, + operations.run_script_locally.__name__) + + +def _configure_remote(operation, configuration, arguments): + """ + Remote SSH operation via Fabric. + """ + # TODO: find a way to configure these generally in the service template + default_user = '' + default_password = '' + + ssh = _get_ssh(configuration.pop('ssh')) if 'ssh' in configuration else {} + if 'user' not in ssh: + ssh['user'] = default_user + if ('password' not in ssh) and ('key' not in ssh) and ('key_filename' not in ssh): + ssh['password'] = default_password + + arguments['use_sudo'] = ssh.get('use_sudo') + arguments['hide_output'] = ssh.get('hide_output') + arguments['fabric_env'] = {} + if 'warn_only' in ssh: + arguments['fabric_env']['warn_only'] = ssh['warn_only'] + arguments['fabric_env']['user'] = ssh.get('user') + arguments['fabric_env']['password'] = ssh.get('password') + arguments['fabric_env']['key'] = ssh.get('key') + arguments['fabric_env']['key_filename'] = ssh.get('key_filename') + if 'address' in ssh: + arguments['fabric_env']['host_string'] = ssh['address'] + + if arguments['fabric_env'].get('user') is None: + context = ConsumptionContext.get_thread_local() + context.validation.report('must configure "ssh.user" for "{0}"' + .format(operation.implementation), + level=validation.Issue.BETWEEN_TYPES) + if (arguments['fabric_env'].get('password') is None) and \ + (arguments['fabric_env'].get('key') is None) and \ + (arguments['fabric_env'].get('key_filename') is None): + context = ConsumptionContext.get_thread_local() + context.validation.report('must configure "ssh.password", "ssh.key", or "ssh.key_filename" ' + 'for "{0}"' + .format(operation.implementation), + level=validation.Issue.BETWEEN_TYPES) + + from . import operations + operation.implementation = '{0}.{1}'.format(operations.__name__, + operations.run_script_with_ssh.__name__) + + +def _get_process(value): + if value is None: + return None + _validate_type(value, dict, 'process') + for k, v in value.iteritems(): + if k == 'eval_python': + value[k] = _str_to_bool(v, 'process.eval_python') + elif k == 'cwd': + _validate_type(v, basestring, 'process.cwd') + elif k == 'command_prefix': + _validate_type(v, basestring, 'process.command_prefix') + elif k == 'args': + value[k] = _dict_to_list(v, 'process.args') + elif k == 'env': + _validate_type(v, dict, 'process.env') + else: + context = ConsumptionContext.get_thread_local() + context.validation.report('unsupported configuration: "process.{0}"'.format(k), + level=validation.Issue.BETWEEN_TYPES) + return value + + +def _get_ssh(value): + if value is None: + return {} + _validate_type(value, dict, 'ssh') + for k, v in value.iteritems(): + if k == 'use_sudo': + value[k] = _str_to_bool(v, 'ssh.use_sudo') + elif k == 'hide_output': + value[k] = _dict_to_list(v, 'ssh.hide_output') + elif k == 'warn_only': + value[k] = _str_to_bool(v, 'ssh.warn_only') + elif k == 'user': + _validate_type(v, basestring, 'ssh.user') + elif k == 'password': + _validate_type(v, basestring, 'ssh.password') + elif k == 'key': + _validate_type(v, basestring, 'ssh.key') + elif k == 'key_filename': + _validate_type(v, basestring, 'ssh.key_filename') + elif k == 'address': + _validate_type(v, basestring, 'ssh.address') + else: + context = ConsumptionContext.get_thread_local() + context.validation.report('unsupported configuration: "ssh.{0}"'.format(k), + level=validation.Issue.BETWEEN_TYPES) + return value + + +def _validate_type(value, the_type, name): + if not isinstance(value, the_type): + context = ConsumptionContext.get_thread_local() + context.validation.report('"{0}" configuration is not a {1}' + .format(name, full_type_name(the_type)), + level=validation.Issue.BETWEEN_TYPES) + + +def _str_to_bool(value, name): + if value is None: + return None + _validate_type(value, basestring, name) + if value == 'true': + return True + elif value == 'false': + return False + else: + context = ConsumptionContext.get_thread_local() + context.validation.report('"{0}" configuration is not "true" or "false": {1}' + .format(name, repr(value)), + level=validation.Issue.BETWEEN_TYPES) + + +def _dict_to_list(the_dict, name): + _validate_type(the_dict, dict, name) + value = [] + for k in sorted(the_dict): + v = the_dict[k] + if not isinstance(v, basestring): + context = ConsumptionContext.get_thread_local() + context.validation.report('"{0}.{1}" configuration is not a string: {2}' + .format(name, k, repr(v)), + level=validation.Issue.BETWEEN_TYPES) + value.append(v) + return value http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/orchestrator/execution_plugin/ssh/operations.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/execution_plugin/ssh/operations.py b/aria/orchestrator/execution_plugin/ssh/operations.py index f240beb..7147a30 100644 --- a/aria/orchestrator/execution_plugin/ssh/operations.py +++ b/aria/orchestrator/execution_plugin/ssh/operations.py @@ -143,9 +143,9 @@ def _fabric_env(ctx, fabric_env, warn_only): env = constants.FABRIC_ENV_DEFAULTS.copy() env.update(fabric_env or {}) env.setdefault('warn_only', warn_only) - if 'host_string' not in env: - env['host_string'] = ctx.task.runs_on.ip # validations + if (not env.get('host_string')) and (ctx.task) and (ctx.task.actor) and (ctx.task.actor.host): + env['host_string'] = ctx.task.actor.host.host_address if not env.get('host_string'): ctx.task.abort('`host_string` not supplied and ip cannot be deduced automatically') if not (env.get('password') or env.get('key_filename') or env.get('key')): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/orchestrator/workflows/api/task.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/workflows/api/task.py b/aria/orchestrator/workflows/api/task.py index f49ec2e..49c584c 100644 --- a/aria/orchestrator/workflows/api/task.py +++ b/aria/orchestrator/workflows/api/task.py @@ -19,7 +19,7 @@ Provides the tasks to be entered into the task graph import copy from ....modeling import models -from ....utils.collections import OrderedDict +from ....utils.collections import (OrderedDict, FrozenDict) from ....utils.uuid import generate_uuid from ... import context from .. import exceptions @@ -56,7 +56,7 @@ class BaseTask(object): class OperationTask(BaseTask): """ - Represents an operation task in the task_graph + Represents an operation task in the task graph. """ NAME_FORMAT = '{interface}:{operation}@{type}:{name}' @@ -66,32 +66,50 @@ class OperationTask(BaseTask): actor_type, interface_name, operation_name, - runs_on=None, + inputs=None, max_attempts=None, retry_interval=None, - ignore_failure=None, - inputs=None): + ignore_failure=None): """ Do not call this constructor directly. Instead, use :meth:`for_node` or :meth:`for_relationship`. """ - assert isinstance(actor, (models.Node, models.Relationship)) - assert actor_type in ('node', 'relationship') assert interface_name and operation_name - assert runs_on in models.Task.RUNS_ON super(OperationTask, self).__init__() + operation = None + interface = actor.interfaces.get(interface_name) + if interface is not None: + operation = interface.operations.get(operation_name) + + if operation is None: + raise exceptions.OperationNotFoundException( + 'Could not find operation "{0}" on interface "{1}" for {2} "{3}"' + .format(operation_name, interface_name, actor_type, actor.name)) + + if operation.implementation is None: + raise exceptions.OperationNotFoundException( + 'Empty operation "{0}" on interface "{1}" for {2} "{3}"' + .format(operation_name, interface_name, actor_type, actor.name)) + self.actor = actor + self.actor_type = actor_type + self.interface_name = interface_name + self.operation_name = operation_name + + self.name = OperationTask.NAME_FORMAT.format(type=actor_type, + name=actor.name, + interface=interface_name, + operation=operation_name) self.max_attempts = (self.workflow_context._task_max_attempts if max_attempts is None else max_attempts) self.retry_interval = (self.workflow_context._task_retry_interval if retry_interval is None else retry_interval) self.ignore_failure = (self.workflow_context._task_ignore_failure if ignore_failure is None else ignore_failure) - self.runs_on = runs_on - self.interface_name = interface_name - self.operation_name = operation_name + self.implementation = operation.implementation + self.plugin = operation.plugin # Wrap inputs inputs = copy.deepcopy(inputs) if inputs else {} @@ -99,65 +117,33 @@ class OperationTask(BaseTask): if not isinstance(v, models.Parameter): inputs[k] = models.Parameter.wrap(k, v) - # TODO: Suggestion: these extra inputs could be stored as a separate entry in the task - # model, because they are different from the operation inputs. If we do this, then the two - # kinds of inputs should *not* be merged here. - - operation = self._get_operation() - if operation is None: - raise exceptions.OperationNotFoundException( - 'Could not find operation "{0}" on interface "{1}" for {2} "{3}"' - .format(self.operation_name, self.interface_name, actor_type, actor.name)) - - self.plugin = None - if operation.plugin_specification: - self.plugin = OperationTask._find_plugin(operation.plugin_specification) - if self.plugin is None: - raise exceptions.PluginNotFoundException( - 'Could not find plugin of operation "{0}" on interface "{1}" for {2} "{3}"' - .format(self.operation_name, self.interface_name, actor_type, actor.name)) - - self.implementation = operation.implementation - self.inputs = OperationTask._merge_inputs(operation.inputs, inputs) - - self.name = OperationTask.NAME_FORMAT.format(type=actor_type, - name=actor.name, - interface=self.interface_name, - operation=self.operation_name) - - def __repr__(self): - return self.name - - def _get_operation(self): - interface = self.actor.interfaces.get(self.interface_name) - if interface: - return interface.operations.get(self.operation_name) - return None - - + self.inputs = OrderedDict(operation.inputs) + if inputs: + self.inputs.update(inputs) + self.inputs = FrozenDict(self.inputs) @classmethod def for_node(cls, node, interface_name, operation_name, + inputs=None, max_attempts=None, retry_interval=None, - ignore_failure=None, - inputs=None): + ignore_failure=None): """ Creates an operation on a node. :param node: The node on which to run the operation :param interface_name: The interface name :param operation_name: The operation name within the interface + :param inputs: Override the operation's inputs :param max_attempts: The maximum number of attempts in case the operation fails - (if not specified the defaults it taken from the workflow context) + (if not specified the defaults is taken from the workflow context) :param retry_interval: The interval in seconds between attempts when the operation fails - (if not specified the defaults it taken from the workflow context) + (if not specified the defaults is taken from the workflow context) :param ignore_failure: Whether to ignore failures - (if not specified the defaults it taken from the workflow context) - :param inputs: Additional operation inputs + (if not specified the defaults is taken from the workflow context) """ assert isinstance(node, models.Node) @@ -166,62 +152,45 @@ class OperationTask(BaseTask): actor_type='node', interface_name=interface_name, operation_name=operation_name, + inputs=inputs, max_attempts=max_attempts, retry_interval=retry_interval, - ignore_failure=ignore_failure, - inputs=inputs, - runs_on=models.Task.RUNS_ON_NODE) + ignore_failure=ignore_failure) @classmethod def for_relationship(cls, relationship, interface_name, operation_name, - runs_on=models.Task.RUNS_ON_SOURCE, + inputs=None, max_attempts=None, retry_interval=None, - ignore_failure=None, - inputs=None): + ignore_failure=None): """ - Creates an operation on a relationship edge. + Creates an operation on a relationship. :param relationship: The relationship on which to run the operation :param interface_name: The interface name :param operation_name: The operation name within the interface - :param runs_on: where to run the operation ("source" or "target"); defaults to "source" + :param inputs: Override the operation's inputs :param max_attempts: The maximum number of attempts in case the operation fails - (if not specified the defaults it taken from the workflow context) + (if not specified the defaults is taken from the workflow context) :param retry_interval: The interval in seconds between attempts when the operation fails - (if not specified the defaults it taken from the workflow context) + (if not specified the defaults is taken from the workflow context) :param ignore_failure: Whether to ignore failures - (if not specified the defaults it taken from the workflow context) - :param inputs: Additional operation inputs + (if not specified the defaults is taken from the workflow context) """ assert isinstance(relationship, models.Relationship) - assert runs_on in models.Task.RUNS_ON return cls( actor=relationship, actor_type='relationship', interface_name=interface_name, operation_name=operation_name, - runs_on=runs_on, + inputs=inputs, max_attempts=max_attempts, retry_interval=retry_interval, - ignore_failure=ignore_failure, - inputs=inputs) - - @staticmethod - def _find_plugin(plugin_specification): - workflow_context = context.workflow.current.get() - return plugin_specification.find_plugin(workflow_context.model.plugin.list()) - - @staticmethod - def _merge_inputs(operation_inputs, override_inputs=None): - final_inputs = OrderedDict(operation_inputs) - if override_inputs: - final_inputs.update(override_inputs) - return final_inputs + ignore_failure=ignore_failure) class WorkflowTask(BaseTask): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/orchestrator/workflows/api/task_graph.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/workflows/api/task_graph.py b/aria/orchestrator/workflows/api/task_graph.py index 92a39d2..9f0d13b 100644 --- a/aria/orchestrator/workflows/api/task_graph.py +++ b/aria/orchestrator/workflows/api/task_graph.py @@ -37,7 +37,7 @@ def _filter_out_empty_tasks(func=None): return lambda f: _filter_out_empty_tasks(func=f) def _wrapper(task, *tasks, **kwargs): - return func(*(t for t in [task] + list(tasks) if t), **kwargs) + return func(*(t for t in (task,) + tuple(tasks) if t), **kwargs) return _wrapper http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/orchestrator/workflows/builtin/utils.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/workflows/builtin/utils.py b/aria/orchestrator/workflows/builtin/utils.py index d79318f..752fe35 100644 --- a/aria/orchestrator/workflows/builtin/utils.py +++ b/aria/orchestrator/workflows/builtin/utils.py @@ -71,15 +71,13 @@ def relationship_tasks( operations.append( OperationTask.for_relationship(relationship=relationship, interface_name=interface_name, - operation_name=source_operation_name, - runs_on='source') + operation_name=source_operation_name) ) if target_operation_name: operations.append( OperationTask.for_relationship(relationship=relationship, interface_name=interface_name, - operation_name=target_operation_name, - runs_on='target') + operation_name=target_operation_name) ) return operations http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/orchestrator/workflows/core/engine.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/workflows/core/engine.py b/aria/orchestrator/workflows/core/engine.py index d32abb8..f73cade 100644 --- a/aria/orchestrator/workflows/core/engine.py +++ b/aria/orchestrator/workflows/core/engine.py @@ -82,8 +82,8 @@ class Engine(logger.LoggerMixin): events.on_cancelling_workflow_signal.send(self._workflow_context) def _is_cancel(self): - return self._workflow_context.execution.status in [models.Execution.CANCELLING, - models.Execution.CANCELLED] + return self._workflow_context.execution.status in (models.Execution.CANCELLING, + models.Execution.CANCELLED) def _executable_tasks(self): now = datetime.utcnow() http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/orchestrator/workflows/core/events_handler.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/workflows/core/events_handler.py b/aria/orchestrator/workflows/core/events_handler.py index 8534aae..7f61bfa 100644 --- a/aria/orchestrator/workflows/core/events_handler.py +++ b/aria/orchestrator/workflows/core/events_handler.py @@ -125,8 +125,12 @@ def _workflow_cancelling(workflow_context, *args, **kwargs): def _update_node_state_if_necessary(task, is_transitional=False): - if task.interface_name in ['tosca.interfaces.node.lifecycle.Standard', 'Standard']: - node = task.runs_on + # TODO: this is not the right way to check! the interface name is arbitrary + # and also will *never* be the type name + model_task = task.model_task + node = model_task.node if model_task is not None else None + if (node is not None) and \ + (task.interface_name in ('Standard', 'tosca.interfaces.node.lifecycle.Standard')): state = node.determine_state(op_name=task.operation_name, is_transitional=is_transitional) if state: node.state = state http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/orchestrator/workflows/core/task.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/workflows/core/task.py b/aria/orchestrator/workflows/core/task.py index aa8963f..ba93e21 100644 --- a/aria/orchestrator/workflows/core/task.py +++ b/aria/orchestrator/workflows/core/task.py @@ -71,11 +71,11 @@ class StubTask(BaseTask): @property def has_ended(self): - return self.status in [models.Task.SUCCESS, models.Task.FAILED] + return self.status in (models.Task.SUCCESS, models.Task.FAILED) @property def is_waiting(self): - return self.status in [models.Task.PENDING, models.Task.RETRYING] + return self.status in (models.Task.PENDING, models.Task.RETRYING) class StartWorkflowTask(StubTask): @@ -133,15 +133,14 @@ class OperationTask(BaseTask): task_model = create_task_model( name=api_task.name, implementation=api_task.implementation, - instance=api_task.actor, + actor=api_task.actor, inputs=api_task.inputs, status=base_task_model.PENDING, max_attempts=api_task.max_attempts, retry_interval=api_task.retry_interval, ignore_failure=api_task.ignore_failure, plugin=plugin, - execution=self._workflow_context.execution, - runs_on=api_task.runs_on + execution=self._workflow_context.execution ) self._workflow_context.model.task.put(task_model) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/orchestrator/workflows/exceptions.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/workflows/exceptions.py b/aria/orchestrator/workflows/exceptions.py index 4fb8dd7..0ca263f 100644 --- a/aria/orchestrator/workflows/exceptions.py +++ b/aria/orchestrator/workflows/exceptions.py @@ -70,13 +70,19 @@ class TaskException(exceptions.AriaError): """ -class OperationNotFoundException(TaskException): +class TaskCreationException(TaskException): + """ + Could not create the task. + """ + + +class OperationNotFoundException(TaskCreationException): """ Could not find an operation on the node or relationship. """ -class PluginNotFoundException(TaskException): +class PluginNotFoundException(TaskCreationException): """ Could not find a plugin matching the plugin specification. """ http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/orchestrator/workflows/executor/process.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/workflows/executor/process.py b/aria/orchestrator/workflows/executor/process.py index 6397e88..f814c4d 100644 --- a/aria/orchestrator/workflows/executor/process.py +++ b/aria/orchestrator/workflows/executor/process.py @@ -177,7 +177,7 @@ class ProcessExecutor(base.BaseExecutor): pythonpath_dirs = [os.path.join( plugin_prefix, 'lib{0}'.format(b), 'python{0}.{1}'.format(sys.version_info[0], sys.version_info[1]), - 'site-packages') for b in ['', '64']] + 'site-packages') for b in ('', '64')] # Add used supplied directories to injected PYTHONPATH pythonpath_dirs.extend(self._python_path) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/parser/consumption/modeling.py ---------------------------------------------------------------------- diff --git a/aria/parser/consumption/modeling.py b/aria/parser/consumption/modeling.py index 4847ba7..6c616b4 100644 --- a/aria/parser/consumption/modeling.py +++ b/aria/parser/consumption/modeling.py @@ -103,7 +103,7 @@ class InstantiateServiceInstance(Consumer): def consume(self): if self.context.modeling.template is None: self.context.validation.report('InstantiateServiceInstance consumer: missing service ' - 'model') + 'template') return self.context.modeling.template.instantiate(None) @@ -145,6 +145,24 @@ class ValidateCapabilities(Consumer): self.context.modeling.instance.validate_capabilities() +class FindHosts(Consumer): + """ + Find hosts for all nodes in the service instance. + """ + + def consume(self): + self.context.modeling.instance.find_hosts() + + +class ConfigureOperations(Consumer): + """ + Configures all operations in the service instance. + """ + + def consume(self): + self.context.modeling.instance.configure_operations() + + class ServiceInstance(ConsumerChain): """ Generates the service instance by instantiating the service template. @@ -158,6 +176,8 @@ class ServiceInstance(ConsumerChain): SatisfyRequirements, CoerceServiceInstanceValues, ValidateCapabilities, + FindHosts, + ConfigureOperations, CoerceServiceInstanceValues)) def dump(self): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/aria/storage/instrumentation.py ---------------------------------------------------------------------- diff --git a/aria/storage/instrumentation.py b/aria/storage/instrumentation.py index fb95fcf..138432a 100644 --- a/aria/storage/instrumentation.py +++ b/aria/storage/instrumentation.py @@ -110,9 +110,9 @@ class _Instrumentation(object): current = copy.deepcopy(attribute_type(initial)) tracked_attributes[attribute_name] = _Value(initial, current) target.__dict__[attribute_name] = tracked_attributes[attribute_name].current - for listener_args in [(instrumented_class, 'load', listener), + for listener_args in ((instrumented_class, 'load', listener), (instrumented_class, 'refresh', listener), - (instrumented_class, 'refresh_flush', listener)]: + (instrumented_class, 'refresh_flush', listener)): sqlalchemy.event.listen(*listener_args) self.listeners.append(listener_args) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/elasticsearch.yaml ---------------------------------------------------------------------- diff --git a/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/elasticsearch.yaml b/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/elasticsearch.yaml index 32623d1..72b210a 100644 --- a/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/elasticsearch.yaml +++ b/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/elasticsearch.yaml @@ -4,3 +4,5 @@ node_types: tosca.nodes.SoftwareComponent.Elasticsearch: derived_from: tosca.nodes.SoftwareComponent + capabilities: + app: tosca.capabilities.Endpoint http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/kibana.yaml ---------------------------------------------------------------------- diff --git a/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/kibana.yaml b/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/kibana.yaml index 7af00d0..4ee8700 100644 --- a/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/kibana.yaml +++ b/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/kibana.yaml @@ -8,3 +8,5 @@ node_types: - search_endpoint: capability: tosca.capabilities.Endpoint relationship: tosca.relationships.ConnectsTo + capabilities: + app: tosca.capabilities.Endpoint http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/logstash.yaml ---------------------------------------------------------------------- diff --git a/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/logstash.yaml b/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/logstash.yaml index a3eebbe..ea74c7e 100644 --- a/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/logstash.yaml +++ b/examples/tosca-simple-1.0/use-cases/multi-tier-1/custom_types/logstash.yaml @@ -8,3 +8,5 @@ node_types: - search_endpoint: capability: tosca.capabilities.Endpoint relationship: tosca.relationships.ConnectsTo + capabilities: + app: tosca.capabilities.Endpoint http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/custom_types/paypalpizzastore_nodejs_app.yaml ---------------------------------------------------------------------- diff --git a/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/custom_types/paypalpizzastore_nodejs_app.yaml b/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/custom_types/paypalpizzastore_nodejs_app.yaml index 4723a3f..02bb399 100644 --- a/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/custom_types/paypalpizzastore_nodejs_app.yaml +++ b/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/custom_types/paypalpizzastore_nodejs_app.yaml @@ -9,7 +9,7 @@ node_types: type: string requirements: - database_connection: - capability: tosca.capabilities.Container + capability: tosca.capabilities.Node tosca.nodes.WebServer.Nodejs: derived_from: tosca.nodes.WebServer http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/webserver-dbms-2.yaml ---------------------------------------------------------------------- diff --git a/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/webserver-dbms-2.yaml b/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/webserver-dbms-2.yaml index 66eab8e..91f0b35 100644 --- a/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/webserver-dbms-2.yaml +++ b/examples/tosca-simple-1.0/use-cases/webserver-dbms-2/webserver-dbms-2.yaml @@ -53,7 +53,7 @@ topology_template: implementation: scripts/nodejs/configure.sh inputs: github_url: { get_property: [ SELF, github_url ] } - mongodb_ip: { get_attribute: [mongo_server, private_address] } + mongodb_ip: { get_attribute: [ mongo_server, private_address ] } start: scripts/nodejs/start.sh nodejs: @@ -86,7 +86,7 @@ topology_template: configure: implementation: mongodb/config.sh inputs: - mongodb_ip: { get_attribute: [mongo_server, private_address] } + mongodb_ip: { get_attribute: [ mongo_server, private_address ] } start: mongodb/start.sh mongo_server: @@ -109,7 +109,7 @@ topology_template: nodejs_url: description: URL for the nodejs server, http://:3000 - value: { get_attribute: [app_server, private_address] } + value: { get_attribute: [ app_server, private_address ] } mongodb_url: description: URL for the mongodb server. value: { get_attribute: [ mongo_server, private_address ] } http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/extensions/aria_extension_tosca/profiles/aria-1.0/aria-1.0.yaml ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/profiles/aria-1.0/aria-1.0.yaml b/extensions/aria_extension_tosca/profiles/aria-1.0/aria-1.0.yaml index 09cef57..0c5e77f 100644 --- a/extensions/aria_extension_tosca/profiles/aria-1.0/aria-1.0.yaml +++ b/extensions/aria_extension_tosca/profiles/aria-1.0/aria-1.0.yaml @@ -17,6 +17,8 @@ policy_types: aria.Plugin: _extensions: + shorthand_name: Plugin + type_qualified_name: aria:Plugin role: plugin description: >- Policy used to specify plugins used by services. For an operation to be able to use a plugin @@ -29,9 +31,17 @@ policy_types: Minimum plugin version. type: version required: false + enabled: + description: >- + If the policy is to disable the plugin then it will be ignored and all operations and + workflows depending on it will also be disabled. + type: boolean + default: true aria.Workflow: _extensions: + shorthand_name: Workflow + type_qualified_name: aria:Workflow role: workflow description: >- Policy used to specify custom workflows. A workflow is usually a workload of interconnected http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/capabilities.yaml ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/capabilities.yaml b/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/capabilities.yaml index 72f6f0e..0b81a16 100644 --- a/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/capabilities.yaml +++ b/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/capabilities.yaml @@ -32,6 +32,7 @@ capability_types: specification: tosca-simple-1.0 specification_section: 5.4.2 specification_url: 'http://docs.oasis-open.org/tosca/TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html#DEFN_TYPE_CAPABILITIES_NODE' + role: feature description: >- The Node capability indicates the base capabilities of a TOSCA Node Type. derived_from: tosca.capabilities.Root @@ -43,6 +44,7 @@ capability_types: specification: tosca-simple-1.0 specification_section: 5.4.3 specification_url: 'http://docs.oasis-open.org/tosca/TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html#DEFN_TYPE_CAPABILITIES_CONTAINER' + role: host description: >- The Container capability, when included on a Node Type or Template definition, indicates that the node can act as a container for (or a host for) one or more other declared Node Types. http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/interfaces.yaml ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/interfaces.yaml b/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/interfaces.yaml index de1d34f..ff6ba6c 100644 --- a/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/interfaces.yaml +++ b/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/interfaces.yaml @@ -63,24 +63,40 @@ interface_types: pre_configure_source: description: >- Operation to pre-configure the source endpoint. + _extensions: + relationship_edge: source pre_configure_target: description: >- Operation to pre-configure the target endpoint. + _extensions: + relationship_edge: target post_configure_source: description: >- Operation to post-configure the source endpoint. + _extensions: + relationship_edge: source post_configure_target: description: >- Operation to post-configure the target endpoint. + _extensions: + relationship_edge: target add_target: description: >- Operation to notify the source node of a target node being added via a relationship. + _extensions: + relationship_edge: source add_source: description: >- Operation to notify the target node of a source node which is now available via a relationship. + _extensions: + relationship_edge: target target_changed: description: >- Operation to notify source some property or attribute of the target changed + _extensions: + relationship_edge: source remove_target: description: >- Operation to remove a target node. + _extensions: + relationship_edge: source http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/nodes.yaml ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/nodes.yaml b/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/nodes.yaml index 414a388..bb33b6f 100644 --- a/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/nodes.yaml +++ b/extensions/aria_extension_tosca/profiles/tosca-simple-1.0/nodes.yaml @@ -60,6 +60,7 @@ node_types: specification: tosca-simple-1.0 specification_section: 5.8.2 specification_url: 'http://docs.oasis-open.org/tosca/TOSCA-Simple-Profile-YAML/v1.0/cos01/TOSCA-Simple-Profile-YAML-v1.0-cos01.html#DEFN_TYPE_NODES_COMPUTE' + role: host description: >- The TOSCA Compute node represents one or more real or virtual processors of software applications or services along with other essential local resources. Collectively, the resources the compute node represents can logically be viewed as a (real http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a7e7826e/extensions/aria_extension_tosca/simple_v1_0/assignments.py ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/simple_v1_0/assignments.py b/extensions/aria_extension_tosca/simple_v1_0/assignments.py index 2a39ed9..6e36ba8 100644 --- a/extensions/aria_extension_tosca/simple_v1_0/assignments.py +++ b/extensions/aria_extension_tosca/simple_v1_0/assignments.py @@ -84,6 +84,25 @@ class OperationAssignment(ExtensiblePresentation): :rtype: dict of str, :class:`PropertyAssignment` """ + @cachedmethod + def _get_extensions(self, context): + def update_inherited_extensions(extensions, interface_type): + parent = interface_type._get_parent(context) + if parent is not None: + update_inherited_extensions(extensions, parent) + operation_definition = interface_type.operations.get(self._name) + if operation_definition is not None: + if operation_definition._extensions: + extensions.update(operation_definition._extensions) + + extensions = {} + update_inherited_extensions(extensions, self._container._get_type(context)) + if self._container._extensions: + extensions.update(self._container._extensions) + if self._extensions: + extensions.update(self._extensions) + return extensions + @allow_unknown_fields @has_fields @dsl_specification('3.5.14-2', 'tosca-simple-1.0') @@ -247,15 +266,18 @@ class RequirementAssignment(ExtensiblePresentation): @cachedmethod def _get_node(self, context): - node_name = self.node - if node_name is not None: - node = context.presentation.get_from_dict('service_template', 'topology_template', - 'node_templates', node_name) - if node is not None: - return node, 'node_template' - node = context.presentation.get_from_dict('service_template', 'node_types', node_name) - if node is not None: - return node, 'node_type' + node = self.node + + if node is not None: + node_template = context.presentation.get_from_dict('service_template', + 'topology_template', + 'node_templates', node) + if node_template is not None: + return node_template, 'node_template' + node_type = get_type_by_full_or_shorthand_name(context, node, 'node_types') + if node_type is not None: + return node_type, 'node_type' + return None, None @cachedmethod @@ -268,11 +290,10 @@ class RequirementAssignment(ExtensiblePresentation): capabilities = node._get_capabilities(context) if capability in capabilities: return capabilities[capability], 'capability_assignment' - else: - capability_types = context.presentation.get_from_dict('service_template', - 'capability_types') - if (capability_types is not None) and (capability in capability_types): - return capability_types[capability], 'capability_type' + capability_type = get_type_by_full_or_shorthand_name(context, capability, + 'capability_types') + if capability_type is not None: + return capability_type, 'capability_type' return None, None