Repository: incubator-ariatosca Updated Branches: refs/heads/ARIA-92-plugin-in-implementation-string e10d788c9 -> a6bfe803d
Support for operation 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/a6bfe803 Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/a6bfe803 Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/a6bfe803 Branch: refs/heads/ARIA-92-plugin-in-implementation-string Commit: a6bfe803da64370319c8f86d99945dc8fe2dd2af Parents: e10d788 Author: Tal Liron <[email protected]> Authored: Wed Mar 29 20:23:24 2017 -0500 Committer: Tal Liron <[email protected]> Committed: Wed Mar 29 20:23:24 2017 -0500 ---------------------------------------------------------------------- aria/modeling/orchestration.py | 27 ++--- aria/modeling/service_instance.py | 22 ++-- aria/modeling/service_template.py | 18 ++-- aria/orchestrator/execution_plugin/__init__.py | 44 ++++++++ aria/orchestrator/workflows/api/task.py | 102 +++++++++--------- aria/orchestrator/workflows/core/task.py | 3 +- .../profiles/tosca-simple-1.0/interfaces.yaml | 24 +++-- .../simple_v1_0/modeling/__init__.py | 104 +++++++++---------- .../node-cellar/node-cellar.yaml | 14 ++- 9 files changed, 203 insertions(+), 155 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a6bfe803/aria/modeling/orchestration.py ---------------------------------------------------------------------- diff --git a/aria/modeling/orchestration.py b/aria/modeling/orchestration.py index 2d58671..e7118f9 100644 --- a/aria/modeling/orchestration.py +++ b/aria/modeling/orchestration.py @@ -208,7 +208,7 @@ class TaskBase(ModelMixin): __private_fields__ = ['node_fk', 'relationship_fk', 'plugin_fk', - 'execution_fk', + 'execution_fk' 'node_name', 'relationship_name', 'execution_name'] @@ -231,11 +231,6 @@ class TaskBase(ModelMixin): WAIT_STATES = [PENDING, RETRYING] END_STATES = [SUCCESS, 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 @@ -270,17 +265,7 @@ class TaskBase(ModelMixin): # Operation specific fields implementation = Column(String) - _runs_on = Column(Enum(*RUNS_ON, name='runs_on'), name='runs_on') - - @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 + configuration = Column(modeling_types.StrictDict(key_cls=basestring)) @property def actor(self): @@ -338,12 +323,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, instance, **kwargs): + return cls(node=instance, **kwargs) @classmethod - def for_relationship(cls, instance, runs_on, **kwargs): - return cls(relationship=instance, _runs_on=runs_on, **kwargs) + def for_relationship(cls, instance, **kwargs): + return cls(relationship=instance, **kwargs) @staticmethod def abort(message=None): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a6bfe803/aria/modeling/service_instance.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_instance.py b/aria/modeling/service_instance.py index d189213..8852887 100644 --- a/aria/modeling/service_instance.py +++ b/aria/modeling/service_instance.py @@ -326,8 +326,10 @@ class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods :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' @@ -541,7 +543,6 @@ class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods return None self.host = _find_host(self) - print self, self.host @property def as_raw(self): @@ -982,7 +983,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`] """ @@ -1350,8 +1351,10 @@ class OperationBase(InstanceModelMixin): :vartype description: string :ivar plugin_specification: Associated plugin :vartype plugin_specification: :class:`PluginSpecification` - :ivar implementation: Implementation string (interpreted by the plugin) + :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 @@ -1386,8 +1389,9 @@ class OperationBase(InstanceModelMixin): def plugin_specification(cls): return relationship.one_to_one(cls, 'plugin_specification') - plugin_argument = Column(Text) + runs_on = Column(Text) implementation = Column(Text) + configuration = Column(modeling_types.StrictDict(key_cls=basestring)) dependencies = Column(modeling_types.StrictList(item_cls=basestring)) @declared_attr @@ -1450,12 +1454,14 @@ class OperationBase(InstanceModelMixin): if self.plugin_specification is not None: console.puts('Plugin specification: {0}'.format( context.style.literal(self.plugin_specification.name))) - if self.plugin_argument is not None: - console.puts('Plugin argument: {0}'.format( - context.style.literal(self.plugin_argument))) 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/a6bfe803/aria/modeling/service_template.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py index 4e29e87..2eb88c9 100644 --- a/aria/modeling/service_template.py +++ b/aria/modeling/service_template.py @@ -1467,8 +1467,10 @@ 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 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 @@ -1500,8 +1502,8 @@ class OperationTemplateBase(TemplateModelMixin): def plugin_specification(cls): return relationship.one_to_one(cls, 'plugin_specification') - plugin_argument = Column(Text) implementation = Column(Text) + configuration = Column(modeling_types.StrictDict(key_cls=basestring)) dependencies = Column(modeling_types.StrictList(item_cls=basestring)) @declared_attr @@ -1547,10 +1549,10 @@ class OperationTemplateBase(TemplateModelMixin): from . import models operation = models.Operation(name=self.name, description=deepcopy_with_locators(self.description), + plugin_specification=self.plugin_specification, implementation=self.implementation, + configuration=self.configuration, dependencies=self.dependencies, - plugin_specification=self.plugin_specification, - plugin_argument=self.plugin_argument, executor=self.executor, max_retries=self.max_retries, retry_interval=self.retry_interval, @@ -1573,12 +1575,14 @@ class OperationTemplateBase(TemplateModelMixin): if self.plugin_specification is not None: console.puts('Plugin specification: {0}'.format( context.style.literal(self.plugin_specification.name))) - if self.plugin_argument is not None: - console.puts('Plugin argument: {0}'.format( - context.style.literal(self.plugin_argument))) 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)))) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a6bfe803/aria/orchestrator/execution_plugin/__init__.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/execution_plugin/__init__.py b/aria/orchestrator/execution_plugin/__init__.py index 372022f..5561ddf 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 ...modeling import models + # Populated during execution of python scripts ctx = None @@ -31,3 +33,45 @@ def python_script_scope(operation_ctx, operation_inputs): finally: ctx = None inputs = None + + +def init_operation(operation_task): + from . import operations + + inputs = {} + inputs['script_path'] = operation_task.implementation + + host = None + if operation_task.actor_type == 'node': + host = operation_task.actor.host + elif operation_task.actor_type == 'relationship': + edge = operation_task.configuration.get('edge', 'source') + if edge == 'source': + host = operation_task.actor.source_node.host + elif edge == 'target': + host = operation_task.actor.target_node.host + else: + raise ValueError('"edge" configuration must be "source" or "target": {0}'.format(edge)) + + if host is None: + # Local operation + operation_task.implementation = '{0}.{1}'.format(operations.__name__, + operations.run_script_locally.__name__) + else: + # Remote SSH operation via Fabric + inputs['use_sudo'] = _to_bool(operation_task.configuration.get('use_sudo')) + inputs['hide_output'] = operation_task.configuration.get('hide_output') + inputs['fabric_env'] = operation_task.configuration.get('fabric_env') + # How to set up fabric env? + # How to set up host address? + operation_task.implementation = '{0}.{1}'.format(operations.__name__, + operations.run_script_with_ssh.__name__) + + for k, v in inputs.iteritems(): + operation_task.inputs[k] = models.Parameter.wrap(k, v) + + +def _to_bool(value): + if value is None: + return None + return unicode(value).lower() == 'true' http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a6bfe803/aria/orchestrator/workflows/api/task.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/workflows/api/task.py b/aria/orchestrator/workflows/api/task.py index af50120..902f1be 100644 --- a/aria/orchestrator/workflows/api/task.py +++ b/aria/orchestrator/workflows/api/task.py @@ -19,8 +19,9 @@ 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, merge) from ....utils.uuid import generate_uuid +from ... import execution_plugin from ... import context from .. import exceptions @@ -56,7 +57,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,29 +67,45 @@ class OperationTask(BaseTask): actor_type, interface_name, operation_name, - runs_on=None, + inputs=None, + configuration=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 is None) or (runs_on in models.Task.RUNS_ON) super(OperationTask, self).__init__() self.actor = actor + self.actor_type = actor_type + + operation = self._get_operation(interface_name, 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)) + + 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.implementation = operation.implementation + self.configuration = {} + + if operation.configuration: + merge(self.configuration, operation.configuration) + if configuration: + merge(self.configuration, operation.configuration) # Wrap inputs inputs = copy.deepcopy(inputs) if inputs else {} @@ -96,112 +113,97 @@ 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(interface_name, 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)) + self.inputs = OperationTask._merge_inputs(operation.inputs, inputs) self.plugin = None if operation.plugin_specification: self.plugin = OperationTask._find_plugin(operation.plugin_specification) if self.plugin is None: + raise exceptions.OperationNotFoundException() ### raise exceptions.PluginNotFoundException( - 'Could not find plugin of operation "{0}" on interface "{1}" for {2} "{3}"' - .format(operation_name, interface_name, actor_type, actor.name)) - - # TODO: change runs_on to plugin_argument - if operation.plugin_argument is not None: - self.runs_on = operation.plugin_argument + 'Could not find plugin "{0}" of operation "{1}" on interface "{2}" for ' + '{3} "{4}"' + .format(operation.plugin_specification.name, operation_name, interface_name, + actor_type, actor.name)) else: - # TODO: the execution plugin should choose the defaults - if actor_type == 'node': - self.runs_on = models.Task.RUNS_ON_NODE - else: - self.runs_on = models.Task.RUNS_ON_SOURCE - - 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=interface_name, - operation=operation_name) + # Default plugin (execution) + execution_plugin.init_operation(self) @classmethod def for_node(cls, node, interface_name, operation_name, + inputs=None, + configuration=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 configuration: Override the operation's configuration :param max_attempts: The maximum number of attempts in case the operation fails (if not specified the defaults it 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) :param ignore_failure: Whether to ignore failures (if not specified the defaults it taken from the workflow context) - :param inputs: Additional operation inputs """ + assert isinstance(node, models.Node) return cls( actor=node, actor_type='node', interface_name=interface_name, operation_name=operation_name, + inputs=inputs, + configuration=configuration, max_attempts=max_attempts, retry_interval=retry_interval, - ignore_failure=ignore_failure, - inputs=inputs) + ignore_failure=ignore_failure) @classmethod def for_relationship(cls, relationship, interface_name, operation_name, - runs_on=None, + inputs=None, + configuration=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 configuration: Override the operation's configuration :param max_attempts: The maximum number of attempts in case the operation fails (if not specified the defaults it 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) :param ignore_failure: Whether to ignore failures (if not specified the defaults it taken from the workflow context) - :param inputs: Additional operation inputs """ + assert isinstance(relationship, models.Relationship) return cls( actor=relationship, actor_type='relationship', interface_name=interface_name, operation_name=operation_name, - runs_on=runs_on, + inputs=inputs, + configuration=configuration, max_attempts=max_attempts, retry_interval=retry_interval, - ignore_failure=ignore_failure, - inputs=inputs) + ignore_failure=ignore_failure) def _get_operation(self, interface_name, operation_name): interface = self.actor.interfaces.get(interface_name) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a6bfe803/aria/orchestrator/workflows/core/task.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/workflows/core/task.py b/aria/orchestrator/workflows/core/task.py index f23312d..19ce710 100644 --- a/aria/orchestrator/workflows/core/task.py +++ b/aria/orchestrator/workflows/core/task.py @@ -130,8 +130,7 @@ class OperationTask(BaseTask): 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/a6bfe803/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 9eab339..31c27b5 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 @@ -64,39 +64,47 @@ interface_types: description: >- Operation to pre-configure the source endpoint. _extensions: - default_prefix: :source + default_dependencies: + - edge > source pre_configure_target: description: >- Operation to pre-configure the target endpoint. _extensions: - default_prefix: :target + default_dependencies: + - edge > target post_configure_source: description: >- Operation to post-configure the source endpoint. _extensions: - default_prefix: :source + default_dependencies: + - edge > source post_configure_target: description: >- Operation to post-configure the target endpoint. _extensions: - default_prefix: :target + default_dependencies: + - edge > target add_target: description: >- Operation to notify the source node of a target node being added via a relationship. _extensions: - default_prefix: :source + default_dependencies: + - edge > source add_source: description: >- Operation to notify the target node of a source node which is now available via a relationship. _extensions: - default_prefix: :target + default_dependencies: + - edge > target target_changed: description: >- Operation to notify source some property or attribute of the target changed _extensions: - default_prefix: :source + default_dependencies: + - edge > source remove_target: description: >- Operation to remove a target node. _extensions: - default_prefix: :source + default_dependencies: + - edge > source http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a6bfe803/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 e00bbc3..45c26eb 100644 --- a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py +++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py @@ -24,6 +24,7 @@ from types import FunctionType from datetime import datetime from aria.parser.validation import Issue +from aria.utils.collections import StrictDict from aria.modeling.models import (Type, ServiceTemplate, NodeTemplate, RequirementTemplate, RelationshipTemplate, CapabilityTemplate, GroupTemplate, PolicyTemplate, SubstitutionTemplate, @@ -33,10 +34,9 @@ from aria.modeling.models import (Type, ServiceTemplate, NodeTemplate, from ..data_types import coerce_value -# These match the first un-escaped ">" and ":" +# These match the first un-escaped ">" # See: http://stackoverflow.com/a/11819111/849021 IMPLEMENTATION_PREFIX_REGEX = re.compile(r'(?<!\\)(?:\\\\)*>') -PLUGIN_ARGUMENT_REGEX = re.compile(r'(?<!\\)(?:\\\\)*:') def create_service_template_model(context): # pylint: disable=too-many-locals,too-many-branches @@ -354,25 +354,44 @@ def create_interface_template_model(context, service_template, interface): return model if model.operation_templates else None -def create_operation_template_model(context, service_template, operation): # pylint: disable=unused-argument +def create_operation_template_model(context, service_template, operation): model = OperationTemplate(name=operation._name) if operation.description: model.description = operation.description.value implementation = operation.implementation - if (implementation is not None) and operation.implementation.primary: - model.plugin_specification, plugin_argument, model.implementation = \ - parse_implementation_string(context, - service_template, - operation) - - if plugin_argument is not None: - model.plugin_argument = plugin_argument - - dependencies = implementation.dependencies - if dependencies is not None: - model.dependencies = dependencies + if implementation is not None: + primary = implementation.primary + plugin_name, model.implementation = split_prefix(primary) + if plugin_name is not None: + model.plugin_specification = service_template.plugin_specifications.get(plugin_name) + if model.plugin_specification is None: + context.validation.report( + 'unknown plugin "%s" specified in operation implementation: %s' + % (plugin_name, primary), + locator=operation._get_child_locator('implementation', 'primary'), + level=Issue.BETWEEN_TYPES) + + dependencies = [] + + default_dependencies = operation._get_extensions(context).get('default_dependencies') + if default_dependencies: + dependencies += default_dependencies + if implementation.dependencies: + # Because these are after the defaults, they will override them + dependencies += implementation.dependencies + + for dependency in dependencies: + key, value = split_prefix(dependency) + if key is not None: + if model.configuration is None: + model.configuration = {} + set_nested(model.configuration, key.split('.'), value) + else: + if model.dependencies is None: + model.dependencies = [] + model.dependencies.append(dependency) inputs = operation.inputs if inputs: @@ -661,47 +680,18 @@ def create_constraint_clause_lambda(context, node_filter, constraint_clause, pro return None -def parse_implementation_string(context, service_template, operation): - primary = operation.implementation.primary - if not primary: - return None, None, '' +def split_prefix(string): + split = IMPLEMENTATION_PREFIX_REGEX.split(string, 2) + if len(split) < 2: + return None, string + return split[0].strip(), split[1].lstrip() - prefix_split = IMPLEMENTATION_PREFIX_REGEX.split(primary, 2) - if len(prefix_split) < 2: - extensions = operation._get_extensions(context) - default_prefix = extensions.get('default_prefix') - if default_prefix is None: - # No prefix - return None, None, primary - else: - # Use default prefix - prefix = default_prefix - implementation = primary - else: - # Get prefix from string - prefix, implementation = prefix_split - implementation = implementation.lstrip() - - argument_split = PLUGIN_ARGUMENT_REGEX.split(prefix, 2) - if len(argument_split) < 2: - # No plugin argument - name = prefix.strip() - argument = None + +def set_nested(the_dict, keys, value): + key = keys.pop(0) + if len(keys) == 0: + the_dict[key] = value else: - # Plugin argument - name, argument = argument_split - name = name.strip() - argument = argument.strip() - - if name: - plugin_specification = service_template.plugin_specifications.get(name) - if plugin_specification is None: - context.validation.report( - 'unknown plugin "%s" specified in operation implementation: %s' - % (name, primary), - locator=operation._get_child_locator('implementation', 'primary'), - level=Issue.BETWEEN_TYPES) - else: - plugin_specification = None - - return plugin_specification, argument, implementation + if key not in the_dict: + the_dict[key] = StrictDict(key_class=basestring, value_class=basestring) + set_nested(the_dict[key], keys, value) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/a6bfe803/tests/resources/service-templates/tosca-simple-1.0/node-cellar/node-cellar.yaml ---------------------------------------------------------------------- diff --git a/tests/resources/service-templates/tosca-simple-1.0/node-cellar/node-cellar.yaml b/tests/resources/service-templates/tosca-simple-1.0/node-cellar/node-cellar.yaml index b271bf2..3cffc0b 100644 --- a/tests/resources/service-templates/tosca-simple-1.0/node-cellar/node-cellar.yaml +++ b/tests/resources/service-templates/tosca-simple-1.0/node-cellar/node-cellar.yaml @@ -158,7 +158,13 @@ topology_template: relationship: interfaces: Configure: - target_changed: :target > mongodb/host_changed.sh + target_changed: + implementation: + primary: mongodb/host_changed.sh + dependencies: + - runs_on > target + - ssh.user > admin + - ssh.password > 12345 nginx: type: nginx.Nginx @@ -188,7 +194,11 @@ topology_template: Standard: inputs: openstack_credential: { get_input: openstack_credential } - configure: juju > charm.loadbalancer + configure: + implementation: + primary: juju > run_charm + dependencies: + - charm > loadbalancer application_host: copy: loadbalancer_host
