You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@ariatosca.apache.org by em...@apache.org on 2017/03/03 02:15:44 UTC
[3/3] incubator-ariatosca git commit: Add types to modeling,
plus many fixes to models
Add types to modeling, plus many fixes to models
Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/daa2d538
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/daa2d538
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/daa2d538
Branch: refs/heads/ARIA-105-integrate-modeling
Commit: daa2d538b590e38ff97dcec2e721a011c37b3e15
Parents: 58aac8d
Author: Tal Liron <ta...@gmail.com>
Authored: Thu Mar 2 20:15:12 2017 -0600
Committer: Tal Liron <ta...@gmail.com>
Committed: Thu Mar 2 20:15:12 2017 -0600
----------------------------------------------------------------------
aria/VERSION.py | 4 +-
aria/__init__.py | 40 +-
aria/cli/commands.py | 3 +-
aria/exceptions.py | 4 +-
aria/modeling/bases.py | 309 +++++----
aria/modeling/misc.py | 208 ++++++
aria/modeling/models.py | 163 +++--
aria/modeling/orchestration.py | 166 ++---
aria/modeling/service.py | 467 ++++++-------
aria/modeling/service_template.py | 664 ++++++++-----------
aria/modeling/types.py | 6 +
aria/orchestrator/__init__.py | 2 +-
aria/orchestrator/workflows/api/task.py | 4 +-
aria/orchestrator/workflows/builtin/utils.py | 1 -
.../workflows/core/events_handler.py | 2 +-
aria/orchestrator/workflows/dry.py | 2 +-
aria/orchestrator/workflows/events_logging.py | 2 +-
aria/parser/consumption/style.py | 2 +-
aria/parser/modeling/__init__.py | 8 +-
aria/parser/modeling/context.py | 31 +-
aria/storage/__init__.py | 10 +-
aria/storage/core.py | 10 +-
docs/requirements.txt | 4 +-
.../simple_v1_0/modeling/__init__.py | 418 ++++++------
24 files changed, 1392 insertions(+), 1138 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/daa2d538/aria/VERSION.py
----------------------------------------------------------------------
diff --git a/aria/VERSION.py b/aria/VERSION.py
index 7e11072..9ce332c 100644
--- a/aria/VERSION.py
+++ b/aria/VERSION.py
@@ -14,8 +14,8 @@
# limitations under the License.
"""
-Aria Version module:
- * version: Aria Package version
+ARIA Version module:
+ * version: ARIA Package version
"""
version = '0.1.0' # pylint: disable=C0103
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/daa2d538/aria/__init__.py
----------------------------------------------------------------------
diff --git a/aria/__init__.py b/aria/__init__.py
index 7e5da07..4675fc8 100644
--- a/aria/__init__.py
+++ b/aria/__init__.py
@@ -14,7 +14,7 @@
# limitations under the License.
"""
-Aria top level package
+ARIA top level package
"""
import pkgutil
@@ -62,45 +62,9 @@ def application_model_storage(api, api_kwargs=None, initiator=None, initiator_kw
"""
Initiate model storage
"""
- models_to_register = [
- modeling.models.ServiceTemplate,
- modeling.models.NodeTemplate,
- modeling.models.GroupTemplate,
- modeling.models.PolicyTemplate,
- modeling.models.SubstitutionTemplate,
- modeling.models.SubstitutionTemplateMapping,
- modeling.models.RequirementTemplate,
- modeling.models.RelationshipTemplate,
- modeling.models.CapabilityTemplate,
- modeling.models.InterfaceTemplate,
- modeling.models.OperationTemplate,
- modeling.models.ArtifactTemplate,
-
- modeling.models.Parameter,
- modeling.models.Metadata,
-
- modeling.models.Service,
- modeling.models.Node,
- modeling.models.Group,
- modeling.models.Policy,
- modeling.models.SubstitutionMapping,
- modeling.models.Substitution,
- modeling.models.Relationship,
- modeling.models.Capability,
- modeling.models.Interface,
- modeling.models.Operation,
- modeling.models.Artifact,
-
- modeling.models.Execution,
- modeling.models.ServiceUpdate,
- modeling.models.ServiceUpdateStep,
- modeling.models.ServiceModification,
- modeling.models.Plugin,
- modeling.models.Task
- ]
return storage.ModelStorage(api_cls=api,
api_kwargs=api_kwargs,
- items=models_to_register,
+ items=modeling.models.models_to_register,
initiator=initiator,
initiator_kwargs=initiator_kwargs or {})
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/daa2d538/aria/cli/commands.py
----------------------------------------------------------------------
diff --git a/aria/cli/commands.py b/aria/cli/commands.py
index 7ad85af..25a4e0d 100644
--- a/aria/cli/commands.py
+++ b/aria/cli/commands.py
@@ -260,8 +260,7 @@ class WorkflowCommand(BaseCommand):
def _run(self, context, workflow_name, workflow_fn, inputs):
# Storage
def _initialize_storage(model_storage):
- model_storage.service_template.put(context.modeling.template)
- model_storage.service.put(context.modeling.instance)
+ context.modeling.store(model_storage)
# Create runner
runner = Runner(workflow_name, workflow_fn, inputs, _initialize_storage,
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/daa2d538/aria/exceptions.py
----------------------------------------------------------------------
diff --git a/aria/exceptions.py b/aria/exceptions.py
index 28f8be9..0a60f01 100644
--- a/aria/exceptions.py
+++ b/aria/exceptions.py
@@ -14,8 +14,8 @@
# limitations under the License.
"""
-Aria exceptions module
-Every sub-package in Aria has a module with its exceptions.
+ARIA exceptions module
+Every sub-package in ARIA has a module with its exceptions.
aria.exceptions module conveniently collects all these exceptions for easier imports.
"""
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/daa2d538/aria/modeling/bases.py
----------------------------------------------------------------------
diff --git a/aria/modeling/bases.py b/aria/modeling/bases.py
index 0e583cf..c1b2f7a 100644
--- a/aria/modeling/bases.py
+++ b/aria/modeling/bases.py
@@ -53,15 +53,15 @@ class ModelMixin(object):
raise NotImplementedError
@classmethod
- def foreign_key(cls, parent_table_name, nullable=False):
+ def foreign_key(cls, parent_table, nullable=False):
"""
Return a ForeignKey object.
- :param parent_table_name: Parent table name
+ :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_name),
+ ForeignKey('{table}.id'.format(table=parent_table),
ondelete='CASCADE'),
nullable=nullable)
@@ -71,68 +71,75 @@ class ModelMixin(object):
relationship_kwargs=None):
relationship_kwargs = relationship_kwargs or {}
- remote_side_str = '{cls}.{remote_column}'.format(
+ remote_side = '{cls}.{remote_column}'.format(
cls=cls.__name__,
remote_column=cls.id_column_name()
)
- primaryjoin_str = '{remote_side_str} == {cls}.{column}'.format(
- remote_side_str=remote_side_str,
+ 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_str,
- remote_side=remote_side_str,
+ 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 {}
+
+ relationship_kwargs.setdefault('remote_side', '{cls}.{remote_column}'.format(
+ cls=cls.__name__,
+ remote_column=key
+ ))
+
+ return cls._create_relationship(cls.__tablename__, None, relationship_kwargs,
+ backreference='', dict_key=dict_key)
+
+ @classmethod
def one_to_one_relationship(cls,
- other_table_name,
+ other_table,
+ key=None,
+ foreign_key=None,
backreference=None,
+ backref_kwargs=None,
relationship_kwargs=None):
- relationship_kwargs = relationship_kwargs or {}
-
- return relationship(
- lambda: cls._get_cls_by_tablename(other_table_name),
- backref=backref(backreference or cls.__tablename__, uselist=False),
- **relationship_kwargs
- )
+ backref_kwargs = backref_kwargs or {}
+ backref_kwargs.setdefault('uselist', False)
+ return cls._create_relationship(other_table, backref_kwargs, relationship_kwargs,
+ backreference, key=key, foreign_key=foreign_key)
+
@classmethod
def one_to_many_relationship(cls,
- child_table_name,
- foreign_key_name=None,
+ child_table,
+ key=None,
+ foreign_key=None,
+ dict_key=None,
backreference=None,
- key_column_name=None,
+ backref_kwargs=None,
relationship_kwargs=None):
- relationship_kwargs = relationship_kwargs or {}
-
- foreign_keys = lambda: getattr(cls._get_cls_by_tablename(child_table_name),
- foreign_key_name) \
- if foreign_key_name \
- else None
-
- collection_class = attribute_mapped_collection(key_column_name) \
- if key_column_name \
- else list
+ backref_kwargs = backref_kwargs or {}
+ backref_kwargs.setdefault('uselist', False)
- return relationship(
- lambda: cls._get_cls_by_tablename(child_table_name),
- backref=backref(backreference or cls.__tablename__, uselist=False),
- foreign_keys=foreign_keys,
- collection_class=collection_class,
- **relationship_kwargs
- )
+ return cls._create_relationship(child_table, backref_kwargs, relationship_kwargs,
+ backreference, key=key, foreign_key=foreign_key,
+ dict_key=dict_key)
@classmethod
def many_to_one_relationship(cls,
- parent_table_name,
- foreign_key_column=None,
+ parent_table,
+ key=None,
+ foreign_key=None,
backreference=None,
backref_kwargs=None,
relationship_kwargs=None):
@@ -140,33 +147,32 @@ class ModelMixin(object):
Return a one-to-many SQL relationship object
Meant to be used from inside the *child* object
- :param parent_class: Class of the parent table
- :param cls: Class of the child table
- :param foreign_key_column: The column of the foreign key (from the child table)
+ :param parent_table: Name of the parent table
+ :param foreign_key: The column of the foreign key (from the child table)
:param backreference: The name to give to the reference to the child (on the parent table)
"""
- relationship_kwargs = relationship_kwargs or {}
- if foreign_key_column:
- relationship_kwargs.setdefault('foreign_keys', getattr(cls, foreign_key_column))
+ if backreference is None:
+ backreference = utils.pluralize(cls.__tablename__)
backref_kwargs = backref_kwargs or {}
+ backref_kwargs.setdefault('uselist', True)
backref_kwargs.setdefault('lazy', 'dynamic')
# The following line make sure that when the *parent* is deleted, all its connected children
# are deleted as well
backref_kwargs.setdefault('cascade', 'all')
- return relationship(
- lambda: cls._get_cls_by_tablename(parent_table_name),
- backref=backref(backreference or utils.pluralize(cls.__tablename__), **backref_kwargs),
- **relationship_kwargs
- )
+ return cls._create_relationship(parent_table, backref_kwargs, relationship_kwargs,
+ backreference, key=key, foreign_key=foreign_key)
@classmethod
def many_to_many_relationship(cls,
- other_table_name,
+ other_table,
table_prefix=None,
- key_column_name=None,
+ key=None,
+ dict_key=None,
+ backreference=None,
+ backref_kwargs=None,
relationship_kwargs=None):
"""
Return a many-to-many SQL relationship object
@@ -177,80 +183,41 @@ class ModelMixin(object):
2. This method creates a new helper table in the DB
:param cls: The class of the table we're connecting from
- :param other_table_name: The class of the table we're connecting to
- :param table_prefix: Custom prefix for the helper table name and the
- backreference name
- :param key_column_name: If provided, will use a dict class with this column as the key
+ :param other_table: The class of the table we're connecting to
+ :param table_prefix: Custom prefix for the helper table name and the backreference name
+ :param dict_key: If provided, will use a dict class with this column as the key
"""
- relationship_kwargs = relationship_kwargs or {}
- current_table_name = cls.__tablename__
- current_column_name = '{0}_id'.format(current_table_name)
- current_foreign_key = '{0}.id'.format(current_table_name)
+ this_table = cls.__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)
- other_column_name = '{0}_id'.format(other_table_name)
- other_foreign_key = '{0}.id'.format(other_table_name)
+ helper_table = '{0}_{1}'.format(this_table, other_table)
- helper_table_name = '{0}_{1}'.format(current_table_name, other_table_name)
+ if backreference is None:
+ backreference = this_table
+ if table_prefix:
+ helper_table = '{0}_{1}'.format(table_prefix, helper_table)
+ backreference = '{0}_{1}'.format(table_prefix, backreference)
- backref_name = current_table_name
- if table_prefix:
- helper_table_name = '{0}_{1}'.format(table_prefix, helper_table_name)
- backref_name = '{0}_{1}'.format(table_prefix, backref_name)
+ backref_kwargs = backref_kwargs or {}
+ backref_kwargs.setdefault('uselist', True)
- secondary_table = cls.get_secondary_table(
+ relationship_kwargs = relationship_kwargs or {}
+ relationship_kwargs.setdefault('secondary', cls._get_secondary_table(
cls.metadata,
- helper_table_name,
- current_column_name,
+ helper_table,
+ this_column_name,
other_column_name,
- current_foreign_key,
+ this_foreign_key,
other_foreign_key
- )
-
- collection_class = attribute_mapped_collection(key_column_name) \
- if key_column_name \
- else list
+ ))
- return relationship(
- lambda: cls._get_cls_by_tablename(other_table_name),
- secondary=secondary_table,
- backref=backref(backref_name),
- collection_class=collection_class,
- **relationship_kwargs
- )
-
- @staticmethod
- def get_secondary_table(metadata,
- helper_table_name,
- first_column_name,
- second_column_name,
- first_foreign_key,
- second_foreign_key):
- """
- Create a helper table for a many-to-many relationship
-
- :param helper_table_name: The name of the table
- :param first_column_name: The name of the first column in the table
- :param second_column_name: The name of the second column in the table
- :param first_foreign_key: The string representing the first foreign key,
- for example `blueprint.storage_id`, or `tenants.id`
- :param second_foreign_key: The string representing the second foreign key
- :return: A Table object
- """
- return Table(
- helper_table_name,
- metadata,
- Column(
- first_column_name,
- Integer,
- ForeignKey(first_foreign_key)
- ),
- Column(
- second_column_name,
- Integer,
- ForeignKey(second_foreign_key)
- )
- )
+ return cls._create_relationship(other_table, backref_kwargs, relationship_kwargs,
+ backreference, key=key, dict_key=dict_key)
def to_dict(self, fields=None, suppress_error=False):
"""
@@ -260,6 +227,7 @@ class ModelMixin(object):
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:
@@ -281,6 +249,76 @@ class ModelMixin(object):
return res
@classmethod
+ def _create_relationship(cls, table, backref_kwargs, relationship_kwargs, backreference,
+ key=None, foreign_key=None, dict_key=None):
+ relationship_kwargs = relationship_kwargs or {}
+
+ if key:
+ relationship_kwargs.setdefault('foreign_keys',
+ lambda: getattr(
+ cls._get_cls_by_tablename(cls.__tablename__),
+ key))
+
+ elif foreign_key:
+ relationship_kwargs.setdefault('foreign_keys',
+ lambda: getattr(
+ cls._get_cls_by_tablename(table),
+ foreign_key))
+
+ if dict_key:
+ relationship_kwargs.setdefault('collection_class',
+ attribute_mapped_collection(dict_key))
+
+ if backreference == '':
+ return relationship(
+ lambda: cls._get_cls_by_tablename(table),
+ **relationship_kwargs
+ )
+ else:
+ if backreference is None:
+ backreference = cls.__tablename__
+ backref_kwargs = backref_kwargs or {}
+ return relationship(
+ lambda: cls._get_cls_by_tablename(table),
+ backref=backref(backreference, **backref_kwargs),
+ **relationship_kwargs
+ )
+
+ @staticmethod
+ def _get_secondary_table(metadata,
+ helper_table,
+ first_column,
+ second_column,
+ first_foreign_key,
+ second_foreign_key):
+ """
+ Create a helper table for a many-to-many relationship
+
+ :param helper_table: The name of the table
+ :param first_column_name: The name of the first column in the table
+ :param second_column_name: The name of the second column in the table
+ :param first_foreign_key: The string representing the first foreign key,
+ for example `blueprint.storage_id`, or `tenants.id`
+ :param second_foreign_key: The string representing the second foreign key
+ :return: A Table object
+ """
+
+ return Table(
+ helper_table,
+ metadata,
+ Column(
+ first_column,
+ Integer,
+ ForeignKey(first_foreign_key)
+ ),
+ Column(
+ second_column,
+ Integer,
+ ForeignKey(second_foreign_key)
+ )
+ )
+
+ @classmethod
def _association_proxies(cls):
for col, value in vars(cls).items():
if isinstance(value, associationproxy.AssociationProxy):
@@ -293,6 +331,7 @@ class ModelMixin(object):
Mostly for backwards compatibility in the code (that uses `fields`)
"""
+
fields = set(cls._association_proxies())
fields.update(cls.__table__.columns.keys())
return fields - set(getattr(cls, '__private_fields__', []))
@@ -302,9 +341,10 @@ class ModelMixin(object):
"""
Return class reference mapped to table.
- :param tablename: String with name of table.
- :return: Class reference or None.
- """
+ :param tablename: String with name of table.
+ :return: Class reference or None.
+ """
+
if tablename in (cls.__name__, cls.__tablename__):
return cls
@@ -329,3 +369,36 @@ class ModelIDMixin(object):
@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, context):
+ pass
+
+ def coerce_values(self, context, container, report_issues):
+ pass
+
+ def dump(self, context):
+ pass
+
+
+class TemplateModelMixin(InstanceModelMixin):
+ """
+ Mixin for :class:`ServiceTemplate` models.
+
+ All model models can be instantiated into :class:`ServiceInstance` models.
+ """
+
+ def instantiate(self, context, container):
+ raise NotImplementedError
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/daa2d538/aria/modeling/misc.py
----------------------------------------------------------------------
diff --git a/aria/modeling/misc.py b/aria/modeling/misc.py
new file mode 100644
index 0000000..0b50f9a
--- /dev/null
+++ b/aria/modeling/misc.py
@@ -0,0 +1,208 @@
+# 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
+
+from sqlalchemy import (
+ Column,
+ Text,
+ Binary
+)
+from sqlalchemy.ext.declarative import declared_attr
+
+from ..storage import exceptions
+from ..parser.modeling import utils as parser_utils
+from ..utils import collections, formatting, console
+from .bases import InstanceModelMixin, TemplateModelMixin
+
+
+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, nullable=False)
+ type_name = Column(Text, nullable=False)
+
+ # Check: value type
+ _value = Column(Binary, nullable=True)
+ 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:
+ # TODO debug log
+ 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 = parser_utils.coerce_value(context, container, self.value,
+ report_issues)
+
+
+class TypeBase(InstanceModelMixin):
+ """
+ Represents a type and its children.
+ """
+
+ __tablename__ = 'type'
+
+ variant = Column(Text)
+ description = Column(Text)
+ role = Column(Text)
+
+ @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
+
+ 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):
+ if self.name == name:
+ return self
+ for child in self.children:
+ found = child.get_descendant(name)
+ if found is not None:
+ return found
+ return None
+
+ def iter_descendants(self):
+ for child in self.children:
+ yield child
+ for descendant in child.iter_descendants():
+ yield descendant
+
+ def get_role(self, name):
+ def _get_role(the_type):
+ if the_type is None:
+ return None
+ elif the_type.role is None:
+ return _get_role(self.parent)
+ return the_type.role
+
+ return _get_role(self.get_descendant(name))
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('role', self.role)))
+
+ @property
+ def as_raw_all(self):
+ types = []
+ self._append_raw_children(types)
+ return types
+
+ def dump(self, context):
+ if self.name:
+ console.puts(context.style.type(self.name))
+ with context.style.indent:
+ for child in self.children:
+ child.dump(context)
+
+ def _append_raw_children(self, types):
+ for child in self.children:
+ raw_child = formatting.as_raw(child)
+ raw_child['parent'] = self.name
+ types.append(raw_child)
+ child._append_raw_children(types)
+
+
+class MetadataBase(TemplateModelMixin):
+ """
+ Custom values associated with the service.
+
+ This class is used by both service template and service instance elements.
+
+ :ivar name: Name
+ :ivar value: Value
+ """
+
+ __tablename__ = 'metadata'
+
+ name = Column(Text, nullable=False)
+ value = Column(Text)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('value', self.value)))
+
+ def instantiate(self, context, container):
+ from . import models
+ return models.Metadata(name=self.name,
+ value=self.value)
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/daa2d538/aria/modeling/models.py
----------------------------------------------------------------------
diff --git a/aria/modeling/models.py b/aria/modeling/models.py
index b424dea..fc2c669 100644
--- a/aria/modeling/models.py
+++ b/aria/modeling/models.py
@@ -13,62 +13,21 @@
# 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,
orchestration,
+ misc,
bases,
)
-__all__ = (
- 'aria_declarative_base',
-
- # Service template models
- 'ServiceTemplate',
- 'NodeTemplate',
- 'GroupTemplate',
- 'PolicyTemplate',
- 'SubstitutionTemplate',
- 'SubstitutionTemplateMapping',
- 'RequirementTemplate',
- 'RelationshipTemplate',
- 'CapabilityTemplate',
- 'InterfaceTemplate',
- 'OperationTemplate',
- 'ArtifactTemplate',
-
- # Service template and instance models
- 'Parameter',
- 'Metadata',
-
- # Service instance models
- 'Service',
- 'Node',
- 'Group',
- 'Policy',
- 'Substitution',
- 'SubstitutionMapping',
- 'Relationship',
- 'Capability',
- 'Interface',
- 'Operation',
- 'Artifact',
-
- # Orchestrator models
- 'Execution',
- 'ServiceUpdate',
- 'ServiceUpdateStep',
- 'ServiceModification',
- 'Plugin',
- 'Task'
-)
aria_declarative_base = declarative_base(cls=bases.ModelIDMixin)
-# pylint: disable=abstract-method
-
# region service template models
@@ -123,18 +82,6 @@ class ArtifactTemplate(aria_declarative_base, service_template.ArtifactTemplateB
# endregion
-# region service template and instance models
-
-class Parameter(aria_declarative_base, service_template.ParameterBase):
- pass
-
-
-class Metadata(aria_declarative_base, service_template.MetadataBase):
- pass
-
-# endregion
-
-
# region service instance models
class Service(aria_declarative_base, service.ServiceBase):
@@ -209,3 +156,107 @@ class Task(aria_declarative_base, orchestration.TaskBase):
pass
# endregion
+
+
+# region misc models
+
+class Parameter(aria_declarative_base, misc.ParameterBase):
+ pass
+
+
+class Type(aria_declarative_base, misc.TypeBase):
+ pass
+
+
+class Metadata(aria_declarative_base, misc.MetadataBase):
+ 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,
+
+ # Orchestration models
+ Execution,
+ ServiceUpdate,
+ ServiceUpdateStep,
+ ServiceModification,
+ Plugin,
+ Task,
+
+ # Misc models
+ Parameter,
+ Type,
+ Metadata
+]
+
+__all__ = (
+ 'aria_declarative_base',
+ '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',
+ 'Substitution',
+ 'SubstitutionMapping',
+ 'Relationship',
+ 'Capability',
+ 'Interface',
+ 'Operation',
+ 'Artifact',
+
+ # Orchestration models
+ 'Execution',
+ 'ServiceUpdate',
+ 'ServiceUpdateStep',
+ 'ServiceModification',
+ 'Plugin',
+ 'Task',
+
+ # Misc models
+ 'Parameter',
+ 'Type',
+ 'Metadata'
+)
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/daa2d538/aria/modeling/orchestration.py
----------------------------------------------------------------------
diff --git a/aria/modeling/orchestration.py b/aria/modeling/orchestration.py
index 6b5609f..9cbb3cf 100644
--- a/aria/modeling/orchestration.py
+++ b/aria/modeling/orchestration.py
@@ -27,6 +27,9 @@ classes:
* Plugin - plugin implementation model.
* Task - a task
"""
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+
from collections import namedtuple
from datetime import datetime
@@ -57,16 +60,13 @@ __all__ = (
'TaskBase'
)
-# pylint: disable=no-self-argument, no-member, abstract-method
-
class Execution(ModelMixin):
"""
Execution model representation.
"""
- __tablename__ = 'execution' # redundancy for PyLint: SqlAlchemy injects this
- __private_fields__ = ['service_fk']
+ __tablename__ = 'execution'
TERMINATED = 'terminated'
FAILED = 'failed'
@@ -129,6 +129,8 @@ class Execution(ModelMixin):
# region foreign keys
+ __private_fields__ = ['service_fk']
+
@declared_attr
def service_fk(cls):
return cls.foreign_key('service')
@@ -147,13 +149,10 @@ class ServiceUpdateBase(ModelMixin):
"""
Deployment update model representation.
"""
- # Needed only for pylint. the id will be populated by sqlalcehmy and the proper column.
+
steps = None
- __tablename__ = 'service_update' # redundancy for PyLint: SqlAlchemy injects this
-
- __private_fields__ = ['service_fk',
- 'execution_fk']
+ __tablename__ = 'service_update'
_private_fields = ['execution_fk', 'deployment_fk']
@@ -183,6 +182,9 @@ class ServiceUpdateBase(ModelMixin):
# region foreign keys
+ __private_fields__ = ['service_fk',
+ 'execution_fk']
+
@declared_attr
def execution_fk(cls):
return cls.foreign_key('execution', nullable=True)
@@ -204,9 +206,8 @@ class ServiceUpdateStepBase(ModelMixin):
"""
Deployment update step model representation.
"""
- __tablename__ = 'service_update_step' # redundancy for PyLint: SqlAlchemy injects this
- __private_fields__ = ['service_update_fk']
+ __tablename__ = 'service_update_step'
_action_types = namedtuple('ACTION_TYPES', 'ADD, REMOVE, MODIFY')
ACTION_TYPES = _action_types(ADD='add', REMOVE='remove', MODIFY='modify')
@@ -243,6 +244,8 @@ class ServiceUpdateStepBase(ModelMixin):
# region foreign keys
+ __private_fields__ = ['service_update_fk']
+
@declared_attr
def service_update_fk(cls):
return cls.foreign_key('service_update')
@@ -281,9 +284,8 @@ class ServiceModificationBase(ModelMixin):
"""
Deployment modification model representation.
"""
- __tablename__ = 'service_modification' # redundancy for PyLint: SqlAlchemy injects this
- __private_fields__ = ['service_fk']
+ __tablename__ = 'service_modification'
STARTED = 'started'
FINISHED = 'finished'
@@ -300,10 +302,6 @@ class ServiceModificationBase(ModelMixin):
status = Column(Enum(*STATES, name='deployment_modification_status'))
@declared_attr
- def service_fk(cls):
- return cls.foreign_key('service')
-
- @declared_attr
def service(cls):
return cls.many_to_one_relationship('service',
backreference='modifications')
@@ -312,14 +310,23 @@ class ServiceModificationBase(ModelMixin):
def service_name(cls):
return association_proxy('service', cls.name_column_name())
+ # region foreign keys
+
+ __private_fields__ = ['service_fk']
+
+ @declared_attr
+ def service_fk(cls):
+ return cls.foreign_key('service')
+
+ # endregion
+
class PluginBase(ModelMixin):
"""
Plugin model representation.
"""
- __tablename__ = 'plugin' # redundancy for PyLint: SqlAlchemy injects this
- __private_fields__ = ['service_template_fk']
+ __tablename__ = 'plugin'
archive_name = Column(Text, nullable=False, index=True)
distribution = Column(Text)
@@ -335,6 +342,8 @@ class PluginBase(ModelMixin):
# region foreign keys
+ __private_fields__ = ['service_template_fk']
+
@declared_attr
def service_template_fk(cls):
return cls.foreign_key('service_template', nullable=True)
@@ -346,12 +355,33 @@ class TaskBase(ModelMixin):
"""
A Model which represents an task
"""
- __tablename__ = 'task' # redundancy for PyLint: SqlAlchemy injects this
- __private_fields__ = ['node_fk',
- 'relationship_fk',
- 'plugin_fk',
- 'execution_fk']
+ __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_name(cls):
@@ -384,60 +414,7 @@ class TaskBase(ModelMixin):
@declared_attr
def inputs(cls):
return cls.many_to_many_relationship('parameter', table_prefix='inputs',
- key_column_name='name')
-
- # region foreign keys
-
- @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
-
- 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)
-
- @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
-
- INFINITE_RETRIES = -1
+ dict_key='name')
status = Column(Enum(*STATES, name='status'), default=PENDING)
@@ -474,6 +451,39 @@ class TaskBase(ModelMixin):
"""
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_task(cls, instance, runs_on, **kwargs):
return cls(node=instance, _runs_on=runs_on, **kwargs)
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/daa2d538/aria/modeling/service.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service.py b/aria/modeling/service.py
index 78cd920..1309472 100644
--- a/aria/modeling/service.py
+++ b/aria/modeling/service.py
@@ -13,6 +13,8 @@
# 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,
@@ -22,7 +24,7 @@ from sqlalchemy import DateTime
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy.ext.declarative import declared_attr
-from .bases import ModelMixin
+from .bases import InstanceModelMixin
from ..parser import validation
from ..utils import collections, formatting, console
@@ -31,34 +33,10 @@ from . import (
types as modeling_types
)
-# pylint: disable=no-self-argument, no-member, abstract-method
-
-
-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`.
+class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods
"""
-
- @property
- def as_raw(self):
- raise NotImplementedError
-
- def validate(self, context):
- pass
-
- def coerce_values(self, context, container, report_issues):
- pass
-
- def dump(self, context):
- pass
-
-
-class ServiceBase(_InstanceModelMixin): # pylint: disable=too-many-public-methods
- """
- A service instance is an instance of a :class:`ServiceTemplate`.
+ A service instance is usually an instance of a :class:`ServiceTemplate`.
You will usually not create it programmatically, but instead instantiate it from the template.
@@ -73,17 +51,14 @@ class ServiceBase(_InstanceModelMixin): # pylint: disable=too-many-public-method
:ivar operations: Dict of :class:`Operation`
"""
- __tablename__ = 'service' # redundancy for PyLint: SqlAlchemy injects this
-
- __private_fields__ = ['substituion_fk',
- 'service_template_fk']
+ __tablename__ = 'service'
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', key_column_name='name')
+ return cls.many_to_many_relationship('metadata', dict_key='name')
@declared_attr
def nodes(cls):
@@ -104,22 +79,22 @@ class ServiceBase(_InstanceModelMixin): # pylint: disable=too-many-public-method
@declared_attr
def inputs(cls):
return cls.many_to_many_relationship('parameter', table_prefix='inputs',
- key_column_name='name')
+ dict_key='name')
@declared_attr
def outputs(cls):
return cls.many_to_many_relationship('parameter', table_prefix='outputs',
- key_column_name='name')
+ dict_key='name')
@declared_attr
def operations(cls):
- return cls.one_to_many_relationship('operation', key_column_name='name')
+ 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 orchestrator required columns
+ # region orchestration
created_at = Column(DateTime, nullable=False, index=True)
updated_at = Column(DateTime)
@@ -135,6 +110,9 @@ class ServiceBase(_InstanceModelMixin): # pylint: disable=too-many-public-method
# region foreign keys
+ __private_fields__ = ['substituion_fk',
+ 'service_template_fk']
+
# Service one-to-one to Substitution
@declared_attr
def substitution_fk(cls):
@@ -143,7 +121,7 @@ class ServiceBase(_InstanceModelMixin): # pylint: disable=too-many-public-method
# Service many-to-one to ServiceTemplate
@declared_attr
def service_template_fk(cls):
- return cls.foreign_key('service_template')
+ return cls.foreign_key('service_template', nullable=True)
# endregion
@@ -276,14 +254,13 @@ class ServiceBase(_InstanceModelMixin): # pylint: disable=too-many-public-method
self._dump_graph_node(context, target_node)
-class NodeBase(_InstanceModelMixin):
+class NodeBase(InstanceModelMixin):
"""
- An instance of a :class:`NodeTemplate`.
+ Usually an instance of a :class:`NodeTemplate`.
Nodes may have zero or more :class:`Relationship` instances to other nodes.
- :ivar name: Unique ID (prefixed with the template name)
- :ivar type_name: Must be represented in the :class:`ModelingContext`
+ :ivar name: Unique ID (often prefixed with the template name)
:ivar properties: Dict of :class:`Parameter`
:ivar interfaces: Dict of :class:`Interface`
:ivar artifacts: Dict of :class:`Artifact`
@@ -291,41 +268,39 @@ class NodeBase(_InstanceModelMixin):
:ivar relationships: List of :class:`Relationship`
"""
- __tablename__ = 'node' # redundancy for PyLint: SqlAlchemy injects this
+ __tablename__ = 'node'
- __private_fields__ = ['host_fk',
- 'service_fk',
- 'node_template_fk']
-
- type_name = Column(Text)
+ @declared_attr
+ def type(cls):
+ return cls.many_to_one_relationship('type')
@declared_attr
def properties(cls):
return cls.many_to_many_relationship('parameter', table_prefix='properties',
- key_column_name='name')
+ dict_key='name')
@declared_attr
def interfaces(cls):
- return cls.one_to_many_relationship('interface', key_column_name='name')
+ return cls.one_to_many_relationship('interface', dict_key='name')
@declared_attr
def artifacts(cls):
- return cls.one_to_many_relationship('artifact', key_column_name='name')
+ return cls.one_to_many_relationship('artifact', dict_key='name')
@declared_attr
def capabilities(cls):
- return cls.one_to_many_relationship('capability', key_column_name='name')
+ 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_name='source_node_fk',
+ foreign_key='source_node_fk',
backreference='source_node')
@declared_attr
def inbound_relationships(cls):
return cls.one_to_many_relationship('relationship',
- foreign_key_name='target_node_fk',
+ foreign_key='target_node_fk',
backreference='target_node')
@declared_attr
@@ -336,7 +311,7 @@ class NodeBase(_InstanceModelMixin):
def node_template(cls):
return cls.many_to_one_relationship('node_template')
- # region orchestrator required columns
+ # region orchestration
runtime_properties = Column(modeling_types.Dict)
scaling_groups = Column(modeling_types.List)
@@ -369,6 +344,16 @@ class NodeBase(_InstanceModelMixin):
# 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):
@@ -382,7 +367,7 @@ class NodeBase(_InstanceModelMixin):
# Node many-to-one to NodeTemplate
@declared_attr
def node_template_fk(cls):
- return cls.foreign_key('node_template')
+ return cls.foreign_key('node_template', nullable=True)
# endregion
@@ -426,13 +411,16 @@ class NodeBase(_InstanceModelMixin):
target_node = target_nodes[0]
if target_node is not None:
- relationship = models.Relationship(
- name=requirement_template.name,
- requirement_template=requirement_template,
- target_node=target_node,
- capability=target_capability
- )
+ if requirement_template.relationship_template is not None:
+ relationship = \
+ requirement_template.relationship_template.instantiate(context, self)
+ else:
+ relationship = models.Relationship(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 '
@@ -503,8 +491,8 @@ class NodeBase(_InstanceModelMixin):
def dump(self, context):
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)))
- console.puts('Type: {0}'.format(context.style.type(self.type_name)))
utils.dump_parameters(context, self.properties)
utils.dump_interfaces(context, self.interfaces)
utils.dump_dict_values(context, self.artifacts, 'Artifacts')
@@ -512,38 +500,33 @@ class NodeBase(_InstanceModelMixin):
utils.dump_list_values(context, self.outbound_relationships, 'Relationships')
-class GroupBase(_InstanceModelMixin):
+class GroupBase(InstanceModelMixin):
"""
- An instance of a :class:`GroupTemplate`.
+ Usually an instance of a :class:`GroupTemplate`.
- :ivar name: Unique ID (prefixed with the template name)
- :ivar type_name: Must be represented in the :class:`ModelingContext`
- :ivar template_name: Must be represented in the :class:`ServiceTemplate`
+ :ivar name: Unique ID (often equal to the template name)
:ivar properties: Dict of :class:`Parameter`
:ivar interfaces: Dict of :class:`Interface`
- :ivar member_node_ids: Must be represented in the :class:`ServiceInstance`
- :ivar member_group_ids: Must be represented in the :class:`ServiceInstance`
"""
- __tablename__ = 'group' # redundancy for PyLint: SqlAlchemy injects this
-
- __private_fields__ = ['service_fk',
- 'group_template_fk']
+ __tablename__ = 'group'
- type_name = Column(Text)
- template_name = Column(Text)
+ @declared_attr
+ def type(cls):
+ return cls.many_to_one_relationship('type')
@declared_attr
def properties(cls):
return cls.many_to_many_relationship('parameter', table_prefix='properties',
- key_column_name='name')
+ dict_key='name')
@declared_attr
def interfaces(cls):
- return cls.one_to_many_relationship('interface', key_column_name='name')
+ return cls.one_to_many_relationship('interface', dict_key='name')
- member_node_ids = Column(modeling_types.StrictList(basestring))
- member_group_ids = Column(modeling_types.StrictList(basestring))
+ @declared_attr
+ def nodes(cls):
+ return cls.many_to_many_relationship('node')
@declared_attr
def group_template(cls):
@@ -551,6 +534,15 @@ class GroupBase(_InstanceModelMixin):
# region foreign_keys
+ __private_fields__ = ['type_fk',
+ 'service_fk',
+ 'group_template_fk']
+
+ # Group many-to-one to Type
+ @declared_attr
+ def type_fk(cls):
+ return cls.foreign_key('type')
+
# Service one-to-many to Group
@declared_attr
def service_fk(cls):
@@ -559,7 +551,7 @@ class GroupBase(_InstanceModelMixin):
# Group many-to-one to GroupTemplate
@declared_attr
def group_template_fk(cls):
- return cls.foreign_key('group_template')
+ return cls.foreign_key('group_template', nullable=True)
# endregion
@@ -567,21 +559,10 @@ class GroupBase(_InstanceModelMixin):
def as_raw(self):
return collections.OrderedDict((
('name', self.name),
- ('type_name', self.type_name),
- ('template_name', self.template_name),
('properties', formatting.as_raw_dict(self.properties)),
- ('interfaces', formatting.as_raw_list(self.interfaces)),
- ('member_node_ids', self.member_node_ids),
- ('member_group_ids', self.member_group_ids)))
+ ('interfaces', formatting.as_raw_list(self.interfaces))))
def validate(self, context):
- if context.modeling.group_types.get_descendant(self.type_name) is None:
- context.validation.report('group "{0}" has an unknown type: {1}'.format(
- self.name, # pylint: disable=no-member
- # TODO fix self.name reference
- formatting.safe_repr(self.type_name)),
- level=validation.Issue.BETWEEN_TYPES)
-
utils.validate_dict_values(context, self.properties)
utils.validate_dict_values(context, self.interfaces)
@@ -592,42 +573,42 @@ class GroupBase(_InstanceModelMixin):
def dump(self, context):
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)))
- console.puts('Template: {0}'.format(context.style.type(self.template_name)))
+ console.puts('Type: {0}'.format(context.style.type(self.type.name)))
utils.dump_parameters(context, self.properties)
utils.dump_interfaces(context, self.interfaces)
- if self.member_node_ids:
+ if self.nodes:
console.puts('Member nodes:')
with context.style.indent:
- for node_id in self.member_node_ids:
- console.puts(context.style.node(node_id))
+ for node in self.nodes:
+ console.puts(context.style.node(node.name))
-class PolicyBase(_InstanceModelMixin):
+class PolicyBase(InstanceModelMixin):
"""
- An instance of a :class:`PolicyTemplate`.
+ Usually an instance of a :class:`PolicyTemplate`.
:ivar name: Name
- :ivar type_name: Must be represented in the :class:`ModelingContext`
:ivar properties: Dict of :class:`Parameter`
- :ivar target_node_ids: Must be represented in the :class:`ServiceInstance`
- :ivar target_group_ids: Must be represented in the :class:`ServiceInstance`
"""
- __tablename__ = 'policy' # redundancy for PyLint: SqlAlchemy injects this
+ __tablename__ = 'policy'
- __private_fields__ = ['service_fk',
- 'policy_template_fk']
-
- type_name = Column(Text)
+ @declared_attr
+ def type(cls):
+ return cls.many_to_one_relationship('type')
@declared_attr
def properties(cls):
return cls.many_to_many_relationship('parameter', table_prefix='properties',
- key_column_name='name')
+ dict_key='name')
- target_node_ids = Column(modeling_types.StrictList(basestring))
- target_group_ids = Column(modeling_types.StrictList(basestring))
+ @declared_attr
+ def nodes(cls):
+ return cls.many_to_many_relationship('node')
+
+ @declared_attr
+ def groups(cls):
+ return cls.many_to_many_relationship('group')
@declared_attr
def policy_template(cls):
@@ -635,6 +616,15 @@ class PolicyBase(_InstanceModelMixin):
# region foreign_keys
+ __private_fields__ = ['type_fk',
+ 'service_fk',
+ 'policy_template_fk']
+
+ # Policy many-to-one to Type
+ @declared_attr
+ def type_fk(cls):
+ return cls.foreign_key('type')
+
# Service one-to-many to Policy
@declared_attr
def service_fk(cls):
@@ -643,7 +633,7 @@ class PolicyBase(_InstanceModelMixin):
# Policy many-to-one to PolicyTemplate
@declared_attr
def policy_template_fk(cls):
- return cls.foreign_key('policy_template')
+ return cls.foreign_key('policy_template', nullable=True)
# endregion
@@ -652,16 +642,9 @@ class PolicyBase(_InstanceModelMixin):
return collections.OrderedDict((
('name', self.name),
('type_name', self.type_name),
- ('properties', formatting.as_raw_dict(self.properties)),
- ('target_node_ids', self.target_node_ids),
- ('target_group_ids', self.target_group_ids)))
+ ('properties', formatting.as_raw_dict(self.properties))))
def validate(self, context):
- if context.modeling.policy_types.get_descendant(self.type_name) is None:
- context.validation.report('policy "{0}" has an unknown type: {1}'.format(
- self.name, formatting.safe_repr(self.type_name)),
- level=validation.Issue.BETWEEN_TYPES)
-
utils.validate_dict_values(context, self.properties)
def coerce_values(self, context, container, report_issues):
@@ -670,37 +653,36 @@ class PolicyBase(_InstanceModelMixin):
def dump(self, context):
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)))
+ console.puts('Type: {0}'.format(context.style.type(self.type.name)))
utils.dump_parameters(context, self.properties)
- if self.target_node_ids:
+ if self.nodes:
console.puts('Target nodes:')
with context.style.indent:
- for node_id in self.target_node_ids:
- console.puts(context.style.node(node_id))
- if self.target_group_ids:
+ 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_id in self.target_group_ids:
- console.puts(context.style.node(group_id))
+ for group in self.groups:
+ console.puts(context.style.node(group.name))
-class SubstitutionBase(_InstanceModelMixin):
+class SubstitutionBase(InstanceModelMixin):
"""
- An instance of a :class:`SubstitutionTemplate`.
+ Usually an instance of a :class:`SubstitutionTemplate`.
- :ivar node_type_name: Must be represented in the :class:`ModelingContext`
:ivar mappings: Dict of :class:` SubstitutionMapping`
"""
- __tablename__ = 'substitution' # redundancy for PyLint: SqlAlchemy injects this
-
- __private_fields__ = ['substitution_template_fk']
+ __tablename__ = 'substitution'
- node_type_name = Column(Text)
+ @declared_attr
+ def node_type(cls):
+ return cls.many_to_one_relationship('type')
@declared_attr
def mappings(cls):
- return cls.one_to_many_relationship('substitution_mapping', key_column_name='name')
+ return cls.one_to_many_relationship('substitution_mapping', dict_key='name')
@declared_attr
def substitution_template(cls):
@@ -708,12 +690,21 @@ class SubstitutionBase(_InstanceModelMixin):
# region foreign_keys
+ __private_fields__ = ['node_type_fk',
+ 'substitution_template_fk']
+
+ # Substitution many-to-one to Type
+ @declared_attr
+ def node_type_fk(cls):
+ return cls.foreign_key('type')
+
# Substitution many-to-one to SubstitutionTemplate
@declared_attr
def substitution_template_fk(cls):
- return cls.foreign_key('substitution_template')
+ return cls.foreign_key('substitution_template', nullable=True)
# endregion
+
@property
def as_raw(self):
return collections.OrderedDict((
@@ -721,13 +712,6 @@ class SubstitutionBase(_InstanceModelMixin):
('mappings', formatting.as_raw_dict(self.mappings))))
def validate(self, context):
- if context.modeling.node_types.get_descendant(self.node_type_name) is None:
- context.validation.report('substitution "{0}" has an unknown type: {1}'.format(
- self.name, # pylint: disable=no-member
- # TODO fix self.name reference
- formatting.safe_repr(self.node_type_name)),
- level=validation.Issue.BETWEEN_TYPES)
-
utils.validate_dict_values(context, self.mappings)
def coerce_values(self, context, container, report_issues):
@@ -736,23 +720,18 @@ class SubstitutionBase(_InstanceModelMixin):
def dump(self, context):
console.puts('Substitution:')
with context.style.indent:
- console.puts('Node type: {0}'.format(context.style.type(self.node_type_name)))
+ console.puts('Node type: {0}'.format(context.style.type(self.node_type.name)))
utils.dump_dict_values(context, self.mappings, 'Mappings')
-class SubstitutionMappingBase(_InstanceModelMixin):
+class SubstitutionMappingBase(InstanceModelMixin):
"""
An instance of a :class:`SubstitutionMappingTemplate`.
:ivar name: Exposed capability or requirement name
"""
- __tablename__ = 'substitution_mapping' # redundancy for PyLint: SqlAlchemy injects this
-
- __private_fields__ = ['substitution_fk',
- 'node_fk',
- 'capability_fk',
- 'requirement_template_fk']
+ __tablename__ = 'substitution_mapping'
@declared_attr
def node(cls):
@@ -768,6 +747,11 @@ class SubstitutionMappingBase(_InstanceModelMixin):
# region foreign keys
+ __private_fields__ = ['substitution_fk',
+ 'node_fk',
+ 'capability_fk',
+ 'requirement_template_fk']
+
# Substitution one-to-many to SubstitutionMapping
@declared_attr
def substitution_fk(cls):
@@ -812,36 +796,31 @@ class SubstitutionMappingBase(_InstanceModelMixin):
else self.requirement_template.name)))
-class RelationshipBase(_InstanceModelMixin):
+class RelationshipBase(InstanceModelMixin):
"""
Connects :class:`Node` to another node.
- An instance of a :class:`RelationshipTemplate`.
+ Might be an instance of a :class:`RelationshipTemplate`.
:ivar name: Name (usually the name of the requirement at the source node template)
- :ivar type_name: Must be represented in the :class:`ModelingContext`
:ivar properties: Dict of :class:`Parameter`
:ivar interfaces: Dict of :class:`Interface`
"""
- __tablename__ = 'relationship' # redundancy for PyLint: SqlAlchemy injects this
+ __tablename__ = 'relationship'
- __private_fields__ = ['source_node_fk',
- 'target_node_fk',
- 'capability_fk',
- 'requirement_template_fk',
- 'relationship_template_fk']
-
- type_name = Column(Text)
+ @declared_attr
+ def type(cls):
+ return cls.many_to_one_relationship('type')
@declared_attr
def properties(cls):
return cls.many_to_many_relationship('parameter', table_prefix='properties',
- key_column_name='name')
+ dict_key='name')
@declared_attr
def interfaces(cls):
- return cls.one_to_many_relationship('interface', key_column_name='name')
+ return cls.one_to_many_relationship('interface', dict_key='name')
@declared_attr
def capability(cls):
@@ -855,7 +834,7 @@ class RelationshipBase(_InstanceModelMixin):
def relationship_template(cls):
return cls.many_to_one_relationship('relationship_template')
- # region orchestrator required columns
+ # region orchestration
source_position = Column(Integer) # ???
target_position = Column(Integer) # ???
@@ -864,6 +843,18 @@ class RelationshipBase(_InstanceModelMixin):
# region foreign keys
+ __private_fields__ = ['type_fk',
+ 'source_node_fk',
+ 'target_node_fk',
+ 'capability_fk',
+ 'requirement_template_fk',
+ 'relationship_template_fk']
+
+ # Relationship many-to-one to Type
+ @declared_attr
+ def type_fk(cls):
+ return cls.foreign_key('type', nullable=True)
+
# Node one-to-many to Relationship
@declared_attr
def source_node_fk(cls):
@@ -882,7 +873,7 @@ class RelationshipBase(_InstanceModelMixin):
# Relationship many-to-one to RequirementTemplate
@declared_attr
def requirement_template_fk(cls):
- return cls.foreign_key('requirement_template')
+ return cls.foreign_key('requirement_template', nullable=True)
# Relationship many-to-one to RelationshipTemplate
@declared_attr
@@ -902,12 +893,6 @@ class RelationshipBase(_InstanceModelMixin):
('interfaces', formatting.as_raw_list(self.interfaces))))
def validate(self, context):
- if self.type_name:
- if context.modeling.relationship_types.get_descendant(self.type_name) is None:
- context.validation.report('relationship "{0}" has an unknown type: {1}'.format(
- self.name,
- formatting.safe_repr(self.type_name)),
- level=validation.Issue.BETWEEN_TYPES)
utils.validate_dict_values(context, self.properties)
utils.validate_dict_values(context, self.interfaces)
@@ -922,34 +907,35 @@ class RelationshipBase(_InstanceModelMixin):
console.puts('->')
with context.style.indent:
console.puts('Node: {0}'.format(context.style.node(self.target_node.name)))
- if self.type_name is not None:
- console.puts('Relationship type: {0}'.format(context.style.type(self.type_name)))
- #if self.template_name is not None:
- # console.puts('Relationship template: {0}'.format(
- # context.style.node(self.template_name)))
+ if self.capability:
+ console.puts('Capability: {0}'.format(context.style.node(self.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_parameters(context, self.properties)
utils.dump_interfaces(context, self.interfaces, 'Interfaces')
-class CapabilityBase(_InstanceModelMixin):
+class CapabilityBase(InstanceModelMixin):
"""
A capability of a :class:`Node`.
- An instance of a :class:`CapabilityTemplate`.
+ Usually an instance of a :class:`CapabilityTemplate`.
:ivar name: Name
- :ivar type_name: Must be represented in the :class:`ModelingContext`
:ivar min_occurrences: Minimum number of requirement matches required
:ivar max_occurrences: Maximum number of requirement matches allowed
:ivar properties: Dict of :class:`Parameter`
"""
- __tablename__ = 'capability' # redundancy for PyLint: SqlAlchemy injects this
+ __tablename__ = 'capability'
- __private_fields__ = ['node_fk',
- 'capability_template_fk']
+ @declared_attr
+ def type(cls):
+ return cls.many_to_one_relationship('type')
- type_name = Column(Text)
min_occurrences = Column(Integer, default=None) # optional
max_occurrences = Column(Integer, default=None) # optional
occurrences = Column(Integer, default=0)
@@ -957,7 +943,7 @@ class CapabilityBase(_InstanceModelMixin):
@declared_attr
def properties(cls):
return cls.many_to_many_relationship('parameter', table_prefix='properties',
- key_column_name='name')
+ dict_key='name')
@declared_attr
def capability_template(cls):
@@ -965,6 +951,15 @@ class CapabilityBase(_InstanceModelMixin):
# region foreign_keys
+ __private_fields__ = ['capability_fk',
+ 'node_fk',
+ 'capability_template_fk']
+
+ # Capability many-to-one to Type
+ @declared_attr
+ def type_fk(cls):
+ return cls.foreign_key('type')
+
# Node one-to-many to Capability
@declared_attr
def node_fk(cls):
@@ -973,7 +968,7 @@ class CapabilityBase(_InstanceModelMixin):
# Capability many-to-one to CapabilityTemplate
@declared_attr
def capability_template_fk(cls):
- return cls.foreign_key('capability_template')
+ return cls.foreign_key('capability_template', nullable=True)
# endregion
@@ -998,12 +993,6 @@ class CapabilityBase(_InstanceModelMixin):
('properties', formatting.as_raw_dict(self.properties))))
def validate(self, context):
- if context.modeling.capability_types.get_descendant(self.type_name) is None:
- context.validation.report('capability "{0}" has an unknown type: {1}'.format(
- self.name,
- formatting.safe_repr(self.type_name)),
- level=validation.Issue.BETWEEN_TYPES)
-
utils.validate_dict_values(context, self.properties)
def coerce_values(self, context, container, report_issues):
@@ -1012,7 +1001,7 @@ class CapabilityBase(_InstanceModelMixin):
def dump(self, context):
console.puts(context.style.node(self.name))
with context.style.indent:
- console.puts('Type: {0}'.format(context.style.type(self.type_name)))
+ console.puts('Type: {0}'.format(context.style.type(self.type.name)))
console.puts('Occurrences: {0:d} ({1:d}{2})'.format(
self.occurrences,
self.min_occurrences or 0,
@@ -1022,35 +1011,34 @@ class CapabilityBase(_InstanceModelMixin):
utils.dump_parameters(context, self.properties)
-class InterfaceBase(_InstanceModelMixin):
+class InterfaceBase(InstanceModelMixin):
"""
A typed set of :class:`Operation`.
+
+ Usually an instance of :class:`InterfaceTemplate`.
:ivar name: Name
:ivar description: Description
- :ivar type_name: Must be represented in the :class:`ModelingContext`
:ivar inputs: Dict of :class:`Parameter`
:ivar operations: Dict of :class:`Operation`
"""
- __tablename__ = 'interface' # redundancy for PyLint: SqlAlchemy injects this
+ __tablename__ = 'interface'
- __private_fields__ = ['node_fk',
- 'group_fk',
- 'relationship_fk',
- 'interface_template_fk']
+ @declared_attr
+ def type(cls):
+ return cls.many_to_one_relationship('type')
description = Column(Text)
- type_name = Column(Text)
@declared_attr
def inputs(cls):
return cls.many_to_many_relationship('parameter', table_prefix='inputs',
- key_column_name='name')
+ dict_key='name')
@declared_attr
def operations(cls):
- return cls.one_to_many_relationship('operation', key_column_name='name')
+ return cls.one_to_many_relationship('operation', dict_key='name')
@declared_attr
def interface_template(cls):
@@ -1058,6 +1046,17 @@ class InterfaceBase(_InstanceModelMixin):
# region foreign_keys
+ __private_fields__ = ['type_fk',
+ 'node_fk',
+ 'group_fk',
+ 'relationship_fk',
+ 'interface_template_fk']
+
+ # Interface many-to-one to Type
+ @declared_attr
+ def type_fk(cls):
+ return cls.foreign_key('type')
+
# Node one-to-many to Interface
@declared_attr
def node_fk(cls):
@@ -1076,7 +1075,7 @@ class InterfaceBase(_InstanceModelMixin):
# Interface many-to-one to InterfaceTemplate
@declared_attr
def interface_template_fk(cls):
- return cls.foreign_key('interface_template')
+ return cls.foreign_key('interface_template', nullable=True)
# endregion
@@ -1090,13 +1089,6 @@ class InterfaceBase(_InstanceModelMixin):
('operations', formatting.as_raw_list(self.operations))))
def validate(self, context):
- if self.type_name:
- if context.modeling.interface_types.get_descendant(self.type_name) is None:
- context.validation.report('interface "{0}" has an unknown type: {1}'.format(
- self.name,
- formatting.safe_repr(self.type_name)),
- level=validation.Issue.BETWEEN_TYPES)
-
utils.validate_dict_values(context, self.inputs)
utils.validate_dict_values(context, self.operations)
@@ -1109,14 +1101,16 @@ class InterfaceBase(_InstanceModelMixin):
if self.description:
console.puts(context.style.meta(self.description))
with context.style.indent:
- console.puts('Interface type: {0}'.format(context.style.type(self.type_name)))
+ console.puts('Interface type: {0}'.format(context.style.type(self.type.name)))
utils.dump_parameters(context, self.inputs, 'Inputs')
utils.dump_dict_values(context, self.operations, 'Operations')
-class OperationBase(_InstanceModelMixin):
+class OperationBase(InstanceModelMixin):
"""
An operation in a :class:`Interface`.
+
+ Might be an instance of :class:`OperationTemplate`.
:ivar name: Name
:ivar description: Description
@@ -1128,12 +1122,7 @@ class OperationBase(_InstanceModelMixin):
:ivar inputs: Dict of :class:`Parameter`
"""
- __tablename__ = 'operation' # redundancy for PyLint: SqlAlchemy injects this
-
- __private_fields__ = ['service_fk',
- 'interface_fk',
- 'plugin_fk',
- 'operation_template_fk']
+ __tablename__ = 'operation'
description = Column(Text)
implementation = Column(Text)
@@ -1145,7 +1134,7 @@ class OperationBase(_InstanceModelMixin):
@declared_attr
def inputs(cls):
return cls.many_to_many_relationship('parameter', table_prefix='inputs',
- key_column_name='name')
+ dict_key='name')
@declared_attr
def plugin(cls):
@@ -1157,6 +1146,11 @@ class OperationBase(_InstanceModelMixin):
# region foreign_keys
+ __private_fields__ = ['service_fk',
+ 'interface_fk',
+ 'plugin_fk',
+ 'operation_template_fk']
+
# Service one-to-many to Operation
@declared_attr
def service_fk(cls):
@@ -1175,7 +1169,7 @@ class OperationBase(_InstanceModelMixin):
# Operation many-to-one to OperationTemplate
@declared_attr
def operation_template_fk(cls):
- return cls.foreign_key('operation_template')
+ return cls.foreign_key('operation_template', nullable=True)
# endregion
@@ -1192,6 +1186,7 @@ class OperationBase(_InstanceModelMixin):
('inputs', formatting.as_raw_dict(self.inputs))))
def validate(self, context):
+ # TODO must be associated with interface or service
utils.validate_dict_values(context, self.inputs)
def coerce_values(self, context, container, report_issues):
@@ -1219,13 +1214,14 @@ class OperationBase(_InstanceModelMixin):
utils.dump_parameters(context, self.inputs, 'Inputs')
-class ArtifactBase(_InstanceModelMixin):
+class ArtifactBase(InstanceModelMixin):
"""
A file associated with a :class:`Node`.
+
+ Usually an instance of :class:`ArtifactTemplate`.
:ivar name: Name
:ivar description: Description
- :ivar type_name: Must be represented in the :class:`ModelingContext`
:ivar source_path: Source path (CSAR or repository)
:ivar target_path: Path at destination machine
:ivar repository_url: Repository URL
@@ -1233,10 +1229,11 @@ class ArtifactBase(_InstanceModelMixin):
:ivar properties: Dict of :class:`Parameter`
"""
- __tablename__ = 'artifact' # redundancy for PyLint: SqlAlchemy injects this
+ __tablename__ = 'artifact'
- __private_fields__ = ['node_fk',
- 'artifact_template_fk']
+ @declared_attr
+ def type(cls):
+ return cls.many_to_one_relationship('type')
description = Column(Text)
type_name = Column(Text)
@@ -1248,7 +1245,7 @@ class ArtifactBase(_InstanceModelMixin):
@declared_attr
def properties(cls):
return cls.many_to_many_relationship('parameter', table_prefix='properties',
- key_column_name='name')
+ dict_key='name')
@declared_attr
def artifact_template(cls):
@@ -1256,6 +1253,15 @@ class ArtifactBase(_InstanceModelMixin):
# region foreign_keys
+ __private_fields__ = ['type_fk',
+ 'node_fk',
+ 'artifact_template_fk']
+
+ # Artifact many-to-one to Type
+ @declared_attr
+ def type_fk(cls):
+ return cls.foreign_key('type')
+
# Node one-to-many to Artifact
@declared_attr
def node_fk(cls):
@@ -1264,7 +1270,7 @@ class ArtifactBase(_InstanceModelMixin):
# Artifact many-to-one to ArtifactTemplate
@declared_attr
def artifact_template_fk(cls):
- return cls.foreign_key('artifact_template')
+ return cls.foreign_key('artifact_template', nullable=True)
# endregion
@@ -1281,11 +1287,6 @@ class ArtifactBase(_InstanceModelMixin):
('properties', formatting.as_raw_dict(self.properties))))
def validate(self, context):
- if context.modeling.artifact_types.get_descendant(self.type_name) is None:
- context.validation.report('artifact "{0}" has an unknown type: {1}'.format(
- self.name,
- formatting.safe_repr(self.type_name)),
- level=validation.Issue.BETWEEN_TYPES)
utils.validate_dict_values(context, self.properties)
def coerce_values(self, context, container, report_issues):
@@ -1296,7 +1297,7 @@ class ArtifactBase(_InstanceModelMixin):
if self.description:
console.puts(context.style.meta(self.description))
with context.style.indent:
- console.puts('Artifact type: {0}'.format(context.style.type(self.type_name)))
+ console.puts('Artifact type: {0}'.format(context.style.type(self.type.name)))
console.puts('Source path: {0}'.format(context.style.literal(self.source_path)))
if self.target_path is not None:
console.puts('Target path: {0}'.format(context.style.literal(self.target_path)))