# Licensed to the Apache Software Foundation (ASF) under one or more # contributor license agreements. See the NOTICE file distributed with # this work for additional information regarding copyright ownership. # The ASF licenses this file to You under the Apache License, Version 2.0 # (the "License"); you may not use this file except in compliance with # the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. from ... parser.modeling import context from ... modeling import models, functions from ... utils import formatting from .. import execution_plugin from .. import decorators from . import common class Artifact(common.InstanceHandlerBase): def coerce(self, **kwargs): self._topology.coerce(self._model.properties, **kwargs) def validate(self, **kwargs): self._topology.validate(self._model.properties, **kwargs) def dump(self, out_stream): with out_stream.indent(): out_stream.write(out_stream.node_style(self._model.name)) out_stream.write(out_stream.meta_style(self._model.description)) with out_stream.indent(): out_stream.write('Artifact type: {0}'.format(out_stream.type_style( self._model.type.name))) out_stream.write('Source path: {0}'.format( out_stream.literal_style(self._model.source_path))) if self._model.target_path is not None: out_stream.write('Target path: {0}'.format( out_stream.literal_style(self._model.target_path))) if self._model.repository_url is not None: out_stream.write('Repository URL: {0}'.format( out_stream.literal_style(self._model.repository_url))) if self._model.repository_credential: out_stream.write('Repository credential: {0}'.format( out_stream.literal_style(self._model.repository_credential))) self._topology.dump(self._model.properties, out_stream, title='Properties') class Capability(common.InstanceHandlerBase): def coerce(self, **kwargs): self._topology.coerce(self._model.properties, **kwargs) def validate(self, **kwargs): self._topology.validate(self._model.properties, **kwargs) def dump(self, out_stream): out_stream.write(out_stream.node_style(self._model.name)) with out_stream.indent(): out_stream.write('Type: {0}'.format(out_stream.type_style(self._model.type.name))) out_stream.write('Occurrences: {0:d} ({1:d}{2})'.format( self._model.occurrences, self._model.min_occurrences or 0, ' to {0:d}'.format(self._model.max_occurrences) if self._model.max_occurrences is not None else ' or more')) self._topology.dump(self._model.properties, out_stream, title='Properties') class Group(common.ActorHandlerBase): def coerce(self, **kwargs): self._coerce(self._model.properties, self._model.interfaces, **kwargs) def validate(self, **kwargs): self._validate(self._model.properties, self._model.interfaces, **kwargs) def dump(self, out_stream): out_stream.write('Group: {0}'.format(out_stream.node_style(self._model.name))) with out_stream.indent(): out_stream.write('Type: {0}'.format(out_stream.type_style(self._model.type.name))) self._topology.dump(self._model.properties, out_stream, title='Properties') self._topology.dump(self._model.interfaces, out_stream, title='Interfaces') if self._model.nodes: out_stream.write('Member nodes:') with out_stream.indent(): for node in self._model.nodes: out_stream.write(out_stream.node_style(node.name)) def configure_operations(self): for interface in self._model.interfaces.values(): self._topology.configure_operations(interface) class Interface(common.ActorHandlerBase): def coerce(self, **kwargs): self._coerce(self._model.inputs, self._model.operations, **kwargs) def validate(self, **kwargs): self._validate(self._model.inputs, self._model.operations, **kwargs) def dump(self, out_stream): out_stream.write(out_stream.node_style(self._model.name)) if self._model.description: out_stream.write(out_stream.meta_style(self._model.description)) with out_stream.indent(): out_stream.write('Interface type: {0}'.format( out_stream.type_style(self._model.type.name))) self._topology.dump(self._model.inputs, out_stream, title='Inputs') self._topology.dump(self._model.operations, out_stream, title='Operations') def configure_operations(self): for operation in self._model.operations.values(): self._topology.configure_operations(operation) class Node(common.ActorHandlerBase): def coerce(self, **kwargs): self._coerce(self._model.properties, self._model.attributes, self._model.interfaces, self._model.artifacts, self._model.capabilities, self._model.outbound_relationships, **kwargs) def validate(self, **kwargs): if len(self._model.name) > context.ID_MAX_LENGTH: self._topology.report( '"{0}" has an ID longer than the limit of {1:d} characters: {2:d}'.format( self._model.name, context.ID_MAX_LENGTH, len(self._model.name)), level=self._topology.Issue.BETWEEN_INSTANCES) self._validate(self._model.properties, self._model.attributes, self._model.interfaces, self._model.artifacts, self._model.capabilities, self._model.outbound_relationships) def dump(self, out_stream): out_stream.write('Node: {0}'.format(out_stream.node_style(self._model.name))) with out_stream.indent(): out_stream.write('Type: {0}'.format(out_stream.type_style(self._model.type.name))) out_stream.write('Template: {0}'.format( out_stream.node_style(self._model.node_template.name))) self._topology.dump(self._model.properties, out_stream, title='Properties') self._topology.dump(self._model.attributes, out_stream, title='Attributes') self._topology.dump(self._model.interfaces, out_stream, title='Interfaces') self._topology.dump(self._model.artifacts, out_stream, title='Artifacts') self._topology.dump(self._model.capabilities, out_stream, title='Capabilities') self._topology.dump(self._model.outbound_relationships, out_stream, title='Relationships') def configure_operations(self): for interface in self._model.interfaces.values(): self._topology.configure_operations(interface) for relationship in self._model.outbound_relationships: self._topology.configure_operations(relationship) def validate_capabilities(self): satisfied = False for capability in self._model.capabilities.itervalues(): if not capability.has_enough_relationships: self._topology.report( 'capability "{0}" of node "{1}" requires at least {2:d} ' 'relationships but has {3:d}'.format(capability.name, self._model.name, capability.min_occurrences, capability.occurrences), level=self._topology.Issue.BETWEEN_INSTANCES) satisfied = False return satisfied def satisfy_requirements(self): satisfied = True for requirement_template in self._model.node_template.requirement_templates: # Since we try and satisfy requirements, which are node template bound, and use that # information in the creation of the relationship, Some requirements may have been # satisfied by a previous run on that node template. # The entire mechanism of satisfying requirements needs to be refactored. if any(rel.requirement_template == requirement_template for rel in self._model.outbound_relationships): continue # Find target template target_node_template, target_node_capability = self._find_target(requirement_template) if target_node_template is not None: satisfied = self._satisfy_capability( target_node_capability, target_node_template, requirement_template) else: self._topology.report('requirement "{0}" of node "{1}" has no target node template'. format(requirement_template.name, self._model.name), level=self._topology.Issue.BETWEEN_INSTANCES) satisfied = False return satisfied def _satisfy_capability(self, target_node_capability, target_node_template, requirement_template): # Find target nodes target_nodes = target_node_template.nodes 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: a_target_capability = node.capabilities.get(target_node_capability.name) if a_target_capability.relate(): target_node = node target_capability = a_target_capability 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_model = self._topology.instantiate( requirement_template.relationship_template) else: relationship_model = models.Relationship() relationship_model.name = requirement_template.name relationship_model.requirement_template = requirement_template relationship_model.target_node = target_node relationship_model.target_capability = target_capability self._model.outbound_relationships.append(relationship_model) return True else: self._topology.report( 'requirement "{0}" of node "{1}" targets node ' 'template "{2}" but its instantiated nodes do not ' 'have enough capacity'.format( requirement_template.name, self._model.name, target_node_template.name), level=self._topology.Issue.BETWEEN_INSTANCES) return False else: self._topology.report( 'requirement "{0}" of node "{1}" targets node template ' '"{2}" but it has no instantiated nodes'.format( requirement_template.name, self._model.name, target_node_template.name), level=self._topology.Issue.BETWEEN_INSTANCES) return False def _find_target(self, requirement_template): # We might already have a specific node template from the requirement template, so # we'll just verify it if requirement_template.target_node_template is not None: if not self._model.node_template.is_target_node_template_valid( requirement_template.target_node_template): self._topology.report( 'requirement "{0}" of node template "{1}" is for node ' 'template "{2}" but it does not match constraints'.format( requirement_template.name, requirement_template.target_node_template.name, self._model.node_template.name), level=self._topology.Issue.BETWEEN_TYPES) if (requirement_template.target_capability_type is not None or requirement_template.target_capability_name is not None): target_node_capability = self._get_capability(requirement_template) if target_node_capability is None: return None, None else: target_node_capability = None return requirement_template.target_node_template, target_node_capability # Find first node that matches the type elif requirement_template.target_node_type is not None: for target_node_template in \ self._model.node_template.service_template.node_templates.itervalues(): if requirement_template.target_node_type.get_descendant( target_node_template.type.name) is None: continue if not self._model.node_template.is_target_node_template_valid( target_node_template): continue target_node_capability = self._get_capability(requirement_template, target_node_template) if target_node_capability is None: continue return target_node_template, target_node_capability # Find the first node which has a capability of the required type elif requirement_template.target_capability_type is not None: for target_node_template in \ self._model.node_template.service_template.node_templates.itervalues(): target_node_capability = \ self._get_capability(requirement_template, target_node_template) if target_node_capability: return target_node_template, target_node_capability return None, None def _get_capability(self, requirement_template, target_node_template=None): target_node_template = target_node_template or requirement_template.target_node_template for capability_template in target_node_template.capability_templates.values(): if self._satisfies_requirement( capability_template, requirement_template, target_node_template): return capability_template return None def _satisfies_requirement( self, capability_template, requirement_template, target_node_template): # Do we match the required capability type? if (requirement_template.target_capability_type and requirement_template.target_capability_type.get_descendant( capability_template.type.name) is None): return False # Are we in valid_source_node_types? if capability_template.valid_source_node_types: for valid_source_node_type in capability_template.valid_source_node_types: if valid_source_node_type.get_descendant( self._model.node_template.type.name) is None: return False # Apply requirement constraints if requirement_template.target_node_template_constraints: for node_template_constraint in requirement_template.target_node_template_constraints: if not node_template_constraint.matches( self._model.node_template, target_node_template): return False return True class Operation(common.ActorHandlerBase): def coerce(self, **kwargs): self._coerce(self._model.inputs, self._model.configurations, self._model.arguments, **kwargs) def validate(self, **kwargs): self._validate(self._model.inputs, self._model.configurations, self._model.arguments, **kwargs) def dump(self, out_stream): out_stream.write(out_stream.node_style(self._model.name)) if self._model.description: out_stream.write(out_stream.meta_style(self._model.description)) with out_stream.indent(): if self._model.implementation is not None: out_stream.write('Implementation: {0}'.format( out_stream.literal_style(self._model.implementation))) if self._model.dependencies: out_stream.write( 'Dependencies: {0}'.format(', '.join((str(out_stream.literal_style(v)) for v in self._model.dependencies)))) self._topology.dump(self._model.inputs, out_stream, title='Inputs') if self._model.executor is not None: out_stream.write('Executor: {0}'.format(out_stream.literal_style( self._model.executor))) if self._model.max_attempts is not None: out_stream.write('Max attempts: {0}'.format(out_stream.literal_style( self._model.max_attempts))) if self._model.retry_interval is not None: out_stream.write('Retry interval: {0}'.format( out_stream.literal_style(self._model.retry_interval))) if self._model.plugin is not None: out_stream.write('Plugin: {0}'.format( out_stream.literal_style(self._model.plugin.name))) self._topology.dump(self._model.configurations, out_stream, title='Configuration') if self._model.function is not None: out_stream.write('Function: {0}'.format(out_stream.literal_style( self._model.function))) self._topology.dump(self._model.arguments, out_stream, title='Arguments') def configure_operations(self): if self._model.implementation is None and self._model.function is None: return if (self._model.interface is not None and self._model.plugin is None and self._model.function is None): # ("interface" is None for workflow operations, which do not currently use "plugin") # The default (None) plugin is the execution plugin execution_plugin.instantiation.configure_operation(self._model, self._topology) else: # In the future plugins may be able to add their own "configure_operation" hook that # can validate the configuration and otherwise create specially derived arguments. For # now, we just send all configuration parameters as arguments without validation. for key, conf in self._model.configurations.items(): self._model.arguments[key] = self._topology.instantiate(conf.as_argument()) if self._model.interface is not None: # Send all interface inputs as extra arguments # ("interface" is None for workflow operations) # Note that they will override existing arguments of the same names for key, input in self._model.interface.inputs.items(): self._model.arguments[key] = self._topology.instantiate(input.as_argument()) # Send all inputs as extra arguments # Note that they will override existing arguments of the same names for key, input in self._model.inputs.items(): self._model.arguments[key] = self._topology.instantiate(input.as_argument()) # Check for reserved arguments used_reserved_names = set(decorators.OPERATION_DECORATOR_RESERVED_ARGUMENTS).intersection( self._model.arguments.keys()) if used_reserved_names: self._topology.report( 'using reserved arguments in operation "{0}": {1}'.format( self._model.name, formatting.string_list_as_string(used_reserved_names)), level=self._topology.Issue.EXTERNAL) class Policy(common.InstanceHandlerBase): def coerce(self, **kwargs): self._topology.coerce(self._model.properties, **kwargs) def validate(self, **kwargs): self._topology.validate(self._model.properties, **kwargs) def dump(self, out_stream): out_stream.write('Policy: {0}'.format(out_stream.node_style(self._model.name))) with out_stream.indent(): out_stream.write('Type: {0}'.format(out_stream.type_style(self._model.type.name))) self._topology.dump(self._model.properties, out_stream, title='Properties') if self._model.nodes: out_stream.write('Target nodes:') with out_stream.indent(): for node in self._model.nodes: out_stream.write(out_stream.node_style(node.name)) if self._model.groups: out_stream.write('Target groups:') with out_stream.indent(): for group in self._model.groups: out_stream.write(out_stream.node_style(group.name)) class Relationship(common.ActorHandlerBase): def coerce(self, **kwargs): self._coerce(self._model.properties, self._model.interfaces, **kwargs) def validate(self, **kwargs): self._validate(self._model.properties, self._model.interfaces, **kwargs) def dump(self, out_stream): if self._model.name: out_stream.write('{0} ->'.format(out_stream.node_style(self._model.name))) else: out_stream.write('->') with out_stream.indent(): out_stream.write('Node: {0}'.format(out_stream.node_style( self._model.target_node.name))) if self._model.target_capability: out_stream.write('Capability: {0}'.format(out_stream.node_style( self._model.target_capability.name))) if self._model.type is not None: out_stream.write('Relationship type: {0}'.format( out_stream.type_style(self._model.type.name))) if (self._model.relationship_template is not None and self._model.relationship_template.name): out_stream.write('Relationship template: {0}'.format( out_stream.node_style(self._model.relationship_template.name))) self._topology.dump(self._model.properties, out_stream, title='Properties') self._topology.dump(self._model.interfaces, out_stream, title='Interfaces') def configure_operations(self): for interface in self._model.interfaces.values(): self._topology.configure_operations(interface) class Service(common.ActorHandlerBase): def coerce(self, **kwargs): self._coerce(self._model.meta_data, self._model.nodes, self._model.groups, self._model.policies, self._model.substitution, self._model.inputs, self._model.outputs, self._model.workflows, **kwargs) def validate(self, **kwargs): self._validate(self._model.meta_data, self._model.nodes, self._model.groups, self._model.policies, self._model.substitution, self._model.inputs, self._model.outputs, self._model.workflows, **kwargs) def dump(self, out_stream): if self._model.description is not None: out_stream.write(out_stream.meta_style(self._model.description)) self._topology.dump(self._model.meta_data, out_stream, title='Metadata') self._topology.dump(self._model.nodes, out_stream) self._topology.dump(self._model.groups, out_stream) self._topology.dump(self._model.policies, out_stream) self._topology.dump(self._model.substitution, out_stream) self._topology.dump(self._model.inputs, out_stream, title='Inputs') self._topology.dump(self._model.outputs, out_stream, title='Outputs') self._topology.dump(self._model.workflows, out_stream, title='Workflows') def configure_operations(self): for node in self._model.nodes.itervalues(): self._topology.configure_operations(node) for group in self._model.groups.itervalues(): self._topology.configure_operations(group) for operation in self._model.workflows.itervalues(): self._topology.configure_operations(operation) def validate_capabilities(self): satisfied = True for node in self._model.nodes.values(): if not self._topology.validate_capabilities(node): satisfied = False return satisfied def satisfy_requirements(self): return all(self._topology.satisfy_requirements(node) for node in self._model.nodes.values()) class Substitution(common.InstanceHandlerBase): def coerce(self, **kwargs): self._topology.coerce(self._model.mappings, **kwargs) def validate(self, **kwargs): self._topology.validate(self._model.mappings, **kwargs) def dump(self, out_stream): out_stream.write('Substitution:') with out_stream.indent(): out_stream.write('Node type: {0}'.format(out_stream.type_style( self._model.node_type.name))) self._topology.dump(self._model.mappings, out_stream, title='Mappings') class SubstitutionMapping(common.InstanceHandlerBase): def coerce(self, **kwargs): pass def validate(self, **_): if (self._model.capability is None) and (self._model.requirement_template is None): self._topology.report( 'mapping "{0}" refers to neither capability nor a requirement' ' in node: {1}'.format( self._model.name, formatting.safe_repr(self._model.node_style.name)), level=self._topology.Issue.BETWEEN_TYPES) def dump(self, out_stream): if self._model.capability is not None: out_stream.write('{0} -> {1}.{2}'.format( out_stream.node_style(self._model.name), out_stream.node_style(self._model.capability.node.name), out_stream.node_style(self._model.capability.name))) else: out_stream.write('{0} -> {1}.{2}'.format( out_stream.node_style(self._model.name), out_stream.node_style(self._model.node.name), out_stream.node_style(self._model.requirement_template.name))) class Metadata(common.InstanceHandlerBase): def dump(self, out_stream): out_stream.write('{0}: {1}'.format( out_stream.property_style(self._model.name), out_stream.literal_style(self._model.value))) def coerce(self, **_): pass def instantiate(self, instance_cls): return instance_cls(name=self._model.name, value=self._model.value) def validate(self): pass class _Parameter(common.InstanceHandlerBase): def dump(self, out_stream): if self._model.type_name is not None: out_stream.write('{0}: {1} ({2})'.format( out_stream.property_style(self._model.name), out_stream.literal_style(formatting.as_raw(self._model.value)), out_stream.type_style(self._model.type_name))) else: out_stream.write('{0}: {1}'.format( out_stream.property_style(self._model.name), out_stream.literal_style(formatting.as_raw(self._model.value)))) if self._model.description: out_stream.write(out_stream.meta_style(self._model.description)) def instantiate(self, instance_cls, **kwargs): return instance_cls( name=self._model.name, # pylint: disable=unexpected-keyword-arg type_name=self._model.type_name, _value=self._model._value, description=self._model.description ) def validate(self): pass def coerce(self, report_issues): # pylint: disable=arguments-differ value = self._model._value if value is not None: evaluation = functions.evaluate(value, self._model, report_issues) if (evaluation is not None) and evaluation.final: # A final evaluation can safely replace the existing value self._model._value = evaluation.value class Attribute(_Parameter): pass class Input(_Parameter): pass class Output(_Parameter): pass class Argument(_Parameter): pass class Property(_Parameter): pass class Configuration(_Parameter): pass class Type(common.InstanceHandlerBase): def coerce(self, **_): pass def dump(self, out_stream): if self._model.name: out_stream.write(out_stream.type_style(self._model.name)) with out_stream.indent(): for child in self._model.children: self._topology.dump(child, out_stream) def validate(self, **kwargs): pass