Add rich docs, fix many tests
Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/24ad8ba1 Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/24ad8ba1 Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/24ad8ba1 Branch: refs/heads/ARIA-105-integrate-modeling Commit: 24ad8ba1e1a013a2348880459e81830fc270b16f Parents: 36eb2d2 Author: Tal Liron <[email protected]> Authored: Sat Mar 4 21:31:42 2017 -0600 Committer: Tal Liron <[email protected]> Committed: Sat Mar 4 21:31:42 2017 -0600 ---------------------------------------------------------------------- aria/modeling/orchestration.py | 18 +- aria/modeling/service.py | 369 +++++++++++------ aria/modeling/service_template.py | 402 ++++++++++++------- aria/modeling/utils.py | 36 ++ .../simple_v1_0/modeling/__init__.py | 16 +- tests/mock/context.py | 4 +- tests/mock/models.py | 217 +++++----- tests/mock/topology.py | 90 ++--- tests/orchestrator/context/test_operation.py | 56 +-- tests/orchestrator/test_runner.py | 5 +- tests/storage/test_instrumentation.py | 10 +- tests/storage/test_model_storage.py | 38 +- tests/storage/test_models.py | 337 ++++++++-------- tests/storage/test_structures.py | 52 ++- 14 files changed, 971 insertions(+), 679 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/24ad8ba1/aria/modeling/orchestration.py ---------------------------------------------------------------------- diff --git a/aria/modeling/orchestration.py b/aria/modeling/orchestration.py index 9cbb3cf..30879e9 100644 --- a/aria/modeling/orchestration.py +++ b/aria/modeling/orchestration.py @@ -154,13 +154,13 @@ class ServiceUpdateBase(ModelMixin): __tablename__ = 'service_update' - _private_fields = ['execution_fk', 'deployment_fk'] + _private_fields = ['execution_fk', 'service_fk'] created_at = Column(DateTime, nullable=False, index=True) service_plan = Column(Dict, nullable=False) - service_update_node_instances = Column(Dict) - service_update_service_instance = Column(Dict) - service_update_nodes = Column(List) + service_update_nodes = Column(Dict) + service_update_service = Column(Dict) + service_update_node_templates = Column(List) modified_entity_ids = Column(Dict) state = Column(Text) @@ -239,8 +239,8 @@ class ServiceUpdateStepBase(ModelMixin): backreference='steps') @declared_attr - def deployment_update_name(cls): - return association_proxy('deployment_update', cls.name_column_name()) + def service_update_name(cls): + return association_proxy('service_update', cls.name_column_name()) # region foreign keys @@ -297,9 +297,9 @@ class ServiceModificationBase(ModelMixin): context = Column(Dict) created_at = Column(DateTime, nullable=False, index=True) ended_at = Column(DateTime, index=True) - modified_nodes = Column(Dict) - node_instances = Column(Dict) - status = Column(Enum(*STATES, name='deployment_modification_status')) + modified_node_templates = Column(Dict) + nodes = Column(Dict) + status = Column(Enum(*STATES, name='service_modification_status')) @declared_attr def service(cls): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/24ad8ba1/aria/modeling/service.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service.py b/aria/modeling/service.py index e555b70..842d0fc 100644 --- a/aria/modeling/service.py +++ b/aria/modeling/service.py @@ -36,23 +36,47 @@ from . import ( class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods """ - A service instance is usually an instance of a :class:`ServiceTemplate`. + A service is usually an instance of a :class:`ServiceTemplate`. - You will usually not create it programmatically, but instead instantiate it from the template. + 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 - :ivar meta_data: Dict of :class:`Metadata` - :ivar nodes: Dict of :class:`Node` - :ivar groups: Dict of :class:`Group` - :ivar policies: Dict of :class:`Policy` - :ivar substitution: :class:`Substitution` - :ivar inputs: Dict of :class:`Parameter` - :ivar outputs: Dict of :class:`Parameter` - :ivar operations: Dict of :class:`Operation` + :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` """ __tablename__ = 'service' + @declared_attr + def service_template(cls): + return cls.many_to_one_relationship('service_template') + description = Column(Text) @declared_attr @@ -91,20 +115,16 @@ class ServiceBase(InstanceModelMixin): # pylint: disable=too-many-public-methods return cls.one_to_many_relationship('operation', dict_key='name') @declared_attr - def service_template(cls): - return cls.many_to_one_relationship('service_template') - - # region orchestration + 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) - workflows = Column(modeling_types.Dict) - - @declared_attr - def service_template_name(cls): - return association_proxy('service_template', 'name') # endregion @@ -260,20 +280,44 @@ class NodeBase(InstanceModelMixin): Nodes may have zero or more :class:`Relationship` instances to other nodes. - :ivar name: Unique ID (often prefixed with the template name) - :ivar properties: Dict of :class:`Parameter` - :ivar interfaces: Dict of :class:`Interface` - :ivar artifacts: Dict of :class:`Artifact` - :ivar capabilities: Dict of :class:`CapabilityTemplate` - :ivar relationships: List of :class:`Relationship` + :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` """ __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', @@ -304,12 +348,12 @@ class NodeBase(InstanceModelMixin): backreference='target_node') @declared_attr - def host(cls): - return cls.relationship_to_self('host_fk') + def plugins(cls): + return cls.many_to_many_relationship('plugin') @declared_attr - def node_template(cls): - return cls.many_to_one_relationship('node_template') + def host(cls): + return cls.relationship_to_self('host_fk') # region orchestration @@ -319,10 +363,6 @@ class NodeBase(InstanceModelMixin): version = Column(Integer, default=1) @declared_attr - def plugins(cls): - return association_proxy('node_template', 'plugins') - - @declared_attr def service_name(cls): return association_proxy('service', 'name') @@ -335,9 +375,9 @@ class NodeBase(InstanceModelMixin): if 'ip' in host_node.runtime_properties: # pylint: disable=no-member return host_node.runtime_properties['ip'] # pylint: disable=no-member host_node = host_node.node_template # pylint: disable=no-member - host_ip_property = [prop for prop in host_node.properties if prop.name == 'ip'] + host_ip_property = host_node.properties.get('ip') if host_ip_property: - return host_ip_property[0].value + return host_ip_property.value return None # endregion @@ -415,7 +455,7 @@ class NodeBase(InstanceModelMixin): relationship = \ requirement_template.relationship_template.instantiate(context, self) else: - relationship = models.Relationship(capability=target_capability) + relationship = models.Relationship(target_capability=target_capability) relationship.name = requirement_template.name relationship.requirement_template = requirement_template relationship.target_node = target_node @@ -504,17 +544,38 @@ class GroupBase(InstanceModelMixin): """ Usually an instance of a :class:`GroupTemplate`. - :ivar name: Unique ID (often equal to the template name) - :ivar properties: Dict of :class:`Parameter` - :ivar interfaces: Dict of :class:`Interface` + :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`} """ __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', @@ -524,14 +585,6 @@ class GroupBase(InstanceModelMixin): def interfaces(cls): return cls.one_to_many_relationship('interface', dict_key='name') - @declared_attr - def nodes(cls): - return cls.many_to_many_relationship('node') - - @declared_attr - def group_template(cls): - return cls.many_to_one_relationship('group_template') - # region foreign_keys __private_fields__ = ['type_fk', @@ -587,16 +640,34 @@ class PolicyBase(InstanceModelMixin): """ Usually an instance of a :class:`PolicyTemplate`. - :ivar name: Name - :ivar properties: Dict of :class:`Parameter` + :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`} """ __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', @@ -610,10 +681,6 @@ class PolicyBase(InstanceModelMixin): def groups(cls): return cls.many_to_many_relationship('group') - @declared_attr - def policy_template(cls): - return cls.many_to_one_relationship('policy_template') - # region foreign_keys __private_fields__ = ['type_fk', @@ -669,14 +736,25 @@ class PolicyBase(InstanceModelMixin): class SubstitutionBase(InstanceModelMixin): """ + Used to substitute a single node for the entire deployment. + Usually an instance of a :class:`SubstitutionTemplate`. - :ivar mappings: Dict of :class:` SubstitutionMapping` + :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`} """ __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') @@ -684,10 +762,6 @@ class SubstitutionBase(InstanceModelMixin): def mappings(cls): return cls.one_to_many_relationship('substitution_mapping', dict_key='name') - @declared_attr - def substitution_template(cls): - return cls.many_to_one_relationship('substitution_template') - # region foreign_keys __private_fields__ = ['node_type_fk', @@ -726,9 +800,20 @@ class SubstitutionBase(InstanceModelMixin): class SubstitutionMappingBase(InstanceModelMixin): """ - An instance of a :class:`SubstitutionMappingTemplate`. + 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` """ __tablename__ = 'substitution_mapping' @@ -798,22 +883,43 @@ class SubstitutionMappingBase(InstanceModelMixin): class RelationshipBase(InstanceModelMixin): """ - Connects :class:`Node` to another node. + 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) - :ivar properties: Dict of :class:`Parameter` - :ivar interfaces: Dict of :class:`Interface` + :vartype name: basestring + :ivar relationship_template: Template from which this relationship was instantiated (optional) + :vartype relationship_template: :class:`RelationshipTemplate` + :ivar type: Relationship type + :vartype type: :class:`Type` + :ivar source_node: Source node + :vartype source_node: :class:`Node` + :ivar target_node: Target node + :vartype target_node: :class:`Node` + :ivar target_capability: Capability at the target node (optional) + :vartype target_capability: :class:`Capability` + :ivar properties: Associated parameters + :vartype properties: {basestring: :class:`Parameter`} + :ivar interface_templates: Bundles of operations + :vartype interface_templates: {basestring: :class:`InterfaceTemplate`} """ __tablename__ = 'relationship' @declared_attr + def relationship_template(cls): + return cls.many_to_one_relationship('relationship_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') @@ -823,17 +929,9 @@ class RelationshipBase(InstanceModelMixin): return cls.one_to_many_relationship('interface', dict_key='name') @declared_attr - def capability(cls): - return cls.one_to_one_relationship('capability') - - @declared_attr def requirement_template(cls): return cls.many_to_one_relationship('requirement_template') - @declared_attr - def relationship_template(cls): - return cls.many_to_one_relationship('relationship_template') - # region orchestration source_position = Column(Integer) # ??? @@ -907,8 +1005,9 @@ class RelationshipBase(InstanceModelMixin): console.puts('->') with context.style.indent: console.puts('Node: {0}'.format(context.style.node(self.target_node.name))) - if self.capability: - console.puts('Capability: {0}'.format(context.style.node(self.capability.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: @@ -924,20 +1023,34 @@ class CapabilityBase(InstanceModelMixin): Usually an instance of a :class:`CapabilityTemplate`. - :ivar name: Name + :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 - :ivar properties: Dict of :class:`Parameter` + :vartype min_occurrences: int + :ivar occurrences: Actual number of requirement matches + :vartype occurrences: int + :ivar properties: Associated parameters + :vartype properties: {basestring: :class:`Parameter`} """ __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) # optional - max_occurrences = Column(Integer, default=None) # optional + min_occurrences = Column(Integer, default=None) + max_occurrences = Column(Integer, default=None) occurrences = Column(Integer, default=0) @declared_attr @@ -945,10 +1058,6 @@ class CapabilityBase(InstanceModelMixin): return cls.many_to_many_relationship('parameter', table_prefix='properties', dict_key='name') - @declared_attr - def capability_template(cls): - return cls.many_to_one_relationship('capability_template') - # region foreign_keys __private_fields__ = ['capability_fk', @@ -1017,15 +1126,27 @@ class InterfaceBase(InstanceModelMixin): Usually an instance of :class:`InterfaceTemplate`. - :ivar name: Name - :ivar description: Description - :ivar inputs: Dict of :class:`Parameter` - :ivar operations: Dict of :class:`Operation` + :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`} """ __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') @@ -1040,10 +1161,6 @@ class InterfaceBase(InstanceModelMixin): def operations(cls): return cls.one_to_many_relationship('operation', dict_key='name') - @declared_attr - def interface_template(cls): - return cls.many_to_one_relationship('interface_template') - # region foreign_keys __private_fields__ = ['type_fk', @@ -1112,37 +1229,51 @@ class OperationBase(InstanceModelMixin): Might be an instance of :class:`OperationTemplate`. - :ivar name: Name - :ivar description: Description - :ivar implementation: Implementation string (interpreted by the orchestrator) - :ivar dependencies: List of strings (interpreted by the orchestrator) - :ivar executor: Executor string (interpreted by the orchestrator) + :ivar 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 - :ivar retry_interval: Interval between retries - :ivar inputs: Dict of :class:`Parameter` + :vartype max_retries: int + :ivar retry_interval: Interval between retries (in seconds) + :vartype retry_interval: int """ __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)) - executor = Column(Text) - max_retries = Column(Integer) - retry_interval = Column(Integer) @declared_attr def inputs(cls): return cls.many_to_many_relationship('parameter', table_prefix='inputs', dict_key='name') - @declared_attr - def plugin(cls): - return cls.one_to_one_relationship('plugin') - - @declared_attr - def operation_template(cls): - return cls.many_to_one_relationship('operation_template') + executor = Column(Text) + max_retries = Column(Integer) + retry_interval = Column(Integer) # region foreign_keys @@ -1220,23 +1351,37 @@ class ArtifactBase(InstanceModelMixin): Usually an instance of :class:`ArtifactTemplate`. - :ivar name: Name - :ivar description: Description + :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 - :ivar repository_credential: Dict of string - :ivar properties: Dict of :class:`Parameter` + :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`} """ __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) - type_name = Column(Text) source_path = Column(Text) target_path = Column(Text) repository_url = Column(Text) @@ -1247,10 +1392,6 @@ class ArtifactBase(InstanceModelMixin): return cls.many_to_many_relationship('parameter', table_prefix='properties', dict_key='name') - @declared_attr - def artifact_template(cls): - return cls.many_to_one_relationship('artifact_template') - # region foreign_keys __private_fields__ = ['type_fk', http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/24ad8ba1/aria/modeling/service_template.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py index 1f2d1f3..aec7aa0 100644 --- a/aria/modeling/service_template.py +++ b/aria/modeling/service_template.py @@ -17,7 +17,6 @@ from __future__ import absolute_import # so we can import standard 'types' -from copy import deepcopy from types import FunctionType from datetime import datetime @@ -40,27 +39,61 @@ from . import ( class ServiceTemplateBase(TemplateModelMixin): """ - A service template is a normalized blueprint from which :class:`ServiceInstance` instances can - be created. + 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: string - :ivar meta_data: Dict of :class:`Metadata` - :ivar node_templates: List of :class:`NodeTemplate` - :ivar group_templates: List of :class:`GroupTemplate` - :ivar policy_templates: List of :class:`PolicyTemplate` - :ivar substitution_template: :class:`SubstitutionTemplate` - :ivar inputs: Dict of :class:`Parameter` - :ivar outputs: Dict of :class:`Parameter` - :ivar operation_templates: Dict of :class:`OperationTemplate` + :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: [:class:`NodeTemplate`] + :ivar group_templates: Templates for creating groups + :vartype group_templates: [:class:`GroupTemplate`] + :ivar policy_templates: Templates for creating policies + :vartype policy_templates: [: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 operation_templates: Custom operations that can be performed on the service + :vartype operation_templates: {basestring: :class:`OperationTemplate`} + :ivar plugins: Plugins required by services + :vartype plugins: {basestring: :class:`Plugin`} + :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 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` """ __tablename__ = 'service_template' description = Column(Text) + main_file_name = Column(Text) @declared_attr def meta_data(cls): @@ -98,6 +131,10 @@ class ServiceTemplateBase(TemplateModelMixin): return cls.one_to_many_relationship('operation_template', dict_key='name') @declared_attr + def plugins(cls): + return cls.one_to_many_relationship('plugin') + + @declared_attr def node_types(cls): return cls.one_to_one_relationship('type', key='node_type_fk', backreference='') @@ -106,40 +143,42 @@ class ServiceTemplateBase(TemplateModelMixin): return cls.one_to_one_relationship('type', key='group_type_fk', backreference='') @declared_attr - def capability_types(cls): - return cls.one_to_one_relationship('type', key='capability_type_fk', backreference='') + def policy_types(cls): + return cls.one_to_one_relationship('type', key='policy_type_fk', backreference='') @declared_attr def relationship_types(cls): return cls.one_to_one_relationship('type', key='relationship_type_fk', backreference='') @declared_attr - def policy_types(cls): - return cls.one_to_one_relationship('type', key='policy_type_fk', backreference='') - - @declared_attr - def artifact_types(cls): - return cls.one_to_one_relationship('type', key='artifact_type_fk', backreference='') + def capability_types(cls): + return cls.one_to_one_relationship('type', key='capability_type_fk', backreference='') @declared_attr def interface_types(cls): return cls.one_to_one_relationship('type', key='interface_type_fk', backreference='') + @declared_attr + def artifact_types(cls): + return cls.one_to_one_relationship('type', key='artifact_type_fk', backreference='') + # region orchestration created_at = Column(DateTime, nullable=False, index=True) updated_at = Column(DateTime) - main_file_name = Column(Text) - - @declared_attr - def plugins(cls): - return cls.one_to_many_relationship('plugin', dict_key='name') # endregion # region foreign keys - __private_fields__ = ['substitution_template_fk'] + __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'] # ServiceTemplate one-to-one to SubstitutionTemplate @declared_attr @@ -158,7 +197,7 @@ class ServiceTemplateBase(TemplateModelMixin): # ServiceTemplate one-to-one to Type @declared_attr - def capability_type_fk(cls): + def policy_type_fk(cls): return cls.foreign_key('type', nullable=True) # ServiceTemplate one-to-one to Type @@ -168,17 +207,17 @@ class ServiceTemplateBase(TemplateModelMixin): # ServiceTemplate one-to-one to Type @declared_attr - def policy_type_fk(cls): + def capability_type_fk(cls): return cls.foreign_key('type', nullable=True) # ServiceTemplate one-to-one to Type @declared_attr - def artifact_type_fk(cls): + def interface_type_fk(cls): return cls.foreign_key('type', nullable=True) # ServiceTemplate one-to-one to Type @declared_attr - def interface_type_fk(cls): + def artifact_type_fk(cls): return cls.foreign_key('type', nullable=True) # endregion @@ -215,18 +254,18 @@ class ServiceTemplateBase(TemplateModelMixin): return collections.OrderedDict(( ('node_types', formatting.as_raw(self.node_types)), ('group_types', formatting.as_raw(self.group_types)), - ('capability_types', formatting.as_raw(self.capability_types)), - ('relationship_types', formatting.as_raw(self.relationship_types)), ('policy_types', formatting.as_raw(self.policy_types)), - ('artifact_types', formatting.as_raw(self.artifact_types)), - ('interface_types', formatting.as_raw(self.interface_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, context, container): from . import models now = datetime.now() service = models.Service(created_at=now, updated_at=now, - description=deepcopy_with_locators(self.description), + description=utils.deepcopy_with_locators(self.description), service_template=self) #service.name = '{0}_{1}'.format(self.name, service.id) @@ -267,6 +306,20 @@ class ServiceTemplateBase(TemplateModelMixin): utils.validate_dict_values(context, self.inputs) utils.validate_dict_values(context, self.outputs) utils.validate_dict_values(context, self.operation_templates) + if self.node_types is not None: + self.node_types.validate(context) + if self.group_types is not None: + self.group_types.validate(context) + if self.policy_types is not None: + self.policy_types.validate(context) + if self.relationship_types is not None: + self.relationship_types.validate(context) + if self.capability_types is not None: + self.capability_types.validate(context) + if self.interface_types is not None: + self.interface_types.validate(context) + if self.artifact_types is not None: + self.artifact_types.validate(context) def coerce_values(self, context, container, report_issues): utils.coerce_dict_values(context, container, self.meta_data, report_issues) @@ -283,7 +336,6 @@ class ServiceTemplateBase(TemplateModelMixin): if self.description is not None: console.puts(context.style.meta(self.description)) utils.dump_dict_values(context, self.meta_data, 'Metadata') - for node_template in self.node_templates: node_template.dump(context) for group_template in self.group_templates: @@ -327,17 +379,33 @@ class NodeTemplateBase(TemplateModelMixin): """ A template for creating zero or more :class:`Node` instances. - :ivar name: Name (will be used as a prefix for node IDs) - :ivar description: Description - :ivar default_instances: Default number nodes that will appear in the deployment plan - :ivar min_instances: Minimum number nodes that will appear in the deployment plan - :ivar max_instances: Maximum number nodes that will appear in the deployment plan - :ivar properties: Dict of :class:`Parameter` - :ivar interface_templates: Dict of :class:`InterfaceTemplate` - :ivar artifact_templates: Dict of :class:`ArtifactTemplate` - :ivar capability_templates: Dict of :class:`CapabilityTemplate` - :ivar requirement_templates: List of :class:`RequirementTemplate` - :ivar target_node_template_constraints: List of :class:`FunctionType` + :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:`FunctionType`] + :ivar plugins: Plugins required to be installed on the node's host + :vartype plugins: {basestring: :class:`Plugin`} """ __tablename__ = 'node_template' @@ -350,7 +418,6 @@ class NodeTemplateBase(TemplateModelMixin): default_instances = Column(Integer, default=1) min_instances = Column(Integer, default=0) max_instances = Column(Integer, default=None) - target_node_template_constraints = Column(modeling_types.StrictList(FunctionType)) @declared_attr def properties(cls): @@ -372,15 +439,14 @@ class NodeTemplateBase(TemplateModelMixin): @declared_attr def requirement_templates(cls): return cls.one_to_many_relationship('requirement_template', - foreign_key='node_template_fk') + foreign_key='node_template_fk', + backreference='node_template') - # region orchestration + target_node_template_constraints = Column(modeling_types.StrictList(FunctionType)) @declared_attr def plugins(cls): - return cls.many_to_many_relationship('plugin', dict_key='name') - - # endregion + return cls.many_to_many_relationship('plugin') # region foreign_keys @@ -426,6 +492,7 @@ class NodeTemplateBase(TemplateModelMixin): name = context.modeling.generate_node_id(self.name) node = models.Node(name=name, type=self.type, + description=utils.deepcopy_with_locators(self.description), state='', node_template=self) utils.instantiate_dict(context, node, node.properties, self.properties) @@ -469,15 +536,22 @@ class NodeTemplateBase(TemplateModelMixin): class GroupTemplateBase(TemplateModelMixin): """ - A template for creating zero or more :class:`Group` instances. + A template for creating a :class:`Group` instance. - Groups are logical containers for zero or more nodes that allow applying zero or more - :class:`GroupPolicy` instances to the nodes together. + Groups are logical containers for zero or more nodes. - :ivar name: Name (will be used as a prefix for group IDs) - :ivar description: Description - :ivar properties: Dict of :class:`Parameter` - :ivar interface_templates: Dict of :class:`InterfaceTemplate` + :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`} """ __tablename__ = 'group_template' @@ -489,6 +563,10 @@ class GroupTemplateBase(TemplateModelMixin): description = Column(Text) @declared_attr + def node_templates(cls): + return cls.many_to_many_relationship('node_template') + + @declared_attr def properties(cls): return cls.many_to_many_relationship('parameter', table_prefix='properties', dict_key='name') @@ -497,10 +575,6 @@ class GroupTemplateBase(TemplateModelMixin): def interface_templates(cls): return cls.one_to_many_relationship('interface_template', dict_key='name') - @declared_attr - def node_templates(cls): - return cls.many_to_many_relationship('node_template') - # region foreign keys __private_fields__ = ['type_fk', @@ -531,6 +605,7 @@ class GroupTemplateBase(TemplateModelMixin): from . import models group = models.Group(name=self.name, type=self.type, + description=utils.deepcopy_with_locators(self.description), group_template=self) utils.instantiate_dict(context, self, group.properties, self.properties) utils.instantiate_dict(context, self, group.interfaces, self.interface_templates) @@ -565,9 +640,18 @@ class PolicyTemplateBase(TemplateModelMixin): Policies can be applied to zero or more :class:`NodeTemplate` or :class:`GroupTemplate` instances. - :ivar name: Name - :ivar description: Description - :ivar properties: Dict of :class:`Parameter` + :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`} """ __tablename__ = 'policy_template' @@ -579,11 +663,6 @@ class PolicyTemplateBase(TemplateModelMixin): description = Column(Text) @declared_attr - def properties(cls): - return cls.many_to_many_relationship('parameter', table_prefix='properties', - dict_key='name') - - @declared_attr def node_templates(cls): return cls.many_to_many_relationship('node_template') @@ -591,6 +670,11 @@ class PolicyTemplateBase(TemplateModelMixin): def group_templates(cls): return cls.many_to_many_relationship('group_template') + @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', @@ -620,6 +704,7 @@ class PolicyTemplateBase(TemplateModelMixin): from . import models policy = models.Policy(name=self.name, type=self.type, + description=utils.deepcopy_with_locators(self.description), policy_template=self) utils.instantiate_dict(context, self, policy.properties, self.properties) if self.node_templates: @@ -655,7 +740,10 @@ class SubstitutionTemplateBase(TemplateModelMixin): """ Used to substitute a single node for the entire deployment. - :ivar mappings: Dict of :class:` SubstitutionTemplateMapping` + :ivar node_type: Exposed node type + :vartype node_type: :class:`Type` + :ivar mappings: Requirement and capability mappings + :vartype mappings: {basestring: :class:`SubstitutionTemplateMapping`} """ __tablename__ = 'substitution_template' @@ -708,8 +796,17 @@ class SubstitutionTemplateBase(TemplateModelMixin): 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` """ __tablename__ = 'substitution_template_mapping' @@ -807,10 +904,20 @@ class RequirementTemplateBase(TemplateModelMixin): Requirements may optionally contain a :class:`RelationshipTemplate` that will be created between the nodes. - :ivar name: Name - :ivar target_node_template_constraints: List of :class:`FunctionType` - :ivar target_capability_name: Name of capability in target node - :ivar relationship_template: :class:`RelationshipTemplate` + :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:`FunctionType`] + :ivar relationship_template: Template for relationships (optional) + :vartype relationship_template: :class:`RelationshipTemplate` """ __tablename__ = 'requirement_template' @@ -829,8 +936,8 @@ class RequirementTemplateBase(TemplateModelMixin): return cls.one_to_one_relationship('type', key='target_capability_type_fk', backreference='') - target_node_template_constraints = Column(modeling_types.StrictList(FunctionType)) target_capability_name = Column(Text) + target_node_template_constraints = Column(modeling_types.StrictList(FunctionType)) @declared_attr def relationship_template(cls): @@ -972,10 +1079,21 @@ class RelationshipTemplateBase(TemplateModelMixin): """ Optional addition to a :class:`RequirementTemplate` in :class:`NodeTemplate` that can be applied when the requirement is matched with a capability. - - :ivar description: Description - :ivar properties: Dict of :class:`Parameter` - :ivar interface_templates: Dict of :class:`InterfaceTemplate` + + 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`} """ __tablename__ = 'relationship_template' @@ -1052,11 +1170,18 @@ class CapabilityTemplateBase(TemplateModelMixin): A capability of a :class:`NodeTemplate`. Nodes expose zero or more capabilities that can be matched with :class:`Requirement` instances of other nodes. - :ivar name: Name - :ivar description: Description + :ivar 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 min_occurrences: Minimum number of requirement matches required + :vartype min_occurrences: int :ivar max_occurrences: Maximum number of requirement matches allowed - :ivar properties: Dict of :class:`Parameter` + :vartype min_occurrences: int + :ivar properties: Associated parameters + :vartype properties: {basestring: :class:`Parameter`} """ __tablename__ = 'capability_template' @@ -1170,10 +1295,16 @@ class InterfaceTemplateBase(TemplateModelMixin): """ A typed set of :class:`OperationTemplate`. - :ivar name: Name - :ivar description: Description - :ivar inputs: Dict of :class:`Parameter` - :ivar operation_templates: Dict 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`} """ __tablename__ = 'interface_template' @@ -1234,8 +1365,8 @@ class InterfaceTemplateBase(TemplateModelMixin): def instantiate(self, context, container): from . import models interface = models.Interface(name=self.name, - description=deepcopy_with_locators(self.description), type=self.type, + description=utils.deepcopy_with_locators(self.description), interface_template=self) utils.instantiate_dict(context, container, interface.inputs, self.inputs) utils.instantiate_dict(context, container, interface.operations, self.operation_templates) @@ -1263,34 +1394,44 @@ class OperationTemplateBase(TemplateModelMixin): """ An operation in a :class:`InterfaceTemplate`. - :ivar name: Name - :ivar description: Description - :ivar implementation: Implementation string (interpreted by the orchestrator) - :ivar dependencies: List of strings (interpreted by the orchestrator) - :ivar executor: Executor string (interpreted by the orchestrator) + Operations are executed by an associated :class:`Plugin` 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: 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 - :ivar retry_interval: Interval between retries - :ivar inputs: Dict of :class:`Parameter` + :vartype max_retries: int + :ivar retry_interval: Interval between retries (in seconds) + :vartype retry_interval: int """ __tablename__ = '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)) - executor = Column(Text) - max_retries = Column(Integer) - retry_interval = Column(Integer) @declared_attr def inputs(cls): return cls.many_to_many_relationship('parameter', table_prefix='inputs', dict_key='name') - @declared_attr - def plugin(cls): - return cls.one_to_one_relationship('plugin') - executor = Column(Text) max_retries = Column(Integer) retry_interval = Column(Integer) @@ -1333,7 +1474,7 @@ class OperationTemplateBase(TemplateModelMixin): def instantiate(self, context, container): from . import models operation = models.Operation(name=self.name, - description=deepcopy_with_locators(self.description), + description=utils.deepcopy_with_locators(self.description), implementation=self.implementation, dependencies=self.dependencies, plugin=self.plugin, @@ -1375,13 +1516,22 @@ class ArtifactTemplateBase(TemplateModelMixin): """ A file associated with a :class:`NodeTemplate`. - :ivar name: Name - :ivar description: Description + :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 - :ivar repository_credential: Dict of string - :ivar properties: Dict of :class:`Parameter` + :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`} """ __tablename__ = 'artifact_template' @@ -1434,8 +1584,8 @@ class ArtifactTemplateBase(TemplateModelMixin): from . import models artifact = models.Artifact(name=self.name, type=self.type, + description=utils.deepcopy_with_locators(self.description), source_path=self.source_path, - description=deepcopy_with_locators(self.description), target_path=self.target_path, repository_url=self.repository_url, repository_credential=self.repository_credential, @@ -1465,35 +1615,3 @@ class ArtifactTemplateBase(TemplateModelMixin): console.puts('Repository credential: {0}'.format( context.style.literal(self.repository_credential))) utils.dump_dict_values(context, self.properties, 'Properties') - - -def deepcopy_with_locators(value): - """ - Like :code:`deepcopy`, but also copies over locators. - """ - - res = deepcopy(value) - copy_locators(res, value) - return res - - -def copy_locators(target, source): - """ - Copies over :code:`_locator` for all elements, recursively. - - Assumes that target and source have exactly the same list/dict structure. - """ - - locator = getattr(source, '_locator', None) - if locator is not None: - try: - setattr(target, '_locator', locator) - except AttributeError: - pass - - if isinstance(target, list) and isinstance(source, list): - for i, _ in enumerate(target): - copy_locators(target[i], source[i]) - elif isinstance(target, dict) and isinstance(source, dict): - for k, v in target.items(): - copy_locators(v, source[k]) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/24ad8ba1/aria/modeling/utils.py ---------------------------------------------------------------------- diff --git a/aria/modeling/utils.py b/aria/modeling/utils.py index a2f0bfc..a071d69 100644 --- a/aria/modeling/utils.py +++ b/aria/modeling/utils.py @@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from copy import deepcopy + from ..parser.exceptions import InvalidValueError from ..parser.presentation import Value from ..utils.collections import OrderedDict @@ -109,6 +111,40 @@ def dump_interfaces(context, interfaces, name='Interfaces'): interface.dump(context) + + +def deepcopy_with_locators(value): + """ + Like :code:`deepcopy`, but also copies over locators. + """ + + res = deepcopy(value) + copy_locators(res, value) + return res + + +def copy_locators(target, source): + """ + Copies over :code:`_locator` for all elements, recursively. + + Assumes that target and source have exactly the same list/dict structure. + """ + + locator = getattr(source, '_locator', None) + if locator is not None: + try: + setattr(target, '_locator', locator) + except AttributeError: + pass + + if isinstance(target, list) and isinstance(source, list): + for i, _ in enumerate(target): + copy_locators(target[i], source[i]) + elif isinstance(target, dict) and isinstance(source, dict): + for k, v in target.items(): + copy_locators(v, source[k]) + + class classproperty(object): # pylint: disable=invalid-name def __init__(self, f): self._func = f http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/24ad8ba1/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py index 959f3c4..0206e03 100644 --- a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py +++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py @@ -64,14 +64,14 @@ def create_service_template_model(context): # pylint: disable=too-many-locals,to create_types(context, model.capability_types, context.presentation.get('service_template', 'capability_types')) - model.artifact_types = Type(variant='artifact') - create_types(context, - model.artifact_types, - context.presentation.get('service_template', 'artifact_types')) model.interface_types = Type(variant='interface') create_types(context, model.interface_types, context.presentation.get('service_template', 'interface_types')) + model.artifact_types = Type(variant='artifact') + create_types(context, + model.artifact_types, + context.presentation.get('service_template', 'artifact_types')) # Topology template topology_template = context.presentation.get('service_template', 'topology_template') @@ -86,7 +86,7 @@ def create_service_template_model(context): # pylint: disable=too-many-locals,to if policies: for policy in policies.itervalues(): if model.policy_types.get_descendant(policy.type).role == 'plugin': - model.plugins[policy._name] = create_plugin_model(context, policy) + model.plugins.append(create_plugin_model(context, policy)) # Node templates node_templates = context.presentation.get('service_template', 'topology_template', @@ -667,7 +667,11 @@ def parse_implementation_string(context, service_template, implementation): if plugin_name == 'execution': plugin = None else: - plugin = service_template.plugins.get(plugin_name) + plugin = None + for the_plugin in service_template.plugins: + if the_plugin.name == plugin_name: + plugin = the_plugin + break if plugin is None: raise ValueError('unknown plugin: "{0}"'.format(plugin_name)) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/24ad8ba1/tests/mock/context.py ---------------------------------------------------------------------- diff --git a/tests/mock/context.py b/tests/mock/context.py index cf1a32d..3de3133 100644 --- a/tests/mock/context.py +++ b/tests/mock/context.py @@ -38,13 +38,13 @@ def simple(tmpdir, inmemory=False, context_kwargs=None): api_kwargs=dict(directory=os.path.join(tmpdir, 'resources')) ) - service_instance_id = create_simple_topology_two_nodes(model_storage) + service_id = create_simple_topology_two_nodes(model_storage) final_kwargs = dict( name='simple_context', model_storage=model_storage, resource_storage=resource_storage, - service_instance_id=service_instance_id, + service_id=service_id, workflow_name=models.WORKFLOW_NAME, task_max_attempts=models.TASK_MAX_ATTEMPTS, task_retry_interval=models.TASK_RETRY_INTERVAL http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/24ad8ba1/tests/mock/models.py ---------------------------------------------------------------------- diff --git a/tests/mock/models.py b/tests/mock/models.py index 522cc78..3ccd97e 100644 --- a/tests/mock/models.py +++ b/tests/mock/models.py @@ -18,107 +18,161 @@ from datetime import datetime from aria.modeling import models from . import operations -SERVICE_NAME = 'test_service_id' -SERVICE_TEMPLATE_NAME = 'test_service_template_id' -WORKFLOW_NAME = 'test_workflow_id' -EXECUTION_NAME = 'test_execution_id' +SERVICE_NAME = 'test_service_name' +SERVICE_TEMPLATE_NAME = 'test_service_template_name' +WORKFLOW_NAME = 'test_workflow_name' +EXECUTION_NAME = 'test_execution_name' TASK_RETRY_INTERVAL = 1 TASK_MAX_ATTEMPTS = 1 -DEPENDENCY_NODE_NAME = 'dependency_node_template' -DEPENDENCY_NODE_INSTANCE_NAME = 'dependency_node' -DEPENDENT_NODE_NAME = 'dependent_node_template' -DEPENDENT_NODE_INSTANCE_NAME = 'dependent_node' +DEPENDENCY_NODE_TEMPLATE_NAME = 'dependency_node_template' +DEPENDENCY_NODE_NAME = 'dependency_node' +DEPENDENT_NODE_TEMPLATE_NAME = 'dependent_node_template' +DEPENDENT_NODE_NAME = 'dependent_node' -def create_dependency_node_template(service): - return models.NodeTemplate( - name=DEPENDENCY_NODE_NAME, - type=service.service_template.node_types.get_descendant('test_node_type'), - default_instances=1, - min_instances=1, - max_instances=1, - service_template_fk=service.service_template.id, +def create_service_template(): + now = datetime.now() + return models.ServiceTemplate( + name=SERVICE_TEMPLATE_NAME, + description=None, + created_at=now, + updated_at=now, + main_file_name='main_file_name', + node_types=models.Type(variant='node', name='test_node_type'), + group_types=models.Type(variant='group', name='test_group_type'), + policy_types=models.Type(variant='policy', name='test_policy_type'), + relationship_types=models.Type(variant='relationship', name='test_relationship_type'), + capability_types=models.Type(variant='capability', name='test_capability_type'), + artifact_types=models.Type(variant='artifact', name='test_artifact_type'), + interface_types=models.Type(variant='interface', name='test_interface_type') ) -def create_dependency_node(dependency_node_template, service): - return models.Node( - name=DEPENDENCY_NODE_INSTANCE_NAME, - type=dependency_node_template.type, - service=service, - runtime_properties={'ip': '1.1.1.1'}, - version=None, - node_template=dependency_node_template, - state='', - scaling_groups=[] +def create_service(service_template): + now = datetime.utcnow() + return models.Service( + name=SERVICE_NAME, + service_template=service_template, + description='', + created_at=now, + updated_at=now, + permalink='', + scaling_groups={}, ) -def create_requirement(source): - requirement_template = models.RequirementTemplate(node_template=source) +def create_dependency_node_template(service_template): + the_type = service_template.node_types.get_descendant('test_node_type') + capability_template = models.CapabilityTemplate( - node_template=source, - type=source.service_template.capability_types.get_descendant('test_capability_type')) - return requirement_template, capability_template - - -def create_relationship(source_instance, target_instance): - return models.Relationship( - target_node=target_instance, - source_node=source_instance, + name='capability', + type=service_template.capability_types.get_descendant('test_capability_type') ) + + node_template = models.NodeTemplate( + name=DEPENDENCY_NODE_TEMPLATE_NAME, + type=the_type, + capability_templates=_dictify(capability_template), + default_instances=1, + min_instances=1, + max_instances=1, + service_template=service_template + ) + service_template.node_templates.append(node_template) + return node_template + +def create_dependent_node_template(service_template, dependency_node_template): + the_type = service_template.node_types.get_descendant('test_node_type') -def create_dependent_node_template(service, requirement_template, capability_template): operation_templates = dict((op, models.OperationTemplate( name=op, implementation='test')) for op in operations.NODE_OPERATIONS) interface_template = models.InterfaceTemplate( - type=service.service_template.interface_types.get_descendant('test_interface_type'), + type=service_template.interface_types.get_descendant('test_interface_type'), operation_templates=operation_templates) - return models.NodeTemplate( - name=DEPENDENT_NODE_NAME, - type=service.service_template.node_types.get_descendant('test_node_type'), + requirement_template = models.RequirementTemplate( + name='requirement', + target_node_template=dependency_node_template + ) + + node_template = models.NodeTemplate( + name=DEPENDENT_NODE_TEMPLATE_NAME, + type=the_type, default_instances=1, min_instances=1, max_instances=1, - service_template=service.service_template, interface_templates=_dictify(interface_template), requirement_templates=[requirement_template], - capability_templates=_dictify(capability_template), + service_template=service_template + ) + service_template.node_templates.append(node_template) + return node_template + + +def create_dependency_node(dependency_node_template, service): + node = models.Node( + name=DEPENDENCY_NODE_NAME, + type=dependency_node_template.type, + runtime_properties={'ip': '1.1.1.1'}, + version=None, + node_template=dependency_node_template, + state='', + scaling_groups=[] ) + service.nodes.append(node) + return node def create_dependent_node(dependent_node_template, service): - return models.Node( - name=DEPENDENT_NODE_INSTANCE_NAME, - service=service, + node = models.Node( + name=DEPENDENT_NODE_NAME, + type=dependent_node_template.type, runtime_properties={}, version=None, node_template=dependent_node_template, state='', scaling_groups=[], ) + service.nodes.append(node) + return node -def create_service_template(): - now = datetime.now() - return models.ServiceTemplate( - name=SERVICE_TEMPLATE_NAME, - description=None, - created_at=now, - updated_at=now, - main_file_name='main_file_name', - node_types=models.Type(variant='node', name='test_node_type'), - group_types=models.Type(variant='group', name='test_group_type'), - policy_types=models.Type(variant='policy', name='test_policy_type'), - relationship_types=models.Type(variant='relationship', name='test_relationship_type'), - capability_types=models.Type(variant='capability', name='test_capability_type'), - artifact_types=models.Type(variant='artifact', name='test_artifact_type'), - interface_types=models.Type(variant='interface', name='test_interface_type') +def create_relationship(source, target): + return models.Relationship( + source_node=source, + target_node=target + ) + + +def create_interface_template(service_template, operation_name, operation_kwargs=None, + interface_kwargs=None): + the_type = service_template.interface_types.get_descendant('test_interface_type') + interface_name, operation_name = operation_name.rsplit('.', 1) + operation_template = models.OperationTemplate( + name=operation_name, + **(operation_kwargs or {}) + ) + return models.InterfaceTemplate( + type=the_type, + operation_templates=_dictify(operation_template), + name=interface_name, + **(interface_kwargs or {}) + ) + + +def create_interface(service, operation_name, operation_kwargs=None, interface_kwargs=None): + the_type = service.service_template.interface_types.get_descendant('test_interface_type') + interface_name, operation_name = operation_name.rsplit('.', 1) + operation = models.Operation(name=operation_name, **(operation_kwargs or {})) + return models.Interface( + type=the_type, + operations=_dictify(operation), + name=interface_name, + **(interface_kwargs or {}) ) @@ -132,20 +186,6 @@ def create_execution(service): ) -def create_service(service_template): - now = datetime.utcnow() - return models.Service( - name=SERVICE_NAME, - service_template=service_template, - description='', - created_at=now, - updated_at=now, - workflows={}, - permalink='', - scaling_groups={}, - ) - - def create_plugin(package_name='package', package_version='0.1'): return models.Plugin( archive_name='archive_name', @@ -162,30 +202,5 @@ def create_plugin(package_name='package', package_version='0.1'): ) -def create_interface_template(operation_name, operation_kwargs=None, interface_kwargs=None): - operation_template = models.OperationTemplate( - name=operation_name, - **(operation_kwargs or {}) - - ) - return models.InterfaceTemplate( - operation_templates=_dictify(operation_template), - name=operation_name.rsplit('.', 1)[0], - **(interface_kwargs or {}) - ) - - -def create_interface(operation_name, - operation_kwargs=None, - interface_kwargs=None, - edge=None): - operation = models.Operation(name=operation_name, **(operation_kwargs or {})) - interface_name = operation_name.rsplit('.', 1)[0] - return models.Interface(operations=_dictify(operation), - name=interface_name, - edge=edge, - **(interface_kwargs or {})) - - def _dictify(item): return dict(((item.name, item),)) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/24ad8ba1/tests/mock/topology.py ---------------------------------------------------------------------- diff --git a/tests/mock/topology.py b/tests/mock/topology.py index b511ee3..b702783 100644 --- a/tests/mock/topology.py +++ b/tests/mock/topology.py @@ -20,67 +20,55 @@ from . import models def create_simple_topology_single_node(model_storage, create_operation): service_template = models.create_service_template() - model_storage.service_template.put(service_template) service = models.create_service(service_template) - model_storage.service.put(service) - node_template = models.create_dependency_node_template(service) - node_template.interface_templates = [models.create_interface_template( - 'tosca.interfaces.node.lifecycle.Standard.create', + node_template = models.create_dependency_node_template(service_template) + interface_template = models.create_interface_template( + service_template, + 'Standard.create', operation_kwargs=dict( implementation=create_operation, - inputs=[aria_models.Parameter(name='key', value='create', type_name='string'), - aria_models.Parameter(name='value', value=True, type_name='boolean')] - ) - )] - model_storage.node_template.put(node_template) + inputs={'key': aria_models.Parameter(name='key', value='create', type_name='string'), + 'value': aria_models.Parameter(name='value', value=True, type_name='boolean')}) + ) + node_template.interface_templates[interface_template.name] = interface_template node = models.create_dependency_node(node_template, service) - node.interfaces = [models.create_interface( - 'tosca.interfaces.node.lifecycle.Standard.create', + interface = models.create_interface( + service, + 'Standard.create', operation_kwargs=dict( implementation=create_operation, - inputs=[aria_models.Parameter(name='key', value='create', type_name='string'), - aria_models.Parameter(name='value', value=True, type_name='boolean')]) - )] - model_storage.node.put(node) + inputs={'key': aria_models.Parameter(name='key', value='create', type_name='string'), + 'value': aria_models.Parameter(name='value', value=True, type_name='boolean')}) + ) + node.interfaces[interface.name] = interface + + model_storage.service_template.put(service_template) + model_storage.service.put(service) def create_simple_topology_two_nodes(model_storage): - blueprint = models.create_service_template() - model_storage.service_template.put(blueprint) - deployment = models.create_service(blueprint) - model_storage.service.put(deployment) - - ################################################################################# - # Creating a simple deployment with node -> node as a graph - - dependency_node = models.create_dependency_node_template(deployment) - model_storage.node_template.put(dependency_node) - storage_dependency_node = model_storage.node_template.get(dependency_node.id) - - dependency_node_instance = models.create_dependency_node(storage_dependency_node, - deployment) - model_storage.node.put(dependency_node_instance) - storage_dependency_node_instance = model_storage.node.get(dependency_node_instance.id) - - req_template, cap_template = models.create_requirement(storage_dependency_node) - model_storage.requirement_template.put(req_template) - model_storage.capability_template.put(cap_template) - - dependent_node = models.create_dependent_node_template(deployment, req_template, cap_template) - model_storage.node_template.put(dependent_node) - storage_dependent_node = model_storage.node_template.get(dependent_node.id) - - dependent_node_instance = models.create_dependent_node(storage_dependent_node, deployment) - model_storage.node.put(dependent_node_instance) - storage_dependent_node_instance = model_storage.node.get(dependent_node_instance.id) - - relationship_instance = models.create_relationship( - target_instance=storage_dependency_node_instance, - source_instance=storage_dependent_node_instance - ) - model_storage.relationship.put(relationship_instance) + service_template = models.create_service_template() + + service = models.create_service(service_template) + + # Creating a simple service with node -> node as a graph + + dependency_node_template = models.create_dependency_node_template(service_template) + dependent_node_template = models.create_dependent_node_template(service_template, + dependency_node_template) + + dependency_node = models.create_dependency_node(dependency_node_template, service) + dependent_node = models.create_dependent_node(dependent_node_template, service) + + dependent_node.outbound_relationships.append(models.create_relationship( + source=dependent_node, + target=dependency_node + )) + + model_storage.service_template.put(service_template) + model_storage.service.put(service) - return deployment.id + return service.id http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/24ad8ba1/tests/orchestrator/context/test_operation.py ---------------------------------------------------------------------- diff --git a/tests/orchestrator/context/test_operation.py b/tests/orchestrator/context/test_operation.py index 3f39979..b460b9f 100644 --- a/tests/orchestrator/context/test_operation.py +++ b/tests/orchestrator/context/test_operation.py @@ -58,21 +58,23 @@ def executor(): def test_node_operation_task_execution(ctx, executor): operation_name = 'aria.interfaces.lifecycle.create' - node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_INSTANCE_NAME) - interface = mock.models.get_interface( + node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME) + interface = mock.models.create_interface( + node.service, operation_name, operation_kwargs=dict(implementation=op_path(my_operation, module_path=__name__)) ) - node.interfaces = [interface] + node.interfaces[interface.name] = interface ctx.model.node.update(node) inputs = {'putput': True} @workflow def basic_workflow(graph, **_): graph.add_tasks( - api.task.OperationTask.node( - name=operation_name, - instance=node, + api.task.OperationTask.for_node( + interface_name='aria.interfaces.lifecycle', + operation_name='create', + node=node, inputs=inputs ) ) @@ -100,24 +102,24 @@ def test_relationship_operation_task_execution(ctx, executor): operation_name = 'aria.interfaces.relationship_lifecycle.post_configure' relationship = ctx.model.relationship.list()[0] - interface = mock.models.get_interface( + interface = mock.models.create_interface( + relationship.source_node.service, operation_name=operation_name, operation_kwargs=dict(implementation=op_path(my_operation, module_path=__name__)), - edge='source' ) - relationship.interfaces = [interface] + relationship.interfaces[interface.name] = interface ctx.model.relationship.update(relationship) inputs = {'putput': True} @workflow def basic_workflow(graph, **_): graph.add_tasks( - api.task.OperationTask.relationship( - instance=relationship, - name=operation_name, - inputs=inputs, - edge='source' + api.task.OperationTask.for_relationship( + relationship=relationship, + interface_name='aria.interfaces.relationship_lifecycle', + operation_name='post_configure', + inputs=inputs ) ) @@ -137,9 +139,9 @@ def test_relationship_operation_task_execution(ctx, executor): # Context based attributes (sugaring) dependency_node_template = ctx.model.node_template.get_by_name(mock.models.DEPENDENCY_NODE_NAME) - dependency_node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_INSTANCE_NAME) + dependency_node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME) dependent_node_template = ctx.model.node_template.get_by_name(mock.models.DEPENDENT_NODE_NAME) - dependent_node = ctx.model.node.get_by_name(mock.models.DEPENDENT_NODE_INSTANCE_NAME) + dependent_node = ctx.model.node.get_by_name(mock.models.DEPENDENT_NODE_NAME) assert operation_context.target_node_template == dependency_node_template assert operation_context.target_node == dependency_node @@ -161,9 +163,9 @@ def test_invalid_task_operation_id(ctx, executor): assert other_node.id == 1 assert node.id == 2 - interface = mock.models.get_interface( + interface = mock.models.create_interface( operation_name=operation_name, - operation_kwargs=dict(implementation=op_path(get_node_instance_id, module_path=__name__)) + operation_kwargs=dict(implementation=op_path(get_node_id, module_path=__name__)) ) node.interfaces = [interface] ctx.model.node.update(node) @@ -171,21 +173,21 @@ def test_invalid_task_operation_id(ctx, executor): @workflow def basic_workflow(graph, **_): graph.add_tasks( - api.task.OperationTask.node(name=operation_name, instance=node) + api.task.OperationTask.for_node(name=operation_name, node=node) ) execute(workflow_func=basic_workflow, workflow_context=ctx, executor=executor) - op_node_instance_id = global_test_holder[op_name(node, operation_name)] - assert op_node_instance_id == node.id - assert op_node_instance_id != other_node.id + op_node_id = global_test_holder[op_name(node, operation_name)] + assert op_node_id == node.id + assert op_node_id != other_node.id def test_plugin_workdir(ctx, executor, tmpdir): op = 'test.op' plugin_name = 'mock_plugin' - node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_INSTANCE_NAME) - node.interfaces = [mock.models.get_interface( + node = ctx.model.node.get_by_name(mock.models.DEPENDENCY_NODE_NAME) + node.interfaces = [mock.models.create_interface( op, operation_kwargs=dict( implementation='{0}.{1}'.format(__name__, _test_plugin_workdir.__name__), @@ -200,8 +202,8 @@ def test_plugin_workdir(ctx, executor, tmpdir): @workflow def basic_workflow(graph, **_): - graph.add_tasks(api.task.OperationTask.node( - name=op, instance=node, inputs=inputs)) + graph.add_tasks(api.task.OperationTask.for_node( + name=op, node=node, inputs=inputs)) execute(workflow_func=basic_workflow, workflow_context=ctx, executor=executor) expected_file = tmpdir.join('workdir', 'plugins', str(ctx.service_instance.id), @@ -216,7 +218,7 @@ def my_operation(ctx, **_): @operation -def get_node_instance_id(ctx, **_): +def get_node_id(ctx, **_): global_test_holder[ctx.name] = ctx.node.id http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/24ad8ba1/tests/orchestrator/test_runner.py ---------------------------------------------------------------------- diff --git a/tests/orchestrator/test_runner.py b/tests/orchestrator/test_runner.py index 2e509b5..74e98ad 100644 --- a/tests/orchestrator/test_runner.py +++ b/tests/orchestrator/test_runner.py @@ -50,7 +50,8 @@ def test_runner_tasks(): for node in ctx.model.node: graph.add_tasks( OperationTask.for_node(node=node, - name='tosca.interfaces.node.lifecycle.Standard.create')) + interface_name='Standard', + operation_name='create')) _test_runner(workflow_fn) @@ -69,5 +70,5 @@ def _test_runner(workflow_fn): workflow_fn=workflow_fn, inputs={}, initialize_model_storage_fn=_initialize_model_storage_fn, - service_instance_id=1) + service_id_fn=lambda: 1) runner.run()
