You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ariatosca.apache.org by tliron <gi...@git.apache.org> on 2017/03/05 03:47:40 UTC

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

GitHub user tliron opened a pull request:

    https://github.com/apache/incubator-ariatosca/pull/72

    Aria 105 integrate modeling

    So, this is a very big pull request.
    
    As it stands, I am not yet done fixing all the tests, however it's been a long time and we need to at least start the review process.
    
    Let me give an overview:
    
    `aria.modeling` is where all the new models sit. They are all very detailed and documented using Sphinx. They ended up diverging quite a lot from what Maxim first created, though I built a lot on his work. In particular, I made sure that SqlAlchemy specifics would be "hidden" from users using standard Python lists and dicts. I reworked a lot of the helper functions in `bases` to support these additional features.
    
    Some highlights:
    
    * A true normalized relational database structure. No fields with string dicts or lists in them.
    * No more name lookups: all relationships between models are done using foreign keys. I would actually want to add more constraints in the future to improve validation. As of the now, the `validate` functions are not good enough: a user could create invalid models.
    * Parameters are serialized via pickling. This allows us to maintain the rich type structure of TOSCA. So far it seems to be working great, though ... we would need to keep an eye on this.
    * There might be seem to be a lot of duplication between templates and instances. This is because 1) the move from one to the other may involve intrinsic function calls, so all properties might indeed be different, and 2) I want to make it possible, in theory at least, to be able to create a service instance even if there is no service template. This would allow the ARIA orchestrator to work on any service, even if it does not have a blueprint. (Actually, a smart modeling tool could possibly "templatize" an existing modeled service, allowing one to generate a blueprint in order to duplicate it. Neat!) The relationship between the two sets of models should be optional, not required.
    
    If you want to see how they are created by the parser, see `aria_extensions_tosca.simple_v1_0.modeling.__init__`.
    
    `aria.orchestrator.workflows.task.api` has been reworked to use both "interface_name" and "operation_name" instead of a single string that is parsed using the period. This has helped a lot in reducing confusion and ambiguity in many parts of the code, and correctly represents the reality of the modeling structure.
    
    The impact on our tests has been huge. :/ I am marching through all of them and am not done yet. So please don't be worried to see many tests failing.

You can merge this pull request into a Git repository by running:

    $ git pull https://github.com/apache/incubator-ariatosca ARIA-105-integrate-modeling

Alternatively you can review and apply these changes as the patch at:

    https://github.com/apache/incubator-ariatosca/pull/72.patch

To close this pull request, make a commit to your master/trunk branch
with (at least) the following in the commit message:

    This closes #72
    
----
commit 58e10523713f08b51181d84f4a303fbc15e23810
Author: Tal Liron <ta...@gmail.com>
Date:   2017-02-17T22:00:40Z

    Initial refactoring (mostly broken)

commit f28b0af157e13c41482921ecba03409c07ca2660
Author: Tal Liron <ta...@gmail.com>
Date:   2017-02-21T23:49:14Z

    Added RelationshipTemplate; added one-to-many support

commit 1a0b85ab1bc048bee7a9efe3e9e11969ce202c0a
Author: Tal Liron <ta...@gmail.com>
Date:   2017-02-23T00:05:49Z

    Full use of aria.modeling! (but with problems)

commit f1074fee24d3a9895ad7e35dd8d58a7ab7265b99
Author: Tal Liron <ta...@gmail.com>
Date:   2017-02-23T23:07:03Z

    Modeling refactoring; improved metadata; work on instance models

commit f62cdd4b95c556c3b893654cba6842b1f23f51a0
Author: Tal Liron <ta...@gmail.com>
Date:   2017-02-24T23:12:46Z

    Service instance models

commit 33a4ee7252ddfbfdfc6a6bcf7ab5c5c6117c71aa
Author: Tal Liron <ta...@gmail.com>
Date:   2017-02-27T18:50:34Z

    Template and instance complete (as_raw as well)

commit 0150da178ebd63c055f9cd05af7ddba5e5a711e8
Author: Tal Liron <ta...@gmail.com>
Date:   2017-02-28T23:00:38Z

    Major reworking of models to make better use of SqlAlchemy; unfinished work on orchestration models

commit 58aac8da2e6ecdc9749a1d128f78810d39cd0d67
Author: Tal Liron <ta...@gmail.com>
Date:   2017-03-01T22:10:12Z

    Modeling integation complete -- except testing ;)

commit daa2d538b590e38ff97dcec2e721a011c37b3e15
Author: Tal Liron <ta...@gmail.com>
Date:   2017-03-03T02:15:12Z

    Add types to modeling, plus many fixes to models

commit 091152f2716ae8abfdd42f23f399ff540abea41a
Author: Tal Liron <ta...@gmail.com>
Date:   2017-03-03T17:05:19Z

    Some test updates (unfinished)

commit 36eb2d24c2d0791738e6b9f88a81fc5f906745ce
Author: Tal Liron <ta...@gmail.com>
Date:   2017-03-03T23:42:53Z

    Move types into service template; continue fixing tests

commit 24ad8ba1e1a013a2348880459e81830fc270b16f
Author: Tal Liron <ta...@gmail.com>
Date:   2017-03-05T03:31:42Z

    Add rich docs, fix many tests

----


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106940791
  
    --- Diff: aria/orchestrator/workflows/builtin/utils.py ---
    @@ -14,33 +14,38 @@
     # limitations under the License.
     
     from ..api.task import OperationTask
    +from .. import exceptions
     
     
    -def create_node_task(operation_name, node):
    +def create_node_task(interface_name, operation_name, node):
         """
         Returns a new operation task if the operation exists in the node, otherwise returns None.
         """
     
    -    if _has_operation(node.interfaces, operation_name):
    -        return OperationTask.node(instance=node,
    -                                  name=operation_name)
    -    return None
    +    try:
    +        return OperationTask.for_node(node=node,
    +                                      interface_name=interface_name,
    +                                      operation_name=operation_name)
    +    except exceptions.TaskException:
    --- End diff --
    
    OK. This means that I will need to create a new exception sub-class here, specifically for task creation.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106804951
  
    --- Diff: aria/orchestrator/workflows/builtin/utils.py ---
    @@ -14,33 +14,38 @@
     # limitations under the License.
     
     from ..api.task import OperationTask
    +from .. import exceptions
     
     
    -def create_node_task(operation_name, node):
    +def create_node_task(interface_name, operation_name, node):
         """
         Returns a new operation task if the operation exists in the node, otherwise returns None.
         """
     
    -    if _has_operation(node.interfaces, operation_name):
    -        return OperationTask.node(instance=node,
    -                                  name=operation_name)
    -    return None
    +    try:
    +        return OperationTask.for_node(node=node,
    +                                      interface_name=interface_name,
    +                                      operation_name=operation_name)
    +    except exceptions.TaskException:
    +        return None
     
     
    -def create_relationship_tasks(operation_name, runs_on, node):
    +def create_relationship_tasks(interface_name, operation_name, runs_on, node):
         """
         Returns a list of operation tasks for each outbound relationship of the node if the operation
         exists there.
         """
     
         sequence = []
         for relationship in node.outbound_relationships:
    -        if _has_operation(relationship.interfaces, operation_name):
    +        try:
                 sequence.append(
    -                OperationTask.relationship(instance=relationship,
    -                                           name=operation_name,
    -                                           edge='source',
    -                                           runs_on=runs_on))
    +                OperationTask.for_relationship(relationship=relationship,
    +                                               interface_name=interface_name,
    +                                               operation_name=operation_name,
    +                                               runs_on=runs_on))
    +        except exceptions.TaskException:
    --- End diff --
    
    ditto


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106802272
  
    --- Diff: aria/modeling/__init__.py ---
    @@ -0,0 +1,48 @@
    +# Licensed to the Apache Software Foundation (ASF) under one or more
    +# contributor license agreements.  See the NOTICE file distributed with
    +# this work for additional information regarding copyright ownership.
    +# The ASF licenses this file to You under the Apache License, Version 2.0
    +# (the "License"); you may not use this file except in compliance with
    +# the License.  You may obtain a copy of the License at
    +#
    +#     http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +
    +from collections import namedtuple
    +
    +from . import (
    +    mixins,
    +    types,
    +    models,
    +    service_template as _service_template_bases,
    --- End diff --
    
    the naming sounds good :)


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104925924
  
    --- Diff: aria/orchestrator/workflows/api/task.py ---
    @@ -69,21 +73,38 @@ def __init__(self,
                      ignore_failure=None,
                      inputs=None,
                      plugin=None,
    -                 runs_on=None):
    +                 runs_on=None,
    +                 dry=False):
             """
             Creates an operation task using the name, details, node instance and any additional kwargs.
    -        :param name: the operation of the name.
    +
    +        :param name: the name of the operation.
             :param actor: the operation host on which this operation is registered.
             :param inputs: operation inputs.
             """
    -        assert isinstance(actor, (model.Node,
    -                                  model.Relationship))
    +
    +        assert isinstance(actor, (models.Node, models.Relationship))
             super(OperationTask, self).__init__()
    +
    +        if dry:
    +            from ..dry import convert_to_dry
    +            plugin, implementation, inputs = convert_to_dry(plugin, implementation, inputs)
    +
    +        # Coerce inputs
    +        if inputs is None:
    +            inputs = {}
    +        else:
    +            for k, v in inputs.iteritems():
    +                if not isinstance(v, models.Parameter):
    +                    inputs[k] = models.Parameter(name=k,
    +                                                 type_name=full_type_name(v),
    --- End diff --
    
    the type here should in principle correspond to the one in template's operation's inputs, correct? i understand theres no validation on this at the moment but im asking whether theres any benefit to saving the type name of an arbitrary input


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104932844
  
    --- Diff: aria/modeling/service.py ---
    @@ -0,0 +1,1529 @@
    +# 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.
    +
    +# pylint: disable=no-self-argument, no-member, abstract-method
    +
    +from sqlalchemy import (
    +    Column,
    +    Text,
    +    Integer
    +)
    +from sqlalchemy import DateTime
    +from sqlalchemy.ext.associationproxy import association_proxy
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from .bases import InstanceModelMixin
    +from ..parser import validation
    +from ..utils import collections, formatting, console
    +
    +from . import (
    +    utils,
    +    types as modeling_types
    +)
    +
    +
    +class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
    +    """
    +    A service is usually an instance of a :class:`ServiceTemplate`.
    +
    +    You will usually not create it programmatically, but instead instantiate it from a service
    +    template.
    +
    +    :ivar name: Name (unique for this ARIA installation)
    +    :vartype name: basestring
    +    :ivar service_template: Template from which this service was instantiated (optional)
    +    :vartype service_template: :class:`ServiceTemplate`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar meta_data: Custom annotations
    +    :vartype meta_data: {basestring: :class:`Metadata`}
    +    :ivar node: Nodes
    +    :vartype node: [:class:`Node`]
    +    :ivar groups: Groups of nodes
    +    :vartype groups: [:class:`Group`]
    +    :ivar policies: Policies
    +    :vartype policies: [:class:`Policy`]
    +    :ivar substitution: The entire service can appear as a node
    +    :vartype substitution: :class:`Substitution`
    +    :ivar inputs: Externally provided parameters
    +    :vartype inputs: {basestring: :class:`Parameter`}
    +    :ivar outputs: These parameters are filled in after service installation
    +    :vartype outputs: {basestring: :class:`Parameter`}
    +    :ivar operations: Custom operations that can be performed on the service
    +    :vartype operations: {basestring: :class:`Operation`}
    +    :ivar plugins: Plugins required to be installed
    +    :vartype plugins: {basestring: :class:`Plugin`}
    +    :ivar created_at: Creation timestamp
    +    :vartype created_at: :class:`datetime.datetime`
    +    :ivar updated_at: Update timestamp
    +    :vartype updated_at: :class:`datetime.datetime`
    +
    +    :ivar permalink: ??
    +    :vartype permalink: basestring
    +    :ivar scaling_groups: ??
    +    :vartype scaling_groups: {}
    +
    +    :ivar modifications: Modifications of this service
    +    :vartype modifications: [:class:`ServiceModification`]
    +    :ivar updates: Updates of this service
    +    :vartype updates: [:class:`ServiceUpdate`]
    +    :ivar executions: Executions on this service
    +    :vartype executions: [:class:`Execution`]
    +    """
    +
    +    __tablename__ = 'service'
    +
    +    @declared_attr
    +    def service_template(cls):
    +        return cls.many_to_one_relationship('service_template')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def meta_data(cls):
    +        # Warning! We cannot use the attr name "metadata" because it's used by SqlAlchemy!
    +        return cls.many_to_many_relationship('metadata', dict_key='name')
    +
    +    @declared_attr
    +    def nodes(cls):
    +        return cls.one_to_many_relationship('node')
    +
    +    @declared_attr
    +    def groups(cls):
    +        return cls.one_to_many_relationship('group')
    +
    +    @declared_attr
    +    def policies(cls):
    +        return cls.one_to_many_relationship('policy')
    +
    +    @declared_attr
    +    def substitution(cls):
    +        return cls.one_to_one_relationship('substitution')
    +
    +    @declared_attr
    +    def inputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def outputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='outputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def operations(cls):
    +        return cls.one_to_many_relationship('operation', dict_key='name')
    +
    +    @declared_attr
    +    def plugins(cls):
    +        return cls.many_to_many_relationship('plugin')
    +
    +    created_at = Column(DateTime, nullable=False, index=True)
    +    updated_at = Column(DateTime)
    +
    +    # region orchestration
    +
    +    permalink = Column(Text)
    +    scaling_groups = Column(modeling_types.Dict)
    +
    +    # endregion
    +
    +    # region foreign keys
    +
    +    __private_fields__ = ['substituion_fk',
    +                          'service_template_fk']
    +
    +    # Service one-to-one to Substitution
    +    @declared_attr
    +    def substitution_fk(cls):
    +        return cls.foreign_key('substitution', nullable=True)
    +
    +    # Service many-to-one to ServiceTemplate
    +    @declared_attr
    +    def service_template_fk(cls):
    +        return cls.foreign_key('service_template', nullable=True)
    +
    +    # endregion
    +
    +    def satisfy_requirements(self, context):
    +        satisfied = True
    +        for node in self.nodes:
    +            if not node.satisfy_requirements(context):
    +                satisfied = False
    +        return satisfied
    +
    +    def validate_capabilities(self, context):
    +        satisfied = True
    +        for node in self.nodes:
    +            if not node.validate_capabilities(context):
    +                satisfied = False
    +        return satisfied
    +
    +    def find_nodes(self, node_template_name):
    +        nodes = []
    +        for node in self.nodes:
    +            if node.node_template.name == node_template_name:
    +                nodes.append(node)
    +        return collections.FrozenList(nodes)
    +
    +    def get_node_ids(self, node_template_name):
    +        return collections.FrozenList((node.name for node in self.find_nodes(node_template_name)))
    +
    +    def find_groups(self, group_template_name):
    +        groups = []
    +        for group in self.groups:
    +            if group.template_name == group_template_name:
    +                groups.append(group)
    +        return collections.FrozenList(groups)
    +
    +    def get_group_ids(self, group_template_name):
    +        return collections.FrozenList((group.name
    +                                       for group in self.find_groups(group_template_name)))
    +
    +    def is_node_a_target(self, context, target_node):
    +        for node in self.nodes:
    +            if self._is_node_a_target(context, node, target_node):
    +                return True
    +        return False
    +
    +    def _is_node_a_target(self, context, source_node, target_node):
    +        if source_node.relationships:
    +            for relationship in source_node.relationships:
    +                if relationship.target_node_id == target_node.name:
    +                    return True
    +                else:
    +                    node = context.modeling.instance.nodes.get(relationship.target_node_id)
    +                    if node is not None:
    +                        if self._is_node_a_target(context, node, target_node):
    +                            return True
    +        return False
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('description', self.description),
    +            ('metadata', formatting.as_raw_dict(self.meta_data)),
    +            ('nodes', formatting.as_raw_list(self.nodes)),
    +            ('groups', formatting.as_raw_list(self.groups)),
    +            ('policies', formatting.as_raw_list(self.policies)),
    +            ('substitution', formatting.as_raw(self.substitution)),
    +            ('inputs', formatting.as_raw_dict(self.inputs)),
    +            ('outputs', formatting.as_raw_dict(self.outputs)),
    +            ('operations', formatting.as_raw_list(self.operations))))
    +
    +    def validate(self, context):
    +        utils.validate_dict_values(context, self.meta_data)
    +        utils.validate_list_values(context, self.nodes)
    +        utils.validate_list_values(context, self.groups)
    +        utils.validate_list_values(context, self.policies)
    +        if self.substitution is not None:
    +            self.substitution.validate(context)
    +        utils.validate_dict_values(context, self.inputs)
    +        utils.validate_dict_values(context, self.outputs)
    +        utils.validate_dict_values(context, self.operations)
    +
    +    def coerce_values(self, context, container, report_issues):
    +        utils.coerce_dict_values(context, container, self.meta_data, report_issues)
    +        utils.coerce_list_values(context, container, self.nodes, report_issues)
    +        utils.coerce_list_values(context, container, self.groups, report_issues)
    +        utils.coerce_list_values(context, container, self.policies, report_issues)
    +        if self.substitution is not None:
    +            self.substitution.coerce_values(context, container, report_issues)
    +        utils.coerce_dict_values(context, container, self.inputs, report_issues)
    +        utils.coerce_dict_values(context, container, self.outputs, report_issues)
    +        utils.coerce_dict_values(context, container, self.operations, report_issues)
    +
    +    def dump(self, context):
    --- End diff --
    
    what does this method does?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106866981
  
    --- Diff: aria/orchestrator/workflows/api/task.py ---
    @@ -92,144 +93,133 @@ def __init__(self,
                                    if ignore_failure is None else ignore_failure)
             self.runs_on = runs_on
     
    -    @classmethod
    -    def _merge_inputs(cls, operation_inputs, additional_inputs=None):
    -        final_inputs = dict((p.name, p.as_raw['value']) for p in operation_inputs)
    -        final_inputs.update(additional_inputs or {})
    -        return final_inputs
    +        # Wrap inputs
    +        if inputs:
    +            for k, v in inputs.iteritems():
    +                if not isinstance(v, models.Parameter):
    +                    inputs[k] = models.Parameter.wrap(k, v)
    +
    +        # TODO: These extra inputs should likely be stored as a separate entry in the task model,
    --- End diff --
    
    i'm not sure I agree with this comment, but we can discuss it at a later time


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104515849
  
    --- Diff: aria/modeling/types.py ---
    @@ -177,6 +177,7 @@ def coerce(cls, key, value):
             except ValueError as e:
                 raise exceptions.StorageError('SQL Storage error: {0}'.format(str(e)))
    --- End diff --
    
    i wonder if StorageErrors should be named something different now that the package is named "modeling"


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106996445
  
    --- Diff: aria/orchestrator/workflows/builtin/utils.py ---
    @@ -51,14 +56,14 @@ def create_node_task_dependencies(graph, tasks_and_nodes, reverse=False):
     
         def get_task(node_id):
    --- End diff --
    
    Since you will be fixing the builtins in ARIA-120, I want to suggest that perhaps much of these needs to be reworked, and it is better to be reworked there.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104313292
  
    --- Diff: aria/modeling/utils.py ---
    @@ -73,7 +46,7 @@ def coerce_value(context, container, value, report_issues=False):
     def validate_dict_values(context, the_dict):
         if not the_dict:
             return
    -    validate_list_values(context, the_dict.values())
    +    validate_list_values(context, the_dict.itervalues())
    --- End diff --
    
    python3?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106802476
  
    --- Diff: aria/modeling/relationships.py ---
    @@ -0,0 +1,402 @@
    +# Licensed to the Apache Software Foundation (ASF) under one or more
    --- End diff --
    
    maybe relationship.py instead? 
    relationship.one_to_one looks cleaner


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106942655
  
    --- Diff: aria/orchestrator/workflows/api/task.py ---
    @@ -92,144 +93,133 @@ def __init__(self,
                                    if ignore_failure is None else ignore_failure)
             self.runs_on = runs_on
     
    -    @classmethod
    -    def _merge_inputs(cls, operation_inputs, additional_inputs=None):
    -        final_inputs = dict((p.name, p.as_raw['value']) for p in operation_inputs)
    -        final_inputs.update(additional_inputs or {})
    -        return final_inputs
    +        # Wrap inputs
    +        if inputs:
    +            for k, v in inputs.iteritems():
    +                if not isinstance(v, models.Parameter):
    +                    inputs[k] = models.Parameter.wrap(k, v)
    +
    +        # TODO: These extra inputs should likely be stored as a separate entry in the task model,
    --- End diff --
    
    Changed the comment to say "suggestion"


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104835312
  
    --- Diff: aria/cli/args_parser.py ---
    @@ -137,10 +137,6 @@ def add_workflow_parser(workflow):
             '-w', '--workflow',
             default='install',
             help='The workflow name')
    -    workflow.add_argument(
    --- End diff --
    
    It's in the context. Awkward, I know. :/ This is something we should fix outside the scope of this JIRA.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104974218
  
    --- Diff: aria/modeling/service.py ---
    @@ -0,0 +1,1529 @@
    +# 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.
    +
    +# pylint: disable=no-self-argument, no-member, abstract-method
    +
    +from sqlalchemy import (
    +    Column,
    +    Text,
    +    Integer
    +)
    +from sqlalchemy import DateTime
    +from sqlalchemy.ext.associationproxy import association_proxy
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from .bases import InstanceModelMixin
    +from ..parser import validation
    +from ..utils import collections, formatting, console
    +
    +from . import (
    +    utils,
    +    types as modeling_types
    +)
    +
    +
    +class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
    +    """
    +    A service is usually an instance of a :class:`ServiceTemplate`.
    +
    +    You will usually not create it programmatically, but instead instantiate it from a service
    +    template.
    +
    +    :ivar name: Name (unique for this ARIA installation)
    +    :vartype name: basestring
    +    :ivar service_template: Template from which this service was instantiated (optional)
    +    :vartype service_template: :class:`ServiceTemplate`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar meta_data: Custom annotations
    +    :vartype meta_data: {basestring: :class:`Metadata`}
    +    :ivar node: Nodes
    +    :vartype node: [:class:`Node`]
    +    :ivar groups: Groups of nodes
    +    :vartype groups: [:class:`Group`]
    +    :ivar policies: Policies
    +    :vartype policies: [:class:`Policy`]
    +    :ivar substitution: The entire service can appear as a node
    +    :vartype substitution: :class:`Substitution`
    +    :ivar inputs: Externally provided parameters
    +    :vartype inputs: {basestring: :class:`Parameter`}
    +    :ivar outputs: These parameters are filled in after service installation
    +    :vartype outputs: {basestring: :class:`Parameter`}
    +    :ivar operations: Custom operations that can be performed on the service
    +    :vartype operations: {basestring: :class:`Operation`}
    +    :ivar plugins: Plugins required to be installed
    +    :vartype plugins: {basestring: :class:`Plugin`}
    +    :ivar created_at: Creation timestamp
    +    :vartype created_at: :class:`datetime.datetime`
    +    :ivar updated_at: Update timestamp
    +    :vartype updated_at: :class:`datetime.datetime`
    +
    +    :ivar permalink: ??
    +    :vartype permalink: basestring
    +    :ivar scaling_groups: ??
    +    :vartype scaling_groups: {}
    +
    +    :ivar modifications: Modifications of this service
    +    :vartype modifications: [:class:`ServiceModification`]
    +    :ivar updates: Updates of this service
    +    :vartype updates: [:class:`ServiceUpdate`]
    +    :ivar executions: Executions on this service
    +    :vartype executions: [:class:`Execution`]
    +    """
    +
    +    __tablename__ = 'service'
    +
    +    @declared_attr
    +    def service_template(cls):
    +        return cls.many_to_one_relationship('service_template')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def meta_data(cls):
    +        # Warning! We cannot use the attr name "metadata" because it's used by SqlAlchemy!
    +        return cls.many_to_many_relationship('metadata', dict_key='name')
    +
    +    @declared_attr
    +    def nodes(cls):
    +        return cls.one_to_many_relationship('node')
    +
    +    @declared_attr
    +    def groups(cls):
    +        return cls.one_to_many_relationship('group')
    +
    +    @declared_attr
    +    def policies(cls):
    +        return cls.one_to_many_relationship('policy')
    +
    +    @declared_attr
    +    def substitution(cls):
    +        return cls.one_to_one_relationship('substitution')
    +
    +    @declared_attr
    +    def inputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def outputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='outputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def operations(cls):
    +        return cls.one_to_many_relationship('operation', dict_key='name')
    +
    +    @declared_attr
    +    def plugins(cls):
    +        return cls.many_to_many_relationship('plugin')
    +
    +    created_at = Column(DateTime, nullable=False, index=True)
    +    updated_at = Column(DateTime)
    +
    +    # region orchestration
    +
    +    permalink = Column(Text)
    +    scaling_groups = Column(modeling_types.Dict)
    +
    +    # endregion
    +
    +    # region foreign keys
    +
    +    __private_fields__ = ['substituion_fk',
    +                          'service_template_fk']
    +
    +    # Service one-to-one to Substitution
    +    @declared_attr
    +    def substitution_fk(cls):
    +        return cls.foreign_key('substitution', nullable=True)
    +
    +    # Service many-to-one to ServiceTemplate
    +    @declared_attr
    +    def service_template_fk(cls):
    --- End diff --
    
    service_template.services should return a dictionary (names as the keys) and not a list - same goes for all models with a unique name.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106868018
  
    --- Diff: aria/orchestrator/workflows/api/task.py ---
    @@ -92,144 +93,133 @@ def __init__(self,
                                    if ignore_failure is None else ignore_failure)
             self.runs_on = runs_on
     
    -    @classmethod
    -    def _merge_inputs(cls, operation_inputs, additional_inputs=None):
    -        final_inputs = dict((p.name, p.as_raw['value']) for p in operation_inputs)
    -        final_inputs.update(additional_inputs or {})
    -        return final_inputs
    +        # Wrap inputs
    +        if inputs:
    +            for k, v in inputs.iteritems():
    +                if not isinstance(v, models.Parameter):
    +                    inputs[k] = models.Parameter.wrap(k, v)
    +
    +        # TODO: These extra inputs should likely be stored as a separate entry in the task model,
    +        # because they are different from the operation inputs. The two kinds of inputs should also
    +        # not be merged.
    +
    +        if interface_name or operation_name:
    +            operation = OperationTask._get_operation(actor.interfaces, interface_name,
    +                                                     operation_name)
    +            if operation is None:
    +                raise exceptions.TaskException(
    +                    'Could not find operation "{0}" on interface "{1}" for {2} "{3}"'
    +                    .format(operation_name, 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.TaskException(
    +                        'Could not find plugin of operation "{0}" on interface "{1}" for {2} "{3}"'
    +                        .format(operation_name, 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=interface_name,
    +                                                         operation=operation_name)
    +        else:
    --- End diff --
    
    what is this for?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106947025
  
    --- Diff: aria/cli/dry.py ---
    @@ -0,0 +1,88 @@
    +# Licensed to the Apache Software Foundation (ASF) under one or more
    --- End diff --
    
    We said CLI.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104312962
  
    --- Diff: aria/modeling/orchestration.py ---
    @@ -451,12 +451,45 @@ def actor(self):
             """
             return self.node or self.relationship
     
    +    @orm.validates('max_attempts')
    +    def validate_max_attempts(self, _, value):                                  # pylint: disable=no-self-use
    +        """Validates that max attempts is either -1 or a positive number"""
    +        if value < 1 and value != TaskBase.INFINITE_RETRIES:
    +            raise ValueError('Max attempts can be either -1 (infinite) or any positive number. '
    +                             'Got {value}'.format(value=value))
    +        return value
    +
    +    # region foreign keys
    +
    +    __private_fields__ = ['node_fk',
    +                          'relationship_fk',
    +                          'plugin_fk',
    +                          'execution_fk']
    +
    +    @declared_attr
    +    def node_fk(cls):
    +        return cls.foreign_key('node', nullable=True)
    +
    +    @declared_attr
    +    def relationship_fk(cls):
    +        return cls.foreign_key('relationship', nullable=True)
    +
    +    @declared_attr
    +    def plugin_fk(cls):
    +        return cls.foreign_key('plugin', nullable=True)
    +
    +    @declared_attr
    +    def execution_fk(cls):
    +        return cls.foreign_key('execution', nullable=True)
    +
    +    # endregion
    +
         @classmethod
    -    def as_node_instance(cls, instance, runs_on, **kwargs):
    +    def as_node_task(cls, instance, runs_on, **kwargs):
    --- End diff --
    
    so the current api looks some thing like `Task.as_node_task(...)` instead of `Task.as_node(...)`?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104309303
  
    --- Diff: aria/orchestrator/workflows/api/task.py ---
    @@ -69,21 +70,38 @@ def __init__(self,
                      ignore_failure=None,
                      inputs=None,
                      plugin=None,
    -                 runs_on=None):
    +                 runs_on=None,
    +                 dry=False):
             """
             Creates an operation task using the name, details, node instance and any additional kwargs.
    -        :param name: the operation of the name.
    +
    +        :param name: the name of the operation.
             :param actor: the operation host on which this operation is registered.
             :param inputs: operation inputs.
             """
    -        assert isinstance(actor, (model.Node,
    -                                  model.Relationship))
    +
    +        assert isinstance(actor, (models.Node, models.Relationship))
             super(OperationTask, self).__init__()
    +
    +        if dry:
    --- End diff --
    
    why keep this at all?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r105652210
  
    --- Diff: aria/modeling/mixins.py ---
    @@ -67,9 +108,9 @@ def foreign_key(cls, parent_table, nullable=False):
                           nullable=nullable)
     
         @classmethod
    -    def relationship_to_self(cls,
    -                             column_name,
    -                             relationship_kwargs=None):
    +    def _create_relationship_to_self(cls,
    --- End diff --
    
    why did it turn to create? this implies that each time i call any method which utilizes this helper function i'll create a new relationship. Furthermore why is it a private method.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106802336
  
    --- Diff: aria/modeling/orchestration.py ---
    @@ -0,0 +1,350 @@
    +# 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.
    +
    +"""
    +classes:
    +    * Execution - execution implementation model.
    +    * Plugin - plugin implementation model.
    +    * Task - a task
    +"""
    +
    +# pylint: disable=no-self-argument, no-member, abstract-method
    +
    +from datetime import datetime
    +
    +from sqlalchemy import (
    +    Column,
    +    Integer,
    +    Text,
    +    DateTime,
    +    Boolean,
    +    Enum,
    +    String,
    +    Float,
    +    orm,
    +)
    +from sqlalchemy.ext.associationproxy import association_proxy
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from ..orchestrator.exceptions import (TaskAbortException, TaskRetryException)
    +from .types import (List, Dict)
    +from .mixins import ModelMixin
    +from . import relationships
    +
    +
    +class ExecutionBase(ModelMixin):
    +    """
    +    Execution model representation.
    +    """
    +
    +    __tablename__ = 'execution'
    +
    +    TERMINATED = 'terminated'
    +    FAILED = 'failed'
    +    CANCELLED = 'cancelled'
    +    PENDING = 'pending'
    +    STARTED = 'started'
    +    CANCELLING = 'cancelling'
    +    FORCE_CANCELLING = 'force_cancelling'
    +
    +    STATES = [TERMINATED, FAILED, CANCELLED, PENDING, STARTED, CANCELLING, FORCE_CANCELLING]
    +    END_STATES = [TERMINATED, FAILED, CANCELLED]
    +    ACTIVE_STATES = [state for state in STATES if state not in END_STATES]
    +
    +    VALID_TRANSITIONS = {
    +        PENDING: [STARTED, CANCELLED],
    +        STARTED: END_STATES + [CANCELLING],
    +        CANCELLING: END_STATES + [FORCE_CANCELLING]
    +    }
    +
    +    @orm.validates('status')
    +    def validate_status(self, key, value):
    +        """Validation function that verifies execution status transitions are OK"""
    +        try:
    +            current_status = getattr(self, key)
    +        except AttributeError:
    +            return
    +        valid_transitions = self.VALID_TRANSITIONS.get(current_status, [])
    +        if all([current_status is not None,
    +                current_status != value,
    +                value not in valid_transitions]):
    +            raise ValueError('Cannot change execution status from {current} to {new}'.format(
    +                current=current_status,
    +                new=value))
    +        return value
    +
    +    created_at = Column(DateTime, index=True)
    +    started_at = Column(DateTime, nullable=True, index=True)
    +    ended_at = Column(DateTime, nullable=True, index=True)
    +    error = Column(Text, nullable=True)
    +    is_system_workflow = Column(Boolean, nullable=False, default=False)
    +    parameters = Column(Dict)
    +    status = Column(Enum(*STATES, name='execution_status'), default=PENDING)
    +    workflow_name = Column(Text)
    +
    +    @declared_attr
    +    def service(cls):
    +        return relationships.many_to_one(cls, 'service')
    +
    +    # region foreign keys
    +
    +    @declared_attr
    +    def service_fk(cls):
    +        return relationships.fk('service')
    +
    +    # endregion
    +
    +    # region association proxies
    +
    +    @declared_attr
    +    def service_name(cls):
    +        """Required for use by SQLAlchemy queries"""
    +        return association_proxy('service', cls.name_column_name())
    +
    +    @declared_attr
    +    def service_template(cls):
    +        """Required for use by SQLAlchemy queries"""
    +        return association_proxy('service', 'service_template')
    +
    +    @declared_attr
    +    def service_template_name(cls):
    +        """Required for use by SQLAlchemy queries"""
    +        return association_proxy('service', 'service_template_name')
    +
    +    # endregion
    +
    +    def __str__(self):
    +        return '<{0} id=`{1}` (status={2})>'.format(
    +            self.__class__.__name__,
    +            getattr(self, self.name_column_name()),
    +            self.status
    +        )
    +
    +    __private_fields__ = ['service_fk',
    +                          'service_name',
    +                          'service_template',
    +                          'service_template_name']
    +
    +
    +class PluginBase(ModelMixin):
    +    """
    +    Plugin model representation.
    +    """
    +
    +    __tablename__ = 'plugin'
    +
    +    archive_name = Column(Text, nullable=False, index=True)
    +    distribution = Column(Text)
    +    distribution_release = Column(Text)
    +    distribution_version = Column(Text)
    +    package_name = Column(Text, nullable=False, index=True)
    +    package_source = Column(Text)
    +    package_version = Column(Text)
    +    supported_platform = Column(Text)
    +    supported_py_versions = Column(List)
    +    uploaded_at = Column(DateTime, nullable=False, index=True)
    +    wheels = Column(List, nullable=False)
    +
    +
    +class TaskBase(ModelMixin):
    +    """
    +    A Model which represents an task
    +    """
    +
    +    __tablename__ = 'task'
    +
    +    PENDING = 'pending'
    +    RETRYING = 'retrying'
    +    SENT = 'sent'
    +    STARTED = 'started'
    +    SUCCESS = 'success'
    +    FAILED = 'failed'
    +    STATES = (
    +        PENDING,
    +        RETRYING,
    +        SENT,
    +        STARTED,
    +        SUCCESS,
    +        FAILED,
    +    )
    +
    +    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
    +    def node(cls):
    +        return relationships.many_to_one(cls, 'node')
    --- End diff --
    
    nice :wink: 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r105643914
  
    --- Diff: aria/modeling/service.py ---
    @@ -91,20 +115,16 @@ def operations(cls):
             return cls.one_to_many_relationship('operation', dict_key='name')
     
         @declared_attr
    -    def service_template(cls):
    -        return cls.many_to_one_relationship('service_template')
    -
    -    # region orchestration
    +    def plugins(cls):
    +        return cls.many_to_many_relationship('plugin')
     
         created_at = Column(DateTime, nullable=False, index=True)
         updated_at = Column(DateTime)
    +
    +    # region orchestration
    +
         permalink = Column(Text)
         scaling_groups = Column(modeling_types.Dict)
    -    workflows = Column(modeling_types.Dict)
    -
    -    @declared_attr
    -    def service_template_name(cls):
    --- End diff --
    
    Why?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106939485
  
    --- Diff: tests/modeling/test_models.py ---
    @@ -173,92 +168,83 @@ def _test_model(is_valid, storage, model_cls, model_kwargs):
             getattr(storage, model_cls.__modelname__).put(model)
             return model
         else:
    -        with pytest.raises((exceptions.StorageError, TypeError),):
    +        with pytest.raises((ValueFormatException, StorageError, TypeError),):
                 getattr(storage, model_cls.__modelname__).put(model_cls(**model_kwargs))
     
     
     class TestServiceTemplate(object):
     
         @pytest.mark.parametrize(
    -        'is_valid, plan, description, created_at, updated_at, main_file_name',
    +        'is_valid, description, created_at, updated_at, main_file_name',
             [
    -            (False, None, 'description', now, now, '/path'),
    -            (False, {}, {}, now, now, '/path'),
    -            (False, {}, 'description', 'error', now, '/path'),
    -            (False, {}, 'description', now, 'error', '/path'),
    -            (False, {}, 'description', now, now, {}),
    -            (True, {}, 'description', now, now, '/path'),
    +            (True, 'description', now, now, '/path'),
    --- End diff --
    
    Fixed


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by asfgit <gi...@git.apache.org>.
Github user asfgit closed the pull request at:

    https://github.com/apache/incubator-ariatosca/pull/72


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106802429
  
    --- Diff: aria/modeling/service_changes.py ---
    @@ -0,0 +1,231 @@
    +# Licensed to the Apache Software Foundation (ASF) under one or more
    --- End diff --
    
    I think that eventually ServiceUpdate and ServiceModification would be merged, and the module could be called service_modification.py
    
    just something to consider later on...


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106804866
  
    --- Diff: aria/orchestrator/workflows/api/task.py ---
    @@ -92,144 +93,133 @@ def __init__(self,
                                    if ignore_failure is None else ignore_failure)
             self.runs_on = runs_on
     
    -    @classmethod
    -    def _merge_inputs(cls, operation_inputs, additional_inputs=None):
    -        final_inputs = dict((p.name, p.as_raw['value']) for p in operation_inputs)
    -        final_inputs.update(additional_inputs or {})
    -        return final_inputs
    +        # Wrap inputs
    +        if inputs:
    +            for k, v in inputs.iteritems():
    +                if not isinstance(v, models.Parameter):
    +                    inputs[k] = models.Parameter.wrap(k, v)
    +
    +        # TODO: These extra inputs should likely be stored as a separate entry in the task model,
    +        # because they are different from the operation inputs. The two kinds of inputs should also
    +        # not be merged.
    +
    +        if interface_name or operation_name:
    +            operation = OperationTask._get_operation(actor.interfaces, interface_name,
    +                                                     operation_name)
    +            if operation is None:
    +                raise exceptions.TaskException(
    +                    'Could not find operation "{0}" on interface "{1}" for {2} "{3}"'
    +                    .format(operation_name, 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.TaskException(
    +                        'Could not find plugin of operation "{0}" on interface "{1}" for {2} "{3}"'
    +                        .format(operation_name, 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=interface_name,
    +                                                         operation=operation_name)
    +        else:
    +            self.name = name
    +            self.implementation = implementation
    +            self.inputs = inputs or {}
    +            self.plugin = None
     
         @classmethod
    -    def node(cls, instance, name, inputs=None, *args, **kwargs):
    +    def for_node(cls,
    +                 node,
    +                 interface_name,
    +                 operation_name,
    +                 max_attempts=None,
    +                 retry_interval=None,
    +                 ignore_failure=None,
    +                 inputs=None):
             """
    -        Represents a node based operation
    +        Creates an operation on a node.
     
    -        :param instance: the node of which this operation belongs to.
    -        :param name: the name of the operation.
    +        :param node: the node of which this operation belongs to.
    +        :param interface_name: the name of the interface.
    +        :param operation_name: the name of the operation.
    +        :param inputs: any additional inputs to the operation
             """
    -        assert isinstance(instance, model.Node)
    -        interface_name = _get_interface_name(name)
    -        interfaces = instance.interfaces.filter_by(name=interface_name)
    -        if interfaces.count() > 1:
    -            raise exceptions.TaskException(
    -                "More than one interface with the same name `{0}` found".format(name)
    -            )
    -        elif interfaces.count() == 0:
    -            raise exceptions.TaskException(
    -                "No Interface with the name `{interface_name}` found".format(
    -                    interface_name=interface_name)
    -            )
    -
    -        operation_templates = interfaces[0].operations.filter_by(name=name)
    -        if operation_templates.count() > 1:
    -            raise exceptions.TaskException(
    -                "More than one operation with the same name `{0}` were found".format(name)
    -            )
    -
    -        elif operation_templates.count() == 0:
    -            raise exceptions.TaskException(
    -                "No interface with the name `{operation_name}` found".format(
    -                    operation_name=name)
    -            )
    -
    -        return cls._instance(
    -            instance=instance,
    -            name=name,
    -            operation_template=operation_templates[0],
    -            plugins=instance.plugins or [],
    -            runs_on=model.Task.RUNS_ON_NODE_INSTANCE,
    -            inputs=cls._merge_inputs(operation_templates[0].inputs, inputs),
    -            *args,
    -            **kwargs)
    +
    +        assert isinstance(node, models.Node)
    +        return cls(
    +            actor=node,
    +            actor_type='node',
    +            interface_name=interface_name,
    +            operation_name=operation_name,
    +            max_attempts=max_attempts,
    +            retry_interval=retry_interval,
    +            ignore_failure=ignore_failure,
    +            inputs=inputs,
    +            runs_on=models.Task.RUNS_ON_NODE)
     
         @classmethod
    -    def relationship(cls, instance, name, edge, runs_on=None, inputs=None, *args,
    -                     **kwargs):
    +    def for_relationship(cls,
    +                         relationship,
    +                         interface_name,
    +                         operation_name,
    +                         max_attempts=None,
    +                         retry_interval=None,
    +                         ignore_failure=None,
    +                         inputs=None,
    +                         runs_on=models.Task.RUNS_ON_SOURCE):
             """
    -        Represents a relationship based operation
    +        Creates an operation on a relationship edge.
     
    -        :param instance: the relationship of which this operation belongs to.
    -        :param name: the name of the operation.
    -        :param edge: the edge of the interface ("source" or "target").
    -        :param runs_on: where to run the operation ("source" or "target"); if None defaults to the
    -                        interface edge.
    -        :param inputs any additional inputs to the operation
    +        :param relationship: the relationship of which this operation belongs to.
    +        :param interface_name: the name of the interface.
    +        :param operation_name: the name of the operation.
    +        :param inputs: any additional inputs to the operation
    +        :param runs_on: where to run the operation ("source" or "target"); defaults to "source"
             """
    -        assert isinstance(instance, model.Relationship)
    -        interface_name = _get_interface_name(name)
    -        interfaces = instance.interfaces.filter_by(name=interface_name, edge=edge)
    -        count = interfaces.count()
    -        if count > 1:
    -            raise exceptions.TaskException(
    -                "More than one interface with the same name `{interface_name}` found at `{edge}`"
    -                + " edge".format(
    -                    interface_name=interface_name, edge=edge)
    -            )
    -        elif count == 0:
    -            raise exceptions.TaskException(
    -                "No interface with the name `{interface_name}` found at `{edge}` edge".format(
    -                    interface_name=interface_name, edge=edge)
    -            )
    -
    -        operations = interfaces.all()[0].operations.filter_by(name=name)
    -        count = operations.count()
    -        if count > 1:
    -            raise exceptions.TaskException(
    -                "More than one operation with the same name `{0}` found".format(name)
    -            )
    -        elif count == 0:
    -            raise exceptions.TaskException(
    -                "No operation with the name `{operation_name}` found".format(
    -                    operation_name=name)
    -            )
    -
    -        if not runs_on:
    -            if edge == cls.SOURCE_OPERATION:
    -                runs_on = model.Task.RUNS_ON_SOURCE
    -            else:
    -                runs_on = model.Task.RUNS_ON_TARGET
    -
    -        if runs_on == model.Task.RUNS_ON_SOURCE:
    -            plugins = instance.source_node.plugins
    -        else:
    -            plugins = instance.target_node.plugins
    -
    -        return cls._instance(instance=instance,
    -                             name=name,
    -                             operation_template=operations[0],
    -                             plugins=plugins or [],
    -                             runs_on=runs_on,
    -                             inputs=cls._merge_inputs(operations[0].inputs, inputs),
    -                             *args,
    -                             **kwargs)
     
    -    @classmethod
    -    def _instance(cls,
    -                  instance,
    -                  name,
    -                  operation_template,
    -                  inputs,
    -                  plugins,
    -                  runs_on,
    -                  *args,
    -                  **kwargs):
    -        matching_plugins = [p for p in plugins if p['name'] == operation_template.plugin]
    -        # All matching plugins should have identical package_name/package_version, so it's safe to
    -        # take the first found.
    -        plugin = matching_plugins[0] if matching_plugins else {}
    -        return cls(actor=instance,
    -                   name=name,
    -                   implementation=operation_template.implementation,
    -                   inputs=inputs,
    -                   plugin=plugin,
    -                   runs_on=runs_on,
    -                   *args,
    -                   **kwargs)
    +        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,
    +            max_attempts=max_attempts,
    +            retry_interval=retry_interval,
    +            ignore_failure=ignore_failure,
    +            inputs=inputs,
    +            runs_on=runs_on)
    +
    +    @staticmethod
    +    def _get_operation(interfaces, interface_name, operation_name):
    +        interface = interfaces.get(interface_name)
    +        if interface is not None:
    +            return interface.operations.get(operation_name)
    +        return None
    +
    +    @staticmethod
    +    def _find_plugin(plugin_specification):
    --- End diff --
    
    still not sure about extracting the context and using it in the model...


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104922547
  
    --- Diff: aria/orchestrator/workflows/core/task.py ---
    @@ -66,65 +67,60 @@ class StubTask(BaseTask):
     
         def __init__(self, *args, **kwargs):
             super(StubTask, self).__init__(*args, **kwargs)
    -        self.status = model.Task.PENDING
    +        self.status = models.Task.PENDING
             self.due_at = datetime.utcnow()
     
     
     class StartWorkflowTask(StubTask):
         """
    -    Tasks marking a workflow start
    +    Task marking a workflow start
         """
         pass
     
     
     class EndWorkflowTask(StubTask):
         """
    -    Tasks marking a workflow end
    +    Task marking a workflow end
         """
         pass
     
     
     class StartSubWorkflowTask(StubTask):
         """
    -    Tasks marking a subworkflow start
    +    Task marking a subworkflow start
         """
         pass
     
     
     class EndSubWorkflowTask(StubTask):
         """
    -    Tasks marking a subworkflow end
    +    Task marking a subworkflow end
         """
         pass
     
     
     class OperationTask(BaseTask):
         """
    -    Operation tasks
    +    Operation task
         """
     
         def __init__(self, api_task, *args, **kwargs):
             super(OperationTask, self).__init__(id=api_task.id, **kwargs)
             self._workflow_context = api_task._workflow_context
             model_storage = api_task._workflow_context.model
    +        plugin = api_task.plugin
     
             base_task_model = model_storage.task.model_cls
    -        if isinstance(api_task.actor, model.Node):
    +        if isinstance(api_task.actor, models.Node):
                 context_cls = operation_context.NodeOperationContext
    -            task_model_cls = base_task_model.as_node_instance
    -        elif isinstance(api_task.actor, model.Relationship):
    +            task_model_cls = base_task_model.as_node_task
    +        elif isinstance(api_task.actor, models.Relationship):
                 context_cls = operation_context.RelationshipOperationContext
    -            task_model_cls = base_task_model.as_relationship_instance
    +            task_model_cls = base_task_model.as_relationship_task
             else:
                 raise RuntimeError('No operation context could be created for {actor.model_cls}'
                                    .format(actor=api_task.actor))
    -        plugin = api_task.plugin
    --- End diff --
    
    why was this deleted again?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106941886
  
    --- Diff: aria/orchestrator/workflows/api/task.py ---
    @@ -92,144 +93,133 @@ def __init__(self,
                                    if ignore_failure is None else ignore_failure)
             self.runs_on = runs_on
     
    -    @classmethod
    -    def _merge_inputs(cls, operation_inputs, additional_inputs=None):
    -        final_inputs = dict((p.name, p.as_raw['value']) for p in operation_inputs)
    -        final_inputs.update(additional_inputs or {})
    -        return final_inputs
    +        # Wrap inputs
    +        if inputs:
    +            for k, v in inputs.iteritems():
    +                if not isinstance(v, models.Parameter):
    +                    inputs[k] = models.Parameter.wrap(k, v)
    +
    +        # TODO: These extra inputs should likely be stored as a separate entry in the task model,
    +        # because they are different from the operation inputs. The two kinds of inputs should also
    +        # not be merged.
    +
    +        if interface_name or operation_name:
    +            operation = OperationTask._get_operation(actor.interfaces, interface_name,
    +                                                     operation_name)
    +            if operation is None:
    +                raise exceptions.TaskException(
    +                    'Could not find operation "{0}" on interface "{1}" for {2} "{3}"'
    +                    .format(operation_name, 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.TaskException(
    +                        'Could not find plugin of operation "{0}" on interface "{1}" for {2} "{3}"'
    +                        .format(operation_name, 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=interface_name,
    +                                                         operation=operation_name)
    +        else:
    +            self.name = name
    +            self.implementation = implementation
    +            self.inputs = inputs or {}
    +            self.plugin = None
     
         @classmethod
    -    def node(cls, instance, name, inputs=None, *args, **kwargs):
    +    def for_node(cls,
    +                 node,
    +                 interface_name,
    +                 operation_name,
    +                 max_attempts=None,
    +                 retry_interval=None,
    +                 ignore_failure=None,
    +                 inputs=None):
             """
    -        Represents a node based operation
    +        Creates an operation on a node.
     
    -        :param instance: the node of which this operation belongs to.
    -        :param name: the name of the operation.
    +        :param node: the node of which this operation belongs to.
    +        :param interface_name: the name of the interface.
    +        :param operation_name: the name of the operation.
    +        :param inputs: any additional inputs to the operation
             """
    -        assert isinstance(instance, model.Node)
    -        interface_name = _get_interface_name(name)
    -        interfaces = instance.interfaces.filter_by(name=interface_name)
    -        if interfaces.count() > 1:
    -            raise exceptions.TaskException(
    -                "More than one interface with the same name `{0}` found".format(name)
    -            )
    -        elif interfaces.count() == 0:
    -            raise exceptions.TaskException(
    -                "No Interface with the name `{interface_name}` found".format(
    -                    interface_name=interface_name)
    -            )
    -
    -        operation_templates = interfaces[0].operations.filter_by(name=name)
    -        if operation_templates.count() > 1:
    -            raise exceptions.TaskException(
    -                "More than one operation with the same name `{0}` were found".format(name)
    -            )
    -
    -        elif operation_templates.count() == 0:
    -            raise exceptions.TaskException(
    -                "No interface with the name `{operation_name}` found".format(
    -                    operation_name=name)
    -            )
    -
    -        return cls._instance(
    -            instance=instance,
    -            name=name,
    -            operation_template=operation_templates[0],
    -            plugins=instance.plugins or [],
    -            runs_on=model.Task.RUNS_ON_NODE_INSTANCE,
    -            inputs=cls._merge_inputs(operation_templates[0].inputs, inputs),
    -            *args,
    -            **kwargs)
    +
    +        assert isinstance(node, models.Node)
    +        return cls(
    +            actor=node,
    +            actor_type='node',
    +            interface_name=interface_name,
    +            operation_name=operation_name,
    +            max_attempts=max_attempts,
    +            retry_interval=retry_interval,
    +            ignore_failure=ignore_failure,
    +            inputs=inputs,
    +            runs_on=models.Task.RUNS_ON_NODE)
     
         @classmethod
    -    def relationship(cls, instance, name, edge, runs_on=None, inputs=None, *args,
    -                     **kwargs):
    +    def for_relationship(cls,
    +                         relationship,
    +                         interface_name,
    +                         operation_name,
    +                         max_attempts=None,
    +                         retry_interval=None,
    +                         ignore_failure=None,
    +                         inputs=None,
    +                         runs_on=models.Task.RUNS_ON_SOURCE):
             """
    -        Represents a relationship based operation
    +        Creates an operation on a relationship edge.
     
    -        :param instance: the relationship of which this operation belongs to.
    -        :param name: the name of the operation.
    -        :param edge: the edge of the interface ("source" or "target").
    -        :param runs_on: where to run the operation ("source" or "target"); if None defaults to the
    -                        interface edge.
    -        :param inputs any additional inputs to the operation
    +        :param relationship: the relationship of which this operation belongs to.
    +        :param interface_name: the name of the interface.
    +        :param operation_name: the name of the operation.
    +        :param inputs: any additional inputs to the operation
    +        :param runs_on: where to run the operation ("source" or "target"); defaults to "source"
             """
    -        assert isinstance(instance, model.Relationship)
    -        interface_name = _get_interface_name(name)
    -        interfaces = instance.interfaces.filter_by(name=interface_name, edge=edge)
    -        count = interfaces.count()
    -        if count > 1:
    -            raise exceptions.TaskException(
    -                "More than one interface with the same name `{interface_name}` found at `{edge}`"
    -                + " edge".format(
    -                    interface_name=interface_name, edge=edge)
    -            )
    -        elif count == 0:
    -            raise exceptions.TaskException(
    -                "No interface with the name `{interface_name}` found at `{edge}` edge".format(
    -                    interface_name=interface_name, edge=edge)
    -            )
    -
    -        operations = interfaces.all()[0].operations.filter_by(name=name)
    -        count = operations.count()
    -        if count > 1:
    -            raise exceptions.TaskException(
    -                "More than one operation with the same name `{0}` found".format(name)
    -            )
    -        elif count == 0:
    -            raise exceptions.TaskException(
    -                "No operation with the name `{operation_name}` found".format(
    -                    operation_name=name)
    -            )
    -
    -        if not runs_on:
    -            if edge == cls.SOURCE_OPERATION:
    -                runs_on = model.Task.RUNS_ON_SOURCE
    -            else:
    -                runs_on = model.Task.RUNS_ON_TARGET
    -
    -        if runs_on == model.Task.RUNS_ON_SOURCE:
    -            plugins = instance.source_node.plugins
    -        else:
    -            plugins = instance.target_node.plugins
    -
    -        return cls._instance(instance=instance,
    -                             name=name,
    -                             operation_template=operations[0],
    -                             plugins=plugins or [],
    -                             runs_on=runs_on,
    -                             inputs=cls._merge_inputs(operations[0].inputs, inputs),
    -                             *args,
    -                             **kwargs)
     
    -    @classmethod
    -    def _instance(cls,
    -                  instance,
    -                  name,
    -                  operation_template,
    -                  inputs,
    -                  plugins,
    -                  runs_on,
    -                  *args,
    -                  **kwargs):
    -        matching_plugins = [p for p in plugins if p['name'] == operation_template.plugin]
    -        # All matching plugins should have identical package_name/package_version, so it's safe to
    -        # take the first found.
    -        plugin = matching_plugins[0] if matching_plugins else {}
    -        return cls(actor=instance,
    -                   name=name,
    -                   implementation=operation_template.implementation,
    -                   inputs=inputs,
    -                   plugin=plugin,
    -                   runs_on=runs_on,
    -                   *args,
    -                   **kwargs)
    +        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,
    +            max_attempts=max_attempts,
    +            retry_interval=retry_interval,
    +            ignore_failure=ignore_failure,
    +            inputs=inputs,
    +            runs_on=runs_on)
    +
    +    @staticmethod
    +    def _get_operation(interfaces, interface_name, operation_name):
    +        interface = interfaces.get(interface_name)
    +        if interface is not None:
    +            return interface.operations.get(operation_name)
    +        return None
    +
    +    @staticmethod
    +    def _find_plugin(plugin_specification):
    --- End diff --
    
    I'm prepared to hear a better suggestion as to where to get the list of installed plugins from.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106993772
  
    --- Diff: aria/modeling/relationships.py ---
    @@ -0,0 +1,402 @@
    +# 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.
    +
    +# pylint: disable=invalid-name, redefined-outer-name
    +
    +from sqlalchemy.orm import relationship, backref
    +from sqlalchemy.orm.collections import attribute_mapped_collection
    +from sqlalchemy import (
    +    Column,
    +    ForeignKey,
    +    Integer,
    +    Table
    +)
    +
    +from ..utils import formatting
    +
    +
    +def fk(other_table,
    +       nullable=False):
    +    """
    +    Declare a foreign key property, which will also create a foreign key column in the table with
    +    the name of the property. By convention the property name should end in "_fk".
    +
    +    You are required to explicitly create foreign keys in order to allow for one-to-one,
    +    one-to-many, and many-to-one relationships (but not for many-to-many relationships). If you do
    +    not do so, SQLAlchemy will fail to create the relationship property and raise an exception with
    +    a clear error message.
    +
    +    You should normally not have to access this property directly, but instead use the associated
    +    relationship properties.
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param other_table: Other table name
    +    :type other_table: basestring
    +    :param nullable: True to allow null values (meaning that there is no relationship)
    +    :type nullable: bool
    +    """
    +
    +    return Column(Integer,
    +                  ForeignKey('{table}.id'.format(table=other_table), ondelete='CASCADE'),
    +                  nullable=nullable)
    +
    +
    +def one_to_one_self(model_class,
    +                    fk,
    +                    relationship_kwargs=None):
    +    """
    +    Declare a one-to-one relationship property. The property value would be an instance of the same
    +    model.
    +
    +    You will need an associated foreign key to our own table.
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param model_class: The class in which this relationship will be declared
    +    :type model_class: type
    +    :param fk: Foreign key name
    +    :type fk: basestring
    +    :param relationship_kwargs: Extra kwargs for SQLAlchemy `relationship`
    +    :type relationship_kwargs: {}
    +    """
    +
    +    relationship_kwargs = relationship_kwargs or {}
    +
    +    remote_side = '{model_class}.{remote_column}'.format(
    +        model_class=model_class.__name__,
    +        remote_column=model_class.id_column_name()
    +    )
    +
    +    primaryjoin = '{remote_side} == {model_class}.{column}'.format(
    +        remote_side=remote_side,
    +        model_class=model_class.__name__,
    +        column=fk
    +    )
    +
    +    return relationship(
    +        _get_class_for_table(model_class, model_class.__tablename__).__name__,
    +        primaryjoin=primaryjoin,
    +        remote_side=remote_side,
    +        post_update=True,
    +        **relationship_kwargs
    +    )
    +
    +
    +def one_to_many_self(model_class,
    +                     fk,
    +                     dict_key=None,
    +                     relationship_kwargs=None):
    +    """
    +    Declare a one-to-many relationship property. The property value would be a list or dict of
    +    instances of the same model.
    +
    +    You will need an associated foreign key to our own table.
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param model_class: The class in which this relationship will be declared
    +    :type model_class: type
    +    :param fk: Foreign key name
    +    :type fk: basestring
    +    :param dict_key: If set the value will be a dict with this key as the dict key; otherwise will
    +                     be a list
    +    :type dict_key: basestring
    +    :param relationship_kwargs: Extra kwargs for SQLAlchemy `relationship`
    +    :type relationship_kwargs: {}
    +    """
    +
    +    relationship_kwargs = relationship_kwargs or {}
    +
    +    relationship_kwargs.setdefault('remote_side', '{model_class}.{remote_column}'.format(
    +        model_class=model_class.__name__,
    +        remote_column=fk
    +    ))
    +
    +    return _relationship(model_class, model_class.__tablename__, None, relationship_kwargs,
    +                         other_property=False, dict_key=dict_key)
    +
    +
    +def one_to_one(model_class,
    +               other_table,
    +               fk=None,
    +               other_fk=None,
    +               other_property=None,
    +               relationship_kwargs=None,
    +               backref_kwargs=None):
    +    """
    +    Declare a one-to-one relationship property. The property value would be an instance of the other
    +    table's model.
    +
    +    You have two options for the foreign key. Either this table can have an associated key to the
    +    other table (use the `fk` argument) or the other table can have an associated foreign key to
    +    this our table (use the `other_fk` argument).
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param model_class: The class in which this relationship will be declared
    +    :type model_class: type
    +    :param other_table: Other table name
    +    :type other_table: basestring
    +    :param fk: Foreign key name at our table (no need specify if there's no ambiguity)
    +    :type fk: basestring
    +    :param other_fk: Foreign key name at the other table (no need specify if there's no ambiguity)
    +    :type other_fk: basestring
    +    :param relationship_kwargs: Extra kwargs for SQLAlchemy `relationship`
    +    :type relationship_kwargs: {}
    +    :param backref_kwargs: Extra kwargs for SQLAlchemy `backref`
    +    :type backref_kwargs: {}
    +    """
    +
    +    backref_kwargs = backref_kwargs or {}
    +    backref_kwargs.setdefault('uselist', False)
    +
    +    return _relationship(model_class, other_table, backref_kwargs, relationship_kwargs,
    +                         other_property, fk=fk, other_fk=other_fk)
    +
    +
    +def one_to_many(model_class,
    +                child_table,
    +                child_fk=None,
    +                dict_key=None,
    +                child_property=None,
    +                relationship_kwargs=None,
    +                backref_kwargs=None):
    +    """
    +    Declare a one-to-many relationship property. The property value would be a list or dict of
    +    instances of the child table's model.
    +
    +    The child table will need an associated foreign key to our table.
    +
    +    The declaration will automatically create a matching many-to-one property at the child model,
    +    named after our table name. Use the `child_property` argument to override this name.
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param model_class: The class in which this relationship will be declared
    +    :type model_class: type
    +    :param child_table: Child table name
    +    :type child_table: basestring
    +    :param child_fk: Foreign key name at the child table (no need specify if there's no ambiguity)
    +    :type child_fk: basestring
    +    :param dict_key: If set the value will be a dict with this key as the dict key; otherwise will
    +                     be a list
    +    :type dict_key: basestring
    +    :param child_property: Override name of matching many-to-one property at child table; set to
    +                           false to disable
    +    :type child_property: basestring|bool
    +    :param relationship_kwargs: Extra kwargs for SQLAlchemy `relationship`
    +    :type relationship_kwargs: {}
    +    :param backref_kwargs: Extra kwargs for SQLAlchemy `backref`
    +    :type backref_kwargs: {}
    +    """
    +
    +    backref_kwargs = backref_kwargs or {}
    +    backref_kwargs.setdefault('uselist', False)
    +
    +    return _relationship(model_class, child_table, backref_kwargs, relationship_kwargs,
    +                         child_property, other_fk=child_fk, dict_key=dict_key)
    +
    +
    +def many_to_one(model_class,
    +                parent_table,
    +                fk=None,
    +                parent_fk=None,
    +                parent_property=None,
    +                relationship_kwargs=None,
    +                backref_kwargs=None):
    +    """
    +    Declare a many-to-one relationship property. The property value would be an instance of the
    +    parent table's model.
    +
    +    You will need an associated foreign key to the parent table.
    +
    +    The declaration will automatically create a matching one-to-many property at the child model,
    +    named after the plural form of our table name. Use the `parent_property` argument to override
    +    this name. Note: the automatic property will always be a SQLAlchemy query object; if you need a
    +    Python collection then use :meth:`one_to_many` at that model.
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param model_class: The class in which this relationship will be declared
    +    :type model_class: type
    +    :param parent_table: Parent table name
    +    :type parent_table: basestring
    +    :param fk: Foreign key name at our table (no need specify if there's no ambiguity)
    +    :type fk: basestring
    +    :param parent_property: Override name of matching one-to-many property at parent table; set to
    +                            false to disable
    +    :type parent_property: basestring|bool
    +    :param relationship_kwargs: Extra kwargs for SQLAlchemy `relationship`
    +    :type relationship_kwargs: {}
    +    :param backref_kwargs: Extra kwargs for SQLAlchemy `backref`
    +    :type backref_kwargs: {}
    +    """
    +
    +    if parent_property is None:
    +        parent_property = formatting.pluralize(model_class.__tablename__)
    +
    +    backref_kwargs = backref_kwargs or {}
    +    backref_kwargs.setdefault('uselist', True)
    +    backref_kwargs.setdefault('lazy', 'dynamic')
    +    backref_kwargs.setdefault('cascade', 'all') # delete children when parent is deleted
    +
    +    return _relationship(model_class, parent_table, backref_kwargs, relationship_kwargs,
    +                         parent_property, fk=fk, other_fk=parent_fk)
    +
    +
    +def many_to_many(model_class,
    +                 other_table,
    +                 prefix=None,
    +                 dict_key=None,
    +                 other_property=None,
    +                 relationship_kwargs=None,
    +                 backref_kwargs=None):
    +    """
    +    Declare a many-to-many relationship property. The property value would be a list or dict of
    +    instances of the other table's model.
    +
    +    You do not need associated foreign keys for this relationship. Instead, an extra table will be
    +    created for you.
    +
    +    The declaration will automatically create a matching many-to-many property at the other model,
    +    named after the plural form of our table name. Use the `other_property` argument to override
    +    this name. Note: the automatic property will always be a SQLAlchemy query object; if you need a
    +    Python collection then use :meth:`many_to_many` again at that model.
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param model_class: The class in which this relationship will be declared
    +    :type model_class: type
    +    :param parent_table: Parent table name
    +    :type parent_table: basestring
    +    :param prefix: Optional prefix for extra table name as well as for `other_property`
    +    :type prefix: basestring
    +    :param dict_key: If set the value will be a dict with this key as the dict key; otherwise will
    +                     be a list
    +    :type dict_key: basestring
    +    :param other_property: Override name of matching many-to-many property at other table; set to
    +                           false to disable
    +    :type other_property: basestring|bool
    +    :param relationship_kwargs: Extra kwargs for SQLAlchemy `relationship`
    +    :type relationship_kwargs: {}
    +    :param backref_kwargs: Extra kwargs for SQLAlchemy `backref`
    +    :type backref_kwargs: {}
    +    """
    +
    +    this_table = model_class.__tablename__
    +    this_column_name = '{0}_id'.format(this_table)
    +    this_foreign_key = '{0}.id'.format(this_table)
    +
    +    other_column_name = '{0}_id'.format(other_table)
    +    other_foreign_key = '{0}.id'.format(other_table)
    +
    +    secondary_table = '{0}_{1}'.format(this_table, other_table)
    +
    +    if other_property is None:
    +        other_property = formatting.pluralize(this_table)
    +        if prefix is not None:
    +            secondary_table = '{0}_{1}'.format(prefix, secondary_table)
    +            other_property = '{0}_{1}'.format(prefix, other_property)
    +
    +    backref_kwargs = backref_kwargs or {}
    +    backref_kwargs.setdefault('uselist', True)
    +
    +    relationship_kwargs = relationship_kwargs or {}
    +    relationship_kwargs.setdefault('secondary', _get_secondary_table(
    +        model_class.metadata,
    +        secondary_table,
    +        this_column_name,
    +        other_column_name,
    +        this_foreign_key,
    +        other_foreign_key
    +    ))
    +
    +    return _relationship(model_class, other_table, backref_kwargs, relationship_kwargs,
    +                         other_property, dict_key=dict_key)
    +
    +
    +def _relationship(model_class, other_table, backref_kwargs, relationship_kwargs, other_property,
    +                  fk=None, other_fk=None, dict_key=None):
    +    relationship_kwargs = relationship_kwargs or {}
    +
    +    if fk:
    +        relationship_kwargs.setdefault('foreign_keys',
    +                                       lambda: getattr(
    +                                           _get_class_for_table(
    +                                               model_class,
    +                                               model_class.__tablename__),
    +                                           fk))
    +
    +    elif other_fk:
    +        relationship_kwargs.setdefault('foreign_keys',
    +                                       lambda: getattr(
    +                                           _get_class_for_table(
    +                                               model_class,
    +                                               other_table),
    +                                           other_fk))
    +
    +    if dict_key:
    +        relationship_kwargs.setdefault('collection_class',
    +                                       attribute_mapped_collection(dict_key))
    +
    +    if other_property is False:
    +        # No backref
    +        return relationship(
    +            lambda: _get_class_for_table(model_class, other_table),
    +            **relationship_kwargs
    +        )
    +    else:
    +        if other_property is None:
    +            other_property = model_class.__tablename__
    +        backref_kwargs = backref_kwargs or {}
    +        return relationship(
    +            lambda: _get_class_for_table(model_class, other_table),
    +            backref=backref(other_property, **backref_kwargs),
    +            **relationship_kwargs
    +        )
    +
    +
    +def _get_class_for_table(model_class, tablename):
    --- End diff --
    
    Right, it should be opposite. Fixed.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104515969
  
    --- Diff: aria/modeling/misc.py ---
    @@ -0,0 +1,232 @@
    +# Licensed to the Apache Software Foundation (ASF) under one or more
    +# contributor license agreements.  See the NOTICE file distributed with
    +# this work for additional information regarding copyright ownership.
    +# The ASF licenses this file to You under the Apache License, Version 2.0
    +# (the "License"); you may not use this file except in compliance with
    +# the License.  You may obtain a copy of the License at
    +#
    +#     http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +
    +import cPickle as pickle
    +import logging
    +
    +from sqlalchemy import (
    +    Column,
    +    Text,
    +    Binary
    +)
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from ..storage import exceptions
    +from ..utils import collections, formatting, console
    +from .bases import InstanceModelMixin, TemplateModelMixin
    +from . import utils
    +
    +
    +class ParameterBase(TemplateModelMixin):
    +    """
    +    Represents a typed value.
    +
    +    This class is used by both service template and service instance elements.
    +
    +    :ivar name: Name
    +    :ivar type_name: Type name
    +    :ivar value: Value
    +    :ivar description: Description
    +    """
    +
    +    __tablename__ = 'parameter'
    +
    +    name = Column(Text)
    +    type_name = Column(Text)
    +
    +    # Check: value type
    +    _value = Column(Binary, name='value')
    +    description = Column(Text)
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('name', self.name),
    +            ('type_name', self.type_name),
    +            ('value', self.value),
    +            ('description', self.description)))
    +
    +    @property
    +    def value(self):
    +        if self._value is None:
    +            return None
    +        try:
    +            return pickle.loads(self._value)
    +        except BaseException:
    +            raise exceptions.StorageError('bad format for parameter of type "{0}": {1}'.format(
    +                self.type_name, self._value))
    +
    +    @value.setter
    +    def value(self, value):
    +        if value is None:
    +            self._value = None
    +        else:
    +            try:
    +                self._value = pickle.dumps(value)
    +            except (pickle.PicklingError, TypeError):
    +                logging.getLogger('aria').warn('Could not pickle parameter of type "{0}": {1}'
    --- End diff --
    
    this needs to raise an error, not log a warning


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106802735
  
    --- Diff: aria/modeling/mixins.py ---
    @@ -0,0 +1,142 @@
    +# 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.
    +
    +"""
    +classes:
    +    * ModelMixin - abstract model implementation.
    +    * ModelIDMixin - abstract model implementation with IDs.
    +"""
    +
    +from sqlalchemy.ext import associationproxy
    +from sqlalchemy import (
    +    Column,
    +    Integer,
    +    Text,
    +)
    +
    +from .utils import classproperty
    +
    +
    +class ModelMixin(object):
    +
    +    @classproperty
    +    def __modelname__(cls):                                                                         # pylint: disable=no-self-argument
    +        return getattr(cls, '__mapiname__', cls.__tablename__)
    +
    +    @classmethod
    +    def id_column_name(cls):
    +        raise NotImplementedError
    +
    +    @classmethod
    +    def name_column_name(cls):
    +        raise NotImplementedError
    +
    +    def to_dict(self, fields=None, suppress_error=False):
    +        """
    +        Return a dict representation of the model
    +
    +        :param suppress_error: If set to True, sets `None` to attributes that it's unable to
    +                               retrieve (e.g., if a relationship wasn't established yet, and so it's
    +                               impossible to access a property through it)
    +        """
    +
    +        res = dict()
    +        fields = fields or self.fields()
    +        for field in fields:
    +            try:
    +                field_value = getattr(self, field)
    +            except AttributeError:
    +                if suppress_error:
    +                    field_value = None
    +                else:
    +                    raise
    +            if isinstance(field_value, list):
    +                field_value = list(field_value)
    +            elif isinstance(field_value, dict):
    +                field_value = dict(field_value)
    +            elif isinstance(field_value, ModelMixin):
    +                field_value = field_value.to_dict()
    +            res[field] = field_value
    +
    +        return res
    +
    +    @classmethod
    +    def fields(cls):
    +        """
    +        Return the list of field names for this table
    +
    +        Mostly for backwards compatibility in the code (that uses `fields`)
    +        """
    +
    +        fields = set(cls._iter_association_proxies())
    +        fields.update(cls.__table__.columns.keys())
    +        return fields - set(getattr(cls, '__private_fields__', []))
    +
    +    @classmethod
    +    def _iter_association_proxies(cls):
    +        for col, value in vars(cls).items():
    +            if isinstance(value, associationproxy.AssociationProxy):
    +                yield col
    +
    +    def __repr__(self):
    +        return '<{cls} id=`{id}`>'.format(
    +            cls=self.__class__.__name__,
    +            id=getattr(self, self.name_column_name()))
    +
    +
    +class ModelIDMixin(object):
    +    id = Column(Integer, primary_key=True, autoincrement=True)
    +    name = Column(Text, index=True)
    +
    +    @classmethod
    +    def id_column_name(cls):
    +        return 'id'
    +
    +    @classmethod
    +    def name_column_name(cls):
    +        return 'name'
    +
    +
    +class InstanceModelMixin(ModelMixin):
    +    """
    +    Mixin for :class:`ServiceInstance` models.
    +
    +    All models support validation, diagnostic dumping, and representation as
    +    raw data (which can be translated into JSON or YAML) via :code:`as_raw`.
    +    """
    +
    +    @property
    +    def as_raw(self):
    +        raise NotImplementedError
    +
    +    def validate(self):
    +        pass
    +
    +    def coerce_values(self, container, report_issues):
    --- End diff --
    
    There is currently an old PR for a unified coerce. I think that's a low hanging fruit to pick right now....
    
    https://github.com/apache/incubator-ariatosca/pull/78


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106802232
  
    --- Diff: aria/cli/dry.py ---
    @@ -0,0 +1,88 @@
    +# Licensed to the Apache Software Foundation (ASF) under one or more
    --- End diff --
    
    I thought we decided to put this module in the testing package


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104923355
  
    --- Diff: aria/modeling/service_template.py ---
    @@ -0,0 +1,1693 @@
    +# Licensed to the Apache Software Foundation (ASF) under one or more
    --- End diff --
    
    shouldnt this module be called templates and not service templates?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104932469
  
    --- Diff: aria/cli/commands.py ---
    @@ -230,43 +228,48 @@ def _parse(self, uri):
         
         def _get_workflow(self, context, workflow_name):
             if workflow_name in BUILTIN_WORKFLOWS:
    -            workflow_fn = import_fullname('aria.orchestrator.workflows.builtin.%s' % workflow_name)
    +            workflow_fn = import_fullname('aria.orchestrator.workflows.builtin.{0}'.format(
    +                workflow_name))
                 inputs = {}
             else:
    +            workflow = None
    +            for policy in context.modeling.instance.policies:
    --- End diff --
    
    if by now there should be a service in the storage, shouldn't this access be to the service's "operations" field instead?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104932478
  
    --- Diff: aria/modeling/service.py ---
    @@ -0,0 +1,1529 @@
    +# 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.
    +
    +# pylint: disable=no-self-argument, no-member, abstract-method
    +
    +from sqlalchemy import (
    +    Column,
    +    Text,
    +    Integer
    +)
    +from sqlalchemy import DateTime
    +from sqlalchemy.ext.associationproxy import association_proxy
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from .bases import InstanceModelMixin
    +from ..parser import validation
    +from ..utils import collections, formatting, console
    +
    +from . import (
    +    utils,
    +    types as modeling_types
    +)
    +
    +
    +class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
    +    """
    +    A service is usually an instance of a :class:`ServiceTemplate`.
    +
    +    You will usually not create it programmatically, but instead instantiate it from a service
    +    template.
    +
    +    :ivar name: Name (unique for this ARIA installation)
    +    :vartype name: basestring
    +    :ivar service_template: Template from which this service was instantiated (optional)
    +    :vartype service_template: :class:`ServiceTemplate`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar meta_data: Custom annotations
    +    :vartype meta_data: {basestring: :class:`Metadata`}
    +    :ivar node: Nodes
    +    :vartype node: [:class:`Node`]
    +    :ivar groups: Groups of nodes
    +    :vartype groups: [:class:`Group`]
    +    :ivar policies: Policies
    +    :vartype policies: [:class:`Policy`]
    +    :ivar substitution: The entire service can appear as a node
    +    :vartype substitution: :class:`Substitution`
    +    :ivar inputs: Externally provided parameters
    +    :vartype inputs: {basestring: :class:`Parameter`}
    +    :ivar outputs: These parameters are filled in after service installation
    +    :vartype outputs: {basestring: :class:`Parameter`}
    +    :ivar operations: Custom operations that can be performed on the service
    +    :vartype operations: {basestring: :class:`Operation`}
    +    :ivar plugins: Plugins required to be installed
    +    :vartype plugins: {basestring: :class:`Plugin`}
    +    :ivar created_at: Creation timestamp
    +    :vartype created_at: :class:`datetime.datetime`
    +    :ivar updated_at: Update timestamp
    +    :vartype updated_at: :class:`datetime.datetime`
    +
    +    :ivar permalink: ??
    +    :vartype permalink: basestring
    +    :ivar scaling_groups: ??
    +    :vartype scaling_groups: {}
    +
    +    :ivar modifications: Modifications of this service
    +    :vartype modifications: [:class:`ServiceModification`]
    +    :ivar updates: Updates of this service
    +    :vartype updates: [:class:`ServiceUpdate`]
    +    :ivar executions: Executions on this service
    +    :vartype executions: [:class:`Execution`]
    +    """
    +
    +    __tablename__ = 'service'
    +
    +    @declared_attr
    +    def service_template(cls):
    +        return cls.many_to_one_relationship('service_template')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def meta_data(cls):
    +        # Warning! We cannot use the attr name "metadata" because it's used by SqlAlchemy!
    +        return cls.many_to_many_relationship('metadata', dict_key='name')
    +
    +    @declared_attr
    +    def nodes(cls):
    +        return cls.one_to_many_relationship('node')
    +
    +    @declared_attr
    +    def groups(cls):
    +        return cls.one_to_many_relationship('group')
    +
    +    @declared_attr
    +    def policies(cls):
    +        return cls.one_to_many_relationship('policy')
    +
    +    @declared_attr
    +    def substitution(cls):
    +        return cls.one_to_one_relationship('substitution')
    +
    +    @declared_attr
    +    def inputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def outputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='outputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def operations(cls):
    +        return cls.one_to_many_relationship('operation', dict_key='name')
    +
    +    @declared_attr
    +    def plugins(cls):
    +        return cls.many_to_many_relationship('plugin')
    +
    +    created_at = Column(DateTime, nullable=False, index=True)
    +    updated_at = Column(DateTime)
    +
    +    # region orchestration
    +
    +    permalink = Column(Text)
    +    scaling_groups = Column(modeling_types.Dict)
    +
    +    # endregion
    +
    +    # region foreign keys
    +
    +    __private_fields__ = ['substituion_fk',
    +                          'service_template_fk']
    +
    +    # Service one-to-one to Substitution
    +    @declared_attr
    +    def substitution_fk(cls):
    +        return cls.foreign_key('substitution', nullable=True)
    +
    +    # Service many-to-one to ServiceTemplate
    +    @declared_attr
    +    def service_template_fk(cls):
    +        return cls.foreign_key('service_template', nullable=True)
    +
    +    # endregion
    +
    +    def satisfy_requirements(self, context):
    +        satisfied = True
    +        for node in self.nodes:
    +            if not node.satisfy_requirements(context):
    +                satisfied = False
    +        return satisfied
    +
    +    def validate_capabilities(self, context):
    +        satisfied = True
    +        for node in self.nodes:
    +            if not node.validate_capabilities(context):
    +                satisfied = False
    +        return satisfied
    +
    +    def find_nodes(self, node_template_name):
    --- End diff --
    
    couldn't this be replaced by an sql query (in a single line), and if so, why do we need this helper method?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104312966
  
    --- Diff: aria/modeling/orchestration.py ---
    @@ -451,12 +451,45 @@ def actor(self):
             """
             return self.node or self.relationship
     
    +    @orm.validates('max_attempts')
    +    def validate_max_attempts(self, _, value):                                  # pylint: disable=no-self-use
    +        """Validates that max attempts is either -1 or a positive number"""
    +        if value < 1 and value != TaskBase.INFINITE_RETRIES:
    +            raise ValueError('Max attempts can be either -1 (infinite) or any positive number. '
    +                             'Got {value}'.format(value=value))
    +        return value
    +
    +    # region foreign keys
    +
    +    __private_fields__ = ['node_fk',
    +                          'relationship_fk',
    +                          'plugin_fk',
    +                          'execution_fk']
    +
    +    @declared_attr
    +    def node_fk(cls):
    +        return cls.foreign_key('node', nullable=True)
    +
    +    @declared_attr
    +    def relationship_fk(cls):
    +        return cls.foreign_key('relationship', nullable=True)
    +
    +    @declared_attr
    +    def plugin_fk(cls):
    +        return cls.foreign_key('plugin', nullable=True)
    +
    +    @declared_attr
    +    def execution_fk(cls):
    +        return cls.foreign_key('execution', nullable=True)
    +
    +    # endregion
    +
         @classmethod
    -    def as_node_instance(cls, instance, runs_on, **kwargs):
    +    def as_node_task(cls, instance, runs_on, **kwargs):
             return cls(node=instance, _runs_on=runs_on, **kwargs)
     
         @classmethod
    -    def as_relationship_instance(cls, instance, runs_on, **kwargs):
    +    def as_relationship_task(cls, instance, runs_on, **kwargs):
    --- End diff --
    
    ditto


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106802921
  
    --- Diff: aria/modeling/service_instance.py ---
    @@ -0,0 +1,1553 @@
    +# 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.
    +
    +# pylint: disable=too-many-lines, no-self-argument, no-member, abstract-method
    +
    +from sqlalchemy import (
    +    Column,
    +    Text,
    +    Integer
    +)
    +from sqlalchemy import DateTime
    +from sqlalchemy.ext.associationproxy import association_proxy
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from .mixins import InstanceModelMixin
    +from ..parser import validation
    +from ..parser.consumption import ConsumptionContext
    +from ..utils import collections, formatting, console
    +from . import (
    +    relationships,
    +    utils,
    +    types as modeling_types
    +)
    +
    +
    +class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
    +    """
    +    A service is usually an instance of a :class:`ServiceTemplate`.
    +
    +    You will usually not create it programmatically, but instead instantiate it from a service
    +    template.
    +
    +    :ivar name: Name (unique for this ARIA installation)
    +    :vartype name: basestring
    +    :ivar service_template: Template from which this service was instantiated (optional)
    +    :vartype service_template: :class:`ServiceTemplate`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar meta_data: Custom annotations
    +    :vartype meta_data: {basestring: :class:`Metadata`}
    +    :ivar node: Nodes
    +    :vartype node: {basestring: :class:`Node`}
    +    :ivar groups: Groups of nodes
    +    :vartype groups: {basestring: :class:`Group`}
    +    :ivar policies: Policies
    +    :vartype policies: {basestring: :class:`Policy`]}
    +    :ivar substitution: The entire service can appear as a node
    +    :vartype substitution: :class:`Substitution`
    +    :ivar inputs: Externally provided parameters
    +    :vartype inputs: {basestring: :class:`Parameter`}
    +    :ivar outputs: These parameters are filled in after service installation
    +    :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 required to be installed
    +    :vartype plugin_specifications: {basestring: :class:`PluginSpecification`}
    +    :ivar created_at: Creation timestamp
    +    :vartype created_at: :class:`datetime.datetime`
    +    :ivar updated_at: Update timestamp
    +    :vartype updated_at: :class:`datetime.datetime`
    +
    +    :ivar permalink: ??
    +    :vartype permalink: basestring
    +    :ivar scaling_groups: ??
    +    :vartype scaling_groups: {}
    +
    +    :ivar modifications: Modifications of this service
    +    :vartype modifications: [:class:`ServiceModification`]
    +    :ivar updates: Updates of this service
    +    :vartype updates: [:class:`ServiceUpdate`]
    +    :ivar executions: Executions on this service
    +    :vartype executions: [:class:`Execution`]
    +    """
    +
    +    __tablename__ = 'service'
    +
    +    @declared_attr
    +    def service_template(cls):
    +        return relationships.many_to_one(cls, 'service_template')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def meta_data(cls):
    +        # Warning! We cannot use the attr name "metadata" because it's used by SQLAlchemy!
    +        return relationships.many_to_many(cls, 'metadata', dict_key='name')
    +
    +    @declared_attr
    +    def nodes(cls):
    +        return relationships.one_to_many(cls, 'node', dict_key='name')
    +
    +    @declared_attr
    +    def groups(cls):
    +        return relationships.one_to_many(cls, 'group', dict_key='name')
    +
    +    @declared_attr
    +    def policies(cls):
    +        return relationships.one_to_many(cls, 'policy', dict_key='name')
    +
    +    @declared_attr
    +    def substitution(cls):
    +        return relationships.one_to_one(cls, 'substitution')
    +
    +    @declared_attr
    +    def inputs(cls):
    +        return relationships.many_to_many(cls, 'parameter', prefix='inputs', dict_key='name')
    +
    +    @declared_attr
    +    def outputs(cls):
    +        return relationships.many_to_many(cls, 'parameter', prefix='outputs', dict_key='name')
    +
    +    @declared_attr
    +    def workflows(cls):
    +        return relationships.one_to_many(cls, 'operation', dict_key='name')
    +
    +    @declared_attr
    +    def plugin_specifications(cls):
    +        return relationships.many_to_many(cls, 'plugin_specification')
    +
    +    created_at = Column(DateTime, nullable=False, index=True)
    +    updated_at = Column(DateTime)
    +
    +    # region orchestration
    +
    +    permalink = Column(Text)
    +    scaling_groups = Column(modeling_types.Dict)
    +
    +    # endregion
    +
    +    # region foreign keys
    +
    +    @declared_attr
    +    def substitution_fk(cls):
    +        """Service one-to-one to Substitution"""
    +        return relationships.fk('substitution', nullable=True)
    +
    +    @declared_attr
    +    def service_template_fk(cls):
    +        """For Service many-to-one to ServiceTemplate"""
    +        return relationships.fk('service_template', nullable=True)
    +
    +    # endregion
    +
    +    # region association proxies
    +
    +    @declared_attr
    +    def service_template_name(cls):
    +        """Required for use by SQLAlchemy queries"""
    +        return association_proxy('service_template', 'name')
    +
    +    # endregion
    +
    +    def satisfy_requirements(self):
    +        satisfied = True
    +        for node in self.nodes.itervalues():
    +            if not node.satisfy_requirements():
    +                satisfied = False
    +        return satisfied
    +
    +    def validate_capabilities(self):
    +        satisfied = True
    +        for node in self.nodes.itervalues():
    +            if not node.validate_capabilities():
    +                satisfied = False
    +        return satisfied
    +
    +    def is_node_a_target(self, target_node):
    +        for node in self.nodes.itervalues():
    +            if self._is_node_a_target(node, target_node):
    +                return True
    +        return False
    +
    +    def _is_node_a_target(self, source_node, target_node):
    +        if source_node.outbound_relationships:
    +            for relationship in source_node.outbound_relationships:
    +                if relationship.target_node.name == target_node.name:
    +                    return True
    +                else:
    +                    node = relationship.target_node
    +                    if node is not None:
    +                        if self._is_node_a_target(node, target_node):
    +                            return True
    +        return False
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('description', self.description),
    +            ('metadata', formatting.as_raw_dict(self.meta_data)),
    +            ('nodes', formatting.as_raw_list(self.nodes)),
    +            ('groups', formatting.as_raw_list(self.groups)),
    +            ('policies', formatting.as_raw_list(self.policies)),
    +            ('substitution', formatting.as_raw(self.substitution)),
    +            ('inputs', formatting.as_raw_dict(self.inputs)),
    +            ('outputs', formatting.as_raw_dict(self.outputs)),
    +            ('workflows', formatting.as_raw_list(self.workflows))))
    +
    +    def validate(self):
    +        utils.validate_dict_values(self.meta_data)
    +        utils.validate_dict_values(self.nodes)
    +        utils.validate_dict_values(self.groups)
    +        utils.validate_dict_values(self.policies)
    +        if self.substitution is not None:
    +            self.substitution.validate()
    +        utils.validate_dict_values(self.inputs)
    +        utils.validate_dict_values(self.outputs)
    +        utils.validate_dict_values(self.workflows)
    +
    +    def coerce_values(self, container, report_issues):
    +        utils.coerce_dict_values(container, self.meta_data, report_issues)
    +        utils.coerce_dict_values(container, self.nodes, report_issues)
    +        utils.coerce_dict_values(container, self.groups, report_issues)
    +        utils.coerce_dict_values(container, self.policies, report_issues)
    +        if self.substitution is not None:
    +            self.substitution.coerce_values(container, report_issues)
    +        utils.coerce_dict_values(container, self.inputs, report_issues)
    +        utils.coerce_dict_values(container, self.outputs, report_issues)
    +        utils.coerce_dict_values(container, self.workflows, report_issues)
    +
    +    def dump(self):
    +        context = ConsumptionContext.get_thread_local()
    +        if self.description is not None:
    +            console.puts(context.style.meta(self.description))
    +        utils.dump_dict_values(self.meta_data, 'Metadata')
    +        for node in self.nodes.itervalues():
    +            node.dump()
    +        for group in self.groups.itervalues():
    +            group.dump()
    +        for policy in self.policies.itervalues():
    +            policy.dump()
    +        if self.substitution is not None:
    +            self.substitution.dump()
    +        utils.dump_dict_values(self.inputs, 'Inputs')
    +        utils.dump_dict_values(self.outputs, 'Outputs')
    +        utils.dump_dict_values(self.workflows, 'Workflows')
    +
    +    def dump_graph(self):
    +        for node in self.nodes.itervalues():
    +            if not self.is_node_a_target(node):
    +                self._dump_graph_node(node)
    +
    +    def _dump_graph_node(self, node):
    +        context = ConsumptionContext.get_thread_local()
    +        console.puts(context.style.node(node.name))
    +        if node.outbound_relationships:
    +            with context.style.indent:
    +                for relationship in node.outbound_relationships:
    +                    if relationship.relationship_template is not None:
    +                        relationship_name = context.style.node(
    +                            relationship.relationship_template.name)
    +                    elif relationship.type is not None:
    +                        relationship_name = context.style.type(relationship.type.name)
    +                    else:
    +                        relationship_name = '?'
    +                    if relationship.target_capability is not None:
    +                        capability_name = context.style.node(relationship.target_capability.name)
    +                    else:
    +                        capability_name = None
    +                    if capability_name is not None:
    +                        console.puts('-> {0} {1}'.format(relationship_name, capability_name))
    +                    else:
    +                        console.puts('-> {0}'.format(relationship_name))
    +                    target_node = relationship.target_node
    +                    with console.indent(3):
    +                        self._dump_graph_node(target_node)
    +
    +    __private_fields__ = ['substitution_fk',
    +                          'service_template_fk',
    +                          'service_template_name']
    +
    +
    +class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
    +    """
    +    Usually an instance of a :class:`NodeTemplate`.
    +
    +    Nodes may have zero or more :class:`Relationship` instances to other nodes.
    +
    +    :ivar name: Name (unique for this service)
    +    :vartype name: basestring
    +    :ivar node_template: Template from which this node was instantiated (optional)
    +    :vartype node_template: :class:`NodeTemplate`
    +    :ivar type: Node type
    +    :vartype type: :class:`Type`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar properties: Associated parameters
    +    :vartype properties: {basestring: :class:`Parameter`}
    +    :ivar interfaces: Bundles of operations
    +    :vartype interfaces: {basestring: :class:`Interface`}
    +    :ivar artifacts: Associated files
    +    :vartype artifacts: {basestring: :class:`Artifact`}
    +    :ivar capabilities: Exposed capabilities
    +    :vartype capabilities: {basestring: :class:`Capability`}
    +    :ivar outbound_relationships: Relationships to other nodes
    +    :vartype outbound_relationships: [:class:`Relationship`]
    +    :ivar inbound_relationships: Relationships from other nodes
    +    :vartype inbound_relationships: [:class:`Relationship`]
    +    :ivar plugin_specifications: Plugins required to be installed on the node's host
    +    :vartype plugin_specifications: {basestring: :class:`PluginSpecification`}
    +    :ivar host: Host node (can be self)
    +    :vartype host: :class:`Node`
    +
    +    :ivar runtime_properties: TODO: should be replaced with attributes
    +    :vartype runtime_properties: {}
    +    :ivar scaling_groups: ??
    +    :vartype scaling_groups: []
    +    :ivar state: ??
    +    :vartype state: basestring
    +    :ivar version: Used by `aria.storage.instrumentation`
    +    :vartype version: int
    +
    +    :ivar service: Containing service
    +    :vartype service: :class:`Service`
    +    :ivar groups: We are a member of these groups
    +    :vartype groups: [:class:`Group`]
    +    :ivar policies: Policies enacted on this node
    +    :vartype policies: [:class:`Policy`]
    +    :ivar substitution_mapping: Our contribution to service substitution
    +    :vartype substitution_mapping: :class:`SubstitutionMapping`
    +    :ivar tasks: Tasks on this node
    +    :vartype tasks: [:class:`Task`]
    +    """
    +
    +    __tablename__ = 'node'
    +
    +    @declared_attr
    +    def node_template(cls):
    +        return relationships.many_to_one(cls, 'node_template')
    +
    +    @declared_attr
    +    def type(cls):
    +        return relationships.many_to_one(cls, 'type')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def properties(cls):
    +        return relationships.many_to_many(cls, 'parameter', prefix='properties', dict_key='name')
    +
    +    @declared_attr
    +    def interfaces(cls):
    +        return relationships.one_to_many(cls, 'interface', dict_key='name')
    +
    +    @declared_attr
    +    def artifacts(cls):
    +        return relationships.one_to_many(cls, 'artifact', dict_key='name')
    +
    +    @declared_attr
    +    def capabilities(cls):
    +        return relationships.one_to_many(cls, 'capability', dict_key='name')
    +
    +    @declared_attr
    +    def outbound_relationships(cls):
    +        return relationships.one_to_many(cls, 'relationship', child_fk='source_node_fk',
    +                                         child_property='source_node')
    +
    +    @declared_attr
    +    def inbound_relationships(cls):
    +        return relationships.one_to_many(cls, 'relationship', child_fk='target_node_fk',
    +                                         child_property='target_node')
    +
    +    @declared_attr
    +    def plugin_specifications(cls):
    +        return relationships.many_to_many(cls, 'plugin_specification', dict_key='name')
    +
    +    @declared_attr
    +    def host(cls):
    +        return relationships.one_to_one_self(cls, 'host_fk')
    +
    +    # region orchestration
    +
    +    runtime_properties = Column(modeling_types.Dict)
    +    scaling_groups = Column(modeling_types.List)
    +    state = Column(Text, nullable=False)
    +    version = Column(Integer, default=1)
    +
    +    __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
    +        return None
    +
    +    # endregion
    +
    +    # region foreign_keys
    +
    +    @declared_attr
    +    def type_fk(cls):
    +        """For Node many-to-one to Type"""
    +        return relationships.fk('type')
    +
    +    @declared_attr
    +    def host_fk(cls):
    +        """For Node one-to-one to Node"""
    +        return relationships.fk('node', nullable=True)
    +
    +    @declared_attr
    +    def service_fk(cls):
    +        """For Service one-to-many to Node"""
    +        return relationships.fk('service')
    +
    +    @declared_attr
    +    def node_template_fk(cls):
    +        """For Node many-to-one to NodeTemplate"""
    +        return relationships.fk('node_template', nullable=True)
    +
    +    # endregion
    +
    +    # region association proxies
    +
    +    @declared_attr
    +    def service_name(cls):
    +        """Required for use by SQLAlchemy queries"""
    +        return association_proxy('service', 'name')
    +
    +    # endregion
    +
    +    def satisfy_requirements(self):
    +        node_template = self.node_template
    +        satisfied = True
    +        for requirement_template in node_template.requirement_templates:
    +            # Find target template
    +            target_node_template, target_node_capability = \
    +                requirement_template.find_target(node_template)
    +            if target_node_template is not None:
    +                satisfied = self._satisfy_capability(target_node_capability,
    +                                                     target_node_template,
    +                                                     requirement_template)
    +            else:
    +                context = ConsumptionContext.get_thread_local()
    +                context.validation.report('requirement "{0}" of node "{1}" has no target node '
    +                                          'template'.format(requirement_template.name, self.name),
    +                                          level=validation.Issue.BETWEEN_INSTANCES)
    +                satisfied = False
    +        return satisfied
    +
    +    def _satisfy_capability(self, target_node_capability, target_node_template,
    +                            requirement_template):
    +        from . import models
    +        context = ConsumptionContext.get_thread_local()
    +        # Find target nodes
    +        target_nodes = target_node_template.nodes.all()
    +        if target_nodes:
    +            target_node = None
    +            target_capability = None
    +
    +            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():
    +                        target_node = node
    +                        break
    +            else:
    +                # Use first target node
    +                target_node = target_nodes[0]
    +
    +            if target_node is not None:
    +                if requirement_template.relationship_template is not None:
    +                    relationship = \
    +                        requirement_template.relationship_template.instantiate(self)
    +                else:
    +                    relationship = models.Relationship(target_capability=target_capability)
    +                relationship.name = requirement_template.name
    +                relationship.requirement_template = requirement_template
    +                relationship.target_node = target_node
    +                self.outbound_relationships.append(relationship)
    +                return True
    +            else:
    +                context.validation.report('requirement "{0}" of node "{1}" targets node '
    +                                          'template "{2}" but its instantiated nodes do not '
    +                                          'have enough capacity'.format(
    +                                              requirement_template.name,
    +                                              self.name,
    +                                              target_node_template.name),
    +                                          level=validation.Issue.BETWEEN_INSTANCES)
    +                return False
    +        else:
    +            context.validation.report('requirement "{0}" of node "{1}" targets node template '
    +                                      '"{2}" but it has no instantiated nodes'.format(
    +                                          requirement_template.name,
    +                                          self.name,
    +                                          target_node_template.name),
    +                                      level=validation.Issue.BETWEEN_INSTANCES)
    +            return False
    +
    +    def validate_capabilities(self):
    +        context = ConsumptionContext.get_thread_local()
    +        satisfied = False
    +        for capability in self.capabilities.itervalues():
    +            if not capability.has_enough_relationships:
    +                context.validation.report('capability "{0}" of node "{1}" requires at least {2:d} '
    +                                          'relationships but has {3:d}'.format(
    +                                              capability.name,
    +                                              self.name,
    +                                              capability.min_occurrences,
    +                                              capability.occurrences),
    +                                          level=validation.Issue.BETWEEN_INSTANCES)
    +                satisfied = False
    +        return satisfied
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('name', self.name),
    +            ('type_name', self.type.name),
    +            ('properties', formatting.as_raw_dict(self.properties)),
    +            ('interfaces', formatting.as_raw_list(self.interfaces)),
    +            ('artifacts', formatting.as_raw_list(self.artifacts)),
    +            ('capabilities', formatting.as_raw_list(self.capabilities)),
    +            ('relationships', formatting.as_raw_list(self.outbound_relationships))))
    +
    +    def validate(self):
    +        context = ConsumptionContext.get_thread_local()
    +        if len(self.name) > context.modeling.id_max_length:
    +            context.validation.report('"{0}" has an ID longer than the limit of {1:d} characters: '
    +                                      '{2:d}'.format(
    +                                          self.name,
    +                                          context.modeling.id_max_length,
    +                                          len(self.name)),
    +                                      level=validation.Issue.BETWEEN_INSTANCES)
    +
    +        # TODO: validate that node template is of type?
    +
    +        utils.validate_dict_values(self.properties)
    +        utils.validate_dict_values(self.interfaces)
    +        utils.validate_dict_values(self.artifacts)
    +        utils.validate_dict_values(self.capabilities)
    +        utils.validate_list_values(self.outbound_relationships)
    +
    +    def coerce_values(self, container, report_issues):
    +        utils.coerce_dict_values(self, self.properties, report_issues)
    +        utils.coerce_dict_values(self, self.interfaces, report_issues)
    +        utils.coerce_dict_values(self, self.artifacts, report_issues)
    +        utils.coerce_dict_values(self, self.capabilities, report_issues)
    +        utils.coerce_list_values(self, self.outbound_relationships, report_issues)
    +
    +    def dump(self):
    +        context = ConsumptionContext.get_thread_local()
    +        console.puts('Node: {0}'.format(context.style.node(self.name)))
    +        with context.style.indent:
    +            console.puts('Type: {0}'.format(context.style.type(self.type.name)))
    +            console.puts('Template: {0}'.format(context.style.node(self.node_template.name)))
    +            utils.dump_dict_values(self.properties, 'Properties')
    +            utils.dump_interfaces(self.interfaces)
    +            utils.dump_dict_values(self.artifacts, 'Artifacts')
    +            utils.dump_dict_values(self.capabilities, 'Capabilities')
    +            utils.dump_list_values(self.outbound_relationships, 'Relationships')
    +
    +    __private_fields__ = ['type_fk',
    +                          'host_fk',
    +                          'service_fk',
    +                          'node_template_fk',
    +                          'service_name']
    +
    +class GroupBase(InstanceModelMixin):
    +    """
    +    Usually an instance of a :class:`GroupTemplate`.
    +
    +    :ivar name: Name (unique for this service)
    +    :vartype name: basestring
    +    :ivar group_template: Template from which this group was instantiated (optional)
    +    :vartype group_template: :class:`GroupTemplate`
    +    :ivar type: Group type
    +    :vartype type: :class:`Type`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar nodes: Members of this group
    +    :vartype nodes: [:class:`Node`]
    +    :ivar properties: Associated parameters
    +    :vartype properties: {basestring: :class:`Parameter`}
    +    :ivar interfaces: Bundles of operations
    +    :vartype interfaces: {basestring: :class:`Interface`}
    +
    +    :ivar service: Containing service
    +    :vartype service: :class:`Service`
    +    :ivar policies: Policies enacted on this group
    +    :vartype policies: [:class:`Policy`]
    +    """
    +
    +    __tablename__ = 'group'
    +
    +    @declared_attr
    +    def group_template(cls):
    +        return relationships.many_to_one(cls, 'group_template')
    +
    +    @declared_attr
    +    def type(cls):
    +        return relationships.many_to_one(cls, 'type')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def nodes(cls):
    +        return relationships.many_to_many(cls, 'node')
    +
    +    @declared_attr
    +    def properties(cls):
    +        return relationships.many_to_many(cls, 'parameter', prefix='properties', dict_key='name')
    +
    +    @declared_attr
    +    def interfaces(cls):
    +        return relationships.one_to_many(cls, 'interface', dict_key='name')
    +
    +    # region foreign_keys
    +
    +    @declared_attr
    +    def type_fk(cls):
    +        """For Group many-to-one to Type"""
    +        return relationships.fk('type')
    +
    +    @declared_attr
    +    def service_fk(cls):
    +        """For Service one-to-many to Group"""
    +        return relationships.fk('service')
    +
    +    @declared_attr
    +    def group_template_fk(cls):
    +        """For Group many-to-one to GroupTemplate"""
    +        return relationships.fk('group_template', nullable=True)
    +
    +    # endregion
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('name', self.name),
    +            ('properties', formatting.as_raw_dict(self.properties)),
    +            ('interfaces', formatting.as_raw_list(self.interfaces))))
    +
    +    def validate(self):
    +        utils.validate_dict_values(self.properties)
    +        utils.validate_dict_values(self.interfaces)
    +
    +    def coerce_values(self, container, report_issues):
    +        utils.coerce_dict_values(container, self.properties, report_issues)
    +        utils.coerce_dict_values(container, self.interfaces, report_issues)
    +
    +    def dump(self):
    +        context = ConsumptionContext.get_thread_local()
    +        console.puts('Group: {0}'.format(context.style.node(self.name)))
    +        with context.style.indent:
    +            console.puts('Type: {0}'.format(context.style.type(self.type.name)))
    +            utils.dump_dict_values(self.properties, 'Properties')
    +            utils.dump_interfaces(self.interfaces)
    +            if self.nodes:
    +                console.puts('Member nodes:')
    +                with context.style.indent:
    +                    for node in self.nodes:
    +                        console.puts(context.style.node(node.name))
    +
    +    __private_fields__ = ['type_fk',
    +                          'service_fk',
    +                          'group_template_fk']
    +
    +
    +class PolicyBase(InstanceModelMixin):
    +    """
    +    Usually an instance of a :class:`PolicyTemplate`.
    +
    +    :ivar name: Name (unique for this service)
    +    :vartype name: basestring
    +    :ivar policy_template: Template from which this policy was instantiated (optional)
    +    :vartype policy_template: :class:`PolicyTemplate`
    +    :ivar type: Policy type
    +    :vartype type: :class:`Type`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar nodes: Policy will be enacted on all these nodes
    +    :vartype nodes: [:class:`Node`]
    +    :ivar groups: Policy will be enacted on all nodes in these groups
    +    :vartype groups: [:class:`Group`]
    +    :ivar properties: Associated parameters
    +    :vartype properties: {basestring: :class:`Parameter`}
    +
    +    :ivar service: Containing service
    +    :vartype service: :class:`Service`
    +    """
    +
    +    __tablename__ = 'policy'
    +
    +    @declared_attr
    +    def policy_template(cls):
    +        return relationships.many_to_one(cls, 'policy_template')
    +
    +    @declared_attr
    +    def type(cls):
    +        return relationships.many_to_one(cls, 'type')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def properties(cls):
    +        return relationships.many_to_many(cls, 'parameter', prefix='properties', dict_key='name')
    +
    +    @declared_attr
    +    def nodes(cls):
    +        return relationships.many_to_many(cls, 'node')
    +
    +    @declared_attr
    +    def groups(cls):
    +        return relationships.many_to_many(cls, 'group')
    +
    +    # region foreign_keys
    +
    +    @declared_attr
    +    def type_fk(cls):
    +        """For Policy many-to-one to Type"""
    +        return relationships.fk('type')
    +
    +    @declared_attr
    +    def service_fk(cls):
    +        """For Service one-to-many to Policy"""
    +        return relationships.fk('service')
    +
    +    @declared_attr
    +    def policy_template_fk(cls):
    +        """For Policy many-to-one to PolicyTemplate"""
    +        return relationships.fk('policy_template', nullable=True)
    +
    +    # endregion
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('name', self.name),
    +            ('type_name', self.type.name),
    +            ('properties', formatting.as_raw_dict(self.properties))))
    +
    +    def validate(self):
    +        utils.validate_dict_values(self.properties)
    +
    +    def coerce_values(self, container, report_issues):
    +        utils.coerce_dict_values(container, self.properties, report_issues)
    +
    +    def dump(self):
    +        context = ConsumptionContext.get_thread_local()
    +        console.puts('Policy: {0}'.format(context.style.node(self.name)))
    +        with context.style.indent:
    +            console.puts('Type: {0}'.format(context.style.type(self.type.name)))
    +            utils.dump_dict_values(self.properties, 'Properties')
    +            if self.nodes:
    +                console.puts('Target nodes:')
    +                with context.style.indent:
    +                    for node in self.nodes:
    +                        console.puts(context.style.node(node.name))
    +            if self.groups:
    +                console.puts('Target groups:')
    +                with context.style.indent:
    +                    for group in self.groups:
    +                        console.puts(context.style.node(group.name))
    +
    +    __private_fields__ = ['type_fk',
    +                          'service_fk',
    +                          'policy_template_fk']
    +
    +
    +class SubstitutionBase(InstanceModelMixin):
    +    """
    +    Used to substitute a single node for the entire deployment.
    +
    +    Usually an instance of a :class:`SubstitutionTemplate`.
    +
    +    :ivar substitution_template: Template from which this substitution was instantiated (optional)
    +    :vartype substitution_template: :class:`SubstitutionTemplate`
    +    :ivar node_type: Exposed node type
    +    :vartype node_type: :class:`Type`
    +    :ivar mappings: Requirement and capability mappings
    +    :vartype mappings: {basestring: :class:`SubstitutionTemplate`}
    +
    +    :ivar service: Containing service
    +    :vartype service: :class:`Service`
    +    """
    +
    +    __tablename__ = 'substitution'
    +
    +    @declared_attr
    +    def substitution_template(cls):
    +        return relationships.many_to_one(cls, 'substitution_template')
    +
    +    @declared_attr
    +    def node_type(cls):
    +        return relationships.many_to_one(cls, 'type')
    +
    +    @declared_attr
    +    def mappings(cls):
    +        return relationships.one_to_many(cls, 'substitution_mapping', dict_key='name')
    +
    +    # region foreign_keys
    +
    +    @declared_attr
    +    def node_type_fk(cls):
    +        """For Substitution many-to-one to Type"""
    +        return relationships.fk('type')
    +
    +    @declared_attr
    +    def substitution_template_fk(cls):
    +        """For Substitution many-to-one to SubstitutionTemplate"""
    +        return relationships.fk('substitution_template', nullable=True)
    +
    +    # endregion
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('node_type_name', self.node_type_name),
    +            ('mappings', formatting.as_raw_dict(self.mappings))))
    +
    +    def validate(self):
    +        utils.validate_dict_values(self.mappings)
    +
    +    def coerce_values(self, container, report_issues):
    +        utils.coerce_dict_values(container, self.mappings, report_issues)
    +
    +    def dump(self):
    +        context = ConsumptionContext.get_thread_local()
    +        console.puts('Substitution:')
    +        with context.style.indent:
    +            console.puts('Node type: {0}'.format(context.style.type(self.node_type.name)))
    +            utils.dump_dict_values(self.mappings, 'Mappings')
    +
    +    __private_fields__ = ['node_type_fk',
    +                          'substitution_template_fk']
    +
    +
    +class SubstitutionMappingBase(InstanceModelMixin):
    +    """
    +    Used by :class:`Substitution` to map a capability or a requirement to a node.
    +
    +    Only one of `capability_template` and `requirement_template` can be set.
    +
    +    Usually an instance of a :class:`SubstitutionTemplate`.
    +
    +    :ivar name: Exposed capability or requirement name
    +    :vartype name: basestring
    +    :ivar node: Node
    +    :vartype node: :class:`Node`
    +    :ivar capability: Capability in the node
    +    :vartype capability: :class:`Capability`
    +    :ivar requirement_template: Requirement template in the node template
    +    :vartype requirement_template: :class:`RequirementTemplate`
    +
    +    :ivar substitution: Containing substitution
    +    :vartype substitution: :class:`Substitution`
    +    """
    +
    +    __tablename__ = 'substitution_mapping'
    +
    +    @declared_attr
    +    def node(cls):
    +        return relationships.one_to_one(cls, 'node')
    +
    +    @declared_attr
    +    def capability(cls):
    +        return relationships.one_to_one(cls, 'capability')
    +
    +    @declared_attr
    +    def requirement_template(cls):
    +        return relationships.one_to_one(cls, 'requirement_template')
    +
    +    # region foreign keys
    +
    +    @declared_attr
    +    def substitution_fk(cls):
    +        """For Substitution one-to-many to SubstitutionMapping"""
    +        return relationships.fk('substitution')
    +
    +    @declared_attr
    +    def node_fk(cls):
    +        """For Substitution one-to-one to NodeTemplate"""
    +        return relationships.fk('node')
    +
    +    @declared_attr
    +    def capability_fk(cls):
    +        """For Substitution one-to-one to Capability"""
    +        return relationships.fk('capability', nullable=True)
    +
    +    @declared_attr
    +    def requirement_template_fk(cls):
    +        """For Substitution one-to-one to RequirementTemplate"""
    +        return relationships.fk('requirement_template', nullable=True)
    +
    +    # endregion
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('name', self.name)))
    +
    +    def validate(self):
    +        context = ConsumptionContext.get_thread_local()
    +        if (self.capability is None) and (self.requirement_template is None):
    +            context.validation.report('mapping "{0}" refers to neither capability nor a requirement'
    +                                      ' in node: {1}'.format(
    +                                          self.name,
    +                                          formatting.safe_repr(self.node.name)),
    +                                      level=validation.Issue.BETWEEN_TYPES)
    +
    +    def dump(self):
    +        context = ConsumptionContext.get_thread_local()
    +        console.puts('{0} -> {1}.{2}'.format(
    +            context.style.node(self.name),
    +            context.style.node(self.node.name),
    +            context.style.node(self.capability.name
    +                               if self.capability
    +                               else self.requirement_template.name)))
    +
    +    __private_fields__ = ['substitution_fk',
    +                          'node_fk',
    +                          'capability_fk',
    +                          'requirement_template_fk']
    +
    +
    +class RelationshipBase(InstanceModelMixin):
    +    """
    +    Connects :class:`Node` to a capability in another node.
    +
    +    Might be an instance of a :class:`RelationshipTemplate`.
    +
    +    :ivar name: Name (usually the name of the requirement at the source node template)
    +    :vartype name: basestring
    +    :ivar relationship_template: Template from which this relationship was instantiated (optional)
    +    :vartype relationship_template: :class:`RelationshipTemplate`
    +    :ivar requirement_template: Template from which this relationship was instantiated (optional)
    +    :vartype requirement_template: :class:`RequirementTemplate`
    +    :ivar type: Relationship type
    +    :vartype type: :class:`Type`
    +    :ivar target_capability: Capability at the target node (optional)
    +    :vartype target_capability: :class:`Capability`
    +    :ivar properties: Associated parameters
    +    :vartype properties: {basestring: :class:`Parameter`}
    +    :ivar interfaces: Bundles of operations
    +    :vartype interfaces: {basestring: :class:`Interfaces`}
    +
    +    :ivar source_position: ??
    +    :vartype source_position: int
    +    :ivar target_position: ??
    +    :vartype target_position: int
    +
    +    :ivar source_node: Source node
    +    :vartype source_node: :class:`Node`
    +    :ivar target_node: Target node
    +    :vartype target_node: :class:`Node`
    +    :ivar tasks: Tasks on this node
    +    :vartype tasks: [:class:`Task`]
    +    """
    +
    +    __tablename__ = 'relationship'
    +
    +    @declared_attr
    +    def relationship_template(cls):
    +        return relationships.many_to_one(cls, 'relationship_template')
    +
    +    @declared_attr
    +    def requirement_template(cls):
    +        return relationships.many_to_one(cls, 'requirement_template')
    +
    +    @declared_attr
    +    def type(cls):
    +        return relationships.many_to_one(cls, 'type')
    +
    +    @declared_attr
    +    def target_capability(cls):
    +        return relationships.one_to_one(cls, 'capability')
    +
    +    @declared_attr
    +    def properties(cls):
    +        return relationships.many_to_many(cls, 'parameter', prefix='properties', dict_key='name')
    +
    +    @declared_attr
    +    def interfaces(cls):
    +        return relationships.one_to_many(cls, 'interface', dict_key='name')
    +
    +    # region orchestration
    +
    +    source_position = Column(Integer) # ???
    +    target_position = Column(Integer) # ???
    +
    +    # endregion
    +
    +    # region foreign keys
    +
    +    @declared_attr
    +    def type_fk(cls):
    +        """For Relationship many-to-one to Type"""
    +        return relationships.fk('type', nullable=True)
    +
    +    @declared_attr
    +    def source_node_fk(cls):
    +        """For Node one-to-many to Relationship"""
    +        return relationships.fk('node')
    +
    +    @declared_attr
    +    def target_node_fk(cls):
    +        """For Node one-to-many to Relationship"""
    +        return relationships.fk('node')
    +
    +    @declared_attr
    +    def target_capability_fk(cls):
    +        """For Relationship one-to-one to Capability"""
    +        return relationships.fk('capability', nullable=True)
    +
    +    @declared_attr
    +    def requirement_template_fk(cls):
    +        """For Relationship many-to-one to RequirementTemplate"""
    +        return relationships.fk('requirement_template', nullable=True)
    +
    +    @declared_attr
    +    def relationship_template_fk(cls):
    +        """For Relationship many-to-one to RelationshipTemplate"""
    +        return relationships.fk('relationship_template', nullable=True)
    +
    +    # endregion
    +
    +    # region association proxies
    +
    +    @declared_attr
    +    def source_node_name(cls):
    +        """Required for use by SQLAlchemy queries"""
    +        return association_proxy('source_node', 'name')
    +
    +    @declared_attr
    +    def target_node_name(cls):
    +        """Required for use by SQLAlchemy queries"""
    +        return association_proxy('target_node', 'name')
    +
    +    # endregion
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('name', self.name),
    +            ('target_node_id', self.target_node.name),
    +            ('type_name', self.type.name
    +             if self.type is not None else None),
    +            ('template_name', self.relationship_template.name
    +             if self.relationship_template is not None else None),
    +            ('properties', formatting.as_raw_dict(self.properties)),
    +            ('interfaces', formatting.as_raw_list(self.interfaces))))
    +
    +    def validate(self):
    +        utils.validate_dict_values(self.properties)
    +        utils.validate_dict_values(self.interfaces)
    +
    +    def coerce_values(self, container, report_issues):
    +        utils.coerce_dict_values(container, self.properties, report_issues)
    +        utils.coerce_dict_values(container, self.interfaces, report_issues)
    +
    +    def dump(self):
    +        context = ConsumptionContext.get_thread_local()
    +        if self.name:
    +            console.puts('{0} ->'.format(context.style.node(self.name)))
    +        else:
    +            console.puts('->')
    +        with context.style.indent:
    +            console.puts('Node: {0}'.format(context.style.node(self.target_node.name)))
    +            if self.target_capability:
    +                console.puts('Capability: {0}'.format(context.style.node(
    +                    self.target_capability.name)))
    +            if self.type is not None:
    +                console.puts('Relationship type: {0}'.format(context.style.type(self.type.name)))
    +            if (self.relationship_template is not None) and self.relationship_template.name:
    +                console.puts('Relationship template: {0}'.format(
    +                    context.style.node(self.relationship_template.name)))
    +            utils.dump_dict_values(self.properties, 'Properties')
    +            utils.dump_interfaces(self.interfaces, 'Interfaces')
    +
    +    __private_fields__ = ['type_fk',
    --- End diff --
    
    I think that any __x__ attribute should be on top (as if we are declaring attributes for sqlalcehmy...)


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---


[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104891757
  
    --- Diff: aria/orchestrator/workflows/api/task.py ---
    @@ -69,21 +73,38 @@ def __init__(self,
                      ignore_failure=None,
                      inputs=None,
                      plugin=None,
    -                 runs_on=None):
    +                 runs_on=None,
    +                 dry=False):
             """
             Creates an operation task using the name, details, node instance and any additional kwargs.
    -        :param name: the operation of the name.
    +
    +        :param name: the name of the operation.
             :param actor: the operation host on which this operation is registered.
             :param inputs: operation inputs.
             """
    -        assert isinstance(actor, (model.Node,
    -                                  model.Relationship))
    +
    +        assert isinstance(actor, (models.Node, models.Relationship))
             super(OperationTask, self).__init__()
    +
    +        if dry:
    +            from ..dry import convert_to_dry
    +            plugin, implementation, inputs = convert_to_dry(plugin, implementation, inputs)
    +
    +        # Coerce inputs
    +        if inputs is None:
    +            inputs = {}
    +        else:
    +            for k, v in inputs.iteritems():
    +                if not isinstance(v, models.Parameter):
    +                    inputs[k] = models.Parameter(name=k,
    +                                                 type_name=full_type_name(v),
    +                                                 value=v)
    +
    +        self.name = name
             self.actor = actor
    -        self.name = '{name}.{actor.id}'.format(name=name, actor=actor)
    --- End diff --
    
    why was this changed?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104932505
  
    --- Diff: aria/modeling/service.py ---
    @@ -0,0 +1,1529 @@
    +# 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.
    +
    +# pylint: disable=no-self-argument, no-member, abstract-method
    +
    +from sqlalchemy import (
    +    Column,
    +    Text,
    +    Integer
    +)
    +from sqlalchemy import DateTime
    +from sqlalchemy.ext.associationproxy import association_proxy
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from .bases import InstanceModelMixin
    +from ..parser import validation
    +from ..utils import collections, formatting, console
    +
    +from . import (
    +    utils,
    +    types as modeling_types
    +)
    +
    +
    +class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
    +    """
    +    A service is usually an instance of a :class:`ServiceTemplate`.
    +
    +    You will usually not create it programmatically, but instead instantiate it from a service
    +    template.
    +
    +    :ivar name: Name (unique for this ARIA installation)
    +    :vartype name: basestring
    +    :ivar service_template: Template from which this service was instantiated (optional)
    +    :vartype service_template: :class:`ServiceTemplate`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar meta_data: Custom annotations
    +    :vartype meta_data: {basestring: :class:`Metadata`}
    +    :ivar node: Nodes
    +    :vartype node: [:class:`Node`]
    +    :ivar groups: Groups of nodes
    +    :vartype groups: [:class:`Group`]
    +    :ivar policies: Policies
    +    :vartype policies: [:class:`Policy`]
    +    :ivar substitution: The entire service can appear as a node
    +    :vartype substitution: :class:`Substitution`
    +    :ivar inputs: Externally provided parameters
    +    :vartype inputs: {basestring: :class:`Parameter`}
    +    :ivar outputs: These parameters are filled in after service installation
    +    :vartype outputs: {basestring: :class:`Parameter`}
    +    :ivar operations: Custom operations that can be performed on the service
    +    :vartype operations: {basestring: :class:`Operation`}
    +    :ivar plugins: Plugins required to be installed
    +    :vartype plugins: {basestring: :class:`Plugin`}
    +    :ivar created_at: Creation timestamp
    +    :vartype created_at: :class:`datetime.datetime`
    +    :ivar updated_at: Update timestamp
    +    :vartype updated_at: :class:`datetime.datetime`
    +
    +    :ivar permalink: ??
    +    :vartype permalink: basestring
    +    :ivar scaling_groups: ??
    +    :vartype scaling_groups: {}
    +
    +    :ivar modifications: Modifications of this service
    +    :vartype modifications: [:class:`ServiceModification`]
    +    :ivar updates: Updates of this service
    +    :vartype updates: [:class:`ServiceUpdate`]
    +    :ivar executions: Executions on this service
    +    :vartype executions: [:class:`Execution`]
    +    """
    +
    +    __tablename__ = 'service'
    +
    +    @declared_attr
    +    def service_template(cls):
    +        return cls.many_to_one_relationship('service_template')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def meta_data(cls):
    +        # Warning! We cannot use the attr name "metadata" because it's used by SqlAlchemy!
    +        return cls.many_to_many_relationship('metadata', dict_key='name')
    +
    +    @declared_attr
    +    def nodes(cls):
    +        return cls.one_to_many_relationship('node')
    +
    +    @declared_attr
    +    def groups(cls):
    +        return cls.one_to_many_relationship('group')
    +
    +    @declared_attr
    +    def policies(cls):
    +        return cls.one_to_many_relationship('policy')
    +
    +    @declared_attr
    +    def substitution(cls):
    +        return cls.one_to_one_relationship('substitution')
    +
    +    @declared_attr
    +    def inputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def outputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='outputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def operations(cls):
    +        return cls.one_to_many_relationship('operation', dict_key='name')
    +
    +    @declared_attr
    +    def plugins(cls):
    +        return cls.many_to_many_relationship('plugin')
    +
    +    created_at = Column(DateTime, nullable=False, index=True)
    +    updated_at = Column(DateTime)
    +
    +    # region orchestration
    +
    +    permalink = Column(Text)
    +    scaling_groups = Column(modeling_types.Dict)
    +
    +    # endregion
    +
    +    # region foreign keys
    +
    +    __private_fields__ = ['substituion_fk',
    +                          'service_template_fk']
    +
    +    # Service one-to-one to Substitution
    +    @declared_attr
    +    def substitution_fk(cls):
    +        return cls.foreign_key('substitution', nullable=True)
    +
    +    # Service many-to-one to ServiceTemplate
    +    @declared_attr
    +    def service_template_fk(cls):
    +        return cls.foreign_key('service_template', nullable=True)
    +
    +    # endregion
    +
    +    def satisfy_requirements(self, context):
    +        satisfied = True
    +        for node in self.nodes:
    +            if not node.satisfy_requirements(context):
    +                satisfied = False
    +        return satisfied
    +
    +    def validate_capabilities(self, context):
    +        satisfied = True
    +        for node in self.nodes:
    +            if not node.validate_capabilities(context):
    +                satisfied = False
    +        return satisfied
    +
    +    def find_nodes(self, node_template_name):
    +        nodes = []
    +        for node in self.nodes:
    +            if node.node_template.name == node_template_name:
    +                nodes.append(node)
    +        return collections.FrozenList(nodes)
    +
    +    def get_node_ids(self, node_template_name):
    --- End diff --
    
    ditto


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106804848
  
    --- Diff: aria/orchestrator/workflows/api/task.py ---
    @@ -92,144 +93,133 @@ def __init__(self,
                                    if ignore_failure is None else ignore_failure)
             self.runs_on = runs_on
     
    -    @classmethod
    -    def _merge_inputs(cls, operation_inputs, additional_inputs=None):
    -        final_inputs = dict((p.name, p.as_raw['value']) for p in operation_inputs)
    -        final_inputs.update(additional_inputs or {})
    -        return final_inputs
    +        # Wrap inputs
    +        if inputs:
    +            for k, v in inputs.iteritems():
    +                if not isinstance(v, models.Parameter):
    +                    inputs[k] = models.Parameter.wrap(k, v)
    +
    +        # TODO: These extra inputs should likely be stored as a separate entry in the task model,
    +        # because they are different from the operation inputs. The two kinds of inputs should also
    +        # not be merged.
    +
    +        if interface_name or operation_name:
    +            operation = OperationTask._get_operation(actor.interfaces, interface_name,
    +                                                     operation_name)
    +            if operation is None:
    +                raise exceptions.TaskException(
    +                    'Could not find operation "{0}" on interface "{1}" for {2} "{3}"'
    +                    .format(operation_name, 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.TaskException(
    +                        'Could not find plugin of operation "{0}" on interface "{1}" for {2} "{3}"'
    +                        .format(operation_name, 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=interface_name,
    +                                                         operation=operation_name)
    +        else:
    +            self.name = name
    +            self.implementation = implementation
    +            self.inputs = inputs or {}
    +            self.plugin = None
     
         @classmethod
    -    def node(cls, instance, name, inputs=None, *args, **kwargs):
    +    def for_node(cls,
    +                 node,
    +                 interface_name,
    +                 operation_name,
    +                 max_attempts=None,
    +                 retry_interval=None,
    +                 ignore_failure=None,
    +                 inputs=None):
             """
    -        Represents a node based operation
    +        Creates an operation on a node.
     
    -        :param instance: the node of which this operation belongs to.
    -        :param name: the name of the operation.
    +        :param node: the node of which this operation belongs to.
    +        :param interface_name: the name of the interface.
    +        :param operation_name: the name of the operation.
    +        :param inputs: any additional inputs to the operation
             """
    -        assert isinstance(instance, model.Node)
    -        interface_name = _get_interface_name(name)
    -        interfaces = instance.interfaces.filter_by(name=interface_name)
    -        if interfaces.count() > 1:
    -            raise exceptions.TaskException(
    -                "More than one interface with the same name `{0}` found".format(name)
    -            )
    -        elif interfaces.count() == 0:
    -            raise exceptions.TaskException(
    -                "No Interface with the name `{interface_name}` found".format(
    -                    interface_name=interface_name)
    -            )
    -
    -        operation_templates = interfaces[0].operations.filter_by(name=name)
    -        if operation_templates.count() > 1:
    -            raise exceptions.TaskException(
    -                "More than one operation with the same name `{0}` were found".format(name)
    -            )
    -
    -        elif operation_templates.count() == 0:
    -            raise exceptions.TaskException(
    -                "No interface with the name `{operation_name}` found".format(
    -                    operation_name=name)
    -            )
    -
    -        return cls._instance(
    -            instance=instance,
    -            name=name,
    -            operation_template=operation_templates[0],
    -            plugins=instance.plugins or [],
    -            runs_on=model.Task.RUNS_ON_NODE_INSTANCE,
    -            inputs=cls._merge_inputs(operation_templates[0].inputs, inputs),
    -            *args,
    -            **kwargs)
    +
    +        assert isinstance(node, models.Node)
    +        return cls(
    +            actor=node,
    +            actor_type='node',
    +            interface_name=interface_name,
    +            operation_name=operation_name,
    +            max_attempts=max_attempts,
    +            retry_interval=retry_interval,
    +            ignore_failure=ignore_failure,
    +            inputs=inputs,
    +            runs_on=models.Task.RUNS_ON_NODE)
     
         @classmethod
    -    def relationship(cls, instance, name, edge, runs_on=None, inputs=None, *args,
    -                     **kwargs):
    +    def for_relationship(cls,
    +                         relationship,
    +                         interface_name,
    +                         operation_name,
    +                         max_attempts=None,
    +                         retry_interval=None,
    +                         ignore_failure=None,
    +                         inputs=None,
    +                         runs_on=models.Task.RUNS_ON_SOURCE):
             """
    -        Represents a relationship based operation
    +        Creates an operation on a relationship edge.
     
    -        :param instance: the relationship of which this operation belongs to.
    -        :param name: the name of the operation.
    -        :param edge: the edge of the interface ("source" or "target").
    -        :param runs_on: where to run the operation ("source" or "target"); if None defaults to the
    -                        interface edge.
    -        :param inputs any additional inputs to the operation
    +        :param relationship: the relationship of which this operation belongs to.
    +        :param interface_name: the name of the interface.
    +        :param operation_name: the name of the operation.
    +        :param inputs: any additional inputs to the operation
    +        :param runs_on: where to run the operation ("source" or "target"); defaults to "source"
             """
    -        assert isinstance(instance, model.Relationship)
    -        interface_name = _get_interface_name(name)
    -        interfaces = instance.interfaces.filter_by(name=interface_name, edge=edge)
    -        count = interfaces.count()
    -        if count > 1:
    -            raise exceptions.TaskException(
    -                "More than one interface with the same name `{interface_name}` found at `{edge}`"
    -                + " edge".format(
    -                    interface_name=interface_name, edge=edge)
    -            )
    -        elif count == 0:
    -            raise exceptions.TaskException(
    -                "No interface with the name `{interface_name}` found at `{edge}` edge".format(
    -                    interface_name=interface_name, edge=edge)
    -            )
    -
    -        operations = interfaces.all()[0].operations.filter_by(name=name)
    -        count = operations.count()
    -        if count > 1:
    -            raise exceptions.TaskException(
    -                "More than one operation with the same name `{0}` found".format(name)
    -            )
    -        elif count == 0:
    -            raise exceptions.TaskException(
    -                "No operation with the name `{operation_name}` found".format(
    -                    operation_name=name)
    -            )
    -
    -        if not runs_on:
    -            if edge == cls.SOURCE_OPERATION:
    -                runs_on = model.Task.RUNS_ON_SOURCE
    -            else:
    -                runs_on = model.Task.RUNS_ON_TARGET
    -
    -        if runs_on == model.Task.RUNS_ON_SOURCE:
    -            plugins = instance.source_node.plugins
    -        else:
    -            plugins = instance.target_node.plugins
    -
    -        return cls._instance(instance=instance,
    -                             name=name,
    -                             operation_template=operations[0],
    -                             plugins=plugins or [],
    -                             runs_on=runs_on,
    -                             inputs=cls._merge_inputs(operations[0].inputs, inputs),
    -                             *args,
    -                             **kwargs)
     
    -    @classmethod
    -    def _instance(cls,
    -                  instance,
    -                  name,
    -                  operation_template,
    -                  inputs,
    -                  plugins,
    -                  runs_on,
    -                  *args,
    -                  **kwargs):
    -        matching_plugins = [p for p in plugins if p['name'] == operation_template.plugin]
    -        # All matching plugins should have identical package_name/package_version, so it's safe to
    -        # take the first found.
    -        plugin = matching_plugins[0] if matching_plugins else {}
    -        return cls(actor=instance,
    -                   name=name,
    -                   implementation=operation_template.implementation,
    -                   inputs=inputs,
    -                   plugin=plugin,
    -                   runs_on=runs_on,
    -                   *args,
    -                   **kwargs)
    +        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,
    +            max_attempts=max_attempts,
    +            retry_interval=retry_interval,
    +            ignore_failure=ignore_failure,
    +            inputs=inputs,
    +            runs_on=runs_on)
    +
    +    @staticmethod
    +    def _get_operation(interfaces, interface_name, operation_name):
    +        interface = interfaces.get(interface_name)
    --- End diff --
    
    why do i need to pass interfaces when it's always actor.interfaces? (which would make this a simple method, and not a static one).


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106806283
  
    --- Diff: tests/modeling/test_models.py ---
    @@ -173,92 +168,83 @@ def _test_model(is_valid, storage, model_cls, model_kwargs):
             getattr(storage, model_cls.__modelname__).put(model)
             return model
         else:
    -        with pytest.raises((exceptions.StorageError, TypeError),):
    +        with pytest.raises((ValueFormatException, StorageError, TypeError),):
                 getattr(storage, model_cls.__modelname__).put(model_cls(**model_kwargs))
     
     
     class TestServiceTemplate(object):
     
         @pytest.mark.parametrize(
    -        'is_valid, plan, description, created_at, updated_at, main_file_name',
    +        'is_valid, description, created_at, updated_at, main_file_name',
             [
    -            (False, None, 'description', now, now, '/path'),
    -            (False, {}, {}, now, now, '/path'),
    -            (False, {}, 'description', 'error', now, '/path'),
    -            (False, {}, 'description', now, 'error', '/path'),
    -            (False, {}, 'description', now, now, {}),
    -            (True, {}, 'description', now, now, '/path'),
    +            (True, 'description', now, now, '/path'),
    --- End diff --
    
    group False and True together?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104313519
  
    --- Diff: aria/orchestrator/workflows/api/task.py ---
    @@ -69,21 +70,38 @@ def __init__(self,
                      ignore_failure=None,
                      inputs=None,
                      plugin=None,
    -                 runs_on=None):
    +                 runs_on=None,
    +                 dry=False):
             """
             Creates an operation task using the name, details, node instance and any additional kwargs.
    -        :param name: the operation of the name.
    +
    +        :param name: the name of the operation.
             :param actor: the operation host on which this operation is registered.
             :param inputs: operation inputs.
             """
    -        assert isinstance(actor, (model.Node,
    -                                  model.Relationship))
    +
    +        assert isinstance(actor, (models.Node, models.Relationship))
             super(OperationTask, self).__init__()
    +
    +        if dry:
    +            from ..dry import convert_to_dry
    +            plugin, implementation, inputs = convert_to_dry(plugin, implementation, inputs)
    +
    +        # Coerce inputs
    +        if inputs is None:
    +            inputs = {}
    +        else:
    +            for k, v in inputs.iteritems():
    --- End diff --
    
    python3?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r105640732
  
    --- Diff: aria/modeling/bases.py ---
    @@ -360,7 +361,7 @@ def __repr__(self):
     
     class ModelIDMixin(object):
         id = Column(Integer, primary_key=True, autoincrement=True)
    -    name = Column(Text, nullable=True, index=True)
    +    name = Column(Text, index=True)
    --- End diff --
    
    why?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106946082
  
    --- Diff: aria/modeling/orchestration.py ---
    @@ -0,0 +1,350 @@
    +# 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.
    +
    +"""
    +classes:
    +    * Execution - execution implementation model.
    +    * Plugin - plugin implementation model.
    +    * Task - a task
    +"""
    +
    +# pylint: disable=no-self-argument, no-member, abstract-method
    +
    +from datetime import datetime
    +
    +from sqlalchemy import (
    +    Column,
    +    Integer,
    +    Text,
    +    DateTime,
    +    Boolean,
    +    Enum,
    +    String,
    +    Float,
    +    orm,
    +)
    +from sqlalchemy.ext.associationproxy import association_proxy
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from ..orchestrator.exceptions import (TaskAbortException, TaskRetryException)
    +from .types import (List, Dict)
    +from .mixins import ModelMixin
    +from . import relationships
    +
    +
    +class ExecutionBase(ModelMixin):
    +    """
    +    Execution model representation.
    +    """
    +
    +    __tablename__ = 'execution'
    +
    +    TERMINATED = 'terminated'
    +    FAILED = 'failed'
    +    CANCELLED = 'cancelled'
    +    PENDING = 'pending'
    +    STARTED = 'started'
    +    CANCELLING = 'cancelling'
    +    FORCE_CANCELLING = 'force_cancelling'
    +
    +    STATES = [TERMINATED, FAILED, CANCELLED, PENDING, STARTED, CANCELLING, FORCE_CANCELLING]
    +    END_STATES = [TERMINATED, FAILED, CANCELLED]
    +    ACTIVE_STATES = [state for state in STATES if state not in END_STATES]
    +
    +    VALID_TRANSITIONS = {
    +        PENDING: [STARTED, CANCELLED],
    +        STARTED: END_STATES + [CANCELLING],
    +        CANCELLING: END_STATES + [FORCE_CANCELLING]
    +    }
    +
    +    @orm.validates('status')
    +    def validate_status(self, key, value):
    +        """Validation function that verifies execution status transitions are OK"""
    +        try:
    +            current_status = getattr(self, key)
    +        except AttributeError:
    +            return
    +        valid_transitions = self.VALID_TRANSITIONS.get(current_status, [])
    +        if all([current_status is not None,
    +                current_status != value,
    +                value not in valid_transitions]):
    +            raise ValueError('Cannot change execution status from {current} to {new}'.format(
    +                current=current_status,
    +                new=value))
    +        return value
    +
    +    created_at = Column(DateTime, index=True)
    +    started_at = Column(DateTime, nullable=True, index=True)
    +    ended_at = Column(DateTime, nullable=True, index=True)
    +    error = Column(Text, nullable=True)
    +    is_system_workflow = Column(Boolean, nullable=False, default=False)
    +    parameters = Column(Dict)
    +    status = Column(Enum(*STATES, name='execution_status'), default=PENDING)
    +    workflow_name = Column(Text)
    +
    +    @declared_attr
    +    def service(cls):
    +        return relationships.many_to_one(cls, 'service')
    +
    +    # region foreign keys
    +
    +    @declared_attr
    +    def service_fk(cls):
    +        return relationships.fk('service')
    +
    +    # endregion
    +
    +    # region association proxies
    +
    +    @declared_attr
    +    def service_name(cls):
    +        """Required for use by SQLAlchemy queries"""
    +        return association_proxy('service', cls.name_column_name())
    +
    +    @declared_attr
    +    def service_template(cls):
    +        """Required for use by SQLAlchemy queries"""
    +        return association_proxy('service', 'service_template')
    +
    +    @declared_attr
    +    def service_template_name(cls):
    +        """Required for use by SQLAlchemy queries"""
    +        return association_proxy('service', 'service_template_name')
    +
    +    # endregion
    +
    +    def __str__(self):
    +        return '<{0} id=`{1}` (status={2})>'.format(
    +            self.__class__.__name__,
    +            getattr(self, self.name_column_name()),
    +            self.status
    +        )
    +
    +    __private_fields__ = ['service_fk',
    +                          'service_name',
    +                          'service_template',
    +                          'service_template_name']
    +
    +
    +class PluginBase(ModelMixin):
    +    """
    +    Plugin model representation.
    +    """
    +
    +    __tablename__ = 'plugin'
    +
    +    archive_name = Column(Text, nullable=False, index=True)
    +    distribution = Column(Text)
    +    distribution_release = Column(Text)
    +    distribution_version = Column(Text)
    +    package_name = Column(Text, nullable=False, index=True)
    +    package_source = Column(Text)
    +    package_version = Column(Text)
    +    supported_platform = Column(Text)
    +    supported_py_versions = Column(List)
    +    uploaded_at = Column(DateTime, nullable=False, index=True)
    +    wheels = Column(List, nullable=False)
    +
    +
    +class TaskBase(ModelMixin):
    +    """
    +    A Model which represents an task
    +    """
    +
    +    __tablename__ = 'task'
    +
    +    PENDING = 'pending'
    +    RETRYING = 'retrying'
    +    SENT = 'sent'
    +    STARTED = 'started'
    +    SUCCESS = 'success'
    +    FAILED = 'failed'
    +    STATES = (
    +        PENDING,
    +        RETRYING,
    +        SENT,
    +        STARTED,
    +        SUCCESS,
    +        FAILED,
    +    )
    +
    +    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
    +    def node(cls):
    +        return relationships.many_to_one(cls, 'node')
    +
    +    @declared_attr
    +    def relationship(cls):
    +        return relationships.many_to_one(cls, 'relationship')
    +
    +    @declared_attr
    +    def plugin(cls):
    +        return relationships.many_to_one(cls, 'plugin')
    +
    +    @declared_attr
    +    def execution(cls):
    +        return relationships.many_to_one(cls, 'execution')
    +
    +    @declared_attr
    +    def inputs(cls):
    +        return relationships.many_to_many(cls, 'parameter', prefix='inputs', dict_key='name')
    +
    +    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 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
    +
    +    @property
    +    def actor(self):
    +        """
    +        Return the actor of the task
    +        :return:
    +        """
    +        return self.node or self.relationship
    +
    +    @orm.validates('max_attempts')
    +    def validate_max_attempts(self, _, value):                                  # pylint: disable=no-self-use
    +        """Validates that max attempts is either -1 or a positive number"""
    +        if value < 1 and value != TaskBase.INFINITE_RETRIES:
    +            raise ValueError('Max attempts can be either -1 (infinite) or any positive number. '
    +                             'Got {value}'.format(value=value))
    +        return value
    +
    +    # region foreign keys
    +
    +    @declared_attr
    +    def node_fk(cls):
    +        return relationships.fk('node', nullable=True)
    --- End diff --
    
    Will change


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106802861
  
    --- Diff: aria/modeling/service_instance.py ---
    @@ -0,0 +1,1553 @@
    +# 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.
    +
    +# pylint: disable=too-many-lines, no-self-argument, no-member, abstract-method
    +
    +from sqlalchemy import (
    +    Column,
    +    Text,
    +    Integer
    +)
    +from sqlalchemy import DateTime
    +from sqlalchemy.ext.associationproxy import association_proxy
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from .mixins import InstanceModelMixin
    +from ..parser import validation
    +from ..parser.consumption import ConsumptionContext
    +from ..utils import collections, formatting, console
    +from . import (
    +    relationships,
    +    utils,
    +    types as modeling_types
    +)
    +
    +
    +class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
    +    """
    +    A service is usually an instance of a :class:`ServiceTemplate`.
    +
    +    You will usually not create it programmatically, but instead instantiate it from a service
    +    template.
    +
    +    :ivar name: Name (unique for this ARIA installation)
    +    :vartype name: basestring
    +    :ivar service_template: Template from which this service was instantiated (optional)
    +    :vartype service_template: :class:`ServiceTemplate`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar meta_data: Custom annotations
    +    :vartype meta_data: {basestring: :class:`Metadata`}
    +    :ivar node: Nodes
    +    :vartype node: {basestring: :class:`Node`}
    +    :ivar groups: Groups of nodes
    +    :vartype groups: {basestring: :class:`Group`}
    +    :ivar policies: Policies
    +    :vartype policies: {basestring: :class:`Policy`]}
    +    :ivar substitution: The entire service can appear as a node
    +    :vartype substitution: :class:`Substitution`
    +    :ivar inputs: Externally provided parameters
    +    :vartype inputs: {basestring: :class:`Parameter`}
    +    :ivar outputs: These parameters are filled in after service installation
    +    :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 required to be installed
    +    :vartype plugin_specifications: {basestring: :class:`PluginSpecification`}
    +    :ivar created_at: Creation timestamp
    +    :vartype created_at: :class:`datetime.datetime`
    +    :ivar updated_at: Update timestamp
    +    :vartype updated_at: :class:`datetime.datetime`
    +
    +    :ivar permalink: ??
    +    :vartype permalink: basestring
    +    :ivar scaling_groups: ??
    +    :vartype scaling_groups: {}
    +
    +    :ivar modifications: Modifications of this service
    +    :vartype modifications: [:class:`ServiceModification`]
    +    :ivar updates: Updates of this service
    +    :vartype updates: [:class:`ServiceUpdate`]
    +    :ivar executions: Executions on this service
    +    :vartype executions: [:class:`Execution`]
    +    """
    +
    +    __tablename__ = 'service'
    +
    +    @declared_attr
    +    def service_template(cls):
    +        return relationships.many_to_one(cls, 'service_template')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def meta_data(cls):
    +        # Warning! We cannot use the attr name "metadata" because it's used by SQLAlchemy!
    +        return relationships.many_to_many(cls, 'metadata', dict_key='name')
    --- End diff --
    
    what name does the metadata have?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r105642533
  
    --- Diff: aria/modeling/service_template.py ---
    @@ -97,6 +97,34 @@ def outputs(cls):
         def operation_templates(cls):
             return cls.one_to_many_relationship('operation_template', dict_key='name')
     
    +    @declared_attr
    +    def node_types(cls):
    +        return cls.one_to_one_relationship('type', key='node_type_fk', backreference='')
    --- End diff --
    
    this solution looks weird. Having to pass blank string in order not to have backref...


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106943474
  
    --- Diff: aria/modeling/service_instance.py ---
    @@ -0,0 +1,1553 @@
    +# 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.
    +
    +# pylint: disable=too-many-lines, no-self-argument, no-member, abstract-method
    +
    +from sqlalchemy import (
    +    Column,
    +    Text,
    +    Integer
    +)
    +from sqlalchemy import DateTime
    +from sqlalchemy.ext.associationproxy import association_proxy
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from .mixins import InstanceModelMixin
    +from ..parser import validation
    +from ..parser.consumption import ConsumptionContext
    +from ..utils import collections, formatting, console
    +from . import (
    +    relationships,
    +    utils,
    +    types as modeling_types
    +)
    +
    +
    +class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
    +    """
    +    A service is usually an instance of a :class:`ServiceTemplate`.
    +
    +    You will usually not create it programmatically, but instead instantiate it from a service
    +    template.
    +
    +    :ivar name: Name (unique for this ARIA installation)
    +    :vartype name: basestring
    +    :ivar service_template: Template from which this service was instantiated (optional)
    +    :vartype service_template: :class:`ServiceTemplate`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar meta_data: Custom annotations
    +    :vartype meta_data: {basestring: :class:`Metadata`}
    +    :ivar node: Nodes
    +    :vartype node: {basestring: :class:`Node`}
    +    :ivar groups: Groups of nodes
    +    :vartype groups: {basestring: :class:`Group`}
    +    :ivar policies: Policies
    +    :vartype policies: {basestring: :class:`Policy`]}
    +    :ivar substitution: The entire service can appear as a node
    +    :vartype substitution: :class:`Substitution`
    +    :ivar inputs: Externally provided parameters
    +    :vartype inputs: {basestring: :class:`Parameter`}
    +    :ivar outputs: These parameters are filled in after service installation
    +    :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 required to be installed
    +    :vartype plugin_specifications: {basestring: :class:`PluginSpecification`}
    +    :ivar created_at: Creation timestamp
    +    :vartype created_at: :class:`datetime.datetime`
    +    :ivar updated_at: Update timestamp
    +    :vartype updated_at: :class:`datetime.datetime`
    +
    +    :ivar permalink: ??
    +    :vartype permalink: basestring
    +    :ivar scaling_groups: ??
    +    :vartype scaling_groups: {}
    +
    +    :ivar modifications: Modifications of this service
    +    :vartype modifications: [:class:`ServiceModification`]
    +    :ivar updates: Updates of this service
    +    :vartype updates: [:class:`ServiceUpdate`]
    +    :ivar executions: Executions on this service
    +    :vartype executions: [:class:`Execution`]
    +    """
    +
    +    __tablename__ = 'service'
    +
    +    @declared_attr
    +    def service_template(cls):
    +        return relationships.many_to_one(cls, 'service_template')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def meta_data(cls):
    +        # Warning! We cannot use the attr name "metadata" because it's used by SQLAlchemy!
    +        return relationships.many_to_many(cls, 'metadata', dict_key='name')
    --- End diff --
    
    `meta_data`. Not sure I understand the question?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106806483
  
    --- Diff: tests/orchestrator/workflows/executor/test_executor.py ---
    @@ -43,7 +43,9 @@ def test_execute(executor):
         expected_value = 'value'
         successful_task = MockTask(mock_successful_task)
         failing_task = MockTask(mock_failing_task)
    -    task_with_inputs = MockTask(mock_task_with_input, inputs=dict(input='value'))
    +    #task_with_inputs = MockTask(mock_task_with_input, inputs=dict(input='value'))
    --- End diff --
    
    leftovers 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104515579
  
    --- Diff: aria/modeling/utils.py ---
    @@ -120,27 +102,52 @@ def dump_dict_values(context, the_dict, name):
         dump_list_values(context, the_dict.itervalues(), name)
     
     
    -def dump_parameters(context, parameters, name='Properties'):
    -    if not parameters:
    -        return
    -    puts('%s:' % name)
    -    with context.style.indent:
    -        for parameter_name, parameter in parameters.iteritems():
    -            if parameter.type_name is not None:
    -                puts('%s = %s (%s)' % (context.style.property(parameter_name),
    -                                       context.style.literal(parameter.value),
    -                                       context.style.type(parameter.type_name)))
    -            else:
    -                puts('%s = %s' % (context.style.property(parameter_name),
    -                                  context.style.literal(parameter.value)))
    -            if parameter.description:
    -                puts(context.style.meta(parameter.description))
    -
    -
     def dump_interfaces(context, interfaces, name='Interfaces'):
         if not interfaces:
             return
         puts('%s:' % name)
         with context.style.indent:
             for interface in interfaces.itervalues():
                 interface.dump(context)
    +
    +
    +
    +
    +def deepcopy_with_locators(value):
    +    """
    +    Like :code:`deepcopy`, but also copies over locators.
    +    """
    +
    +    res = deepcopy(value)
    +    copy_locators(res, value)
    +    return res
    +
    +
    +def copy_locators(target, source):
    +    """
    +    Copies over :code:`_locator` for all elements, recursively.
    +
    +    Assumes that target and source have exactly the same list/dict structure.
    +    """
    +
    +    locator = getattr(source, '_locator', None)
    --- End diff --
    
    _locator shouldn't be handled in this part of the code


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104961747
  
    --- Diff: aria/modeling/service_template.py ---
    @@ -0,0 +1,1693 @@
    +# Licensed to the Apache Software Foundation (ASF) under one or more
    --- End diff --
    
    How about templates.py and instances.py?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104932693
  
    --- Diff: aria/modeling/service.py ---
    @@ -0,0 +1,1529 @@
    +# 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.
    +
    +# pylint: disable=no-self-argument, no-member, abstract-method
    +
    +from sqlalchemy import (
    +    Column,
    +    Text,
    +    Integer
    +)
    +from sqlalchemy import DateTime
    +from sqlalchemy.ext.associationproxy import association_proxy
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from .bases import InstanceModelMixin
    +from ..parser import validation
    +from ..utils import collections, formatting, console
    +
    +from . import (
    +    utils,
    +    types as modeling_types
    +)
    +
    +
    +class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
    +    """
    +    A service is usually an instance of a :class:`ServiceTemplate`.
    +
    +    You will usually not create it programmatically, but instead instantiate it from a service
    +    template.
    +
    +    :ivar name: Name (unique for this ARIA installation)
    +    :vartype name: basestring
    +    :ivar service_template: Template from which this service was instantiated (optional)
    +    :vartype service_template: :class:`ServiceTemplate`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar meta_data: Custom annotations
    +    :vartype meta_data: {basestring: :class:`Metadata`}
    +    :ivar node: Nodes
    +    :vartype node: [:class:`Node`]
    +    :ivar groups: Groups of nodes
    +    :vartype groups: [:class:`Group`]
    +    :ivar policies: Policies
    +    :vartype policies: [:class:`Policy`]
    +    :ivar substitution: The entire service can appear as a node
    +    :vartype substitution: :class:`Substitution`
    +    :ivar inputs: Externally provided parameters
    +    :vartype inputs: {basestring: :class:`Parameter`}
    +    :ivar outputs: These parameters are filled in after service installation
    +    :vartype outputs: {basestring: :class:`Parameter`}
    +    :ivar operations: Custom operations that can be performed on the service
    +    :vartype operations: {basestring: :class:`Operation`}
    +    :ivar plugins: Plugins required to be installed
    +    :vartype plugins: {basestring: :class:`Plugin`}
    +    :ivar created_at: Creation timestamp
    +    :vartype created_at: :class:`datetime.datetime`
    +    :ivar updated_at: Update timestamp
    +    :vartype updated_at: :class:`datetime.datetime`
    +
    +    :ivar permalink: ??
    +    :vartype permalink: basestring
    +    :ivar scaling_groups: ??
    +    :vartype scaling_groups: {}
    +
    +    :ivar modifications: Modifications of this service
    +    :vartype modifications: [:class:`ServiceModification`]
    +    :ivar updates: Updates of this service
    +    :vartype updates: [:class:`ServiceUpdate`]
    +    :ivar executions: Executions on this service
    +    :vartype executions: [:class:`Execution`]
    +    """
    +
    +    __tablename__ = 'service'
    +
    +    @declared_attr
    +    def service_template(cls):
    +        return cls.many_to_one_relationship('service_template')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def meta_data(cls):
    +        # Warning! We cannot use the attr name "metadata" because it's used by SqlAlchemy!
    +        return cls.many_to_many_relationship('metadata', dict_key='name')
    +
    +    @declared_attr
    +    def nodes(cls):
    +        return cls.one_to_many_relationship('node')
    +
    +    @declared_attr
    +    def groups(cls):
    +        return cls.one_to_many_relationship('group')
    +
    +    @declared_attr
    +    def policies(cls):
    +        return cls.one_to_many_relationship('policy')
    +
    +    @declared_attr
    +    def substitution(cls):
    +        return cls.one_to_one_relationship('substitution')
    +
    +    @declared_attr
    +    def inputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def outputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='outputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def operations(cls):
    +        return cls.one_to_many_relationship('operation', dict_key='name')
    +
    +    @declared_attr
    +    def plugins(cls):
    +        return cls.many_to_many_relationship('plugin')
    +
    +    created_at = Column(DateTime, nullable=False, index=True)
    +    updated_at = Column(DateTime)
    +
    +    # region orchestration
    +
    +    permalink = Column(Text)
    +    scaling_groups = Column(modeling_types.Dict)
    +
    +    # endregion
    +
    +    # region foreign keys
    +
    +    __private_fields__ = ['substituion_fk',
    +                          'service_template_fk']
    +
    +    # Service one-to-one to Substitution
    +    @declared_attr
    +    def substitution_fk(cls):
    +        return cls.foreign_key('substitution', nullable=True)
    +
    +    # Service many-to-one to ServiceTemplate
    +    @declared_attr
    +    def service_template_fk(cls):
    +        return cls.foreign_key('service_template', nullable=True)
    +
    +    # endregion
    +
    +    def satisfy_requirements(self, context):
    +        satisfied = True
    +        for node in self.nodes:
    +            if not node.satisfy_requirements(context):
    +                satisfied = False
    +        return satisfied
    +
    +    def validate_capabilities(self, context):
    +        satisfied = True
    +        for node in self.nodes:
    +            if not node.validate_capabilities(context):
    +                satisfied = False
    +        return satisfied
    +
    +    def find_nodes(self, node_template_name):
    +        nodes = []
    +        for node in self.nodes:
    +            if node.node_template.name == node_template_name:
    +                nodes.append(node)
    +        return collections.FrozenList(nodes)
    +
    +    def get_node_ids(self, node_template_name):
    +        return collections.FrozenList((node.name for node in self.find_nodes(node_template_name)))
    +
    +    def find_groups(self, group_template_name):
    +        groups = []
    +        for group in self.groups:
    +            if group.template_name == group_template_name:
    +                groups.append(group)
    +        return collections.FrozenList(groups)
    +
    +    def get_group_ids(self, group_template_name):
    +        return collections.FrozenList((group.name
    +                                       for group in self.find_groups(group_template_name)))
    +
    +    def is_node_a_target(self, context, target_node):
    --- End diff --
    
    everynode which is a target has inbound_relationships. these methods are probably not relavent anymore


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106947691
  
    --- Diff: aria/modeling/__init__.py ---
    @@ -0,0 +1,48 @@
    +# Licensed to the Apache Software Foundation (ASF) under one or more
    --- End diff --
    
    These methods have almost no use on their own. They are utility methods that are exactly only useful for the complete instantiation of the service template. I suggest we wait until we have an instantiation (or however we will call it) module.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106804972
  
    --- Diff: aria/orchestrator/workflows/builtin/utils.py ---
    @@ -51,14 +56,14 @@ def create_node_task_dependencies(graph, tasks_and_nodes, reverse=False):
     
         def get_task(node_id):
    --- End diff --
    
    passing node_id and comparing it to node.name? doesn't make sense...


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r105633635
  
    --- Diff: aria/modeling/service_changes.py ---
    @@ -0,0 +1,219 @@
    +# Licensed to the Apache Software Foundation (ASF) under one or more
    --- End diff --
    
    why create this..?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106943383
  
    --- Diff: tests/modeling/test_models.py ---
    @@ -486,83 +472,79 @@ def test_deployment_update_step_order(self):
             assert not add_node < None
     
     
    -class TestDeploymentModification(object):
    +class TestServiceModification(object):
         @pytest.mark.parametrize(
             'is_valid, context, created_at, ended_at, modified_node_templates, nodes, status',
             [
    -            (False, m_cls, now, now, {}, {}, ServiceInstanceModification.STARTED),
    -            (False, {}, m_cls, now, {}, {}, ServiceInstanceModification.STARTED),
    -            (False, {}, now, m_cls, {}, {}, ServiceInstanceModification.STARTED),
    -            (False, {}, now, now, m_cls, {}, ServiceInstanceModification.STARTED),
    -            (False, {}, now, now, {}, m_cls, ServiceInstanceModification.STARTED),
    +            (False, m_cls, now, now, {}, {}, ServiceModification.STARTED),
    +            (False, {}, m_cls, now, {}, {}, ServiceModification.STARTED),
    +            (False, {}, now, m_cls, {}, {}, ServiceModification.STARTED),
    +            (False, {}, now, now, m_cls, {}, ServiceModification.STARTED),
    +            (False, {}, now, now, {}, m_cls, ServiceModification.STARTED),
                 (False, {}, now, now, {}, {}, m_cls),
     
    -            (True, {}, now, now, {}, {}, ServiceInstanceModification.STARTED),
    -            (True, {}, now, None, {}, {}, ServiceInstanceModification.STARTED),
    -            (True, {}, now, now, None, {}, ServiceInstanceModification.STARTED),
    -            (True, {}, now, now, {}, None, ServiceInstanceModification.STARTED),
    +            (True, {}, now, now, {}, {}, ServiceModification.STARTED),
    +            (True, {}, now, None, {}, {}, ServiceModification.STARTED),
    +            (True, {}, now, now, None, {}, ServiceModification.STARTED),
    +            (True, {}, now, now, {}, None, ServiceModification.STARTED),
             ]
         )
    -    def test_deployment_modification_model_creation(
    -            self, service_instance_storage, is_valid, context, created_at, ended_at,
    -            modified_node_templates, nodes, status):
    -        deployment_modification = _test_model(
    +    def test_service_modification_model_creation(self, service_storage, is_valid, context,
    +                                                 created_at, ended_at, modified_node_templates,
    +                                                 nodes, status):
    +        service_modification = _test_model(
                 is_valid=is_valid,
    -            storage=service_instance_storage,
    -            model_cls=ServiceInstanceModification,
    +            storage=service_storage,
    +            model_cls=ServiceModification,
                 model_kwargs=dict(
    -                service_instance=service_instance_storage.service_instance.list()[0],
    +                service=service_storage.service.list()[0],
                     context=context,
                     created_at=created_at,
                     ended_at=ended_at,
    -                modified_nodes=modified_node_templates,
    -                node_instances=nodes,
    +                modified_node_templates=modified_node_templates,
    +                nodes=nodes,
                     status=status,
                 ))
             if is_valid:
    -            assert deployment_modification.service_instance == \
    -                   service_instance_storage.service_instance.list()[0]
    +            assert service_modification.service == \
    +                   service_storage.service.list()[0]
     
     
     class TestNodeTemplate(object):
         @pytest.mark.parametrize(
    -        'is_valid, name, default_instances, max_instances, min_instances, plugins, properties, '
    -        'type_name, type_hierarchy',
    +        'is_valid, name, default_instances, max_instances, min_instances, plugin_specifications, '
    +        'properties',
             [
    -            (False, m_cls, 1, 1, 1, [], [], 'type', []),
    -            (False, 'name', m_cls, 1, 1, [], [], 'type', []),
    -            (False, 'name', 1, m_cls, 1, [], [], 'type', []),
    -            (False, 'name', 1, 1, m_cls, [], [], 'type', []),
    -            (False, 'name', 1, 1, 1, m_cls, [], 'type', []),
    -            (False, 'name', 1, 1, 1, [], [], m_cls, []),
    -            (False, 'name', 1, 1, 1, [], [], 'type', m_cls),
    -            #
    -            (True, 'name', 1, 1, 1, [], [], 'type', []),
    -            (True, 'name', 1, 1, 1, None, [], 'type', []),
    -            (True, 'name', 1, 1, 1, [], [], 'type', None),
    +            (False, m_cls, 1, 1, 1, {}, {}),
    +            (False, 'name', m_cls, 1, 1, {}, {}),
    +            (False, 'name', 1, m_cls, 1, {}, {}),
    +            (False, 'name', 1, 1, m_cls, {}, {}),
    +            (False, 'name', 1, 1, 1, m_cls, {}),
    +            (False, 'name', 1, 1, 1, None, {}),
    +
    +            (True, 'name', 1, 1, 1, {}, {}),
    --- End diff --
    
    Since moving so much to foreign keys, there are much fewer raw columns to test now.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104934259
  
    --- Diff: aria/modeling/service_template.py ---
    @@ -0,0 +1,1693 @@
    +# 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.
    +
    +# pylint: disable=too-many-lines, no-self-argument, no-member, abstract-method
    +
    +from __future__ import absolute_import  # so we can import standard 'types'
    +
    +from types import FunctionType
    +from datetime import datetime
    +
    +from sqlalchemy import (
    +    Column,
    +    Text,
    +    Integer,
    +    DateTime
    +)
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from ..parser import validation
    +from ..utils import collections, formatting, console
    +from .bases import TemplateModelMixin
    +from . import (
    +    utils,
    +    types as modeling_types
    +)
    +
    +
    +class ServiceTemplateBase(TemplateModelMixin):
    +    """
    +    A service template is a source for creating :class:`Service` instances.
    +
    +    It is usually created by various DSL parsers, such as ARIA's TOSCA extension. However, it can
    +    also be created programmatically.
    +
    +    :ivar name: Name (unique for this ARIA installation)
    +    :vartype name: basestring
    +    :ivar description: Human-readable description
    +    :vartype description: basestring
    +    :ivar main_file_name: Filename of CSAR or YAML file from which this service template was parsed
    +    :vartype main_file_name: basestring
    +    :ivar meta_data: Custom annotations
    +    :vartype meta_data: {basestring: :class:`Metadata`}
    +    :ivar node_templates: Templates for creating nodes
    +    :vartype node_templates: [:class:`NodeTemplate`]
    +    :ivar group_templates: Templates for creating groups
    +    :vartype group_templates: [:class:`GroupTemplate`]
    +    :ivar policy_templates: Templates for creating policies
    +    :vartype policy_templates: [:class:`PolicyTemplate`]
    +    :ivar substitution_template: The entire service can appear as a node
    +    :vartype substitution_template: :class:`SubstitutionTemplate`
    +    :ivar inputs: Externally provided parameters
    +    :vartype inputs: {basestring: :class:`Parameter`}
    +    :ivar outputs: These parameters are filled in after service installation
    +    :vartype outputs: {basestring: :class:`Parameter`}
    +    :ivar operation_templates: Custom operations that can be performed on the service
    +    :vartype operation_templates: {basestring: :class:`OperationTemplate`}
    +    :ivar plugins: Plugins required by services
    +    :vartype plugins: {basestring: :class:`Plugin`}
    +    :ivar node_types: Base for the node type hierarchy
    +    :vartype node_types: :class:`Type`
    +    :ivar group_types: Base for the group type hierarchy
    +    :vartype group_types: :class:`Type`
    +    :ivar policy_types: Base for the policy type hierarchy
    +    :vartype policy_types: :class:`Type`
    +    :ivar relationship_types: Base for the relationship type hierarchy
    +    :vartype relationship_types: :class:`Type`
    +    :ivar capability_types: Base for the capability type hierarchy
    +    :vartype capability_types: :class:`Type`
    +    :ivar interface_types: Base for the interface type hierarchy
    +    :vartype interface_types: :class:`Type`
    +    :ivar artifact_types: Base for the artifact type hierarchy
    +    :vartype artifact_types: :class:`Type`
    +    :ivar plugins: Plugins required to be installed
    +    :vartype plugins: {basestring: :class:`Plugin`}
    +    :ivar created_at: Creation timestamp
    +    :vartype created_at: :class:`datetime.datetime`
    +    :ivar updated_at: Update timestamp
    +    :vartype updated_at: :class:`datetime.datetime`
    +
    +    :ivar services: Instantiated services
    +    :vartype services: [:class:`Service`]
    +    """
    +
    +    __tablename__ = 'service_template'
    +
    +    description = Column(Text)
    +    main_file_name = Column(Text)
    +
    +    @declared_attr
    +    def meta_data(cls):
    +        # Warning! We cannot use the attr name "metadata" because it's used by SqlAlchemy!
    +        return cls.many_to_many_relationship('metadata', dict_key='name')
    +
    +    @declared_attr
    +    def node_templates(cls):
    +        return cls.one_to_many_relationship('node_template')
    +
    +    @declared_attr
    +    def group_templates(cls):
    +        return cls.one_to_many_relationship('group_template')
    +
    +    @declared_attr
    +    def policy_templates(cls):
    +        return cls.one_to_many_relationship('policy_template')
    +
    +    @declared_attr
    +    def substitution_template(cls):
    +        return cls.one_to_one_relationship('substitution_template')
    +
    +    @declared_attr
    +    def inputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def outputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='outputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def operation_templates(cls):
    +        return cls.one_to_many_relationship('operation_template', dict_key='name')
    +
    +    @declared_attr
    +    def plugins(cls):
    +        return cls.one_to_many_relationship('plugin')
    +
    +    @declared_attr
    +    def node_types(cls):
    +        return cls.one_to_one_relationship('type', key='node_type_fk', backreference='')
    +
    +    @declared_attr
    +    def group_types(cls):
    +        return cls.one_to_one_relationship('type', key='group_type_fk', backreference='')
    +
    +    @declared_attr
    +    def policy_types(cls):
    +        return cls.one_to_one_relationship('type', key='policy_type_fk', backreference='')
    +
    +    @declared_attr
    +    def relationship_types(cls):
    +        return cls.one_to_one_relationship('type', key='relationship_type_fk', backreference='')
    +
    +    @declared_attr
    +    def capability_types(cls):
    +        return cls.one_to_one_relationship('type', key='capability_type_fk', backreference='')
    +
    +    @declared_attr
    +    def interface_types(cls):
    +        return cls.one_to_one_relationship('type', key='interface_type_fk', backreference='')
    +
    +    @declared_attr
    +    def artifact_types(cls):
    +        return cls.one_to_one_relationship('type', key='artifact_type_fk', backreference='')
    +
    +    # region orchestration
    +
    +    created_at = Column(DateTime, nullable=False, index=True)
    +    updated_at = Column(DateTime)
    +
    +    # endregion
    +
    +    # region foreign keys
    +
    +    __private_fields__ = ['substitution_template_fk',
    +                          'node_type_fk',
    +                          'group_type_fk',
    +                          'policy_type_fk',
    +                          'relationship_type_fk',
    +                          'capability_type_fk',
    +                          'interface_type_fk',
    +                          'artifact_type_fk']
    +
    +    # ServiceTemplate one-to-one to SubstitutionTemplate
    +    @declared_attr
    +    def substitution_template_fk(cls):
    +        return cls.foreign_key('substitution_template', nullable=True)
    +
    +    # ServiceTemplate one-to-one to Type
    +    @declared_attr
    +    def node_type_fk(cls):
    +        return cls.foreign_key('type', nullable=True)
    +
    +    # ServiceTemplate one-to-one to Type
    +    @declared_attr
    +    def group_type_fk(cls):
    +        return cls.foreign_key('type', nullable=True)
    +
    +    # ServiceTemplate one-to-one to Type
    +    @declared_attr
    +    def policy_type_fk(cls):
    +        return cls.foreign_key('type', nullable=True)
    +
    +    # ServiceTemplate one-to-one to Type
    +    @declared_attr
    +    def relationship_type_fk(cls):
    +        return cls.foreign_key('type', nullable=True)
    +
    +    # ServiceTemplate one-to-one to Type
    +    @declared_attr
    +    def capability_type_fk(cls):
    +        return cls.foreign_key('type', nullable=True)
    +
    +    # ServiceTemplate one-to-one to Type
    +    @declared_attr
    +    def interface_type_fk(cls):
    +        return cls.foreign_key('type', nullable=True)
    +
    +    # ServiceTemplate one-to-one to Type
    +    @declared_attr
    +    def artifact_type_fk(cls):
    +        return cls.foreign_key('type', nullable=True)
    +
    +    # endregion
    +
    +    def get_node_template(self, node_template_name):
    +        if self.node_templates:
    +            for node_template in self.node_templates:
    +                if node_template.name == node_template_name:
    +                    return node_template
    +        return None
    +
    +    def get_group_template(self, group_template_name):
    +        if self.group_templates:
    +            for group_template in self.group_templates:
    +                if group_template.name == group_template_name:
    +                    return group_template
    +        return None
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('description', self.description),
    +            ('metadata', formatting.as_raw_dict(self.meta_data)),
    +            ('node_templates', formatting.as_raw_list(self.node_templates)),
    +            ('group_templates', formatting.as_raw_list(self.group_templates)),
    +            ('policy_templates', formatting.as_raw_list(self.policy_templates)),
    +            ('substitution_template', formatting.as_raw(self.substitution_template)),
    +            ('inputs', formatting.as_raw_dict(self.inputs)),
    +            ('outputs', formatting.as_raw_dict(self.outputs)),
    +            ('operation_templates', formatting.as_raw_list(self.operation_templates))))
    +
    +    @property
    +    def types_as_raw(self):
    +        return collections.OrderedDict((
    +            ('node_types', formatting.as_raw(self.node_types)),
    +            ('group_types', formatting.as_raw(self.group_types)),
    +            ('policy_types', formatting.as_raw(self.policy_types)),
    +            ('relationship_types', formatting.as_raw(self.relationship_types)),
    +            ('capability_types', formatting.as_raw(self.capability_types)),
    +            ('interface_types', formatting.as_raw(self.interface_types)),
    +            ('artifact_types', formatting.as_raw(self.artifact_types))))
    +
    +    def instantiate(self, context, container):
    +        from . import models
    +        now = datetime.now()
    +        service = models.Service(created_at=now,
    +                                 updated_at=now,
    +                                 description=utils.deepcopy_with_locators(self.description),
    +                                 service_template=self)
    +        #service.name = '{0}_{1}'.format(self.name, service.id)
    +
    +        context.modeling.instance = service
    +
    +        utils.instantiate_dict(context, self, service.meta_data, self.meta_data)
    +
    +        for node_template in self.node_templates:
    +            for _ in range(node_template.default_instances):
    +                node = node_template.instantiate(context, container)
    +                service.nodes.append(node)
    +
    +        utils.instantiate_list(context, self, service.groups, self.group_templates)
    +        utils.instantiate_list(context, self, service.policies, self.policy_templates)
    +        utils.instantiate_dict(context, self, service.operations, self.operation_templates)
    +
    +        if self.substitution_template is not None:
    +            service.substitution = self.substitution_template.instantiate(context, container)
    +
    +        utils.instantiate_dict(context, self, service.inputs, self.inputs)
    +        utils.instantiate_dict(context, self, service.outputs, self.outputs)
    +
    +        for name, the_input in context.modeling.inputs.iteritems():
    +            if name not in service.inputs:
    +                context.validation.report('input "{0}" is not supported'.format(name))
    +            else:
    +                service.inputs[name].value = the_input
    +
    +        return service
    +
    +    def validate(self, context):
    +        utils.validate_dict_values(context, self.meta_data)
    +        utils.validate_list_values(context, self.node_templates)
    +        utils.validate_list_values(context, self.group_templates)
    +        utils.validate_list_values(context, self.policy_templates)
    +        if self.substitution_template is not None:
    +            self.substitution_template.validate(context)
    +        utils.validate_dict_values(context, self.inputs)
    +        utils.validate_dict_values(context, self.outputs)
    +        utils.validate_dict_values(context, self.operation_templates)
    +        if self.node_types is not None:
    +            self.node_types.validate(context)
    +        if self.group_types is not None:
    +            self.group_types.validate(context)
    +        if self.policy_types is not None:
    +            self.policy_types.validate(context)
    +        if self.relationship_types is not None:
    +            self.relationship_types.validate(context)
    +        if self.capability_types is not None:
    +            self.capability_types.validate(context)
    +        if self.interface_types is not None:
    +            self.interface_types.validate(context)
    +        if self.artifact_types is not None:
    +            self.artifact_types.validate(context)
    +
    +    def coerce_values(self, context, container, report_issues):
    +        utils.coerce_dict_values(context, container, self.meta_data, report_issues)
    +        utils.coerce_list_values(context, container, self.node_templates, report_issues)
    +        utils.coerce_list_values(context, container, self.group_templates, report_issues)
    +        utils.coerce_list_values(context, container, self.policy_templates, report_issues)
    +        if self.substitution_template is not None:
    +            self.substitution_template.coerce_values(context, container, report_issues)
    +        utils.coerce_dict_values(context, container, self.inputs, report_issues)
    +        utils.coerce_dict_values(context, container, self.outputs, report_issues)
    +        utils.coerce_dict_values(context, container, self.operation_templates, report_issues)
    +
    +    def dump(self, context):
    +        if self.description is not None:
    +            console.puts(context.style.meta(self.description))
    +        utils.dump_dict_values(context, self.meta_data, 'Metadata')
    +        for node_template in self.node_templates:
    +            node_template.dump(context)
    +        for group_template in self.group_templates:
    +            group_template.dump(context)
    +        for policy_template in self.policy_templates:
    +            policy_template.dump(context)
    +        if self.substitution_template is not None:
    +            self.substitution_template.dump(context)
    +        utils.dump_dict_values(context, self.inputs, 'Inputs')
    +        utils.dump_dict_values(context, self.outputs, 'Outputs')
    +        utils.dump_dict_values(context, self.operation_templates, 'Operation templates')
    +
    +    def dump_types(self, context):
    --- End diff --
    
    this as well


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104891794
  
    --- Diff: aria/orchestrator/workflows/api/task.py ---
    @@ -69,21 +73,38 @@ def __init__(self,
                      ignore_failure=None,
                      inputs=None,
                      plugin=None,
    -                 runs_on=None):
    +                 runs_on=None,
    +                 dry=False):
             """
             Creates an operation task using the name, details, node instance and any additional kwargs.
    -        :param name: the operation of the name.
    +
    +        :param name: the name of the operation.
             :param actor: the operation host on which this operation is registered.
             :param inputs: operation inputs.
             """
    -        assert isinstance(actor, (model.Node,
    -                                  model.Relationship))
    +
    +        assert isinstance(actor, (models.Node, models.Relationship))
             super(OperationTask, self).__init__()
    +
    +        if dry:
    +            from ..dry import convert_to_dry
    +            plugin, implementation, inputs = convert_to_dry(plugin, implementation, inputs)
    +
    +        # Coerce inputs
    +        if inputs is None:
    +            inputs = {}
    +        else:
    +            for k, v in inputs.iteritems():
    +                if not isinstance(v, models.Parameter):
    --- End diff --
    
    im not sure i understand why this check is needed


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104313185
  
    --- Diff: aria/modeling/service_template.py ---
    @@ -0,0 +1,1617 @@
    +# Licensed to the Apache Software Foundation (ASF) under one or more
    --- End diff --
    
    I think it could be nice if `name` would be the default key for dicts.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106803094
  
    --- Diff: aria/modeling/__init__.py ---
    @@ -0,0 +1,48 @@
    +# Licensed to the Apache Software Foundation (ASF) under one or more
    --- End diff --
    
    OK, so the following message actually belong to each module. Currently each models had an instantiate, coerce and validate methods which do a log of different logic. This needs to be tested, on unit test level, since the only other test for it is currently an end to end test (which by itself lacks asserts), and we need to be sure that each and every one of this methods are tested.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104313485
  
    --- Diff: aria/orchestrator/workflows/api/task.py ---
    @@ -69,21 +70,38 @@ def __init__(self,
                      ignore_failure=None,
                      inputs=None,
                      plugin=None,
    -                 runs_on=None):
    +                 runs_on=None,
    +                 dry=False):
             """
             Creates an operation task using the name, details, node instance and any additional kwargs.
    -        :param name: the operation of the name.
    +
    +        :param name: the name of the operation.
             :param actor: the operation host on which this operation is registered.
             :param inputs: operation inputs.
             """
    -        assert isinstance(actor, (model.Node,
    -                                  model.Relationship))
    +
    +        assert isinstance(actor, (models.Node, models.Relationship))
             super(OperationTask, self).__init__()
    +
    +        if dry:
    --- End diff --
    
    why should the task know about whether it's being dry-ran or not?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104934167
  
    --- Diff: aria/modeling/service_template.py ---
    @@ -0,0 +1,1693 @@
    +# 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.
    +
    +# pylint: disable=too-many-lines, no-self-argument, no-member, abstract-method
    +
    +from __future__ import absolute_import  # so we can import standard 'types'
    +
    +from types import FunctionType
    +from datetime import datetime
    +
    +from sqlalchemy import (
    +    Column,
    +    Text,
    +    Integer,
    +    DateTime
    +)
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from ..parser import validation
    +from ..utils import collections, formatting, console
    +from .bases import TemplateModelMixin
    +from . import (
    +    utils,
    +    types as modeling_types
    +)
    +
    +
    +class ServiceTemplateBase(TemplateModelMixin):
    +    """
    +    A service template is a source for creating :class:`Service` instances.
    +
    +    It is usually created by various DSL parsers, such as ARIA's TOSCA extension. However, it can
    +    also be created programmatically.
    +
    +    :ivar name: Name (unique for this ARIA installation)
    +    :vartype name: basestring
    +    :ivar description: Human-readable description
    +    :vartype description: basestring
    +    :ivar main_file_name: Filename of CSAR or YAML file from which this service template was parsed
    +    :vartype main_file_name: basestring
    +    :ivar meta_data: Custom annotations
    +    :vartype meta_data: {basestring: :class:`Metadata`}
    +    :ivar node_templates: Templates for creating nodes
    +    :vartype node_templates: [:class:`NodeTemplate`]
    +    :ivar group_templates: Templates for creating groups
    +    :vartype group_templates: [:class:`GroupTemplate`]
    +    :ivar policy_templates: Templates for creating policies
    +    :vartype policy_templates: [:class:`PolicyTemplate`]
    +    :ivar substitution_template: The entire service can appear as a node
    +    :vartype substitution_template: :class:`SubstitutionTemplate`
    +    :ivar inputs: Externally provided parameters
    +    :vartype inputs: {basestring: :class:`Parameter`}
    +    :ivar outputs: These parameters are filled in after service installation
    +    :vartype outputs: {basestring: :class:`Parameter`}
    +    :ivar operation_templates: Custom operations that can be performed on the service
    +    :vartype operation_templates: {basestring: :class:`OperationTemplate`}
    +    :ivar plugins: Plugins required by services
    +    :vartype plugins: {basestring: :class:`Plugin`}
    +    :ivar node_types: Base for the node type hierarchy
    +    :vartype node_types: :class:`Type`
    +    :ivar group_types: Base for the group type hierarchy
    +    :vartype group_types: :class:`Type`
    +    :ivar policy_types: Base for the policy type hierarchy
    +    :vartype policy_types: :class:`Type`
    +    :ivar relationship_types: Base for the relationship type hierarchy
    +    :vartype relationship_types: :class:`Type`
    +    :ivar capability_types: Base for the capability type hierarchy
    +    :vartype capability_types: :class:`Type`
    +    :ivar interface_types: Base for the interface type hierarchy
    +    :vartype interface_types: :class:`Type`
    +    :ivar artifact_types: Base for the artifact type hierarchy
    +    :vartype artifact_types: :class:`Type`
    +    :ivar plugins: Plugins required to be installed
    +    :vartype plugins: {basestring: :class:`Plugin`}
    +    :ivar created_at: Creation timestamp
    +    :vartype created_at: :class:`datetime.datetime`
    +    :ivar updated_at: Update timestamp
    +    :vartype updated_at: :class:`datetime.datetime`
    +
    +    :ivar services: Instantiated services
    +    :vartype services: [:class:`Service`]
    +    """
    +
    +    __tablename__ = 'service_template'
    +
    +    description = Column(Text)
    +    main_file_name = Column(Text)
    +
    +    @declared_attr
    +    def meta_data(cls):
    +        # Warning! We cannot use the attr name "metadata" because it's used by SqlAlchemy!
    +        return cls.many_to_many_relationship('metadata', dict_key='name')
    +
    +    @declared_attr
    +    def node_templates(cls):
    +        return cls.one_to_many_relationship('node_template')
    +
    +    @declared_attr
    +    def group_templates(cls):
    +        return cls.one_to_many_relationship('group_template')
    +
    +    @declared_attr
    +    def policy_templates(cls):
    +        return cls.one_to_many_relationship('policy_template')
    +
    +    @declared_attr
    +    def substitution_template(cls):
    +        return cls.one_to_one_relationship('substitution_template')
    +
    +    @declared_attr
    +    def inputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def outputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='outputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def operation_templates(cls):
    +        return cls.one_to_many_relationship('operation_template', dict_key='name')
    +
    +    @declared_attr
    +    def plugins(cls):
    +        return cls.one_to_many_relationship('plugin')
    +
    +    @declared_attr
    +    def node_types(cls):
    +        return cls.one_to_one_relationship('type', key='node_type_fk', backreference='')
    +
    +    @declared_attr
    +    def group_types(cls):
    +        return cls.one_to_one_relationship('type', key='group_type_fk', backreference='')
    +
    +    @declared_attr
    +    def policy_types(cls):
    +        return cls.one_to_one_relationship('type', key='policy_type_fk', backreference='')
    +
    +    @declared_attr
    +    def relationship_types(cls):
    +        return cls.one_to_one_relationship('type', key='relationship_type_fk', backreference='')
    +
    +    @declared_attr
    +    def capability_types(cls):
    +        return cls.one_to_one_relationship('type', key='capability_type_fk', backreference='')
    +
    +    @declared_attr
    +    def interface_types(cls):
    +        return cls.one_to_one_relationship('type', key='interface_type_fk', backreference='')
    +
    +    @declared_attr
    +    def artifact_types(cls):
    +        return cls.one_to_one_relationship('type', key='artifact_type_fk', backreference='')
    +
    +    # region orchestration
    +
    +    created_at = Column(DateTime, nullable=False, index=True)
    +    updated_at = Column(DateTime)
    +
    +    # endregion
    +
    +    # region foreign keys
    +
    +    __private_fields__ = ['substitution_template_fk',
    +                          'node_type_fk',
    +                          'group_type_fk',
    +                          'policy_type_fk',
    +                          'relationship_type_fk',
    +                          'capability_type_fk',
    +                          'interface_type_fk',
    +                          'artifact_type_fk']
    +
    +    # ServiceTemplate one-to-one to SubstitutionTemplate
    +    @declared_attr
    +    def substitution_template_fk(cls):
    +        return cls.foreign_key('substitution_template', nullable=True)
    +
    +    # ServiceTemplate one-to-one to Type
    +    @declared_attr
    +    def node_type_fk(cls):
    +        return cls.foreign_key('type', nullable=True)
    +
    +    # ServiceTemplate one-to-one to Type
    +    @declared_attr
    +    def group_type_fk(cls):
    +        return cls.foreign_key('type', nullable=True)
    +
    +    # ServiceTemplate one-to-one to Type
    +    @declared_attr
    +    def policy_type_fk(cls):
    +        return cls.foreign_key('type', nullable=True)
    +
    +    # ServiceTemplate one-to-one to Type
    +    @declared_attr
    +    def relationship_type_fk(cls):
    +        return cls.foreign_key('type', nullable=True)
    +
    +    # ServiceTemplate one-to-one to Type
    +    @declared_attr
    +    def capability_type_fk(cls):
    +        return cls.foreign_key('type', nullable=True)
    +
    +    # ServiceTemplate one-to-one to Type
    +    @declared_attr
    +    def interface_type_fk(cls):
    +        return cls.foreign_key('type', nullable=True)
    +
    +    # ServiceTemplate one-to-one to Type
    +    @declared_attr
    +    def artifact_type_fk(cls):
    +        return cls.foreign_key('type', nullable=True)
    +
    +    # endregion
    +
    +    def get_node_template(self, node_template_name):
    +        if self.node_templates:
    +            for node_template in self.node_templates:
    +                if node_template.name == node_template_name:
    +                    return node_template
    +        return None
    +
    +    def get_group_template(self, group_template_name):
    +        if self.group_templates:
    +            for group_template in self.group_templates:
    +                if group_template.name == group_template_name:
    +                    return group_template
    +        return None
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('description', self.description),
    +            ('metadata', formatting.as_raw_dict(self.meta_data)),
    +            ('node_templates', formatting.as_raw_list(self.node_templates)),
    +            ('group_templates', formatting.as_raw_list(self.group_templates)),
    +            ('policy_templates', formatting.as_raw_list(self.policy_templates)),
    +            ('substitution_template', formatting.as_raw(self.substitution_template)),
    +            ('inputs', formatting.as_raw_dict(self.inputs)),
    +            ('outputs', formatting.as_raw_dict(self.outputs)),
    +            ('operation_templates', formatting.as_raw_list(self.operation_templates))))
    +
    +    @property
    +    def types_as_raw(self):
    --- End diff --
    
    is this really needed?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106802311
  
    --- Diff: aria/modeling/models.py ---
    @@ -0,0 +1,283 @@
    +# 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.
    +
    +# pylint: disable=abstract-method
    +
    +from sqlalchemy.ext.declarative import declarative_base
    +
    +from . import (
    +    service_template,
    +    service_instance,
    +    service_changes,
    +    service_common,
    +    orchestration,
    +    mixins,
    +)
    +
    +
    +aria_declarative_base = declarative_base(cls=mixins.ModelIDMixin)
    +
    +
    +# region service template models
    +
    +class ServiceTemplate(aria_declarative_base, service_template.ServiceTemplateBase):
    +    pass
    +
    +
    +class NodeTemplate(aria_declarative_base, service_template.NodeTemplateBase):
    +    pass
    +
    +
    +class GroupTemplate(aria_declarative_base, service_template.GroupTemplateBase):
    +    pass
    +
    +
    +class PolicyTemplate(aria_declarative_base, service_template.PolicyTemplateBase):
    +    pass
    +
    +
    +class SubstitutionTemplate(aria_declarative_base, service_template.SubstitutionTemplateBase):
    +    pass
    +
    +
    +class SubstitutionTemplateMapping(aria_declarative_base,
    +                                  service_template.SubstitutionTemplateMappingBase):
    +    pass
    +
    +
    +class RequirementTemplate(aria_declarative_base, service_template.RequirementTemplateBase):
    +    pass
    +
    +
    +class RelationshipTemplate(aria_declarative_base, service_template.RelationshipTemplateBase):
    +    pass
    +
    +
    +class CapabilityTemplate(aria_declarative_base, service_template.CapabilityTemplateBase):
    +    pass
    +
    +
    +class InterfaceTemplate(aria_declarative_base, service_template.InterfaceTemplateBase):
    +    pass
    +
    +
    +class OperationTemplate(aria_declarative_base, service_template.OperationTemplateBase):
    +    pass
    +
    +
    +class ArtifactTemplate(aria_declarative_base, service_template.ArtifactTemplateBase):
    +    pass
    +
    +# endregion
    +
    +
    +# region service instance models
    +
    +class Service(aria_declarative_base, service_instance.ServiceBase):
    +    pass
    +
    +
    +class Node(aria_declarative_base, service_instance.NodeBase):
    +    pass
    +
    +
    +class Group(aria_declarative_base, service_instance.GroupBase):
    +    pass
    +
    +
    +class Policy(aria_declarative_base, service_instance.PolicyBase):
    +    pass
    +
    +
    +class Substitution(aria_declarative_base, service_instance.SubstitutionBase):
    +    pass
    +
    +
    +class SubstitutionMapping(aria_declarative_base, service_instance.SubstitutionMappingBase):
    +    pass
    +
    +
    +class Relationship(aria_declarative_base, service_instance.RelationshipBase):
    +    pass
    +
    +
    +class Capability(aria_declarative_base, service_instance.CapabilityBase):
    +    pass
    +
    +
    +class Interface(aria_declarative_base, service_instance.InterfaceBase):
    +    pass
    +
    +
    +class Operation(aria_declarative_base, service_instance.OperationBase):
    +    pass
    +
    +
    +class Artifact(aria_declarative_base, service_instance.ArtifactBase):
    +    pass
    +
    +# endregion
    +
    +
    +# region service changes models
    +
    +class ServiceUpdate(aria_declarative_base, service_changes.ServiceUpdateBase):
    +    pass
    +
    +
    +class ServiceUpdateStep(aria_declarative_base, service_changes.ServiceUpdateStepBase):
    +    pass
    +
    +
    +class ServiceModification(aria_declarative_base, service_changes.ServiceModificationBase):
    +    pass
    +
    +# endregion
    +
    +
    +# region common service models
    +
    +class Parameter(aria_declarative_base, service_common.ParameterBase):
    +    pass
    +
    +
    +class Type(aria_declarative_base, service_common.TypeBase):
    +    pass
    +
    +
    +class Metadata(aria_declarative_base, service_common.MetadataBase):
    +    pass
    +
    +
    +class PluginSpecification(aria_declarative_base, service_common.PluginSpecificationBase):
    +    pass
    +
    +# endregion
    +
    +
    +# region orchestration models
    +
    +class Execution(aria_declarative_base, orchestration.ExecutionBase):
    +    pass
    +
    +
    +class Plugin(aria_declarative_base, orchestration.PluginBase):
    +    pass
    +
    +
    +class Task(aria_declarative_base, orchestration.TaskBase):
    +    pass
    +
    +
    +class Log(aria_declarative_base, orchestration.LogBase):
    +    pass
    +
    +# endregion
    +
    +
    +models_to_register = [
    +    # Service template models
    +    ServiceTemplate,
    +    NodeTemplate,
    +    GroupTemplate,
    +    PolicyTemplate,
    +    SubstitutionTemplate,
    +    SubstitutionTemplateMapping,
    +    RequirementTemplate,
    +    RelationshipTemplate,
    +    CapabilityTemplate,
    +    InterfaceTemplate,
    +    OperationTemplate,
    +    ArtifactTemplate,
    +
    +    # Service instance models
    +    Service,
    +    Node,
    +    Group,
    +    Policy,
    +    SubstitutionMapping,
    +    Substitution,
    +    Relationship,
    +    Capability,
    +    Interface,
    +    Operation,
    +    Artifact,
    +
    +    # Service changes models
    +    ServiceUpdate,
    +    ServiceUpdateStep,
    +    ServiceModification,
    +
    +    # Common service models
    +    Parameter,
    +    Type,
    +    Metadata,
    +    PluginSpecification,
    +
    +    # Orchestration models
    +    Execution,
    +    Plugin,
    +    Task,
    +    Log
    +]
    +
    +__all__ = (
    --- End diff --
    
    I think that for consistency this should be at the top.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104974724
  
    --- Diff: aria/modeling/orchestration.py ---
    @@ -327,20 +340,48 @@ class PluginBase(ModelMixin):
         uploaded_at = Column(DateTime, nullable=False, index=True)
         wheels = Column(List, nullable=False)
     
    +    # region foreign keys
    +
    +    __private_fields__ = ['service_template_fk']
    --- End diff --
    
    as we mentioned, this should change; need two separate plugin models, one for those declared in service templates, and another for those installed via the plugin manager.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104933586
  
    --- Diff: aria/modeling/service.py ---
    @@ -0,0 +1,1529 @@
    +# 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.
    +
    +# pylint: disable=no-self-argument, no-member, abstract-method
    +
    +from sqlalchemy import (
    +    Column,
    +    Text,
    +    Integer
    +)
    +from sqlalchemy import DateTime
    +from sqlalchemy.ext.associationproxy import association_proxy
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from .bases import InstanceModelMixin
    +from ..parser import validation
    +from ..utils import collections, formatting, console
    +
    +from . import (
    +    utils,
    +    types as modeling_types
    +)
    +
    +
    +class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
    +    """
    +    A service is usually an instance of a :class:`ServiceTemplate`.
    +
    +    You will usually not create it programmatically, but instead instantiate it from a service
    +    template.
    +
    +    :ivar name: Name (unique for this ARIA installation)
    +    :vartype name: basestring
    +    :ivar service_template: Template from which this service was instantiated (optional)
    +    :vartype service_template: :class:`ServiceTemplate`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar meta_data: Custom annotations
    +    :vartype meta_data: {basestring: :class:`Metadata`}
    +    :ivar node: Nodes
    +    :vartype node: [:class:`Node`]
    +    :ivar groups: Groups of nodes
    +    :vartype groups: [:class:`Group`]
    +    :ivar policies: Policies
    +    :vartype policies: [:class:`Policy`]
    +    :ivar substitution: The entire service can appear as a node
    +    :vartype substitution: :class:`Substitution`
    +    :ivar inputs: Externally provided parameters
    +    :vartype inputs: {basestring: :class:`Parameter`}
    +    :ivar outputs: These parameters are filled in after service installation
    +    :vartype outputs: {basestring: :class:`Parameter`}
    +    :ivar operations: Custom operations that can be performed on the service
    +    :vartype operations: {basestring: :class:`Operation`}
    +    :ivar plugins: Plugins required to be installed
    +    :vartype plugins: {basestring: :class:`Plugin`}
    +    :ivar created_at: Creation timestamp
    +    :vartype created_at: :class:`datetime.datetime`
    +    :ivar updated_at: Update timestamp
    +    :vartype updated_at: :class:`datetime.datetime`
    +
    +    :ivar permalink: ??
    +    :vartype permalink: basestring
    +    :ivar scaling_groups: ??
    +    :vartype scaling_groups: {}
    +
    +    :ivar modifications: Modifications of this service
    +    :vartype modifications: [:class:`ServiceModification`]
    +    :ivar updates: Updates of this service
    +    :vartype updates: [:class:`ServiceUpdate`]
    +    :ivar executions: Executions on this service
    +    :vartype executions: [:class:`Execution`]
    +    """
    +
    +    __tablename__ = 'service'
    +
    +    @declared_attr
    +    def service_template(cls):
    +        return cls.many_to_one_relationship('service_template')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def meta_data(cls):
    +        # Warning! We cannot use the attr name "metadata" because it's used by SqlAlchemy!
    +        return cls.many_to_many_relationship('metadata', dict_key='name')
    +
    +    @declared_attr
    +    def nodes(cls):
    +        return cls.one_to_many_relationship('node')
    +
    +    @declared_attr
    +    def groups(cls):
    +        return cls.one_to_many_relationship('group')
    +
    +    @declared_attr
    +    def policies(cls):
    +        return cls.one_to_many_relationship('policy')
    +
    +    @declared_attr
    +    def substitution(cls):
    +        return cls.one_to_one_relationship('substitution')
    +
    +    @declared_attr
    +    def inputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def outputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='outputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def operations(cls):
    +        return cls.one_to_many_relationship('operation', dict_key='name')
    +
    +    @declared_attr
    +    def plugins(cls):
    +        return cls.many_to_many_relationship('plugin')
    +
    +    created_at = Column(DateTime, nullable=False, index=True)
    +    updated_at = Column(DateTime)
    +
    +    # region orchestration
    +
    +    permalink = Column(Text)
    +    scaling_groups = Column(modeling_types.Dict)
    +
    +    # endregion
    +
    +    # region foreign keys
    +
    +    __private_fields__ = ['substituion_fk',
    +                          'service_template_fk']
    +
    +    # Service one-to-one to Substitution
    +    @declared_attr
    +    def substitution_fk(cls):
    +        return cls.foreign_key('substitution', nullable=True)
    +
    +    # Service many-to-one to ServiceTemplate
    +    @declared_attr
    +    def service_template_fk(cls):
    +        return cls.foreign_key('service_template', nullable=True)
    +
    +    # endregion
    +
    +    def satisfy_requirements(self, context):
    +        satisfied = True
    +        for node in self.nodes:
    +            if not node.satisfy_requirements(context):
    +                satisfied = False
    +        return satisfied
    +
    +    def validate_capabilities(self, context):
    +        satisfied = True
    +        for node in self.nodes:
    +            if not node.validate_capabilities(context):
    +                satisfied = False
    +        return satisfied
    +
    +    def find_nodes(self, node_template_name):
    +        nodes = []
    +        for node in self.nodes:
    +            if node.node_template.name == node_template_name:
    +                nodes.append(node)
    +        return collections.FrozenList(nodes)
    +
    +    def get_node_ids(self, node_template_name):
    +        return collections.FrozenList((node.name for node in self.find_nodes(node_template_name)))
    +
    +    def find_groups(self, group_template_name):
    +        groups = []
    +        for group in self.groups:
    +            if group.template_name == group_template_name:
    +                groups.append(group)
    +        return collections.FrozenList(groups)
    +
    +    def get_group_ids(self, group_template_name):
    +        return collections.FrozenList((group.name
    +                                       for group in self.find_groups(group_template_name)))
    +
    +    def is_node_a_target(self, context, target_node):
    +        for node in self.nodes:
    +            if self._is_node_a_target(context, node, target_node):
    +                return True
    +        return False
    +
    +    def _is_node_a_target(self, context, source_node, target_node):
    +        if source_node.relationships:
    +            for relationship in source_node.relationships:
    +                if relationship.target_node_id == target_node.name:
    +                    return True
    +                else:
    +                    node = context.modeling.instance.nodes.get(relationship.target_node_id)
    +                    if node is not None:
    +                        if self._is_node_a_target(context, node, target_node):
    +                            return True
    +        return False
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('description', self.description),
    +            ('metadata', formatting.as_raw_dict(self.meta_data)),
    +            ('nodes', formatting.as_raw_list(self.nodes)),
    +            ('groups', formatting.as_raw_list(self.groups)),
    +            ('policies', formatting.as_raw_list(self.policies)),
    +            ('substitution', formatting.as_raw(self.substitution)),
    +            ('inputs', formatting.as_raw_dict(self.inputs)),
    +            ('outputs', formatting.as_raw_dict(self.outputs)),
    +            ('operations', formatting.as_raw_list(self.operations))))
    +
    +    def validate(self, context):
    +        utils.validate_dict_values(context, self.meta_data)
    +        utils.validate_list_values(context, self.nodes)
    +        utils.validate_list_values(context, self.groups)
    +        utils.validate_list_values(context, self.policies)
    +        if self.substitution is not None:
    +            self.substitution.validate(context)
    +        utils.validate_dict_values(context, self.inputs)
    +        utils.validate_dict_values(context, self.outputs)
    +        utils.validate_dict_values(context, self.operations)
    +
    +    def coerce_values(self, context, container, report_issues):
    +        utils.coerce_dict_values(context, container, self.meta_data, report_issues)
    +        utils.coerce_list_values(context, container, self.nodes, report_issues)
    +        utils.coerce_list_values(context, container, self.groups, report_issues)
    +        utils.coerce_list_values(context, container, self.policies, report_issues)
    +        if self.substitution is not None:
    +            self.substitution.coerce_values(context, container, report_issues)
    +        utils.coerce_dict_values(context, container, self.inputs, report_issues)
    +        utils.coerce_dict_values(context, container, self.outputs, report_issues)
    +        utils.coerce_dict_values(context, container, self.operations, report_issues)
    +
    +    def dump(self, context):
    +        if self.description is not None:
    +            console.puts(context.style.meta(self.description))
    +        utils.dump_dict_values(context, self.meta_data, 'Metadata')
    +        for node in self.nodes:
    +            node.dump(context)
    +        for group in self.groups:
    +            group.dump(context)
    +        for policy in self.policies:
    +            policy.dump(context)
    +        if self.substitution is not None:
    +            self.substitution.dump(context)
    +        utils.dump_dict_values(context, self.inputs, 'Inputs')
    +        utils.dump_dict_values(context, self.outputs, 'Outputs')
    +        utils.dump_dict_values(context, self.operations, 'Operations')
    +
    +    def dump_graph(self, context):
    +        for node in self.nodes.itervalues():
    +            if not self.is_node_a_target(context, node):
    +                self._dump_graph_node(context, node)
    +
    +    def _dump_graph_node(self, context, node):
    +        console.puts(context.style.node(node.name))
    +        if node.relationships:
    +            with context.style.indent:
    +                for relationship in node.relationships:
    +                    relationship_name = (context.style.node(relationship.template_name)
    +                                         if relationship.template_name is not None
    +                                         else context.style.type(relationship.type_name))
    +                    capability_name = (context.style.node(relationship.target_capability_name)
    +                                       if relationship.target_capability_name is not None
    +                                       else None)
    +                    if capability_name is not None:
    +                        console.puts('-> {0} {1}'.format(relationship_name, capability_name))
    +                    else:
    +                        console.puts('-> {0}'.format(relationship_name))
    +                    target_node = self.nodes.get(relationship.target_node_id)
    +                    with console.indent(3):
    +                        self._dump_graph_node(context, target_node)
    +
    +
    +class NodeBase(InstanceModelMixin):
    +    """
    +    Usually an instance of a :class:`NodeTemplate`.
    +
    +    Nodes may have zero or more :class:`Relationship` instances to other nodes.
    +
    +    :ivar name: Name (unique for this service)
    +    :vartype name: basestring
    +    :ivar node_template: Template from which this node was instantiated (optional)
    +    :vartype node_template: :class:`NodeTemplate`
    +    :ivar type: Node type
    +    :vartype type: :class:`Type`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar properties: Associated parameters
    +    :vartype properties: {basestring: :class:`Parameter`}
    +    :ivar interfaces: Bundles of operations
    +    :vartype interfaces: {basestring: :class:`Interface`}
    +    :ivar artifacts: Associated files
    +    :vartype artifacts: {basestring: :class:`Artifact`}
    +    :ivar capabilities: Exposed capabilities
    +    :vartype capabilities: {basestring: :class:`Capability`}
    +    :ivar outbound_relationships: Relationships to other nodes
    +    :vartype outbound_relationships: [:class:`Relationship`]
    +    :ivar inbound_relationships: Relationships from other nodes
    +    :vartype inbound_relationships: [:class:`Relationship`]
    +    :ivar plugins: Plugins required to be installed on the node's host
    +    :vartype plugins: {basestring: :class:`Plugin`}
    +    :ivar host: Host node (can be self)
    +    :vartype host: :class:`Node`
    +
    +    :ivar runtime_properties: TODO: should be replaced with attributes
    +    :vartype runtime_properties: {}
    +    :ivar scaling_groups: ??
    +    :vartype scaling_groups: []
    +    :ivar state: ??
    +    :vartype state: basestring
    +    :ivar version: ??
    +    :vartype version: int
    +
    +    :ivar service: Containing service
    +    :vartype service: :class:`Service`
    +    :ivar groups: We are a member of these groups
    +    :vartype groups: [:class:`Group`]
    +    :ivar policies: Policies enacted on this node
    +    :vartype policies: [:class:`Policy`]
    +    :ivar substitution_mapping: Our contribution to service substitution
    +    :vartype substitution_mapping: :class:`SubstitutionMapping`
    +    :ivar tasks: Tasks on this node
    +    :vartype tasks: [:class:`Task`]
    +    """
    +
    +    __tablename__ = 'node'
    +
    +    @declared_attr
    +    def node_template(cls):
    +        return cls.many_to_one_relationship('node_template')
    +
    +    @declared_attr
    +    def type(cls):
    +        return cls.many_to_one_relationship('type')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def properties(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='properties',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def interfaces(cls):
    +        return cls.one_to_many_relationship('interface', dict_key='name')
    +
    +    @declared_attr
    +    def artifacts(cls):
    +        return cls.one_to_many_relationship('artifact', dict_key='name')
    +
    +    @declared_attr
    +    def capabilities(cls):
    +        return cls.one_to_many_relationship('capability', dict_key='name')
    +
    +    @declared_attr
    +    def outbound_relationships(cls):
    +        return cls.one_to_many_relationship('relationship',
    +                                            foreign_key='source_node_fk',
    +                                            backreference='source_node')
    +
    +    @declared_attr
    +    def inbound_relationships(cls):
    +        return cls.one_to_many_relationship('relationship',
    +                                            foreign_key='target_node_fk',
    +                                            backreference='target_node')
    +
    +    @declared_attr
    +    def plugins(cls):
    +        return cls.many_to_many_relationship('plugin')
    +
    +    @declared_attr
    +    def host(cls):
    +        return cls.relationship_to_self('host_fk')
    +
    +    # region orchestration
    +
    +    runtime_properties = Column(modeling_types.Dict)
    +    scaling_groups = Column(modeling_types.List)
    +    state = Column(Text, nullable=False)
    +    version = Column(Integer, default=1)
    +
    +    @declared_attr
    +    def service_name(cls):
    +        return association_proxy('service', 'name')
    +
    +    @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
    +        return None
    +
    +    # endregion
    +
    +    # region foreign_keys
    +
    +    __private_fields__ = ['type_fk',
    +                          'host_fk',
    +                          'service_fk',
    +                          'node_template_fk']
    +
    +    # Node many-to-one to Type
    +    @declared_attr
    +    def type_fk(cls):
    +        return cls.foreign_key('type')
    +
    +    # Node one-to-one to Node
    +    @declared_attr
    +    def host_fk(cls):
    +        return cls.foreign_key('node', nullable=True)
    +
    +    # Service one-to-many to Node
    +    @declared_attr
    +    def service_fk(cls):
    +        return cls.foreign_key('service')
    +
    +    # Node many-to-one to NodeTemplate
    +    @declared_attr
    +    def node_template_fk(cls):
    +        return cls.foreign_key('node_template', nullable=True)
    +
    +    # endregion
    +
    +    def satisfy_requirements(self, context):
    +        node_template = self.node_template
    +        satisfied = True
    +        for requirement_template in node_template.requirement_templates:
    +            # Find target template
    +            target_node_template, target_node_capability = \
    +                requirement_template.find_target(context, node_template)
    +            if target_node_template is not None:
    +                satisfied = self._satisfy_capability(context,
    +                                                     target_node_capability,
    +                                                     target_node_template,
    +                                                     requirement_template)
    +            else:
    +                context.validation.report('requirement "{0}" of node "{1}" has no target node '
    --- End diff --
    
    there should be unified model for logging, it's weird that some log msgs, while other report it to the parser's context...


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106947980
  
    --- Diff: aria/orchestrator/workflows/api/task.py ---
    @@ -92,144 +93,133 @@ def __init__(self,
                                    if ignore_failure is None else ignore_failure)
             self.runs_on = runs_on
     
    -    @classmethod
    -    def _merge_inputs(cls, operation_inputs, additional_inputs=None):
    -        final_inputs = dict((p.name, p.as_raw['value']) for p in operation_inputs)
    -        final_inputs.update(additional_inputs or {})
    -        return final_inputs
    +        # Wrap inputs
    +        if inputs:
    +            for k, v in inputs.iteritems():
    +                if not isinstance(v, models.Parameter):
    +                    inputs[k] = models.Parameter.wrap(k, v)
    +
    +        # TODO: These extra inputs should likely be stored as a separate entry in the task model,
    +        # because they are different from the operation inputs. The two kinds of inputs should also
    +        # not be merged.
    +
    +        if interface_name or operation_name:
    +            operation = OperationTask._get_operation(actor.interfaces, interface_name,
    +                                                     operation_name)
    +            if operation is None:
    +                raise exceptions.TaskException(
    +                    'Could not find operation "{0}" on interface "{1}" for {2} "{3}"'
    +                    .format(operation_name, 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.TaskException(
    +                        'Could not find plugin of operation "{0}" on interface "{1}" for {2} "{3}"'
    +                        .format(operation_name, 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=interface_name,
    +                                                         operation=operation_name)
    +        else:
    +            self.name = name
    +            self.implementation = implementation
    +            self.inputs = inputs or {}
    +            self.plugin = None
     
         @classmethod
    -    def node(cls, instance, name, inputs=None, *args, **kwargs):
    +    def for_node(cls,
    +                 node,
    +                 interface_name,
    +                 operation_name,
    +                 max_attempts=None,
    +                 retry_interval=None,
    +                 ignore_failure=None,
    +                 inputs=None):
             """
    -        Represents a node based operation
    +        Creates an operation on a node.
     
    -        :param instance: the node of which this operation belongs to.
    -        :param name: the name of the operation.
    +        :param node: the node of which this operation belongs to.
    +        :param interface_name: the name of the interface.
    +        :param operation_name: the name of the operation.
    +        :param inputs: any additional inputs to the operation
             """
    -        assert isinstance(instance, model.Node)
    -        interface_name = _get_interface_name(name)
    -        interfaces = instance.interfaces.filter_by(name=interface_name)
    -        if interfaces.count() > 1:
    -            raise exceptions.TaskException(
    -                "More than one interface with the same name `{0}` found".format(name)
    -            )
    -        elif interfaces.count() == 0:
    -            raise exceptions.TaskException(
    -                "No Interface with the name `{interface_name}` found".format(
    -                    interface_name=interface_name)
    -            )
    -
    -        operation_templates = interfaces[0].operations.filter_by(name=name)
    -        if operation_templates.count() > 1:
    -            raise exceptions.TaskException(
    -                "More than one operation with the same name `{0}` were found".format(name)
    -            )
    -
    -        elif operation_templates.count() == 0:
    -            raise exceptions.TaskException(
    -                "No interface with the name `{operation_name}` found".format(
    -                    operation_name=name)
    -            )
    -
    -        return cls._instance(
    -            instance=instance,
    -            name=name,
    -            operation_template=operation_templates[0],
    -            plugins=instance.plugins or [],
    -            runs_on=model.Task.RUNS_ON_NODE_INSTANCE,
    -            inputs=cls._merge_inputs(operation_templates[0].inputs, inputs),
    -            *args,
    -            **kwargs)
    +
    +        assert isinstance(node, models.Node)
    +        return cls(
    +            actor=node,
    +            actor_type='node',
    +            interface_name=interface_name,
    +            operation_name=operation_name,
    +            max_attempts=max_attempts,
    +            retry_interval=retry_interval,
    +            ignore_failure=ignore_failure,
    +            inputs=inputs,
    +            runs_on=models.Task.RUNS_ON_NODE)
     
         @classmethod
    -    def relationship(cls, instance, name, edge, runs_on=None, inputs=None, *args,
    -                     **kwargs):
    +    def for_relationship(cls,
    +                         relationship,
    +                         interface_name,
    +                         operation_name,
    +                         max_attempts=None,
    +                         retry_interval=None,
    +                         ignore_failure=None,
    +                         inputs=None,
    +                         runs_on=models.Task.RUNS_ON_SOURCE):
             """
    -        Represents a relationship based operation
    +        Creates an operation on a relationship edge.
     
    -        :param instance: the relationship of which this operation belongs to.
    -        :param name: the name of the operation.
    -        :param edge: the edge of the interface ("source" or "target").
    -        :param runs_on: where to run the operation ("source" or "target"); if None defaults to the
    -                        interface edge.
    -        :param inputs any additional inputs to the operation
    +        :param relationship: the relationship of which this operation belongs to.
    +        :param interface_name: the name of the interface.
    +        :param operation_name: the name of the operation.
    +        :param inputs: any additional inputs to the operation
    +        :param runs_on: where to run the operation ("source" or "target"); defaults to "source"
             """
    -        assert isinstance(instance, model.Relationship)
    -        interface_name = _get_interface_name(name)
    -        interfaces = instance.interfaces.filter_by(name=interface_name, edge=edge)
    -        count = interfaces.count()
    -        if count > 1:
    -            raise exceptions.TaskException(
    -                "More than one interface with the same name `{interface_name}` found at `{edge}`"
    -                + " edge".format(
    -                    interface_name=interface_name, edge=edge)
    -            )
    -        elif count == 0:
    -            raise exceptions.TaskException(
    -                "No interface with the name `{interface_name}` found at `{edge}` edge".format(
    -                    interface_name=interface_name, edge=edge)
    -            )
    -
    -        operations = interfaces.all()[0].operations.filter_by(name=name)
    -        count = operations.count()
    -        if count > 1:
    -            raise exceptions.TaskException(
    -                "More than one operation with the same name `{0}` found".format(name)
    -            )
    -        elif count == 0:
    -            raise exceptions.TaskException(
    -                "No operation with the name `{operation_name}` found".format(
    -                    operation_name=name)
    -            )
    -
    -        if not runs_on:
    -            if edge == cls.SOURCE_OPERATION:
    -                runs_on = model.Task.RUNS_ON_SOURCE
    -            else:
    -                runs_on = model.Task.RUNS_ON_TARGET
    -
    -        if runs_on == model.Task.RUNS_ON_SOURCE:
    -            plugins = instance.source_node.plugins
    -        else:
    -            plugins = instance.target_node.plugins
    -
    -        return cls._instance(instance=instance,
    -                             name=name,
    -                             operation_template=operations[0],
    -                             plugins=plugins or [],
    -                             runs_on=runs_on,
    -                             inputs=cls._merge_inputs(operations[0].inputs, inputs),
    -                             *args,
    -                             **kwargs)
     
    -    @classmethod
    -    def _instance(cls,
    -                  instance,
    -                  name,
    -                  operation_template,
    -                  inputs,
    -                  plugins,
    -                  runs_on,
    -                  *args,
    -                  **kwargs):
    -        matching_plugins = [p for p in plugins if p['name'] == operation_template.plugin]
    -        # All matching plugins should have identical package_name/package_version, so it's safe to
    -        # take the first found.
    -        plugin = matching_plugins[0] if matching_plugins else {}
    -        return cls(actor=instance,
    -                   name=name,
    -                   implementation=operation_template.implementation,
    -                   inputs=inputs,
    -                   plugin=plugin,
    -                   runs_on=runs_on,
    -                   *args,
    -                   **kwargs)
    +        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,
    +            max_attempts=max_attempts,
    +            retry_interval=retry_interval,
    +            ignore_failure=ignore_failure,
    +            inputs=inputs,
    +            runs_on=runs_on)
    +
    +    @staticmethod
    +    def _get_operation(interfaces, interface_name, operation_name):
    +        interface = interfaces.get(interface_name)
    --- End diff --
    
    Done


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106806321
  
    --- Diff: tests/modeling/test_models.py ---
    @@ -663,66 +646,45 @@ def _node(self, storage, node, is_host, ip, host_fk=None):
             kwargs = dict(
                 name='node',
                 node_template=node,
    +            type=storage.type.list()[0],
                 runtime_properties={},
                 state='',
    -            service_instance=storage.service_instance.list()[0]
    +            service=storage.service.list()[0]
             )
             if ip:
                 kwargs['runtime_properties']['ip'] = ip
             if is_host:
                 kwargs['host_fk'] = 1
             elif host_fk:
                 kwargs['host_fk'] = host_fk
    -        node_instance = Node(**kwargs)
    -        storage.node.put(node_instance)
    -        return node_instance
    +        node = Node(**kwargs)
    +        storage.node.put(node)
    +        return node
     
     
    -class TestRelationshipInstance(object):
    +class TestRelationship(object):
         @pytest.mark.parametrize(
    -        'is_valid, source_requirement_index, target_node_id, target_capability_name, type_name, '
    -        'template_name, type_hierarchy, source_position, target_position',
    +        'is_valid, source_position, target_position',
             [
    -            (False, m_cls, 'target_node_id', 'target_cap_name', 'type_name', 'template_name', [], 0,
    -             0),
    -            (False, 0, m_cls, 'target_cap_name', 'type_name', 'template_name', [], 0, 0),
    -            (False, 0, 'target_node_id', m_cls, 'type_name', 'template_name', [], 0, 0),
    -            (False, 0, 'target_node_id', 'target_cap_name', m_cls, 'template_name', [], 0, 0),
    -            (False, 0, 'target_node_id', 'target_cap_name', 'type_name', m_cls, [], 0, 0),
    -            (False, 0, 'target_node_id', 'target_cap_name', 'type_name', 'template_name', m_cls, 0,
    -             0),
    -            (False, 0, 'target_node_id', 'target_cap_name', 'type_name', 'template_name', [], m_cls,
    -             0),
    -            (False, 0, 'target_node_id', 'target_cap_name', 'type_name', 'template_name', [], 0,
    -             m_cls),
    -
    -            (True, 0, 'target_node_id', 'target_cap_name', 'type_name', 'template_name', [], 0, 0),
    -            (True, None, 'target_node_id', 'target_cap_name', 'type_name', 'template_name', [], 0,
    -             0),
    -            (True, 0, None, 'target_cap_name', 'type_name', 'template_name', [], 0, 0),
    -            (True, 0, 'target_node_id', None, 'type_name', 'template_name', [], 0, 0),
    -            (True, 0, 'target_node_id', 'target_cap_name', None, 'template_name', [], 0, 0),
    -            (True, 0, 'target_node_id', 'target_cap_name', 'type_name', None, [], 0, 0),
    -            (True, 0, 'target_node_id', 'target_cap_name', 'type_name', 'template_name', [], None,
    -             0),
    -            (True, 0, 'target_node_id', 'target_cap_name', 'type_name', 'template_name', [], 0,
    -             None),
    +            (False, m_cls, 0),
    --- End diff --
    
    what happened to the rest of the test cases?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106993935
  
    --- Diff: aria/modeling/relationships.py ---
    @@ -0,0 +1,402 @@
    +# 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.
    +
    +# pylint: disable=invalid-name, redefined-outer-name
    +
    +from sqlalchemy.orm import relationship, backref
    +from sqlalchemy.orm.collections import attribute_mapped_collection
    +from sqlalchemy import (
    +    Column,
    +    ForeignKey,
    +    Integer,
    +    Table
    +)
    +
    +from ..utils import formatting
    +
    +
    +def fk(other_table,
    +       nullable=False):
    +    """
    +    Declare a foreign key property, which will also create a foreign key column in the table with
    +    the name of the property. By convention the property name should end in "_fk".
    +
    +    You are required to explicitly create foreign keys in order to allow for one-to-one,
    +    one-to-many, and many-to-one relationships (but not for many-to-many relationships). If you do
    +    not do so, SQLAlchemy will fail to create the relationship property and raise an exception with
    +    a clear error message.
    +
    +    You should normally not have to access this property directly, but instead use the associated
    +    relationship properties.
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param other_table: Other table name
    +    :type other_table: basestring
    +    :param nullable: True to allow null values (meaning that there is no relationship)
    +    :type nullable: bool
    +    """
    +
    +    return Column(Integer,
    +                  ForeignKey('{table}.id'.format(table=other_table), ondelete='CASCADE'),
    +                  nullable=nullable)
    +
    +
    +def one_to_one_self(model_class,
    +                    fk,
    +                    relationship_kwargs=None):
    +    """
    +    Declare a one-to-one relationship property. The property value would be an instance of the same
    +    model.
    +
    +    You will need an associated foreign key to our own table.
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param model_class: The class in which this relationship will be declared
    +    :type model_class: type
    +    :param fk: Foreign key name
    +    :type fk: basestring
    +    :param relationship_kwargs: Extra kwargs for SQLAlchemy `relationship`
    +    :type relationship_kwargs: {}
    +    """
    +
    +    relationship_kwargs = relationship_kwargs or {}
    +
    +    remote_side = '{model_class}.{remote_column}'.format(
    +        model_class=model_class.__name__,
    +        remote_column=model_class.id_column_name()
    +    )
    +
    +    primaryjoin = '{remote_side} == {model_class}.{column}'.format(
    +        remote_side=remote_side,
    +        model_class=model_class.__name__,
    +        column=fk
    +    )
    +
    +    return relationship(
    +        _get_class_for_table(model_class, model_class.__tablename__).__name__,
    +        primaryjoin=primaryjoin,
    +        remote_side=remote_side,
    +        post_update=True,
    +        **relationship_kwargs
    +    )
    +
    +
    +def one_to_many_self(model_class,
    +                     fk,
    +                     dict_key=None,
    +                     relationship_kwargs=None):
    +    """
    +    Declare a one-to-many relationship property. The property value would be a list or dict of
    +    instances of the same model.
    +
    +    You will need an associated foreign key to our own table.
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param model_class: The class in which this relationship will be declared
    +    :type model_class: type
    +    :param fk: Foreign key name
    +    :type fk: basestring
    +    :param dict_key: If set the value will be a dict with this key as the dict key; otherwise will
    +                     be a list
    +    :type dict_key: basestring
    +    :param relationship_kwargs: Extra kwargs for SQLAlchemy `relationship`
    +    :type relationship_kwargs: {}
    +    """
    +
    +    relationship_kwargs = relationship_kwargs or {}
    +
    +    relationship_kwargs.setdefault('remote_side', '{model_class}.{remote_column}'.format(
    +        model_class=model_class.__name__,
    +        remote_column=fk
    +    ))
    +
    +    return _relationship(model_class, model_class.__tablename__, None, relationship_kwargs,
    +                         other_property=False, dict_key=dict_key)
    +
    +
    +def one_to_one(model_class,
    +               other_table,
    +               fk=None,
    +               other_fk=None,
    +               other_property=None,
    +               relationship_kwargs=None,
    +               backref_kwargs=None):
    +    """
    +    Declare a one-to-one relationship property. The property value would be an instance of the other
    +    table's model.
    +
    +    You have two options for the foreign key. Either this table can have an associated key to the
    +    other table (use the `fk` argument) or the other table can have an associated foreign key to
    +    this our table (use the `other_fk` argument).
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param model_class: The class in which this relationship will be declared
    +    :type model_class: type
    +    :param other_table: Other table name
    +    :type other_table: basestring
    +    :param fk: Foreign key name at our table (no need specify if there's no ambiguity)
    +    :type fk: basestring
    +    :param other_fk: Foreign key name at the other table (no need specify if there's no ambiguity)
    +    :type other_fk: basestring
    +    :param relationship_kwargs: Extra kwargs for SQLAlchemy `relationship`
    +    :type relationship_kwargs: {}
    +    :param backref_kwargs: Extra kwargs for SQLAlchemy `backref`
    +    :type backref_kwargs: {}
    +    """
    +
    +    backref_kwargs = backref_kwargs or {}
    +    backref_kwargs.setdefault('uselist', False)
    +
    +    return _relationship(model_class, other_table, backref_kwargs, relationship_kwargs,
    +                         other_property, fk=fk, other_fk=other_fk)
    +
    +
    +def one_to_many(model_class,
    +                child_table,
    +                child_fk=None,
    +                dict_key=None,
    +                child_property=None,
    +                relationship_kwargs=None,
    +                backref_kwargs=None):
    +    """
    +    Declare a one-to-many relationship property. The property value would be a list or dict of
    +    instances of the child table's model.
    +
    +    The child table will need an associated foreign key to our table.
    +
    +    The declaration will automatically create a matching many-to-one property at the child model,
    +    named after our table name. Use the `child_property` argument to override this name.
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param model_class: The class in which this relationship will be declared
    +    :type model_class: type
    +    :param child_table: Child table name
    +    :type child_table: basestring
    +    :param child_fk: Foreign key name at the child table (no need specify if there's no ambiguity)
    +    :type child_fk: basestring
    +    :param dict_key: If set the value will be a dict with this key as the dict key; otherwise will
    +                     be a list
    +    :type dict_key: basestring
    +    :param child_property: Override name of matching many-to-one property at child table; set to
    +                           false to disable
    +    :type child_property: basestring|bool
    +    :param relationship_kwargs: Extra kwargs for SQLAlchemy `relationship`
    +    :type relationship_kwargs: {}
    +    :param backref_kwargs: Extra kwargs for SQLAlchemy `backref`
    +    :type backref_kwargs: {}
    +    """
    +
    +    backref_kwargs = backref_kwargs or {}
    +    backref_kwargs.setdefault('uselist', False)
    +
    +    return _relationship(model_class, child_table, backref_kwargs, relationship_kwargs,
    +                         child_property, other_fk=child_fk, dict_key=dict_key)
    +
    +
    +def many_to_one(model_class,
    +                parent_table,
    +                fk=None,
    +                parent_fk=None,
    +                parent_property=None,
    +                relationship_kwargs=None,
    +                backref_kwargs=None):
    +    """
    +    Declare a many-to-one relationship property. The property value would be an instance of the
    +    parent table's model.
    +
    +    You will need an associated foreign key to the parent table.
    +
    +    The declaration will automatically create a matching one-to-many property at the child model,
    +    named after the plural form of our table name. Use the `parent_property` argument to override
    +    this name. Note: the automatic property will always be a SQLAlchemy query object; if you need a
    +    Python collection then use :meth:`one_to_many` at that model.
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param model_class: The class in which this relationship will be declared
    +    :type model_class: type
    +    :param parent_table: Parent table name
    +    :type parent_table: basestring
    +    :param fk: Foreign key name at our table (no need specify if there's no ambiguity)
    +    :type fk: basestring
    +    :param parent_property: Override name of matching one-to-many property at parent table; set to
    +                            false to disable
    +    :type parent_property: basestring|bool
    +    :param relationship_kwargs: Extra kwargs for SQLAlchemy `relationship`
    +    :type relationship_kwargs: {}
    +    :param backref_kwargs: Extra kwargs for SQLAlchemy `backref`
    +    :type backref_kwargs: {}
    +    """
    +
    +    if parent_property is None:
    +        parent_property = formatting.pluralize(model_class.__tablename__)
    +
    +    backref_kwargs = backref_kwargs or {}
    +    backref_kwargs.setdefault('uselist', True)
    +    backref_kwargs.setdefault('lazy', 'dynamic')
    +    backref_kwargs.setdefault('cascade', 'all') # delete children when parent is deleted
    +
    +    return _relationship(model_class, parent_table, backref_kwargs, relationship_kwargs,
    +                         parent_property, fk=fk, other_fk=parent_fk)
    +
    +
    +def many_to_many(model_class,
    +                 other_table,
    +                 prefix=None,
    +                 dict_key=None,
    +                 other_property=None,
    +                 relationship_kwargs=None,
    +                 backref_kwargs=None):
    +    """
    +    Declare a many-to-many relationship property. The property value would be a list or dict of
    +    instances of the other table's model.
    +
    +    You do not need associated foreign keys for this relationship. Instead, an extra table will be
    +    created for you.
    +
    +    The declaration will automatically create a matching many-to-many property at the other model,
    +    named after the plural form of our table name. Use the `other_property` argument to override
    +    this name. Note: the automatic property will always be a SQLAlchemy query object; if you need a
    +    Python collection then use :meth:`many_to_many` again at that model.
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param model_class: The class in which this relationship will be declared
    +    :type model_class: type
    +    :param parent_table: Parent table name
    +    :type parent_table: basestring
    +    :param prefix: Optional prefix for extra table name as well as for `other_property`
    +    :type prefix: basestring
    +    :param dict_key: If set the value will be a dict with this key as the dict key; otherwise will
    +                     be a list
    +    :type dict_key: basestring
    +    :param other_property: Override name of matching many-to-many property at other table; set to
    +                           false to disable
    +    :type other_property: basestring|bool
    +    :param relationship_kwargs: Extra kwargs for SQLAlchemy `relationship`
    +    :type relationship_kwargs: {}
    +    :param backref_kwargs: Extra kwargs for SQLAlchemy `backref`
    +    :type backref_kwargs: {}
    +    """
    +
    +    this_table = model_class.__tablename__
    +    this_column_name = '{0}_id'.format(this_table)
    +    this_foreign_key = '{0}.id'.format(this_table)
    +
    +    other_column_name = '{0}_id'.format(other_table)
    +    other_foreign_key = '{0}.id'.format(other_table)
    +
    +    secondary_table = '{0}_{1}'.format(this_table, other_table)
    +
    +    if other_property is None:
    +        other_property = formatting.pluralize(this_table)
    +        if prefix is not None:
    +            secondary_table = '{0}_{1}'.format(prefix, secondary_table)
    +            other_property = '{0}_{1}'.format(prefix, other_property)
    +
    +    backref_kwargs = backref_kwargs or {}
    +    backref_kwargs.setdefault('uselist', True)
    +
    +    relationship_kwargs = relationship_kwargs or {}
    +    relationship_kwargs.setdefault('secondary', _get_secondary_table(
    +        model_class.metadata,
    +        secondary_table,
    +        this_column_name,
    +        other_column_name,
    +        this_foreign_key,
    +        other_foreign_key
    +    ))
    +
    +    return _relationship(model_class, other_table, backref_kwargs, relationship_kwargs,
    +                         other_property, dict_key=dict_key)
    +
    +
    +def _relationship(model_class, other_table, backref_kwargs, relationship_kwargs, other_property,
    +                  fk=None, other_fk=None, dict_key=None):
    +    relationship_kwargs = relationship_kwargs or {}
    +
    +    if fk:
    +        relationship_kwargs.setdefault('foreign_keys',
    +                                       lambda: getattr(
    +                                           _get_class_for_table(
    +                                               model_class,
    +                                               model_class.__tablename__),
    +                                           fk))
    +
    +    elif other_fk:
    +        relationship_kwargs.setdefault('foreign_keys',
    +                                       lambda: getattr(
    +                                           _get_class_for_table(
    +                                               model_class,
    +                                               other_table),
    +                                           other_fk))
    +
    +    if dict_key:
    +        relationship_kwargs.setdefault('collection_class',
    +                                       attribute_mapped_collection(dict_key))
    +
    +    if other_property is False:
    +        # No backref
    +        return relationship(
    +            lambda: _get_class_for_table(model_class, other_table),
    +            **relationship_kwargs
    +        )
    +    else:
    +        if other_property is None:
    +            other_property = model_class.__tablename__
    +        backref_kwargs = backref_kwargs or {}
    +        return relationship(
    +            lambda: _get_class_for_table(model_class, other_table),
    +            backref=backref(other_property, **backref_kwargs),
    +            **relationship_kwargs
    +        )
    +
    +
    +def _get_class_for_table(model_class, tablename):
    --- End diff --
    
    It receives both a class and a tablename, and returns a class. This was your function originally, I did not change it.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104313124
  
    --- Diff: aria/modeling/bases.py ---
    @@ -0,0 +1,405 @@
    +# 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.
    +
    +"""
    +ARIA's storage.structures module
    +Path: aria.storage.structures
    +
    +models module holds ARIA's models.
    +
    +classes:
    +    * ModelMixin - abstract model implementation.
    +    * ModelIDMixin - abstract model implementation with IDs.
    +"""
    +
    +from sqlalchemy.orm import relationship, backref
    +from sqlalchemy.orm.collections import attribute_mapped_collection
    +from sqlalchemy.ext import associationproxy
    +from sqlalchemy import (
    +    Column,
    +    ForeignKey,
    +    Integer,
    +    Text,
    +    Table,
    +)
    +
    +from . import utils
    +from ..utils import formatting
    +
    +
    +class ModelMixin(object):
    +
    +    @utils.classproperty
    +    def __modelname__(cls):                                                                         # pylint: disable=no-self-argument
    +        return getattr(cls, '__mapiname__', cls.__tablename__)
    +
    +    @classmethod
    +    def id_column_name(cls):
    +        raise NotImplementedError
    +
    +    @classmethod
    +    def name_column_name(cls):
    +        raise NotImplementedError
    +
    +    @classmethod
    +    def foreign_key(cls, parent_table, nullable=False):
    +        """
    +        Return a ForeignKey object.
    +
    +        :param parent_table: Parent table name
    +        :param nullable: Should the column be allowed to remain empty
    +        """
    +        return Column(Integer,
    +                      ForeignKey('{table}.id'.format(table=parent_table),
    +                                 ondelete='CASCADE'),
    +                      nullable=nullable)
    +
    +    @classmethod
    +    def relationship_to_self(cls,
    +                             column_name,
    +                             relationship_kwargs=None):
    +        relationship_kwargs = relationship_kwargs or {}
    +
    +        remote_side = '{cls}.{remote_column}'.format(
    +            cls=cls.__name__,
    +            remote_column=cls.id_column_name()
    +        )
    +
    +        primaryjoin = '{remote_side} == {cls}.{column}'.format(
    +            remote_side=remote_side,
    +            cls=cls.__name__,
    +            column=column_name
    +        )
    +
    +        return relationship(
    +            cls._get_cls_by_tablename(cls.__tablename__).__name__,
    +            primaryjoin=primaryjoin,
    +            remote_side=remote_side,
    +            post_update=True,
    +            **relationship_kwargs
    +        )
    +
    +    @classmethod
    +    def one_to_many_relationship_to_self(cls,
    --- End diff --
    
    This one is only used in the type hierarchy structure. Maybe it'd be better not to expose this method (just implement that logic only in `TypeBase`).


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104308959
  
    --- Diff: aria/cli/args_parser.py ---
    @@ -137,10 +137,6 @@ def add_workflow_parser(workflow):
             '-w', '--workflow',
             default='install',
             help='The workflow name')
    -    workflow.add_argument(
    --- End diff --
    
    putting aside the fact this code is going to be thrown out anyway,
    how would a workflow execution not require a service instance to run on?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104312798
  
    --- Diff: aria/modeling/__init__.py ---
    @@ -16,20 +16,27 @@
     from collections import namedtuple
    --- End diff --
    
    The naming convention is inconsistent throughout the package. some are plural, while others are singular (IMO singular is better). Furthermore, it's kinda rough to figure out which module does what (without looking in the code).



---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106804940
  
    --- Diff: aria/orchestrator/workflows/builtin/utils.py ---
    @@ -14,33 +14,38 @@
     # limitations under the License.
     
     from ..api.task import OperationTask
    +from .. import exceptions
     
     
    -def create_node_task(operation_name, node):
    +def create_node_task(interface_name, operation_name, node):
         """
         Returns a new operation task if the operation exists in the node, otherwise returns None.
         """
     
    -    if _has_operation(node.interfaces, operation_name):
    -        return OperationTask.node(instance=node,
    -                                  name=operation_name)
    -    return None
    +    try:
    +        return OperationTask.for_node(node=node,
    +                                      interface_name=interface_name,
    +                                      operation_name=operation_name)
    +    except exceptions.TaskException:
    --- End diff --
    
    This might catch things we don't want to catch.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106802356
  
    --- Diff: aria/modeling/orchestration.py ---
    @@ -0,0 +1,350 @@
    +# 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.
    +
    +"""
    +classes:
    +    * Execution - execution implementation model.
    +    * Plugin - plugin implementation model.
    +    * Task - a task
    +"""
    +
    +# pylint: disable=no-self-argument, no-member, abstract-method
    +
    +from datetime import datetime
    +
    +from sqlalchemy import (
    +    Column,
    +    Integer,
    +    Text,
    +    DateTime,
    +    Boolean,
    +    Enum,
    +    String,
    +    Float,
    +    orm,
    +)
    +from sqlalchemy.ext.associationproxy import association_proxy
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from ..orchestrator.exceptions import (TaskAbortException, TaskRetryException)
    +from .types import (List, Dict)
    +from .mixins import ModelMixin
    +from . import relationships
    +
    +
    +class ExecutionBase(ModelMixin):
    +    """
    +    Execution model representation.
    +    """
    +
    +    __tablename__ = 'execution'
    +
    +    TERMINATED = 'terminated'
    +    FAILED = 'failed'
    +    CANCELLED = 'cancelled'
    +    PENDING = 'pending'
    +    STARTED = 'started'
    +    CANCELLING = 'cancelling'
    +    FORCE_CANCELLING = 'force_cancelling'
    +
    +    STATES = [TERMINATED, FAILED, CANCELLED, PENDING, STARTED, CANCELLING, FORCE_CANCELLING]
    +    END_STATES = [TERMINATED, FAILED, CANCELLED]
    +    ACTIVE_STATES = [state for state in STATES if state not in END_STATES]
    +
    +    VALID_TRANSITIONS = {
    +        PENDING: [STARTED, CANCELLED],
    +        STARTED: END_STATES + [CANCELLING],
    +        CANCELLING: END_STATES + [FORCE_CANCELLING]
    +    }
    +
    +    @orm.validates('status')
    +    def validate_status(self, key, value):
    +        """Validation function that verifies execution status transitions are OK"""
    +        try:
    +            current_status = getattr(self, key)
    +        except AttributeError:
    +            return
    +        valid_transitions = self.VALID_TRANSITIONS.get(current_status, [])
    +        if all([current_status is not None,
    +                current_status != value,
    +                value not in valid_transitions]):
    +            raise ValueError('Cannot change execution status from {current} to {new}'.format(
    +                current=current_status,
    +                new=value))
    +        return value
    +
    +    created_at = Column(DateTime, index=True)
    +    started_at = Column(DateTime, nullable=True, index=True)
    +    ended_at = Column(DateTime, nullable=True, index=True)
    +    error = Column(Text, nullable=True)
    +    is_system_workflow = Column(Boolean, nullable=False, default=False)
    +    parameters = Column(Dict)
    +    status = Column(Enum(*STATES, name='execution_status'), default=PENDING)
    +    workflow_name = Column(Text)
    +
    +    @declared_attr
    +    def service(cls):
    +        return relationships.many_to_one(cls, 'service')
    +
    +    # region foreign keys
    +
    +    @declared_attr
    +    def service_fk(cls):
    +        return relationships.fk('service')
    +
    +    # endregion
    +
    +    # region association proxies
    +
    +    @declared_attr
    +    def service_name(cls):
    +        """Required for use by SQLAlchemy queries"""
    +        return association_proxy('service', cls.name_column_name())
    +
    +    @declared_attr
    +    def service_template(cls):
    +        """Required for use by SQLAlchemy queries"""
    +        return association_proxy('service', 'service_template')
    +
    +    @declared_attr
    +    def service_template_name(cls):
    +        """Required for use by SQLAlchemy queries"""
    +        return association_proxy('service', 'service_template_name')
    +
    +    # endregion
    +
    +    def __str__(self):
    +        return '<{0} id=`{1}` (status={2})>'.format(
    +            self.__class__.__name__,
    +            getattr(self, self.name_column_name()),
    +            self.status
    +        )
    +
    +    __private_fields__ = ['service_fk',
    +                          'service_name',
    +                          'service_template',
    +                          'service_template_name']
    +
    +
    +class PluginBase(ModelMixin):
    +    """
    +    Plugin model representation.
    +    """
    +
    +    __tablename__ = 'plugin'
    +
    +    archive_name = Column(Text, nullable=False, index=True)
    +    distribution = Column(Text)
    +    distribution_release = Column(Text)
    +    distribution_version = Column(Text)
    +    package_name = Column(Text, nullable=False, index=True)
    +    package_source = Column(Text)
    +    package_version = Column(Text)
    +    supported_platform = Column(Text)
    +    supported_py_versions = Column(List)
    +    uploaded_at = Column(DateTime, nullable=False, index=True)
    +    wheels = Column(List, nullable=False)
    +
    +
    +class TaskBase(ModelMixin):
    +    """
    +    A Model which represents an task
    +    """
    +
    +    __tablename__ = 'task'
    +
    +    PENDING = 'pending'
    +    RETRYING = 'retrying'
    +    SENT = 'sent'
    +    STARTED = 'started'
    +    SUCCESS = 'success'
    +    FAILED = 'failed'
    +    STATES = (
    +        PENDING,
    +        RETRYING,
    +        SENT,
    +        STARTED,
    +        SUCCESS,
    +        FAILED,
    +    )
    +
    +    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
    +    def node(cls):
    +        return relationships.many_to_one(cls, 'node')
    +
    +    @declared_attr
    +    def relationship(cls):
    +        return relationships.many_to_one(cls, 'relationship')
    +
    +    @declared_attr
    +    def plugin(cls):
    +        return relationships.many_to_one(cls, 'plugin')
    +
    +    @declared_attr
    +    def execution(cls):
    +        return relationships.many_to_one(cls, 'execution')
    +
    +    @declared_attr
    +    def inputs(cls):
    +        return relationships.many_to_many(cls, 'parameter', prefix='inputs', dict_key='name')
    +
    +    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 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
    +
    +    @property
    +    def actor(self):
    +        """
    +        Return the actor of the task
    +        :return:
    +        """
    +        return self.node or self.relationship
    +
    +    @orm.validates('max_attempts')
    +    def validate_max_attempts(self, _, value):                                  # pylint: disable=no-self-use
    +        """Validates that max attempts is either -1 or a positive number"""
    +        if value < 1 and value != TaskBase.INFINITE_RETRIES:
    +            raise ValueError('Max attempts can be either -1 (infinite) or any positive number. '
    +                             'Got {value}'.format(value=value))
    +        return value
    +
    +    # region foreign keys
    +
    +    @declared_attr
    +    def node_fk(cls):
    +        return relationships.fk('node', nullable=True)
    --- End diff --
    
    i do think foreignkey is better than fk


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106806308
  
    --- Diff: tests/modeling/test_models.py ---
    @@ -486,83 +472,79 @@ def test_deployment_update_step_order(self):
             assert not add_node < None
     
     
    -class TestDeploymentModification(object):
    +class TestServiceModification(object):
         @pytest.mark.parametrize(
             'is_valid, context, created_at, ended_at, modified_node_templates, nodes, status',
             [
    -            (False, m_cls, now, now, {}, {}, ServiceInstanceModification.STARTED),
    -            (False, {}, m_cls, now, {}, {}, ServiceInstanceModification.STARTED),
    -            (False, {}, now, m_cls, {}, {}, ServiceInstanceModification.STARTED),
    -            (False, {}, now, now, m_cls, {}, ServiceInstanceModification.STARTED),
    -            (False, {}, now, now, {}, m_cls, ServiceInstanceModification.STARTED),
    +            (False, m_cls, now, now, {}, {}, ServiceModification.STARTED),
    +            (False, {}, m_cls, now, {}, {}, ServiceModification.STARTED),
    +            (False, {}, now, m_cls, {}, {}, ServiceModification.STARTED),
    +            (False, {}, now, now, m_cls, {}, ServiceModification.STARTED),
    +            (False, {}, now, now, {}, m_cls, ServiceModification.STARTED),
                 (False, {}, now, now, {}, {}, m_cls),
     
    -            (True, {}, now, now, {}, {}, ServiceInstanceModification.STARTED),
    -            (True, {}, now, None, {}, {}, ServiceInstanceModification.STARTED),
    -            (True, {}, now, now, None, {}, ServiceInstanceModification.STARTED),
    -            (True, {}, now, now, {}, None, ServiceInstanceModification.STARTED),
    +            (True, {}, now, now, {}, {}, ServiceModification.STARTED),
    +            (True, {}, now, None, {}, {}, ServiceModification.STARTED),
    +            (True, {}, now, now, None, {}, ServiceModification.STARTED),
    +            (True, {}, now, now, {}, None, ServiceModification.STARTED),
             ]
         )
    -    def test_deployment_modification_model_creation(
    -            self, service_instance_storage, is_valid, context, created_at, ended_at,
    -            modified_node_templates, nodes, status):
    -        deployment_modification = _test_model(
    +    def test_service_modification_model_creation(self, service_storage, is_valid, context,
    +                                                 created_at, ended_at, modified_node_templates,
    +                                                 nodes, status):
    +        service_modification = _test_model(
                 is_valid=is_valid,
    -            storage=service_instance_storage,
    -            model_cls=ServiceInstanceModification,
    +            storage=service_storage,
    +            model_cls=ServiceModification,
                 model_kwargs=dict(
    -                service_instance=service_instance_storage.service_instance.list()[0],
    +                service=service_storage.service.list()[0],
                     context=context,
                     created_at=created_at,
                     ended_at=ended_at,
    -                modified_nodes=modified_node_templates,
    -                node_instances=nodes,
    +                modified_node_templates=modified_node_templates,
    +                nodes=nodes,
                     status=status,
                 ))
             if is_valid:
    -            assert deployment_modification.service_instance == \
    -                   service_instance_storage.service_instance.list()[0]
    +            assert service_modification.service == \
    +                   service_storage.service.list()[0]
     
     
     class TestNodeTemplate(object):
         @pytest.mark.parametrize(
    -        'is_valid, name, default_instances, max_instances, min_instances, plugins, properties, '
    -        'type_name, type_hierarchy',
    +        'is_valid, name, default_instances, max_instances, min_instances, plugin_specifications, '
    +        'properties',
             [
    -            (False, m_cls, 1, 1, 1, [], [], 'type', []),
    -            (False, 'name', m_cls, 1, 1, [], [], 'type', []),
    -            (False, 'name', 1, m_cls, 1, [], [], 'type', []),
    -            (False, 'name', 1, 1, m_cls, [], [], 'type', []),
    -            (False, 'name', 1, 1, 1, m_cls, [], 'type', []),
    -            (False, 'name', 1, 1, 1, [], [], m_cls, []),
    -            (False, 'name', 1, 1, 1, [], [], 'type', m_cls),
    -            #
    -            (True, 'name', 1, 1, 1, [], [], 'type', []),
    -            (True, 'name', 1, 1, 1, None, [], 'type', []),
    -            (True, 'name', 1, 1, 1, [], [], 'type', None),
    +            (False, m_cls, 1, 1, 1, {}, {}),
    +            (False, 'name', m_cls, 1, 1, {}, {}),
    +            (False, 'name', 1, m_cls, 1, {}, {}),
    +            (False, 'name', 1, 1, m_cls, {}, {}),
    +            (False, 'name', 1, 1, 1, m_cls, {}),
    +            (False, 'name', 1, 1, 1, None, {}),
    +
    +            (True, 'name', 1, 1, 1, {}, {}),
    --- End diff --
    
    what happened to the rest of the valid node template creation params?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106943948
  
    --- Diff: aria/modeling/service_instance.py ---
    @@ -0,0 +1,1553 @@
    +# 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.
    +
    +# pylint: disable=too-many-lines, no-self-argument, no-member, abstract-method
    +
    +from sqlalchemy import (
    +    Column,
    +    Text,
    +    Integer
    +)
    +from sqlalchemy import DateTime
    +from sqlalchemy.ext.associationproxy import association_proxy
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from .mixins import InstanceModelMixin
    +from ..parser import validation
    +from ..parser.consumption import ConsumptionContext
    +from ..utils import collections, formatting, console
    +from . import (
    +    relationships,
    +    utils,
    +    types as modeling_types
    +)
    +
    +
    +class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
    +    """
    +    A service is usually an instance of a :class:`ServiceTemplate`.
    +
    +    You will usually not create it programmatically, but instead instantiate it from a service
    +    template.
    +
    +    :ivar name: Name (unique for this ARIA installation)
    +    :vartype name: basestring
    +    :ivar service_template: Template from which this service was instantiated (optional)
    +    :vartype service_template: :class:`ServiceTemplate`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar meta_data: Custom annotations
    +    :vartype meta_data: {basestring: :class:`Metadata`}
    +    :ivar node: Nodes
    +    :vartype node: {basestring: :class:`Node`}
    +    :ivar groups: Groups of nodes
    +    :vartype groups: {basestring: :class:`Group`}
    +    :ivar policies: Policies
    +    :vartype policies: {basestring: :class:`Policy`]}
    +    :ivar substitution: The entire service can appear as a node
    +    :vartype substitution: :class:`Substitution`
    +    :ivar inputs: Externally provided parameters
    +    :vartype inputs: {basestring: :class:`Parameter`}
    +    :ivar outputs: These parameters are filled in after service installation
    +    :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 required to be installed
    +    :vartype plugin_specifications: {basestring: :class:`PluginSpecification`}
    +    :ivar created_at: Creation timestamp
    +    :vartype created_at: :class:`datetime.datetime`
    +    :ivar updated_at: Update timestamp
    +    :vartype updated_at: :class:`datetime.datetime`
    +
    +    :ivar permalink: ??
    +    :vartype permalink: basestring
    +    :ivar scaling_groups: ??
    +    :vartype scaling_groups: {}
    +
    +    :ivar modifications: Modifications of this service
    +    :vartype modifications: [:class:`ServiceModification`]
    +    :ivar updates: Updates of this service
    +    :vartype updates: [:class:`ServiceUpdate`]
    +    :ivar executions: Executions on this service
    +    :vartype executions: [:class:`Execution`]
    +    """
    +
    +    __tablename__ = 'service'
    +
    +    @declared_attr
    +    def service_template(cls):
    +        return relationships.many_to_one(cls, 'service_template')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def meta_data(cls):
    +        # Warning! We cannot use the attr name "metadata" because it's used by SQLAlchemy!
    +        return relationships.many_to_many(cls, 'metadata', dict_key='name')
    +
    +    @declared_attr
    +    def nodes(cls):
    +        return relationships.one_to_many(cls, 'node', dict_key='name')
    +
    +    @declared_attr
    +    def groups(cls):
    +        return relationships.one_to_many(cls, 'group', dict_key='name')
    +
    +    @declared_attr
    +    def policies(cls):
    +        return relationships.one_to_many(cls, 'policy', dict_key='name')
    +
    +    @declared_attr
    +    def substitution(cls):
    +        return relationships.one_to_one(cls, 'substitution')
    +
    +    @declared_attr
    +    def inputs(cls):
    +        return relationships.many_to_many(cls, 'parameter', prefix='inputs', dict_key='name')
    +
    +    @declared_attr
    +    def outputs(cls):
    +        return relationships.many_to_many(cls, 'parameter', prefix='outputs', dict_key='name')
    +
    +    @declared_attr
    +    def workflows(cls):
    +        return relationships.one_to_many(cls, 'operation', dict_key='name')
    +
    +    @declared_attr
    +    def plugin_specifications(cls):
    +        return relationships.many_to_many(cls, 'plugin_specification')
    +
    +    created_at = Column(DateTime, nullable=False, index=True)
    +    updated_at = Column(DateTime)
    +
    +    # region orchestration
    +
    +    permalink = Column(Text)
    +    scaling_groups = Column(modeling_types.Dict)
    +
    +    # endregion
    +
    +    # region foreign keys
    +
    +    @declared_attr
    +    def substitution_fk(cls):
    +        """Service one-to-one to Substitution"""
    +        return relationships.fk('substitution', nullable=True)
    +
    +    @declared_attr
    +    def service_template_fk(cls):
    +        """For Service many-to-one to ServiceTemplate"""
    +        return relationships.fk('service_template', nullable=True)
    +
    +    # endregion
    +
    +    # region association proxies
    +
    +    @declared_attr
    +    def service_template_name(cls):
    +        """Required for use by SQLAlchemy queries"""
    +        return association_proxy('service_template', 'name')
    +
    +    # endregion
    +
    +    def satisfy_requirements(self):
    +        satisfied = True
    +        for node in self.nodes.itervalues():
    +            if not node.satisfy_requirements():
    +                satisfied = False
    +        return satisfied
    +
    +    def validate_capabilities(self):
    +        satisfied = True
    +        for node in self.nodes.itervalues():
    +            if not node.validate_capabilities():
    +                satisfied = False
    +        return satisfied
    +
    +    def is_node_a_target(self, target_node):
    +        for node in self.nodes.itervalues():
    +            if self._is_node_a_target(node, target_node):
    +                return True
    +        return False
    +
    +    def _is_node_a_target(self, source_node, target_node):
    +        if source_node.outbound_relationships:
    +            for relationship in source_node.outbound_relationships:
    +                if relationship.target_node.name == target_node.name:
    +                    return True
    +                else:
    +                    node = relationship.target_node
    +                    if node is not None:
    +                        if self._is_node_a_target(node, target_node):
    +                            return True
    +        return False
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('description', self.description),
    +            ('metadata', formatting.as_raw_dict(self.meta_data)),
    +            ('nodes', formatting.as_raw_list(self.nodes)),
    +            ('groups', formatting.as_raw_list(self.groups)),
    +            ('policies', formatting.as_raw_list(self.policies)),
    +            ('substitution', formatting.as_raw(self.substitution)),
    +            ('inputs', formatting.as_raw_dict(self.inputs)),
    +            ('outputs', formatting.as_raw_dict(self.outputs)),
    +            ('workflows', formatting.as_raw_list(self.workflows))))
    +
    +    def validate(self):
    +        utils.validate_dict_values(self.meta_data)
    +        utils.validate_dict_values(self.nodes)
    +        utils.validate_dict_values(self.groups)
    +        utils.validate_dict_values(self.policies)
    +        if self.substitution is not None:
    +            self.substitution.validate()
    +        utils.validate_dict_values(self.inputs)
    +        utils.validate_dict_values(self.outputs)
    +        utils.validate_dict_values(self.workflows)
    +
    +    def coerce_values(self, container, report_issues):
    +        utils.coerce_dict_values(container, self.meta_data, report_issues)
    +        utils.coerce_dict_values(container, self.nodes, report_issues)
    +        utils.coerce_dict_values(container, self.groups, report_issues)
    +        utils.coerce_dict_values(container, self.policies, report_issues)
    +        if self.substitution is not None:
    +            self.substitution.coerce_values(container, report_issues)
    +        utils.coerce_dict_values(container, self.inputs, report_issues)
    +        utils.coerce_dict_values(container, self.outputs, report_issues)
    +        utils.coerce_dict_values(container, self.workflows, report_issues)
    +
    +    def dump(self):
    +        context = ConsumptionContext.get_thread_local()
    +        if self.description is not None:
    +            console.puts(context.style.meta(self.description))
    +        utils.dump_dict_values(self.meta_data, 'Metadata')
    +        for node in self.nodes.itervalues():
    +            node.dump()
    +        for group in self.groups.itervalues():
    +            group.dump()
    +        for policy in self.policies.itervalues():
    +            policy.dump()
    +        if self.substitution is not None:
    +            self.substitution.dump()
    +        utils.dump_dict_values(self.inputs, 'Inputs')
    +        utils.dump_dict_values(self.outputs, 'Outputs')
    +        utils.dump_dict_values(self.workflows, 'Workflows')
    +
    +    def dump_graph(self):
    +        for node in self.nodes.itervalues():
    +            if not self.is_node_a_target(node):
    +                self._dump_graph_node(node)
    +
    +    def _dump_graph_node(self, node):
    +        context = ConsumptionContext.get_thread_local()
    +        console.puts(context.style.node(node.name))
    +        if node.outbound_relationships:
    +            with context.style.indent:
    +                for relationship in node.outbound_relationships:
    +                    if relationship.relationship_template is not None:
    +                        relationship_name = context.style.node(
    +                            relationship.relationship_template.name)
    +                    elif relationship.type is not None:
    +                        relationship_name = context.style.type(relationship.type.name)
    +                    else:
    +                        relationship_name = '?'
    +                    if relationship.target_capability is not None:
    +                        capability_name = context.style.node(relationship.target_capability.name)
    +                    else:
    +                        capability_name = None
    +                    if capability_name is not None:
    +                        console.puts('-> {0} {1}'.format(relationship_name, capability_name))
    +                    else:
    +                        console.puts('-> {0}'.format(relationship_name))
    +                    target_node = relationship.target_node
    +                    with console.indent(3):
    +                        self._dump_graph_node(target_node)
    +
    +    __private_fields__ = ['substitution_fk',
    +                          'service_template_fk',
    +                          'service_template_name']
    +
    +
    +class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
    +    """
    +    Usually an instance of a :class:`NodeTemplate`.
    +
    +    Nodes may have zero or more :class:`Relationship` instances to other nodes.
    +
    +    :ivar name: Name (unique for this service)
    +    :vartype name: basestring
    +    :ivar node_template: Template from which this node was instantiated (optional)
    +    :vartype node_template: :class:`NodeTemplate`
    +    :ivar type: Node type
    +    :vartype type: :class:`Type`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar properties: Associated parameters
    +    :vartype properties: {basestring: :class:`Parameter`}
    +    :ivar interfaces: Bundles of operations
    +    :vartype interfaces: {basestring: :class:`Interface`}
    +    :ivar artifacts: Associated files
    +    :vartype artifacts: {basestring: :class:`Artifact`}
    +    :ivar capabilities: Exposed capabilities
    +    :vartype capabilities: {basestring: :class:`Capability`}
    +    :ivar outbound_relationships: Relationships to other nodes
    +    :vartype outbound_relationships: [:class:`Relationship`]
    +    :ivar inbound_relationships: Relationships from other nodes
    +    :vartype inbound_relationships: [:class:`Relationship`]
    +    :ivar plugin_specifications: Plugins required to be installed on the node's host
    +    :vartype plugin_specifications: {basestring: :class:`PluginSpecification`}
    +    :ivar host: Host node (can be self)
    +    :vartype host: :class:`Node`
    +
    +    :ivar runtime_properties: TODO: should be replaced with attributes
    +    :vartype runtime_properties: {}
    +    :ivar scaling_groups: ??
    +    :vartype scaling_groups: []
    +    :ivar state: ??
    +    :vartype state: basestring
    +    :ivar version: Used by `aria.storage.instrumentation`
    +    :vartype version: int
    +
    +    :ivar service: Containing service
    +    :vartype service: :class:`Service`
    +    :ivar groups: We are a member of these groups
    +    :vartype groups: [:class:`Group`]
    +    :ivar policies: Policies enacted on this node
    +    :vartype policies: [:class:`Policy`]
    +    :ivar substitution_mapping: Our contribution to service substitution
    +    :vartype substitution_mapping: :class:`SubstitutionMapping`
    +    :ivar tasks: Tasks on this node
    +    :vartype tasks: [:class:`Task`]
    +    """
    +
    +    __tablename__ = 'node'
    +
    +    @declared_attr
    +    def node_template(cls):
    +        return relationships.many_to_one(cls, 'node_template')
    +
    +    @declared_attr
    +    def type(cls):
    +        return relationships.many_to_one(cls, 'type')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def properties(cls):
    +        return relationships.many_to_many(cls, 'parameter', prefix='properties', dict_key='name')
    +
    +    @declared_attr
    +    def interfaces(cls):
    +        return relationships.one_to_many(cls, 'interface', dict_key='name')
    +
    +    @declared_attr
    +    def artifacts(cls):
    +        return relationships.one_to_many(cls, 'artifact', dict_key='name')
    +
    +    @declared_attr
    +    def capabilities(cls):
    +        return relationships.one_to_many(cls, 'capability', dict_key='name')
    +
    +    @declared_attr
    +    def outbound_relationships(cls):
    +        return relationships.one_to_many(cls, 'relationship', child_fk='source_node_fk',
    +                                         child_property='source_node')
    +
    +    @declared_attr
    +    def inbound_relationships(cls):
    +        return relationships.one_to_many(cls, 'relationship', child_fk='target_node_fk',
    +                                         child_property='target_node')
    +
    +    @declared_attr
    +    def plugin_specifications(cls):
    +        return relationships.many_to_many(cls, 'plugin_specification', dict_key='name')
    +
    +    @declared_attr
    +    def host(cls):
    +        return relationships.one_to_one_self(cls, 'host_fk')
    +
    +    # region orchestration
    +
    +    runtime_properties = Column(modeling_types.Dict)
    +    scaling_groups = Column(modeling_types.List)
    +    state = Column(Text, nullable=False)
    +    version = Column(Integer, default=1)
    +
    +    __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
    +        return None
    +
    +    # endregion
    +
    +    # region foreign_keys
    +
    +    @declared_attr
    +    def type_fk(cls):
    +        """For Node many-to-one to Type"""
    +        return relationships.fk('type')
    +
    +    @declared_attr
    +    def host_fk(cls):
    +        """For Node one-to-one to Node"""
    +        return relationships.fk('node', nullable=True)
    +
    +    @declared_attr
    +    def service_fk(cls):
    +        """For Service one-to-many to Node"""
    +        return relationships.fk('service')
    +
    +    @declared_attr
    +    def node_template_fk(cls):
    +        """For Node many-to-one to NodeTemplate"""
    +        return relationships.fk('node_template', nullable=True)
    +
    +    # endregion
    +
    +    # region association proxies
    +
    +    @declared_attr
    +    def service_name(cls):
    +        """Required for use by SQLAlchemy queries"""
    +        return association_proxy('service', 'name')
    +
    +    # endregion
    +
    +    def satisfy_requirements(self):
    +        node_template = self.node_template
    +        satisfied = True
    +        for requirement_template in node_template.requirement_templates:
    +            # Find target template
    +            target_node_template, target_node_capability = \
    +                requirement_template.find_target(node_template)
    +            if target_node_template is not None:
    +                satisfied = self._satisfy_capability(target_node_capability,
    +                                                     target_node_template,
    +                                                     requirement_template)
    +            else:
    +                context = ConsumptionContext.get_thread_local()
    +                context.validation.report('requirement "{0}" of node "{1}" has no target node '
    +                                          'template'.format(requirement_template.name, self.name),
    +                                          level=validation.Issue.BETWEEN_INSTANCES)
    +                satisfied = False
    +        return satisfied
    +
    +    def _satisfy_capability(self, target_node_capability, target_node_template,
    +                            requirement_template):
    +        from . import models
    +        context = ConsumptionContext.get_thread_local()
    +        # Find target nodes
    +        target_nodes = target_node_template.nodes.all()
    +        if target_nodes:
    +            target_node = None
    +            target_capability = None
    +
    +            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():
    +                        target_node = node
    +                        break
    +            else:
    +                # Use first target node
    +                target_node = target_nodes[0]
    +
    +            if target_node is not None:
    +                if requirement_template.relationship_template is not None:
    +                    relationship = \
    +                        requirement_template.relationship_template.instantiate(self)
    +                else:
    +                    relationship = models.Relationship(target_capability=target_capability)
    +                relationship.name = requirement_template.name
    +                relationship.requirement_template = requirement_template
    +                relationship.target_node = target_node
    +                self.outbound_relationships.append(relationship)
    +                return True
    +            else:
    +                context.validation.report('requirement "{0}" of node "{1}" targets node '
    +                                          'template "{2}" but its instantiated nodes do not '
    +                                          'have enough capacity'.format(
    +                                              requirement_template.name,
    +                                              self.name,
    +                                              target_node_template.name),
    +                                          level=validation.Issue.BETWEEN_INSTANCES)
    +                return False
    +        else:
    +            context.validation.report('requirement "{0}" of node "{1}" targets node template '
    +                                      '"{2}" but it has no instantiated nodes'.format(
    +                                          requirement_template.name,
    +                                          self.name,
    +                                          target_node_template.name),
    +                                      level=validation.Issue.BETWEEN_INSTANCES)
    +            return False
    +
    +    def validate_capabilities(self):
    +        context = ConsumptionContext.get_thread_local()
    +        satisfied = False
    +        for capability in self.capabilities.itervalues():
    +            if not capability.has_enough_relationships:
    +                context.validation.report('capability "{0}" of node "{1}" requires at least {2:d} '
    +                                          'relationships but has {3:d}'.format(
    +                                              capability.name,
    +                                              self.name,
    +                                              capability.min_occurrences,
    +                                              capability.occurrences),
    +                                          level=validation.Issue.BETWEEN_INSTANCES)
    +                satisfied = False
    +        return satisfied
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('name', self.name),
    +            ('type_name', self.type.name),
    +            ('properties', formatting.as_raw_dict(self.properties)),
    +            ('interfaces', formatting.as_raw_list(self.interfaces)),
    +            ('artifacts', formatting.as_raw_list(self.artifacts)),
    +            ('capabilities', formatting.as_raw_list(self.capabilities)),
    +            ('relationships', formatting.as_raw_list(self.outbound_relationships))))
    +
    +    def validate(self):
    +        context = ConsumptionContext.get_thread_local()
    +        if len(self.name) > context.modeling.id_max_length:
    +            context.validation.report('"{0}" has an ID longer than the limit of {1:d} characters: '
    +                                      '{2:d}'.format(
    +                                          self.name,
    +                                          context.modeling.id_max_length,
    +                                          len(self.name)),
    +                                      level=validation.Issue.BETWEEN_INSTANCES)
    +
    +        # TODO: validate that node template is of type?
    +
    +        utils.validate_dict_values(self.properties)
    +        utils.validate_dict_values(self.interfaces)
    +        utils.validate_dict_values(self.artifacts)
    +        utils.validate_dict_values(self.capabilities)
    +        utils.validate_list_values(self.outbound_relationships)
    +
    +    def coerce_values(self, container, report_issues):
    +        utils.coerce_dict_values(self, self.properties, report_issues)
    +        utils.coerce_dict_values(self, self.interfaces, report_issues)
    +        utils.coerce_dict_values(self, self.artifacts, report_issues)
    +        utils.coerce_dict_values(self, self.capabilities, report_issues)
    +        utils.coerce_list_values(self, self.outbound_relationships, report_issues)
    +
    +    def dump(self):
    +        context = ConsumptionContext.get_thread_local()
    +        console.puts('Node: {0}'.format(context.style.node(self.name)))
    +        with context.style.indent:
    +            console.puts('Type: {0}'.format(context.style.type(self.type.name)))
    +            console.puts('Template: {0}'.format(context.style.node(self.node_template.name)))
    +            utils.dump_dict_values(self.properties, 'Properties')
    +            utils.dump_interfaces(self.interfaces)
    +            utils.dump_dict_values(self.artifacts, 'Artifacts')
    +            utils.dump_dict_values(self.capabilities, 'Capabilities')
    +            utils.dump_list_values(self.outbound_relationships, 'Relationships')
    +
    +    __private_fields__ = ['type_fk',
    +                          'host_fk',
    +                          'service_fk',
    +                          'node_template_fk',
    +                          'service_name']
    +
    +class GroupBase(InstanceModelMixin):
    +    """
    +    Usually an instance of a :class:`GroupTemplate`.
    +
    +    :ivar name: Name (unique for this service)
    +    :vartype name: basestring
    +    :ivar group_template: Template from which this group was instantiated (optional)
    +    :vartype group_template: :class:`GroupTemplate`
    +    :ivar type: Group type
    +    :vartype type: :class:`Type`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar nodes: Members of this group
    +    :vartype nodes: [:class:`Node`]
    +    :ivar properties: Associated parameters
    +    :vartype properties: {basestring: :class:`Parameter`}
    +    :ivar interfaces: Bundles of operations
    +    :vartype interfaces: {basestring: :class:`Interface`}
    +
    +    :ivar service: Containing service
    +    :vartype service: :class:`Service`
    +    :ivar policies: Policies enacted on this group
    +    :vartype policies: [:class:`Policy`]
    +    """
    +
    +    __tablename__ = 'group'
    +
    +    @declared_attr
    +    def group_template(cls):
    +        return relationships.many_to_one(cls, 'group_template')
    +
    +    @declared_attr
    +    def type(cls):
    +        return relationships.many_to_one(cls, 'type')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def nodes(cls):
    +        return relationships.many_to_many(cls, 'node')
    +
    +    @declared_attr
    +    def properties(cls):
    +        return relationships.many_to_many(cls, 'parameter', prefix='properties', dict_key='name')
    +
    +    @declared_attr
    +    def interfaces(cls):
    +        return relationships.one_to_many(cls, 'interface', dict_key='name')
    +
    +    # region foreign_keys
    +
    +    @declared_attr
    +    def type_fk(cls):
    +        """For Group many-to-one to Type"""
    +        return relationships.fk('type')
    +
    +    @declared_attr
    +    def service_fk(cls):
    +        """For Service one-to-many to Group"""
    +        return relationships.fk('service')
    +
    +    @declared_attr
    +    def group_template_fk(cls):
    +        """For Group many-to-one to GroupTemplate"""
    +        return relationships.fk('group_template', nullable=True)
    +
    +    # endregion
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('name', self.name),
    +            ('properties', formatting.as_raw_dict(self.properties)),
    +            ('interfaces', formatting.as_raw_list(self.interfaces))))
    +
    +    def validate(self):
    +        utils.validate_dict_values(self.properties)
    +        utils.validate_dict_values(self.interfaces)
    +
    +    def coerce_values(self, container, report_issues):
    +        utils.coerce_dict_values(container, self.properties, report_issues)
    +        utils.coerce_dict_values(container, self.interfaces, report_issues)
    +
    +    def dump(self):
    +        context = ConsumptionContext.get_thread_local()
    +        console.puts('Group: {0}'.format(context.style.node(self.name)))
    +        with context.style.indent:
    +            console.puts('Type: {0}'.format(context.style.type(self.type.name)))
    +            utils.dump_dict_values(self.properties, 'Properties')
    +            utils.dump_interfaces(self.interfaces)
    +            if self.nodes:
    +                console.puts('Member nodes:')
    +                with context.style.indent:
    +                    for node in self.nodes:
    +                        console.puts(context.style.node(node.name))
    +
    +    __private_fields__ = ['type_fk',
    +                          'service_fk',
    +                          'group_template_fk']
    +
    +
    +class PolicyBase(InstanceModelMixin):
    +    """
    +    Usually an instance of a :class:`PolicyTemplate`.
    +
    +    :ivar name: Name (unique for this service)
    +    :vartype name: basestring
    +    :ivar policy_template: Template from which this policy was instantiated (optional)
    +    :vartype policy_template: :class:`PolicyTemplate`
    +    :ivar type: Policy type
    +    :vartype type: :class:`Type`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar nodes: Policy will be enacted on all these nodes
    +    :vartype nodes: [:class:`Node`]
    +    :ivar groups: Policy will be enacted on all nodes in these groups
    +    :vartype groups: [:class:`Group`]
    +    :ivar properties: Associated parameters
    +    :vartype properties: {basestring: :class:`Parameter`}
    +
    +    :ivar service: Containing service
    +    :vartype service: :class:`Service`
    +    """
    +
    +    __tablename__ = 'policy'
    +
    +    @declared_attr
    +    def policy_template(cls):
    +        return relationships.many_to_one(cls, 'policy_template')
    +
    +    @declared_attr
    +    def type(cls):
    +        return relationships.many_to_one(cls, 'type')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def properties(cls):
    +        return relationships.many_to_many(cls, 'parameter', prefix='properties', dict_key='name')
    +
    +    @declared_attr
    +    def nodes(cls):
    +        return relationships.many_to_many(cls, 'node')
    +
    +    @declared_attr
    +    def groups(cls):
    +        return relationships.many_to_many(cls, 'group')
    +
    +    # region foreign_keys
    +
    +    @declared_attr
    +    def type_fk(cls):
    +        """For Policy many-to-one to Type"""
    +        return relationships.fk('type')
    +
    +    @declared_attr
    +    def service_fk(cls):
    +        """For Service one-to-many to Policy"""
    +        return relationships.fk('service')
    +
    +    @declared_attr
    +    def policy_template_fk(cls):
    +        """For Policy many-to-one to PolicyTemplate"""
    +        return relationships.fk('policy_template', nullable=True)
    +
    +    # endregion
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('name', self.name),
    +            ('type_name', self.type.name),
    +            ('properties', formatting.as_raw_dict(self.properties))))
    +
    +    def validate(self):
    +        utils.validate_dict_values(self.properties)
    +
    +    def coerce_values(self, container, report_issues):
    +        utils.coerce_dict_values(container, self.properties, report_issues)
    +
    +    def dump(self):
    +        context = ConsumptionContext.get_thread_local()
    +        console.puts('Policy: {0}'.format(context.style.node(self.name)))
    +        with context.style.indent:
    +            console.puts('Type: {0}'.format(context.style.type(self.type.name)))
    +            utils.dump_dict_values(self.properties, 'Properties')
    +            if self.nodes:
    +                console.puts('Target nodes:')
    +                with context.style.indent:
    +                    for node in self.nodes:
    +                        console.puts(context.style.node(node.name))
    +            if self.groups:
    +                console.puts('Target groups:')
    +                with context.style.indent:
    +                    for group in self.groups:
    +                        console.puts(context.style.node(group.name))
    +
    +    __private_fields__ = ['type_fk',
    +                          'service_fk',
    +                          'policy_template_fk']
    +
    +
    +class SubstitutionBase(InstanceModelMixin):
    +    """
    +    Used to substitute a single node for the entire deployment.
    +
    +    Usually an instance of a :class:`SubstitutionTemplate`.
    +
    +    :ivar substitution_template: Template from which this substitution was instantiated (optional)
    +    :vartype substitution_template: :class:`SubstitutionTemplate`
    +    :ivar node_type: Exposed node type
    +    :vartype node_type: :class:`Type`
    +    :ivar mappings: Requirement and capability mappings
    +    :vartype mappings: {basestring: :class:`SubstitutionTemplate`}
    +
    +    :ivar service: Containing service
    +    :vartype service: :class:`Service`
    +    """
    +
    +    __tablename__ = 'substitution'
    +
    +    @declared_attr
    +    def substitution_template(cls):
    +        return relationships.many_to_one(cls, 'substitution_template')
    +
    +    @declared_attr
    +    def node_type(cls):
    +        return relationships.many_to_one(cls, 'type')
    +
    +    @declared_attr
    +    def mappings(cls):
    +        return relationships.one_to_many(cls, 'substitution_mapping', dict_key='name')
    +
    +    # region foreign_keys
    +
    +    @declared_attr
    +    def node_type_fk(cls):
    +        """For Substitution many-to-one to Type"""
    +        return relationships.fk('type')
    +
    +    @declared_attr
    +    def substitution_template_fk(cls):
    +        """For Substitution many-to-one to SubstitutionTemplate"""
    +        return relationships.fk('substitution_template', nullable=True)
    +
    +    # endregion
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('node_type_name', self.node_type_name),
    +            ('mappings', formatting.as_raw_dict(self.mappings))))
    +
    +    def validate(self):
    +        utils.validate_dict_values(self.mappings)
    +
    +    def coerce_values(self, container, report_issues):
    +        utils.coerce_dict_values(container, self.mappings, report_issues)
    +
    +    def dump(self):
    +        context = ConsumptionContext.get_thread_local()
    +        console.puts('Substitution:')
    +        with context.style.indent:
    +            console.puts('Node type: {0}'.format(context.style.type(self.node_type.name)))
    +            utils.dump_dict_values(self.mappings, 'Mappings')
    +
    +    __private_fields__ = ['node_type_fk',
    +                          'substitution_template_fk']
    +
    +
    +class SubstitutionMappingBase(InstanceModelMixin):
    +    """
    +    Used by :class:`Substitution` to map a capability or a requirement to a node.
    +
    +    Only one of `capability_template` and `requirement_template` can be set.
    +
    +    Usually an instance of a :class:`SubstitutionTemplate`.
    +
    +    :ivar name: Exposed capability or requirement name
    +    :vartype name: basestring
    +    :ivar node: Node
    +    :vartype node: :class:`Node`
    +    :ivar capability: Capability in the node
    +    :vartype capability: :class:`Capability`
    +    :ivar requirement_template: Requirement template in the node template
    +    :vartype requirement_template: :class:`RequirementTemplate`
    +
    +    :ivar substitution: Containing substitution
    +    :vartype substitution: :class:`Substitution`
    +    """
    +
    +    __tablename__ = 'substitution_mapping'
    +
    +    @declared_attr
    +    def node(cls):
    +        return relationships.one_to_one(cls, 'node')
    +
    +    @declared_attr
    +    def capability(cls):
    +        return relationships.one_to_one(cls, 'capability')
    +
    +    @declared_attr
    +    def requirement_template(cls):
    +        return relationships.one_to_one(cls, 'requirement_template')
    +
    +    # region foreign keys
    +
    +    @declared_attr
    +    def substitution_fk(cls):
    +        """For Substitution one-to-many to SubstitutionMapping"""
    +        return relationships.fk('substitution')
    +
    +    @declared_attr
    +    def node_fk(cls):
    +        """For Substitution one-to-one to NodeTemplate"""
    +        return relationships.fk('node')
    +
    +    @declared_attr
    +    def capability_fk(cls):
    +        """For Substitution one-to-one to Capability"""
    +        return relationships.fk('capability', nullable=True)
    +
    +    @declared_attr
    +    def requirement_template_fk(cls):
    +        """For Substitution one-to-one to RequirementTemplate"""
    +        return relationships.fk('requirement_template', nullable=True)
    +
    +    # endregion
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('name', self.name)))
    +
    +    def validate(self):
    +        context = ConsumptionContext.get_thread_local()
    +        if (self.capability is None) and (self.requirement_template is None):
    +            context.validation.report('mapping "{0}" refers to neither capability nor a requirement'
    +                                      ' in node: {1}'.format(
    +                                          self.name,
    +                                          formatting.safe_repr(self.node.name)),
    +                                      level=validation.Issue.BETWEEN_TYPES)
    +
    +    def dump(self):
    +        context = ConsumptionContext.get_thread_local()
    +        console.puts('{0} -> {1}.{2}'.format(
    +            context.style.node(self.name),
    +            context.style.node(self.node.name),
    +            context.style.node(self.capability.name
    +                               if self.capability
    +                               else self.requirement_template.name)))
    +
    +    __private_fields__ = ['substitution_fk',
    +                          'node_fk',
    +                          'capability_fk',
    +                          'requirement_template_fk']
    +
    +
    +class RelationshipBase(InstanceModelMixin):
    +    """
    +    Connects :class:`Node` to a capability in another node.
    +
    +    Might be an instance of a :class:`RelationshipTemplate`.
    +
    +    :ivar name: Name (usually the name of the requirement at the source node template)
    +    :vartype name: basestring
    +    :ivar relationship_template: Template from which this relationship was instantiated (optional)
    +    :vartype relationship_template: :class:`RelationshipTemplate`
    +    :ivar requirement_template: Template from which this relationship was instantiated (optional)
    +    :vartype requirement_template: :class:`RequirementTemplate`
    +    :ivar type: Relationship type
    +    :vartype type: :class:`Type`
    +    :ivar target_capability: Capability at the target node (optional)
    +    :vartype target_capability: :class:`Capability`
    +    :ivar properties: Associated parameters
    +    :vartype properties: {basestring: :class:`Parameter`}
    +    :ivar interfaces: Bundles of operations
    +    :vartype interfaces: {basestring: :class:`Interfaces`}
    +
    +    :ivar source_position: ??
    +    :vartype source_position: int
    +    :ivar target_position: ??
    +    :vartype target_position: int
    +
    +    :ivar source_node: Source node
    +    :vartype source_node: :class:`Node`
    +    :ivar target_node: Target node
    +    :vartype target_node: :class:`Node`
    +    :ivar tasks: Tasks on this node
    +    :vartype tasks: [:class:`Task`]
    +    """
    +
    +    __tablename__ = 'relationship'
    +
    +    @declared_attr
    +    def relationship_template(cls):
    +        return relationships.many_to_one(cls, 'relationship_template')
    +
    +    @declared_attr
    +    def requirement_template(cls):
    +        return relationships.many_to_one(cls, 'requirement_template')
    +
    +    @declared_attr
    +    def type(cls):
    +        return relationships.many_to_one(cls, 'type')
    +
    +    @declared_attr
    +    def target_capability(cls):
    +        return relationships.one_to_one(cls, 'capability')
    +
    +    @declared_attr
    +    def properties(cls):
    +        return relationships.many_to_many(cls, 'parameter', prefix='properties', dict_key='name')
    +
    +    @declared_attr
    +    def interfaces(cls):
    +        return relationships.one_to_many(cls, 'interface', dict_key='name')
    +
    +    # region orchestration
    +
    +    source_position = Column(Integer) # ???
    +    target_position = Column(Integer) # ???
    +
    +    # endregion
    +
    +    # region foreign keys
    +
    +    @declared_attr
    +    def type_fk(cls):
    +        """For Relationship many-to-one to Type"""
    +        return relationships.fk('type', nullable=True)
    +
    +    @declared_attr
    +    def source_node_fk(cls):
    +        """For Node one-to-many to Relationship"""
    +        return relationships.fk('node')
    +
    +    @declared_attr
    +    def target_node_fk(cls):
    +        """For Node one-to-many to Relationship"""
    +        return relationships.fk('node')
    +
    +    @declared_attr
    +    def target_capability_fk(cls):
    +        """For Relationship one-to-one to Capability"""
    +        return relationships.fk('capability', nullable=True)
    +
    +    @declared_attr
    +    def requirement_template_fk(cls):
    +        """For Relationship many-to-one to RequirementTemplate"""
    +        return relationships.fk('requirement_template', nullable=True)
    +
    +    @declared_attr
    +    def relationship_template_fk(cls):
    +        """For Relationship many-to-one to RelationshipTemplate"""
    +        return relationships.fk('relationship_template', nullable=True)
    +
    +    # endregion
    +
    +    # region association proxies
    +
    +    @declared_attr
    +    def source_node_name(cls):
    +        """Required for use by SQLAlchemy queries"""
    +        return association_proxy('source_node', 'name')
    +
    +    @declared_attr
    +    def target_node_name(cls):
    +        """Required for use by SQLAlchemy queries"""
    +        return association_proxy('target_node', 'name')
    +
    +    # endregion
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('name', self.name),
    +            ('target_node_id', self.target_node.name),
    +            ('type_name', self.type.name
    +             if self.type is not None else None),
    +            ('template_name', self.relationship_template.name
    +             if self.relationship_template is not None else None),
    +            ('properties', formatting.as_raw_dict(self.properties)),
    +            ('interfaces', formatting.as_raw_list(self.interfaces))))
    +
    +    def validate(self):
    +        utils.validate_dict_values(self.properties)
    +        utils.validate_dict_values(self.interfaces)
    +
    +    def coerce_values(self, container, report_issues):
    +        utils.coerce_dict_values(container, self.properties, report_issues)
    +        utils.coerce_dict_values(container, self.interfaces, report_issues)
    +
    +    def dump(self):
    +        context = ConsumptionContext.get_thread_local()
    +        if self.name:
    +            console.puts('{0} ->'.format(context.style.node(self.name)))
    +        else:
    +            console.puts('->')
    +        with context.style.indent:
    +            console.puts('Node: {0}'.format(context.style.node(self.target_node.name)))
    +            if self.target_capability:
    +                console.puts('Capability: {0}'.format(context.style.node(
    +                    self.target_capability.name)))
    +            if self.type is not None:
    +                console.puts('Relationship type: {0}'.format(context.style.type(self.type.name)))
    +            if (self.relationship_template is not None) and self.relationship_template.name:
    +                console.puts('Relationship template: {0}'.format(
    +                    context.style.node(self.relationship_template.name)))
    +            utils.dump_dict_values(self.properties, 'Properties')
    +            utils.dump_interfaces(self.interfaces, 'Interfaces')
    +
    +    __private_fields__ = ['type_fk',
    --- End diff --
    
    I always prefer everything private to be pushed to the end, because it's an implementation detail. People reading the code want to see what's public first. But I will change it to your preference.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---


[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106946846
  
    --- Diff: aria/modeling/models.py ---
    @@ -0,0 +1,283 @@
    +# 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.
    +
    +# pylint: disable=abstract-method
    +
    +from sqlalchemy.ext.declarative import declarative_base
    +
    +from . import (
    +    service_template,
    +    service_instance,
    +    service_changes,
    +    service_common,
    +    orchestration,
    +    mixins,
    +)
    +
    +
    +aria_declarative_base = declarative_base(cls=mixins.ModelIDMixin)
    +
    +
    +# region service template models
    +
    +class ServiceTemplate(aria_declarative_base, service_template.ServiceTemplateBase):
    +    pass
    +
    +
    +class NodeTemplate(aria_declarative_base, service_template.NodeTemplateBase):
    +    pass
    +
    +
    +class GroupTemplate(aria_declarative_base, service_template.GroupTemplateBase):
    +    pass
    +
    +
    +class PolicyTemplate(aria_declarative_base, service_template.PolicyTemplateBase):
    +    pass
    +
    +
    +class SubstitutionTemplate(aria_declarative_base, service_template.SubstitutionTemplateBase):
    +    pass
    +
    +
    +class SubstitutionTemplateMapping(aria_declarative_base,
    +                                  service_template.SubstitutionTemplateMappingBase):
    +    pass
    +
    +
    +class RequirementTemplate(aria_declarative_base, service_template.RequirementTemplateBase):
    +    pass
    +
    +
    +class RelationshipTemplate(aria_declarative_base, service_template.RelationshipTemplateBase):
    +    pass
    +
    +
    +class CapabilityTemplate(aria_declarative_base, service_template.CapabilityTemplateBase):
    +    pass
    +
    +
    +class InterfaceTemplate(aria_declarative_base, service_template.InterfaceTemplateBase):
    +    pass
    +
    +
    +class OperationTemplate(aria_declarative_base, service_template.OperationTemplateBase):
    +    pass
    +
    +
    +class ArtifactTemplate(aria_declarative_base, service_template.ArtifactTemplateBase):
    +    pass
    +
    +# endregion
    +
    +
    +# region service instance models
    +
    +class Service(aria_declarative_base, service_instance.ServiceBase):
    +    pass
    +
    +
    +class Node(aria_declarative_base, service_instance.NodeBase):
    +    pass
    +
    +
    +class Group(aria_declarative_base, service_instance.GroupBase):
    +    pass
    +
    +
    +class Policy(aria_declarative_base, service_instance.PolicyBase):
    +    pass
    +
    +
    +class Substitution(aria_declarative_base, service_instance.SubstitutionBase):
    +    pass
    +
    +
    +class SubstitutionMapping(aria_declarative_base, service_instance.SubstitutionMappingBase):
    +    pass
    +
    +
    +class Relationship(aria_declarative_base, service_instance.RelationshipBase):
    +    pass
    +
    +
    +class Capability(aria_declarative_base, service_instance.CapabilityBase):
    +    pass
    +
    +
    +class Interface(aria_declarative_base, service_instance.InterfaceBase):
    +    pass
    +
    +
    +class Operation(aria_declarative_base, service_instance.OperationBase):
    +    pass
    +
    +
    +class Artifact(aria_declarative_base, service_instance.ArtifactBase):
    +    pass
    +
    +# endregion
    +
    +
    +# region service changes models
    +
    +class ServiceUpdate(aria_declarative_base, service_changes.ServiceUpdateBase):
    +    pass
    +
    +
    +class ServiceUpdateStep(aria_declarative_base, service_changes.ServiceUpdateStepBase):
    +    pass
    +
    +
    +class ServiceModification(aria_declarative_base, service_changes.ServiceModificationBase):
    +    pass
    +
    +# endregion
    +
    +
    +# region common service models
    +
    +class Parameter(aria_declarative_base, service_common.ParameterBase):
    +    pass
    +
    +
    +class Type(aria_declarative_base, service_common.TypeBase):
    +    pass
    +
    +
    +class Metadata(aria_declarative_base, service_common.MetadataBase):
    +    pass
    +
    +
    +class PluginSpecification(aria_declarative_base, service_common.PluginSpecificationBase):
    +    pass
    +
    +# endregion
    +
    +
    +# region orchestration models
    +
    +class Execution(aria_declarative_base, orchestration.ExecutionBase):
    +    pass
    +
    +
    +class Plugin(aria_declarative_base, orchestration.PluginBase):
    +    pass
    +
    +
    +class Task(aria_declarative_base, orchestration.TaskBase):
    +    pass
    +
    +
    +class Log(aria_declarative_base, orchestration.LogBase):
    +    pass
    +
    +# endregion
    +
    +
    +models_to_register = [
    +    # Service template models
    +    ServiceTemplate,
    +    NodeTemplate,
    +    GroupTemplate,
    +    PolicyTemplate,
    +    SubstitutionTemplate,
    +    SubstitutionTemplateMapping,
    +    RequirementTemplate,
    +    RelationshipTemplate,
    +    CapabilityTemplate,
    +    InterfaceTemplate,
    +    OperationTemplate,
    +    ArtifactTemplate,
    +
    +    # Service instance models
    +    Service,
    +    Node,
    +    Group,
    +    Policy,
    +    SubstitutionMapping,
    +    Substitution,
    +    Relationship,
    +    Capability,
    +    Interface,
    +    Operation,
    +    Artifact,
    +
    +    # Service changes models
    +    ServiceUpdate,
    +    ServiceUpdateStep,
    +    ServiceModification,
    +
    +    # Common service models
    +    Parameter,
    +    Type,
    +    Metadata,
    +    PluginSpecification,
    +
    +    # Orchestration models
    +    Execution,
    +    Plugin,
    +    Task,
    +    Log
    +]
    +
    +__all__ = (
    --- End diff --
    
    I wanted this to be close to `models_to_register` because they are almost the same, but I will do it your way. I am adding comments though so people will know to update the other list.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104313280
  
    --- Diff: aria/modeling/types.py ---
    @@ -23,7 +23,7 @@
     )
     from sqlalchemy.ext import mutable
     
    -from .. import exceptions
    +from ..storage import exceptions
    --- End diff --
    
    I think we should create new exceptions - modelling exceptions. from within the same package it think it's better if we trow package specific exceptions.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106946059
  
    --- Diff: aria/modeling/relationships.py ---
    @@ -0,0 +1,402 @@
    +# Licensed to the Apache Software Foundation (ASF) under one or more
    --- End diff --
    
    Well, we usually name our modules in plural. But OK, I will change this.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106943057
  
    --- Diff: aria/orchestrator/workflows/api/task.py ---
    @@ -92,144 +93,133 @@ def __init__(self,
                                    if ignore_failure is None else ignore_failure)
             self.runs_on = runs_on
     
    -    @classmethod
    -    def _merge_inputs(cls, operation_inputs, additional_inputs=None):
    -        final_inputs = dict((p.name, p.as_raw['value']) for p in operation_inputs)
    -        final_inputs.update(additional_inputs or {})
    -        return final_inputs
    +        # Wrap inputs
    +        if inputs:
    +            for k, v in inputs.iteritems():
    +                if not isinstance(v, models.Parameter):
    +                    inputs[k] = models.Parameter.wrap(k, v)
    +
    +        # TODO: These extra inputs should likely be stored as a separate entry in the task model,
    +        # because they are different from the operation inputs. The two kinds of inputs should also
    +        # not be merged.
    +
    +        if interface_name or operation_name:
    +            operation = OperationTask._get_operation(actor.interfaces, interface_name,
    +                                                     operation_name)
    +            if operation is None:
    +                raise exceptions.TaskException(
    +                    'Could not find operation "{0}" on interface "{1}" for {2} "{3}"'
    +                    .format(operation_name, 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.TaskException(
    +                        'Could not find plugin of operation "{0}" on interface "{1}" for {2} "{3}"'
    +                        .format(operation_name, 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=interface_name,
    +                                                         operation=operation_name)
    +        else:
    --- End diff --
    
    We have quite a few tests that instantiate `OperationTask` "from scratch" using raw arguments, instead of calling `for_node` and `for_relationship`. In order to support those I needed to allow for this separate codepath.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104312850
  
    --- Diff: aria/modeling/bases.py ---
    @@ -0,0 +1,405 @@
    +# 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.
    +
    +"""
    +ARIA's storage.structures module
    +Path: aria.storage.structures
    +
    +models module holds ARIA's models.
    +
    +classes:
    +    * ModelMixin - abstract model implementation.
    +    * ModelIDMixin - abstract model implementation with IDs.
    +"""
    +
    +from sqlalchemy.orm import relationship, backref
    +from sqlalchemy.orm.collections import attribute_mapped_collection
    +from sqlalchemy.ext import associationproxy
    +from sqlalchemy import (
    +    Column,
    +    ForeignKey,
    +    Integer,
    +    Text,
    +    Table,
    +)
    +
    +from . import utils
    +from ..utils import formatting
    +
    +
    +class ModelMixin(object):
    +
    +    @utils.classproperty
    +    def __modelname__(cls):                                                                         # pylint: disable=no-self-argument
    +        return getattr(cls, '__mapiname__', cls.__tablename__)
    +
    +    @classmethod
    +    def id_column_name(cls):
    +        raise NotImplementedError
    +
    +    @classmethod
    +    def name_column_name(cls):
    +        raise NotImplementedError
    +
    +    @classmethod
    +    def foreign_key(cls, parent_table, nullable=False):
    +        """
    +        Return a ForeignKey object.
    +
    +        :param parent_table: Parent table name
    +        :param nullable: Should the column be allowed to remain empty
    +        """
    +        return Column(Integer,
    +                      ForeignKey('{table}.id'.format(table=parent_table),
    +                                 ondelete='CASCADE'),
    +                      nullable=nullable)
    +
    +    @classmethod
    +    def relationship_to_self(cls,
    +                             column_name,
    +                             relationship_kwargs=None):
    +        relationship_kwargs = relationship_kwargs or {}
    +
    +        remote_side = '{cls}.{remote_column}'.format(
    +            cls=cls.__name__,
    +            remote_column=cls.id_column_name()
    +        )
    +
    +        primaryjoin = '{remote_side} == {cls}.{column}'.format(
    +            remote_side=remote_side,
    +            cls=cls.__name__,
    +            column=column_name
    +        )
    +
    +        return relationship(
    +            cls._get_cls_by_tablename(cls.__tablename__).__name__,
    +            primaryjoin=primaryjoin,
    +            remote_side=remote_side,
    +            post_update=True,
    +            **relationship_kwargs
    +        )
    +
    +    @classmethod
    +    def one_to_many_relationship_to_self(cls,
    +                                         key,
    +                                         dict_key=None,
    +                                         relationship_kwargs=None):
    +        relationship_kwargs = relationship_kwargs or {}
    --- End diff --
    
    It has become somewhat problematic to figure out how the x_to_many relationships work, since there must be a foreign key on the "many" side. This is exactly the issue we talked about. it seems that using the `backref` could actually fix this. 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104309365
  
    --- Diff: aria/modeling/misc.py ---
    @@ -0,0 +1,232 @@
    +# Licensed to the Apache Software Foundation (ASF) under one or more
    +# contributor license agreements.  See the NOTICE file distributed with
    +# this work for additional information regarding copyright ownership.
    +# The ASF licenses this file to You under the Apache License, Version 2.0
    +# (the "License"); you may not use this file except in compliance with
    +# the License.  You may obtain a copy of the License at
    +#
    +#     http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +
    +import cPickle as pickle
    +import logging
    +
    +from sqlalchemy import (
    +    Column,
    +    Text,
    +    Binary
    +)
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from ..storage import exceptions
    +from ..utils import collections, formatting, console
    +from .bases import InstanceModelMixin, TemplateModelMixin
    +from . import utils
    +
    +
    +class ParameterBase(TemplateModelMixin):
    +    """
    +    Represents a typed value.
    +
    +    This class is used by both service template and service instance elements.
    +
    +    :ivar name: Name
    +    :ivar type_name: Type name
    +    :ivar value: Value
    +    :ivar description: Description
    +    """
    +
    +    __tablename__ = 'parameter'
    +
    +    name = Column(Text)
    +    type_name = Column(Text)
    +
    +    # Check: value type
    +    _value = Column(Binary, name='value')
    +    description = Column(Text)
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('name', self.name),
    +            ('type_name', self.type_name),
    +            ('value', self.value),
    +            ('description', self.description)))
    +
    +    @property
    +    def value(self):
    +        if self._value is None:
    +            return None
    +        try:
    +            return pickle.loads(self._value)
    +        except BaseException:
    +            raise exceptions.StorageError('bad format for parameter of type "{0}": {1}'.format(
    +                self.type_name, self._value))
    +
    +    @value.setter
    +    def value(self, value):
    +        if value is None:
    +            self._value = None
    +        else:
    +            try:
    +                self._value = pickle.dumps(value)
    +            except (pickle.PicklingError, TypeError):
    +                logging.getLogger('aria').warn('Could not pickle parameter of type "{0}": {1}'
    +                                               .format(self.type_name, value))
    +                self._value = pickle.dumps(str(value))
    + 
    +    def instantiate(self, context, container):
    +        from . import models
    +        return models.Parameter(name=self.name,
    +                                type_name=self.type_name,
    +                                _value=self._value,
    +                                description=self.description)
    +
    +    def coerce_values(self, context, container, report_issues):
    +        if self.value is not None:
    +            self.value = utils.coerce_value(context, container, self.value,
    +                                            report_issues)
    +
    +    def dump(self, context):
    +        if self.type_name is not None:
    +            console.puts('{0}: {1} ({2})'.format(
    +                context.style.property(self.name),
    +                context.style.literal(self.value),
    +                context.style.type(self.type_name)))
    +        else:
    +            console.puts('{0}: {1}'.format(
    +                context.style.property(self.name),
    +                context.style.literal(self.value)))
    +        if self.description:
    +            console.puts(context.style.meta(self.description))
    +
    +
    +class TypeBase(InstanceModelMixin):
    +    """
    +    Represents a type and its children.
    +    """
    +
    +    __tablename__ = 'type'
    +
    +    variant = Column(Text, nullable=False) 
    +    description = Column(Text)
    +    _role = Column(Text, name='role')
    +
    +    @declared_attr
    +    def parent(cls):
    +        return cls.relationship_to_self('parent_type_fk')
    +
    +    @declared_attr
    +    def children(cls):
    +        return cls.one_to_many_relationship_to_self('parent_type_fk')
    +
    +    # region foreign keys
    +
    +    __private_fields__ = ['parent_type_fk']
    +
    +    # Type one-to-many to Type
    +    @declared_attr
    +    def parent_type_fk(cls):
    +        return cls.foreign_key('type', nullable=True)
    +
    +    # endregion
    +    
    +    @property
    +    def role(self):
    +        def get_role(the_type):
    +            if the_type is None:
    +                return None
    +            elif the_type._role is None:
    +                return get_role(the_type.parent)
    +            return the_type._role
    +
    +        return get_role(self)
    +
    +    @role.setter
    +    def role(self, value):
    +        self._role = value
    +
    +    def is_descendant(self, base_name, name):
    --- End diff --
    
    base_name/name? the names aren't clear, nor what they represent


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r105656646
  
    --- Diff: aria/modeling/service_instance.py ---
    @@ -192,21 +193,21 @@ def get_group_ids(self, group_template_name):
             return collections.FrozenList((group.name
                                            for group in self.find_groups(group_template_name)))
     
    -    def is_node_a_target(self, context, target_node):
    +    def is_node_a_target(self, target_node):
             for node in self.nodes:
    -            if self._is_node_a_target(context, node, target_node):
    +            if self._is_node_a_target(node, target_node):
                     return True
             return False
     
    -    def _is_node_a_target(self, context, source_node, target_node):
    -        if source_node.relationships:
    -            for relationship in source_node.relationships:
    -                if relationship.target_node_id == target_node.name:
    +    def _is_node_a_target(self, source_node, target_node):
    +        if source_node.outbound_relationships:
    --- End diff --
    
    if source_node.outbound_relationships.filter_by(target_node_name=target_node.name):...


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106802395
  
    --- Diff: aria/modeling/relationships.py ---
    @@ -0,0 +1,402 @@
    +# 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.
    +
    +# pylint: disable=invalid-name, redefined-outer-name
    +
    +from sqlalchemy.orm import relationship, backref
    +from sqlalchemy.orm.collections import attribute_mapped_collection
    +from sqlalchemy import (
    +    Column,
    +    ForeignKey,
    +    Integer,
    +    Table
    +)
    +
    +from ..utils import formatting
    +
    +
    +def fk(other_table,
    +       nullable=False):
    +    """
    +    Declare a foreign key property, which will also create a foreign key column in the table with
    +    the name of the property. By convention the property name should end in "_fk".
    +
    +    You are required to explicitly create foreign keys in order to allow for one-to-one,
    +    one-to-many, and many-to-one relationships (but not for many-to-many relationships). If you do
    +    not do so, SQLAlchemy will fail to create the relationship property and raise an exception with
    +    a clear error message.
    +
    +    You should normally not have to access this property directly, but instead use the associated
    +    relationship properties.
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param other_table: Other table name
    +    :type other_table: basestring
    +    :param nullable: True to allow null values (meaning that there is no relationship)
    +    :type nullable: bool
    +    """
    +
    +    return Column(Integer,
    +                  ForeignKey('{table}.id'.format(table=other_table), ondelete='CASCADE'),
    +                  nullable=nullable)
    +
    +
    +def one_to_one_self(model_class,
    +                    fk,
    +                    relationship_kwargs=None):
    +    """
    +    Declare a one-to-one relationship property. The property value would be an instance of the same
    +    model.
    +
    +    You will need an associated foreign key to our own table.
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param model_class: The class in which this relationship will be declared
    +    :type model_class: type
    +    :param fk: Foreign key name
    +    :type fk: basestring
    +    :param relationship_kwargs: Extra kwargs for SQLAlchemy `relationship`
    +    :type relationship_kwargs: {}
    +    """
    +
    +    relationship_kwargs = relationship_kwargs or {}
    +
    +    remote_side = '{model_class}.{remote_column}'.format(
    +        model_class=model_class.__name__,
    +        remote_column=model_class.id_column_name()
    +    )
    +
    +    primaryjoin = '{remote_side} == {model_class}.{column}'.format(
    +        remote_side=remote_side,
    +        model_class=model_class.__name__,
    +        column=fk
    +    )
    +
    +    return relationship(
    +        _get_class_for_table(model_class, model_class.__tablename__).__name__,
    +        primaryjoin=primaryjoin,
    +        remote_side=remote_side,
    +        post_update=True,
    +        **relationship_kwargs
    +    )
    +
    +
    +def one_to_many_self(model_class,
    +                     fk,
    +                     dict_key=None,
    +                     relationship_kwargs=None):
    +    """
    +    Declare a one-to-many relationship property. The property value would be a list or dict of
    +    instances of the same model.
    +
    +    You will need an associated foreign key to our own table.
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param model_class: The class in which this relationship will be declared
    +    :type model_class: type
    +    :param fk: Foreign key name
    +    :type fk: basestring
    +    :param dict_key: If set the value will be a dict with this key as the dict key; otherwise will
    +                     be a list
    +    :type dict_key: basestring
    +    :param relationship_kwargs: Extra kwargs for SQLAlchemy `relationship`
    +    :type relationship_kwargs: {}
    +    """
    +
    +    relationship_kwargs = relationship_kwargs or {}
    +
    +    relationship_kwargs.setdefault('remote_side', '{model_class}.{remote_column}'.format(
    +        model_class=model_class.__name__,
    +        remote_column=fk
    +    ))
    +
    +    return _relationship(model_class, model_class.__tablename__, None, relationship_kwargs,
    +                         other_property=False, dict_key=dict_key)
    +
    +
    +def one_to_one(model_class,
    +               other_table,
    +               fk=None,
    +               other_fk=None,
    +               other_property=None,
    +               relationship_kwargs=None,
    +               backref_kwargs=None):
    +    """
    +    Declare a one-to-one relationship property. The property value would be an instance of the other
    +    table's model.
    +
    +    You have two options for the foreign key. Either this table can have an associated key to the
    +    other table (use the `fk` argument) or the other table can have an associated foreign key to
    +    this our table (use the `other_fk` argument).
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param model_class: The class in which this relationship will be declared
    +    :type model_class: type
    +    :param other_table: Other table name
    +    :type other_table: basestring
    +    :param fk: Foreign key name at our table (no need specify if there's no ambiguity)
    +    :type fk: basestring
    +    :param other_fk: Foreign key name at the other table (no need specify if there's no ambiguity)
    +    :type other_fk: basestring
    +    :param relationship_kwargs: Extra kwargs for SQLAlchemy `relationship`
    +    :type relationship_kwargs: {}
    +    :param backref_kwargs: Extra kwargs for SQLAlchemy `backref`
    +    :type backref_kwargs: {}
    +    """
    +
    +    backref_kwargs = backref_kwargs or {}
    +    backref_kwargs.setdefault('uselist', False)
    +
    +    return _relationship(model_class, other_table, backref_kwargs, relationship_kwargs,
    +                         other_property, fk=fk, other_fk=other_fk)
    +
    +
    +def one_to_many(model_class,
    +                child_table,
    +                child_fk=None,
    +                dict_key=None,
    +                child_property=None,
    +                relationship_kwargs=None,
    +                backref_kwargs=None):
    +    """
    +    Declare a one-to-many relationship property. The property value would be a list or dict of
    +    instances of the child table's model.
    +
    +    The child table will need an associated foreign key to our table.
    +
    +    The declaration will automatically create a matching many-to-one property at the child model,
    +    named after our table name. Use the `child_property` argument to override this name.
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param model_class: The class in which this relationship will be declared
    +    :type model_class: type
    +    :param child_table: Child table name
    +    :type child_table: basestring
    +    :param child_fk: Foreign key name at the child table (no need specify if there's no ambiguity)
    +    :type child_fk: basestring
    +    :param dict_key: If set the value will be a dict with this key as the dict key; otherwise will
    +                     be a list
    +    :type dict_key: basestring
    +    :param child_property: Override name of matching many-to-one property at child table; set to
    +                           false to disable
    +    :type child_property: basestring|bool
    +    :param relationship_kwargs: Extra kwargs for SQLAlchemy `relationship`
    +    :type relationship_kwargs: {}
    +    :param backref_kwargs: Extra kwargs for SQLAlchemy `backref`
    +    :type backref_kwargs: {}
    +    """
    +
    +    backref_kwargs = backref_kwargs or {}
    +    backref_kwargs.setdefault('uselist', False)
    +
    +    return _relationship(model_class, child_table, backref_kwargs, relationship_kwargs,
    +                         child_property, other_fk=child_fk, dict_key=dict_key)
    +
    +
    +def many_to_one(model_class,
    +                parent_table,
    +                fk=None,
    +                parent_fk=None,
    +                parent_property=None,
    +                relationship_kwargs=None,
    +                backref_kwargs=None):
    +    """
    +    Declare a many-to-one relationship property. The property value would be an instance of the
    +    parent table's model.
    +
    +    You will need an associated foreign key to the parent table.
    +
    +    The declaration will automatically create a matching one-to-many property at the child model,
    +    named after the plural form of our table name. Use the `parent_property` argument to override
    +    this name. Note: the automatic property will always be a SQLAlchemy query object; if you need a
    +    Python collection then use :meth:`one_to_many` at that model.
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param model_class: The class in which this relationship will be declared
    +    :type model_class: type
    +    :param parent_table: Parent table name
    +    :type parent_table: basestring
    +    :param fk: Foreign key name at our table (no need specify if there's no ambiguity)
    +    :type fk: basestring
    +    :param parent_property: Override name of matching one-to-many property at parent table; set to
    +                            false to disable
    +    :type parent_property: basestring|bool
    +    :param relationship_kwargs: Extra kwargs for SQLAlchemy `relationship`
    +    :type relationship_kwargs: {}
    +    :param backref_kwargs: Extra kwargs for SQLAlchemy `backref`
    +    :type backref_kwargs: {}
    +    """
    +
    +    if parent_property is None:
    +        parent_property = formatting.pluralize(model_class.__tablename__)
    +
    +    backref_kwargs = backref_kwargs or {}
    +    backref_kwargs.setdefault('uselist', True)
    +    backref_kwargs.setdefault('lazy', 'dynamic')
    +    backref_kwargs.setdefault('cascade', 'all') # delete children when parent is deleted
    +
    +    return _relationship(model_class, parent_table, backref_kwargs, relationship_kwargs,
    +                         parent_property, fk=fk, other_fk=parent_fk)
    +
    +
    +def many_to_many(model_class,
    +                 other_table,
    +                 prefix=None,
    +                 dict_key=None,
    +                 other_property=None,
    +                 relationship_kwargs=None,
    +                 backref_kwargs=None):
    +    """
    +    Declare a many-to-many relationship property. The property value would be a list or dict of
    +    instances of the other table's model.
    +
    +    You do not need associated foreign keys for this relationship. Instead, an extra table will be
    +    created for you.
    +
    +    The declaration will automatically create a matching many-to-many property at the other model,
    +    named after the plural form of our table name. Use the `other_property` argument to override
    +    this name. Note: the automatic property will always be a SQLAlchemy query object; if you need a
    +    Python collection then use :meth:`many_to_many` again at that model.
    +
    +    *This utility method should only be used during class creation.*
    +
    +    :param model_class: The class in which this relationship will be declared
    +    :type model_class: type
    +    :param parent_table: Parent table name
    +    :type parent_table: basestring
    +    :param prefix: Optional prefix for extra table name as well as for `other_property`
    +    :type prefix: basestring
    +    :param dict_key: If set the value will be a dict with this key as the dict key; otherwise will
    +                     be a list
    +    :type dict_key: basestring
    +    :param other_property: Override name of matching many-to-many property at other table; set to
    +                           false to disable
    +    :type other_property: basestring|bool
    +    :param relationship_kwargs: Extra kwargs for SQLAlchemy `relationship`
    +    :type relationship_kwargs: {}
    +    :param backref_kwargs: Extra kwargs for SQLAlchemy `backref`
    +    :type backref_kwargs: {}
    +    """
    +
    +    this_table = model_class.__tablename__
    +    this_column_name = '{0}_id'.format(this_table)
    +    this_foreign_key = '{0}.id'.format(this_table)
    +
    +    other_column_name = '{0}_id'.format(other_table)
    +    other_foreign_key = '{0}.id'.format(other_table)
    +
    +    secondary_table = '{0}_{1}'.format(this_table, other_table)
    +
    +    if other_property is None:
    +        other_property = formatting.pluralize(this_table)
    +        if prefix is not None:
    +            secondary_table = '{0}_{1}'.format(prefix, secondary_table)
    +            other_property = '{0}_{1}'.format(prefix, other_property)
    +
    +    backref_kwargs = backref_kwargs or {}
    +    backref_kwargs.setdefault('uselist', True)
    +
    +    relationship_kwargs = relationship_kwargs or {}
    +    relationship_kwargs.setdefault('secondary', _get_secondary_table(
    +        model_class.metadata,
    +        secondary_table,
    +        this_column_name,
    +        other_column_name,
    +        this_foreign_key,
    +        other_foreign_key
    +    ))
    +
    +    return _relationship(model_class, other_table, backref_kwargs, relationship_kwargs,
    +                         other_property, dict_key=dict_key)
    +
    +
    +def _relationship(model_class, other_table, backref_kwargs, relationship_kwargs, other_property,
    +                  fk=None, other_fk=None, dict_key=None):
    +    relationship_kwargs = relationship_kwargs or {}
    +
    +    if fk:
    +        relationship_kwargs.setdefault('foreign_keys',
    +                                       lambda: getattr(
    +                                           _get_class_for_table(
    +                                               model_class,
    +                                               model_class.__tablename__),
    +                                           fk))
    +
    +    elif other_fk:
    +        relationship_kwargs.setdefault('foreign_keys',
    +                                       lambda: getattr(
    +                                           _get_class_for_table(
    +                                               model_class,
    +                                               other_table),
    +                                           other_fk))
    +
    +    if dict_key:
    +        relationship_kwargs.setdefault('collection_class',
    +                                       attribute_mapped_collection(dict_key))
    +
    +    if other_property is False:
    +        # No backref
    +        return relationship(
    +            lambda: _get_class_for_table(model_class, other_table),
    +            **relationship_kwargs
    +        )
    +    else:
    +        if other_property is None:
    +            other_property = model_class.__tablename__
    +        backref_kwargs = backref_kwargs or {}
    +        return relationship(
    +            lambda: _get_class_for_table(model_class, other_table),
    +            backref=backref(other_property, **backref_kwargs),
    +            **relationship_kwargs
    +        )
    +
    +
    +def _get_class_for_table(model_class, tablename):
    --- End diff --
    
    it actually receive a tablename (and not a table), not sure about the name...


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r105641678
  
    --- Diff: aria/modeling/misc.py ---
    @@ -42,11 +43,11 @@ class ParameterBase(TemplateModelMixin):
     
         __tablename__ = 'parameter'
     
    -    name = Column(Text, nullable=False)
    -    type_name = Column(Text, nullable=False)
    +    name = Column(Text)
    --- End diff --
    
    it doesn't make sense to require for each model to have a name...


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104309335
  
    --- Diff: aria/orchestrator/workflows/builtin/utils.py ---
    @@ -14,33 +14,41 @@
     # limitations under the License.
     
     from ..api.task import OperationTask
    +from .. import exceptions
     
     
    -def create_node_task(operation_name, node):
    +def create_node_task(interface_name, operation_name, node, dry=False):
    --- End diff --
    
    why is the dry param here now as well?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r105640668
  
    --- Diff: aria/orchestrator/workflows/api/task.py ---
    @@ -165,13 +166,21 @@ def for_relationship(cls, relationship, interface_name, operation_name, inputs=N
                     'Could not find operation "{0}" on interface "{1}" for relationship "{2}"'.format(
                         operation_name, interface_name, relationship.name))
     
    +        plugin = None
    --- End diff --
    
    refactor?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106938910
  
    --- Diff: tests/orchestrator/workflows/executor/test_executor.py ---
    @@ -43,7 +43,9 @@ def test_execute(executor):
         expected_value = 'value'
         successful_task = MockTask(mock_successful_task)
         failing_task = MockTask(mock_failing_task)
    -    task_with_inputs = MockTask(mock_task_with_input, inputs=dict(input='value'))
    +    #task_with_inputs = MockTask(mock_task_with_input, inputs=dict(input='value'))
    --- End diff --
    
    Fixed


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by tliron <gi...@git.apache.org>.
Github user tliron commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r106939198
  
    --- Diff: tests/modeling/test_models.py ---
    @@ -663,66 +646,45 @@ def _node(self, storage, node, is_host, ip, host_fk=None):
             kwargs = dict(
                 name='node',
                 node_template=node,
    +            type=storage.type.list()[0],
                 runtime_properties={},
                 state='',
    -            service_instance=storage.service_instance.list()[0]
    +            service=storage.service.list()[0]
             )
             if ip:
                 kwargs['runtime_properties']['ip'] = ip
             if is_host:
                 kwargs['host_fk'] = 1
             elif host_fk:
                 kwargs['host_fk'] = host_fk
    -        node_instance = Node(**kwargs)
    -        storage.node.put(node_instance)
    -        return node_instance
    +        node = Node(**kwargs)
    +        storage.node.put(node)
    +        return node
     
     
    -class TestRelationshipInstance(object):
    +class TestRelationship(object):
         @pytest.mark.parametrize(
    -        'is_valid, source_requirement_index, target_node_id, target_capability_name, type_name, '
    -        'template_name, type_hierarchy, source_position, target_position',
    +        'is_valid, source_position, target_position',
             [
    -            (False, m_cls, 'target_node_id', 'target_cap_name', 'type_name', 'template_name', [], 0,
    -             0),
    -            (False, 0, m_cls, 'target_cap_name', 'type_name', 'template_name', [], 0, 0),
    -            (False, 0, 'target_node_id', m_cls, 'type_name', 'template_name', [], 0, 0),
    -            (False, 0, 'target_node_id', 'target_cap_name', m_cls, 'template_name', [], 0, 0),
    -            (False, 0, 'target_node_id', 'target_cap_name', 'type_name', m_cls, [], 0, 0),
    -            (False, 0, 'target_node_id', 'target_cap_name', 'type_name', 'template_name', m_cls, 0,
    -             0),
    -            (False, 0, 'target_node_id', 'target_cap_name', 'type_name', 'template_name', [], m_cls,
    -             0),
    -            (False, 0, 'target_node_id', 'target_cap_name', 'type_name', 'template_name', [], 0,
    -             m_cls),
    -
    -            (True, 0, 'target_node_id', 'target_cap_name', 'type_name', 'template_name', [], 0, 0),
    -            (True, None, 'target_node_id', 'target_cap_name', 'type_name', 'template_name', [], 0,
    -             0),
    -            (True, 0, None, 'target_cap_name', 'type_name', 'template_name', [], 0, 0),
    -            (True, 0, 'target_node_id', None, 'type_name', 'template_name', [], 0, 0),
    -            (True, 0, 'target_node_id', 'target_cap_name', None, 'template_name', [], 0, 0),
    -            (True, 0, 'target_node_id', 'target_cap_name', 'type_name', None, [], 0, 0),
    -            (True, 0, 'target_node_id', 'target_cap_name', 'type_name', 'template_name', [], None,
    -             0),
    -            (True, 0, 'target_node_id', 'target_cap_name', 'type_name', 'template_name', [], 0,
    -             None),
    +            (False, m_cls, 0),
    --- End diff --
    
    `Relationship` comprises almost entirely foreign keys now! For the test I left the only few columns that are values.


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104313005
  
    --- Diff: aria/modeling/service.py ---
    @@ -0,0 +1,1451 @@
    +# 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.
    +
    +# pylint: disable=no-self-argument, no-member, abstract-method
    +
    +from sqlalchemy import (
    +    Column,
    +    Text,
    +    Integer
    +)
    +from sqlalchemy import DateTime
    +from sqlalchemy.ext.associationproxy import association_proxy
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from .bases import InstanceModelMixin
    +from ..parser import validation
    +from ..utils import collections, formatting, console
    +
    +from . import (
    +    utils,
    +    types as modeling_types
    +)
    +
    +
    +class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
    +    """
    +    A service is usually an instance of a :class:`ServiceTemplate`.
    +
    +    You will usually not create it programmatically, but instead instantiate it from a service
    +    template.
    +
    +    :ivar name: Name (unique for this ARIA installation)
    +    :vartype name: basestring
    +    :ivar service_template: Template from which this service was instantiated (optional)
    +    :vartype service_template: :class:`ServiceTemplate`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar meta_data: Custom annotations
    +    :vartype meta_data: {basestring: :class:`Metadata`}
    +    :ivar node: Nodes
    +    :vartype node: [:class:`Node`]
    +    :ivar groups: Groups of nodes
    +    :vartype groups: [:class:`Group`]
    +    :ivar policies: Policies
    +    :vartype policies: [:class:`Policy`]
    +    :ivar substitution: The entire service can appear as a node
    +    :vartype substitution: :class:`Substitution`
    +    :ivar inputs: Externally provided parameters
    +    :vartype inputs: {basestring: :class:`Parameter`}
    +    :ivar outputs: These parameters are filled in after service installation
    +    :vartype outputs: {basestring: :class:`Parameter`}
    +    :ivar operations: Custom operations that can be performed on the service
    +    :vartype operations: {basestring: :class:`Operation`}
    +    :ivar plugins: Plugins required to be installed
    +    :vartype plugins: {basestring: :class:`Plugin`}
    +    :ivar created_at: Creation timestamp
    +    :vartype created_at: :class:`datetime.datetime`
    +    :ivar updated_at: Update timestamp
    +    :vartype updated_at: :class:`datetime.datetime`
    +    """
    +
    +    __tablename__ = 'service'
    +
    +    @declared_attr
    +    def service_template(cls):
    +        return cls.many_to_one_relationship('service_template')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def meta_data(cls):
    +        # Warning! We cannot use the attr name "metadata" because it's used by SqlAlchemy!
    +        return cls.many_to_many_relationship('metadata', dict_key='name')
    +
    +    @declared_attr
    +    def nodes(cls):
    +        return cls.one_to_many_relationship('node')
    +
    +    @declared_attr
    +    def groups(cls):
    +        return cls.one_to_many_relationship('group')
    +
    +    @declared_attr
    +    def policies(cls):
    +        return cls.one_to_many_relationship('policy')
    +
    +    @declared_attr
    +    def substitution(cls):
    +        return cls.one_to_one_relationship('substitution')
    +
    +    @declared_attr
    +    def inputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def outputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='outputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def operations(cls):
    +        return cls.one_to_many_relationship('operation', dict_key='name')
    +
    +    @declared_attr
    +    def plugins(cls):
    +        return cls.many_to_many_relationship('plugin')
    +
    +    created_at = Column(DateTime, nullable=False, index=True)
    +    updated_at = Column(DateTime)
    +
    +    # region orchestration
    +
    +    permalink = Column(Text)
    +    scaling_groups = Column(modeling_types.Dict)
    +
    +    # endregion
    +
    +    # region foreign keys
    +
    +    __private_fields__ = ['substituion_fk',
    +                          'service_template_fk']
    +
    +    # Service one-to-one to Substitution
    +    @declared_attr
    +    def substitution_fk(cls):
    +        return cls.foreign_key('substitution', nullable=True)
    +
    +    # Service many-to-one to ServiceTemplate
    +    @declared_attr
    +    def service_template_fk(cls):
    +        return cls.foreign_key('service_template', nullable=True)
    +
    +    # endregion
    +
    +    def satisfy_requirements(self, context):
    +        satisfied = True
    +        for node in self.nodes:
    +            if not node.satisfy_requirements(context):
    +                satisfied = False
    +        return satisfied
    +
    +    def validate_capabilities(self, context):
    +        satisfied = True
    +        for node in self.nodes:
    +            if not node.validate_capabilities(context):
    +                satisfied = False
    +        return satisfied
    +
    +    def find_nodes(self, node_template_name):
    +        nodes = []
    +        for node in self.nodes:
    +            if node.node_template.name == node_template_name:
    +                nodes.append(node)
    +        return collections.FrozenList(nodes)
    +
    +    def get_node_ids(self, node_template_name):
    +        return collections.FrozenList((node.name for node in self.find_nodes(node_template_name)))
    +
    +    def find_groups(self, group_template_name):
    +        groups = []
    +        for group in self.groups:
    +            if group.template_name == group_template_name:
    +                groups.append(group)
    +        return collections.FrozenList(groups)
    +
    +    def get_group_ids(self, group_template_name):
    +        return collections.FrozenList((group.name
    +                                       for group in self.find_groups(group_template_name)))
    +
    +    def is_node_a_target(self, context, target_node):
    +        for node in self.nodes:
    +            if self._is_node_a_target(context, node, target_node):
    +                return True
    +        return False
    +
    +    def _is_node_a_target(self, context, source_node, target_node):
    +        if source_node.relationships:
    +            for relationship in source_node.relationships:
    +                if relationship.target_node_id == target_node.name:
    +                    return True
    +                else:
    +                    node = context.modeling.instance.nodes.get(relationship.target_node_id)
    +                    if node is not None:
    +                        if self._is_node_a_target(context, node, target_node):
    +                            return True
    +        return False
    +
    +    @property
    +    def as_raw(self):
    --- End diff --
    
    as_raw should eventually vanish, since the as_dict provides roughly the same functionality (and it is defined at the base of each class).


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r105642858
  
    --- Diff: aria/orchestrator/workflows/api/task.py ---
    @@ -173,6 +170,13 @@ def for_relationship(cls, relationship, interface_name, operation_name, inputs=N
                 **kwargs)
     
         @classmethod
    +    def _get_operation(cls, interfaces, interface_name, operation_name):
    --- End diff --
    
    staticmethod?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104974006
  
    --- Diff: aria/modeling/service.py ---
    @@ -0,0 +1,1529 @@
    +# 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.
    +
    +# pylint: disable=no-self-argument, no-member, abstract-method
    +
    +from sqlalchemy import (
    +    Column,
    +    Text,
    +    Integer
    +)
    +from sqlalchemy import DateTime
    +from sqlalchemy.ext.associationproxy import association_proxy
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from .bases import InstanceModelMixin
    +from ..parser import validation
    +from ..utils import collections, formatting, console
    +
    +from . import (
    +    utils,
    +    types as modeling_types
    +)
    +
    +
    +class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
    +    """
    +    A service is usually an instance of a :class:`ServiceTemplate`.
    +
    +    You will usually not create it programmatically, but instead instantiate it from a service
    +    template.
    +
    +    :ivar name: Name (unique for this ARIA installation)
    +    :vartype name: basestring
    +    :ivar service_template: Template from which this service was instantiated (optional)
    +    :vartype service_template: :class:`ServiceTemplate`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar meta_data: Custom annotations
    +    :vartype meta_data: {basestring: :class:`Metadata`}
    +    :ivar node: Nodes
    +    :vartype node: [:class:`Node`]
    +    :ivar groups: Groups of nodes
    +    :vartype groups: [:class:`Group`]
    +    :ivar policies: Policies
    +    :vartype policies: [:class:`Policy`]
    +    :ivar substitution: The entire service can appear as a node
    +    :vartype substitution: :class:`Substitution`
    +    :ivar inputs: Externally provided parameters
    +    :vartype inputs: {basestring: :class:`Parameter`}
    +    :ivar outputs: These parameters are filled in after service installation
    +    :vartype outputs: {basestring: :class:`Parameter`}
    +    :ivar operations: Custom operations that can be performed on the service
    +    :vartype operations: {basestring: :class:`Operation`}
    +    :ivar plugins: Plugins required to be installed
    +    :vartype plugins: {basestring: :class:`Plugin`}
    +    :ivar created_at: Creation timestamp
    +    :vartype created_at: :class:`datetime.datetime`
    +    :ivar updated_at: Update timestamp
    +    :vartype updated_at: :class:`datetime.datetime`
    +
    +    :ivar permalink: ??
    +    :vartype permalink: basestring
    +    :ivar scaling_groups: ??
    +    :vartype scaling_groups: {}
    +
    +    :ivar modifications: Modifications of this service
    +    :vartype modifications: [:class:`ServiceModification`]
    +    :ivar updates: Updates of this service
    +    :vartype updates: [:class:`ServiceUpdate`]
    +    :ivar executions: Executions on this service
    +    :vartype executions: [:class:`Execution`]
    +    """
    +
    +    __tablename__ = 'service'
    +
    +    @declared_attr
    +    def service_template(cls):
    +        return cls.many_to_one_relationship('service_template')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def meta_data(cls):
    +        # Warning! We cannot use the attr name "metadata" because it's used by SqlAlchemy!
    +        return cls.many_to_many_relationship('metadata', dict_key='name')
    +
    +    @declared_attr
    +    def nodes(cls):
    +        return cls.one_to_many_relationship('node')
    +
    +    @declared_attr
    +    def groups(cls):
    +        return cls.one_to_many_relationship('group')
    +
    +    @declared_attr
    +    def policies(cls):
    +        return cls.one_to_many_relationship('policy')
    +
    +    @declared_attr
    +    def substitution(cls):
    +        return cls.one_to_one_relationship('substitution')
    +
    +    @declared_attr
    +    def inputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def outputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='outputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def operations(cls):
    +        return cls.one_to_many_relationship('operation', dict_key='name')
    +
    +    @declared_attr
    +    def plugins(cls):
    +        return cls.many_to_many_relationship('plugin')
    +
    +    created_at = Column(DateTime, nullable=False, index=True)
    +    updated_at = Column(DateTime)
    +
    +    # region orchestration
    +
    +    permalink = Column(Text)
    +    scaling_groups = Column(modeling_types.Dict)
    +
    +    # endregion
    +
    +    # region foreign keys
    +
    +    __private_fields__ = ['substituion_fk',
    +                          'service_template_fk']
    +
    +    # Service one-to-one to Substitution
    +    @declared_attr
    +    def substitution_fk(cls):
    +        return cls.foreign_key('substitution', nullable=True)
    +
    +    # Service many-to-one to ServiceTemplate
    +    @declared_attr
    +    def service_template_fk(cls):
    +        return cls.foreign_key('service_template', nullable=True)
    +
    +    # endregion
    +
    +    def satisfy_requirements(self, context):
    +        satisfied = True
    +        for node in self.nodes:
    +            if not node.satisfy_requirements(context):
    +                satisfied = False
    +        return satisfied
    +
    +    def validate_capabilities(self, context):
    +        satisfied = True
    +        for node in self.nodes:
    +            if not node.validate_capabilities(context):
    +                satisfied = False
    +        return satisfied
    +
    +    def find_nodes(self, node_template_name):
    +        nodes = []
    +        for node in self.nodes:
    +            if node.node_template.name == node_template_name:
    +                nodes.append(node)
    +        return collections.FrozenList(nodes)
    +
    +    def get_node_ids(self, node_template_name):
    +        return collections.FrozenList((node.name for node in self.find_nodes(node_template_name)))
    +
    +    def find_groups(self, group_template_name):
    +        groups = []
    +        for group in self.groups:
    +            if group.template_name == group_template_name:
    +                groups.append(group)
    +        return collections.FrozenList(groups)
    +
    +    def get_group_ids(self, group_template_name):
    +        return collections.FrozenList((group.name
    +                                       for group in self.find_groups(group_template_name)))
    +
    +    def is_node_a_target(self, context, target_node):
    +        for node in self.nodes:
    +            if self._is_node_a_target(context, node, target_node):
    +                return True
    +        return False
    +
    +    def _is_node_a_target(self, context, source_node, target_node):
    --- End diff --
    
    need to get rid of context throughout the models..


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by mxmrlv <gi...@git.apache.org>.
Github user mxmrlv commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104309404
  
    --- Diff: aria/modeling/misc.py ---
    @@ -0,0 +1,232 @@
    +# Licensed to the Apache Software Foundation (ASF) under one or more
    +# contributor license agreements.  See the NOTICE file distributed with
    +# this work for additional information regarding copyright ownership.
    +# The ASF licenses this file to You under the Apache License, Version 2.0
    +# (the "License"); you may not use this file except in compliance with
    +# the License.  You may obtain a copy of the License at
    +#
    +#     http://www.apache.org/licenses/LICENSE-2.0
    +#
    +# Unless required by applicable law or agreed to in writing, software
    +# distributed under the License is distributed on an "AS IS" BASIS,
    +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    +# See the License for the specific language governing permissions and
    +# limitations under the License.
    +
    +import cPickle as pickle
    +import logging
    +
    +from sqlalchemy import (
    +    Column,
    +    Text,
    +    Binary
    +)
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from ..storage import exceptions
    +from ..utils import collections, formatting, console
    +from .bases import InstanceModelMixin, TemplateModelMixin
    +from . import utils
    +
    +
    +class ParameterBase(TemplateModelMixin):
    +    """
    +    Represents a typed value.
    +
    +    This class is used by both service template and service instance elements.
    +
    +    :ivar name: Name
    +    :ivar type_name: Type name
    +    :ivar value: Value
    +    :ivar description: Description
    +    """
    +
    +    __tablename__ = 'parameter'
    +
    +    name = Column(Text)
    +    type_name = Column(Text)
    +
    +    # Check: value type
    +    _value = Column(Binary, name='value')
    +    description = Column(Text)
    +
    +    @property
    +    def as_raw(self):
    +        return collections.OrderedDict((
    +            ('name', self.name),
    +            ('type_name', self.type_name),
    +            ('value', self.value),
    +            ('description', self.description)))
    +
    +    @property
    +    def value(self):
    +        if self._value is None:
    +            return None
    +        try:
    +            return pickle.loads(self._value)
    +        except BaseException:
    +            raise exceptions.StorageError('bad format for parameter of type "{0}": {1}'.format(
    +                self.type_name, self._value))
    +
    +    @value.setter
    +    def value(self, value):
    +        if value is None:
    +            self._value = None
    +        else:
    +            try:
    +                self._value = pickle.dumps(value)
    +            except (pickle.PicklingError, TypeError):
    +                logging.getLogger('aria').warn('Could not pickle parameter of type "{0}": {1}'
    +                                               .format(self.type_name, value))
    +                self._value = pickle.dumps(str(value))
    + 
    +    def instantiate(self, context, container):
    +        from . import models
    +        return models.Parameter(name=self.name,
    +                                type_name=self.type_name,
    +                                _value=self._value,
    +                                description=self.description)
    +
    +    def coerce_values(self, context, container, report_issues):
    +        if self.value is not None:
    +            self.value = utils.coerce_value(context, container, self.value,
    +                                            report_issues)
    +
    +    def dump(self, context):
    +        if self.type_name is not None:
    +            console.puts('{0}: {1} ({2})'.format(
    +                context.style.property(self.name),
    +                context.style.literal(self.value),
    +                context.style.type(self.type_name)))
    +        else:
    +            console.puts('{0}: {1}'.format(
    +                context.style.property(self.name),
    +                context.style.literal(self.value)))
    +        if self.description:
    +            console.puts(context.style.meta(self.description))
    +
    +
    +class TypeBase(InstanceModelMixin):
    +    """
    +    Represents a type and its children.
    +    """
    +
    +    __tablename__ = 'type'
    +
    +    variant = Column(Text, nullable=False) 
    +    description = Column(Text)
    +    _role = Column(Text, name='role')
    +
    +    @declared_attr
    +    def parent(cls):
    +        return cls.relationship_to_self('parent_type_fk')
    +
    +    @declared_attr
    +    def children(cls):
    +        return cls.one_to_many_relationship_to_self('parent_type_fk')
    +
    +    # region foreign keys
    +
    +    __private_fields__ = ['parent_type_fk']
    +
    +    # Type one-to-many to Type
    +    @declared_attr
    +    def parent_type_fk(cls):
    +        return cls.foreign_key('type', nullable=True)
    +
    +    # endregion
    +    
    +    @property
    +    def role(self):
    +        def get_role(the_type):
    +            if the_type is None:
    +                return None
    +            elif the_type._role is None:
    +                return get_role(the_type.parent)
    +            return the_type._role
    +
    +        return get_role(self)
    +
    +    @role.setter
    +    def role(self, value):
    +        self._role = value
    +
    +    def is_descendant(self, base_name, name):
    +        base = self.get_descendant(base_name)
    +        if base is not None:
    +            if base.get_descendant(name) is not None:
    +                return True
    +        return False
    +
    +    def get_descendant(self, name):
    --- End diff --
    
    isn't this suppose to be recursive?


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: Aria 105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104309126
  
    --- Diff: aria/modeling/orchestration.py ---
    @@ -291,28 +297,35 @@ class ServiceInstanceModificationBase(ModelMixin):
         context = Column(Dict)
         created_at = Column(DateTime, nullable=False, index=True)
         ended_at = Column(DateTime, index=True)
    -    modified_nodes = Column(Dict)
    -    node_instances = Column(Dict)
    -    status = Column(Enum(*STATES, name='deployment_modification_status'))
    +    modified_node_templates = Column(Dict)
    +    nodes = Column(Dict)
    +    status = Column(Enum(*STATES, name='service_modification_status'))
     
         @declared_attr
    -    def service_instance_fk(cls):
    -        return cls.foreign_key('service_instance')
    +    def service(cls):
    +        return cls.many_to_one_relationship('service',
    +                                            backreference='modifications')
     
         @declared_attr
    -    def service_instance(cls):
    -        return cls.many_to_one_relationship('service_instance',
    -                                            backreference='modifications')
    +    def service_name(cls):
    --- End diff --
    
    What's with changing "ServiceInstance" to "Service" all around? 


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---

[GitHub] incubator-ariatosca pull request #72: ARIA-105 integrate modeling

Posted by ran-z <gi...@git.apache.org>.
Github user ran-z commented on a diff in the pull request:

    https://github.com/apache/incubator-ariatosca/pull/72#discussion_r104973798
  
    --- Diff: aria/modeling/service.py ---
    @@ -0,0 +1,1529 @@
    +# 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.
    +
    +# pylint: disable=no-self-argument, no-member, abstract-method
    +
    +from sqlalchemy import (
    +    Column,
    +    Text,
    +    Integer
    +)
    +from sqlalchemy import DateTime
    +from sqlalchemy.ext.associationproxy import association_proxy
    +from sqlalchemy.ext.declarative import declared_attr
    +
    +from .bases import InstanceModelMixin
    +from ..parser import validation
    +from ..utils import collections, formatting, console
    +
    +from . import (
    +    utils,
    +    types as modeling_types
    +)
    +
    +
    +class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
    +    """
    +    A service is usually an instance of a :class:`ServiceTemplate`.
    +
    +    You will usually not create it programmatically, but instead instantiate it from a service
    +    template.
    +
    +    :ivar name: Name (unique for this ARIA installation)
    +    :vartype name: basestring
    +    :ivar service_template: Template from which this service was instantiated (optional)
    +    :vartype service_template: :class:`ServiceTemplate`
    +    :ivar description: Human-readable description
    +    :vartype description: string
    +    :ivar meta_data: Custom annotations
    +    :vartype meta_data: {basestring: :class:`Metadata`}
    +    :ivar node: Nodes
    +    :vartype node: [:class:`Node`]
    +    :ivar groups: Groups of nodes
    +    :vartype groups: [:class:`Group`]
    +    :ivar policies: Policies
    +    :vartype policies: [:class:`Policy`]
    +    :ivar substitution: The entire service can appear as a node
    +    :vartype substitution: :class:`Substitution`
    +    :ivar inputs: Externally provided parameters
    +    :vartype inputs: {basestring: :class:`Parameter`}
    +    :ivar outputs: These parameters are filled in after service installation
    +    :vartype outputs: {basestring: :class:`Parameter`}
    +    :ivar operations: Custom operations that can be performed on the service
    +    :vartype operations: {basestring: :class:`Operation`}
    +    :ivar plugins: Plugins required to be installed
    +    :vartype plugins: {basestring: :class:`Plugin`}
    +    :ivar created_at: Creation timestamp
    +    :vartype created_at: :class:`datetime.datetime`
    +    :ivar updated_at: Update timestamp
    +    :vartype updated_at: :class:`datetime.datetime`
    +
    +    :ivar permalink: ??
    +    :vartype permalink: basestring
    +    :ivar scaling_groups: ??
    +    :vartype scaling_groups: {}
    +
    +    :ivar modifications: Modifications of this service
    +    :vartype modifications: [:class:`ServiceModification`]
    +    :ivar updates: Updates of this service
    +    :vartype updates: [:class:`ServiceUpdate`]
    +    :ivar executions: Executions on this service
    +    :vartype executions: [:class:`Execution`]
    +    """
    +
    +    __tablename__ = 'service'
    +
    +    @declared_attr
    +    def service_template(cls):
    +        return cls.many_to_one_relationship('service_template')
    +
    +    description = Column(Text)
    +
    +    @declared_attr
    +    def meta_data(cls):
    +        # Warning! We cannot use the attr name "metadata" because it's used by SqlAlchemy!
    +        return cls.many_to_many_relationship('metadata', dict_key='name')
    +
    +    @declared_attr
    +    def nodes(cls):
    +        return cls.one_to_many_relationship('node')
    +
    +    @declared_attr
    +    def groups(cls):
    +        return cls.one_to_many_relationship('group')
    +
    +    @declared_attr
    +    def policies(cls):
    +        return cls.one_to_many_relationship('policy')
    +
    +    @declared_attr
    +    def substitution(cls):
    +        return cls.one_to_one_relationship('substitution')
    +
    +    @declared_attr
    +    def inputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='inputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def outputs(cls):
    +        return cls.many_to_many_relationship('parameter', table_prefix='outputs',
    +                                             dict_key='name')
    +
    +    @declared_attr
    +    def operations(cls):
    --- End diff --
    
    change this to "workflows"


---
If your project is set up for it, you can reply to this email and have your
reply appear on GitHub as well. If your project does not have this feature
enabled and wishes so, or if the feature is enabled but not working, please
contact infrastructure at infrastructure@apache.org or file a JIRA ticket
with INFRA.
---