blob: 12195a13554ddc420a94186387c5f7909b576bd5 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# pylint: disable=too-many-lines, no-self-argument, no-member, abstract-method
from __future__ import absolute_import # so we can import standard 'types'
from datetime import datetime
from sqlalchemy import (
Column,
Text,
Integer,
Boolean,
DateTime,
PickleType
)
from sqlalchemy.ext.declarative import declared_attr
from sqlalchemy.ext.associationproxy import association_proxy
from ..parser import validation
from ..parser.consumption import ConsumptionContext
from ..parser.reading import deepcopy_with_locators
from ..utils import (collections, formatting, console)
from ..utils.versions import VersionString
from .mixins import TemplateModelMixin
from . import (
relationship,
utils,
types as modeling_types
)
class ServiceTemplateBase(TemplateModelMixin):
"""
A service template is a source for creating :class:`Service` instances.
It is usually created by various DSL parsers, such as ARIA's TOSCA extension. However, it can
also be created programmatically.
:ivar name: Name (unique for this ARIA installation)
:vartype name: basestring
:ivar description: Human-readable description
:vartype description: basestring
:ivar main_file_name: Filename of CSAR or YAML file from which this service template was parsed
:vartype main_file_name: basestring
:ivar meta_data: Custom annotations
:vartype meta_data: {basestring: :class:`Metadata`}
:ivar node_templates: Templates for creating nodes
:vartype node_templates: {basestring: :class:`NodeTemplate`}
:ivar group_templates: Templates for creating groups
:vartype group_templates: {basestring: :class:`GroupTemplate`}
:ivar policy_templates: Templates for creating policies
:vartype policy_templates: {basestring: :class:`PolicyTemplate`}
:ivar substitution_template: The entire service can appear as a node
:vartype substitution_template: :class:`SubstitutionTemplate`
:ivar inputs: Externally provided parameters
:vartype inputs: {basestring: :class:`Parameter`}
:ivar outputs: These parameters are filled in after service installation
:vartype outputs: {basestring: :class:`Parameter`}
:ivar workflow_templates: Custom workflows that can be performed on the service
:vartype workflow_templates: {basestring: :class:`OperationTemplate`}
:ivar plugin_specifications: Plugins used by the service
:vartype plugin_specifications: {basestring: :class:`PluginSpecification`}
:ivar node_types: Base for the node type hierarchy
:vartype node_types: :class:`Type`
:ivar group_types: Base for the group type hierarchy
:vartype group_types: :class:`Type`
:ivar policy_types: Base for the policy type hierarchy
:vartype policy_types: :class:`Type`
:ivar relationship_types: Base for the relationship type hierarchy
:vartype relationship_types: :class:`Type`
:ivar capability_types: Base for the capability type hierarchy
:vartype capability_types: :class:`Type`
:ivar interface_types: Base for the interface type hierarchy
:vartype interface_types: :class:`Type`
:ivar artifact_types: Base for the artifact type hierarchy
:vartype artifact_types: :class:`Type`
:ivar created_at: Creation timestamp
:vartype created_at: :class:`datetime.datetime`
:ivar updated_at: Update timestamp
:vartype updated_at: :class:`datetime.datetime`
:ivar services: Instantiated services
:vartype services: [:class:`Service`]
"""
__tablename__ = 'service_template'
__private_fields__ = ['substitution_template_fk',
'node_type_fk',
'group_type_fk',
'policy_type_fk',
'relationship_type_fk',
'capability_type_fk',
'interface_type_fk',
'artifact_type_fk']
description = Column(Text)
main_file_name = Column(Text)
created_at = Column(DateTime, nullable=False, index=True)
updated_at = Column(DateTime)
# region foreign keys
@declared_attr
def substitution_template_fk(cls):
"""For ServiceTemplate one-to-one to SubstitutionTemplate"""
return relationship.foreign_key('substitution_template', nullable=True)
@declared_attr
def node_type_fk(cls):
"""For ServiceTemplate one-to-one to Type"""
return relationship.foreign_key('type', nullable=True)
@declared_attr
def group_type_fk(cls):
"""For ServiceTemplate one-to-one to Type"""
return relationship.foreign_key('type', nullable=True)
@declared_attr
def policy_type_fk(cls):
"""For ServiceTemplate one-to-one to Type"""
return relationship.foreign_key('type', nullable=True)
@declared_attr
def relationship_type_fk(cls):
"""For ServiceTemplate one-to-one to Type"""
return relationship.foreign_key('type', nullable=True)
@declared_attr
def capability_type_fk(cls):
"""For ServiceTemplate one-to-one to Type"""
return relationship.foreign_key('type', nullable=True)
@declared_attr
def interface_type_fk(cls):
"""For ServiceTemplate one-to-one to Type"""
return relationship.foreign_key('type', nullable=True)
@declared_attr
def artifact_type_fk(cls):
"""For ServiceTemplate one-to-one to Type"""
return relationship.foreign_key('type', nullable=True)
# endregion
# region association proxies
# endregion
# region one_to_one relationships
@declared_attr
def substitution_template(cls):
return relationship.one_to_one(
cls, 'substitution_template', back_populates=relationship.NO_BACK_POP)
@declared_attr
def node_types(cls):
return relationship.one_to_one(
cls, 'type', fk='node_type_fk', back_populates=relationship.NO_BACK_POP)
@declared_attr
def group_types(cls):
return relationship.one_to_one(
cls, 'type', fk='group_type_fk', back_populates=relationship.NO_BACK_POP)
@declared_attr
def policy_types(cls):
return relationship.one_to_one(
cls, 'type', fk='policy_type_fk', back_populates=relationship.NO_BACK_POP)
@declared_attr
def relationship_types(cls):
return relationship.one_to_one(
cls, 'type', fk='relationship_type_fk', back_populates=relationship.NO_BACK_POP)
@declared_attr
def capability_types(cls):
return relationship.one_to_one(
cls, 'type', fk='capability_type_fk', back_populates=relationship.NO_BACK_POP)
@declared_attr
def interface_types(cls):
return relationship.one_to_one(
cls, 'type', fk='interface_type_fk', back_populates=relationship.NO_BACK_POP)
@declared_attr
def artifact_types(cls):
return relationship.one_to_one(
cls, 'type', fk='artifact_type_fk', back_populates=relationship.NO_BACK_POP)
# endregion
# region one_to_many relationships
@declared_attr
def services(cls):
return relationship.one_to_many(cls, 'service', dict_key='name')
@declared_attr
def node_templates(cls):
return relationship.one_to_many(cls, 'node_template', dict_key='name')
@declared_attr
def group_templates(cls):
return relationship.one_to_many(cls, 'group_template', dict_key='name')
@declared_attr
def policy_templates(cls):
return relationship.one_to_many(cls, 'policy_template', dict_key='name')
@declared_attr
def workflow_templates(cls):
return relationship.one_to_many(cls, 'operation_template', dict_key='name')
@declared_attr
def plugin_specifications(cls):
return relationship.one_to_many(cls, 'plugin_specification', dict_key='name')
# endregion
# region many_to_one relationships
# 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 relationship.many_to_many(cls, 'metadata', dict_key='name')
@declared_attr
def inputs(cls):
return relationship.many_to_many(cls, 'parameter', prefix='inputs', dict_key='name')
@declared_attr
def outputs(cls):
return relationship.many_to_many(cls, 'parameter', prefix='outputs', dict_key='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)),
('workflow_templates', formatting.as_raw_list(self.workflow_templates))))
@property
def types_as_raw(self):
return collections.OrderedDict((
('node_types', formatting.as_raw(self.node_types)),
('group_types', formatting.as_raw(self.group_types)),
('policy_types', formatting.as_raw(self.policy_types)),
('relationship_types', formatting.as_raw(self.relationship_types)),
('capability_types', formatting.as_raw(self.capability_types)),
('interface_types', formatting.as_raw(self.interface_types)),
('artifact_types', formatting.as_raw(self.artifact_types))))
def instantiate(self, container, model_storage, inputs=None): # pylint: disable=arguments-differ
from . import models
context = ConsumptionContext.get_thread_local()
now = datetime.now()
service = models.Service(created_at=now,
updated_at=now,
description=deepcopy_with_locators(self.description),
service_template=self)
context.modeling.instance = service
service.inputs = utils.create_inputs(inputs or {}, self.inputs)
# TODO: now that we have inputs, we should scan properties and inputs and evaluate functions
for plugin_specification in self.plugin_specifications.itervalues():
if plugin_specification.enabled:
if plugin_specification.resolve(model_storage):
plugin = plugin_specification.plugin
service.plugins[plugin.name] = plugin
else:
context = ConsumptionContext.get_thread_local()
context.validation.report('specified plugin not found: {0}'.format(
plugin_specification.name), level=validation.Issue.EXTERNAL)
utils.instantiate_dict(self, service.meta_data, self.meta_data)
for node_template in self.node_templates.itervalues():
for _ in range(node_template.default_instances):
node = node_template.instantiate(container)
service.nodes[node.name] = node
utils.instantiate_dict(self, service.groups, self.group_templates)
utils.instantiate_dict(self, service.policies, self.policy_templates)
utils.instantiate_dict(self, service.workflows, self.workflow_templates)
if self.substitution_template is not None:
service.substitution = self.substitution_template.instantiate(container)
utils.instantiate_dict(self, service.outputs, self.outputs)
return service
def validate(self):
utils.validate_dict_values(self.meta_data)
utils.validate_dict_values(self.node_templates)
utils.validate_dict_values(self.group_templates)
utils.validate_dict_values(self.policy_templates)
if self.substitution_template is not None:
self.substitution_template.validate()
utils.validate_dict_values(self.inputs)
utils.validate_dict_values(self.outputs)
utils.validate_dict_values(self.workflow_templates)
if self.node_types is not None:
self.node_types.validate()
if self.group_types is not None:
self.group_types.validate()
if self.policy_types is not None:
self.policy_types.validate()
if self.relationship_types is not None:
self.relationship_types.validate()
if self.capability_types is not None:
self.capability_types.validate()
if self.interface_types is not None:
self.interface_types.validate()
if self.artifact_types is not None:
self.artifact_types.validate()
def coerce_values(self, report_issues):
utils.coerce_dict_values(self.meta_data, report_issues)
utils.coerce_dict_values(self.node_templates, report_issues)
utils.coerce_dict_values(self.group_templates, report_issues)
utils.coerce_dict_values(self.policy_templates, report_issues)
if self.substitution_template is not None:
self.substitution_template.coerce_values(report_issues)
utils.coerce_dict_values(self.inputs, report_issues)
utils.coerce_dict_values(self.outputs, report_issues)
utils.coerce_dict_values(self.workflow_templates, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
if self.description is not None:
console.puts(context.style.meta(self.description))
utils.dump_dict_values(self.meta_data, 'Metadata')
for node_template in self.node_templates.itervalues():
node_template.dump()
for group_template in self.group_templates.itervalues():
group_template.dump()
for policy_template in self.policy_templates.itervalues():
policy_template.dump()
if self.substitution_template is not None:
self.substitution_template.dump()
utils.dump_dict_values(self.inputs, 'Inputs')
utils.dump_dict_values(self.outputs, 'Outputs')
utils.dump_dict_values(self.workflow_templates, 'Workflow templates')
def dump_types(self):
if self.node_types.children:
console.puts('Node types:')
self.node_types.dump()
if self.group_types.children:
console.puts('Group types:')
self.group_types.dump()
if self.capability_types.children:
console.puts('Capability types:')
self.capability_types.dump()
if self.relationship_types.children:
console.puts('Relationship types:')
self.relationship_types.dump()
if self.policy_types.children:
console.puts('Policy types:')
self.policy_types.dump()
if self.artifact_types.children:
console.puts('Artifact types:')
self.artifact_types.dump()
if self.interface_types.children:
console.puts('Interface types:')
self.interface_types.dump()
class NodeTemplateBase(TemplateModelMixin):
"""
A template for creating zero or more :class:`Node` instances.
:ivar name: Name (unique for this service template; will usually be used as a prefix for node
names)
:vartype name: basestring
:ivar type: Node type
:vartype type: :class:`Type`
:ivar description: Human-readable description
:vartype description: basestring
:ivar default_instances: Default number nodes that will appear in the service
:vartype default_instances: int
:ivar min_instances: Minimum number nodes that will appear in the service
:vartype min_instances: int
:ivar max_instances: Maximum number nodes that will appear in the service
:vartype max_instances: int
:ivar properties: Associated parameters
:vartype properties: {basestring: :class:`Parameter`}
:ivar interface_templates: Bundles of operations
:vartype interface_templates: {basestring: :class:`InterfaceTemplate`}
:ivar artifact_templates: Associated files
:vartype artifact_templates: {basestring: :class:`ArtifactTemplate`}
:ivar capability_templates: Exposed capabilities
:vartype capability_templates: {basestring: :class:`CapabilityTemplate`}
:ivar requirement_templates: Potential relationships with other nodes
:vartype requirement_templates: [:class:`RequirementTemplate`]
:ivar target_node_template_constraints: Constraints for filtering relationship targets
:vartype target_node_template_constraints: [:class:`NodeTemplateConstraint`]
:ivar service_template: Containing service template
:vartype service_template: :class:`ServiceTemplate`
:ivar group_templates: We are a member of these groups
:vartype group_templates: [:class:`GroupTemplate`]
:ivar policy_templates: Policy templates enacted on this node
:vartype policy_templates: [:class:`PolicyTemplate`]
:ivar substitution_template_mapping: Our contribution to service substitution
:vartype substitution_template_mapping: :class:`SubstitutionTemplateMapping`
:ivar nodes: Instantiated nodes
:vartype nodes: [:class:`Node`]
"""
__tablename__ = 'node_template'
__private_fields__ = ['type_fk',
'service_template_fk']
# region foreign_keys
@declared_attr
def type_fk(cls):
"""For NodeTemplate many-to-one to Type"""
return relationship.foreign_key('type')
@declared_attr
def service_template_fk(cls):
"""For ServiceTemplate one-to-many to NodeTemplate"""
return relationship.foreign_key('service_template')
# endregion
# region association proxies
@declared_attr
def service_template_name(cls):
"""Required for use by SQLAlchemy queries"""
return association_proxy('service_template', 'name')
@declared_attr
def type_name(cls):
"""Required for use by SQLAlchemy queries"""
return association_proxy('type', 'name')
# endregion
# region one_to_one relationships
# endregion
# region one_to_many relationships
@declared_attr
def nodes(cls):
return relationship.one_to_many(cls, 'node')
@declared_attr
def interface_templates(cls):
return relationship.one_to_many(cls, 'interface_template', dict_key='name')
@declared_attr
def artifact_templates(cls):
return relationship.one_to_many(cls, 'artifact_template', dict_key='name')
@declared_attr
def capability_templates(cls):
return relationship.one_to_many(cls, 'capability_template', dict_key='name')
@declared_attr
def requirement_templates(cls):
return relationship.one_to_many(cls, 'requirement_template', child_fk='node_template_fk')
# endregion
# region many_to_one relationships
@declared_attr
def type(cls):
return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
@declared_attr
def service_template(cls):
return relationship.many_to_one(cls, 'service_template')
# endregion
# region many_to_many relationships
@declared_attr
def properties(cls):
return relationship.many_to_many(cls, 'parameter', prefix='properties', dict_key='name')
@declared_attr
def attributes(cls):
return relationship.many_to_many(cls, 'parameter', prefix='attributes', dict_key='name')
# endregion
description = 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(PickleType)
def is_target_node_template_valid(self, target_node_template):
if self.target_node_template_constraints:
for node_template_constraint in self.target_node_template_constraints:
if not node_template_constraint.matches(self, target_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),
('default_instances', self.default_instances),
('min_instances', self.min_instances),
('max_instances', self.max_instances),
('properties', formatting.as_raw_dict(self.properties)),
('attributes', 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, container):
from . import models
if self.nodes:
highest_name_suffix = max(int(n.name.rsplit('_', 1)[-1]) for n in self.nodes)
suffix = highest_name_suffix + 1
else:
suffix = 1
name = '{name}_{index}'.format(name=self.name, index=suffix)
node = models.Node(name=name,
type=self.type,
description=deepcopy_with_locators(self.description),
state=models.Node.INITIAL,
runtime_properties={},
node_template=self)
utils.instantiate_dict(node, node.properties, self.properties)
utils.instantiate_dict(node, node.attributes, self.attributes)
utils.instantiate_dict(node, node.interfaces, self.interface_templates)
utils.instantiate_dict(node, node.artifacts, self.artifact_templates)
utils.instantiate_dict(node, node.capabilities, self.capability_templates)
# Default attributes
if 'tosca_name' in node.attributes:
node.attributes['tosca_name'].value = self.name
if 'tosca_id' in node.attributes:
node.attributes['tosca_id'].value = name
return node
def validate(self):
utils.validate_dict_values(self.properties)
utils.validate_dict_values(self.attributes)
utils.validate_dict_values(self.interface_templates)
utils.validate_dict_values(self.artifact_templates)
utils.validate_dict_values(self.capability_templates)
utils.validate_list_values(self.requirement_templates)
def coerce_values(self, report_issues):
utils.coerce_dict_values(self.properties, report_issues)
utils.coerce_dict_values(self.attributes, report_issues)
utils.coerce_dict_values(self.interface_templates, report_issues)
utils.coerce_dict_values(self.artifact_templates, report_issues)
utils.coerce_dict_values(self.capability_templates, report_issues)
utils.coerce_list_values(self.requirement_templates, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
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_dict_values(self.properties, 'Properties')
utils.dump_dict_values(self.attributes, 'Attributes')
utils.dump_interfaces(self.interface_templates)
utils.dump_dict_values(self.artifact_templates, 'Artifact templates')
utils.dump_dict_values(self.capability_templates, 'Capability templates')
utils.dump_list_values(self.requirement_templates, 'Requirement templates')
class GroupTemplateBase(TemplateModelMixin):
"""
A template for creating a :class:`Group` instance.
Groups are logical containers for zero or more nodes.
:ivar name: Name (unique for this service template)
:vartype name: basestring
:ivar type: Group type
:vartype type: :class:`Type`
:ivar description: Human-readable description
:vartype description: basestring
:ivar node_templates: All nodes instantiated by these templates will be members of the group
:vartype node_templates: [:class:`NodeTemplate`]
:ivar properties: Associated parameters
:vartype properties: {basestring: :class:`Parameter`}
:ivar interface_templates: Bundles of operations
:vartype interface_templates: {basestring: :class:`InterfaceTemplate`}
:ivar service_template: Containing service template
:vartype service_template: :class:`ServiceTemplate`
:ivar policy_templates: Policy templates enacted on this group
:vartype policy_templates: [:class:`PolicyTemplate`]
:ivar groups: Instantiated groups
:vartype groups: [:class:`Group`]
"""
__tablename__ = 'group_template'
__private_fields__ = ['type_fk',
'service_template_fk']
# region foreign keys
@declared_attr
def type_fk(cls):
"""For GroupTemplate many-to-one to Type"""
return relationship.foreign_key('type')
@declared_attr
def service_template_fk(cls):
"""For ServiceTemplate one-to-many to GroupTemplate"""
return relationship.foreign_key('service_template')
# endregion
# region association proxies
# endregion
# region one_to_one relationships
# endregion
# region one_to_many relationships
@declared_attr
def groups(cls):
return relationship.one_to_many(cls, 'group')
@declared_attr
def interface_templates(cls):
return relationship.one_to_many(cls, 'interface_template', dict_key='name')
# endregion
# region many_to_one relationships
@declared_attr
def service_template(cls):
return relationship.many_to_one(cls, 'service_template')
@declared_attr
def type(cls):
return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
# endregion
# region many_to_many relationships
@declared_attr
def node_templates(cls):
return relationship.many_to_many(cls, 'node_template')
@declared_attr
def properties(cls):
return relationship.many_to_many(cls, 'parameter', prefix='properties', dict_key='name')
# endregion
description = Column(Text)
@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))))
def instantiate(self, container):
from . import models
group = models.Group(name=self.name,
type=self.type,
description=deepcopy_with_locators(self.description),
group_template=self)
utils.instantiate_dict(self, group.properties, self.properties)
utils.instantiate_dict(self, group.interfaces, self.interface_templates)
if self.node_templates:
for node_template in self.node_templates:
group.nodes += node_template.nodes
return group
def validate(self):
utils.validate_dict_values(self.properties)
utils.validate_dict_values(self.interface_templates)
def coerce_values(self, report_issues):
utils.coerce_dict_values(self.properties, report_issues)
utils.coerce_dict_values(self.interface_templates, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
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:
console.puts('Type: {0}'.format(context.style.type(self.type.name)))
utils.dump_dict_values(self.properties, 'Properties')
utils.dump_interfaces(self.interface_templates)
if self.node_templates:
console.puts('Member node templates: {0}'.format(', '.join(
(str(context.style.node(v.name)) for v in self.node_templates))))
class PolicyTemplateBase(TemplateModelMixin):
"""
Policies can be applied to zero or more :class:`NodeTemplate` or :class:`GroupTemplate`
instances.
:ivar name: Name (unique for this service template)
:vartype name: basestring
:ivar type: Policy type
:vartype type: :class:`Type`
:ivar description: Human-readable description
:vartype description: basestring
:ivar node_templates: Policy will be enacted on all nodes instantiated by these templates
:vartype node_templates: [:class:`NodeTemplate`]
:ivar group_templates: Policy will be enacted on all nodes in these groups
:vartype group_templates: [:class:`GroupTemplate`]
:ivar properties: Associated parameters
:vartype properties: {basestring: :class:`Parameter`}
:ivar service_template: Containing service template
:vartype service_template: :class:`ServiceTemplate`
:ivar policies: Instantiated policies
:vartype policies: [:class:`Policy`]
"""
__tablename__ = 'policy_template'
__private_fields__ = ['type_fk', 'service_template_fk']
# region foreign keys
@declared_attr
def type_fk(cls):
"""For PolicyTemplate many-to-one to Type"""
return relationship.foreign_key('type')
@declared_attr
def service_template_fk(cls):
"""For ServiceTemplate one-to-many to PolicyTemplate"""
return relationship.foreign_key('service_template')
# endregion
# region association proxies
# endregion
# region one_to_one relationships
# endregion
# region one_to_many relationships
@declared_attr
def policies(cls):
return relationship.one_to_many(cls, 'policy')
# endregion
# region many_to_one relationships
@declared_attr
def service_template(cls):
return relationship.many_to_one(cls, 'service_template')
@declared_attr
def type(cls):
return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
# endregion
# region many_to_many relationships
@declared_attr
def node_templates(cls):
return relationship.many_to_many(cls, 'node_template')
@declared_attr
def group_templates(cls):
return relationship.many_to_many(cls, 'group_template')
@declared_attr
def properties(cls):
return relationship.many_to_many(cls, 'parameter', prefix='properties', dict_key='name')
# endregion
description = Column(Text)
@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))))
def instantiate(self, container):
from . import models
policy = models.Policy(name=self.name,
type=self.type,
description=deepcopy_with_locators(self.description),
policy_template=self)
utils.instantiate_dict(self, policy.properties, self.properties)
if self.node_templates:
for node_template in self.node_templates:
policy.nodes += node_template.nodes
if self.group_templates:
for group_template in self.group_templates:
policy.groups += group_template.groups
return policy
def validate(self):
utils.validate_dict_values(self.properties)
def coerce_values(self, report_issues):
utils.coerce_dict_values(self.properties, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
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_dict_values(self.properties, 'Properties')
if self.node_templates:
console.puts('Target node templates: {0}'.format(', '.join(
(str(context.style.node(v.name)) for v in self.node_templates))))
if self.group_templates:
console.puts('Target group templates: {0}'.format(', '.join(
(str(context.style.node(v.name)) for v in self.group_templates))))
class SubstitutionTemplateBase(TemplateModelMixin):
"""
Used to substitute a single node for the entire deployment.
:ivar node_type: Exposed node type
:vartype node_type: :class:`Type`
:ivar mappings: Requirement and capability mappings
:vartype mappings: {basestring: :class:`SubstitutionTemplateMapping`}
:ivar service_template: Containing service template
:vartype service_template: :class:`ServiceTemplate`
:ivar substitutions: Instantiated substitutions
:vartype substitutions: [:class:`Substitution`]
"""
__tablename__ = 'substitution_template'
__private_fields__ = ['node_type_fk']
# region foreign keys
@declared_attr
def node_type_fk(cls):
"""For SubstitutionTemplate many-to-one to Type"""
return relationship.foreign_key('type')
# endregion
# region association proxies
# endregion
# region one_to_one relationships
# endregion
# region one_to_many relationships
@declared_attr
def substitutions(cls):
return relationship.one_to_many(cls, 'substitution')
@declared_attr
def mappings(cls):
return relationship.one_to_many(cls, 'substitution_template_mapping', dict_key='name')
# endregion
# region many_to_one relationships
@declared_attr
def node_type(cls):
return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
# endregion
# region many_to_many relationships
# endregion
@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, container):
from . import models
substitution = models.Substitution(node_type=self.node_type,
substitution_template=self)
utils.instantiate_dict(container, substitution.mappings, self.mappings)
return substitution
def validate(self):
utils.validate_dict_values(self.mappings)
def coerce_values(self, report_issues):
utils.coerce_dict_values(self.mappings, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
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(self.mappings, 'Mappings')
class SubstitutionTemplateMappingBase(TemplateModelMixin):
"""
Used by :class:`SubstitutionTemplate` to map a capability or a requirement to a node.
Only one of `capability_template` and `requirement_template` can be set.
:ivar name: Exposed capability or requirement name
:vartype name: basestring
:ivar node_template: Node template
:vartype node_template: :class:`NodeTemplate`
:ivar capability_template: Capability template in the node template
:vartype capability_template: :class:`CapabilityTemplate`
:ivar requirement_template: Requirement template in the node template
:vartype requirement_template: :class:`RequirementTemplate`
:ivar substitution_template: Containing substitution template
:vartype substitution_template: :class:`SubstitutionTemplate`
"""
__tablename__ = 'substitution_template_mapping'
__private_fields__ = ['substitution_template_fk',
'node_template_fk',
'capability_template_fk',
'requirement_template_fk']
# region foreign keys
@declared_attr
def substitution_template_fk(cls):
"""For SubstitutionTemplate one-to-many to SubstitutionTemplateMapping"""
return relationship.foreign_key('substitution_template')
@declared_attr
def node_template_fk(cls):
"""For SubstitutionTemplate one-to-one to NodeTemplate"""
return relationship.foreign_key('node_template')
@declared_attr
def capability_template_fk(cls):
"""For SubstitutionTemplate one-to-one to CapabilityTemplate"""
return relationship.foreign_key('capability_template', nullable=True)
@declared_attr
def requirement_template_fk(cls):
"""For SubstitutionTemplate one-to-one to RequirementTemplate"""
return relationship.foreign_key('requirement_template', nullable=True)
# endregion
# region association proxies
# endregion
# region one_to_one relationships
@declared_attr
def node_template(cls):
return relationship.one_to_one(
cls, 'node_template', back_populates=relationship.NO_BACK_POP)
@declared_attr
def capability_template(cls):
return relationship.one_to_one(
cls, 'capability_template', back_populates=relationship.NO_BACK_POP)
@declared_attr
def requirement_template(cls):
return relationship.one_to_one(
cls, 'requirement_template', back_populates=relationship.NO_BACK_POP)
# endregion
# region one_to_many relationships
# endregion
# region many_to_one relationships
@declared_attr
def substitution_template(cls):
return relationship.many_to_one(cls, 'substitution_template', back_populates='mappings')
# endregion
# region many_to_many relationships
# endregion
@property
def as_raw(self):
return collections.OrderedDict((
('name', self.name),))
def coerce_values(self, report_issues):
pass
def instantiate(self, container):
from . import models
context = ConsumptionContext.get_thread_local()
nodes = self.node_template.nodes
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
# The TOSCA spec does not provide a way to choose the node,
# so we will just pick the first one
node = nodes[0]
capability = None
if self.capability_template:
for a_capability in node.capabilities.itervalues():
if a_capability.capability_template.name == self.capability_template.name:
capability = a_capability
return models.SubstitutionMapping(name=self.name,
node=node,
capability=capability,
requirement_template=self.requirement_template)
def validate(self):
context = ConsumptionContext.get_thread_local()
if (self.capability_template is None) and (self.requirement_template is None):
context.validation.report('mapping "{0}" refers to neither capability nor a requirement'
' in node template: {1}'.format(
self.name,
formatting.safe_repr(self.node_template.name)),
level=validation.Issue.BETWEEN_TYPES)
def dump(self):
context = ConsumptionContext.get_thread_local()
console.puts('{0} -> {1}.{2}'.format(
context.style.node(self.name),
context.style.node(self.node_template.name),
context.style.node(self.capability_template.name
if self.capability_template
else self.requirement_template.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 (a node template can have multiple requirements with the same name)
:vartype name: basestring
:ivar target_node_type: Required node type (optional)
:vartype target_node_type: :class:`Type`
:ivar target_node_template: Required node template (optional)
:vartype target_node_template: :class:`NodeTemplate`
:ivar target_capability_type: Required capability type (optional)
:vartype target_capability_type: :class:`Type`
:ivar target_capability_name: Name of capability in target node (optional)
:vartype target_capability_name: basestring
:ivar target_node_template_constraints: Constraints for filtering relationship targets
:vartype target_node_template_constraints: [:class:`NodeTemplateConstraint`]
:ivar relationship_template: Template for relationships (optional)
:vartype relationship_template: :class:`RelationshipTemplate`
:ivar node_template: Containing node template
:vartype node_template: :class:`NodeTemplate`
:ivar substitution_template_mapping: Our contribution to service substitution
:vartype substitution_template_mapping: :class:`SubstitutionTemplateMapping`
:ivar substitution_mapping: Our contribution to service substitution
:vartype substitution_mapping: :class:`SubstitutionMapping`
"""
__tablename__ = 'requirement_template'
__private_fields__ = ['target_node_type_fk',
'target_node_template_fk',
'target_capability_type_fk'
'node_template_fk',
'relationship_template_fk']
# region foreign keys
@declared_attr
def target_node_type_fk(cls):
"""For RequirementTemplate many-to-one to Type"""
return relationship.foreign_key('type', nullable=True)
@declared_attr
def target_node_template_fk(cls):
"""For RequirementTemplate one-to-one to NodeTemplate"""
return relationship.foreign_key('node_template', nullable=True)
@declared_attr
def target_capability_type_fk(cls):
"""For RequirementTemplate one-to-one to NodeTemplate"""
return relationship.foreign_key('type', nullable=True)
@declared_attr
def node_template_fk(cls):
"""For NodeTemplate one-to-many to RequirementTemplate"""
return relationship.foreign_key('node_template')
@declared_attr
def relationship_template_fk(cls):
"""For RequirementTemplate one-to-one to RelationshipTemplate"""
return relationship.foreign_key('relationship_template', nullable=True)
# endregion
# region association proxies
# endregion
# region one_to_one relationships
@declared_attr
def target_node_template(cls):
return relationship.one_to_one(cls,
'node_template',
fk='target_node_template_fk',
back_populates=relationship.NO_BACK_POP)
@declared_attr
def target_capability_type(cls):
return relationship.one_to_one(cls,
'type',
fk='target_capability_type_fk',
back_populates=relationship.NO_BACK_POP)
@declared_attr
def relationship_template(cls):
return relationship.one_to_one(cls, 'relationship_template')
# endregion
# region one_to_many relationships
@declared_attr
def relationships(cls):
return relationship.one_to_many(cls, 'relationship')
# endregion
# region many_to_one relationships
@declared_attr
def node_template(cls):
return relationship.many_to_one(cls, 'node_template', fk='node_template_fk')
@declared_attr
def target_node_type(cls):
return relationship.many_to_one(
cls, 'type', fk='target_node_type_fk', back_populates=relationship.NO_BACK_POP)
# endregion
# region many_to_many relationships
# endregion
target_capability_name = Column(Text)
target_node_template_constraints = Column(PickleType)
def find_target(self, source_node_template):
context = ConsumptionContext.get_thread_local()
# We might already have a specific node template, so we'll just verify it
if self.target_node_template is not None:
if not source_node_template.is_target_node_template_valid(self.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)
if (self.target_capability_type is not None) \
or (self.target_capability_name is not None):
target_node_capability = self.find_target_capability(source_node_template,
self.target_node_template)
if target_node_capability is None:
return None, None
else:
target_node_capability = None
return self.target_node_template, target_node_capability
# Find first node that matches the type
elif self.target_node_type is not None:
for target_node_template in \
self.node_template.service_template.node_templates.values():
if self.target_node_type.get_descendant(target_node_template.type.name) is None:
continue
if not source_node_template.is_target_node_template_valid(target_node_template):
continue
target_node_capability = self.find_target_capability(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, source_node_template, target_node_template):
for capability_template in target_node_template.capability_templates.itervalues():
if capability_template.satisfies_requirement(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
if self.target_node_type is not None else None),
('target_node_template_name', self.target_node_template.name
if self.target_node_template is not None else None),
('target_capability_type_name', self.target_capability_type.name
if self.target_capability_type is not None else None),
('target_capability_name', self.target_capability_name),
('relationship_template', formatting.as_raw(self.relationship_template))))
def validate(self):
if self.relationship_template:
self.relationship_template.validate()
def coerce_values(self, report_issues):
if self.relationship_template is not None:
self.relationship_template.coerce_values(report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
if self.name:
console.puts(context.style.node(self.name))
else:
console.puts('Requirement:')
with context.style.indent:
if self.target_node_type is not None:
console.puts('Target node type: {0}'.format(
context.style.type(self.target_node_type.name)))
elif self.target_node_template is not None:
console.puts('Target node template: {0}'.format(
context.style.node(self.target_node_template.name)))
if self.target_capability_type 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()
class RelationshipTemplateBase(TemplateModelMixin):
"""
Optional addition to a :class:`RequirementTemplate` in :class:`NodeTemplate` that can be applied
when the requirement is matched with a capability.
Note that a relationship template here is not equivalent to a relationship template entity in
TOSCA. For example, a TOSCA requirement specifying a relationship type instead of a template
would still be represented here as a relationship template.
:ivar name: Name (optional; if present is unique for this service template)
:vartype name: basestring
:ivar type: Relationship type
:vartype type: :class:`Type`
:ivar description: Human-readable description
:vartype description: basestring
:ivar properties: Associated parameters
:vartype properties: {basestring: :class:`Parameter`}
:ivar interface_templates: Bundles of operations
:vartype interface_templates: {basestring: :class:`InterfaceTemplate`}
:ivar requirement_template: Containing requirement template
:vartype requirement_template: :class:`RequirementTemplate`
:ivar relationships: Instantiated relationships
:vartype relationships: [:class:`Relationship`]
"""
__tablename__ = 'relationship_template'
__private_fields__ = ['type_fk']
# region foreign keys
@declared_attr
def type_fk(cls):
"""For RelationshipTemplate many-to-one to Type"""
return relationship.foreign_key('type', nullable=True)
# endregion
# region association proxies
# endregion
# region one_to_one relationships
# endregion
# region one_to_many relationships
@declared_attr
def relationships(cls):
return relationship.one_to_many(cls, 'relationship')
@declared_attr
def interface_templates(cls):
return relationship.one_to_many(cls, 'interface_template', dict_key='name')
# endregion
# region many_to_one relationships
@declared_attr
def type(cls):
return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
# endregion
# region many_to_many relationships
@declared_attr
def properties(cls):
return relationship.many_to_many(cls, 'parameter', prefix='properties', dict_key='name')
# endregion
description = Column(Text)
@property
def as_raw(self):
return collections.OrderedDict((
('type_name', self.type.name if self.type is not None else None),
('name', self.name),
('description', self.description),
('properties', formatting.as_raw_dict(self.properties)),
('interface_templates', formatting.as_raw_list(self.interface_templates))))
def instantiate(self, container):
from . import models
relationship_model = models.Relationship(name=self.name,
type=self.type,
relationship_template=self)
utils.instantiate_dict(container, relationship_model.properties, self.properties)
utils.instantiate_dict(container, relationship_model.interfaces, self.interface_templates)
return relationship_model
def validate(self):
# TODO: either type or name must be set
utils.validate_dict_values(self.properties)
utils.validate_dict_values(self.interface_templates)
def coerce_values(self, report_issues):
utils.coerce_dict_values(self.properties, report_issues)
utils.coerce_dict_values(self.interface_templates, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
if self.type 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.name)))
if self.description:
console.puts(context.style.meta(self.description))
with context.style.indent:
utils.dump_dict_values(self.properties, 'Properties')
utils.dump_interfaces(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 (unique for the node template)
:vartype name: basestring
:ivar type: Capability type
:vartype type: :class:`Type`
:ivar description: Human-readable description
:vartype description: basestring
:ivar valid_source_node_types: Reject requirements that are not from these node types (optional)
:vartype valid_source_node_types: [:class:`Type`]
:ivar min_occurrences: Minimum number of requirement matches required
:vartype min_occurrences: int
:ivar max_occurrences: Maximum number of requirement matches allowed
:vartype min_occurrences: int
:ivar properties: Associated parameters
:vartype properties: {basestring: :class:`Parameter`}
:ivar node_template: Containing node template
:vartype node_template: :class:`NodeTemplate`
:ivar substitution_template_mapping: Our contribution to service substitution
:vartype substitution_template_mapping: :class:`SubstitutionTemplateMapping`
:ivar capabilities: Instantiated capabilities
:vartype capabilities: [:class:`Capability`]
"""
__tablename__ = 'capability_template'
__private_fields__ = ['type_fk',
'node_template_fk']
# region foreign keys
@declared_attr
def type_fk(cls):
"""For CapabilityTemplate many-to-one to Type"""
return relationship.foreign_key('type')
@declared_attr
def node_template_fk(cls):
"""For NodeTemplate one-to-many to CapabilityTemplate"""
return relationship.foreign_key('node_template')
# endregion
# region association proxies
# endregion
# region one_to_one relationships
# endregion
# region one_to_many relationships
@declared_attr
def capabilities(cls):
return relationship.one_to_many(cls, 'capability')
# endregion
# region many_to_one relationships
@declared_attr
def node_template(cls):
return relationship.many_to_one(cls, 'node_template')
@declared_attr
def type(cls):
return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
# endregion
# region many_to_many relationships
@declared_attr
def valid_source_node_types(cls):
return relationship.many_to_many(cls, 'type', prefix='valid_sources')
@declared_attr
def properties(cls):
return relationship.many_to_many(cls, 'parameter', prefix='properties', dict_key='name')
# endregion
description = Column(Text)
min_occurrences = Column(Integer, default=None) # optional
max_occurrences = Column(Integer, default=None) # optional
def satisfies_requirement(self,
source_node_template,
requirement,
target_node_template):
# Do we match the required capability type?
if requirement.target_capability_type and \
requirement.target_capability_type.get_descendant(self.type.name) is None:
return False
# Are we in valid_source_node_types?
if self.valid_source_node_types:
for valid_source_node_type in self.valid_source_node_types:
if valid_source_node_type.get_descendant(source_node_template.type.name) is None:
return False
# Apply requirement constraints
if requirement.target_node_template_constraints:
for node_template_constraint in requirement.target_node_template_constraints:
if not node_template_constraint.matches(source_node_template, target_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_types', [v.name for v in self.valid_source_node_types]),
('properties', formatting.as_raw_dict(self.properties))))
def instantiate(self, container):
from . import models
capability = models.Capability(name=self.name,
type=self.type,
min_occurrences=self.min_occurrences,
max_occurrences=self.max_occurrences,
occurrences=0,
capability_template=self)
utils.instantiate_dict(container, capability.properties, self.properties)
return capability
def validate(self):
utils.validate_dict_values(self.properties)
def coerce_values(self, report_issues):
utils.coerce_dict_values(self.properties, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
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_types:
console.puts('Valid source node types: {0}'.format(
', '.join((str(context.style.type(v.name))
for v in self.valid_source_node_types))))
utils.dump_dict_values(self.properties, 'Properties')
class InterfaceTemplateBase(TemplateModelMixin):
"""
A typed set of :class:`OperationTemplate`.
:ivar name: Name (unique for the node, group, or relationship template)
:vartype name: basestring
:ivar type: Interface type
:vartype type: :class:`Type`
:ivar description: Human-readable description
:vartype description: basestring
:ivar inputs: Parameters that can be used by all operations in the interface
:vartype inputs: {basestring: :class:`Parameter`}
:ivar operation_templates: Operations
:vartype operation_templates: {basestring: :class:`OperationTemplate`}
:ivar node_template: Containing node template
:vartype node_template: :class:`NodeTemplate`
:ivar group_template: Containing group template
:vartype group_template: :class:`GroupTemplate`
:ivar relationship_template: Containing relationship template
:vartype relationship_template: :class:`RelationshipTemplate`
:ivar interfaces: Instantiated interfaces
:vartype interfaces: [:class:`Interface`]
"""
__tablename__ = 'interface_template'
__private_fields__ = ['type_fk',
'node_template_fk',
'group_template_fk',
'relationship_template_fk']
# region foreign keys
@declared_attr
def type_fk(cls):
"""For InterfaceTemplate many-to-one to Type"""
return relationship.foreign_key('type')
@declared_attr
def node_template_fk(cls):
"""For NodeTemplate one-to-many to InterfaceTemplate"""
return relationship.foreign_key('node_template', nullable=True)
@declared_attr
def group_template_fk(cls):
"""For GroupTemplate one-to-many to InterfaceTemplate"""
return relationship.foreign_key('group_template', nullable=True)
@declared_attr
def relationship_template_fk(cls):
"""For RelationshipTemplate one-to-many to InterfaceTemplate"""
return relationship.foreign_key('relationship_template', nullable=True)
# endregion
# region association proxies
# endregion
# region one_to_one relationships
# endregion
# region one_to_many relationships
@declared_attr
def interfaces(cls):
return relationship.one_to_many(cls, 'interface')
@declared_attr
def operation_templates(cls):
return relationship.one_to_many(cls, 'operation_template', dict_key='name')
# endregion
# region many_to_one relationships
@declared_attr
def relationship_template(cls):
return relationship.many_to_one(cls, 'relationship_template')
@declared_attr
def group_template(cls):
return relationship.many_to_one(cls, 'group_template')
@declared_attr
def node_template(cls):
return relationship.many_to_one(cls, 'node_template')
@declared_attr
def type(cls):
return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
# endregion
# region many_to_many relationships
@declared_attr
def inputs(cls):
return relationship.many_to_many(cls, 'parameter', prefix='inputs', dict_key='name')
# endregion
description = Column(Text)
@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, container):
from . import models
interface = models.Interface(name=self.name,
type=self.type,
description=deepcopy_with_locators(self.description),
interface_template=self)
utils.instantiate_dict(container, interface.inputs, self.inputs)
utils.instantiate_dict(container, interface.operations, self.operation_templates)
return interface
def validate(self):
utils.validate_dict_values(self.inputs)
utils.validate_dict_values(self.operation_templates)
def coerce_values(self, report_issues):
utils.coerce_dict_values(self.inputs, report_issues)
utils.coerce_dict_values(self.operation_templates, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
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_dict_values(self.inputs, 'Inputs')
utils.dump_dict_values(self.operation_templates, 'Operation templates')
class OperationTemplateBase(TemplateModelMixin):
"""
An operation in a :class:`InterfaceTemplate`.
Operations are executed by an associated :class:`PluginSpecification` via an executor.
:ivar name: Name (unique for the interface or service template)
:vartype name: basestring
:ivar description: Human-readable description
:vartype description: basestring
:ivar plugin_specification: Associated plugin
:vartype plugin_specification: :class:`PluginSpecification`
:ivar relationship_edge: When true specified that the operation is on the relationship's
target edge instead of its source (only used by relationship
operations)
:vartype relationship_edge: bool
:ivar implementation: Implementation (interpreted by the plugin)
:vartype implementation: basestring
:ivar configuration: Configuration (interpreted by the plugin)
:vartype configuration: {basestring, object}
:ivar dependencies: Dependency strings (interpreted by the plugin)
:vartype dependencies: [basestring]
:ivar inputs: Parameters that can be used by this operation
:vartype inputs: {basestring: :class:`Parameter`}
:ivar executor: Name of executor to run the operation with
:vartype executor: basestring
:ivar max_attempts: Maximum number of attempts allowed in case of failure
:vartype max_attempts: int
:ivar retry_interval: Interval between retries (in seconds)
:vartype retry_interval: int
:ivar interface_template: Containing interface template
:vartype interface_template: :class:`InterfaceTemplate`
:ivar service_template: Containing service template
:vartype service_template: :class:`ServiceTemplate`
:ivar operations: Instantiated operations
:vartype operations: [:class:`Operation`]
"""
__tablename__ = 'operation_template'
__private_fields__ = ['service_template_fk',
'interface_template_fk',
'plugin_fk']
# region foreign keys
@declared_attr
def service_template_fk(cls):
"""For ServiceTemplate one-to-many to OperationTemplate"""
return relationship.foreign_key('service_template', nullable=True)
@declared_attr
def interface_template_fk(cls):
"""For InterfaceTemplate one-to-many to OperationTemplate"""
return relationship.foreign_key('interface_template', nullable=True)
@declared_attr
def plugin_specification_fk(cls):
"""For OperationTemplate one-to-one to PluginSpecification"""
return relationship.foreign_key('plugin_specification', nullable=True)
# endregion
# region association proxies
# endregion
# region one_to_one relationships
@declared_attr
def plugin_specification(cls):
return relationship.one_to_one(
cls, 'plugin_specification', back_populates=relationship.NO_BACK_POP)
# endregion
# region one_to_many relationships
@declared_attr
def operations(cls):
return relationship.one_to_many(cls, 'operation')
# endregion
# region many_to_one relationships
@declared_attr
def service_template(cls):
return relationship.many_to_one(cls, 'service_template',
back_populates='workflow_templates')
@declared_attr
def interface_template(cls):
return relationship.many_to_one(cls, 'interface_template')
# endregion
# region many_to_many relationships
@declared_attr
def inputs(cls):
return relationship.many_to_many(cls, 'parameter', prefix='inputs', dict_key='name')
# endregion
description = Column(Text)
relationship_edge = Column(Boolean)
implementation = Column(Text)
configuration = Column(modeling_types.StrictDict(key_cls=basestring))
dependencies = Column(modeling_types.StrictList(item_cls=basestring))
executor = Column(Text)
max_attempts = Column(Integer)
retry_interval = Column(Integer)
@property
def as_raw(self):
return collections.OrderedDict((
('name', self.name),
('description', self.description),
('implementation', self.implementation),
('dependencies', self.dependencies),
('executor', self.executor),
('max_attempts', self.max_attempts),
('retry_interval', self.retry_interval),
('inputs', formatting.as_raw_dict(self.inputs))))
def instantiate(self, container):
from . import models
if self.plugin_specification:
if self.plugin_specification.enabled:
plugin = self.plugin_specification.plugin
implementation = self.implementation if plugin is not None else None
# "plugin" would be none if a match was not found. In that case, a validation error
# should already have been reported in ServiceTemplateBase.instantiate, so we will
# continue silently here
else:
# If the plugin is disabled, the operation should be disabled, too
plugin = None
implementation = None
else:
# Using the execution plugin
plugin = None
implementation = self.implementation
operation = models.Operation(name=self.name,
description=deepcopy_with_locators(self.description),
relationship_edge=self.relationship_edge,
plugin=plugin,
implementation=implementation,
configuration=self.configuration,
dependencies=self.dependencies,
executor=self.executor,
max_attempts=self.max_attempts,
retry_interval=self.retry_interval,
operation_template=self)
utils.instantiate_dict(container, operation.inputs, self.inputs)
return operation
def validate(self):
utils.validate_dict_values(self.inputs)
def coerce_values(self, report_issues):
utils.coerce_dict_values(self.inputs, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
console.puts(context.style.node(self.name))
if self.description:
console.puts(context.style.meta(self.description))
with context.style.indent:
if self.plugin_specification is not None:
console.puts('Plugin specification: {0}'.format(
context.style.literal(self.plugin_specification.name)))
if self.implementation is not None:
console.puts('Implementation: {0}'.format(
context.style.literal(self.implementation)))
if self.configuration:
with context.style.indent:
for k, v in self.configuration.iteritems():
console.puts('{0}: {1}'.format(context.style.property(k),
context.style.literal(v)))
if self.dependencies:
console.puts('Dependencies: {0}'.format(
', '.join((str(context.style.literal(v)) for v in self.dependencies))))
if self.executor is not None:
console.puts('Executor: {0}'.format(context.style.literal(self.executor)))
if self.max_attempts is not None:
console.puts('Max attempts: {0}'.format(context.style.literal(self.max_attempts)))
if self.retry_interval is not None:
console.puts('Retry interval: {0}'.format(
context.style.literal(self.retry_interval)))
utils.dump_dict_values(self.inputs, 'Inputs')
class ArtifactTemplateBase(TemplateModelMixin):
"""
A file associated with a :class:`NodeTemplate`.
:ivar name: Name (unique for the node template)
:vartype name: basestring
:ivar type: Artifact type
:vartype type: :class:`Type`
:ivar description: Human-readable description
:vartype description: basestring
:ivar source_path: Source path (CSAR or repository)
:vartype source_path: basestring
:ivar target_path: Path at destination machine
:vartype target_path: basestring
:ivar repository_url: Repository URL
:vartype repository_path: basestring
:ivar repository_credential: Credentials for accessing the repository
:vartype repository_credential: {basestring: basestring}
:ivar properties: Associated parameters
:vartype properties: {basestring: :class:`Parameter`}
:ivar node_template: Containing node template
:vartype node_template: :class:`NodeTemplate`
:ivar artifacts: Instantiated artifacts
:vartype artifacts: [:class:`Artifact`]
"""
__tablename__ = 'artifact_template'
__private_fields__ = ['type_fk',
'node_template_fk']
# region foreign keys
@declared_attr
def type_fk(cls):
"""For ArtifactTemplate many-to-one to Type"""
return relationship.foreign_key('type')
@declared_attr
def node_template_fk(cls):
"""For NodeTemplate one-to-many to ArtifactTemplate"""
return relationship.foreign_key('node_template')
# endregion
# region association proxies
# endregion
# region one_to_one relationships
# endregion
# region one_to_many relationships
@declared_attr
def artifacts(cls):
return relationship.one_to_many(cls, 'artifact')
# endregion
# region many_to_one relationships
@declared_attr
def node_template(cls):
return relationship.many_to_one(cls, 'node_template')
@declared_attr
def type(cls):
return relationship.many_to_one(cls, 'type', back_populates=relationship.NO_BACK_POP)
# endregion
# region many_to_many relationships
@declared_attr
def properties(cls):
return relationship.many_to_many(cls, 'parameter', prefix='properties', dict_key='name')
# endregion
description = Column(Text)
source_path = Column(Text)
target_path = Column(Text)
repository_url = Column(Text)
repository_credential = Column(modeling_types.StrictDict(basestring, basestring))
@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, container):
from . import models
artifact = models.Artifact(name=self.name,
type=self.type,
description=deepcopy_with_locators(self.description),
source_path=self.source_path,
target_path=self.target_path,
repository_url=self.repository_url,
repository_credential=self.repository_credential,
artifact_template=self)
utils.instantiate_dict(container, artifact.properties, self.properties)
return artifact
def validate(self):
utils.validate_dict_values(self.properties)
def coerce_values(self, report_issues):
utils.coerce_dict_values(self.properties, report_issues)
def dump(self):
context = ConsumptionContext.get_thread_local()
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_dict_values(self.properties, 'Properties')
class PluginSpecificationBase(TemplateModelMixin):
"""
Plugin specification.
:ivar name: Required plugin name
:vartype name: basestring
:ivar version: Minimum plugin version
:vartype version: basestring
:ivar enabled: Whether the plugin is enabled
:vartype enabled: bool
:ivar plugin: The matching plugin (or None if not matched)
:vartype plugin: :class:`Plugin`
"""
__tablename__ = 'plugin_specification'
__private_fields__ = ['service_template_fk',
'plugin_fk']
version = Column(Text)
enabled = Column(Boolean, nullable=False, default=True)
# region foreign keys
@declared_attr
def service_template_fk(cls):
"""For ServiceTemplate one-to-many to PluginSpecification"""
return relationship.foreign_key('service_template', nullable=True)
@declared_attr
def plugin_fk(cls):
"""For PluginSpecification many-to-one to Plugin"""
return relationship.foreign_key('plugin', nullable=True)
# endregion
# region many_to_one relationships
@declared_attr
def service_template(cls):
return relationship.many_to_one(cls, 'service_template')
@declared_attr
def plugin(cls): # pylint: disable=method-hidden
return relationship.many_to_one(cls, 'plugin', back_populates=relationship.NO_BACK_POP)
# endregion
@property
def as_raw(self):
return collections.OrderedDict((
('name', self.name),
('version', self.version),
('enabled', self.enabled)))
def coerce_values(self, report_issues):
pass
def resolve(self, model_storage):
# TODO: we are planning a separate "instantiation" module where this will be called or
# moved to.
plugins = model_storage.plugin.list()
matching_plugins = []
if plugins:
for plugin in plugins:
if (plugin.name == self.name) and \
((self.version is None) or \
(VersionString(plugin.package_version) >= self.version)):
matching_plugins.append(plugin)
self.plugin = None
if matching_plugins:
# Return highest version of plugin
key = lambda plugin: VersionString(plugin.package_version).key
self.plugin = sorted(matching_plugins, key=key)[-1]
return self.plugin is not None