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/02/24 03:18:07 UTC
[1/4] incubator-ariatosca git commit: Modeling refactoring;
improved metadata; work on instance models
Repository: incubator-ariatosca
Updated Branches:
refs/heads/ARIA-105-integrate-modeling 1a0b85ab1 -> f1074fee2
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/template_elements.py
----------------------------------------------------------------------
diff --git a/aria/modeling/template_elements.py b/aria/modeling/template_elements.py
deleted file mode 100644
index fded72c..0000000
--- a/aria/modeling/template_elements.py
+++ /dev/null
@@ -1,1426 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from copy import deepcopy
-from types import FunctionType
-
-from sqlalchemy import (
- Column,
- Text,
- Integer,
- DateTime,
- Boolean,
-)
-from sqlalchemy.ext.associationproxy import association_proxy
-from sqlalchemy.ext.declarative import declared_attr
-
-from ..parser import validation
-from ..utils import collections, formatting, console
-from . import (
- utils,
- instance_elements,
- structure,
- type as aria_type
-)
-
-
-# pylint: disable=no-self-argument, no-member, abstract-method
-
-# region Element templates
-
-class ServiceTemplateBase(structure.ModelMixin):
- """
- A service model is a normalized service template from which :class:`ServiceInstance` instances
- can be created.
-
- It is usually created by various DSL parsers, such as ARIA's TOSCA extension. However, it
- can also be created programmatically.
-
- Properties:
-
- * :code:`description`: Human-readable description
- * :code:`meta_data`: :class:`Metadata`
- * :code:`node_templates`: Dict of :class:`NodeTemplate`
- * :code:`group_templates`: Dict of :class:`GroupTemplate`
- * :code:`policy_templates`: Dict of :class:`PolicyTemplate`
- * :code:`substitution_template`: :class:`SubstituionTemplate`
- * :code:`inputs`: Dict of :class:`Parameter`
- * :code:`outputs`: Dict of :class:`Parameter`
- * :code:`operation_templates`: Dict of :class:`Operation`
- """
-
- __tablename__ = 'service_template'
-
- __private_fields__ = ['substitution_template_fk']
-
- description = Column(Text)
-
- # region orchestrator required columns
-
- created_at = Column(DateTime, nullable=False, index=True)
- main_file_name = Column(Text)
- plan = Column(aria_type.Dict, nullable=False)
- updated_at = Column(DateTime)
-
- # endregion
-
- # region foreign keys
-
- @declared_attr
- def substitution_template_fk(cls):
- return cls.foreign_key('substitution_template', nullable=True)
-
- @declared_attr
- def meta_data_fk(cls):
- return cls.foreign_key('meta_data', nullable=True)
-
- # endregion
-
- # region one-to-one relationships
-
- @declared_attr
- def substitution_template(cls):
- return cls.one_to_one_relationship('substitution_template')
-
- @declared_attr
- def meta_data(cls):
- return cls.one_to_one_relationship('meta_data')
-
- # endregion
-
- # region one-to-many relationships
-
- @declared_attr
- def operation_templates(cls):
- return cls.one_to_many_relationship('operation_template', key_column_name='name')
-
- # endregion
-
- # region many-to-many relationships
-
- @declared_attr
- def inputs(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='inputs',
- key_column_name='name')
-
- @declared_attr
- def outputs(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='outputs',
- key_column_name='name')
-
- # endregion
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('description', self.description),
- ('metadata', formatting.as_raw(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))))
-
- def instantiate(self, context, container):
- service_instance = instance_elements.ServiceInstanceBase()
- context.modeling.instance = service_instance
-
- service_instance.description = deepcopy_with_locators(self.description)
-
- if self.meta_data is not None:
- service_instance.meta_data = self.meta_data.instantiate(context, container)
-
- for node_template in self.node_templates.itervalues():
- for _ in range(node_template.default_instances):
- node = node_template.instantiate(context, container)
- service_instance.nodes[node.id] = node
-
- utils.instantiate_dict(context, self, service_instance.groups, self.group_templates)
- utils.instantiate_dict(context, self, service_instance.policies, self.policy_templates)
- utils.instantiate_dict(context, self, service_instance.operations, self.operation_templates)
-
- if self.substitution_template is not None:
- service_instance.substitution = self.substitution_template.instantiate(context,
- container)
-
- utils.instantiate_dict(context, self, service_instance.inputs, self.inputs)
- utils.instantiate_dict(context, self, service_instance.outputs, self.outputs)
-
- for name, the_input in context.modeling.inputs.iteritems():
- if name not in service_instance.inputs:
- context.validation.report('input "%s" is not supported' % name)
- else:
- service_instance.inputs[name].value = the_input
-
- return service_instance
-
- def validate(self, context):
- if self.meta_data is not None:
- self.meta_data.validate(context)
- 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)
-
- def coerce_values(self, context, container, report_issues):
- if self.meta_data is not None:
- self.meta_data.coerce_values(context, container, 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))
- if self.meta_data is not None:
- self.meta_data.dump(context)
- for node_template in self.node_templates.all():
- node_template.dump(context)
- for group_template in self.group_templates.all():
- group_template.dump(context)
- for policy_template in self.policy_templates.all():
- policy_template.dump(context)
- if self.substitution_template is not None:
- self.substitution_template.dump(context)
- dump_parameters(context, self.inputs, 'Inputs')
- dump_parameters(context, self.outputs, 'Outputs')
- utils.dump_dict_values(context, self.operation_templates, 'Operation templates')
-
-
-class InterfaceTemplateBase(structure.ModelMixin):
- """
- A typed set of :class:`OperationTemplate`.
-
- Properties:
-
- * :code:`name`: Name
- * :code:`description`: Description
- * :code:`type_name`: Must be represented in the :class:`ModelingContext`
- * :code:`inputs`: Dict of :class:`Parameter`
- * :code:`operation_templates`: Dict of :class:`OperationTemplate`
- """
-
- __tablename__ = 'interface_template'
-
- __private_fields__ = ['node_template_fk',
- 'group_template_fk',
- 'relationship_template_fk']
-
- # region foreign keys
-
- @declared_attr
- def node_template_fk(cls):
- return cls.foreign_key('node_template', nullable=True)
-
- @declared_attr
- def group_template_fk(cls):
- return cls.foreign_key('group_template', nullable=True)
-
- @declared_attr
- def relationship_template_fk(cls):
- return cls.foreign_key('relationship_template', nullable=True)
-
- # endregion
-
- description = Column(Text)
- type_name = Column(Text)
-
- # region one-to-many relationships
-
- @declared_attr
- def operation_templates(cls):
- return cls.one_to_many_relationship('operation_template', key_column_name='name')
-
- # endregion
-
- # region many-to-many relationships
-
- @declared_attr
- def inputs(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='inputs',
- key_column_name='name')
-
- @declared_attr
- def properties(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='properties',
- key_column_name='name')
-
- # endregion
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('name', self.name),
- ('description', self.description),
- ('type_name', self.type_name),
- ('inputs', formatting.as_raw_dict(self.properties)), # pylint: disable=no-member
- # TODO fix self.properties reference
- ('operation_templates', formatting.as_raw_list(self.operation_templates))))
-
- def instantiate(self, context, container):
- interface = instance_elements.InterfaceBase(self.name, self.type_name)
- interface.description = deepcopy_with_locators(self.description)
- utils.instantiate_dict(context, container, interface.inputs, self.inputs)
- utils.instantiate_dict(context, container, interface.operations, self.operation_templates)
- return interface
-
- def validate(self, context):
- if self.type_name:
- if context.modeling.interface_types.get_descendant(self.type_name) is None:
- context.validation.report('interface "%s" has an unknown type: %s'
- % (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.operation_templates)
-
- def coerce_values(self, context, container, report_issues):
- utils.coerce_dict_values(context, container, self.inputs, report_issues)
- utils.coerce_dict_values(context, container, self.operation_templates, report_issues)
-
- def dump(self, context):
- console.puts(context.style.node(self.name))
- if self.description:
- console.puts(context.style.meta(self.description))
- with context.style.indent:
- console.puts('Interface type: %s' % context.style.type(self.type_name))
- dump_parameters(context, self.inputs, 'Inputs')
- utils.dump_dict_values(context, self.operation_templates, 'Operation templates')
-
-
-class OperationTemplateBase(structure.ModelMixin):
- """
- An operation in a :class:`InterfaceTemplate`.
-
- Properties:
-
- * :code:`name`: Name
- * :code:`description`: Description
- * :code:`implementation`: Implementation string (interpreted by the orchestrator)
- * :code:`dependencies`: List of strings (interpreted by the orchestrator)
- * :code:`executor`: Executor string (interpreted by the orchestrator)
- * :code:`max_retries`: Maximum number of retries allowed in case of failure
- * :code:`retry_interval`: Interval between retries
- * :code:`inputs`: Dict of :class:`Parameter`
- """
-
- __tablename__ = 'operation_template'
-
- __private_fields__ = ['service_template_fk',
- 'interface_template_fk']
-
- # region foreign keys
-
- @declared_attr
- def service_template_fk(cls):
- return cls.foreign_key('service_template', nullable=True)
-
- @declared_attr
- def interface_template_fk(cls):
- return cls.foreign_key('interface_template', nullable=True)
-
- # endregion
-
- description = Column(Text)
- implementation = Column(Text)
- dependencies = Column(aria_type.StrictList(item_cls=basestring))
- executor = Column(Text)
- max_retries = Column(Integer)
- retry_interval = Column(Integer)
-
- # region orchestrator required columns
-
- plugin = Column(Text)
- operation = Column(Boolean)
-
- # endregion
-
- # region many-to-many relationships
-
- @declared_attr
- def inputs(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='inputs',
- key_column_name='name')
-
- # endregion
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('name', self.name),
- ('description', self.description),
- ('implementation', self.implementation),
- ('dependencies', self.dependencies),
- ('executor', self.executor),
- ('max_retries', self.max_retries),
- ('retry_interval', self.retry_interval),
- ('inputs', formatting.as_raw_dict(self.inputs))))
-
- def instantiate(self, context, container):
- operation = instance_elements.OperationBase(self.name)
- operation.description = deepcopy_with_locators(self.description)
- operation.implementation = self.implementation
- operation.dependencies = self.dependencies
- operation.executor = self.executor
- operation.max_retries = self.max_retries
- operation.retry_interval = self.retry_interval
- utils.instantiate_dict(context, container, operation.inputs, self.inputs)
- return operation
-
- def validate(self, context):
- utils.validate_dict_values(context, self.inputs)
-
- def coerce_values(self, context, container, report_issues):
- utils.coerce_dict_values(context, container, self.inputs, report_issues)
-
- def dump(self, context):
- console.puts(context.style.node(self.name))
- if self.description:
- console.puts(context.style.meta(self.description))
- with context.style.indent:
- if self.implementation is not None:
- console.puts('Implementation: %s' % context.style.literal(self.implementation))
- if self.dependencies:
- console.puts('Dependencies: %s' % ', '.join(
- (str(context.style.literal(v)) for v in self.dependencies)))
- if self.executor is not None:
- console.puts('Executor: %s' % context.style.literal(self.executor))
- if self.max_retries is not None:
- console.puts('Max retries: %s' % context.style.literal(self.max_retries))
- if self.retry_interval is not None:
- console.puts('Retry interval: %s' % context.style.literal(self.retry_interval))
- dump_parameters(context, self.inputs, 'Inputs')
-
-
-class ArtifactTemplateBase(structure.ModelMixin):
- """
- A file associated with a :class:`NodeTemplate`.
-
- Properties:
-
- * :code:`name`: Name
- * :code:`description`: Description
- * :code:`type_name`: Must be represented in the :class:`ModelingContext`
- * :code:`source_path`: Source path (CSAR or repository)
- * :code:`target_path`: Path at destination machine
- * :code:`repository_url`: Repository URL
- * :code:`repository_credential`: Dict of string
- * :code:`properties`: Dict of :class:`Parameter`
- """
-
- __tablename__ = 'artifact_template'
-
- __private_fields__ = ['node_template_fk']
-
- # region foreign keys
-
- @declared_attr
- def node_template_fk(cls):
- return cls.foreign_key('node_template')
-
- # endregion
-
- description = Column(Text)
- type_name = Column(Text)
- source_path = Column(Text)
- target_path = Column(Text)
- repository_url = Column(Text)
- repository_credential = Column(aria_type.StrictDict(basestring, basestring))
-
- # region many-to-many relationships
-
- @declared_attr
- def properties(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='properties',
- key_column_name='name')
-
- # endregion
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('name', self.name),
- ('description', self.description),
- ('type_name', self.type_name),
- ('source_path', self.source_path),
- ('target_path', self.target_path),
- ('repository_url', self.repository_url),
- ('repository_credential', formatting.as_agnostic(self.repository_credential)),
- ('properties', formatting.as_raw_dict(self.properties.iteritems()))))
-
- def instantiate(self, context, container):
- artifact = instance_elements.ArtifactBase(self.name, self.type_name, self.source_path)
- artifact.description = deepcopy_with_locators(self.description)
- artifact.target_path = self.target_path
- artifact.repository_url = self.repository_url
- artifact.repository_credential = self.repository_credential
- utils.instantiate_dict(context, container, artifact.properties, self.properties)
- return artifact
-
- def validate(self, context):
- if context.modeling.artifact_types.get_descendant(self.type_name) is None:
- context.validation.report('artifact "%s" has an unknown type: %s'
- % (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):
- utils.coerce_dict_values(context, container, self.properties, report_issues)
-
- def dump(self, context):
- console.puts(context.style.node(self.name))
- if self.description:
- console.puts(context.style.meta(self.description))
- with context.style.indent:
- console.puts('Artifact type: %s' % context.style.type(self.type_name))
- console.puts('Source path: %s' % context.style.literal(self.source_path))
- if self.target_path is not None:
- console.puts('Target path: %s' % context.style.literal(self.target_path))
- if self.repository_url is not None:
- console.puts('Repository URL: %s' % context.style.literal(self.repository_url))
- if self.repository_credential:
- console.puts('Repository credential: %s'
- % context.style.literal(self.repository_credential))
- dump_parameters(context, self.properties)
-
-
-class PolicyTemplateBase(structure.ModelMixin):
- """
- Policies can be applied to zero or more :class:`NodeTemplate` or :class:`GroupTemplate`
- instances.
-
- Properties:
-
- * :code:`name`: Name
- * :code:`description`: Description
- * :code:`type_name`: Must be represented in the :class:`ModelingContext`
- * :code:`properties`: Dict of :class:`Parameter`
- * :code:`target_node_template_names`: Must be represented in the :class:`ServiceModel`
- * :code:`target_group_template_names`: Must be represented in the :class:`ServiceModel`
- """
- __tablename__ = 'policy_template'
-
- __private_fields__ = ['service_template_fk']
-
- # region foreign keys
-
- @declared_attr
- def service_template_fk(cls):
- return cls.foreign_key('service_template')
-
- # endregion
-
- description = Column(Text)
- type_name = Column(Text)
- target_node_template_names = Column(aria_type.StrictList(basestring))
- target_group_template_names = Column(aria_type.StrictList(basestring))
-
- # region many-to-one relationships
-
- @declared_attr
- def service_template(cls):
- return cls.many_to_one_relationship('service_template')
-
- # endregion
-
- # region many-to-many relationships
-
- @declared_attr
- def properties(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='properties',
- key_column_name='name')
-
- # endregion
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('name', self.name),
- ('description', self.description),
- ('type_name', self.type_name),
- ('properties', formatting.as_raw_dict(self.properties)),
- ('target_node_template_names', self.target_node_template_names),
- ('target_group_template_names', self.target_group_template_names)))
-
- def instantiate(self, context, *args, **kwargs):
- policy = instance_elements.PolicyBase(self.name, self.type_name)
- utils.instantiate_dict(context, self, policy.properties, self.properties)
- for node_template_name in self.target_node_template_names:
- policy.target_node_ids.extend(
- context.modeling.instance.get_node_ids(node_template_name))
- for group_template_name in self.target_group_template_names:
- policy.target_group_ids.extend(
- context.modeling.instance.get_group_ids(group_template_name))
- return policy
-
- def validate(self, context):
- if context.modeling.policy_types.get_descendant(self.type_name) is None:
- context.validation.report('policy template "%s" has an unknown type: %s'
- % (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):
- utils.coerce_dict_values(context, self, self.properties, report_issues)
-
- def dump(self, context):
- console.puts('Policy template: %s' % context.style.node(self.name))
- if self.description:
- console.puts(context.style.meta(self.description))
- with context.style.indent:
- console.puts('Type: %s' % context.style.type(self.type_name))
- dump_parameters(context, self.properties)
- if self.target_node_template_names:
- console.puts('Target node templates: %s' % ', '.join(
- (str(context.style.node(v)) for v in self.target_node_template_names)))
- if self.target_group_template_names:
- console.puts('Target group templates: %s' % ', '.join(
- (str(context.style.node(v)) for v in self.target_group_template_names)))
-
-
-class MappingTemplateBase(structure.ModelMixin):
- """
- Used by :class:`SubstitutionTemplate` to map a capability or a requirement to a node.
-
- Properties:
-
- * :code:`mapped_name`: Exposed capability or requirement name
- * :code:`node_template_name`: Must be represented in the :class:`ServiceModel`
- * :code:`name`: Name of capability or requirement at the node template
- """
-
- __tablename__ = 'mapping_template'
-
- __private_fields__ = ['substitution_template_fk']
-
- mapped_name = Column(Text)
- node_template_name = Column(Text)
-
- # region foreign keys
-
- @declared_attr
- def substitution_template_fk(cls):
- return cls.foreign_key('substitution_template', nullable=True)
-
- # endregion
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('mapped_name', self.mapped_name),
- ('node_template_name', self.node_template_name),
- ('name', self.name)))
-
- def instantiate(self, context, *args, **kwargs):
- nodes = context.modeling.instance.find_nodes(self.node_template_name)
- if len(nodes) == 0:
- context.validation.report(
- 'mapping "%s" refer to node template "%s" but there are no '
- 'node instances' % (self.mapped_name,
- self.node_template_name),
- level=validation.Issue.BETWEEN_INSTANCES)
- return None
- return instance_elements.MappingBase(self.mapped_name, nodes[0].id, self.name)
-
- def validate(self, context):
- if not utils.query_has_item_named(context.modeling.model.node_templates,
- self.node_template_name):
- context.validation.report('mapping "%s" refers to an unknown node template: %s'
- % (
- self.mapped_name,
- formatting.safe_repr(self.node_template_name)),
- level=validation.Issue.BETWEEN_TYPES)
-
- def dump(self, context):
- console.puts('%s -> %s.%s' % (context.style.node(self.mapped_name),
- context.style.node(self.node_template_name),
- context.style.node(self.name)))
-
-
-class SubstitutionTemplateBase(structure.ModelMixin):
- """
- Used to substitute a single node for the entire deployment.
-
- Properties:
-
- * :code:`node_type_name`: Must be represented in the :class:`ModelingContext`
- * :code:`capability_templates`: Dict of :class:`MappingTemplate`
- * :code:`requirement_templates`: Dict of :class:`MappingTemplate`
- """
-
- __tablename__ = 'substitution_template'
-
- node_type_name = Column(Text)
-
- # region one-to-many relationships
-
- @declared_attr
- def mappings(cls):
- return cls.one_to_many_relationship('mapping_template', key_column_name='name')
-
- # endregion
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('node_type_name', self.node_type_name),
- ('mappings', formatting.as_raw_list(self.mappings))))
-
- def instantiate(self, context, container):
- substitution = instance_elements.SubstitutionBase(self.node_type_name)
- utils.instantiate_dict(context, container, substitution.mappings,
- self.mappings)
- return substitution
-
- def validate(self, context):
- if context.modeling.node_types.get_descendant(self.node_type_name) is None:
- context.validation.report('substitution template has an unknown type: %s'
- % 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):
- utils.coerce_dict_values(context, self, self.mappings, report_issues)
-
- def dump(self, context):
- console.puts('Substitution template:')
- with context.style.indent:
- console.puts('Node type: %s' % context.style.type(self.node_type_name))
- utils.dump_dict_values(context, self.mappings,
- 'Mappings')
-
-
-# endregion
-
-# region Node templates
-
-class NodeTemplateBase(structure.ModelMixin):
- """
- A template for creating zero or more :class:`Node` instances.
-
- Properties:
-
- * :code:`name`: Name (will be used as a prefix for node IDs)
- * :code:`description`: Description
- * :code:`type_name`: Must be represented in the :class:`ModelingContext`
- * :code:`default_instances`: Default number nodes that will appear in the deployment plan
- * :code:`min_instances`: Minimum number nodes that will appear in the deployment plan
- * :code:`max_instances`: Maximum number nodes that will appear in the deployment plan
- * :code:`properties`: Dict of :class:`Parameter`
- * :code:`interface_templates`: Dict of :class:`InterfaceTemplate`
- * :code:`artifact_templates`: Dict of :class:`ArtifactTemplate`
- * :code:`capability_templates`: Dict of :class:`CapabilityTemplate`
- * :code:`requirement_templates`: List of :class:`RequirementTemplate`
- * :code:`target_node_template_constraints`: List of :class:`FunctionType`
- """
-
- __tablename__ = 'node_template'
-
- __private_fields__ = ['service_template_fk',
- 'host_fk']
-
- # region foreign_keys
-
- @declared_attr
- def service_template_fk(cls):
- return cls.foreign_key('service_template')
-
- @declared_attr
- def host_fk(cls):
- return cls.foreign_key('node_template', nullable=True)
-
- # endregion
-
- description = Column(Text)
- type_name = Column(Text)
- default_instances = Column(Integer, default=1)
- min_instances = Column(Integer, default=0)
- max_instances = Column(Integer, default=None)
- target_node_template_constraints = Column(aria_type.StrictList(FunctionType))
-
- # region orchestrator required columns
-
- plugins = Column(aria_type.List)
- type_hierarchy = Column(aria_type.List)
-
- @declared_attr
- def host(cls):
- return cls.relationship_to_self('host_fk')
-
- @declared_attr
- def service_template_name(cls):
- return association_proxy('service_template', cls.name_column_name())
-
- # endregion
-
- # region many-to-one relationships
-
- @declared_attr
- def service_template(cls):
- return cls.many_to_one_relationship('service_template')
-
- # endregion
-
- # region one-to-many relationships
-
- @declared_attr
- def interface_templates(cls):
- return cls.one_to_many_relationship('interface_template', key_column_name='name')
-
- @declared_attr
- def artifact_templates(cls):
- return cls.one_to_many_relationship('artifact_template', key_column_name='name')
-
- @declared_attr
- def capability_templates(cls):
- return cls.one_to_many_relationship('capability_template', key_column_name='name')
-
- # endregion
-
- # region many-to-many relationships
-
- @declared_attr
- def properties(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='properties',
- key_column_name='name')
-
- # endregion
-
- def is_target_node_valid(self, target_node_template):
- if self.target_node_template_constraints:
- for node_type_constraint in self.target_node_template_constraints:
- if not node_type_constraint(target_node_template, self):
- return False
- return True
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('name', self.name),
- ('description', self.description),
- ('type_name', self.type_name),
- ('default_instances', self.default_instances),
- ('min_instances', self.min_instances),
- ('max_instances', self.max_instances),
- ('properties', formatting.as_raw_dict(self.properties)),
- ('interface_templates', formatting.as_raw_list(self.interface_templates)),
- ('artifact_templates', formatting.as_raw_list(self.artifact_templates)),
- ('capability_templates', formatting.as_raw_list(self.capability_templates)),
- ('requirement_templates', formatting.as_raw_list(self.requirement_templates))))
-
- def instantiate(self, context, *args, **kwargs):
- node = instance_elements.NodeBase(context, self.type_name, self.name)
- utils.instantiate_dict(context, node, node.properties, self.properties)
- utils.instantiate_dict(context, node, node.interfaces, self.interface_templates)
- utils.instantiate_dict(context, node, node.artifacts, self.artifact_templates)
- utils.instantiate_dict(context, node, node.capabilities, self.capability_templates)
- return node
-
- def validate(self, context):
- if context.modeling.node_types.get_descendant(self.type_name) is None:
- context.validation.report('node template "%s" has an unknown type: %s'
- % (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.interface_templates)
- utils.validate_dict_values(context, self.artifact_templates)
- utils.validate_dict_values(context, self.capability_templates)
- utils.validate_list_values(context, self.requirement_templates)
-
- def coerce_values(self, context, container, report_issues):
- utils.coerce_dict_values(context, self, self.properties, report_issues)
- utils.coerce_dict_values(context, self, self.interface_templates, report_issues)
- utils.coerce_dict_values(context, self, self.artifact_templates, report_issues)
- utils.coerce_dict_values(context, self, self.capability_templates, report_issues)
- utils.coerce_list_values(context, self, self.requirement_templates, report_issues)
-
- def dump(self, context):
- console.puts('Node template: %s' % context.style.node(self.name))
- if self.description:
- console.puts(context.style.meta(self.description))
- with context.style.indent:
- console.puts('Type: %s' % context.style.type(self.type_name))
- console.puts('Instances: %d (%d%s)'
- % (self.default_instances,
- self.min_instances,
- (' to %d' % self.max_instances
- if self.max_instances is not None
- else ' or more')))
- dump_parameters(context, self.properties)
- utils.dump_interfaces(context, self.interface_templates)
- utils.dump_dict_values(context, self.artifact_templates, 'Artifact tempaltes')
- utils.dump_dict_values(context, self.capability_templates, 'Capability templates')
- utils.dump_list_values(context, self.requirement_templates, 'Requirement templates')
-
-
-class GroupTemplateBase(structure.ModelMixin):
- """
- A template for creating zero or more :class:`Group` instances.
-
- Groups are logical containers for zero or more nodes that allow applying zero or more
- :class:`GroupPolicy` instances to the nodes together.
-
- Properties:
-
- * :code:`name`: Name (will be used as a prefix for group IDs)
- * :code:`description`: Description
- * :code:`type_name`: Must be represented in the :class:`ModelingContext`
- * :code:`properties`: Dict of :class:`Parameter`
- * :code:`interface_templates`: Dict of :class:`InterfaceTemplate`
- * :code:`policy_templates`: Dict of :class:`GroupPolicyTemplate`
- * :code:`member_node_template_names`: Must be represented in the :class:`ServiceModel`
- * :code:`member_group_template_names`: Must be represented in the :class:`ServiceModel`
- """
-
- __tablename__ = 'group_template'
-
- __private_fields__ = ['service_template_fk']
-
- # region foreign keys
-
- @declared_attr
- def service_template_fk(cls):
- return cls.foreign_key('service_template')
-
- # endregion
-
- description = Column(Text)
- type_name = Column(Text)
- member_node_template_names = Column(aria_type.StrictList(basestring))
- member_group_template_names = Column(aria_type.StrictList(basestring))
-
- # region many-to-one relationships
-
- @declared_attr
- def service_template(cls):
- return cls.many_to_one_relationship('service_template')
-
- # endregion
-
- # region one-to-many relationships
-
- @declared_attr
- def interface_templates(cls):
- return cls.one_to_many_relationship('interface_template', key_column_name='name')
-
- # endregion
-
- # region many-to-many relationships
-
- @declared_attr
- def properties(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='properties',
- key_column_name='name')
-
- # endregion
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('name', self.name),
- ('description', self.description),
- ('type_name', self.type_name),
- ('properties', formatting.as_raw_dict(self.properties)),
- ('interface_templates', formatting.as_raw_list(self.interface_templates)),
- ('member_node_template_names', self.member_node_template_names),
- ('member_group_template_names', self.member_group_template_names1)))
-
- def instantiate(self, context, *args, **kwargs):
- group = instance_elements.GroupBase(context, self.type_name, self.name)
- utils.instantiate_dict(context, self, group.properties, self.properties)
- utils.instantiate_dict(context, self, group.interfaces, self.interface_templates)
- utils.instantiate_dict(context, self, group.policies, self.policy_templates)
- for member_node_template_name in self.member_node_template_names:
- group.member_node_ids += \
- context.modeling.instance.get_node_ids(member_node_template_name)
- for member_group_template_name in self.member_group_template_names:
- group.member_group_ids += \
- context.modeling.instance.get_group_ids(member_group_template_name)
- return group
-
- def validate(self, context):
- if context.modeling.group_types.get_descendant(self.type_name) is None:
- context.validation.report('group template "%s" has an unknown type: %s'
- % (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.interface_templates)
-
- def coerce_values(self, context, container, report_issues):
- utils.coerce_dict_values(context, self, self.properties, report_issues)
- utils.coerce_dict_values(context, self, self.interface_templates, report_issues)
-
- def dump(self, context):
- console.puts('Group template: %s' % context.style.node(self.name))
- if self.description:
- console.puts(context.style.meta(self.description))
- with context.style.indent:
- if self.type_name:
- console.puts('Type: %s' % context.style.type(self.type_name))
- dump_parameters(context, self.properties)
- utils.dump_interfaces(context, self.interface_templates)
- if self.member_node_template_names:
- console.puts('Member node templates: %s' % ', '.join(
- (str(context.style.node(v)) for v in self.member_node_template_names)))
-
-
-# endregion
-
-# region Relationship templates
-
-class RequirementTemplateBase(structure.ModelMixin):
- """
- A requirement for a :class:`NodeTemplate`. During instantiation will be matched with a
- capability of another
- node.
-
- Requirements may optionally contain a :class:`RelationshipTemplate` that will be created between
- the nodes.
-
- Properties:
-
- * :code:`name`: Name
- * :code:`target_node_type_name`: Must be represented in the :class:`ModelingContext`
- * :code:`target_node_template_name`: Must be represented in the :class:`ServiceModel`
- * :code:`target_node_template_constraints`: List of :class:`FunctionType`
- * :code:`target_capability_type_name`: Type of capability in target node
- * :code:`target_capability_name`: Name of capability in target node
- * :code:`relationship_template`: :class:`RelationshipTemplate`
- """
-
- __tablename__ = 'requirement_template'
-
- __private_fields__ = ['node_template_fk']
-
- # region foreign keys
-
- @declared_attr
- def node_template_fk(cls):
- return cls.foreign_key('node_template', nullable=True)
-
- # endregion
-
- target_node_type_name = Column(Text)
- target_node_template_name = Column(Text)
- target_node_template_constraints = Column(aria_type.StrictList(FunctionType))
- target_capability_type_name = Column(Text)
- target_capability_name = Column(Text)
- # CHECK: ???
- relationship_template = Column(Text) # optional
-
- # region many-to-one relationships
-
- @declared_attr
- def node_template(cls):
- return cls.many_to_one_relationship('node_template')
-
- # endregion
-
- def instantiate(self, context, container):
- raise NotImplementedError
-
- def find_target(self, context, source_node_template):
- # We might already have a specific node template, so we'll just verify it
- if self.target_node_template_name is not None:
- target_node_template = \
- context.modeling.model.node_templates.get(self.target_node_template_name)
-
- if not source_node_template.is_target_node_valid(target_node_template):
- context.validation.report('requirement "%s" of node template "%s" is for node '
- 'template "%s" but it does not match constraints'
- % (self.name,
- self.target_node_template_name,
- source_node_template.name),
- level=validation.Issue.BETWEEN_TYPES)
- return None, None
-
- if self.target_capability_type_name is not None \
- or self.target_capability_name is not None:
- target_node_capability = self.find_target_capability(context,
- source_node_template,
- target_node_template)
- if target_node_capability is None:
- return None, None
- else:
- target_node_capability = None
-
- return target_node_template, target_node_capability
-
- # Find first node that matches the type
- elif self.target_node_type_name is not None:
- for target_node_template in context.modeling.model.node_templates.itervalues():
- if not context.modeling.node_types.is_descendant(self.target_node_type_name,
- target_node_template.type_name):
- continue
-
- if not source_node_template.is_target_node_valid(target_node_template):
- continue
-
- target_node_capability = self.find_target_capability(context,
- source_node_template,
- target_node_template)
- if target_node_capability is None:
- continue
-
- return target_node_template, target_node_capability
-
- return None, None
-
- def find_target_capability(self, context, source_node_template, target_node_template):
- for capability_template in target_node_template.capability_templates.itervalues():
- if capability_template.satisfies_requirement(context,
- source_node_template,
- self,
- target_node_template):
- return capability_template
- return None
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('name', self.name),
- ('target_node_type_name', self.target_node_type_name),
- ('target_node_template_name', self.target_node_template_name),
- ('target_capability_type_name', self.target_capability_type_name),
- ('target_capability_name', self.target_capability_name),
- ('relationship_template', formatting.as_raw(self.relationship_template))))
-
- def validate(self, context):
- node_types = context.modeling.node_types
- capability_types = context.modeling.capability_types
- if self.target_node_type_name \
- and node_types.get_descendant(self.target_node_type_name) is None:
- context.validation.report('requirement "%s" refers to an unknown node type: %s'
- % (self.name,
- formatting.safe_repr(self.target_node_type_name)),
- level=validation.Issue.BETWEEN_TYPES)
- if self.target_capability_type_name and \
- capability_types.get_descendant(self.target_capability_type_name is None):
- context.validation.report('requirement "%s" refers to an unknown capability type: %s'
- % (self.name,
- formatting.safe_repr(self.target_capability_type_name)),
- level=validation.Issue.BETWEEN_TYPES)
- if self.relationship_template:
- self.relationship_template.validate(context)
-
- def coerce_values(self, context, container, report_issues):
- if self.relationship_template is not None:
- self.relationship_template.coerce_values(context, container, report_issues)
-
- def dump(self, context):
- if self.name:
- console.puts(context.style.node(self.name))
- else:
- console.puts('Requirement:')
- with context.style.indent:
- if self.target_node_type_name is not None:
- console.puts('Target node type: %s'
- % context.style.type(self.target_node_type_name))
- elif self.target_node_template_name is not None:
- console.puts('Target node template: %s'
- % context.style.node(self.target_node_template_name))
- if self.target_capability_type_name is not None:
- console.puts('Target capability type: %s'
- % context.style.type(self.target_capability_type_name))
- elif self.target_capability_name is not None:
- console.puts('Target capability name: %s'
- % context.style.node(self.target_capability_name))
- if self.target_node_template_constraints:
- console.puts('Target node template constraints:')
- with context.style.indent:
- for constraint in self.target_node_template_constraints:
- console.puts(context.style.literal(constraint))
- if self.relationship_template:
- console.puts('Relationship:')
- with context.style.indent:
- self.relationship_template.dump(context)
-
-
-class CapabilityTemplateBase(structure.ModelMixin):
- """
- A capability of a :class:`NodeTemplate`. Nodes expose zero or more capabilities that can be
- matched with :class:`Requirement` instances of other nodes.
-
- Properties:
-
- * :code:`name`: Name
- * :code:`description`: Description
- * :code:`type_name`: Must be represented in the :class:`ModelingContext`
- * :code:`min_occurrences`: Minimum number of requirement matches required
- * :code:`max_occurrences`: Maximum number of requirement matches allowed
- * :code:`valid_source_node_type_names`: Must be represented in the :class:`ModelingContext`
- * :code:`properties`: Dict of :class:`Parameter`
- """
-
- __tablename__ = 'capability_template'
-
- __private_fields__ = ['node_template_fk']
-
- # region foreign keys
-
- @declared_attr
- def node_template_fk(cls):
- return cls.foreign_key('node_template', nullable=True)
-
- # endregion
-
- description = Column(Text)
- type_name = Column(Text)
- min_occurrences = Column(Integer, default=None) # optional
- max_occurrences = Column(Integer, default=None) # optional
- # CHECK: type?
- valid_source_node_type_names = Column(Text)
-
- # region many-to-one relationships
-
- #@declared_attr
- #def node_template(cls):
- # return cls.many_to_one_relationship('node_template')
-
- # endregion
-
- # region many-to-many relationships
-
- @declared_attr
- def properties(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='properties',
- key_column_name='name')
-
- # endregion
-
- def satisfies_requirement(self,
- context,
- source_node_template,
- requirement,
- target_node_template):
- # Do we match the required capability type?
- capability_types = context.modeling.capability_types
- if not capability_types.is_descendant(requirement.target_capability_type_name,
- self.type_name):
- return False
-
- # Are we in valid_source_node_type_names?
- if self.valid_source_node_type_names:
- for valid_source_node_type_name in self.valid_source_node_type_names:
- if not context.modeling.node_types.is_descendant(valid_source_node_type_name,
- source_node_template.type_name):
- return False
-
- # Apply requirement constraints
- if requirement.target_node_template_constraints:
- for node_type_constraint in requirement.target_node_template_constraints:
- if not node_type_constraint(target_node_template, source_node_template):
- return False
-
- return True
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('name', self.name),
- ('description', self.description),
- ('type_name', self.type_name),
- ('min_occurrences', self.min_occurrences),
- ('max_occurrences', self.max_occurrences),
- ('valid_source_node_type_names', self.valid_source_node_type_names),
- ('properties', formatting.as_raw_dict(self.properties))))
-
- def instantiate(self, context, container):
- capability = instance_elements.CapabilityBase(self.name, self.type_name)
- capability.min_occurrences = self.min_occurrences
- capability.max_occurrences = self.max_occurrences
- utils.instantiate_dict(context, container, capability.properties, self.properties)
- return capability
-
- def validate(self, context):
- if context.modeling.capability_types.get_descendant(self.type_name) is None:
- context.validation.report('capability "%s" refers to an unknown type: %s'
- % (self.name, formatting.safe_repr(self.type)), # pylint: disable=no-member
- # TODO fix self.type reference
- level=validation.Issue.BETWEEN_TYPES)
-
- utils.validate_dict_values(context, self.properties)
-
- def coerce_values(self, context, container, report_issues):
- utils.coerce_dict_values(context, self, self.properties, report_issues)
-
- def dump(self, context):
- console.puts(context.style.node(self.name))
- if self.description:
- console.puts(context.style.meta(self.description))
- with context.style.indent:
- console.puts('Type: %s' % context.style.type(self.type_name))
- console.puts(
- 'Occurrences: %d%s'
- % (self.min_occurrences or 0, (' to %d' % self.max_occurrences)
- if self.max_occurrences is not None else ' or more'))
- if self.valid_source_node_type_names:
- console.puts('Valid source node types: %s'
- % ', '.join((str(context.style.type(v))
- for v in self.valid_source_node_type_names)))
- dump_parameters(context, self.properties)
-
-
-class RelationshipTemplateBase(structure.ModelMixin):
- """
- Optional addition to a :class:`Requirement` in :class:`NodeTemplate` that can be applied when
- the requirement is matched with a capability.
-
- Properties:
-
- * :code:`type_name`: Must be represented in the :class:`ModelingContext`
- * :code:`template_name`: Must be represented in the :class:`ServiceModel`
- * :code:`description`: Description
- * :code:`properties`: Dict of :class:`Parameter`
- * :code:`source_interface_templates`: Dict of :class:`InterfaceTemplate`
- * :code:`target_interface_templates`: Dict of :class:`InterfaceTemplate`
- """
-
- __tablename__ = 'relationship_template'
-
- description = Column(Text)
- type_name = Column(Text)
-
- # region many-to-many relationships
-
- @declared_attr
- def properties(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='properties',
- key_column_name='name')
-
- # endregion
-
- # region one-to-many relationships
-
- @declared_attr
- def interface_templates(cls):
- return cls.one_to_many_relationship('interface_template', key_column_name='name')
-
- # endregion
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('type_name', self.type_name),
- ('template_name', self.template_name),
- ('description', self.description),
- ('properties', formatting.as_raw_dict(self.properties)),
- ('interface_templates', formatting.as_raw_list(self.interface_templates))))
-
- def instantiate(self, context, container):
- relationship = instance_elements.RelationshipBase(name=self.template_name,
- type_name=self.type_name)
- utils.instantiate_dict(context, container,
- relationship.properties, self.properties)
- utils.instantiate_dict(context, container,
- relationship.interfaces, self.interface_templates)
- return relationship
-
- def validate(self, context):
- if context.modeling.relationship_types.get_descendant(self.type_name) is None:
- context.validation.report(
- 'relationship template "{0}" has an unknown type: {1}'.format(
- self.name,
- formatting.safe_repr(self.type_name)), # pylint: disable=no-member
- # TODO fix self.name reference
- level=validation.Issue.BETWEEN_TYPES)
-
- utils.validate_dict_values(context, self.properties)
- utils.validate_dict_values(context, self.interface_templates)
-
- def coerce_values(self, context, container, report_issues):
- utils.coerce_dict_values(context, self, self.properties, report_issues)
- utils.coerce_dict_values(context, self, self.interface_templates, report_issues)
-
- def dump(self, context):
- if self.type_name is not None:
- console.puts('Relationship type: {0}'.format(context.style.type(self.type_name)))
- else:
- console.puts('Relationship template: {0}'.format(
- context.style.node(self.template_name)))
- if self.description:
- console.puts(context.style.meta(self.description))
- with context.style.indent:
- utils.dump_parameters(context, self.properties)
- utils.dump_interfaces(context, self.interface_templates, 'Interface templates')
-
-# endregion
-
-
-def dump_parameters(context, parameters, name='Properties'):
- if not parameters:
- return
- console.puts('%s:' % name)
- with context.style.indent:
- for parameter_name, parameter in parameters.items():
- if parameter.type_name is not None:
- console.puts('%s = %s (%s)' % (context.style.property(parameter_name),
- context.style.literal(parameter.value),
- context.style.type(parameter.type_name)))
- else:
- console.puts('%s = %s' % (context.style.property(parameter_name),
- context.style.literal(parameter.value)))
- if parameter.description:
- console.puts(context.style.meta(parameter.description))
-
-
-# TODO (left for tal): Move following two methods to some place parser specific
-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)
- if locator is not None:
- try:
- setattr(target, '_locator', locator)
- except AttributeError:
- pass
-
- if isinstance(target, list) and isinstance(source, list):
- for i, _ in enumerate(target):
- copy_locators(target[i], source[i])
- elif isinstance(target, dict) and isinstance(source, dict):
- for k, v in target.items():
- copy_locators(v, source[k])
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/utils.py
----------------------------------------------------------------------
diff --git a/aria/modeling/utils.py b/aria/modeling/utils.py
index 944f413..c60ca8f 100644
--- a/aria/modeling/utils.py
+++ b/aria/modeling/utils.py
@@ -83,6 +83,15 @@ def instantiate_dict(context, container, the_dict, from_dict):
the_dict[name] = value
+def instantiate_list(context, container, the_list, from_list):
+ if not from_list:
+ return
+ for value in from_list.iteritems():
+ value = value.instantiate(context, container)
+ if value is not None:
+ the_list.append(value)
+
+
def dump_list_values(context, the_list, name):
if not the_list:
return
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/docs/requirements.txt
----------------------------------------------------------------------
diff --git a/docs/requirements.txt b/docs/requirements.txt
index dfd5f79..2a7d4a7 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -10,5 +10,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.
-Sphinx==1.5b1
-sphinx_rtd_theme==0.1.10a0
+Sphinx==1.5.2
+sphinx_rtd_theme==0.1.10b0
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
----------------------------------------------------------------------
diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
index 291d81f..06497ae 100644
--- a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
+++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py
@@ -40,16 +40,14 @@ def create_service_model(context): # pylint: disable=too-many-locals,too-many-br
# Metadata
metadata = context.presentation.get('service_template', 'metadata')
- if metadata is not None and False: # TODO
- metadata_model = Metadata()
- metadata_model.values['template_name'] = metadata.template_name
- metadata_model.values['template_author'] = metadata.template_author
- metadata_model.values['template_version'] = metadata.template_version
+ if metadata is not None:
+ model.meta_data['template_name'] = Metadata(value=metadata.template_name)
+ model.meta_data['template_author'] = Metadata(value=metadata.template_author)
+ model.meta_data['template_version'] = Metadata(value=metadata.template_version)
custom = metadata.custom
if custom:
for name, v in custom.iteritems():
- metadata_model.values[name] = v
- model.metadata = metadata_model
+ model.meta_data[name] = Metadata(value=v)
# Types
create_types(context,
[4/4] incubator-ariatosca git commit: Modeling refactoring;
improved metadata; work on instance models
Posted by em...@apache.org.
Modeling refactoring; improved metadata; work on instance 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/f1074fee
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/f1074fee
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/f1074fee
Branch: refs/heads/ARIA-105-integrate-modeling
Commit: f1074fee24d3a9895ad7e35dd8d58a7ab7265b99
Parents: 1a0b85a
Author: Tal Liron <ta...@gmail.com>
Authored: Thu Feb 23 17:07:03 2017 -0600
Committer: Tal Liron <ta...@gmail.com>
Committed: Thu Feb 23 17:07:03 2017 -0600
----------------------------------------------------------------------
aria/modeling/__init__.py | 19 +-
aria/modeling/elements.py | 113 --
aria/modeling/instance_elements.py | 1170 --------------
aria/modeling/model.py | 78 +-
aria/modeling/orchestrator_elements.py | 468 ------
aria/modeling/orchestrator_models.py | 468 ++++++
aria/modeling/service_instance_models.py | 1225 +++++++++++++++
aria/modeling/service_template_models.py | 1423 +++++++++++++++++
aria/modeling/shared_service_models.py | 109 ++
aria/modeling/structure.py | 73 +-
aria/modeling/template_elements.py | 1426 ------------------
aria/modeling/utils.py | 9 +
docs/requirements.txt | 4 +-
.../simple_v1_0/modeling/__init__.py | 12 +-
14 files changed, 3325 insertions(+), 3272 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/__init__.py
----------------------------------------------------------------------
diff --git a/aria/modeling/__init__.py b/aria/modeling/__init__.py
index d0d1c09..393a8cd 100644
--- a/aria/modeling/__init__.py
+++ b/aria/modeling/__init__.py
@@ -19,18 +19,19 @@ from . import (
structure,
type,
model,
- instance_elements as _instance_base,
- orchestrator_elements as _orchestrator_base,
- template_elements as _template_base,
+ service_template_models as _service_template_models_base,
+ service_instance_models as _service_instance_models_base,
+ orchestrator_models as _orchestrator_models_base,
)
-_ModelBaseCls = namedtuple('ModelBase', 'instance_elements,'
- 'orchestrator_elements,'
- 'template_elements')
-model_base = _ModelBaseCls(instance_elements=_instance_base,
- orchestrator_elements=_orchestrator_base,
- template_elements=_template_base)
+_ModelBaseCls = namedtuple('ModelBase', 'service_template_models,'
+ 'service_instance_models,'
+ 'orchestrator_models')
+model_base = _ModelBaseCls(service_template_models=_service_template_models_base,
+ service_instance_models=_service_instance_models_base,
+ orchestrator_models=_orchestrator_models_base)
+
__all__ = (
'structure',
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/elements.py
----------------------------------------------------------------------
diff --git a/aria/modeling/elements.py b/aria/modeling/elements.py
deleted file mode 100644
index 6ca9471..0000000
--- a/aria/modeling/elements.py
+++ /dev/null
@@ -1,113 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from sqlalchemy import (
- Column,
- Text
-)
-
-from ..parser.modeling import utils
-from ..storage import exceptions
-from ..utils.collections import OrderedDict
-from ..utils.console import puts
-
-from . import structure
-from . import type
-
-# pylint: disable=no-self-argument, no-member, abstract-method
-
-
-class ParameterBase(structure.ModelMixin):
- """
- Represents a typed value.
-
- This class is used by both service model and service instance elements.
- """
- __tablename__ = 'parameter'
-
- name = Column(Text, nullable=False)
- type_name = Column(Text, nullable=False)
-
- # Check: value type
- str_value = Column(Text)
- description = Column(Text)
-
- @property
- def as_raw(self):
- return OrderedDict((
- ('name', self.name),
- ('type_name', self.type_name),
- ('value', self.value),
- ('description', self.description)))
-
- @property
- def value(self):
- if self.type_name is None:
- return
- try:
- if self.type_name.lower() in ('str', 'unicode'):
- return self.str_value.decode('utf-8')
- elif self.type_name.lower() == 'int':
- return int(self.str_value)
- elif self.type_name.lower() == 'bool':
- return bool(self.str_value)
- elif self.type_name.lower() == 'float':
- return float(self.str_value)
- else:
- return self.str_value
- except ValueError:
- raise exceptions.StorageError('Trying to cast {0} to {1} failed'.format(self.str_value,
- self.type))
-
- @value.setter
- def value(self, value):
- self.str_value = unicode(value)
-
- def instantiate(self, context, container):
- return ParameterBase(self.type_name, self.str_value, self.description)
-
- def coerce_values(self, context, container, report_issues):
- if self.str_value is not None:
- self.str_value = utils.coerce_value(context, container, self.str_value, report_issues)
-
-
-class MetadataBase(structure.ModelMixin):
- """
- Custom values associated with the deployment template and its plans.
-
- This class is used by both service model and service instance elements.
-
- Properties:
-
- * :code:`values`: Dict of custom values
- """
- __tablename__ = 'meta_data'
-
- values = Column(type.StrictDict(key_cls=basestring))
-
- @property
- def as_raw(self):
- return self.values
-
- def instantiate(self, context, container):
- metadata = MetadataBase()
- metadata.values.update(self.values)
- return metadata
-
- def dump(self, context):
- puts('Metadata:')
- with context.style.indent:
- for name, value in self.values.iteritems():
- puts('%s: %s' % (name, context.style.meta(value)))
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/instance_elements.py
----------------------------------------------------------------------
diff --git a/aria/modeling/instance_elements.py b/aria/modeling/instance_elements.py
deleted file mode 100644
index 1d72fbe..0000000
--- a/aria/modeling/instance_elements.py
+++ /dev/null
@@ -1,1170 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-from sqlalchemy import (
- Column,
- Text,
- Integer,
- Boolean,
-)
-from sqlalchemy import DateTime
-from sqlalchemy.ext.associationproxy import association_proxy
-from sqlalchemy.ext.declarative import declared_attr
-from sqlalchemy.ext.orderinglist import ordering_list
-
-from aria.parser import validation
-from aria.utils import collections, formatting, console
-
-from . import (
- utils,
- structure,
- type as aria_types
-)
-
-
-# pylint: disable=no-self-argument, no-member, abstract-method
-
-# region Element instances
-
-class ServiceInstanceBase(structure.ModelMixin):
- __tablename__ = 'service_instance'
-
- __private_fields__ = ['substituion_fk',
- 'service_template_fk']
-
- description = Column(Text)
- _metadata = Column(Text)
-
- # region orchestrator required columns
-
- created_at = Column(DateTime, nullable=False, index=True)
- permalink = Column(Text)
- policy_triggers = Column(aria_types.Dict)
- policy_types = Column(aria_types.Dict)
- scaling_groups = Column(aria_types.Dict)
- updated_at = Column(DateTime)
- workflows = Column(aria_types.Dict)
-
- @declared_attr
- def service_template_name(cls):
- return association_proxy('service_template', 'name')
-
- # endregion
-
- # region foreign keys
-
- @declared_attr
- def substitution_fk(cls):
- return cls.foreign_key('substitution', nullable=True)
-
- @declared_attr
- def service_template_fk(cls):
- return cls.foreign_key('service_template')
-
- # endregion
-
- # region one-to-one relationships
-
- @declared_attr
- def substitution(cls):
- return cls.one_to_one_relationship('substitution')
-
- # endregion
-
- # region many-to-one relationships
-
- @declared_attr
- def service_template(cls):
- return cls.many_to_one_relationship('service_template')
-
- # endregion
-
- # region many-to-many relationships
- @declared_attr
- def inputs(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='inputs')
-
- @declared_attr
- def outputs(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='outputs')
-
- # endregion
-
- # association proxies
-
- def satisfy_requirements(self, context):
- satisfied = True
- for node in self.nodes.all():
- if not node.satisfy_requirements(context):
- satisfied = False
- return satisfied
-
- def validate_capabilities(self, context):
- satisfied = True
- for node in self.nodes.all():
- if not node.validate_capabilities(context):
- satisfied = False
- return satisfied
-
- def find_nodes(self, node_template_name):
- nodes = []
- for node in self.nodes.all():
- if 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.id for node in self.find_nodes(node_template_name)))
-
- def find_groups(self, group_template_name):
- groups = []
- for group in self.groups.all():
- 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.id for group in self.find_groups(group_template_name)))
-
- def is_node_a_target(self, context, target_node):
- for node in self.nodes.all():
- 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.id:
- 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
-
-
-class OperationBase(structure.ModelMixin):
- """
- An operation in a :class:`Interface`.
-
- Properties:
-
- * :code:`name`: Name
- * :code:`description`: Description
- * :code:`implementation`: Implementation string (interpreted by the orchestrator)
- * :code:`dependencies`: List of strings (interpreted by the orchestrator)
- * :code:`executor`: Executor string (interpreted by the orchestrator)
- * :code:`max_retries`: Maximum number of retries allowed in case of failure
- * :code:`retry_interval`: Interval between retries
- * :code:`inputs`: Dict of :class:`Parameter`
- """
- __tablename__ = 'operation'
-
- __private_fields__ = ['service_template_fk',
- 'interface_instance_fk']
-
- # region foreign_keys
-
- @declared_attr
- def service_instance_fk(cls):
- return cls.foreign_key('service_instance', nullable=True)
-
- @declared_attr
- def interface_instance_fk(cls):
- return cls.foreign_key('interface', nullable=True)
-
- # endregion
-
- description = Column(Text)
- implementation = Column(Text)
- dependencies = Column(aria_types.StrictList(item_cls=basestring))
-
- executor = Column(Text)
- max_retries = Column(Integer, default=None)
- retry_interval = Column(Integer, default=None)
- plugin = Column(Text)
- operation = Column(Boolean)
-
- # region many-to-one relationships
-
- @declared_attr
- def service_instance(cls):
- return cls.many_to_one_relationship('service_instance')
-
- @declared_attr
- def interface(cls):
- return cls.many_to_one_relationship('interface')
-
- # endregion
-
- # region many-to-many relationships
-
- @declared_attr
- def inputs(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='inputs')
-
- # endregion
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('name', self.name),
- ('description', self.description),
- ('implementation', self.implementation),
- ('dependencies', self.dependencies),
- ('executor', self.executor),
- ('max_retries', self.max_retries),
- ('retry_interval', self.retry_interval),
- ('inputs', formatting.as_raw_dict(self.inputs))))
-
- def validate(self, context):
- utils.validate_dict_values(context, self.inputs)
-
- def coerce_values(self, context, container, report_issues):
- utils.coerce_dict_values(context, container, self.inputs, report_issues)
-
- def dump(self, context):
- console.puts(context.style.node(self.name))
- if self.description:
- console.puts(context.style.meta(self.description))
- with context.style.indent:
- if self.implementation is not None:
- console.puts('Implementation: %s' % context.style.literal(self.implementation))
- if self.dependencies:
- console.puts(
- 'Dependencies: %s'
- % ', '.join((str(context.style.literal(v)) for v in self.dependencies)))
- if self.executor is not None:
- console.puts('Executor: %s' % context.style.literal(self.executor))
- if self.max_retries is not None:
- console.puts('Max retries: %s' % context.style.literal(self.max_retries))
- if self.retry_interval is not None:
- console.puts('Retry interval: %s' % context.style.literal(self.retry_interval))
- utils.dump_parameters(context, self.inputs, 'Inputs')
-
-
-class InterfaceBase(structure.ModelMixin):
- """
- A typed set of :class:`Operation`.
-
- Properties:
-
- * :code:`name`: Name
- * :code:`description`: Description
- * :code:`type_name`: Must be represented in the :class:`ModelingContext`
- * :code:`inputs`: Dict of :class:`Parameter`
- * :code:`operations`: Dict of :class:`Operation`
- """
- __tablename__ = 'interface'
-
- __private_fields__ = ['group_fk',
- 'node_fk',
- 'relationship_fk']
-
-
- # region foreign_keys
-
- @declared_attr
- def group_fk(cls):
- return cls.foreign_key('group', nullable=True)
-
- @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)
-
- # endregion
-
- description = Column(Text)
- type_name = Column(Text)
- edge = Column(Text)
-
- # region many-to-one relationships
-
- @declared_attr
- def node(cls):
- return cls.many_to_one_relationship('node')
-
- @declared_attr
- def relationship(cls):
- return cls.many_to_one_relationship('relationship')
-
- @declared_attr
- def group(cls):
- return cls.many_to_one_relationship('group')
-
- # endregion
-
- # region many-to-many relationships
-
- @declared_attr
- def inputs(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='inputs')
-
- # endregion
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('name', self.name),
- ('description', self.description),
- ('type_name', self.type_name),
- ('inputs', formatting.as_raw_dict(self.inputs)),
- ('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 "%s" has an unknown type: %s'
- % (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)
-
- def coerce_values(self, context, container, report_issues):
- utils.coerce_dict_values(context, container, self.inputs, report_issues)
- utils.coerce_dict_values(context, container, self.operations, report_issues)
-
- def dump(self, context):
- console.puts(context.style.node(self.name))
- if self.description:
- console.puts(context.style.meta(self.description))
- with context.style.indent:
- console.puts('Interface type: %s' % context.style.type(self.type_name))
- utils.dump_parameters(context, self.inputs, 'Inputs')
- utils.dump_dict_values(context, self.operations, 'Operations')
-
-
-class CapabilityBase(structure.ModelMixin):
- """
- A capability of a :class:`Node`.
-
- An instance of a :class:`CapabilityTemplate`.
-
- Properties:
-
- * :code:`name`: Name
- * :code:`type_name`: Must be represented in the :class:`ModelingContext`
- * :code:`min_occurrences`: Minimum number of requirement matches required
- * :code:`max_occurrences`: Maximum number of requirement matches allowed
- * :code:`properties`: Dict of :class:`Parameter`
- """
- __tablename__ = 'capability'
-
- __private_fields__ = ['node_fk']
-
- # region foreign_keys
-
- @declared_attr
- def node_fk(cls):
- return cls.foreign_key('node')
-
- # endregion
-
- type_name = Column(Text)
-
- min_occurrences = Column(Integer, default=None) # optional
- max_occurrences = Column(Integer, default=None) # optional
- occurrences = Column(Integer, default=0)
-
- # region many-to-one relationships
-
- @declared_attr
- def node(cls):
- return cls.many_to_one_relationship('node')
-
- # endregion
-
- # region many-to-many relationships
-
- @declared_attr
- def properties(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='properties')
-
- # endregion
-
- @property
- def has_enough_relationships(self):
- if self.min_occurrences is not None:
- return self.occurrences >= self.min_occurrences
- return True
-
- def relate(self):
- if self.max_occurrences is not None:
- if self.occurrences == self.max_occurrences:
- return False
- self.occurrences += 1
- return True
-
- @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, context):
- if context.modeling.capability_types.get_descendant(self.type_name) is None:
- context.validation.report('capability "%s" has an unknown type: %s'
- % (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):
- utils.coerce_dict_values(context, container, self.properties, report_issues)
-
- def dump(self, context):
- console.puts(context.style.node(self.name))
- with context.style.indent:
- console.puts('Type: %s' % context.style.type(self.type_name))
- console.puts('Occurrences: %s (%s%s)'
- % (self.occurrences,
- self.min_occurrences or 0,
- (' to %d' % self.max_occurrences)
- if self.max_occurrences is not None
- else ' or more'))
- utils.dump_parameters(context, self.properties)
-
-
-class ArtifactBase(structure.ModelMixin):
- """
- A file associated with a :class:`Node`.
-
- Properties:
-
- * :code:`name`: Name
- * :code:`description`: Description
- * :code:`type_name`: Must be represented in the :class:`ModelingContext`
- * :code:`source_path`: Source path (CSAR or repository)
- * :code:`target_path`: Path at destination machine
- * :code:`repository_url`: Repository URL
- * :code:`repository_credential`: Dict of string
- * :code:`properties`: Dict of :class:`Parameter`
- """
- __tablename__ = 'artifact'
-
- __private_fields__ = ['node_fk']
-
- # region foreign_keys
-
- @declared_attr
- def node_fk(cls):
- return cls.foreign_key('node')
-
- # endregion
-
- description = Column(Text)
- type_name = Column(Text)
- source_path = Column(Text)
- target_path = Column(Text)
- repository_url = Column(Text)
- repository_credential = Column(aria_types.StrictDict(basestring, basestring))
-
- # region many-to-one relationships
-
- @declared_attr
- def node(cls):
- return cls.many_to_one_relationship('node')
-
- # endregion
-
- # region many-to-many relationships
-
- @declared_attr
- def properties(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='properties')
-
- # endregion
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('name', self.name),
- ('description', self.description),
- ('type_name', self.type_name),
- ('source_path', self.source_path),
- ('target_path', self.target_path),
- ('repository_url', self.repository_url),
- ('repository_credential', formatting.as_agnostic(self.repository_credential)),
- ('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 "%s" has an unknown type: %s'
- % (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):
- utils.coerce_dict_values(context, container, self.properties, report_issues)
-
- def dump(self, context):
- console.puts(context.style.node(self.name))
- if self.description:
- console.puts(context.style.meta(self.description))
- with context.style.indent:
- console.puts('Artifact type: %s' % context.style.type(self.type_name))
- console.puts('Source path: %s' % context.style.literal(self.source_path))
- if self.target_path is not None:
- console.puts('Target path: %s' % context.style.literal(self.target_path))
- if self.repository_url is not None:
- console.puts('Repository URL: %s' % context.style.literal(self.repository_url))
- if self.repository_credential:
- console.puts('Repository credential: %s'
- % context.style.literal(self.repository_credential))
- utils.dump_parameters(context, self.properties)
-
-
-class PolicyBase(structure.ModelMixin):
- """
- An instance of a :class:`PolicyTemplate`.
-
- Properties:
-
- * :code:`name`: Name
- * :code:`type_name`: Must be represented in the :class:`ModelingContext`
- * :code:`properties`: Dict of :class:`Parameter`
- * :code:`target_node_ids`: Must be represented in the :class:`ServiceInstance`
- * :code:`target_group_ids`: Must be represented in the :class:`ServiceInstance`
- """
- __tablename__ = 'policy'
-
- __private_fields__ = ['service_instance_fk']
-
- # region foreign_keys
-
- @declared_attr
- def service_instance_fk(cls):
- return cls.foreign_key('service_instance')
-
- # endregion
-
- type_name = Column(Text)
- target_node_ids = Column(aria_types.StrictList(basestring))
- target_group_ids = Column(aria_types.StrictList(basestring))
-
- # region many-to-one relationships
-
- @declared_attr
- def service_instnce(cls):
- return cls.many_to_one_relationship('service_instance')
-
- # region many-to-many relationships
-
- @declared_attr
- def properties(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='properties')
-
- # endregion
-
- @property
- def as_raw(self):
- 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)))
-
- def validate(self, context):
- if context.modeling.policy_types.get_descendant(self.type_name) is None:
- context.validation.report('policy "%s" has an unknown type: %s'
- % (self.name, utils.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):
- utils.coerce_dict_values(context, container, self.properties, report_issues)
-
- def dump(self, context):
- console.puts('Policy: %s' % context.style.node(self.name))
- with context.style.indent:
- console.puts('Type: %s' % context.style.type(self.type_name))
- utils.dump_parameters(context, self.properties)
- if self.target_node_ids:
- 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:
- console.puts('Target groups:')
- with context.style.indent:
- for group_id in self.target_group_ids:
- console.puts(context.style.node(group_id))
-
-
-class MappingBase(structure.ModelMixin):
- """
- An instance of a :class:`MappingTemplate`.
-
- Properties:
-
- * :code:`mapped_name`: Exposed capability or requirement name
- * :code:`node_id`: Must be represented in the :class:`ServiceInstance`
- * :code:`name`: Name of capability or requirement at the node
- """
- __tablename__ = 'mapping'
-
- mapped_name = Column(Text)
- node_id = Column(Text)
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('mapped_name', self.mapped_name),
- ('node_id', self.node_id),
- ('name', self.name)))
-
- def dump(self, context):
- console.puts('%s -> %s.%s'
- % (context.style.node(self.mapped_name),
- context.style.node(self.node_id),
- context.style.node(self.name)))
-
-
-class SubstitutionBase(structure.ModelMixin):
- """
- An instance of a :class:`SubstitutionTemplate`.
-
- Properties:
-
- * :code:`node_type_name`: Must be represented in the :class:`ModelingContext`
- * :code:`capabilities`: Dict of :class:`Mapping`
- * :code:`requirements`: Dict of :class:`Mapping`
- """
- __tablename__ = 'substitution'
-
- node_type_name = Column(Text)
-
- # region many-to-many relationships
-
- @declared_attr
- def capabilities(cls):
- return cls.many_to_many_relationship('mapping', table_prefix='capabilities')
-
- @declared_attr
- def requirements(cls):
- return cls.many_to_many_relationship('mapping',
- table_prefix='requirements',
- relationship_kwargs=dict(lazy='dynamic'))
-
- # endregion
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('node_type_name', self.node_type_name),
- ('capabilities', formatting.as_raw_list(self.capabilities)),
- ('requirements', formatting.as_raw_list(self.requirements))))
-
- def validate(self, context):
- if context.modeling.node_types.get_descendant(self.node_type_name) is None:
- context.validation.report('substitution "%s" has an unknown type: %s'
- % (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.capabilities)
- utils.validate_dict_values(context, self.requirements)
-
- def coerce_values(self, context, container, report_issues):
- utils.coerce_dict_values(context, container, self.capabilities, report_issues)
- utils.coerce_dict_values(context, container, self.requirements, report_issues)
-
- def dump(self, context):
- console.puts('Substitution:')
- with context.style.indent:
- console.puts('Node type: %s' % context.style.type(self.node_type_name))
- utils.dump_dict_values(context, self.capabilities, 'Capability mappings')
- utils.dump_dict_values(context, self.requirements, 'Requirement mappings')
-
-# endregion
-
-
-# region Node instances
-
-class NodeBase(structure.ModelMixin):
- """
- An instance of a :class:`NodeTemplate`.
-
- Nodes may have zero or more :class:`Relationship` instances to other nodes.
-
- Properties:
-
- * :code:`id`: Unique ID (prefixed with the template name)
- * :code:`type_name`: Must be represented in the :class:`ModelingContext`
- * :code:`template_name`: Must be represented in the :class:`ServiceModel`
- * :code:`properties`: Dict of :class:`Parameter`
- * :code:`interfaces`: Dict of :class:`Interface`
- * :code:`artifacts`: Dict of :class:`Artifact`
- * :code:`capabilities`: Dict of :class:`CapabilityTemplate`
- * :code:`relationships`: List of :class:`Relationship`
- """
- __tablename__ = 'node'
-
- __private_fields__ = ['service_instance_fk',
- 'host_fk',
- 'node_template_fk']
-
- # region foreign_keys
-
- @declared_attr
- def service_instance_fk(cls):
- return cls.foreign_key('service_instance')
-
- @declared_attr
- def host_fk(cls):
- return cls.foreign_key('node', nullable=True)
-
- @declared_attr
- def node_template_fk(cls):
- return cls.foreign_key('node_template')
-
- # endregion
-
- type_name = Column(Text)
- template_name = Column(Text)
-
- # region orchestrator required columns
-
- runtime_properties = Column(aria_types.Dict)
- scaling_groups = Column(aria_types.List)
- state = Column(Text, nullable=False)
- version = Column(Integer, default=1)
-
- @declared_attr
- def plugins(cls):
- return association_proxy('node_template', 'plugins')
-
- @declared_attr
- def host(cls):
- return cls.relationship_to_self('host_fk')
-
- @declared_attr
- def service_instance_name(cls):
- return association_proxy('service_instance', 'name')
-
- @property
- def ip(self):
- 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 = [prop for prop in host_node.properties if prop.name == 'ip']
- if host_ip_property:
- return host_ip_property[0].value
- return None
-
- @declared_attr
- def node_template(cls):
- return cls.many_to_one_relationship('node_template')
-
- @declared_attr
- def service_template(cls):
- return association_proxy('service_instance', 'service_template')
-
- # endregion
-
- # region many-to-one relationships
-
- @declared_attr
- def service_instance(cls):
- return cls.many_to_one_relationship('service_instance')
-
- # endregion
-
- # region many-to-many relationships
-
- @declared_attr
- def properties(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='properties')
-
- # endregion
-
- def satisfy_requirements(self, context):
- node_template = context.modeling.model.node_templates.get(self.template_name)
- satisfied = True
- for i in range(len(node_template.requirement_templates)):
- requirement_template = node_template.requirement_templates[i]
-
- # 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,
- requirement_template_index=i)
- else:
- context.validation.report('requirement "%s" of node "%s" has no target node '
- 'template' % (requirement_template.name,
- self.id),
- level=validation.Issue.BETWEEN_INSTANCES)
- satisfied = False
- return satisfied
-
- def _satisfy_capability(self, context, target_node_capability, target_node_template,
- requirement_template, requirement_template_index):
- # Find target nodes
- target_nodes = context.modeling.instance.find_nodes(target_node_template.name)
- 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:
- relationship = RelationshipBase(
- name=requirement_template.name,
- source_requirement_index=requirement_template_index,
- target_node_id=target_node.id,
- target_capability_name=target_capability.name
- )
- self.relationships.append(relationship)
- else:
- context.validation.report('requirement "%s" of node "%s" targets node '
- 'template "%s" but its instantiated nodes do not '
- 'have enough capacity'
- % (requirement_template.name,
- self.id,
- target_node_template.name),
- level=validation.Issue.BETWEEN_INSTANCES)
- return False
- else:
- context.validation.report('requirement "%s" of node "%s" targets node template '
- '"%s" but it has no instantiated nodes'
- % (requirement_template.name,
- self.id,
- target_node_template.name),
- level=validation.Issue.BETWEEN_INSTANCES)
- return False
-
- def validate_capabilities(self, context):
- satisfied = False
- for capability in self.capabilities.itervalues():
- if not capability.has_enough_relationships:
- context.validation.report('capability "%s" of node "%s" requires at least %d '
- 'relationships but has %d'
- % (capability.name,
- self.id,
- capability.min_occurrences,
- capability.occurrences),
- level=validation.Issue.BETWEEN_INSTANCES)
- satisfied = False
- return satisfied
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('id', self.id),
- ('type_name', self.type_name),
- ('template_name', self.template_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.relationships))))
-
- def validate(self, context):
- if len(self.id) > context.modeling.id_max_length:
- context.validation.report('"%s" has an ID longer than the limit of %d characters: %d'
- % (self.id,
- context.modeling.id_max_length,
- len(self.id)),
- level=validation.Issue.BETWEEN_INSTANCES)
-
- # TODO: validate that node template is of type?
-
- utils.validate_dict_values(context, self.properties)
- utils.validate_dict_values(context, self.interfaces)
- utils.validate_dict_values(context, self.artifacts)
- utils.validate_dict_values(context, self.capabilities)
- utils.validate_list_values(context, self.relationships)
-
- def coerce_values(self, context, container, report_issues):
- utils.coerce_dict_values(context, self, self.properties, report_issues)
- utils.coerce_dict_values(context, self, self.interfaces, report_issues)
- utils.coerce_dict_values(context, self, self.artifacts, report_issues)
- utils.coerce_dict_values(context, self, self.capabilities, report_issues)
- utils.coerce_list_values(context, self, self.relationships, report_issues)
-
- def dump(self, context):
- console.puts('Node: %s' % context.style.node(self.id))
- with context.style.indent:
- console.puts('Template: %s' % context.style.node(self.template_name))
- console.puts('Type: %s' % 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')
- utils.dump_dict_values(context, self.capabilities, 'Capabilities')
- utils.dump_list_values(context, self.relationships, 'Relationships')
-
-
-class GroupBase(structure.ModelMixin):
- """
- An instance of a :class:`GroupTemplate`.
-
- Properties:
-
- * :code:`id`: Unique ID (prefixed with the template name)
- * :code:`type_name`: Must be represented in the :class:`ModelingContext`
- * :code:`template_name`: Must be represented in the :class:`ServiceModel`
- * :code:`properties`: Dict of :class:`Parameter`
- * :code:`interfaces`: Dict of :class:`Interface`
- * :code:`policies`: Dict of :class:`GroupPolicy`
- * :code:`member_node_ids`: Must be represented in the :class:`ServiceInstance`
- * :code:`member_group_ids`: Must be represented in the :class:`ServiceInstance`
- """
- __tablename__ = 'group'
-
- __private_fields__ = ['service_instance_fk']
-
- # region foreign_keys
-
- @declared_attr
- def service_instance_fk(cls):
- return cls.foreign_key('service_instance')
-
- # endregion
-
- type_name = Column(Text)
- template_name = Column(Text)
- member_node_ids = Column(aria_types.StrictList(basestring))
- member_group_ids = Column(aria_types.StrictList(basestring))
-
- # region many-to-one relationships
-
- @declared_attr
- def service_instance(cls):
- return cls.many_to_one_relationship('service_instance')
-
- # endregion
-
- # region many-to-many relationships
-
- @declared_attr
- def properties(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='properties')
-
- # endregion
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('id', self.id),
- ('type_name', self.type_name),
- ('template_name', self.template_name),
- ('properties', formatting.as_raw_dict(self.properties)),
- ('interfaces', formatting.as_raw_list(self.interfaces)),
- ('policies', formatting.as_raw_list(self.policies)),
- ('member_node_ids', self.member_node_ids),
- ('member_group_ids', self.member_group_ids)))
-
- def validate(self, context):
- if context.modeling.group_types.get_descendant(self.type_name) is None:
- context.validation.report('group "%s" has an unknown type: %s'
- % (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)
- utils.validate_dict_values(context, self.policies)
-
- def coerce_values(self, context, container, report_issues):
- utils.coerce_dict_values(context, container, self.properties, report_issues)
- utils.coerce_dict_values(context, container, self.interfaces, report_issues)
- utils.coerce_dict_values(context, container, self.policies, report_issues)
-
- def dump(self, context):
- console.puts('Group: %s' % context.style.node(self.id))
- with context.style.indent:
- console.puts('Type: %s' % context.style.type(self.type_name))
- console.puts('Template: %s' % context.style.type(self.template_name))
- utils.dump_parameters(context, self.properties)
- utils.dump_interfaces(context, self.interfaces)
- utils.dump_dict_values(context, self.policies, 'Policies')
- if self.member_node_ids:
- console.puts('Member nodes:')
- with context.style.indent:
- for node_id in self.member_node_ids:
- console.puts(context.style.node(node_id))
-
-# endregion
-
-
-# region Relationship instances
-
-class RelationshipBase(structure.ModelMixin):
- """
- Connects :class:`Node` to another node.
-
- An instance of a :class:`RelationshipTemplate`.
-
- Properties:
-
- * :code:`name`: Name (usually the name of the requirement at the source node template)
- * :code:`source_requirement_index`: Must be represented in the source node template
- * :code:`target_node_id`: Must be represented in the :class:`ServiceInstance`
- * :code:`target_capability_name`: Matches the capability at the target node
- * :code:`type_name`: Must be represented in the :class:`ModelingContext`
- * :code:`template_name`: Must be represented in the :class:`ServiceModel`
- * :code:`properties`: Dict of :class:`Parameter`
- * :code:`source_interfaces`: Dict of :class:`Interface`
- * :code:`target_interfaces`: Dict of :class:`Interface`
- """
- __tablename__ = 'relationship'
-
- __private_fields__ = ['source_node_fk',
- 'target_node_fk']
-
- source_requirement_index = Column(Integer)
- target_node_id = Column(Text)
- target_capability_name = Column(Text)
- type_name = Column(Text)
- template_name = Column(Text)
-
- # region orchestrator required columns
-
- source_position = Column(Integer)
- target_position = Column(Integer)
-
- @declared_attr
- def source_node_fk(cls):
- return cls.foreign_key('node', nullable=True)
-
- @declared_attr
- def source_node(cls):
- return cls.many_to_one_relationship(
- 'node',
- 'source_node_fk',
- backreference='outbound_relationships',
- backref_kwargs=dict(
- order_by=cls.source_position,
- collection_class=ordering_list('source_position', count_from=0),
- )
- )
-
- @declared_attr
- def source_node_name(cls):
- return association_proxy('source_node', cls.name_column_name())
-
- @declared_attr
- def target_node_fk(cls):
- return cls.foreign_key('node', nullable=True)
-
- @declared_attr
- def target_node(cls):
- return cls.many_to_one_relationship(
- 'node',
- 'target_node_fk',
- backreference='inbound_relationships',
- backref_kwargs=dict(
- order_by=cls.target_position,
- collection_class=ordering_list('target_position', count_from=0),
- )
- )
-
- @declared_attr
- def target_node_name(cls):
- return association_proxy('target_node', cls.name_column_name())
-
- # endregion
-
- # region many-to-many relationships
-
- @declared_attr
- def properties(cls):
- return cls.many_to_many_relationship('parameter', table_prefix='properties')
-
- # endregion
-
- @property
- def as_raw(self):
- return collections.OrderedDict((
- ('name', self.name),
- ('source_requirement_index', self.source_requirement_index),
- ('target_node_id', self.target_node_id),
- ('target_capability_name', self.target_capability_name),
- ('type_name', self.type_name),
- ('template_name', self.template_name),
- ('properties', formatting.as_raw_dict(self.properties)),
- ('source_interfaces', formatting.as_raw_list(self.source_interfaces)),
- ('target_interfaces', formatting.as_raw_list(self.target_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 "%s" has an unknown type: %s'
- % (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.source_interfaces)
- utils.validate_dict_values(context, self.target_interfaces)
-
- def coerce_values(self, context, container, report_issues):
- utils.coerce_dict_values(context, container, self.properties, report_issues)
- utils.coerce_dict_values(context, container, self.source_interfaces, report_issues)
- utils.coerce_dict_values(context, container, self.target_interfaces, report_issues)
-
- def dump(self, context):
- if self.name:
- if self.source_requirement_index is not None:
- console.puts('%s (%d) ->' % (
- context.style.node(self.name),
- self.source_requirement_index))
- else:
- console.puts('%s ->' % context.style.node(self.name))
- else:
- console.puts('->')
- with context.style.indent:
- console.puts('Node: %s' % context.style.node(self.target_node_id))
- if self.target_capability_name is not None:
- console.puts('Capability: %s' % context.style.node(self.target_capability_name))
- if self.type_name is not None:
- console.puts('Relationship type: %s' % context.style.type(self.type_name))
- if self.template_name is not None:
- console.puts('Relationship template: %s' % context.style.node(self.template_name))
- utils.dump_parameters(context, self.properties)
- utils.dump_interfaces(context, self.source_interfaces, 'Source interfaces')
- utils.dump_interfaces(context, self.target_interfaces, 'Target interfaces')
-
-# endregion
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/model.py
----------------------------------------------------------------------
diff --git a/aria/modeling/model.py b/aria/modeling/model.py
index 33a7f6a..8459325 100644
--- a/aria/modeling/model.py
+++ b/aria/modeling/model.py
@@ -16,10 +16,10 @@
from sqlalchemy.ext.declarative import declarative_base
from . import (
- template_elements,
- instance_elements,
- orchestrator_elements,
- elements,
+ service_template_models,
+ service_instance_models,
+ orchestrator_models,
+ shared_service_models,
structure,
)
@@ -66,146 +66,144 @@ aria_declarative_base = declarative_base(cls=structure.ModelIDMixin)
# pylint: disable=abstract-method
-# region elements
+# region shared service models
-class Parameter(aria_declarative_base, elements.ParameterBase):
+class Parameter(aria_declarative_base, shared_service_models.ParameterBase):
pass
-class Metadata(aria_declarative_base, elements.MetadataBase):
+class Metadata(aria_declarative_base, shared_service_models.MetadataBase):
pass
-
# endregion
-# region template models
+# region service template models
-class MappingTemplate(aria_declarative_base, template_elements.MappingTemplateBase):
+class MappingTemplate(aria_declarative_base, service_template_models.MappingTemplateBase):
pass
-class SubstitutionTemplate(aria_declarative_base, template_elements.SubstitutionTemplateBase):
+class SubstitutionTemplate(aria_declarative_base, service_template_models.SubstitutionTemplateBase):
pass
-class InterfaceTemplate(aria_declarative_base, template_elements.InterfaceTemplateBase):
+class InterfaceTemplate(aria_declarative_base, service_template_models.InterfaceTemplateBase):
pass
-class OperationTemplate(aria_declarative_base, template_elements.OperationTemplateBase):
+class OperationTemplate(aria_declarative_base, service_template_models.OperationTemplateBase):
pass
-class ServiceTemplate(aria_declarative_base, template_elements.ServiceTemplateBase):
+class ServiceTemplate(aria_declarative_base, service_template_models.ServiceTemplateBase):
pass
-class NodeTemplate(aria_declarative_base, template_elements.NodeTemplateBase):
+class NodeTemplate(aria_declarative_base, service_template_models.NodeTemplateBase):
pass
-class GroupTemplate(aria_declarative_base, template_elements.GroupTemplateBase):
+class GroupTemplate(aria_declarative_base, service_template_models.GroupTemplateBase):
pass
-class ArtifactTemplate(aria_declarative_base, template_elements.ArtifactTemplateBase):
+class ArtifactTemplate(aria_declarative_base, service_template_models.ArtifactTemplateBase):
pass
-class PolicyTemplate(aria_declarative_base, template_elements.PolicyTemplateBase):
+class PolicyTemplate(aria_declarative_base, service_template_models.PolicyTemplateBase):
pass
-class RequirementTemplate(aria_declarative_base, template_elements.RequirementTemplateBase):
+class RequirementTemplate(aria_declarative_base, service_template_models.RequirementTemplateBase):
pass
-class CapabilityTemplate(aria_declarative_base, template_elements.CapabilityTemplateBase):
+class CapabilityTemplate(aria_declarative_base, service_template_models.CapabilityTemplateBase):
pass
-class RelationshipTemplate(aria_declarative_base, template_elements.RelationshipTemplateBase):
+class RelationshipTemplate(aria_declarative_base, service_template_models.RelationshipTemplateBase):
pass
# endregion
-# region instance models
+# region service instance models
-class Mapping(aria_declarative_base, instance_elements.MappingBase):
+class Mapping(aria_declarative_base, service_instance_models.MappingBase):
pass
-class Substitution(aria_declarative_base, instance_elements.SubstitutionBase):
+class Substitution(aria_declarative_base, service_instance_models.SubstitutionBase):
pass
-class ServiceInstance(aria_declarative_base, instance_elements.ServiceInstanceBase):
+class ServiceInstance(aria_declarative_base, service_instance_models.ServiceInstanceBase):
pass
-class Node(aria_declarative_base, instance_elements.NodeBase):
+class Node(aria_declarative_base, service_instance_models.NodeBase):
pass
-class Relationship(aria_declarative_base, instance_elements.RelationshipBase):
+class Relationship(aria_declarative_base, service_instance_models.RelationshipBase):
pass
-class Artifact(aria_declarative_base, instance_elements.ArtifactBase):
+class Artifact(aria_declarative_base, service_instance_models.ArtifactBase):
pass
-class Group(aria_declarative_base, instance_elements.GroupBase):
+class Group(aria_declarative_base, service_instance_models.GroupBase):
pass
-class Interface(aria_declarative_base, instance_elements.InterfaceBase):
+class Interface(aria_declarative_base, service_instance_models.InterfaceBase):
pass
-class Operation(aria_declarative_base, instance_elements.OperationBase):
+class Operation(aria_declarative_base, service_instance_models.OperationBase):
pass
-class Capability(aria_declarative_base, instance_elements.CapabilityBase):
+class Capability(aria_declarative_base, service_instance_models.CapabilityBase):
pass
-class Policy(aria_declarative_base, instance_elements.PolicyBase):
+class Policy(aria_declarative_base, service_instance_models.PolicyBase):
pass
-
# endregion
# region orchestrator models
-class Execution(aria_declarative_base, orchestrator_elements.Execution):
+class Execution(aria_declarative_base, orchestrator_models.Execution):
pass
class ServiceInstanceUpdate(aria_declarative_base,
- orchestrator_elements.ServiceInstanceUpdateBase):
+ orchestrator_models.ServiceInstanceUpdateBase):
pass
class ServiceInstanceUpdateStep(aria_declarative_base,
- orchestrator_elements.ServiceInstanceUpdateStepBase):
+ orchestrator_models.ServiceInstanceUpdateStepBase):
pass
class ServiceInstanceModification(aria_declarative_base,
- orchestrator_elements.ServiceInstanceModificationBase):
+ orchestrator_models.ServiceInstanceModificationBase):
pass
-class Plugin(aria_declarative_base, orchestrator_elements.PluginBase):
+class Plugin(aria_declarative_base, orchestrator_models.PluginBase):
pass
-class Task(aria_declarative_base, orchestrator_elements.TaskBase):
+class Task(aria_declarative_base, orchestrator_models.TaskBase):
pass
# endregion
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/orchestrator_elements.py
----------------------------------------------------------------------
diff --git a/aria/modeling/orchestrator_elements.py b/aria/modeling/orchestrator_elements.py
deleted file mode 100644
index 5f7a3f2..0000000
--- a/aria/modeling/orchestrator_elements.py
+++ /dev/null
@@ -1,468 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements. See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You under the Apache License, Version 2.0
-# (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-"""
-Aria's storage.models module
-Path: aria.storage.models
-
-models module holds aria's models.
-
-classes:
- * Field - represents a single field.
- * IterField - represents an iterable field.
- * Model - abstract model implementation.
- * Snapshot - snapshots implementation model.
- * Deployment - deployment implementation model.
- * DeploymentUpdateStep - deployment update step implementation model.
- * DeploymentUpdate - deployment update implementation model.
- * DeploymentModification - deployment modification implementation model.
- * Execution - execution implementation model.
- * Node - node implementation model.
- * Relationship - relationship implementation model.
- * NodeInstance - node instance implementation model.
- * RelationshipInstance - relationship instance implementation model.
- * Plugin - plugin implementation model.
-"""
-from collections import namedtuple
-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 aria.orchestrator.exceptions import TaskAbortException, TaskRetryException
-
-from .type import List, Dict
-from .structure import ModelMixin
-
-__all__ = (
- 'ServiceInstanceUpdateStepBase',
- 'ServiceInstanceUpdateBase',
- 'ServiceInstanceModificationBase',
- 'Execution',
- 'PluginBase',
- 'TaskBase'
-)
-
-# pylint: disable=no-self-argument, no-member, abstract-method
-
-
-class Execution(ModelMixin):
- """
- Execution model representation.
- """
- # Needed only for pylint. the id will be populated by sqlalcehmy and the proper column.
- __tablename__ = 'execution'
-
- __private_fields__ = ['service_instance_fk']
-
- 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_template(cls):
- return association_proxy('service_instance', 'service_template')
-
- @declared_attr
- def service_instance_fk(cls):
- return cls.foreign_key('service_instance')
-
- @declared_attr
- def service_instance(cls):
- return cls.many_to_one_relationship('service_instance')
-
- @declared_attr
- def service_instance_name(cls):
- return association_proxy('service_instance', cls.name_column_name())
-
- @declared_attr
- def service_template_name(cls):
- return association_proxy('service_instance', 'service_template_name')
-
- def __str__(self):
- return '<{0} id=`{1}` (status={2})>'.format(
- self.__class__.__name__,
- getattr(self, self.name_column_name()),
- self.status
- )
-
-
-class ServiceInstanceUpdateBase(ModelMixin):
- """
- Deployment update model representation.
- """
- # Needed only for pylint. the id will be populated by sqlalcehmy and the proper column.
- steps = None
-
- __tablename__ = 'service_instance_update'
- __private_fields__ = ['service_instance_fk',
- 'execution_fk']
-
- _private_fields = ['execution_fk', 'deployment_fk']
-
- created_at = Column(DateTime, nullable=False, index=True)
- service_instance_plan = Column(Dict, nullable=False)
- service_instance_update_node_instances = Column(Dict)
- service_instance_update_service_instance = Column(Dict)
- service_instance_update_nodes = Column(List)
- modified_entity_ids = Column(Dict)
- state = Column(Text)
-
- @declared_attr
- def execution_fk(cls):
- return cls.foreign_key('execution', nullable=True)
-
- @declared_attr
- def execution(cls):
- return cls.many_to_one_relationship('execution')
-
- @declared_attr
- def execution_name(cls):
- return association_proxy('execution', cls.name_column_name())
-
- @declared_attr
- def service_instance_fk(cls):
- return cls.foreign_key('service_instance')
-
- @declared_attr
- def service_instance(cls):
- return cls.many_to_one_relationship('service_instance')
-
- @declared_attr
- def service_instance_name(cls):
- return association_proxy('service_instance', cls.name_column_name())
-
- def to_dict(self, suppress_error=False, **kwargs):
- dep_update_dict = super(ServiceInstanceUpdateBase, self).to_dict(suppress_error) #pylint: disable=no-member
- # Taking care of the fact the DeploymentSteps are _BaseModels
- dep_update_dict['steps'] = [step.to_dict() for step in self.steps]
- return dep_update_dict
-
-
-class ServiceInstanceUpdateStepBase(ModelMixin):
- """
- Deployment update step model representation.
- """
- # Needed only for pylint. the id will be populated by sqlalcehmy and the proper column.
- __tablename__ = 'service_instance_update_step'
- __private_fields__ = ['service_instance_update_fk']
-
- _action_types = namedtuple('ACTION_TYPES', 'ADD, REMOVE, MODIFY')
- ACTION_TYPES = _action_types(ADD='add', REMOVE='remove', MODIFY='modify')
- _entity_types = namedtuple(
- 'ENTITY_TYPES',
- 'NODE, RELATIONSHIP, PROPERTY, OPERATION, WORKFLOW, OUTPUT, DESCRIPTION, GROUP, '
- 'POLICY_TYPE, POLICY_TRIGGER, PLUGIN')
- ENTITY_TYPES = _entity_types(
- NODE='node',
- RELATIONSHIP='relationship',
- PROPERTY='property',
- OPERATION='operation',
- WORKFLOW='workflow',
- OUTPUT='output',
- DESCRIPTION='description',
- GROUP='group',
- POLICY_TYPE='policy_type',
- POLICY_TRIGGER='policy_trigger',
- PLUGIN='plugin'
- )
-
- action = Column(Enum(*ACTION_TYPES, name='action_type'), nullable=False)
- entity_id = Column(Text, nullable=False)
- entity_type = Column(Enum(*ENTITY_TYPES, name='entity_type'), nullable=False)
-
- @declared_attr
- def service_instance_update_fk(cls):
- return cls.foreign_key('service_instance_update')
-
- @declared_attr
- def service_instance_update(cls):
- return cls.many_to_one_relationship('service_instance_update',
- backreference='steps')
-
- @declared_attr
- def deployment_update_name(cls):
- return association_proxy('deployment_update', cls.name_column_name())
-
- def __hash__(self):
- return hash((getattr(self, self.id_column_name()), self.entity_id))
-
- def __lt__(self, other):
- """
- the order is 'remove' < 'modify' < 'add'
- :param other:
- :return:
- """
- if not isinstance(other, self.__class__):
- return not self >= other
-
- if self.action != other.action:
- if self.action == 'remove':
- return_value = True
- elif self.action == 'add':
- return_value = False
- else:
- return_value = other.action == 'add'
- return return_value
-
- if self.action == 'add':
- return self.entity_type == 'node' and other.entity_type == 'relationship'
- if self.action == 'remove':
- return self.entity_type == 'relationship' and other.entity_type == 'node'
- return False
-
-
-class ServiceInstanceModificationBase(ModelMixin):
- """
- Deployment modification model representation.
- """
- __tablename__ = 'service_instance_modification'
- __private_fields__ = ['service_instance_fk']
-
- STARTED = 'started'
- FINISHED = 'finished'
- ROLLEDBACK = 'rolledback'
-
- STATES = [STARTED, FINISHED, ROLLEDBACK]
- END_STATES = [FINISHED, ROLLEDBACK]
-
- 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'))
-
- @declared_attr
- def service_instance_fk(cls):
- return cls.foreign_key('service_instance')
-
- @declared_attr
- def service_instance(cls):
- return cls.many_to_one_relationship('service_instance',
- backreference='modifications')
-
- @declared_attr
- def service_instance_name(cls):
- return association_proxy('service_instance', cls.name_column_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'
- __private_fields__ = ['node_fk',
- 'relationship_fk',
- 'execution_fk',
- 'plugin_fk']
-
- @declared_attr
- def node_fk(cls):
- return cls.foreign_key('node', nullable=True)
-
- @declared_attr
- def node_name(cls):
- return association_proxy('node', cls.name_column_name())
-
- @declared_attr
- def node(cls):
- return cls.many_to_one_relationship('node')
-
- @declared_attr
- def relationship_fk(cls):
- return cls.foreign_key('relationship', nullable=True)
-
- @declared_attr
- def relationship_name(cls):
- return association_proxy('relationships', cls.name_column_name())
-
- @declared_attr
- def relationship(cls):
- return cls.many_to_one_relationship('relationship')
-
- @declared_attr
- def plugin_fk(cls):
- return cls.foreign_key('plugin', nullable=True)
-
- @declared_attr
- def plugin(cls):
- return cls.many_to_one_relationship('plugin')
-
- @declared_attr
- def execution_fk(cls):
- return cls.foreign_key('execution', nullable=True)
-
- @declared_attr
- def execution(cls):
- return cls.many_to_one_relationship('execution')
-
- @declared_attr
- def execution_name(cls):
- return association_proxy('execution', cls.name_column_name())
-
- 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_INSTANCE = 'node_instance'
- RUNS_ON = (RUNS_ON_NODE_INSTANCE, 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
-
- status = Column(Enum(*STATES, name='status'), default=PENDING)
-
- due_at = Column(DateTime, 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)
- inputs = Column(Dict)
- # This is unrelated to the plugin of the task. This field is related to the plugin name
- # received from the blueprint.
- plugin_name = 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_INSTANCE:
- 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
-
- @classmethod
- def as_node_instance(cls, instance, runs_on, **kwargs):
- return cls(node=instance, _runs_on=runs_on, **kwargs)
-
- @classmethod
- def as_relationship_instance(cls, instance, runs_on, **kwargs):
- return cls(relationship=instance, _runs_on=runs_on, **kwargs)
-
- @staticmethod
- def abort(message=None):
- raise TaskAbortException(message)
-
- @staticmethod
- def retry(message=None, retry_interval=None):
- raise TaskRetryException(message, retry_interval=retry_interval)
[2/4] incubator-ariatosca git commit: Modeling refactoring;
improved metadata; work on instance models
Posted by em...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/service_template_models.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template_models.py b/aria/modeling/service_template_models.py
new file mode 100644
index 0000000..9055c66
--- /dev/null
+++ b/aria/modeling/service_template_models.py
@@ -0,0 +1,1423 @@
+# 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 copy import deepcopy
+from types import FunctionType
+
+from sqlalchemy import (
+ Column,
+ Text,
+ Integer,
+ DateTime,
+ Boolean,
+)
+from sqlalchemy.ext.associationproxy import association_proxy
+from sqlalchemy.ext.declarative import declared_attr
+
+from ..parser import validation
+from ..utils import collections, formatting, console
+from . import (
+ utils,
+ structure,
+ type as aria_type
+)
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+
+
+class ServiceTemplateBase(structure.TemplateModelMixin):
+ """
+ A service template is a normalized blueprint from which :class:`ServiceInstance` instances can
+ be created.
+
+ It is usually created by various DSL parsers, such as ARIA's TOSCA extension. However, it
+ can also be created programmatically.
+
+ Properties:
+
+ * :code:`description`: Human-readable description
+ * :code:`meta_data`: Dict of :class:`Metadata`
+ * :code:`node_templates`: List of :class:`NodeTemplate`
+ * :code:`group_templates`: List of :class:`GroupTemplate`
+ * :code:`policy_templates`: List of :class:`PolicyTemplate`
+ * :code:`substitution_template`: :class:`SubstituionTemplate`
+ * :code:`inputs`: Dict of :class:`Parameter`
+ * :code:`outputs`: Dict of :class:`Parameter`
+ * :code:`operation_templates`: Dict of :class:`OperationTemplate`
+ """
+
+ __tablename__ = 'service_template'
+
+ __private_fields__ = ['substitution_template_fk']
+
+ description = Column(Text)
+
+ # region orchestrator required columns
+
+ created_at = Column(DateTime, nullable=False, index=True)
+ main_file_name = Column(Text)
+ plan = Column(aria_type.Dict, nullable=False)
+ updated_at = Column(DateTime)
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def substitution_template_fk(cls):
+ return cls.foreign_key('substitution_template', nullable=True)
+
+ # endregion
+
+ # region one-to-one relationships
+
+ @declared_attr
+ def substitution_template(cls):
+ return cls.one_to_one_relationship('substitution_template')
+
+ # endregion
+
+ # region one-to-many relationships
+
+ @declared_attr
+ def operation_templates(cls):
+ return cls.one_to_many_relationship('operation_template', key_column_name='name')
+
+ # endregion
+
+ # region many-to-many relationships
+
+ @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')
+
+ @declared_attr
+ def inputs(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='inputs',
+ key_column_name='name')
+
+ @declared_attr
+ def outputs(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='outputs',
+ key_column_name='name')
+
+ # endregion
+
+ @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))))
+
+ def instantiate(self, context, container):
+ from . import model
+ service_instance = model.ServiceInstance()
+ context.modeling.instance = service_instance
+
+ service_instance.description = deepcopy_with_locators(self.description)
+
+ utils.instantiate_dict(context, self, service_instance.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_instance.nodes[node.id] = node
+
+ utils.instantiate_list(context, self, service_instance.groups, self.group_templates)
+ utils.instantiate_list(context, self, service_instance.policies, self.policy_templates)
+ utils.instantiate_dict(context, self, service_instance.operations, self.operation_templates)
+
+ if self.substitution_template is not None:
+ service_instance.substitution = self.substitution_template.instantiate(context,
+ container)
+
+ utils.instantiate_dict(context, self, service_instance.inputs, self.inputs)
+ utils.instantiate_dict(context, self, service_instance.outputs, self.outputs)
+
+ for name, the_input in context.modeling.inputs.iteritems():
+ if name not in service_instance.inputs:
+ context.validation.report('input "%s" is not supported' % name)
+ else:
+ service_instance.inputs[name].value = the_input
+
+ return service_instance
+
+ 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)
+
+ 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))
+ dump_parameters(context, self.meta_data, 'Metadata')
+ for node_template in self.node_templates.all():
+ node_template.dump(context)
+ for group_template in self.group_templates.all():
+ group_template.dump(context)
+ for policy_template in self.policy_templates.all():
+ policy_template.dump(context)
+ if self.substitution_template is not None:
+ self.substitution_template.dump(context)
+ dump_parameters(context, self.inputs, 'Inputs')
+ dump_parameters(context, self.outputs, 'Outputs')
+ utils.dump_dict_values(context, self.operation_templates, 'Operation templates')
+
+
+class InterfaceTemplateBase(structure.TemplateModelMixin):
+ """
+ A typed set of :class:`OperationTemplate`.
+
+ Properties:
+
+ * :code:`name`: Name
+ * :code:`description`: Description
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`inputs`: Dict of :class:`Parameter`
+ * :code:`operation_templates`: Dict of :class:`OperationTemplate`
+ """
+
+ __tablename__ = 'interface_template'
+
+ __private_fields__ = ['node_template_fk',
+ 'group_template_fk',
+ 'relationship_template_fk']
+
+ # region foreign keys
+
+ @declared_attr
+ def node_template_fk(cls):
+ return cls.foreign_key('node_template', nullable=True)
+
+ @declared_attr
+ def group_template_fk(cls):
+ return cls.foreign_key('group_template', nullable=True)
+
+ @declared_attr
+ def relationship_template_fk(cls):
+ return cls.foreign_key('relationship_template', nullable=True)
+
+ # endregion
+
+ description = Column(Text)
+ type_name = Column(Text)
+
+ # region one-to-many relationships
+
+ @declared_attr
+ def operation_templates(cls):
+ return cls.one_to_many_relationship('operation_template', key_column_name='name')
+
+ # endregion
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def inputs(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='inputs',
+ key_column_name='name')
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type_name),
+ ('inputs', formatting.as_raw_dict(self.properties)), # pylint: disable=no-member
+ # TODO fix self.properties reference
+ ('operation_templates', formatting.as_raw_list(self.operation_templates))))
+
+ def instantiate(self, context, container):
+ from . import model
+ interface = model.Interface(name=self.name,
+ type_name=self.type_name)
+ interface.description = deepcopy_with_locators(self.description)
+ utils.instantiate_dict(context, container, interface.inputs, self.inputs)
+ utils.instantiate_dict(context, container, interface.operations, self.operation_templates)
+ return interface
+
+ def validate(self, context):
+ if self.type_name:
+ if context.modeling.interface_types.get_descendant(self.type_name) is None:
+ context.validation.report('interface "%s" has an unknown type: %s'
+ % (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.operation_templates)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, container, self.inputs, report_issues)
+ utils.coerce_dict_values(context, container, self.operation_templates, report_issues)
+
+ def dump(self, context):
+ console.puts(context.style.node(self.name))
+ if self.description:
+ console.puts(context.style.meta(self.description))
+ with context.style.indent:
+ console.puts('Interface type: %s' % context.style.type(self.type_name))
+ dump_parameters(context, self.inputs, 'Inputs')
+ utils.dump_dict_values(context, self.operation_templates, 'Operation templates')
+
+
+class OperationTemplateBase(structure.TemplateModelMixin):
+ """
+ An operation in a :class:`InterfaceTemplate`.
+
+ Properties:
+
+ * :code:`name`: Name
+ * :code:`description`: Description
+ * :code:`implementation`: Implementation string (interpreted by the orchestrator)
+ * :code:`dependencies`: List of strings (interpreted by the orchestrator)
+ * :code:`executor`: Executor string (interpreted by the orchestrator)
+ * :code:`max_retries`: Maximum number of retries allowed in case of failure
+ * :code:`retry_interval`: Interval between retries
+ * :code:`inputs`: Dict of :class:`Parameter`
+ """
+
+ __tablename__ = 'operation_template'
+
+ __private_fields__ = ['service_template_fk',
+ 'interface_template_fk']
+
+ # region foreign keys
+
+ @declared_attr
+ def service_template_fk(cls):
+ return cls.foreign_key('service_template', nullable=True)
+
+ @declared_attr
+ def interface_template_fk(cls):
+ return cls.foreign_key('interface_template', nullable=True)
+
+ # endregion
+
+ description = Column(Text)
+ implementation = Column(Text)
+ dependencies = Column(aria_type.StrictList(item_cls=basestring))
+ executor = Column(Text)
+ max_retries = Column(Integer)
+ retry_interval = Column(Integer)
+
+ # region orchestrator required columns
+
+ plugin = Column(Text)
+ operation = Column(Boolean)
+
+ # endregion
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def inputs(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='inputs',
+ key_column_name='name')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('implementation', self.implementation),
+ ('dependencies', self.dependencies),
+ ('executor', self.executor),
+ ('max_retries', self.max_retries),
+ ('retry_interval', self.retry_interval),
+ ('inputs', formatting.as_raw_dict(self.inputs))))
+
+ def instantiate(self, context, container):
+ from . import model
+ operation = model.Operation()
+ operation.description = deepcopy_with_locators(self.description)
+ operation.implementation = self.implementation
+ operation.dependencies = self.dependencies
+ operation.executor = self.executor
+ operation.max_retries = self.max_retries
+ operation.retry_interval = self.retry_interval
+ utils.instantiate_dict(context, container, operation.inputs, self.inputs)
+ return operation
+
+ def validate(self, context):
+ utils.validate_dict_values(context, self.inputs)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, container, self.inputs, report_issues)
+
+ def dump(self, context):
+ console.puts(context.style.node(self.name))
+ if self.description:
+ console.puts(context.style.meta(self.description))
+ with context.style.indent:
+ if self.implementation is not None:
+ console.puts('Implementation: %s' % context.style.literal(self.implementation))
+ if self.dependencies:
+ console.puts('Dependencies: %s' % ', '.join(
+ (str(context.style.literal(v)) for v in self.dependencies)))
+ if self.executor is not None:
+ console.puts('Executor: %s' % context.style.literal(self.executor))
+ if self.max_retries is not None:
+ console.puts('Max retries: %s' % context.style.literal(self.max_retries))
+ if self.retry_interval is not None:
+ console.puts('Retry interval: %s' % context.style.literal(self.retry_interval))
+ dump_parameters(context, self.inputs, 'Inputs')
+
+
+class ArtifactTemplateBase(structure.TemplateModelMixin):
+ """
+ A file associated with a :class:`NodeTemplate`.
+
+ Properties:
+
+ * :code:`name`: Name
+ * :code:`description`: Description
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`source_path`: Source path (CSAR or repository)
+ * :code:`target_path`: Path at destination machine
+ * :code:`repository_url`: Repository URL
+ * :code:`repository_credential`: Dict of string
+ * :code:`properties`: Dict of :class:`Parameter`
+ """
+
+ __tablename__ = 'artifact_template'
+
+ __private_fields__ = ['node_template_fk']
+
+ # region foreign keys
+
+ @declared_attr
+ def node_template_fk(cls):
+ return cls.foreign_key('node_template')
+
+ # endregion
+
+ description = Column(Text)
+ type_name = Column(Text)
+ source_path = Column(Text)
+ target_path = Column(Text)
+ repository_url = Column(Text)
+ repository_credential = Column(aria_type.StrictDict(basestring, basestring))
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type_name),
+ ('source_path', self.source_path),
+ ('target_path', self.target_path),
+ ('repository_url', self.repository_url),
+ ('repository_credential', formatting.as_agnostic(self.repository_credential)),
+ ('properties', formatting.as_raw_dict(self.properties.iteritems()))))
+
+ def instantiate(self, context, container):
+ from . import model
+ artifact = model.Artifact(self.name, self.type_name, self.source_path)
+ artifact.description = deepcopy_with_locators(self.description)
+ artifact.target_path = self.target_path
+ artifact.repository_url = self.repository_url
+ artifact.repository_credential = self.repository_credential
+ utils.instantiate_dict(context, container, artifact.properties, self.properties)
+ return artifact
+
+ def validate(self, context):
+ if context.modeling.artifact_types.get_descendant(self.type_name) is None:
+ context.validation.report('artifact "%s" has an unknown type: %s'
+ % (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):
+ utils.coerce_dict_values(context, container, self.properties, report_issues)
+
+ def dump(self, context):
+ console.puts(context.style.node(self.name))
+ if self.description:
+ console.puts(context.style.meta(self.description))
+ with context.style.indent:
+ console.puts('Artifact type: %s' % context.style.type(self.type_name))
+ console.puts('Source path: %s' % context.style.literal(self.source_path))
+ if self.target_path is not None:
+ console.puts('Target path: %s' % context.style.literal(self.target_path))
+ if self.repository_url is not None:
+ console.puts('Repository URL: %s' % context.style.literal(self.repository_url))
+ if self.repository_credential:
+ console.puts('Repository credential: %s'
+ % context.style.literal(self.repository_credential))
+ dump_parameters(context, self.properties)
+
+
+class PolicyTemplateBase(structure.TemplateModelMixin):
+ """
+ Policies can be applied to zero or more :class:`NodeTemplate` or :class:`GroupTemplate`
+ instances.
+
+ Properties:
+
+ * :code:`name`: Name
+ * :code:`description`: Description
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`properties`: Dict of :class:`Parameter`
+ * :code:`target_node_template_names`: Must be represented in the :class:`ServiceTemplate`
+ * :code:`target_group_template_names`: Must be represented in the :class:`ServiceTemplate`
+ """
+ __tablename__ = 'policy_template'
+
+ __private_fields__ = ['service_template_fk']
+
+ # region foreign keys
+
+ @declared_attr
+ def service_template_fk(cls):
+ return cls.foreign_key('service_template')
+
+ # endregion
+
+ description = Column(Text)
+ type_name = Column(Text)
+ target_node_template_names = Column(aria_type.StrictList(basestring))
+ target_group_template_names = Column(aria_type.StrictList(basestring))
+
+ # region many-to-one relationships
+
+ @declared_attr
+ def service_template(cls):
+ return cls.many_to_one_relationship('service_template')
+
+ # endregion
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type_name),
+ ('properties', formatting.as_raw_dict(self.properties)),
+ ('target_node_template_names', self.target_node_template_names),
+ ('target_group_template_names', self.target_group_template_names)))
+
+ def instantiate(self, context, *args, **kwargs):
+ from . import model
+ policy = model.Policy(self.name, self.type_name)
+ utils.instantiate_dict(context, self, policy.properties, self.properties)
+ for node_template_name in self.target_node_template_names:
+ policy.target_node_ids.extend(
+ context.modeling.instance.get_node_ids(node_template_name))
+ for group_template_name in self.target_group_template_names:
+ policy.target_group_ids.extend(
+ context.modeling.instance.get_group_ids(group_template_name))
+ return policy
+
+ def validate(self, context):
+ if context.modeling.policy_types.get_descendant(self.type_name) is None:
+ context.validation.report('policy template "%s" has an unknown type: %s'
+ % (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):
+ utils.coerce_dict_values(context, self, self.properties, report_issues)
+
+ def dump(self, context):
+ console.puts('Policy template: %s' % context.style.node(self.name))
+ if self.description:
+ console.puts(context.style.meta(self.description))
+ with context.style.indent:
+ console.puts('Type: %s' % context.style.type(self.type_name))
+ dump_parameters(context, self.properties)
+ if self.target_node_template_names:
+ console.puts('Target node templates: %s' % ', '.join(
+ (str(context.style.node(v)) for v in self.target_node_template_names)))
+ if self.target_group_template_names:
+ console.puts('Target group templates: %s' % ', '.join(
+ (str(context.style.node(v)) for v in self.target_group_template_names)))
+
+
+class MappingTemplateBase(structure.TemplateModelMixin):
+ """
+ Used by :class:`SubstitutionTemplate` to map a capability or a requirement to a node.
+
+ Properties:
+
+ * :code:`mapped_name`: Exposed capability or requirement name
+ * :code:`node_template_name`: Must be represented in the :class:`ServiceTemplate`
+ * :code:`name`: Name of capability or requirement at the node template
+ """
+
+ __tablename__ = 'mapping_template'
+
+ __private_fields__ = ['substitution_template_fk']
+
+ mapped_name = Column(Text)
+ node_template_name = Column(Text)
+
+ # region foreign keys
+
+ @declared_attr
+ def substitution_template_fk(cls):
+ return cls.foreign_key('substitution_template', nullable=True)
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('mapped_name', self.mapped_name),
+ ('node_template_name', self.node_template_name),
+ ('name', self.name)))
+
+ def instantiate(self, context, *args, **kwargs):
+ from . import model
+ nodes = context.modeling.instance.find_nodes(self.node_template_name)
+ if len(nodes) == 0:
+ context.validation.report(
+ 'mapping "%s" refer to node template "%s" but there are no '
+ 'node instances' % (self.mapped_name,
+ self.node_template_name),
+ level=validation.Issue.BETWEEN_INSTANCES)
+ return None
+ return model.Mapping(mapped_name=self.mapped_name,
+ node_id=nodes[0].id,
+ name=self.name)
+
+ def validate(self, context):
+ if not utils.query_has_item_named(context.modeling.model.node_templates,
+ self.node_template_name):
+ context.validation.report('mapping "%s" refers to an unknown node template: %s'
+ % (
+ self.mapped_name,
+ formatting.safe_repr(self.node_template_name)),
+ level=validation.Issue.BETWEEN_TYPES)
+
+ def dump(self, context):
+ console.puts('%s -> %s.%s' % (context.style.node(self.mapped_name),
+ context.style.node(self.node_template_name),
+ context.style.node(self.name)))
+
+
+class SubstitutionTemplateBase(structure.TemplateModelMixin):
+ """
+ Used to substitute a single node for the entire deployment.
+
+ Properties:
+
+ * :code:`node_type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`capability_templates`: Dict of :class:`MappingTemplate`
+ * :code:`requirement_templates`: Dict of :class:`MappingTemplate`
+ """
+
+ __tablename__ = 'substitution_template'
+
+ node_type_name = Column(Text)
+
+ # region one-to-many relationships
+
+ @declared_attr
+ def mappings(cls):
+ return cls.one_to_many_relationship('mapping_template', key_column_name='name')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('node_type_name', self.node_type_name),
+ ('mappings', formatting.as_raw_list(self.mappings))))
+
+ def instantiate(self, context, container):
+ from . import model
+ substitution = model.Substitution(self.node_type_name)
+ utils.instantiate_dict(context, container, substitution.mappings,
+ self.mappings)
+ return substitution
+
+ def validate(self, context):
+ if context.modeling.node_types.get_descendant(self.node_type_name) is None:
+ context.validation.report('substitution template has an unknown type: %s'
+ % 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):
+ utils.coerce_dict_values(context, self, self.mappings, report_issues)
+
+ def dump(self, context):
+ console.puts('Substitution template:')
+ with context.style.indent:
+ console.puts('Node type: %s' % context.style.type(self.node_type_name))
+ utils.dump_dict_values(context, self.mappings,
+ 'Mappings')
+
+
+class NodeTemplateBase(structure.TemplateModelMixin):
+ """
+ A template for creating zero or more :class:`Node` instances.
+
+ Properties:
+
+ * :code:`name`: Name (will be used as a prefix for node IDs)
+ * :code:`description`: Description
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`default_instances`: Default number nodes that will appear in the deployment plan
+ * :code:`min_instances`: Minimum number nodes that will appear in the deployment plan
+ * :code:`max_instances`: Maximum number nodes that will appear in the deployment plan
+ * :code:`properties`: Dict of :class:`Parameter`
+ * :code:`interface_templates`: Dict of :class:`InterfaceTemplate`
+ * :code:`artifact_templates`: Dict of :class:`ArtifactTemplate`
+ * :code:`capability_templates`: Dict of :class:`CapabilityTemplate`
+ * :code:`requirement_templates`: List of :class:`RequirementTemplate`
+ * :code:`target_node_template_constraints`: List of :class:`FunctionType`
+ """
+
+ __tablename__ = 'node_template'
+
+ __private_fields__ = ['service_template_fk',
+ 'host_fk']
+
+ # region foreign_keys
+
+ @declared_attr
+ def service_template_fk(cls):
+ return cls.foreign_key('service_template')
+
+ @declared_attr
+ def host_fk(cls):
+ return cls.foreign_key('node_template', nullable=True)
+
+ # endregion
+
+ description = Column(Text)
+ type_name = Column(Text)
+ default_instances = Column(Integer, default=1)
+ min_instances = Column(Integer, default=0)
+ max_instances = Column(Integer, default=None)
+ target_node_template_constraints = Column(aria_type.StrictList(FunctionType))
+
+ # region orchestrator required columns
+
+ plugins = Column(aria_type.List)
+ type_hierarchy = Column(aria_type.List)
+
+ @declared_attr
+ def host(cls):
+ return cls.relationship_to_self('host_fk')
+
+ @declared_attr
+ def service_template_name(cls):
+ return association_proxy('service_template', cls.name_column_name())
+
+ # endregion
+
+ # region many-to-one relationships
+
+ @declared_attr
+ def service_template(cls):
+ return cls.many_to_one_relationship('service_template')
+
+ # endregion
+
+ # region one-to-many relationships
+
+ @declared_attr
+ def interface_templates(cls):
+ return cls.one_to_many_relationship('interface_template', key_column_name='name')
+
+ @declared_attr
+ def artifact_templates(cls):
+ return cls.one_to_many_relationship('artifact_template', key_column_name='name')
+
+ @declared_attr
+ def capability_templates(cls):
+ return cls.one_to_many_relationship('capability_template', key_column_name='name')
+
+ # endregion
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ # endregion
+
+ def is_target_node_valid(self, target_node_template):
+ if self.target_node_template_constraints:
+ for node_type_constraint in self.target_node_template_constraints:
+ if not node_type_constraint(target_node_template, self):
+ return False
+ return True
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type_name),
+ ('default_instances', self.default_instances),
+ ('min_instances', self.min_instances),
+ ('max_instances', self.max_instances),
+ ('properties', formatting.as_raw_dict(self.properties)),
+ ('interface_templates', formatting.as_raw_list(self.interface_templates)),
+ ('artifact_templates', formatting.as_raw_list(self.artifact_templates)),
+ ('capability_templates', formatting.as_raw_list(self.capability_templates)),
+ ('requirement_templates', formatting.as_raw_list(self.requirement_templates))))
+
+ def instantiate(self, context, *args, **kwargs):
+ from . import model
+ node = model.Node(name=self.name,
+ type_name=self.type_name)
+ utils.instantiate_dict(context, node, node.properties, self.properties)
+ utils.instantiate_dict(context, node, node.interfaces, self.interface_templates)
+ utils.instantiate_dict(context, node, node.artifacts, self.artifact_templates)
+ utils.instantiate_dict(context, node, node.capabilities, self.capability_templates)
+ return node
+
+ def validate(self, context):
+ if context.modeling.node_types.get_descendant(self.type_name) is None:
+ context.validation.report('node template "%s" has an unknown type: %s'
+ % (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.interface_templates)
+ utils.validate_dict_values(context, self.artifact_templates)
+ utils.validate_dict_values(context, self.capability_templates)
+ utils.validate_list_values(context, self.requirement_templates)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, self, self.properties, report_issues)
+ utils.coerce_dict_values(context, self, self.interface_templates, report_issues)
+ utils.coerce_dict_values(context, self, self.artifact_templates, report_issues)
+ utils.coerce_dict_values(context, self, self.capability_templates, report_issues)
+ utils.coerce_list_values(context, self, self.requirement_templates, report_issues)
+
+ def dump(self, context):
+ console.puts('Node template: %s' % context.style.node(self.name))
+ if self.description:
+ console.puts(context.style.meta(self.description))
+ with context.style.indent:
+ console.puts('Type: %s' % context.style.type(self.type_name))
+ console.puts('Instances: %d (%d%s)'
+ % (self.default_instances,
+ self.min_instances,
+ (' to %d' % self.max_instances
+ if self.max_instances is not None
+ else ' or more')))
+ dump_parameters(context, self.properties)
+ utils.dump_interfaces(context, self.interface_templates)
+ utils.dump_dict_values(context, self.artifact_templates, 'Artifact tempaltes')
+ utils.dump_dict_values(context, self.capability_templates, 'Capability templates')
+ utils.dump_list_values(context, self.requirement_templates, 'Requirement templates')
+
+
+class GroupTemplateBase(structure.TemplateModelMixin):
+ """
+ A template for creating zero or more :class:`Group` instances.
+
+ Groups are logical containers for zero or more nodes that allow applying zero or more
+ :class:`GroupPolicy` instances to the nodes together.
+
+ Properties:
+
+ * :code:`name`: Name (will be used as a prefix for group IDs)
+ * :code:`description`: Description
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`properties`: Dict of :class:`Parameter`
+ * :code:`interface_templates`: Dict of :class:`InterfaceTemplate`
+ * :code:`policy_templates`: Dict of :class:`GroupPolicyTemplate`
+ * :code:`member_node_template_names`: Must be represented in the :class:`ServiceTemplate`
+ * :code:`member_group_template_names`: Must be represented in the :class:`ServiceTemplate`
+ """
+
+ __tablename__ = 'group_template'
+
+ __private_fields__ = ['service_template_fk']
+
+ # region foreign keys
+
+ @declared_attr
+ def service_template_fk(cls):
+ return cls.foreign_key('service_template')
+
+ # endregion
+
+ description = Column(Text)
+ type_name = Column(Text)
+ member_node_template_names = Column(aria_type.StrictList(basestring))
+ member_group_template_names = Column(aria_type.StrictList(basestring))
+
+ # region many-to-one relationships
+
+ @declared_attr
+ def service_template(cls):
+ return cls.many_to_one_relationship('service_template')
+
+ # endregion
+
+ # region one-to-many relationships
+
+ @declared_attr
+ def interface_templates(cls):
+ return cls.one_to_many_relationship('interface_template', key_column_name='name')
+
+ # endregion
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type_name),
+ ('properties', formatting.as_raw_dict(self.properties)),
+ ('interface_templates', formatting.as_raw_list(self.interface_templates)),
+ ('member_node_template_names', self.member_node_template_names),
+ ('member_group_template_names', self.member_group_template_names1)))
+
+ def instantiate(self, context, *args, **kwargs):
+ from . import model
+ group = model.Group(context, self.type_name, self.name)
+ utils.instantiate_dict(context, self, group.properties, self.properties)
+ utils.instantiate_dict(context, self, group.interfaces, self.interface_templates)
+ utils.instantiate_dict(context, self, group.policies, self.policy_templates)
+ for member_node_template_name in self.member_node_template_names:
+ group.member_node_ids += \
+ context.modeling.instance.get_node_ids(member_node_template_name)
+ for member_group_template_name in self.member_group_template_names:
+ group.member_group_ids += \
+ context.modeling.instance.get_group_ids(member_group_template_name)
+ return group
+
+ def validate(self, context):
+ if context.modeling.group_types.get_descendant(self.type_name) is None:
+ context.validation.report('group template "%s" has an unknown type: %s'
+ % (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.interface_templates)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, self, self.properties, report_issues)
+ utils.coerce_dict_values(context, self, self.interface_templates, report_issues)
+
+ def dump(self, context):
+ console.puts('Group template: %s' % context.style.node(self.name))
+ if self.description:
+ console.puts(context.style.meta(self.description))
+ with context.style.indent:
+ if self.type_name:
+ console.puts('Type: %s' % context.style.type(self.type_name))
+ dump_parameters(context, self.properties)
+ utils.dump_interfaces(context, self.interface_templates)
+ if self.member_node_template_names:
+ console.puts('Member node templates: %s' % ', '.join(
+ (str(context.style.node(v)) for v in self.member_node_template_names)))
+
+
+class RequirementTemplateBase(structure.TemplateModelMixin):
+ """
+ A requirement for a :class:`NodeTemplate`. During instantiation will be matched with a
+ capability of another
+ node.
+
+ Requirements may optionally contain a :class:`RelationshipTemplate` that will be created between
+ the nodes.
+
+ Properties:
+
+ * :code:`name`: Name
+ * :code:`target_node_type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`target_node_template_name`: Must be represented in the :class:`ServiceTemplate`
+ * :code:`target_node_template_constraints`: List of :class:`FunctionType`
+ * :code:`target_capability_type_name`: Type of capability in target node
+ * :code:`target_capability_name`: Name of capability in target node
+ * :code:`relationship_template`: :class:`RelationshipTemplate`
+ """
+
+ __tablename__ = 'requirement_template'
+
+ __private_fields__ = ['node_template_fk']
+
+ # region foreign keys
+
+ @declared_attr
+ def node_template_fk(cls):
+ return cls.foreign_key('node_template', nullable=True)
+
+ # endregion
+
+ target_node_type_name = Column(Text)
+ target_node_template_name = Column(Text)
+ target_node_template_constraints = Column(aria_type.StrictList(FunctionType))
+ target_capability_type_name = Column(Text)
+ target_capability_name = Column(Text)
+ # CHECK: ???
+ relationship_template = Column(Text) # optional
+
+ # region many-to-one relationships
+
+ @declared_attr
+ def node_template(cls):
+ return cls.many_to_one_relationship('node_template')
+
+ # endregion
+
+ def instantiate(self, context, container):
+ raise NotImplementedError
+
+ def find_target(self, context, source_node_template):
+ # We might already have a specific node template, so we'll just verify it
+ if self.target_node_template_name is not None:
+ target_node_template = \
+ context.modeling.model.node_templates.get(self.target_node_template_name)
+
+ if not source_node_template.is_target_node_valid(target_node_template):
+ context.validation.report('requirement "%s" of node template "%s" is for node '
+ 'template "%s" but it does not match constraints'
+ % (self.name,
+ self.target_node_template_name,
+ source_node_template.name),
+ level=validation.Issue.BETWEEN_TYPES)
+ return None, None
+
+ if self.target_capability_type_name is not None \
+ or self.target_capability_name is not None:
+ target_node_capability = self.find_target_capability(context,
+ source_node_template,
+ target_node_template)
+ if target_node_capability is None:
+ return None, None
+ else:
+ target_node_capability = None
+
+ return target_node_template, target_node_capability
+
+ # Find first node that matches the type
+ elif self.target_node_type_name is not None:
+ for target_node_template in context.modeling.model.node_templates.itervalues():
+ if not context.modeling.node_types.is_descendant(self.target_node_type_name,
+ target_node_template.type_name):
+ continue
+
+ if not source_node_template.is_target_node_valid(target_node_template):
+ continue
+
+ target_node_capability = self.find_target_capability(context,
+ source_node_template,
+ target_node_template)
+ if target_node_capability is None:
+ continue
+
+ return target_node_template, target_node_capability
+
+ return None, None
+
+ def find_target_capability(self, context, source_node_template, target_node_template):
+ for capability_template in target_node_template.capability_templates.itervalues():
+ if capability_template.satisfies_requirement(context,
+ source_node_template,
+ self,
+ target_node_template):
+ return capability_template
+ return None
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('target_node_type_name', self.target_node_type_name),
+ ('target_node_template_name', self.target_node_template_name),
+ ('target_capability_type_name', self.target_capability_type_name),
+ ('target_capability_name', self.target_capability_name),
+ ('relationship_template', formatting.as_raw(self.relationship_template))))
+
+ def validate(self, context):
+ node_types = context.modeling.node_types
+ capability_types = context.modeling.capability_types
+ if self.target_node_type_name \
+ and node_types.get_descendant(self.target_node_type_name) is None:
+ context.validation.report('requirement "%s" refers to an unknown node type: %s'
+ % (self.name,
+ formatting.safe_repr(self.target_node_type_name)),
+ level=validation.Issue.BETWEEN_TYPES)
+ if self.target_capability_type_name and \
+ capability_types.get_descendant(self.target_capability_type_name is None):
+ context.validation.report('requirement "%s" refers to an unknown capability type: %s'
+ % (self.name,
+ formatting.safe_repr(self.target_capability_type_name)),
+ level=validation.Issue.BETWEEN_TYPES)
+ if self.relationship_template:
+ self.relationship_template.validate(context)
+
+ def coerce_values(self, context, container, report_issues):
+ if self.relationship_template is not None:
+ self.relationship_template.coerce_values(context, container, report_issues)
+
+ def dump(self, context):
+ if self.name:
+ console.puts(context.style.node(self.name))
+ else:
+ console.puts('Requirement:')
+ with context.style.indent:
+ if self.target_node_type_name is not None:
+ console.puts('Target node type: %s'
+ % context.style.type(self.target_node_type_name))
+ elif self.target_node_template_name is not None:
+ console.puts('Target node template: %s'
+ % context.style.node(self.target_node_template_name))
+ if self.target_capability_type_name is not None:
+ console.puts('Target capability type: %s'
+ % context.style.type(self.target_capability_type_name))
+ elif self.target_capability_name is not None:
+ console.puts('Target capability name: %s'
+ % context.style.node(self.target_capability_name))
+ if self.target_node_template_constraints:
+ console.puts('Target node template constraints:')
+ with context.style.indent:
+ for constraint in self.target_node_template_constraints:
+ console.puts(context.style.literal(constraint))
+ if self.relationship_template:
+ console.puts('Relationship:')
+ with context.style.indent:
+ self.relationship_template.dump(context)
+
+
+class CapabilityTemplateBase(structure.TemplateModelMixin):
+ """
+ A capability of a :class:`NodeTemplate`. Nodes expose zero or more capabilities that can be
+ matched with :class:`Requirement` instances of other nodes.
+
+ Properties:
+
+ * :code:`name`: Name
+ * :code:`description`: Description
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`min_occurrences`: Minimum number of requirement matches required
+ * :code:`max_occurrences`: Maximum number of requirement matches allowed
+ * :code:`valid_source_node_type_names`: Must be represented in the :class:`ModelingContext`
+ * :code:`properties`: Dict of :class:`Parameter`
+ """
+
+ __tablename__ = 'capability_template'
+
+ __private_fields__ = ['node_template_fk']
+
+ # region foreign keys
+
+ @declared_attr
+ def node_template_fk(cls):
+ return cls.foreign_key('node_template', nullable=True)
+
+ # endregion
+
+ description = Column(Text)
+ type_name = Column(Text)
+ min_occurrences = Column(Integer, default=None) # optional
+ max_occurrences = Column(Integer, default=None) # optional
+ # CHECK: type?
+ valid_source_node_type_names = Column(Text)
+
+ # region many-to-one relationships
+
+ #@declared_attr
+ #def node_template(cls):
+ # return cls.many_to_one_relationship('node_template')
+
+ # endregion
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ # endregion
+
+ def satisfies_requirement(self,
+ context,
+ source_node_template,
+ requirement,
+ target_node_template):
+ # Do we match the required capability type?
+ capability_types = context.modeling.capability_types
+ if not capability_types.is_descendant(requirement.target_capability_type_name,
+ self.type_name):
+ return False
+
+ # Are we in valid_source_node_type_names?
+ if self.valid_source_node_type_names:
+ for valid_source_node_type_name in self.valid_source_node_type_names:
+ if not context.modeling.node_types.is_descendant(valid_source_node_type_name,
+ source_node_template.type_name):
+ return False
+
+ # Apply requirement constraints
+ if requirement.target_node_template_constraints:
+ for node_type_constraint in requirement.target_node_template_constraints:
+ if not node_type_constraint(target_node_template, source_node_template):
+ return False
+
+ return True
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type_name),
+ ('min_occurrences', self.min_occurrences),
+ ('max_occurrences', self.max_occurrences),
+ ('valid_source_node_type_names', self.valid_source_node_type_names),
+ ('properties', formatting.as_raw_dict(self.properties))))
+
+ def instantiate(self, context, container):
+ from . import model
+ capability = model.Capability(self.name, self.type_name)
+ capability.min_occurrences = self.min_occurrences
+ capability.max_occurrences = self.max_occurrences
+ utils.instantiate_dict(context, container, capability.properties, self.properties)
+ return capability
+
+ def validate(self, context):
+ if context.modeling.capability_types.get_descendant(self.type_name) is None:
+ context.validation.report('capability "%s" refers to an unknown type: %s'
+ % (self.name, formatting.safe_repr(self.type)), # pylint: disable=no-member
+ # TODO fix self.type reference
+ level=validation.Issue.BETWEEN_TYPES)
+
+ utils.validate_dict_values(context, self.properties)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, self, self.properties, report_issues)
+
+ def dump(self, context):
+ console.puts(context.style.node(self.name))
+ if self.description:
+ console.puts(context.style.meta(self.description))
+ with context.style.indent:
+ console.puts('Type: %s' % context.style.type(self.type_name))
+ console.puts(
+ 'Occurrences: %d%s'
+ % (self.min_occurrences or 0, (' to %d' % self.max_occurrences)
+ if self.max_occurrences is not None else ' or more'))
+ if self.valid_source_node_type_names:
+ console.puts('Valid source node types: %s'
+ % ', '.join((str(context.style.type(v))
+ for v in self.valid_source_node_type_names)))
+ dump_parameters(context, self.properties)
+
+
+class RelationshipTemplateBase(structure.TemplateModelMixin):
+ """
+ Optional addition to a :class:`Requirement` in :class:`NodeTemplate` that can be applied when
+ the requirement is matched with a capability.
+
+ Properties:
+
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`template_name`: Must be represented in the :class:`ServiceTemplate`
+ * :code:`description`: Description
+ * :code:`properties`: Dict of :class:`Parameter`
+ * :code:`source_interface_templates`: Dict of :class:`InterfaceTemplate`
+ * :code:`target_interface_templates`: Dict of :class:`InterfaceTemplate`
+ """
+
+ __tablename__ = 'relationship_template'
+
+ description = Column(Text)
+ type_name = Column(Text)
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ # endregion
+
+ # region one-to-many relationships
+
+ @declared_attr
+ def interface_templates(cls):
+ return cls.one_to_many_relationship('interface_template', key_column_name='name')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('type_name', self.type_name),
+ ('template_name', self.template_name),
+ ('description', self.description),
+ ('properties', formatting.as_raw_dict(self.properties)),
+ ('interface_templates', formatting.as_raw_list(self.interface_templates))))
+
+ def instantiate(self, context, container):
+ from . import model
+ relationship = model.Relationship(name=self.template_name,
+ type_name=self.type_name)
+ utils.instantiate_dict(context, container,
+ relationship.properties, self.properties)
+ utils.instantiate_dict(context, container,
+ relationship.interfaces, self.interface_templates)
+ return relationship
+
+ def validate(self, context):
+ if context.modeling.relationship_types.get_descendant(self.type_name) is None:
+ context.validation.report(
+ 'relationship template "{0}" has an unknown type: {1}'.format(
+ self.name,
+ formatting.safe_repr(self.type_name)), # pylint: disable=no-member
+ # TODO fix self.name reference
+ level=validation.Issue.BETWEEN_TYPES)
+
+ utils.validate_dict_values(context, self.properties)
+ utils.validate_dict_values(context, self.interface_templates)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, self, self.properties, report_issues)
+ utils.coerce_dict_values(context, self, self.interface_templates, report_issues)
+
+ def dump(self, context):
+ if self.type_name is not None:
+ console.puts('Relationship type: {0}'.format(context.style.type(self.type_name)))
+ else:
+ console.puts('Relationship template: {0}'.format(
+ context.style.node(self.template_name)))
+ if self.description:
+ console.puts(context.style.meta(self.description))
+ with context.style.indent:
+ utils.dump_parameters(context, self.properties)
+ utils.dump_interfaces(context, self.interface_templates, 'Interface templates')
+
+# endregion
+
+
+def dump_parameters(context, parameters, name='Properties'):
+ if not parameters:
+ return
+ console.puts('%s:' % name)
+ with context.style.indent:
+ for parameter_name, parameter in parameters.items():
+ if hasattr(parameter, 'type_name') and (parameter.type_name is not None):
+ console.puts('%s = %s (%s)' % (context.style.property(parameter_name),
+ context.style.literal(parameter.value),
+ context.style.type(parameter.type_name)))
+ else:
+ console.puts('%s = %s' % (context.style.property(parameter_name),
+ context.style.literal(parameter.value)))
+ if hasattr(parameter, 'description') and parameter.description:
+ console.puts(context.style.meta(parameter.description))
+
+
+# TODO (left for tal): Move following two methods to some place parser specific
+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)
+ if locator is not None:
+ try:
+ setattr(target, '_locator', locator)
+ except AttributeError:
+ pass
+
+ if isinstance(target, list) and isinstance(source, list):
+ for i, _ in enumerate(target):
+ copy_locators(target[i], source[i])
+ elif isinstance(target, dict) and isinstance(source, dict):
+ for k, v in target.items():
+ copy_locators(v, source[k])
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/shared_service_models.py
----------------------------------------------------------------------
diff --git a/aria/modeling/shared_service_models.py b/aria/modeling/shared_service_models.py
new file mode 100644
index 0000000..c5676b3
--- /dev/null
+++ b/aria/modeling/shared_service_models.py
@@ -0,0 +1,109 @@
+# 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 sqlalchemy import (
+ Column,
+ Text
+)
+
+from ..parser.modeling import utils
+from ..storage import exceptions
+from ..utils.collections import OrderedDict
+
+from . import structure
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+
+
+class ParameterBase(structure.TemplateModelMixin):
+ """
+ Represents a typed value.
+
+ This class is used by both service template and service instance elements.
+ """
+
+ __tablename__ = 'parameter'
+
+ name = Column(Text, nullable=False)
+ type_name = Column(Text, nullable=False)
+
+ # Check: value type
+ str_value = Column(Text)
+ description = Column(Text)
+
+ @property
+ def as_raw(self):
+ return OrderedDict((
+ ('name', self.name),
+ ('type_name', self.type_name),
+ ('value', self.value),
+ ('description', self.description)))
+
+ @property
+ def value(self):
+ if self.type_name is None:
+ return
+ try:
+ if self.type_name.lower() in ('str', 'unicode'):
+ return self.str_value.decode('utf-8')
+ elif self.type_name.lower() == 'int':
+ return int(self.str_value)
+ elif self.type_name.lower() == 'bool':
+ return bool(self.str_value)
+ elif self.type_name.lower() == 'float':
+ return float(self.str_value)
+ else:
+ return self.str_value
+ except ValueError:
+ raise exceptions.StorageError('Trying to cast {0} to {1} failed'.format(self.str_value,
+ self.type))
+
+ @value.setter
+ def value(self, value):
+ self.str_value = unicode(value)
+
+ def instantiate(self, context, container):
+ from . import model
+ return model.Parameter(type_name=self.type_name,
+ str_value=self.str_value,
+ description=self.description)
+
+ def coerce_values(self, context, container, report_issues):
+ if self.str_value is not None:
+ self.str_value = utils.coerce_value(context, container, self.str_value, report_issues)
+
+
+class MetadataBase(structure.TemplateModelMixin):
+ """
+ Custom values associated with the service.
+
+ This class is used by both service template and service instance elements.
+ """
+
+ __tablename__ = 'metadata'
+
+ name = Column(Text, nullable=False)
+ value = Column(Text)
+
+ @property
+ def as_raw(self):
+ return OrderedDict((
+ ('name', self.name),
+ ('value', self.value)))
+
+ def instantiate(self, context, container):
+ from . import model
+ return model.Metadata(name=self.name,
+ value=self.value)
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/structure.py
----------------------------------------------------------------------
diff --git a/aria/modeling/structure.py b/aria/modeling/structure.py
index c13572c..719257a 100644
--- a/aria/modeling/structure.py
+++ b/aria/modeling/structure.py
@@ -45,8 +45,7 @@ class Function(object):
"""
An intrinsic function.
- Serves as a placeholder for a value that should eventually be derived
- by calling the function.
+ Serves as a placeholder for a value that should eventually be derived by calling the function.
"""
@property
@@ -61,40 +60,7 @@ class Function(object):
return self
-class ElementBase(object):
- """
- Base class for :class:`ServiceInstance` elements.
-
- All elements 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 ModelElementBase(ElementBase):
- """
- Base class for :class:`ServiceModel` elements.
-
- All model elements can be instantiated into :class:`ServiceInstance` elements.
- """
-
- def instantiate(self, context, container):
- raise NotImplementedError
-
-
-class ModelMixin(ModelElementBase):
+class ModelMixin(object):
@utils.classproperty
def __modelname__(cls): # pylint: disable=no-self-argument
@@ -209,7 +175,7 @@ class ModelMixin(ModelElementBase):
**relationship_kwargs)
@classmethod
- def many_to_many_relationship(cls, other_table_name, table_prefix, key_column_name=None,
+ def many_to_many_relationship(cls, other_table_name, table_prefix=None, key_column_name=None,
relationship_kwargs=None):
"""Return a many-to-many SQL relationship object
@@ -339,6 +305,39 @@ class ModelMixin(ModelElementBase):
id=getattr(self, self.name_column_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
+
+
class ModelIDMixin(object):
id = Column(Integer, primary_key=True, autoincrement=True)
name = Column(Text, nullable=True, index=True)
[3/4] incubator-ariatosca git commit: Modeling refactoring;
improved metadata; work on instance models
Posted by em...@apache.org.
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/orchestrator_models.py
----------------------------------------------------------------------
diff --git a/aria/modeling/orchestrator_models.py b/aria/modeling/orchestrator_models.py
new file mode 100644
index 0000000..d76c234
--- /dev/null
+++ b/aria/modeling/orchestrator_models.py
@@ -0,0 +1,468 @@
+# 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.models module
+Path: aria.storage.models
+
+models module holds aria's models.
+
+classes:
+ * Field - represents a single field.
+ * IterField - represents an iterable field.
+ * Model - abstract model implementation.
+ * Snapshot - snapshots implementation model.
+ * Deployment - deployment implementation model.
+ * DeploymentUpdateStep - deployment update step implementation model.
+ * DeploymentUpdate - deployment update implementation model.
+ * DeploymentModification - deployment modification implementation model.
+ * Execution - execution implementation model.
+ * Node - node implementation model.
+ * Relationship - relationship implementation model.
+ * NodeInstance - node instance implementation model.
+ * RelationshipInstance - relationship instance implementation model.
+ * Plugin - plugin implementation model.
+"""
+from collections import namedtuple
+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 .type import List, Dict
+from .structure import ModelMixin
+
+__all__ = (
+ 'ServiceInstanceUpdateStepBase',
+ 'ServiceInstanceUpdateBase',
+ 'ServiceInstanceModificationBase',
+ 'Execution',
+ 'PluginBase',
+ 'TaskBase'
+)
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+
+
+class Execution(ModelMixin):
+ """
+ Execution model representation.
+ """
+ # Needed only for pylint. the id will be populated by sqlalcehmy and the proper column.
+ __tablename__ = 'execution'
+
+ __private_fields__ = ['service_instance_fk']
+
+ 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_template(cls):
+ return association_proxy('service_instance', 'service_template')
+
+ @declared_attr
+ def service_instance_fk(cls):
+ return cls.foreign_key('service_instance')
+
+ @declared_attr
+ def service_instance(cls):
+ return cls.many_to_one_relationship('service_instance')
+
+ @declared_attr
+ def service_instance_name(cls):
+ return association_proxy('service_instance', cls.name_column_name())
+
+ @declared_attr
+ def service_template_name(cls):
+ return association_proxy('service_instance', 'service_template_name')
+
+ def __str__(self):
+ return '<{0} id=`{1}` (status={2})>'.format(
+ self.__class__.__name__,
+ getattr(self, self.name_column_name()),
+ self.status
+ )
+
+
+class ServiceInstanceUpdateBase(ModelMixin):
+ """
+ Deployment update model representation.
+ """
+ # Needed only for pylint. the id will be populated by sqlalcehmy and the proper column.
+ steps = None
+
+ __tablename__ = 'service_instance_update'
+ __private_fields__ = ['service_instance_fk',
+ 'execution_fk']
+
+ _private_fields = ['execution_fk', 'deployment_fk']
+
+ created_at = Column(DateTime, nullable=False, index=True)
+ service_instance_plan = Column(Dict, nullable=False)
+ service_instance_update_node_instances = Column(Dict)
+ service_instance_update_service_instance = Column(Dict)
+ service_instance_update_nodes = Column(List)
+ modified_entity_ids = Column(Dict)
+ state = Column(Text)
+
+ @declared_attr
+ def execution_fk(cls):
+ return cls.foreign_key('execution', nullable=True)
+
+ @declared_attr
+ def execution(cls):
+ return cls.many_to_one_relationship('execution')
+
+ @declared_attr
+ def execution_name(cls):
+ return association_proxy('execution', cls.name_column_name())
+
+ @declared_attr
+ def service_instance_fk(cls):
+ return cls.foreign_key('service_instance')
+
+ @declared_attr
+ def service_instance(cls):
+ return cls.many_to_one_relationship('service_instance')
+
+ @declared_attr
+ def service_instance_name(cls):
+ return association_proxy('service_instance', cls.name_column_name())
+
+ def to_dict(self, suppress_error=False, **kwargs):
+ dep_update_dict = super(ServiceInstanceUpdateBase, self).to_dict(suppress_error) #pylint: disable=no-member
+ # Taking care of the fact the DeploymentSteps are _BaseModels
+ dep_update_dict['steps'] = [step.to_dict() for step in self.steps]
+ return dep_update_dict
+
+
+class ServiceInstanceUpdateStepBase(ModelMixin):
+ """
+ Deployment update step model representation.
+ """
+ # Needed only for pylint. the id will be populated by sqlalcehmy and the proper column.
+ __tablename__ = 'service_instance_update_step'
+ __private_fields__ = ['service_instance_update_fk']
+
+ _action_types = namedtuple('ACTION_TYPES', 'ADD, REMOVE, MODIFY')
+ ACTION_TYPES = _action_types(ADD='add', REMOVE='remove', MODIFY='modify')
+ _entity_types = namedtuple(
+ 'ENTITY_TYPES',
+ 'NODE, RELATIONSHIP, PROPERTY, OPERATION, WORKFLOW, OUTPUT, DESCRIPTION, GROUP, '
+ 'POLICY_TYPE, POLICY_TRIGGER, PLUGIN')
+ ENTITY_TYPES = _entity_types(
+ NODE='node',
+ RELATIONSHIP='relationship',
+ PROPERTY='property',
+ OPERATION='operation',
+ WORKFLOW='workflow',
+ OUTPUT='output',
+ DESCRIPTION='description',
+ GROUP='group',
+ POLICY_TYPE='policy_type',
+ POLICY_TRIGGER='policy_trigger',
+ PLUGIN='plugin'
+ )
+
+ action = Column(Enum(*ACTION_TYPES, name='action_type'), nullable=False)
+ entity_id = Column(Text, nullable=False)
+ entity_type = Column(Enum(*ENTITY_TYPES, name='entity_type'), nullable=False)
+
+ @declared_attr
+ def service_instance_update_fk(cls):
+ return cls.foreign_key('service_instance_update')
+
+ @declared_attr
+ def service_instance_update(cls):
+ return cls.many_to_one_relationship('service_instance_update',
+ backreference='steps')
+
+ @declared_attr
+ def deployment_update_name(cls):
+ return association_proxy('deployment_update', cls.name_column_name())
+
+ def __hash__(self):
+ return hash((getattr(self, self.id_column_name()), self.entity_id))
+
+ def __lt__(self, other):
+ """
+ the order is 'remove' < 'modify' < 'add'
+ :param other:
+ :return:
+ """
+ if not isinstance(other, self.__class__):
+ return not self >= other
+
+ if self.action != other.action:
+ if self.action == 'remove':
+ return_value = True
+ elif self.action == 'add':
+ return_value = False
+ else:
+ return_value = other.action == 'add'
+ return return_value
+
+ if self.action == 'add':
+ return self.entity_type == 'node' and other.entity_type == 'relationship'
+ if self.action == 'remove':
+ return self.entity_type == 'relationship' and other.entity_type == 'node'
+ return False
+
+
+class ServiceInstanceModificationBase(ModelMixin):
+ """
+ Deployment modification model representation.
+ """
+ __tablename__ = 'service_instance_modification'
+ __private_fields__ = ['service_instance_fk']
+
+ STARTED = 'started'
+ FINISHED = 'finished'
+ ROLLEDBACK = 'rolledback'
+
+ STATES = [STARTED, FINISHED, ROLLEDBACK]
+ END_STATES = [FINISHED, ROLLEDBACK]
+
+ 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'))
+
+ @declared_attr
+ def service_instance_fk(cls):
+ return cls.foreign_key('service_instance')
+
+ @declared_attr
+ def service_instance(cls):
+ return cls.many_to_one_relationship('service_instance',
+ backreference='modifications')
+
+ @declared_attr
+ def service_instance_name(cls):
+ return association_proxy('service_instance', cls.name_column_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'
+ __private_fields__ = ['node_fk',
+ 'relationship_fk',
+ 'execution_fk',
+ 'plugin_fk']
+
+ @declared_attr
+ def node_fk(cls):
+ return cls.foreign_key('node', nullable=True)
+
+ @declared_attr
+ def node_name(cls):
+ return association_proxy('node', cls.name_column_name())
+
+ @declared_attr
+ def node(cls):
+ return cls.many_to_one_relationship('node')
+
+ @declared_attr
+ def relationship_fk(cls):
+ return cls.foreign_key('relationship', nullable=True)
+
+ @declared_attr
+ def relationship_name(cls):
+ return association_proxy('relationships', cls.name_column_name())
+
+ @declared_attr
+ def relationship(cls):
+ return cls.many_to_one_relationship('relationship')
+
+ @declared_attr
+ def plugin_fk(cls):
+ return cls.foreign_key('plugin', nullable=True)
+
+ @declared_attr
+ def plugin(cls):
+ return cls.many_to_one_relationship('plugin')
+
+ @declared_attr
+ def execution_fk(cls):
+ return cls.foreign_key('execution', nullable=True)
+
+ @declared_attr
+ def execution(cls):
+ return cls.many_to_one_relationship('execution')
+
+ @declared_attr
+ def execution_name(cls):
+ return association_proxy('execution', cls.name_column_name())
+
+ 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_INSTANCE = 'node_instance'
+ RUNS_ON = (RUNS_ON_NODE_INSTANCE, 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
+
+ status = Column(Enum(*STATES, name='status'), default=PENDING)
+
+ due_at = Column(DateTime, 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)
+ inputs = Column(Dict)
+ # This is unrelated to the plugin of the task. This field is related to the plugin name
+ # received from the blueprint.
+ plugin_name = 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_INSTANCE:
+ 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
+
+ @classmethod
+ def as_node_instance(cls, instance, runs_on, **kwargs):
+ return cls(node=instance, _runs_on=runs_on, **kwargs)
+
+ @classmethod
+ def as_relationship_instance(cls, instance, runs_on, **kwargs):
+ return cls(relationship=instance, _runs_on=runs_on, **kwargs)
+
+ @staticmethod
+ def abort(message=None):
+ raise TaskAbortException(message)
+
+ @staticmethod
+ def retry(message=None, retry_interval=None):
+ raise TaskRetryException(message, retry_interval=retry_interval)
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/f1074fee/aria/modeling/service_instance_models.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_instance_models.py b/aria/modeling/service_instance_models.py
new file mode 100644
index 0000000..52b88af
--- /dev/null
+++ b/aria/modeling/service_instance_models.py
@@ -0,0 +1,1225 @@
+# 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 sqlalchemy import (
+ Column,
+ Text,
+ Integer,
+ Boolean,
+)
+from sqlalchemy import DateTime
+from sqlalchemy.ext.associationproxy import association_proxy
+from sqlalchemy.ext.declarative import declared_attr
+from sqlalchemy.ext.orderinglist import ordering_list
+
+from ..parser import validation
+from ..utils import collections, formatting, console
+
+from . import (
+ utils,
+ structure,
+ type as aria_types
+)
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+
+
+class ServiceInstanceBase(structure.InstanceModelMixin):
+ """
+ A service instance is an instance of a :class:`ServiceTemplate`.
+
+ You will usually not create it programmatically, but instead instantiate it from the template.
+
+ Properties:
+
+ * :code:`description`: Human-readable description
+ * :code:`meta_data`: Dict of :class:`Metadata`
+ * :code:`nodes`: Dict of :class:`Node`
+ * :code:`groups`: Dict of :class:`Group`
+ * :code:`policies`: Dict of :class:`Policy`
+ * :code:`substitution`: :class:`Substitution`
+ * :code:`inputs`: Dict of :class:`Parameter`
+ * :code:`outputs`: Dict of :class:`Parameter`
+ * :code:`operations`: Dict of :class:`Operation`
+ """
+
+ __tablename__ = 'service_instance'
+
+ __private_fields__ = ['substituion_fk',
+ 'service_template_fk']
+
+ description = Column(Text)
+
+ # region orchestrator required columns
+
+ created_at = Column(DateTime, nullable=False, index=True)
+ permalink = Column(Text)
+ policy_triggers = Column(aria_types.Dict)
+ policy_types = Column(aria_types.Dict)
+ scaling_groups = Column(aria_types.Dict)
+ updated_at = Column(DateTime)
+ workflows = Column(aria_types.Dict)
+
+ @declared_attr
+ def service_template_name(cls):
+ return association_proxy('service_template', 'name')
+
+ # endregion
+
+ # region foreign keys
+
+ @declared_attr
+ def substitution_fk(cls):
+ return cls.foreign_key('substitution', nullable=True)
+
+ @declared_attr
+ def service_template_fk(cls):
+ return cls.foreign_key('service_template')
+
+ # endregion
+
+ # region one-to-one relationships
+
+ @declared_attr
+ def substitution(cls):
+ return cls.one_to_one_relationship('substitution')
+
+ # endregion
+
+ # region one-to-many relationships
+
+ @declared_attr
+ def interfaces(cls):
+ return cls.one_to_many_relationship('interfaces', key_column_name='name')
+
+ # endregion
+
+ # region many-to-one relationships
+
+ @declared_attr
+ def service_template(cls):
+ return cls.many_to_one_relationship('service_template')
+
+ # endregion
+
+ # region many-to-many relationships
+
+ @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')
+
+ @declared_attr
+ def inputs(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='inputs')
+
+ @declared_attr
+ def outputs(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='outputs')
+
+ # endregion
+
+ # association proxies
+
+ def satisfy_requirements(self, context):
+ satisfied = True
+ for node in self.nodes.all():
+ if not node.satisfy_requirements(context):
+ satisfied = False
+ return satisfied
+
+ def validate_capabilities(self, context):
+ satisfied = True
+ for node in self.nodes.all():
+ if not node.validate_capabilities(context):
+ satisfied = False
+ return satisfied
+
+ def find_nodes(self, node_template_name):
+ nodes = []
+ for node in self.nodes.all():
+ if 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.id for node in self.find_nodes(node_template_name)))
+
+ def find_groups(self, group_template_name):
+ groups = []
+ for group in self.groups.all():
+ 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.id for group in self.find_groups(group_template_name)))
+
+ def is_node_a_target(self, context, target_node):
+ for node in self.nodes.all():
+ 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.id:
+ 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
+
+
+class OperationBase(structure.InstanceModelMixin):
+ """
+ An operation in a :class:`Interface`.
+
+ Properties:
+
+ * :code:`name`: Name
+ * :code:`description`: Description
+ * :code:`implementation`: Implementation string (interpreted by the orchestrator)
+ * :code:`dependencies`: List of strings (interpreted by the orchestrator)
+ * :code:`executor`: Executor string (interpreted by the orchestrator)
+ * :code:`max_retries`: Maximum number of retries allowed in case of failure
+ * :code:`retry_interval`: Interval between retries
+ * :code:`inputs`: Dict of :class:`Parameter`
+ """
+
+ __tablename__ = 'operation'
+
+ __private_fields__ = ['service_template_fk',
+ 'interface_instance_fk']
+
+ # region foreign_keys
+
+ @declared_attr
+ def service_instance_fk(cls):
+ return cls.foreign_key('service_instance', nullable=True)
+
+ @declared_attr
+ def interface_instance_fk(cls):
+ return cls.foreign_key('interface', nullable=True)
+
+ # endregion
+
+ description = Column(Text)
+ implementation = Column(Text)
+ dependencies = Column(aria_types.StrictList(item_cls=basestring))
+
+ executor = Column(Text)
+ max_retries = Column(Integer, default=None)
+ retry_interval = Column(Integer, default=None)
+ plugin = Column(Text)
+ operation = Column(Boolean)
+
+ # region many-to-one relationships
+
+ @declared_attr
+ def service_instance(cls):
+ return cls.many_to_one_relationship('service_instance')
+
+ @declared_attr
+ def interface(cls):
+ return cls.many_to_one_relationship('interface')
+
+ # endregion
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def inputs(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='inputs')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('implementation', self.implementation),
+ ('dependencies', self.dependencies),
+ ('executor', self.executor),
+ ('max_retries', self.max_retries),
+ ('retry_interval', self.retry_interval),
+ ('inputs', formatting.as_raw_dict(self.inputs))))
+
+ def validate(self, context):
+ utils.validate_dict_values(context, self.inputs)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, container, self.inputs, report_issues)
+
+ def dump(self, context):
+ console.puts(context.style.node(self.name))
+ if self.description:
+ console.puts(context.style.meta(self.description))
+ with context.style.indent:
+ if self.implementation is not None:
+ console.puts('Implementation: %s' % context.style.literal(self.implementation))
+ if self.dependencies:
+ console.puts(
+ 'Dependencies: %s'
+ % ', '.join((str(context.style.literal(v)) for v in self.dependencies)))
+ if self.executor is not None:
+ console.puts('Executor: %s' % context.style.literal(self.executor))
+ if self.max_retries is not None:
+ console.puts('Max retries: %s' % context.style.literal(self.max_retries))
+ if self.retry_interval is not None:
+ console.puts('Retry interval: %s' % context.style.literal(self.retry_interval))
+ utils.dump_parameters(context, self.inputs, 'Inputs')
+
+
+class InterfaceBase(structure.InstanceModelMixin):
+ """
+ A typed set of :class:`Operation`.
+
+ Properties:
+
+ * :code:`name`: Name
+ * :code:`description`: Description
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`inputs`: Dict of :class:`Parameter`
+ * :code:`operations`: Dict of :class:`Operation`
+ """
+
+ __tablename__ = 'interface'
+
+ __private_fields__ = ['group_fk',
+ 'node_fk',
+ 'relationship_fk']
+
+ # region foreign_keys
+
+ @declared_attr
+ def group_fk(cls):
+ return cls.foreign_key('group', nullable=True)
+
+ @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)
+
+ # endregion
+
+ description = Column(Text)
+ type_name = Column(Text)
+ edge = Column(Text)
+
+ # region many-to-one relationships
+
+ @declared_attr
+ def node(cls):
+ return cls.many_to_one_relationship('node')
+
+ @declared_attr
+ def relationship(cls):
+ return cls.many_to_one_relationship('relationship')
+
+ @declared_attr
+ def group(cls):
+ return cls.many_to_one_relationship('group')
+
+ # endregion
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def inputs(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='inputs',
+ key_column_name='name')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type_name),
+ ('inputs', formatting.as_raw_dict(self.inputs)),
+ ('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 "%s" has an unknown type: %s'
+ % (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)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, container, self.inputs, report_issues)
+ utils.coerce_dict_values(context, container, self.operations, report_issues)
+
+ def dump(self, context):
+ console.puts(context.style.node(self.name))
+ if self.description:
+ console.puts(context.style.meta(self.description))
+ with context.style.indent:
+ console.puts('Interface type: %s' % context.style.type(self.type_name))
+ utils.dump_parameters(context, self.inputs, 'Inputs')
+ utils.dump_dict_values(context, self.operations, 'Operations')
+
+
+class CapabilityBase(structure.InstanceModelMixin):
+ """
+ A capability of a :class:`Node`.
+
+ An instance of a :class:`CapabilityTemplate`.
+
+ Properties:
+
+ * :code:`name`: Name
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`min_occurrences`: Minimum number of requirement matches required
+ * :code:`max_occurrences`: Maximum number of requirement matches allowed
+ * :code:`properties`: Dict of :class:`Parameter`
+ """
+
+ __tablename__ = 'capability'
+
+ __private_fields__ = ['node_fk']
+
+ # region foreign_keys
+
+ @declared_attr
+ def node_fk(cls):
+ return cls.foreign_key('node')
+
+ # endregion
+
+ type_name = Column(Text)
+
+ min_occurrences = Column(Integer, default=None) # optional
+ max_occurrences = Column(Integer, default=None) # optional
+ occurrences = Column(Integer, default=0)
+
+ # region many-to-one relationships
+
+ @declared_attr
+ def node(cls):
+ return cls.many_to_one_relationship('node')
+
+ # endregion
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ # endregion
+
+ @property
+ def has_enough_relationships(self):
+ if self.min_occurrences is not None:
+ return self.occurrences >= self.min_occurrences
+ return True
+
+ def relate(self):
+ if self.max_occurrences is not None:
+ if self.occurrences == self.max_occurrences:
+ return False
+ self.occurrences += 1
+ return True
+
+ @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, context):
+ if context.modeling.capability_types.get_descendant(self.type_name) is None:
+ context.validation.report('capability "%s" has an unknown type: %s'
+ % (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):
+ utils.coerce_dict_values(context, container, self.properties, report_issues)
+
+ def dump(self, context):
+ console.puts(context.style.node(self.name))
+ with context.style.indent:
+ console.puts('Type: %s' % context.style.type(self.type_name))
+ console.puts('Occurrences: %s (%s%s)'
+ % (self.occurrences,
+ self.min_occurrences or 0,
+ (' to %d' % self.max_occurrences)
+ if self.max_occurrences is not None
+ else ' or more'))
+ utils.dump_parameters(context, self.properties)
+
+
+class ArtifactBase(structure.InstanceModelMixin):
+ """
+ A file associated with a :class:`Node`.
+
+ Properties:
+
+ * :code:`name`: Name
+ * :code:`description`: Description
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`source_path`: Source path (CSAR or repository)
+ * :code:`target_path`: Path at destination machine
+ * :code:`repository_url`: Repository URL
+ * :code:`repository_credential`: Dict of string
+ * :code:`properties`: Dict of :class:`Parameter`
+ """
+
+ __tablename__ = 'artifact'
+
+ __private_fields__ = ['node_fk']
+
+ # region foreign_keys
+
+ @declared_attr
+ def node_fk(cls):
+ return cls.foreign_key('node')
+
+ # endregion
+
+ description = Column(Text)
+ type_name = Column(Text)
+ source_path = Column(Text)
+ target_path = Column(Text)
+ repository_url = Column(Text)
+ repository_credential = Column(aria_types.StrictDict(basestring, basestring))
+
+ # region many-to-one relationships
+
+ @declared_attr
+ def node(cls):
+ return cls.many_to_one_relationship('node')
+
+ # endregion
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('description', self.description),
+ ('type_name', self.type_name),
+ ('source_path', self.source_path),
+ ('target_path', self.target_path),
+ ('repository_url', self.repository_url),
+ ('repository_credential', formatting.as_agnostic(self.repository_credential)),
+ ('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 "%s" has an unknown type: %s'
+ % (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):
+ utils.coerce_dict_values(context, container, self.properties, report_issues)
+
+ def dump(self, context):
+ console.puts(context.style.node(self.name))
+ if self.description:
+ console.puts(context.style.meta(self.description))
+ with context.style.indent:
+ console.puts('Artifact type: %s' % context.style.type(self.type_name))
+ console.puts('Source path: %s' % context.style.literal(self.source_path))
+ if self.target_path is not None:
+ console.puts('Target path: %s' % context.style.literal(self.target_path))
+ if self.repository_url is not None:
+ console.puts('Repository URL: %s' % context.style.literal(self.repository_url))
+ if self.repository_credential:
+ console.puts('Repository credential: %s'
+ % context.style.literal(self.repository_credential))
+ utils.dump_parameters(context, self.properties)
+
+
+class PolicyBase(structure.InstanceModelMixin):
+ """
+ An instance of a :class:`PolicyTemplate`.
+
+ Properties:
+
+ * :code:`name`: Name
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`properties`: Dict of :class:`Parameter`
+ * :code:`target_node_ids`: Must be represented in the :class:`ServiceInstance`
+ * :code:`target_group_ids`: Must be represented in the :class:`ServiceInstance`
+ """
+
+ __tablename__ = 'policy'
+
+ __private_fields__ = ['service_instance_fk']
+
+ # region foreign_keys
+
+ @declared_attr
+ def service_instance_fk(cls):
+ return cls.foreign_key('service_instance')
+
+ # endregion
+
+ type_name = Column(Text)
+ target_node_ids = Column(aria_types.StrictList(basestring))
+ target_group_ids = Column(aria_types.StrictList(basestring))
+
+ # region many-to-one relationships
+
+ @declared_attr
+ def service_instnce(cls):
+ return cls.many_to_one_relationship('service_instance')
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ 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)))
+
+ def validate(self, context):
+ if context.modeling.policy_types.get_descendant(self.type_name) is None:
+ context.validation.report('policy "%s" has an unknown type: %s'
+ % (self.name, utils.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):
+ utils.coerce_dict_values(context, container, self.properties, report_issues)
+
+ def dump(self, context):
+ console.puts('Policy: %s' % context.style.node(self.name))
+ with context.style.indent:
+ console.puts('Type: %s' % context.style.type(self.type_name))
+ utils.dump_parameters(context, self.properties)
+ if self.target_node_ids:
+ 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:
+ console.puts('Target groups:')
+ with context.style.indent:
+ for group_id in self.target_group_ids:
+ console.puts(context.style.node(group_id))
+
+
+class MappingBase(structure.InstanceModelMixin):
+ """
+ An instance of a :class:`MappingTemplate`.
+
+ Properties:
+
+ * :code:`mapped_name`: Exposed capability or requirement name
+ * :code:`node_id`: Must be represented in the :class:`ServiceInstance`
+ * :code:`name`: Name of capability or requirement at the node
+ """
+
+ __tablename__ = 'mapping'
+
+ mapped_name = Column(Text)
+ node_id = Column(Text)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('mapped_name', self.mapped_name),
+ ('node_id', self.node_id),
+ ('name', self.name)))
+
+ def dump(self, context):
+ console.puts('%s -> %s.%s'
+ % (context.style.node(self.mapped_name),
+ context.style.node(self.node_id),
+ context.style.node(self.name)))
+
+
+class SubstitutionBase(structure.InstanceModelMixin):
+ """
+ An instance of a :class:`SubstitutionTemplate`.
+
+ Properties:
+
+ * :code:`node_type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`capabilities`: Dict of :class:`Mapping`
+ * :code:`requirements`: Dict of :class:`Mapping`
+ """
+
+ __tablename__ = 'substitution'
+
+ node_type_name = Column(Text)
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def capabilities(cls):
+ return cls.many_to_many_relationship('mapping', table_prefix='capabilities')
+
+ @declared_attr
+ def requirements(cls):
+ return cls.many_to_many_relationship('mapping',
+ table_prefix='requirements',
+ relationship_kwargs=dict(lazy='dynamic'))
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('node_type_name', self.node_type_name),
+ ('capabilities', formatting.as_raw_list(self.capabilities)),
+ ('requirements', formatting.as_raw_list(self.requirements))))
+
+ def validate(self, context):
+ if context.modeling.node_types.get_descendant(self.node_type_name) is None:
+ context.validation.report('substitution "%s" has an unknown type: %s'
+ % (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.capabilities)
+ utils.validate_dict_values(context, self.requirements)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, container, self.capabilities, report_issues)
+ utils.coerce_dict_values(context, container, self.requirements, report_issues)
+
+ def dump(self, context):
+ console.puts('Substitution:')
+ with context.style.indent:
+ console.puts('Node type: %s' % context.style.type(self.node_type_name))
+ utils.dump_dict_values(context, self.capabilities, 'Capability mappings')
+ utils.dump_dict_values(context, self.requirements, 'Requirement mappings')
+
+
+class NodeBase(structure.InstanceModelMixin):
+ """
+ An instance of a :class:`NodeTemplate`.
+
+ Nodes may have zero or more :class:`Relationship` instances to other nodes.
+
+ Properties:
+
+ * :code:`id`: Unique ID (prefixed with the template name)
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`template_name`: Must be represented in the :class:`ServiceTemplate`
+ * :code:`properties`: Dict of :class:`Parameter`
+ * :code:`interfaces`: Dict of :class:`Interface`
+ * :code:`artifacts`: Dict of :class:`Artifact`
+ * :code:`capabilities`: Dict of :class:`CapabilityTemplate`
+ * :code:`relationships`: List of :class:`Relationship`
+ """
+
+ __tablename__ = 'node'
+
+ __private_fields__ = ['service_instance_fk',
+ 'host_fk',
+ 'node_template_fk']
+
+ # region foreign_keys
+
+ @declared_attr
+ def service_instance_fk(cls):
+ return cls.foreign_key('service_instance')
+
+ @declared_attr
+ def host_fk(cls):
+ return cls.foreign_key('node', nullable=True)
+
+ @declared_attr
+ def node_template_fk(cls):
+ return cls.foreign_key('node_template')
+
+ # endregion
+
+ type_name = Column(Text)
+ template_name = Column(Text)
+
+ # region orchestrator required columns
+
+ runtime_properties = Column(aria_types.Dict)
+ scaling_groups = Column(aria_types.List)
+ state = Column(Text, nullable=False)
+ version = Column(Integer, default=1)
+
+ @declared_attr
+ def plugins(cls):
+ return association_proxy('node_template', 'plugins')
+
+ @declared_attr
+ def host(cls):
+ return cls.relationship_to_self('host_fk')
+
+ @declared_attr
+ def service_instance_name(cls):
+ return association_proxy('service_instance', 'name')
+
+ @property
+ def ip(self):
+ 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 = [prop for prop in host_node.properties if prop.name == 'ip']
+ if host_ip_property:
+ return host_ip_property[0].value
+ return None
+
+ @declared_attr
+ def node_template(cls):
+ return cls.many_to_one_relationship('node_template')
+
+ @declared_attr
+ def service_template(cls):
+ return association_proxy('service_instance', 'service_template')
+
+ # endregion
+
+ # region one-to-many relationships
+
+ @declared_attr
+ def interfaces(cls):
+ return cls.one_to_many_relationship('interfaces', key_column_name='name')
+
+ # endregion
+
+ # region many-to-one relationships
+
+ @declared_attr
+ def service_instance(cls):
+ return cls.many_to_one_relationship('service_instance')
+
+ # endregion
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ # endregion
+
+ def satisfy_requirements(self, context):
+ node_template = context.modeling.model.node_templates.get(self.template_name)
+ satisfied = True
+ for i in range(len(node_template.requirement_templates)):
+ requirement_template = node_template.requirement_templates[i]
+
+ # 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,
+ requirement_template_index=i)
+ else:
+ context.validation.report('requirement "%s" of node "%s" has no target node '
+ 'template' % (requirement_template.name,
+ self.id),
+ level=validation.Issue.BETWEEN_INSTANCES)
+ satisfied = False
+ return satisfied
+
+ def _satisfy_capability(self, context, target_node_capability, target_node_template,
+ requirement_template, requirement_template_index):
+ # Find target nodes
+ target_nodes = context.modeling.instance.find_nodes(target_node_template.name)
+ 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:
+ relationship = RelationshipBase(
+ name=requirement_template.name,
+ source_requirement_index=requirement_template_index,
+ target_node_id=target_node.id,
+ target_capability_name=target_capability.name
+ )
+ self.relationships.append(relationship)
+ else:
+ context.validation.report('requirement "%s" of node "%s" targets node '
+ 'template "%s" but its instantiated nodes do not '
+ 'have enough capacity'
+ % (requirement_template.name,
+ self.id,
+ target_node_template.name),
+ level=validation.Issue.BETWEEN_INSTANCES)
+ return False
+ else:
+ context.validation.report('requirement "%s" of node "%s" targets node template '
+ '"%s" but it has no instantiated nodes'
+ % (requirement_template.name,
+ self.id,
+ target_node_template.name),
+ level=validation.Issue.BETWEEN_INSTANCES)
+ return False
+
+ def validate_capabilities(self, context):
+ satisfied = False
+ for capability in self.capabilities.itervalues():
+ if not capability.has_enough_relationships:
+ context.validation.report('capability "%s" of node "%s" requires at least %d '
+ 'relationships but has %d'
+ % (capability.name,
+ self.id,
+ capability.min_occurrences,
+ capability.occurrences),
+ level=validation.Issue.BETWEEN_INSTANCES)
+ satisfied = False
+ return satisfied
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('id', self.id),
+ ('type_name', self.type_name),
+ ('template_name', self.template_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.relationships))))
+
+ def validate(self, context):
+ if len(self.id) > context.modeling.id_max_length:
+ context.validation.report('"%s" has an ID longer than the limit of %d characters: %d'
+ % (self.id,
+ context.modeling.id_max_length,
+ len(self.id)),
+ level=validation.Issue.BETWEEN_INSTANCES)
+
+ # TODO: validate that node template is of type?
+
+ utils.validate_dict_values(context, self.properties)
+ utils.validate_dict_values(context, self.interfaces)
+ utils.validate_dict_values(context, self.artifacts)
+ utils.validate_dict_values(context, self.capabilities)
+ utils.validate_list_values(context, self.relationships)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, self, self.properties, report_issues)
+ utils.coerce_dict_values(context, self, self.interfaces, report_issues)
+ utils.coerce_dict_values(context, self, self.artifacts, report_issues)
+ utils.coerce_dict_values(context, self, self.capabilities, report_issues)
+ utils.coerce_list_values(context, self, self.relationships, report_issues)
+
+ def dump(self, context):
+ console.puts('Node: %s' % context.style.node(self.id))
+ with context.style.indent:
+ console.puts('Template: %s' % context.style.node(self.template_name))
+ console.puts('Type: %s' % 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')
+ utils.dump_dict_values(context, self.capabilities, 'Capabilities')
+ utils.dump_list_values(context, self.relationships, 'Relationships')
+
+
+class GroupBase(structure.InstanceModelMixin):
+ """
+ An instance of a :class:`GroupTemplate`.
+
+ Properties:
+
+ * :code:`id`: Unique ID (prefixed with the template name)
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`template_name`: Must be represented in the :class:`ServiceTemplate`
+ * :code:`properties`: Dict of :class:`Parameter`
+ * :code:`interfaces`: Dict of :class:`Interface`
+ * :code:`policies`: Dict of :class:`GroupPolicy`
+ * :code:`member_node_ids`: Must be represented in the :class:`ServiceInstance`
+ * :code:`member_group_ids`: Must be represented in the :class:`ServiceInstance`
+ """
+
+ __tablename__ = 'group'
+
+ __private_fields__ = ['service_instance_fk']
+
+ # region foreign_keys
+
+ @declared_attr
+ def service_instance_fk(cls):
+ return cls.foreign_key('service_instance')
+
+ # endregion
+
+ type_name = Column(Text)
+ template_name = Column(Text)
+ member_node_ids = Column(aria_types.StrictList(basestring))
+ member_group_ids = Column(aria_types.StrictList(basestring))
+
+ # region one-to-many relationships
+
+ @declared_attr
+ def interfaces(cls):
+ return cls.one_to_many_relationship('interfaces', key_column_name='name')
+
+ # endregion
+
+ # region many-to-one relationships
+
+ @declared_attr
+ def service_instance(cls):
+ return cls.many_to_one_relationship('service_instance')
+
+ # endregion
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('id', self.id),
+ ('type_name', self.type_name),
+ ('template_name', self.template_name),
+ ('properties', formatting.as_raw_dict(self.properties)),
+ ('interfaces', formatting.as_raw_list(self.interfaces)),
+ ('policies', formatting.as_raw_list(self.policies)),
+ ('member_node_ids', self.member_node_ids),
+ ('member_group_ids', self.member_group_ids)))
+
+ def validate(self, context):
+ if context.modeling.group_types.get_descendant(self.type_name) is None:
+ context.validation.report('group "%s" has an unknown type: %s'
+ % (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)
+ utils.validate_dict_values(context, self.policies)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, container, self.properties, report_issues)
+ utils.coerce_dict_values(context, container, self.interfaces, report_issues)
+ utils.coerce_dict_values(context, container, self.policies, report_issues)
+
+ def dump(self, context):
+ console.puts('Group: %s' % context.style.node(self.id))
+ with context.style.indent:
+ console.puts('Type: %s' % context.style.type(self.type_name))
+ console.puts('Template: %s' % context.style.type(self.template_name))
+ utils.dump_parameters(context, self.properties)
+ utils.dump_interfaces(context, self.interfaces)
+ utils.dump_dict_values(context, self.policies, 'Policies')
+ if self.member_node_ids:
+ console.puts('Member nodes:')
+ with context.style.indent:
+ for node_id in self.member_node_ids:
+ console.puts(context.style.node(node_id))
+
+
+class RelationshipBase(structure.InstanceModelMixin):
+ """
+ Connects :class:`Node` to another node.
+
+ An instance of a :class:`RelationshipTemplate`.
+
+ Properties:
+
+ * :code:`name`: Name (usually the name of the requirement at the source node template)
+ * :code:`source_requirement_index`: Must be represented in the source node template
+ * :code:`target_node_id`: Must be represented in the :class:`ServiceInstance`
+ * :code:`target_capability_name`: Matches the capability at the target node
+ * :code:`type_name`: Must be represented in the :class:`ModelingContext`
+ * :code:`template_name`: Must be represented in the :class:`ServiceTemplate`
+ * :code:`properties`: Dict of :class:`Parameter`
+ * :code:`source_interfaces`: Dict of :class:`Interface`
+ * :code:`target_interfaces`: Dict of :class:`Interface`
+ """
+
+ __tablename__ = 'relationship'
+
+ __private_fields__ = ['source_node_fk',
+ 'target_node_fk']
+
+ source_requirement_index = Column(Integer)
+ target_node_id = Column(Text)
+ target_capability_name = Column(Text)
+ type_name = Column(Text)
+ template_name = Column(Text)
+
+ # region orchestrator required columns
+
+ source_position = Column(Integer)
+ target_position = Column(Integer)
+
+ @declared_attr
+ def source_node_fk(cls):
+ return cls.foreign_key('node', nullable=True)
+
+ @declared_attr
+ def source_node(cls):
+ return cls.many_to_one_relationship(
+ 'node',
+ 'source_node_fk',
+ backreference='outbound_relationships',
+ backref_kwargs=dict(
+ order_by=cls.source_position,
+ collection_class=ordering_list('source_position', count_from=0),
+ )
+ )
+
+ @declared_attr
+ def source_node_name(cls):
+ return association_proxy('source_node', cls.name_column_name())
+
+ @declared_attr
+ def target_node_fk(cls):
+ return cls.foreign_key('node', nullable=True)
+
+ @declared_attr
+ def target_node(cls):
+ return cls.many_to_one_relationship(
+ 'node',
+ 'target_node_fk',
+ backreference='inbound_relationships',
+ backref_kwargs=dict(
+ order_by=cls.target_position,
+ collection_class=ordering_list('target_position', count_from=0),
+ )
+ )
+
+ @declared_attr
+ def target_node_name(cls):
+ return association_proxy('target_node', cls.name_column_name())
+
+ # endregion
+
+ # region one-to-many relationships
+
+ @declared_attr
+ def interfaces(cls):
+ return cls.one_to_many_relationship('interfaces', key_column_name='name')
+
+ # endregion
+
+ # region many-to-many relationships
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ # endregion
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('source_requirement_index', self.source_requirement_index),
+ ('target_node_id', self.target_node_id),
+ ('target_capability_name', self.target_capability_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))))
+
+ def validate(self, context):
+ if self.type_name:
+ if context.modeling.relationship_types.get_descendant(self.type_name) is None:
+ context.validation.report('relationship "%s" has an unknown type: %s'
+ % (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)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, container, self.properties, report_issues)
+ utils.coerce_dict_values(context, container, self.interfaces, report_issues)
+
+ def dump(self, context):
+ if self.name:
+ if self.source_requirement_index is not None:
+ console.puts('%s (%d) ->' % (
+ context.style.node(self.name),
+ self.source_requirement_index))
+ else:
+ console.puts('%s ->' % context.style.node(self.name))
+ else:
+ console.puts('->')
+ with context.style.indent:
+ console.puts('Node: %s' % context.style.node(self.target_node_id))
+ if self.target_capability_name is not None:
+ console.puts('Capability: %s' % context.style.node(self.target_capability_name))
+ if self.type_name is not None:
+ console.puts('Relationship type: %s' % context.style.type(self.type_name))
+ if self.template_name is not None:
+ console.puts('Relationship template: %s' % context.style.node(self.template_name))
+ utils.dump_parameters(context, self.properties)
+ utils.dump_interfaces(context, self.interfaces, 'Interfaces')