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/28 23:01:25 UTC
[3/6] incubator-ariatosca git commit: Major reworking of models to
make better use of SqlAlchemy; unfinished work on orchestration models
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/0150da17/aria/modeling/service_template.py
----------------------------------------------------------------------
diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py
new file mode 100644
index 0000000..a17f481
--- /dev/null
+++ b/aria/modeling/service_template.py
@@ -0,0 +1,1474 @@
+# 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 __future__ import absolute_import # so we can import standard 'types'
+
+
+from copy import deepcopy
+from types import FunctionType
+from datetime import datetime
+
+from sqlalchemy import (
+ Column,
+ Text,
+ Integer,
+ DateTime
+)
+from sqlalchemy.ext.declarative import declared_attr
+
+from ..storage import exceptions
+from ..parser import validation
+from ..parser.modeling import utils as parser_utils
+from ..utils import collections, formatting, console
+from .service import _InstanceModelMixin
+from . import (
+ utils,
+ types as modeling_types
+)
+
+# pylint: disable=no-self-argument, no-member, abstract-method
+
+
+
+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 ServiceTemplateBase(_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.
+
+ :ivar description: Human-readable description
+ :ivar meta_data: Dict of :class:`Metadata`
+ :ivar node_templates: List of :class:`NodeTemplate`
+ :ivar group_templates: List of :class:`GroupTemplate`
+ :ivar policy_templates: List of :class:`PolicyTemplate`
+ :ivar substitution_template: :class:`SubstitutionTemplate`
+ :ivar inputs: Dict of :class:`Parameter`
+ :ivar outputs: Dict of :class:`Parameter`
+ :ivar operation_templates: Dict of :class:`OperationTemplate`
+ """
+
+ __tablename__ = 'service_template' # redundancy for PyLint: SqlAlchemy injects this
+
+ __private_fields__ = ['substitution_template_fk']
+
+ description = Column(Text)
+
+ @declared_attr
+ def meta_data(cls):
+ # Warning! We cannot use the attr name "metadata" because it's used by SqlAlchemy!
+ return cls.many_to_many_relationship('metadata', key_column_name='name')
+
+ @declared_attr
+ def node_templates(cls):
+ return cls.one_to_many_relationship('node_template')
+
+ @declared_attr
+ def group_templates(cls):
+ return cls.one_to_many_relationship('group_template')
+
+ @declared_attr
+ def policy_templates(cls):
+ return cls.one_to_many_relationship('policy_template')
+
+ @declared_attr
+ def substitution_template(cls):
+ return cls.one_to_one_relationship('substitution_template')
+
+ @declared_attr
+ def inputs(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='inputs',
+ key_column_name='name')
+
+ @declared_attr
+ def outputs(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='outputs',
+ key_column_name='name')
+
+ @declared_attr
+ def operation_templates(cls):
+ return cls.one_to_many_relationship('operation_template', key_column_name='name')
+
+ # region orchestrator required columns
+
+ created_at = Column(DateTime, nullable=False, index=True)
+ updated_at = Column(DateTime)
+ main_file_name = Column(Text)
+
+ @declared_attr
+ def plugins(cls):
+ return cls.one_to_many_relationship('plugin', key_column_name='name')
+
+ # endregion
+
+ # region foreign keys
+
+ # ServiceTemplate one-to-one to SubstitutionTemplate
+ @declared_attr
+ def substitution_template_fk(cls):
+ return cls.foreign_key('substitution_template', nullable=True)
+
+ # endregion
+
+ def get_node_template(self, node_template_name):
+ if self.node_templates:
+ for node_template in self.node_templates:
+ if node_template.name == node_template_name:
+ return node_template
+ return None
+
+ @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 models
+ now = datetime.now()
+ service = models.Service(created_at=now,
+ updated_at=now,
+ description=deepcopy_with_locators(self.description),
+ service_template=self)
+ #service.name = '{0}_{1}'.format(self.name, service.id)
+
+ context.modeling.instance = service
+
+ utils.instantiate_dict(context, self, service.meta_data, self.meta_data)
+
+ for node_template in self.node_templates:
+ for _ in range(node_template.default_instances):
+ node = node_template.instantiate(context, container)
+ service.nodes.append(node)
+
+ utils.instantiate_list(context, self, service.groups, self.group_templates)
+ utils.instantiate_list(context, self, service.policies, self.policy_templates)
+ utils.instantiate_dict(context, self, service.operations, self.operation_templates)
+
+ if self.substitution_template is not None:
+ service.substitution = self.substitution_template.instantiate(context, container)
+
+ utils.instantiate_dict(context, self, service.inputs, self.inputs)
+ utils.instantiate_dict(context, self, service.outputs, self.outputs)
+
+ for name, the_input in context.modeling.inputs.iteritems():
+ if name not in service.inputs:
+ context.validation.report('input "{0}" is not supported'.format(name))
+ else:
+ service.inputs[name].value = the_input
+
+ return service
+
+ def validate(self, context):
+ utils.validate_dict_values(context, self.meta_data)
+ utils.validate_list_values(context, self.node_templates)
+ utils.validate_list_values(context, self.group_templates)
+ utils.validate_list_values(context, self.policy_templates)
+ if self.substitution_template is not None:
+ self.substitution_template.validate(context)
+ utils.validate_dict_values(context, self.inputs)
+ utils.validate_dict_values(context, self.outputs)
+ utils.validate_dict_values(context, self.operation_templates)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, container, self.meta_data, report_issues)
+ utils.coerce_list_values(context, container, self.node_templates, report_issues)
+ utils.coerce_list_values(context, container, self.group_templates, report_issues)
+ utils.coerce_list_values(context, container, self.policy_templates, report_issues)
+ if self.substitution_template is not None:
+ self.substitution_template.coerce_values(context, container, report_issues)
+ utils.coerce_dict_values(context, container, self.inputs, report_issues)
+ utils.coerce_dict_values(context, container, self.outputs, report_issues)
+ utils.coerce_dict_values(context, container, self.operation_templates, report_issues)
+
+ def dump(self, context):
+ if self.description is not None:
+ console.puts(context.style.meta(self.description))
+ utils.dump_parameters(context, self.meta_data, 'Metadata')
+
+ for node_template in self.node_templates:
+ node_template.dump(context)
+ for group_template in self.group_templates:
+ group_template.dump(context)
+ for policy_template in self.policy_templates:
+ policy_template.dump(context)
+ if self.substitution_template is not None:
+ self.substitution_template.dump(context)
+ utils.dump_parameters(context, self.inputs, 'Inputs')
+ utils.dump_parameters(context, self.outputs, 'Outputs')
+ utils.dump_dict_values(context, self.operation_templates, 'Operation templates')
+
+
+class NodeTemplateBase(_TemplateModelMixin):
+ """
+ A template for creating zero or more :class:`Node` instances.
+
+ :ivar name: Name (will be used as a prefix for node IDs)
+ :ivar description: Description
+ :ivar type_name: Must be represented in the :class:`ModelingContext`
+ :ivar default_instances: Default number nodes that will appear in the deployment plan
+ :ivar min_instances: Minimum number nodes that will appear in the deployment plan
+ :ivar max_instances: Maximum number nodes that will appear in the deployment plan
+ :ivar properties: Dict of :class:`Parameter`
+ :ivar interface_templates: Dict of :class:`InterfaceTemplate`
+ :ivar artifact_templates: Dict of :class:`ArtifactTemplate`
+ :ivar capability_templates: Dict of :class:`CapabilityTemplate`
+ :ivar requirement_templates: List of :class:`RequirementTemplate`
+ :ivar target_node_template_constraints: List of :class:`FunctionType`
+ """
+
+ __tablename__ = 'node_template' # redundancy for PyLint: SqlAlchemy injects this
+
+ __private_fields__ = ['service_template_fk']
+
+ 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(modeling_types.StrictList(FunctionType))
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ @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')
+
+ @declared_attr
+ def requirement_templates(cls):
+ return cls.one_to_many_relationship('requirement_template')
+
+ # region orchestrator required columns
+
+ @declared_attr
+ def plugins(cls):
+ return cls.many_to_many_relationship('plugin', key_column_name='name')
+
+ # endregion
+
+ # region foreign_keys
+
+ # ServiceTemplate one-to-many to NodeTemplate
+ @declared_attr
+ def service_template_fk(cls):
+ return cls.foreign_key('service_template')
+
+ # 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 models
+ name = context.modeling.generate_node_id(self.name)
+ node = models.Node(name=name,
+ type_name=self.type_name,
+ state='',
+ node_template=self)
+ 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 "{0}" has an unknown type: {1}'.format(
+ self.name,
+ formatting.safe_repr(self.type_name)),
+ level=validation.Issue.BETWEEN_TYPES)
+
+ utils.validate_dict_values(context, self.properties)
+ utils.validate_dict_values(context, self.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: {0}'.format(context.style.node(self.name)))
+ if self.description:
+ console.puts(context.style.meta(self.description))
+ with context.style.indent:
+ console.puts('Type: {0}'.format(context.style.type(self.type_name)))
+ console.puts('Instances: {0:d} ({1:d}{2})'.format(
+ self.default_instances,
+ self.min_instances,
+ ' to {0:d}'.format(self.max_instances)
+ if self.max_instances is not None
+ else ' or more'))
+ utils.dump_parameters(context, self.properties)
+ utils.dump_interfaces(context, self.interface_templates)
+ utils.dump_dict_values(context, self.artifact_templates, 'Artifact templates')
+ utils.dump_dict_values(context, self.capability_templates, 'Capability templates')
+ utils.dump_list_values(context, self.requirement_templates, 'Requirement templates')
+
+
+class GroupTemplateBase(_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.
+
+ :ivar name: Name (will be used as a prefix for group IDs)
+ :ivar description: Description
+ :ivar type_name: Must be represented in the :class:`ModelingContext`
+ :ivar properties: Dict of :class:`Parameter`
+ :ivar interface_templates: Dict of :class:`InterfaceTemplate`
+ :ivar member_node_template_names: Must be represented in the :class:`ServiceTemplate`
+ :ivar member_group_template_names: Must be represented in the :class:`ServiceTemplate`
+ """
+
+ __tablename__ = 'group_template' # redundancy for PyLint: SqlAlchemy injects this
+
+ __private_fields__ = ['service_template_fk']
+
+ description = Column(Text)
+ type_name = Column(Text)
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ @declared_attr
+ def interface_templates(cls):
+ return cls.one_to_many_relationship('interface_template', key_column_name='name')
+
+ member_node_template_names = Column(modeling_types.StrictList(basestring))
+ member_group_template_names = Column(modeling_types.StrictList(basestring))
+
+ # region foreign keys
+
+ # ServiceTemplate one-to-many to GroupTemplate
+ @declared_attr
+ def service_template_fk(cls):
+ return cls.foreign_key('service_template')
+
+ # 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 models
+ group = models.Group(name=self.name,
+ type_name=self.type_name,
+ group_template=self)
+ utils.instantiate_dict(context, self, group.properties, self.properties)
+ utils.instantiate_dict(context, self, group.interfaces, self.interface_templates)
+ if self.member_node_template_names:
+ group.member_node_ids = []
+ 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)
+ if self.member_group_template_names:
+ group.member_group_ids = []
+ 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 "{0}" has an unknown type: {1}'.format(
+ self.name, formatting.safe_repr(self.type_name)),
+ level=validation.Issue.BETWEEN_TYPES)
+
+ utils.validate_dict_values(context, self.properties)
+ utils.validate_dict_values(context, self.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: {0}'.format(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: {0}'.format(context.style.type(self.type_name)))
+ utils.dump_parameters(context, self.properties)
+ utils.dump_interfaces(context, self.interface_templates)
+ if self.member_node_template_names:
+ console.puts('Member node templates: {0}'.format(', '.join(
+ (str(context.style.node(v)) for v in self.member_node_template_names))))
+
+
+class PolicyTemplateBase(_TemplateModelMixin):
+ """
+ Policies can be applied to zero or more :class:`NodeTemplate` or :class:`GroupTemplate`
+ instances.
+
+ :ivar name: Name
+ :ivar description: Description
+ :ivar type_name: Must be represented in the :class:`ModelingContext`
+ :ivar properties: Dict of :class:`Parameter`
+ :ivar target_node_template_names: Must be represented in the :class:`ServiceTemplate`
+ :ivar target_group_template_names: Must be represented in the :class:`ServiceTemplate`
+ """
+ __tablename__ = 'policy_template' # redundancy for PyLint: SqlAlchemy injects this
+
+ __private_fields__ = ['service_template_fk']
+
+ description = Column(Text)
+ type_name = Column(Text)
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ target_node_template_names = Column(modeling_types.StrictList(basestring))
+ target_group_template_names = Column(modeling_types.StrictList(basestring))
+
+ # region foreign keys
+
+ # ServiceTemplate one-to-many to PolicyTemplate
+ @declared_attr
+ def service_template_fk(cls):
+ return cls.foreign_key('service_template')
+
+ # 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 models
+ policy = models.Policy(name=self.name,
+ type_name=self.type_name,
+ policy_template=self)
+ utils.instantiate_dict(context, self, policy.properties, self.properties)
+ if self.target_node_template_names:
+ policy.target_node_ids = []
+ for node_template_name in self.target_node_template_names:
+ policy.target_node_ids += \
+ context.modeling.instance.get_node_ids(node_template_name)
+ if self.target_group_template_names:
+ policy.target_group_ids = []
+ for group_template_name in self.target_group_template_names:
+ policy.target_group_ids += \
+ 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 "{0}" has an unknown type: {1}'.format(
+ self.name, formatting.safe_repr(self.type_name)),
+ level=validation.Issue.BETWEEN_TYPES)
+
+ utils.validate_dict_values(context, self.properties)
+
+ def coerce_values(self, context, container, report_issues):
+ utils.coerce_dict_values(context, self, self.properties, report_issues)
+
+ def dump(self, context):
+ console.puts('Policy template: {0}'.format(context.style.node(self.name)))
+ if self.description:
+ console.puts(context.style.meta(self.description))
+ with context.style.indent:
+ console.puts('Type: {0}'.format(context.style.type(self.type_name)))
+ utils.dump_parameters(context, self.properties)
+ if self.target_node_template_names:
+ console.puts('Target node templates: {0}'.format(', '.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: {0}'.format(', '.join(
+ (str(context.style.node(v)) for v in self.target_group_template_names))))
+
+
+class SubstitutionTemplateBase(_TemplateModelMixin):
+ """
+ Used to substitute a single node for the entire deployment.
+
+ :ivar node_type_name: Must be represented in the :class:`ModelingContext`
+ :ivar mappings: Dict of :class:` SubstitutionTemplateMapping`
+ """
+
+ __tablename__ = 'substitution_template' # redundancy for PyLint: SqlAlchemy injects this
+
+ node_type_name = Column(Text)
+
+ @declared_attr
+ def mappings(cls):
+ return cls.one_to_many_relationship('substitution_template_mapping', key_column_name='name')
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('node_type_name', self.node_type_name),
+ ('mappings', formatting.as_raw_dict(self.mappings))))
+
+ def instantiate(self, context, container):
+ from . import models
+ substitution = models.Substitution(node_type_name=self.node_type_name,
+ substitution_template=self)
+ 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: {0}'.format(
+ 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: {0}'.format(context.style.type(self.node_type_name)))
+ utils.dump_dict_values(context, self.mappings, 'Mappings')
+
+
+class SubstitutionTemplateMappingBase(_TemplateModelMixin):
+ """
+ Used by :class:`SubstitutionTemplate` to map a capability or a requirement to a node.
+
+ :ivar mapped_name: Exposed capability or requirement name
+ :ivar node_template_name: Must be represented in the :class:`ServiceTemplate`
+ :ivar name: Name of capability or requirement at the node template
+ """
+
+ __tablename__ = 'substitution_template_mapping' # redundancy for PyLint: SqlAlchemy injects this
+
+ __private_fields__ = ['substitution_template_fk']
+
+ mapped_name = Column(Text)
+ node_template_name = Column(Text)
+
+ # region foreign keys
+
+ # SubstitutionTemplate one-to-many to SubstitutionTemplateMapping
+ @declared_attr
+ def substitution_template_fk(cls):
+ return cls.foreign_key('substitution_template')
+
+ # 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 models
+ nodes = context.modeling.instance.find_nodes(self.node_template_name)
+ if len(nodes) == 0:
+ context.validation.report(
+ 'mapping "{0}" refers to node template "{1}" but there are no '
+ 'node instances'.format(self.mapped_name, self.node_template_name),
+ level=validation.Issue.BETWEEN_INSTANCES)
+ return None
+ return models.SubstitutionMapping(mapped_name=self.mapped_name,
+ node_id=nodes[0].name,
+ name=self.name)
+
+ def validate(self, context):
+ if self.node_template_name not in (v.name for v in context.modeling.template.node_templates):
+ context.validation.report('mapping "{0}" refers to an unknown node template: {1}' \
+ .format(
+ self.mapped_name,
+ formatting.safe_repr(self.node_template_name)),
+ level=validation.Issue.BETWEEN_TYPES)
+
+ def dump(self, context):
+ console.puts('{0} -> {1}.{2}'.format(
+ context.style.node(self.mapped_name),
+ context.style.node(self.node_template_name),
+ context.style.node(self.name)))
+
+
+class RequirementTemplateBase(_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.
+
+ :ivar name: Name
+ :ivar target_node_type_name: Must be represented in the :class:`ModelingContext`
+ :ivar target_node_template_name: Must be represented in the :class:`ServiceTemplate`
+ :ivar target_node_template_constraints: List of :class:`FunctionType`
+ :ivar target_capability_type_name: Type of capability in target node
+ :ivar target_capability_name: Name of capability in target node
+ :ivar relationship_template: :class:`RelationshipTemplate`
+ """
+
+ __tablename__ = 'requirement_template' # redundancy for PyLint: SqlAlchemy injects this
+
+ __private_fields__ = ['node_template_fk',
+ 'relationship_template_fk']
+
+ target_node_type_name = Column(Text)
+ target_node_template_name = Column(Text)
+ target_node_template_constraints = Column(modeling_types.StrictList(FunctionType))
+ target_capability_type_name = Column(Text)
+ target_capability_name = Column(Text)
+
+ @declared_attr
+ def relationship_template(cls):
+ return cls.one_to_one_relationship('relationship_template')
+
+ # region foreign keys
+
+ # NodeTemplate one-to-many to RequirementTemplate
+ @declared_attr
+ def node_template_fk(cls):
+ return cls.foreign_key('node_template', nullable=True)
+
+ # RequirementTemplate one-to-one to RelationshipTemplate
+ @declared_attr
+ def relationship_template_fk(cls):
+ return cls.foreign_key('relationship_template', nullable=True)
+
+ # 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.template.get_node_template(self.target_node_template_name)
+
+ if not source_node_template.is_target_node_valid(target_node_template):
+ context.validation.report('requirement "{0}" of node template "{1}" is for node '
+ 'template "{2}" but it does not match constraints'.format(
+ 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.template.node_templates:
+ 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 "{0}" refers to an unknown node type: {1}' \
+ .format(
+ 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 "{0}" refers to an unknown capability type: '
+ '{1}'.format(
+ 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: {0}'.format(
+ context.style.type(self.target_node_type_name)))
+ elif self.target_node_template_name is not None:
+ console.puts('Target node template: {0}'.format(
+ context.style.node(self.target_node_template_name)))
+ if self.target_capability_type_name is not None:
+ console.puts('Target capability type: {0}'.format(
+ context.style.type(self.target_capability_type_name)))
+ elif self.target_capability_name is not None:
+ console.puts('Target capability name: {0}'.format(
+ 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 RelationshipTemplateBase(_TemplateModelMixin):
+ """
+ Optional addition to a :class:`RequirementTemplate` in :class:`NodeTemplate` that can be applied
+ when the requirement is matched with a capability.
+
+ :ivar type_name: Must be represented in the :class:`ModelingContext`
+ :ivar template_name: Must be represented in the :class:`ServiceTemplate`
+ :ivar description: Description
+ :ivar properties: Dict of :class:`Parameter`
+ :ivar interface_templates: Dict of :class:`InterfaceTemplate`
+ """
+
+ __tablename__ = 'relationship_template' # redundancy for PyLint: SqlAlchemy injects this
+
+ type_name = Column(Text)
+ template_name = Column(Text)
+ description = Column(Text)
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ @declared_attr
+ def interface_templates(cls):
+ return cls.one_to_many_relationship('interface_template', key_column_name='name')
+
+ @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 models
+ relationship = models.Relationship(name=self.template_name,
+ type_name=self.type_name,
+ relationship_template=self)
+ 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')
+
+
+class CapabilityTemplateBase(_TemplateModelMixin):
+ """
+ A capability of a :class:`NodeTemplate`. Nodes expose zero or more capabilities that can be
+ matched with :class:`Requirement` instances of other nodes.
+
+ :ivar name: Name
+ :ivar description: Description
+ :ivar type_name: Must be represented in the :class:`ModelingContext`
+ :ivar min_occurrences: Minimum number of requirement matches required
+ :ivar max_occurrences: Maximum number of requirement matches allowed
+ :ivar valid_source_node_type_names: Must be represented in the :class:`ModelingContext`
+ :ivar properties: Dict of :class:`Parameter`
+ """
+
+ __tablename__ = 'capability_template' # redundancy for PyLint: SqlAlchemy injects this
+
+ __private_fields__ = ['node_template_fk']
+
+ description = Column(Text)
+ type_name = Column(Text)
+ min_occurrences = Column(Integer, default=None) # optional
+ max_occurrences = Column(Integer, default=None) # optional
+ valid_source_node_type_names = Column(modeling_types.StrictList(basestring))
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ # region foreign keys
+
+ # NodeTemplate one-to-many to CapabilityTemplate
+ @declared_attr
+ def node_template_fk(cls):
+ return cls.foreign_key('node_template')
+
+ # 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 models
+ capability = models.Capability(name=self.name,
+ type_name=self.type_name,
+ min_occurrences=self.min_occurrences,
+ max_occurrences=self.max_occurrences,
+ occurrences=0,
+ capability_template=self)
+ 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 "{0}" refers to an unknown type: {1}'.format(
+ 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: {0}'.format(context.style.type(self.type_name)))
+ console.puts(
+ 'Occurrences: {0:d}{1}'.format(
+ self.min_occurrences or 0,
+ ' to {0:d}'.format(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: {0}'.format(
+ ', '.join((str(context.style.type(v))
+ for v in self.valid_source_node_type_names))))
+ utils.dump_parameters(context, self.properties)
+
+
+class InterfaceTemplateBase(_TemplateModelMixin):
+ """
+ A typed set of :class:`OperationTemplate`.
+
+ :ivar name: Name
+ :ivar description: Description
+ :ivar type_name: Must be represented in the :class:`ModelingContext`
+ :ivar inputs: Dict of :class:`Parameter`
+ :ivar operation_templates: Dict of :class:`OperationTemplate`
+ """
+
+ __tablename__ = 'interface_template' # redundancy for PyLint: SqlAlchemy injects this
+
+ __private_fields__ = ['node_template_fk',
+ 'group_template_fk',
+ 'relationship_template_fk']
+
+ description = Column(Text)
+ type_name = Column(Text)
+
+ @declared_attr
+ def inputs(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='inputs',
+ key_column_name='name')
+ @declared_attr
+ def operation_templates(cls):
+ return cls.one_to_many_relationship('operation_template', key_column_name='name')
+
+ # region foreign keys
+
+ # NodeTemplate one-to-many to InterfaceTemplate
+ @declared_attr
+ def node_template_fk(cls):
+ return cls.foreign_key('node_template', nullable=True)
+
+ # GroupTemplate one-to-many to InterfaceTemplate
+ @declared_attr
+ def group_template_fk(cls):
+ return cls.foreign_key('group_template', nullable=True)
+
+ # RelationshipTemplate one-to-many to InterfaceTemplate
+ @declared_attr
+ def relationship_template_fk(cls):
+ return cls.foreign_key('relationship_template', nullable=True)
+
+ # 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)), # 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 models
+ interface = models.Interface(name=self.name,
+ description=deepcopy_with_locators(self.description),
+ type_name=self.type_name,
+ interface_template=self)
+ 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 "{0}" has an unknown type: {1}'.format(
+ self.name, formatting.safe_repr(self.type_name)),
+ level=validation.Issue.BETWEEN_TYPES)
+
+ utils.validate_dict_values(context, self.inputs)
+ utils.validate_dict_values(context, self.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: {0}'.format(context.style.type(self.type_name)))
+ utils.dump_parameters(context, self.inputs, 'Inputs')
+ utils.dump_dict_values(context, self.operation_templates, 'Operation templates')
+
+
+class OperationTemplateBase(_TemplateModelMixin):
+ """
+ An operation in a :class:`InterfaceTemplate`.
+
+ :ivar name: Name
+ :ivar description: Description
+ :ivar implementation: Implementation string (interpreted by the orchestrator)
+ :ivar dependencies: List of strings (interpreted by the orchestrator)
+ :ivar executor: Executor string (interpreted by the orchestrator)
+ :ivar max_retries: Maximum number of retries allowed in case of failure
+ :ivar retry_interval: Interval between retries
+ :ivar inputs: Dict of :class:`Parameter`
+ """
+
+ __tablename__ = 'operation_template' # redundancy for PyLint: SqlAlchemy injects this
+
+ __private_fields__ = ['service_template_fk',
+ 'interface_template_fk',
+ 'plugin_fk']
+
+ description = Column(Text)
+ implementation = Column(Text)
+ dependencies = Column(modeling_types.StrictList(item_cls=basestring))
+ executor = Column(Text)
+ max_retries = Column(Integer)
+ retry_interval = Column(Integer)
+
+ @declared_attr
+ def inputs(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='inputs',
+ key_column_name='name')
+
+ @declared_attr
+ def plugin(cls):
+ return cls.one_to_one_relationship('plugin')
+
+ executor = Column(Text)
+ max_retries = Column(Integer)
+ retry_interval = Column(Integer)
+
+ # region foreign keys
+
+ # ServiceTemplate one-to-many to OperationTemplate
+ @declared_attr
+ def service_template_fk(cls):
+ return cls.foreign_key('service_template', nullable=True)
+
+ # InterfaceTemplate one-to-many to OperationTemplate
+ @declared_attr
+ def interface_template_fk(cls):
+ return cls.foreign_key('interface_template', nullable=True)
+
+ # OperationTemplate one-to-one to Plugin
+ @declared_attr
+ def plugin_fk(cls):
+ return cls.foreign_key('plugin', nullable=True)
+
+ # 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 models
+ operation = models.Operation(name=self.name,
+ description=deepcopy_with_locators(self.description),
+ implementation=self.implementation,
+ dependencies=self.dependencies,
+ plugin=self.plugin,
+ executor=self.executor,
+ max_retries=self.max_retries,
+ retry_interval=self.retry_interval,
+ operation_template=self)
+ 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: {0}'.format(
+ context.style.literal(self.implementation)))
+ if self.dependencies:
+ console.puts('Dependencies: {0}'.format(
+ ', '.join((str(context.style.literal(v)) for v in self.dependencies))))
+ if self.executor is not None:
+ console.puts('Executor: {0}'.format(context.style.literal(self.executor)))
+ if self.max_retries is not None:
+ console.puts('Max retries: {0}'.format(context.style.literal(self.max_retries)))
+ if self.retry_interval is not None:
+ console.puts('Retry interval: {0}'.format(
+ context.style.literal(self.retry_interval)))
+ utils.dump_parameters(context, self.inputs, 'Inputs')
+
+
+class ArtifactTemplateBase(_TemplateModelMixin):
+ """
+ A file associated with a :class:`NodeTemplate`.
+
+ :ivar name: Name
+ :ivar description: Description
+ :ivar type_name: Must be represented in the :class:`ModelingContext`
+ :ivar source_path: Source path (CSAR or repository)
+ :ivar target_path: Path at destination machine
+ :ivar repository_url: Repository URL
+ :ivar repository_credential: Dict of string
+ :ivar properties: Dict of :class:`Parameter`
+ """
+
+ __tablename__ = 'artifact_template' # redundancy for PyLint: SqlAlchemy injects this
+
+ __private_fields__ = ['node_template_fk']
+
+ description = Column(Text)
+ type_name = Column(Text)
+ source_path = Column(Text)
+ target_path = Column(Text)
+ repository_url = Column(Text)
+ repository_credential = Column(modeling_types.StrictDict(basestring, basestring))
+
+ @declared_attr
+ def properties(cls):
+ return cls.many_to_many_relationship('parameter', table_prefix='properties',
+ key_column_name='name')
+
+ # region foreign keys
+
+ # NodeTemplate one-to-many to ArtifactTemplate
+ @declared_attr
+ def node_template_fk(cls):
+ return cls.foreign_key('node_template')
+
+ # 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 instantiate(self, context, container):
+ from . import models
+ artifact = models.Artifact(name=self.name,
+ type_name=self.type_name,
+ source_path=self.source_path,
+ description=deepcopy_with_locators(self.description),
+ target_path=self.target_path,
+ repository_url=self.repository_url,
+ repository_credential=self.repository_credential,
+ artifact_template=self)
+ 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 "{0}" has an unknown type: {1}'.format(
+ self.name, formatting.safe_repr(self.type_name)),
+ level=validation.Issue.BETWEEN_TYPES)
+
+ utils.validate_dict_values(context, self.properties)
+
+ def coerce_values(self, context, container, report_issues):
+ 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: {0}'.format(context.style.type(self.type_name)))
+ console.puts('Source path: {0}'.format(context.style.literal(self.source_path)))
+ if self.target_path is not None:
+ console.puts('Target path: {0}'.format(context.style.literal(self.target_path)))
+ if self.repository_url is not None:
+ console.puts('Repository URL: {0}'.format(
+ context.style.literal(self.repository_url)))
+ if self.repository_credential:
+ console.puts('Repository credential: {0}'.format(
+ context.style.literal(self.repository_credential)))
+ utils.dump_parameters(context, self.properties)
+
+
+
+class ParameterBase(_TemplateModelMixin):
+ """
+ Represents a typed value.
+
+ This class is used by both service template and service instance elements.
+
+ :ivar name: Name
+ :ivar type_name: Type name
+ :ivar value: Value
+ :ivar description: Description
+ """
+
+ __tablename__ = 'parameter' # redundancy for PyLint: SqlAlchemy injects this
+
+ 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 collections.OrderedDict((
+ ('name', self.name),
+ ('type_name', self.type_name),
+ ('value', self.value),
+ ('description', self.description)))
+
+ @property
+ def value(self):
+ if self.str_value is None:
+ return None
+ if self.type_name is None:
+ return self.str_value
+ try:
+ type_name = self.type_name.lower()
+ if type_name in ('str', 'unicode'):
+ return self.str_value.decode('utf-8')
+ elif type_name == 'int':
+ return int(self.str_value)
+ elif type_name == 'bool':
+ return bool(self.str_value)
+ elif type_name == '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 models
+ return models.Parameter(name=self.name,
+ 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 = parser_utils.coerce_value(context, container, self.str_value,
+ report_issues)
+
+
+class MetadataBase(_TemplateModelMixin):
+ """
+ Custom values associated with the service.
+
+ This class is used by both service template and service instance elements.
+
+ :ivar name: Name
+ :ivar value: Value
+ """
+
+ __tablename__ = 'metadata' # redundancy for PyLint: SqlAlchemy injects this
+
+ name = Column(Text, nullable=False)
+ value = Column(Text)
+
+ @property
+ def as_raw(self):
+ return collections.OrderedDict((
+ ('name', self.name),
+ ('value', self.value)))
+
+ def instantiate(self, context, container):
+ from . import models
+ return models.Metadata(name=self.name,
+ value=self.value)
+
+
+class Function(object):
+ """
+ An intrinsic function.
+
+ Serves as a placeholder for a value that should eventually be derived by calling the function.
+ """
+
+ @property
+ def as_raw(self):
+ raise NotImplementedError
+
+ def _evaluate(self, context, container):
+ raise NotImplementedError
+
+ def __deepcopy__(self, memo):
+ # Circumvent cloning in order to maintain our state
+ return self
+
+
+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])