http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/modeling/service.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service.py b/aria/modeling/service.py deleted file mode 100644 index bf189f7..0000000 --- a/aria/modeling/service.py +++ /dev/null @@ -1,1529 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -# pylint: disable=too-many-lines, no-self-argument, no-member, abstract-method - -from sqlalchemy import ( - Column, - Text, - Integer -) -from sqlalchemy import DateTime -from sqlalchemy.ext.associationproxy import association_proxy -from sqlalchemy.ext.declarative import declared_attr - -from .bases import InstanceModelMixin -from ..parser import validation -from ..utils import collections, formatting, console - -from . import ( - utils, - types as modeling_types -) - - -class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods - """ - A service is usually an instance of a :class:`ServiceTemplate`. - - You will usually not create it programmatically, but instead instantiate it from a service - template. - - :ivar name: Name (unique for this ARIA installation) - :vartype name: basestring - :ivar service_template: Template from which this service was instantiated (optional) - :vartype service_template: :class:`ServiceTemplate` - :ivar description: Human-readable description - :vartype description: string - :ivar meta_data: Custom annotations - :vartype meta_data: {basestring: :class:`Metadata`} - :ivar node: Nodes - :vartype node: [:class:`Node`] - :ivar groups: Groups of nodes - :vartype groups: [:class:`Group`] - :ivar policies: Policies - :vartype policies: [:class:`Policy`] - :ivar substitution: The entire service can appear as a node - :vartype substitution: :class:`Substitution` - :ivar inputs: Externally provided parameters - :vartype inputs: {basestring: :class:`Parameter`} - :ivar outputs: These parameters are filled in after service installation - :vartype outputs: {basestring: :class:`Parameter`} - :ivar operations: Custom operations that can be performed on the service - :vartype operations: {basestring: :class:`Operation`} - :ivar plugins: Plugins required to be installed - :vartype plugins: {basestring: :class:`Plugin`} - :ivar created_at: Creation timestamp - :vartype created_at: :class:`datetime.datetime` - :ivar updated_at: Update timestamp - :vartype updated_at: :class:`datetime.datetime` - - :ivar permalink: ?? - :vartype permalink: basestring - :ivar scaling_groups: ?? - :vartype scaling_groups: {} - - :ivar modifications: Modifications of this service - :vartype modifications: [:class:`ServiceModification`] - :ivar updates: Updates of this service - :vartype updates: [:class:`ServiceUpdate`] - :ivar executions: Executions on this service - :vartype executions: [:class:`Execution`] - """ - - __tablename__ = 'service' - - @declared_attr - def service_template(cls): - return cls.many_to_one_relationship('service_template') - - description = Column(Text) - - @declared_attr - def meta_data(cls): - # Warning! We cannot use the attr name "metadata" because it's used by SqlAlchemy! - return cls.many_to_many_relationship('metadata', dict_key='name') - - @declared_attr - def nodes(cls): - return cls.one_to_many_relationship('node') - - @declared_attr - def groups(cls): - return cls.one_to_many_relationship('group') - - @declared_attr - def policies(cls): - return cls.one_to_many_relationship('policy') - - @declared_attr - def substitution(cls): - return cls.one_to_one_relationship('substitution') - - @declared_attr - def inputs(cls): - return cls.many_to_many_relationship('parameter', table_prefix='inputs', - dict_key='name') - - @declared_attr - def outputs(cls): - return cls.many_to_many_relationship('parameter', table_prefix='outputs', - dict_key='name') - - @declared_attr - def operations(cls): - return cls.one_to_many_relationship('operation', dict_key='name') - - @declared_attr - def plugins(cls): - return cls.many_to_many_relationship('plugin') - - created_at = Column(DateTime, nullable=False, index=True) - updated_at = Column(DateTime) - - # region orchestration - - permalink = Column(Text) - scaling_groups = Column(modeling_types.Dict) - - # endregion - - # region foreign keys - - __private_fields__ = ['substituion_fk', - 'service_template_fk'] - - # Service one-to-one to Substitution - @declared_attr - def substitution_fk(cls): - return cls.foreign_key('substitution', nullable=True) - - # Service many-to-one to ServiceTemplate - @declared_attr - def service_template_fk(cls): - return cls.foreign_key('service_template', nullable=True) - - # endregion - - def satisfy_requirements(self, context): - satisfied = True - for node in self.nodes: - if not node.satisfy_requirements(context): - satisfied = False - return satisfied - - def validate_capabilities(self, context): - satisfied = True - for node in self.nodes: - if not node.validate_capabilities(context): - satisfied = False - return satisfied - - def find_nodes(self, node_template_name): - nodes = [] - for node in self.nodes: - if node.node_template.name == node_template_name: - nodes.append(node) - return collections.FrozenList(nodes) - - def get_node_ids(self, node_template_name): - return collections.FrozenList((node.name for node in self.find_nodes(node_template_name))) - - def find_groups(self, group_template_name): - groups = [] - for group in self.groups: - if group.template_name == group_template_name: - groups.append(group) - return collections.FrozenList(groups) - - def get_group_ids(self, group_template_name): - return collections.FrozenList((group.name - for group in self.find_groups(group_template_name))) - - def is_node_a_target(self, context, target_node): - for node in self.nodes: - if self._is_node_a_target(context, node, target_node): - return True - return False - - def _is_node_a_target(self, context, source_node, target_node): - if source_node.relationships: - for relationship in source_node.relationships: - if relationship.target_node_id == target_node.name: - return True - else: - node = context.modeling.instance.nodes.get(relationship.target_node_id) - if node is not None: - if self._is_node_a_target(context, node, target_node): - return True - return False - - @property - def as_raw(self): - return collections.OrderedDict(( - ('description', self.description), - ('metadata', formatting.as_raw_dict(self.meta_data)), - ('nodes', formatting.as_raw_list(self.nodes)), - ('groups', formatting.as_raw_list(self.groups)), - ('policies', formatting.as_raw_list(self.policies)), - ('substitution', formatting.as_raw(self.substitution)), - ('inputs', formatting.as_raw_dict(self.inputs)), - ('outputs', formatting.as_raw_dict(self.outputs)), - ('operations', formatting.as_raw_list(self.operations)))) - - def validate(self, context): - utils.validate_dict_values(context, self.meta_data) - utils.validate_list_values(context, self.nodes) - utils.validate_list_values(context, self.groups) - utils.validate_list_values(context, self.policies) - if self.substitution is not None: - self.substitution.validate(context) - utils.validate_dict_values(context, self.inputs) - utils.validate_dict_values(context, self.outputs) - utils.validate_dict_values(context, self.operations) - - def coerce_values(self, context, container, report_issues): - utils.coerce_dict_values(context, container, self.meta_data, report_issues) - utils.coerce_list_values(context, container, self.nodes, report_issues) - utils.coerce_list_values(context, container, self.groups, report_issues) - utils.coerce_list_values(context, container, self.policies, report_issues) - if self.substitution is not None: - self.substitution.coerce_values(context, container, report_issues) - utils.coerce_dict_values(context, container, self.inputs, report_issues) - utils.coerce_dict_values(context, container, self.outputs, report_issues) - utils.coerce_dict_values(context, container, self.operations, report_issues) - - def dump(self, context): - if self.description is not None: - console.puts(context.style.meta(self.description)) - utils.dump_dict_values(context, self.meta_data, 'Metadata') - for node in self.nodes: - node.dump(context) - for group in self.groups: - group.dump(context) - for policy in self.policies: - policy.dump(context) - if self.substitution is not None: - self.substitution.dump(context) - utils.dump_dict_values(context, self.inputs, 'Inputs') - utils.dump_dict_values(context, self.outputs, 'Outputs') - utils.dump_dict_values(context, self.operations, 'Operations') - - def dump_graph(self, context): - for node in self.nodes.itervalues(): - if not self.is_node_a_target(context, node): - self._dump_graph_node(context, node) - - def _dump_graph_node(self, context, node): - console.puts(context.style.node(node.name)) - if node.relationships: - with context.style.indent: - for relationship in node.relationships: - relationship_name = (context.style.node(relationship.template_name) - if relationship.template_name is not None - else context.style.type(relationship.type_name)) - capability_name = (context.style.node(relationship.target_capability_name) - if relationship.target_capability_name is not None - else None) - if capability_name is not None: - console.puts('-> {0} {1}'.format(relationship_name, capability_name)) - else: - console.puts('-> {0}'.format(relationship_name)) - target_node = self.nodes.get(relationship.target_node_id) - with console.indent(3): - self._dump_graph_node(context, target_node) - - -class NodeBase(InstanceModelMixin): # pylint: disable=too-many-public-methods - """ - Usually an instance of a :class:`NodeTemplate`. - - Nodes may have zero or more :class:`Relationship` instances to other nodes. - - :ivar name: Name (unique for this service) - :vartype name: basestring - :ivar node_template: Template from which this node was instantiated (optional) - :vartype node_template: :class:`NodeTemplate` - :ivar type: Node type - :vartype type: :class:`Type` - :ivar description: Human-readable description - :vartype description: string - :ivar properties: Associated parameters - :vartype properties: {basestring: :class:`Parameter`} - :ivar interfaces: Bundles of operations - :vartype interfaces: {basestring: :class:`Interface`} - :ivar artifacts: Associated files - :vartype artifacts: {basestring: :class:`Artifact`} - :ivar capabilities: Exposed capabilities - :vartype capabilities: {basestring: :class:`Capability`} - :ivar outbound_relationships: Relationships to other nodes - :vartype outbound_relationships: [:class:`Relationship`] - :ivar inbound_relationships: Relationships from other nodes - :vartype inbound_relationships: [:class:`Relationship`] - :ivar plugins: Plugins required to be installed on the node's host - :vartype plugins: {basestring: :class:`Plugin`} - :ivar host: Host node (can be self) - :vartype host: :class:`Node` - - :ivar runtime_properties: TODO: should be replaced with attributes - :vartype runtime_properties: {} - :ivar scaling_groups: ?? - :vartype scaling_groups: [] - :ivar state: ?? - :vartype state: basestring - :ivar version: ?? - :vartype version: int - - :ivar service: Containing service - :vartype service: :class:`Service` - :ivar groups: We are a member of these groups - :vartype groups: [:class:`Group`] - :ivar policies: Policies enacted on this node - :vartype policies: [:class:`Policy`] - :ivar substitution_mapping: Our contribution to service substitution - :vartype substitution_mapping: :class:`SubstitutionMapping` - :ivar tasks: Tasks on this node - :vartype tasks: [:class:`Task`] - """ - - __tablename__ = 'node' - - @declared_attr - def node_template(cls): - return cls.many_to_one_relationship('node_template') - - @declared_attr - def type(cls): - return cls.many_to_one_relationship('type') - - description = Column(Text) - - @declared_attr - def properties(cls): - return cls.many_to_many_relationship('parameter', table_prefix='properties', - dict_key='name') - - @declared_attr - def interfaces(cls): - return cls.one_to_many_relationship('interface', dict_key='name') - - @declared_attr - def artifacts(cls): - return cls.one_to_many_relationship('artifact', dict_key='name') - - @declared_attr - def capabilities(cls): - return cls.one_to_many_relationship('capability', dict_key='name') - - @declared_attr - def outbound_relationships(cls): - return cls.one_to_many_relationship('relationship', - foreign_key='source_node_fk', - backreference='source_node') - - @declared_attr - def inbound_relationships(cls): - return cls.one_to_many_relationship('relationship', - foreign_key='target_node_fk', - backreference='target_node') - - @declared_attr - def plugins(cls): - return cls.many_to_many_relationship('plugin') - - @declared_attr - def host(cls): - return cls.relationship_to_self('host_fk') - - # region orchestration - - runtime_properties = Column(modeling_types.Dict) - scaling_groups = Column(modeling_types.List) - state = Column(Text, nullable=False) - version = Column(Integer, default=1) - - @declared_attr - def service_name(cls): - return association_proxy('service', 'name') - - @property - def ip(self): - # TODO: totally broken - if not self.host_fk: - return None - host_node = self.host - if 'ip' in host_node.runtime_properties: # pylint: disable=no-member - return host_node.runtime_properties['ip'] # pylint: disable=no-member - host_node = host_node.node_template # pylint: disable=no-member - host_ip_property = host_node.properties.get('ip') - if host_ip_property: - return host_ip_property.value - return None - - # endregion - - # region foreign_keys - - __private_fields__ = ['type_fk', - 'host_fk', - 'service_fk', - 'node_template_fk'] - - # Node many-to-one to Type - @declared_attr - def type_fk(cls): - return cls.foreign_key('type') - - # Node one-to-one to Node - @declared_attr - def host_fk(cls): - return cls.foreign_key('node', nullable=True) - - # Service one-to-many to Node - @declared_attr - def service_fk(cls): - return cls.foreign_key('service') - - # Node many-to-one to NodeTemplate - @declared_attr - def node_template_fk(cls): - return cls.foreign_key('node_template', nullable=True) - - # endregion - - def satisfy_requirements(self, context): - node_template = self.node_template - satisfied = True - for requirement_template in node_template.requirement_templates: - # Find target template - target_node_template, target_node_capability = \ - requirement_template.find_target(context, node_template) - if target_node_template is not None: - satisfied = self._satisfy_capability(context, - target_node_capability, - target_node_template, - requirement_template) - else: - context.validation.report('requirement "{0}" of node "{1}" has no target node ' - 'template'.format(requirement_template.name, self.name), - level=validation.Issue.BETWEEN_INSTANCES) - satisfied = False - return satisfied - - def _satisfy_capability(self, context, target_node_capability, target_node_template, - requirement_template): - from . import models - # Find target nodes - target_nodes = context.modeling.instance.find_nodes(target_node_template.name) - if target_nodes: - target_node = None - target_capability = None - - if target_node_capability is not None: - # Relate to the first target node that has capacity - for node in target_nodes: - target_capability = node.capabilities.get(target_node_capability.name) - if target_capability.relate(): - target_node = node - break - else: - # Use first target node - target_node = target_nodes[0] - - if target_node is not None: - if requirement_template.relationship_template is not None: - relationship = \ - requirement_template.relationship_template.instantiate(context, self) - else: - relationship = models.Relationship(target_capability=target_capability) - relationship.name = requirement_template.name - relationship.requirement_template = requirement_template - relationship.target_node = target_node - self.outbound_relationships.append(relationship) - return True - else: - context.validation.report('requirement "{0}" of node "{1}" targets node ' - 'template "{2}" but its instantiated nodes do not ' - 'have enough capacity'.format( - requirement_template.name, - self.name, - target_node_template.name), - level=validation.Issue.BETWEEN_INSTANCES) - return False - else: - context.validation.report('requirement "{0}" of node "{1}" targets node template ' - '"{2}" but it has no instantiated nodes'.format( - requirement_template.name, - self.name, - target_node_template.name), - level=validation.Issue.BETWEEN_INSTANCES) - return False - - def validate_capabilities(self, context): - satisfied = False - for capability in self.capabilities.itervalues(): - if not capability.has_enough_relationships: - context.validation.report('capability "{0}" of node "{1}" requires at least {2:d} ' - 'relationships but has {3:d}'.format( - capability.name, - self.name, - capability.min_occurrences, - capability.occurrences), - level=validation.Issue.BETWEEN_INSTANCES) - satisfied = False - return satisfied - - @property - def as_raw(self): - return collections.OrderedDict(( - ('name', self.name), - ('type_name', self.type_name), - ('properties', formatting.as_raw_dict(self.properties)), - ('interfaces', formatting.as_raw_list(self.interfaces)), - ('artifacts', formatting.as_raw_list(self.artifacts)), - ('capabilities', formatting.as_raw_list(self.capabilities)), - ('relationships', formatting.as_raw_list(self.outbound_relationships)))) - - def validate(self, context): - if len(self.name) > context.modeling.id_max_length: - context.validation.report('"{0}" has an ID longer than the limit of {1:d} characters: ' - '{2:d}'.format( - self.name, - context.modeling.id_max_length, - len(self.name)), - level=validation.Issue.BETWEEN_INSTANCES) - - # TODO: validate that node template is of type? - - utils.validate_dict_values(context, self.properties) - utils.validate_dict_values(context, self.interfaces) - utils.validate_dict_values(context, self.artifacts) - utils.validate_dict_values(context, self.capabilities) - utils.validate_list_values(context, self.outbound_relationships) - - def coerce_values(self, context, container, report_issues): - utils.coerce_dict_values(context, self, self.properties, report_issues) - utils.coerce_dict_values(context, self, self.interfaces, report_issues) - utils.coerce_dict_values(context, self, self.artifacts, report_issues) - utils.coerce_dict_values(context, self, self.capabilities, report_issues) - utils.coerce_list_values(context, self, self.outbound_relationships, report_issues) - - def dump(self, context): - console.puts('Node: {0}'.format(context.style.node(self.name))) - with context.style.indent: - console.puts('Type: {0}'.format(context.style.type(self.type.name))) - console.puts('Template: {0}'.format(context.style.node(self.node_template.name))) - utils.dump_dict_values(context, self.properties, 'Properties') - utils.dump_interfaces(context, self.interfaces) - utils.dump_dict_values(context, self.artifacts, 'Artifacts') - utils.dump_dict_values(context, self.capabilities, 'Capabilities') - utils.dump_list_values(context, self.outbound_relationships, 'Relationships') - - -class GroupBase(InstanceModelMixin): - """ - Usually an instance of a :class:`GroupTemplate`. - - :ivar name: Name (unique for this service) - :vartype name: basestring - :ivar group_template: Template from which this group was instantiated (optional) - :vartype group_template: :class:`GroupTemplate` - :ivar type: Group type - :vartype type: :class:`Type` - :ivar description: Human-readable description - :vartype description: string - :ivar nodes: Members of this group - :vartype nodes: [:class:`Node`] - :ivar properties: Associated parameters - :vartype properties: {basestring: :class:`Parameter`} - :ivar interfaces: Bundles of operations - :vartype interfaces: {basestring: :class:`Interface`} - - :ivar service: Containing service - :vartype service: :class:`Service` - :ivar policies: Policies enacted on this group - :vartype policies: [:class:`Policy`] - """ - - __tablename__ = 'group' - - @declared_attr - def group_template(cls): - return cls.many_to_one_relationship('group_template') - - @declared_attr - def type(cls): - return cls.many_to_one_relationship('type') - - description = Column(Text) - - @declared_attr - def nodes(cls): - return cls.many_to_many_relationship('node') - - @declared_attr - def properties(cls): - return cls.many_to_many_relationship('parameter', table_prefix='properties', - dict_key='name') - - @declared_attr - def interfaces(cls): - return cls.one_to_many_relationship('interface', dict_key='name') - - # region foreign_keys - - __private_fields__ = ['type_fk', - 'service_fk', - 'group_template_fk'] - - # Group many-to-one to Type - @declared_attr - def type_fk(cls): - return cls.foreign_key('type') - - # Service one-to-many to Group - @declared_attr - def service_fk(cls): - return cls.foreign_key('service') - - # Group many-to-one to GroupTemplate - @declared_attr - def group_template_fk(cls): - return cls.foreign_key('group_template', nullable=True) - - # endregion - - @property - def as_raw(self): - return collections.OrderedDict(( - ('name', self.name), - ('properties', formatting.as_raw_dict(self.properties)), - ('interfaces', formatting.as_raw_list(self.interfaces)))) - - def validate(self, context): - utils.validate_dict_values(context, self.properties) - utils.validate_dict_values(context, self.interfaces) - - def coerce_values(self, context, container, report_issues): - utils.coerce_dict_values(context, container, self.properties, report_issues) - utils.coerce_dict_values(context, container, self.interfaces, report_issues) - - def dump(self, context): - console.puts('Group: {0}'.format(context.style.node(self.name))) - with context.style.indent: - console.puts('Type: {0}'.format(context.style.type(self.type.name))) - utils.dump_dict_values(context, self.properties, 'Properties') - utils.dump_interfaces(context, self.interfaces) - if self.nodes: - console.puts('Member nodes:') - with context.style.indent: - for node in self.nodes: - console.puts(context.style.node(node.name)) - - -class PolicyBase(InstanceModelMixin): - """ - Usually an instance of a :class:`PolicyTemplate`. - - :ivar name: Name (unique for this service) - :vartype name: basestring - :ivar policy_template: Template from which this policy was instantiated (optional) - :vartype policy_template: :class:`PolicyTemplate` - :ivar type: Policy type - :vartype type: :class:`Type` - :ivar description: Human-readable description - :vartype description: string - :ivar nodes: Policy will be enacted on all these nodes - :vartype nodes: [:class:`Node`] - :ivar groups: Policy will be enacted on all nodes in these groups - :vartype groups: [:class:`Group`] - :ivar properties: Associated parameters - :vartype properties: {basestring: :class:`Parameter`} - - :ivar service: Containing service - :vartype service: :class:`Service` - """ - - __tablename__ = 'policy' - - @declared_attr - def policy_template(cls): - return cls.many_to_one_relationship('policy_template') - - @declared_attr - def type(cls): - return cls.many_to_one_relationship('type') - - description = Column(Text) - - @declared_attr - def properties(cls): - return cls.many_to_many_relationship('parameter', table_prefix='properties', - dict_key='name') - - @declared_attr - def nodes(cls): - return cls.many_to_many_relationship('node') - - @declared_attr - def groups(cls): - return cls.many_to_many_relationship('group') - - # region foreign_keys - - __private_fields__ = ['type_fk', - 'service_fk', - 'policy_template_fk'] - - # Policy many-to-one to Type - @declared_attr - def type_fk(cls): - return cls.foreign_key('type') - - # Service one-to-many to Policy - @declared_attr - def service_fk(cls): - return cls.foreign_key('service') - - # Policy many-to-one to PolicyTemplate - @declared_attr - def policy_template_fk(cls): - return cls.foreign_key('policy_template', nullable=True) - - # endregion - - @property - def as_raw(self): - return collections.OrderedDict(( - ('name', self.name), - ('type_name', self.type_name), - ('properties', formatting.as_raw_dict(self.properties)))) - - def validate(self, context): - utils.validate_dict_values(context, self.properties) - - def coerce_values(self, context, container, report_issues): - utils.coerce_dict_values(context, container, self.properties, report_issues) - - def dump(self, context): - console.puts('Policy: {0}'.format(context.style.node(self.name))) - with context.style.indent: - console.puts('Type: {0}'.format(context.style.type(self.type.name))) - utils.dump_dict_values(context, self.properties, 'Properties') - if self.nodes: - console.puts('Target nodes:') - with context.style.indent: - for node in self.nodes: - console.puts(context.style.node(node.name)) - if self.groups: - console.puts('Target groups:') - with context.style.indent: - for group in self.groups: - console.puts(context.style.node(group.name)) - - -class SubstitutionBase(InstanceModelMixin): - """ - Used to substitute a single node for the entire deployment. - - Usually an instance of a :class:`SubstitutionTemplate`. - - :ivar substitution_template: Template from which this substitution was instantiated (optional) - :vartype substitution_template: :class:`SubstitutionTemplate` - :ivar node_type: Exposed node type - :vartype node_type: :class:`Type` - :ivar mappings: Requirement and capability mappings - :vartype mappings: {basestring: :class:`SubstitutionTemplate`} - - :ivar service: Containing service - :vartype service: :class:`Service` - """ - - __tablename__ = 'substitution' - - @declared_attr - def substitution_template(cls): - return cls.many_to_one_relationship('substitution_template') - - @declared_attr - def node_type(cls): - return cls.many_to_one_relationship('type') - - @declared_attr - def mappings(cls): - return cls.one_to_many_relationship('substitution_mapping', dict_key='name') - - # region foreign_keys - - __private_fields__ = ['node_type_fk', - 'substitution_template_fk'] - - # Substitution many-to-one to Type - @declared_attr - def node_type_fk(cls): - return cls.foreign_key('type') - - # Substitution many-to-one to SubstitutionTemplate - @declared_attr - def substitution_template_fk(cls): - return cls.foreign_key('substitution_template', nullable=True) - - # endregion - - @property - def as_raw(self): - return collections.OrderedDict(( - ('node_type_name', self.node_type_name), - ('mappings', formatting.as_raw_dict(self.mappings)))) - - def validate(self, context): - utils.validate_dict_values(context, self.mappings) - - def coerce_values(self, context, container, report_issues): - utils.coerce_dict_values(context, container, self.mappings, report_issues) - - def dump(self, context): - console.puts('Substitution:') - 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 SubstitutionMappingBase(InstanceModelMixin): - """ - Used by :class:`Substitution` to map a capability or a requirement to a node. - - Only one of `capability_template` and `requirement_template` can be set. - - Usually an instance of a :class:`SubstitutionTemplate`. - - :ivar name: Exposed capability or requirement name - :vartype name: basestring - :ivar node: Node - :vartype node: :class:`Node` - :ivar capability: Capability in the node - :vartype capability: :class:`Capability` - :ivar requirement_template: Requirement template in the node template - :vartype requirement_template: :class:`RequirementTemplate` - - :ivar substitution: Containing substitution - :vartype substitution: :class:`Substitution` - """ - - __tablename__ = 'substitution_mapping' - - @declared_attr - def node(cls): - return cls.one_to_one_relationship('node') - - @declared_attr - def capability(cls): - return cls.one_to_one_relationship('capability') - - @declared_attr - def requirement_template(cls): - return cls.one_to_one_relationship('requirement_template') - - # region foreign keys - - __private_fields__ = ['substitution_fk', - 'node_fk', - 'capability_fk', - 'requirement_template_fk'] - - # Substitution one-to-many to SubstitutionMapping - @declared_attr - def substitution_fk(cls): - return cls.foreign_key('substitution') - - # Substitution one-to-one to NodeTemplate - @declared_attr - def node_fk(cls): - return cls.foreign_key('node') - - # Substitution one-to-one to Capability - @declared_attr - def capability_fk(cls): - return cls.foreign_key('capability', nullable=True) - - # Substitution one-to-one to RequirementTemplate - @declared_attr - def requirement_template_fk(cls): - return cls.foreign_key('requirement_template', nullable=True) - - # endregion - - @property - def as_raw(self): - return collections.OrderedDict(( - ('name', self.name))) - - def validate(self, context): - if (self.capability is None) and (self.requirement_template is None): - context.validation.report('mapping "{0}" refers to neither capability nor a requirement' - ' in node: {1}'.format( - self.name, - formatting.safe_repr(self.node.name)), - level=validation.Issue.BETWEEN_TYPES) - - def dump(self, context): - console.puts('{0} -> {1}.{2}'.format( - context.style.node(self.name), - context.style.node(self.node.name), - context.style.node(self.capability.name - if self.capability - else self.requirement_template.name))) - - -class RelationshipBase(InstanceModelMixin): - """ - Connects :class:`Node` to a capability in another node. - - Might be an instance of a :class:`RelationshipTemplate`. - - :ivar name: Name (usually the name of the requirement at the source node template) - :vartype name: basestring - :ivar relationship_template: Template from which this relationship was instantiated (optional) - :vartype relationship_template: :class:`RelationshipTemplate` - :ivar requirement_template: Template from which this relationship was instantiated (optional) - :vartype requirement_template: :class:`RequirementTemplate` - :ivar type: Relationship type - :vartype type: :class:`Type` - :ivar target_capability: Capability at the target node (optional) - :vartype target_capability: :class:`Capability` - :ivar properties: Associated parameters - :vartype properties: {basestring: :class:`Parameter`} - :ivar interfaces: Bundles of operations - :vartype interfaces: {basestring: :class:`Interfaces`} - - :ivar source_position: ?? - :vartype source_position: int - :ivar target_position: ?? - :vartype target_position: int - - :ivar source_node: Source node - :vartype source_node: :class:`Node` - :ivar target_node: Target node - :vartype target_node: :class:`Node` - :ivar tasks: Tasks on this node - :vartype tasks: [:class:`Task`] - """ - - __tablename__ = 'relationship' - - @declared_attr - def relationship_template(cls): - return cls.many_to_one_relationship('relationship_template') - - @declared_attr - def requirement_template(cls): - return cls.many_to_one_relationship('requirement_template') - - @declared_attr - def type(cls): - return cls.many_to_one_relationship('type') - - @declared_attr - def target_capability(cls): - return cls.one_to_one_relationship('capability') - - @declared_attr - def properties(cls): - return cls.many_to_many_relationship('parameter', table_prefix='properties', - dict_key='name') - - @declared_attr - def interfaces(cls): - return cls.one_to_many_relationship('interface', dict_key='name') - - # region orchestration - - source_position = Column(Integer) # ??? - target_position = Column(Integer) # ??? - - # endregion - - # region foreign keys - - __private_fields__ = ['type_fk', - 'source_node_fk', - 'target_node_fk', - 'target_capability_fk', - 'requirement_template_fk', - 'relationship_template_fk'] - - # Relationship many-to-one to Type - @declared_attr - def type_fk(cls): - return cls.foreign_key('type', nullable=True) - - # Node one-to-many to Relationship - @declared_attr - def source_node_fk(cls): - return cls.foreign_key('node') - - # Node one-to-many to Relationship - @declared_attr - def target_node_fk(cls): - return cls.foreign_key('node') - - # Relationship one-to-one to Capability - @declared_attr - def target_capability_fk(cls): - return cls.foreign_key('capability', nullable=True) - - # Relationship many-to-one to RequirementTemplate - @declared_attr - def requirement_template_fk(cls): - return cls.foreign_key('requirement_template', nullable=True) - - # Relationship many-to-one to RelationshipTemplate - @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), - ('target_node_id', self.target_node.name), - ('type_name', self.type_name), - ('template_name', self.template_name), - ('properties', formatting.as_raw_dict(self.properties)), - ('interfaces', formatting.as_raw_list(self.interfaces)))) - - def validate(self, context): - utils.validate_dict_values(context, self.properties) - utils.validate_dict_values(context, self.interfaces) - - def coerce_values(self, context, container, report_issues): - utils.coerce_dict_values(context, container, self.properties, report_issues) - utils.coerce_dict_values(context, container, self.interfaces, report_issues) - - def dump(self, context): - if self.name: - console.puts('{0} ->'.format(context.style.node(self.name))) - else: - console.puts('->') - with context.style.indent: - console.puts('Node: {0}'.format(context.style.node(self.target_node.name))) - if self.target_capability: - console.puts('Capability: {0}'.format(context.style.node( - self.target_capability.name))) - if self.type is not None: - console.puts('Relationship type: {0}'.format(context.style.type(self.type.name))) - if (self.relationship_template is not None) and self.relationship_template.name: - console.puts('Relationship template: {0}'.format( - context.style.node(self.relationship_template.name))) - utils.dump_dict_values(context, self.properties, 'Properties') - utils.dump_interfaces(context, self.interfaces, 'Interfaces') - - -class CapabilityBase(InstanceModelMixin): - """ - A capability of a :class:`Node`. - - Usually an instance of a :class:`CapabilityTemplate`. - - :ivar name: Name (unique for the node) - :vartype name: basestring - :ivar capability_template: Template from which this capability was instantiated (optional) - :vartype capability_template: :class:`capabilityTemplate` - :ivar type: Capability type - :vartype type: :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 occurrences: Actual number of requirement matches - :vartype occurrences: int - :ivar properties: Associated parameters - :vartype properties: {basestring: :class:`Parameter`} - - :ivar node: Containing node - :vartype node: :class:`Node` - :ivar relationship: Available when we are the target of a relationship - :vartype relationship: :class:`Relationship` - :ivar substitution_mapping: Our contribution to service substitution - :vartype substitution_mapping: :class:`SubstitutionMapping` - """ - - __tablename__ = 'capability' - - @declared_attr - def capability_template(cls): - return cls.many_to_one_relationship('capability_template') - - @declared_attr - def type(cls): - return cls.many_to_one_relationship('type') - - min_occurrences = Column(Integer, default=None) - max_occurrences = Column(Integer, default=None) - occurrences = Column(Integer, default=0) - - @declared_attr - def properties(cls): - return cls.many_to_many_relationship('parameter', table_prefix='properties', - dict_key='name') - - # region foreign_keys - - __private_fields__ = ['capability_fk', - 'node_fk', - 'capability_template_fk'] - - # Capability many-to-one to Type - @declared_attr - def type_fk(cls): - return cls.foreign_key('type') - - # Node one-to-many to Capability - @declared_attr - def node_fk(cls): - return cls.foreign_key('node') - - # Capability many-to-one to CapabilityTemplate - @declared_attr - def capability_template_fk(cls): - return cls.foreign_key('capability_template', nullable=True) - - # endregion - - @property - def has_enough_relationships(self): - if self.min_occurrences is not None: - return self.occurrences >= self.min_occurrences - return True - - def relate(self): - if self.max_occurrences is not None: - if self.occurrences == self.max_occurrences: - return False - self.occurrences += 1 - return True - - @property - def as_raw(self): - return collections.OrderedDict(( - ('name', self.name), - ('type_name', self.type_name), - ('properties', formatting.as_raw_dict(self.properties)))) - - def validate(self, context): - utils.validate_dict_values(context, self.properties) - - def coerce_values(self, context, container, report_issues): - utils.coerce_dict_values(context, container, self.properties, report_issues) - - def dump(self, context): - console.puts(context.style.node(self.name)) - with context.style.indent: - console.puts('Type: {0}'.format(context.style.type(self.type.name))) - console.puts('Occurrences: {0:d} ({1:d}{2})'.format( - self.occurrences, - self.min_occurrences or 0, - ' to {0:d}'.format(self.max_occurrences) - if self.max_occurrences is not None - else ' or more')) - utils.dump_dict_values(context, self.properties, 'Properties') - - -class InterfaceBase(InstanceModelMixin): - """ - A typed set of :class:`Operation`. - - Usually an instance of :class:`InterfaceTemplate`. - - :ivar name: Name (unique for the node, group, or relationship) - :vartype name: basestring - :ivar interface_template: Template from which this interface was instantiated (optional) - :vartype interface_template: :class:`InterfaceTemplate` - :ivar type: Interface type - :vartype type: :class:`Type` - :ivar description: Human-readable description - :vartype description: string - :ivar inputs: Parameters that can be used by all operations in the interface - :vartype inputs: {basestring: :class:`Parameter`} - :ivar operations: Operations - :vartype operations: {basestring: :class:`Operation`} - - :ivar node: Containing node - :vartype node: :class:`Node` - :ivar group: Containing group - :vartype group: :class:`Group` - :ivar relationship: Containing relationship - :vartype relationship: :class:`Relationship` - """ - - __tablename__ = 'interface' - - @declared_attr - def interface_template(cls): - return cls.many_to_one_relationship('interface_template') - - @declared_attr - def type(cls): - return cls.many_to_one_relationship('type') - - description = Column(Text) - - @declared_attr - def inputs(cls): - return cls.many_to_many_relationship('parameter', table_prefix='inputs', - dict_key='name') - - @declared_attr - def operations(cls): - return cls.one_to_many_relationship('operation', dict_key='name') - - # region foreign_keys - - __private_fields__ = ['type_fk', - 'node_fk', - 'group_fk', - 'relationship_fk', - 'interface_template_fk'] - - # Interface many-to-one to Type - @declared_attr - def type_fk(cls): - return cls.foreign_key('type') - - # Node one-to-many to Interface - @declared_attr - def node_fk(cls): - return cls.foreign_key('node', nullable=True) - - # Group one-to-many to Interface - @declared_attr - def group_fk(cls): - return cls.foreign_key('group', nullable=True) - - # Relationship one-to-many to Interface - @declared_attr - def relationship_fk(cls): - return cls.foreign_key('relationship', nullable=True) - - # Interface many-to-one to InterfaceTemplate - @declared_attr - def interface_template_fk(cls): - return cls.foreign_key('interface_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)), - ('operations', formatting.as_raw_list(self.operations)))) - - def validate(self, context): - utils.validate_dict_values(context, self.inputs) - utils.validate_dict_values(context, self.operations) - - def coerce_values(self, context, container, report_issues): - utils.coerce_dict_values(context, container, self.inputs, report_issues) - utils.coerce_dict_values(context, container, self.operations, report_issues) - - def dump(self, context): - console.puts(context.style.node(self.name)) - if self.description: - console.puts(context.style.meta(self.description)) - with context.style.indent: - console.puts('Interface type: {0}'.format(context.style.type(self.type.name))) - utils.dump_dict_values(context, self.inputs, 'Inputs') - utils.dump_dict_values(context, self.operations, 'Operations') - - -class OperationBase(InstanceModelMixin): - """ - An operation in a :class:`Interface`. - - Might be an instance of :class:`OperationTemplate`. - - :ivar name: Name (unique for the interface or service) - :vartype name: basestring - :ivar operation_template: Template from which this operation was instantiated (optional) - :vartype operation_template: :class:`OperationTemplate` - :ivar description: Human-readable description - :vartype description: string - :ivar plugin: Associated plugin - :vartype plugin: :class:`Plugin` - :ivar implementation: Implementation string (interpreted by the plugin) - :vartype implementation: basestring - :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: Executor name - :vartype executor: basestring - :ivar max_retries: Maximum number of retries allowed in case of failure - :vartype max_retries: int - :ivar retry_interval: Interval between retries (in seconds) - :vartype retry_interval: int - - :ivar interface: Containing interface - :vartype interface: :class:`Interface` - :ivar service: Containing service - :vartype service: :class:`Service` - """ - - __tablename__ = 'operation' - - @declared_attr - def operation_template(cls): - return cls.many_to_one_relationship('operation_template') - - description = Column(Text) - - @declared_attr - def plugin(cls): - return cls.one_to_one_relationship('plugin') - - implementation = Column(Text) - dependencies = Column(modeling_types.StrictList(item_cls=basestring)) - - @declared_attr - def inputs(cls): - return cls.many_to_many_relationship('parameter', table_prefix='inputs', - dict_key='name') - - executor = Column(Text) - max_retries = Column(Integer) - retry_interval = Column(Integer) - - # region foreign_keys - - __private_fields__ = ['service_fk', - 'interface_fk', - 'plugin_fk', - 'operation_template_fk'] - - # Service one-to-many to Operation - @declared_attr - def service_fk(cls): - return cls.foreign_key('service', nullable=True) - - # Interface one-to-many to Operation - @declared_attr - def interface_fk(cls): - return cls.foreign_key('interface', nullable=True) - - # Operation one-to-one to Plugin - @declared_attr - def plugin_fk(cls): - return cls.foreign_key('plugin', nullable=True) - - # Operation many-to-one to OperationTemplate - @declared_attr - def operation_template_fk(cls): - return cls.foreign_key('operation_template', 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 validate(self, context): - # TODO must be associated with interface or service - utils.validate_dict_values(context, self.inputs) - - def coerce_values(self, context, container, report_issues): - 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_dict_values(context, self.inputs, 'Inputs') - - -class ArtifactBase(InstanceModelMixin): - """ - A file associated with a :class:`Node`. - - Usually an instance of :class:`ArtifactTemplate`. - - :ivar name: Name (unique for the node) - :vartype name: basestring - :ivar artifact_template: Template from which this artifact was instantiated (optional) - :vartype artifact_template: :class:`ArtifactTemplate` - :ivar type: Artifact type - :vartype type: :class:`Type` - :ivar description: Human-readable description - :vartype description: string - :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: Containing node - :vartype node: :class:`Node` - """ - - __tablename__ = 'artifact' - - @declared_attr - def artifact_template(cls): - return cls.many_to_one_relationship('artifact_template') - - @declared_attr - def type(cls): - return cls.many_to_one_relationship('type') - - description = 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', - dict_key='name') - - # region foreign_keys - - __private_fields__ = ['type_fk', - 'node_fk', - 'artifact_template_fk'] - - # Artifact many-to-one to Type - @declared_attr - def type_fk(cls): - return cls.foreign_key('type') - - # Node one-to-many to Artifact - @declared_attr - def node_fk(cls): - return cls.foreign_key('node') - - # Artifact many-to-one to ArtifactTemplate - @declared_attr - def artifact_template_fk(cls): - return cls.foreign_key('artifact_template', nullable=True) - - # endregion - - @property - def as_raw(self): - return collections.OrderedDict(( - ('name', self.name), - ('description', self.description), - ('type_name', self.type_name), - ('source_path', self.source_path), - ('target_path', self.target_path), - ('repository_url', self.repository_url), - ('repository_credential', formatting.as_agnostic(self.repository_credential)), - ('properties', formatting.as_raw_dict(self.properties)))) - - def validate(self, context): - 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_dict_values(context, self.properties, 'Properties')
http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/modeling/service_changes.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_changes.py b/aria/modeling/service_changes.py new file mode 100644 index 0000000..b83a376 --- /dev/null +++ b/aria/modeling/service_changes.py @@ -0,0 +1,219 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +""" +classes: + * ServiceUpdate - service update implementation model. + * ServiceUpdateStep - service update step implementation model. + * ServiceModification - service modification implementation model. +""" + +# pylint: disable=no-self-argument, no-member, abstract-method + +from collections import namedtuple + +from sqlalchemy import ( + Column, + Text, + DateTime, + Enum, +) +from sqlalchemy.ext.associationproxy import association_proxy +from sqlalchemy.ext.declarative import declared_attr + +from .types import (List, Dict) +from .mixins import ModelMixin + +__all__ = ( + 'ServiceUpdateBase', + 'ServiceUpdateStepBase', + 'ServiceModificationBase' +) + + +class ServiceUpdateBase(ModelMixin): + """ + Deployment update model representation. + """ + + steps = None + + __tablename__ = 'service_update' + + _private_fields = ['execution_fk', + 'service_fk'] + + created_at = Column(DateTime, nullable=False, index=True) + service_plan = Column(Dict, nullable=False) + service_update_nodes = Column(Dict) + service_update_service = Column(Dict) + service_update_node_templates = Column(List) + modified_entity_ids = Column(Dict) + state = Column(Text) + + @declared_attr + def execution(cls): + return cls._create_many_to_one_relationship('execution') + + @declared_attr + def execution_name(cls): + return association_proxy('execution', cls.name_column_name()) + + @declared_attr + def service(cls): + return cls._create_many_to_one_relationship('service', + backreference='updates') + + @declared_attr + def service_name(cls): + return association_proxy('service', cls.name_column_name()) + + # region foreign keys + + __private_fields__ = ['service_fk', + 'execution_fk'] + + @declared_attr + def execution_fk(cls): + return cls._create_foreign_key('execution', nullable=True) + + @declared_attr + def service_fk(cls): + return cls._create_foreign_key('service') + + # endregion + + def to_dict(self, suppress_error=False, **kwargs): + dep_update_dict = super(ServiceUpdateBase, self).to_dict(suppress_error) #pylint: disable=no-member + # Taking care of the fact the DeploymentSteps are _BaseModels + dep_update_dict['steps'] = [step.to_dict() for step in self.steps] + return dep_update_dict + + +class ServiceUpdateStepBase(ModelMixin): + """ + Deployment update step model representation. + """ + + __tablename__ = 'service_update_step' + + _action_types = namedtuple('ACTION_TYPES', 'ADD, REMOVE, MODIFY') + ACTION_TYPES = _action_types(ADD='add', REMOVE='remove', MODIFY='modify') + + _entity_types = namedtuple( + 'ENTITY_TYPES', + 'NODE, RELATIONSHIP, PROPERTY, OPERATION, WORKFLOW, OUTPUT, DESCRIPTION, GROUP, PLUGIN') + ENTITY_TYPES = _entity_types( + NODE='node', + RELATIONSHIP='relationship', + PROPERTY='property', + OPERATION='operation', + WORKFLOW='workflow', + OUTPUT='output', + DESCRIPTION='description', + GROUP='group', + PLUGIN='plugin' + ) + + action = Column(Enum(*ACTION_TYPES, name='action_type'), nullable=False) + entity_id = Column(Text, nullable=False) + entity_type = Column(Enum(*ENTITY_TYPES, name='entity_type'), nullable=False) + + @declared_attr + def service_update(cls): + return cls._create_many_to_one_relationship('service_update', + backreference='steps') + + @declared_attr + def service_update_name(cls): + return association_proxy('service_update', cls.name_column_name()) + + # region foreign keys + + __private_fields__ = ['service_update_fk'] + + @declared_attr + def service_update_fk(cls): + return cls._create_foreign_key('service_update') + + # endregion + + def __hash__(self): + return hash((getattr(self, self.id_column_name()), self.entity_id)) + + def __lt__(self, other): + """ + the order is 'remove' < 'modify' < 'add' + :param other: + :return: + """ + if not isinstance(other, self.__class__): + return not self >= other + + if self.action != other.action: + if self.action == 'remove': + return_value = True + elif self.action == 'add': + return_value = False + else: + return_value = other.action == 'add' + return return_value + + if self.action == 'add': + return self.entity_type == 'node' and other.entity_type == 'relationship' + if self.action == 'remove': + return self.entity_type == 'relationship' and other.entity_type == 'node' + return False + + +class ServiceModificationBase(ModelMixin): + """ + Deployment modification model representation. + """ + + __tablename__ = 'service_modification' + + STARTED = 'started' + FINISHED = 'finished' + ROLLEDBACK = 'rolledback' + + STATES = [STARTED, FINISHED, ROLLEDBACK] + END_STATES = [FINISHED, ROLLEDBACK] + + context = Column(Dict) + created_at = Column(DateTime, nullable=False, index=True) + ended_at = Column(DateTime, index=True) + modified_node_templates = Column(Dict) + nodes = Column(Dict) + status = Column(Enum(*STATES, name='service_modification_status')) + + @declared_attr + def service(cls): + return cls._create_many_to_one_relationship('service', + backreference='modifications') + + @declared_attr + def service_name(cls): + return association_proxy('service', cls.name_column_name()) + + # region foreign keys + + __private_fields__ = ['service_fk'] + + @declared_attr + def service_fk(cls): + return cls._create_foreign_key('service') + + # endregion http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/dd5bfa93/aria/modeling/service_common.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_common.py b/aria/modeling/service_common.py new file mode 100644 index 0000000..b3535a6 --- /dev/null +++ b/aria/modeling/service_common.py @@ -0,0 +1,270 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# pylint: disable=no-self-argument, no-member, abstract-method + +import cPickle as pickle +import logging + +from sqlalchemy import ( + Column, + Text, + Binary, +) +from sqlalchemy.ext.declarative import declared_attr + +from ..storage import exceptions +from ..utils import collections, formatting, console +from .mixins import InstanceModelMixin, TemplateModelMixin +from .types import List +from . import utils + + +class ParameterBase(TemplateModelMixin): + """ + Represents a typed value. + + This model is used by both service template and service instance elements. + + :ivar name: Name + :ivar type_name: Type name + :ivar value: Value + :ivar description: Description + """ + + __tablename__ = 'parameter' + + name = Column(Text) + type_name = Column(Text) + + # Check: value type + _value = Column(Binary, name='value') + description = Column(Text) + + @property + def as_raw(self): + return collections.OrderedDict(( + ('name', self.name), + ('type_name', self.type_name), + ('value', self.value), + ('description', self.description))) + + @property + def value(self): + if self._value is None: + return None + try: + return pickle.loads(self._value) + except BaseException: + raise exceptions.StorageError('bad format for parameter of type "{0}": {1}'.format( + self.type_name, self._value)) + + @value.setter + def value(self, value): + if value is None: + self._value = None + else: + try: + self._value = pickle.dumps(value) + except (pickle.PicklingError, TypeError): + logging.getLogger('aria').warn('Could not pickle parameter of type "{0}": {1}' + .format(self.type_name, value)) + self._value = pickle.dumps(str(value)) + + def instantiate(self, context, container): + from . import models + return models.Parameter(name=self.name, + type_name=self.type_name, + _value=self._value, + description=self.description) + + def coerce_values(self, context, container, report_issues): + if self.value is not None: + self.value = utils.coerce_value(context, container, self.value, + report_issues) + + def dump(self, context): + if self.type_name is not None: + console.puts('{0}: {1} ({2})'.format( + context.style.property(self.name), + context.style.literal(self.value), + context.style.type(self.type_name))) + else: + console.puts('{0}: {1}'.format( + context.style.property(self.name), + context.style.literal(self.value))) + if self.description: + console.puts(context.style.meta(self.description)) + + +class TypeBase(InstanceModelMixin): + """ + Represents a type and its children. + """ + + __tablename__ = 'type' + + variant = Column(Text, nullable=False) + description = Column(Text) + _role = Column(Text, name='role') + + @declared_attr + def parent(cls): + return cls._create_relationship_to_self('parent_type_fk') + + @declared_attr + def children(cls): + return cls._create_one_to_many_relationship_to_self('parent_type_fk') + + # region foreign keys + + __private_fields__ = ['parent_type_fk'] + + # Type one-to-many to Type + @declared_attr + def parent_type_fk(cls): + return cls._create_foreign_key('type', nullable=True) + + # endregion + + @property + def role(self): + def get_role(the_type): + if the_type is None: + return None + elif the_type._role is None: + return get_role(the_type.parent) + return the_type._role + + return get_role(self) + + @role.setter + def role(self, value): + self._role = value + + def is_descendant(self, base_name, name): + base = self.get_descendant(base_name) + if base is not None: + if base.get_descendant(name) is not None: + return True + return False + + def get_descendant(self, name): + if self.name == name: + return self + for child in self.children: + found = child.get_descendant(name) + if found is not None: + return found + return None + + def iter_descendants(self): + for child in self.children: + yield child + for descendant in child.iter_descendants(): + yield descendant + + @property + def as_raw(self): + return collections.OrderedDict(( + ('name', self.name), + ('description', self.description), + ('role', self.role))) + + @property + def as_raw_all(self): + types = [] + self._append_raw_children(types) + return types + + def dump(self, context): + if self.name: + console.puts(context.style.type(self.name)) + with context.style.indent: + for child in self.children: + child.dump(context) + + def _append_raw_children(self, types): + for child in self.children: + raw_child = formatting.as_raw(child) + raw_child['parent'] = self.name + types.append(raw_child) + child._append_raw_children(types) + + +class MetadataBase(TemplateModelMixin): + """ + Custom values associated with the service. + + This model is used by both service template and service instance elements. + + :ivar name: Name + :ivar value: Value + """ + + __tablename__ = 'metadata' + + 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) + + def dump(self, context): + console.puts('{0}: {1}'.format( + context.style.property(self.name), + context.style.literal(self.value))) + + +class PluginSpecificationBase(InstanceModelMixin): + """ + Plugin specification model representation. + """ + + __tablename__ = 'plugin_specification' + + archive_name = Column(Text, nullable=False, index=True) + distribution = Column(Text) + distribution_release = Column(Text) + distribution_version = Column(Text) + package_name = Column(Text, nullable=False, index=True) + package_source = Column(Text) + package_version = Column(Text) + supported_platform = Column(Text) + supported_py_versions = Column(List) + + # region foreign keys + + __private_fields__ = ['service_template_fk'] + + @declared_attr + def service_template_fk(cls): + return cls._create_foreign_key('service_template', nullable=True) + + # endregion + + def find_plugin(self, plugins): + # TODO: this should check versions/distribution and other specification + for plugin in plugins: + if plugin.name == self.name: + return plugin + return None
