blob: 51f26c63b09d5e51165c8edd4eebe5829b597069 [file] [log] [blame]
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership.
# The ASF licenses this file to You under the Apache License, Version 2.0
# (the "License"); you may not use this file except in compliance with
# the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
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