blob: 6c305c38bfd097c2c4d45318a36226f4cdaa139a [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.
"""
Creates ARIA service template models based on the TOSCA presentation.
Relies on many helper methods in the presentation classes.
"""
#pylint: disable=unsubscriptable-object
import os
import re
from datetime import datetime
from aria.parser.validation import Issue
from aria.utils.formatting import (string_list_as_string, safe_repr)
from aria.utils.collections import (StrictDict, OrderedDict)
from aria.utils.yaml import yaml
from aria.orchestrator import WORKFLOW_DECORATOR_RESERVED_ARGUMENTS
from aria.modeling.models import (Type, ServiceTemplate, NodeTemplate,
RequirementTemplate, RelationshipTemplate, CapabilityTemplate,
GroupTemplate, PolicyTemplate, SubstitutionTemplate,
SubstitutionTemplateMapping, InterfaceTemplate, OperationTemplate,
ArtifactTemplate, Metadata, Input, Output, Property,
Attribute, Configuration, PluginSpecification)
from .parameters import coerce_parameter_value
from .constraints import (Equal, GreaterThan, GreaterOrEqual, LessThan, LessOrEqual, InRange,
ValidValues, Length, MinLength, MaxLength, Pattern)
from ..data_types import coerce_value
# These match the first un-escaped ">"
# See: http://stackoverflow.com/a/11819111/849021
IMPLEMENTATION_PREFIX_REGEX = re.compile(r'(?<!\\)(?:\\\\)*>')
def create_service_template_model(context): # pylint: disable=too-many-locals,too-many-branches
model = ServiceTemplate(created_at=datetime.now(),
main_file_name=os.path.basename(str(context.presentation.location)))
model.description = context.presentation.get('service_template', 'description', 'value')
# Metadata
metadata = context.presentation.get('service_template', 'metadata')
if metadata is not None:
create_metadata_models(context, model, metadata)
# Types
model.node_types = Type(variant='node')
create_types(context,
model.node_types,
context.presentation.get('service_template', 'node_types'))
model.group_types = Type(variant='group')
create_types(context,
model.group_types,
context.presentation.get('service_template', 'group_types'))
model.policy_types = Type(variant='policy')
create_types(context,
model.policy_types,
context.presentation.get('service_template', 'policy_types'))
model.relationship_types = Type(variant='relationship')
create_types(context,
model.relationship_types,
context.presentation.get('service_template', 'relationship_types'))
model.capability_types = Type(variant='capability')
create_types(context,
model.capability_types,
context.presentation.get('service_template', 'capability_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')
if topology_template is not None:
model.inputs.update(
create_input_models_from_values(topology_template._get_input_values(context)))
model.outputs.update(
create_output_models_from_values(topology_template._get_output_values(context)))
# Plugin specifications
policies = context.presentation.get('service_template', 'topology_template', 'policies')
if policies:
for policy in policies.itervalues():
role = model.policy_types.get_descendant(policy.type).role
if role == 'plugin':
plugin_specification = create_plugin_specification_model(context, policy)
model.plugin_specifications[plugin_specification.name] = plugin_specification
elif role == 'workflow':
operation_template = create_workflow_operation_template_model(context,
model, policy)
model.workflow_templates[operation_template.name] = operation_template
# Node templates
node_templates = context.presentation.get('service_template', 'topology_template',
'node_templates')
if node_templates:
for node_template in node_templates.itervalues():
node_template_model = create_node_template_model(context, model, node_template)
model.node_templates[node_template_model.name] = node_template_model
for node_template in node_templates.itervalues():
fix_node_template_model(context, model, node_template)
# Group templates
groups = context.presentation.get('service_template', 'topology_template', 'groups')
if groups:
for group in groups.itervalues():
group_template_model = create_group_template_model(context, model, group)
model.group_templates[group_template_model.name] = group_template_model
# Policy templates
policies = context.presentation.get('service_template', 'topology_template', 'policies')
if policies:
for policy in policies.itervalues():
policy_template_model = create_policy_template_model(context, model, policy)
model.policy_templates[policy_template_model.name] = policy_template_model
# Substitution template
substitution_mappings = context.presentation.get('service_template', 'topology_template',
'substitution_mappings')
if substitution_mappings:
model.substitution_template = create_substitution_template_model(context, model,
substitution_mappings)
return model
def create_metadata_models(context, service_template, metadata):
service_template.meta_data['template_name'] = Metadata(name='template_name',
value=metadata.template_name)
service_template.meta_data['template_author'] = Metadata(name='template_author',
value=metadata.template_author)
service_template.meta_data['template_version'] = Metadata(name='template_version',
value=metadata.template_version)
custom = metadata.custom
if custom:
for name, value in custom.iteritems():
service_template.meta_data[name] = Metadata(name=name,
value=value)
def create_node_template_model(context, service_template, node_template):
node_type = node_template._get_type(context)
node_type = service_template.node_types.get_descendant(node_type._name)
model = NodeTemplate(name=node_template._name, type=node_type)
if node_template.description:
model.description = node_template.description.value
if node_template.directives:
model.directives = node_template.directives
model.properties.update(create_property_models_from_values(
template_properties=node_template._get_property_values(context)))
model.attributes.update(create_attribute_models_from_values(
template_attributes=node_template._get_attribute_default_values(context)))
create_interface_template_models(context, service_template, model.interface_templates,
node_template._get_interfaces(context))
artifacts = node_template._get_artifacts(context)
if artifacts:
for artifact_name, artifact in artifacts.iteritems():
model.artifact_templates[artifact_name] = \
create_artifact_template_model(context, service_template, artifact)
capabilities = node_template._get_capabilities(context)
if capabilities:
for capability_name, capability in capabilities.iteritems():
model.capability_templates[capability_name] = \
create_capability_template_model(context, service_template, capability)
if node_template.node_filter:
model.target_node_template_constraints = []
create_node_filter_constraints(context, node_template.node_filter,
model.target_node_template_constraints)
return model
def fix_node_template_model(context, service_template, node_template):
# Requirements have to be created after all node templates have been created, because
# requirements might reference another node template
model = service_template.node_templates[node_template._name]
requirements = node_template._get_requirements(context)
if requirements:
for _, requirement in requirements:
model.requirement_templates.append(create_requirement_template_model(context,
service_template,
requirement))
def create_group_template_model(context, service_template, group):
group_type = group._get_type(context)
group_type = service_template.group_types.get_descendant(group_type._name)
model = GroupTemplate(name=group._name,
type=group_type)
if group.description:
model.description = group.description.value
model.properties.update(create_property_models_from_values(group._get_property_values(context)))
create_interface_template_models(context, service_template, model.interface_templates,
group._get_interfaces(context))
members = group.members
if members:
for member in members:
node_template = service_template.node_templates[member]
assert node_template
model.node_templates.append(node_template)
return model
def create_policy_template_model(context, service_template, policy):
policy_type = policy._get_type(context)
policy_type = service_template.policy_types.get_descendant(policy_type._name)
model = PolicyTemplate(name=policy._name,
type=policy_type)
if policy.description:
model.description = policy.description.value
model.properties.update(
create_property_models_from_values(policy._get_property_values(context)))
node_templates, groups = policy._get_targets(context)
if node_templates:
for target in node_templates:
node_template = service_template.node_templates[target._name]
assert node_template
model.node_templates.append(node_template)
if groups:
for target in groups:
group_template = service_template.group_templates[target._name]
assert group_template
model.group_templates.append(group_template)
return model
def create_requirement_template_model(context, service_template, requirement):
model = {'name': requirement._name}
node, node_variant = requirement._get_node(context)
if node is not None:
if node_variant == 'node_type':
node_type = service_template.node_types.get_descendant(node._name)
model['target_node_type'] = node_type
else:
node_template = service_template.node_templates[node._name]
model['target_node_template'] = node_template
capability, capability_variant = requirement._get_capability(context)
if capability is not None:
if capability_variant == 'capability_type':
capability_type = \
service_template.capability_types.get_descendant(capability._name)
model['target_capability_type'] = capability_type
else:
model['target_capability_name'] = capability._name
model = RequirementTemplate(**model)
if requirement.node_filter:
model.target_node_template_constraints = []
create_node_filter_constraints(context, requirement.node_filter,
model.target_node_template_constraints)
relationship = requirement.relationship
if relationship is not None:
model.relationship_template = \
create_relationship_template_model(context, service_template, relationship)
model.relationship_template.name = requirement._name
return model
def create_relationship_template_model(context, service_template, relationship):
relationship_type, relationship_type_variant = relationship._get_type(context)
if relationship_type_variant == 'relationship_type':
relationship_type = service_template.relationship_types.get_descendant(
relationship_type._name)
model = RelationshipTemplate(type=relationship_type)
else:
relationship_template = relationship_type
relationship_type = relationship_template._get_type(context)
relationship_type = service_template.relationship_types.get_descendant(
relationship_type._name)
model = RelationshipTemplate(type=relationship_type)
if relationship_template.description:
model.description = relationship_template.description.value
create_parameter_models_from_assignments(model.properties,
relationship.properties,
model_cls=Property)
# TODO: does not exist in models, but should
#create_parameter_models_from_assignments(model.attributes,
# relationship.attributes,
# model_cls=Attribute)
create_interface_template_models(context, service_template, model.interface_templates,
relationship.interfaces)
return model
def create_capability_template_model(context, service_template, capability):
capability_type = capability._get_type(context)
capability_type = service_template.capability_types.get_descendant(capability_type._name)
model = CapabilityTemplate(name=capability._name,
type=capability_type)
capability_definition = capability._get_definition(context)
if capability_definition.description:
model.description = capability_definition.description.value
occurrences = capability_definition.occurrences
if occurrences is not None:
model.min_occurrences = occurrences.value[0]
if occurrences.value[1] != 'UNBOUNDED':
model.max_occurrences = occurrences.value[1]
valid_source_types = capability_definition.valid_source_types
if valid_source_types:
for valid_source_type in valid_source_types:
# TODO: handle shortcut type names
node_type = service_template.node_types.get_descendant(valid_source_type)
model.valid_source_node_types.append(node_type)
create_parameter_models_from_assignments(model.properties,
capability.properties,
model_cls=Property)
# TODO: does not exist in models, but should
#create_parameter_models_from_assignments(model.attributes,
# capability.attributes,
# model_cls=Attribute)
return model
def create_interface_template_model(context, service_template, interface):
interface_type = interface._get_type(context)
interface_type = service_template.interface_types.get_descendant(interface_type._name)
model = InterfaceTemplate(name=interface._name, type=interface_type)
if interface_type.description:
model.description = interface_type.description
create_parameter_models_from_assignments(model.inputs, interface.inputs, model_cls=Input)
operations = interface.operations
if operations:
for operation_name, operation in operations.iteritems():
model.operation_templates[operation_name] = \
create_operation_template_model(context, service_template, operation)
return model if model.operation_templates else None
def create_operation_template_model(context, service_template, operation):
model = OperationTemplate(name=operation._name)
if operation.description:
model.description = operation.description.value
implementation = operation.implementation
if implementation is not None:
primary = implementation.primary
extract_implementation_primary(context, service_template, operation, model, primary)
relationship_edge = operation._get_extensions(context).get('relationship_edge')
if relationship_edge is not None:
if relationship_edge == 'source':
model.relationship_edge = False
elif relationship_edge == 'target':
model.relationship_edge = True
dependencies = implementation.dependencies
configuration = OrderedDict()
if dependencies:
for dependency in dependencies:
key, value = split_prefix(dependency)
if key is not None:
# Special ARIA prefix: signifies configuration parameters
# Parse as YAML
try:
value = yaml.load(value, Loader=yaml.SafeLoader)
except yaml.parser.MarkedYAMLError as e:
context.validation.report(
u'YAML parser {0} in operation configuration: {1}'
.format(e.problem, value),
locator=implementation._locator,
level=Issue.FIELD)
continue
# Coerce to intrinsic functions, if there are any
value = coerce_parameter_value(context, implementation, None, value).value
# Support dot-notation nesting
set_nested(configuration, key.split('.'), value)
else:
if model.dependencies is None:
model.dependencies = []
model.dependencies.append(dependency)
# Convert configuration to Configuration models
for key, value in configuration.iteritems():
model.configurations[key] = Configuration.wrap(key, value,
description='Operation configuration.')
create_parameter_models_from_assignments(model.inputs, operation.inputs, model_cls=Input)
return model
def create_artifact_template_model(context, service_template, artifact):
artifact_type = artifact._get_type(context)
artifact_type = service_template.artifact_types.get_descendant(artifact_type._name)
model = ArtifactTemplate(name=artifact._name,
type=artifact_type,
source_path=artifact.file)
if artifact.description:
model.description = artifact.description.value
model.target_path = artifact.deploy_path
repository = artifact._get_repository(context)
if repository is not None:
model.repository_url = repository.url
credential = repository._get_credential(context)
if credential:
model.repository_credential = {}
for k, v in credential.iteritems():
model.repository_credential[k] = v
model.properties.update(
create_property_models_from_values(artifact._get_property_values(context)))
return model
def create_substitution_template_model(context, service_template, substitution_mappings):
node_type = service_template.node_types.get_descendant(substitution_mappings.node_type)
model = SubstitutionTemplate(node_type=node_type)
capabilities = substitution_mappings.capabilities
if capabilities:
for mapped_capability_name, capability in capabilities.iteritems():
name = 'capability.' + mapped_capability_name
node_template_model = service_template.node_templates[capability.node_template]
capability_template_model = \
node_template_model.capability_templates[capability.capability]
model.mappings[name] = \
SubstitutionTemplateMapping(name=name,
capability_template=capability_template_model)
requirements = substitution_mappings.requirements
if requirements:
for mapped_requirement_name, requirement in requirements.iteritems():
name = 'requirement.' + mapped_requirement_name
node_template_model = service_template.node_templates[requirement.node_template]
requirement_template_model = None
for a_model in node_template_model.requirement_templates:
if a_model.name == requirement.requirement:
requirement_template_model = a_model
break
model.mappings[name] = \
SubstitutionTemplateMapping(name=name,
requirement_template=requirement_template_model)
return model
def create_plugin_specification_model(context, policy):
properties = policy.properties
def get(name, default=None):
prop = properties.get(name)
return prop.value if prop is not None else default
model = PluginSpecification(name=policy._name,
version=get('version'),
enabled=get('enabled', True))
return model
def create_workflow_operation_template_model(context, service_template, policy):
model = OperationTemplate(name=policy._name)
# since we use backpopulates, these fields are populated upon commit, we get a weird(temporary)
# behavior where in previous code service_template.workflow_templates is a dict which has None
# as key for the value of model.
service_template.workflow_templates[model.name] = model
if policy.description:
model.description = policy.description.value
properties = policy._get_property_values(context)
for prop_name, prop in properties.iteritems():
if prop_name == 'implementation':
model.function = prop.value
else:
input_model = create_parameter_model_from_value(prop, prop_name, model_cls=Input)
input_model.required = prop.required
model.inputs[prop_name] = input_model
used_reserved_names = WORKFLOW_DECORATOR_RESERVED_ARGUMENTS.intersection(model.inputs.keys())
if used_reserved_names:
context.validation.report(u'using reserved arguments in workflow policy "{0}": {1}'
.format(
policy._name,
string_list_as_string(used_reserved_names)),
locator=policy._locator,
level=Issue.EXTERNAL)
return model
#
# Utils
#
def create_types(context, root, types):
if types is None:
return
def added_all():
for name in types:
if root.get_descendant(name) is None:
return False
return True
while not added_all():
for name, the_type in types.iteritems():
if root.get_descendant(name) is None:
parent_type = the_type._get_parent(context)
model = Type(name=the_type._name,
role=the_type._get_extension('role'))
if the_type.description:
model.description = the_type.description.value
if parent_type is None:
model.parent = root
model.variant = root.variant
root.children.append(model)
else:
container = root.get_descendant(parent_type._name)
if container is not None:
model.parent = container
model.variant = container.variant
container.children.append(model)
def create_input_models_from_values(template_inputs):
model_inputs = {}
if template_inputs:
for template_input_name, template_input in template_inputs.iteritems():
model_input = create_parameter_model_from_value(template_input, template_input_name,
model_cls=Input)
model_input.required = template_input.required
model_inputs[model_input.name] = model_input
return model_inputs
def create_output_models_from_values(template_outputs):
model_outputs = {}
for template_output_name, template_output in template_outputs.iteritems():
model_outputs[template_output_name] = \
create_parameter_model_from_value(template_output,
template_output_name,
model_cls=Output)
return model_outputs
def create_property_models_from_values(template_properties):
model_properties = {}
for template_property_name, template_property in template_properties.iteritems():
model_properties[template_property_name] = \
create_parameter_model_from_value(template_property,
template_property_name,
model_cls=Property)
return model_properties
def create_attribute_models_from_values(template_attributes):
model_attributes = {}
for template_attribute_name, template_attribute in template_attributes.iteritems():
model_attributes[template_attribute_name] = \
create_parameter_model_from_value(template_attribute,
template_attribute_name,
model_cls=Attribute)
return model_attributes
def create_parameter_model_from_value(template_parameter, template_parameter_name, model_cls):
return model_cls(name=template_parameter_name,
type_name=template_parameter.type,
value=template_parameter.value,
description=template_parameter.description)
def create_parameter_models_from_assignments(properties, source_properties, model_cls):
if source_properties:
for property_name, prop in source_properties.iteritems():
properties[property_name] = model_cls(name=property_name, # pylint: disable=unexpected-keyword-arg
type_name=prop.value.type,
value=prop.value.value,
description=prop.value.description)
def create_interface_template_models(context, service_template, interfaces, source_interfaces):
if source_interfaces:
for interface_name, interface in source_interfaces.iteritems():
interface = create_interface_template_model(context, service_template, interface)
if interface is not None:
interfaces[interface_name] = interface
def create_node_filter_constraints(context, node_filter, target_node_template_constraints):
properties = node_filter.properties
if properties is not None:
for property_name, constraint_clause in properties:
constraint = create_constraint(context, node_filter, constraint_clause, property_name,
None)
if constraint is not None:
target_node_template_constraints.append(constraint)
capabilities = node_filter.capabilities
if capabilities is not None:
for capability_name, capability in capabilities:
properties = capability.properties
if properties is not None:
for property_name, constraint_clause in properties:
constraint = create_constraint(context, node_filter, constraint_clause,
property_name, capability_name)
if constraint is not None:
target_node_template_constraints.append(constraint)
def create_constraint(context, node_filter, constraint_clause, property_name, capability_name): # pylint: disable=too-many-return-statements
if (not isinstance(constraint_clause._raw, dict)) or (len(constraint_clause._raw) != 1):
context.validation.report(
u'node_filter constraint is not a dict with one key: {0}'
.format(safe_repr(constraint_clause._raw)),
locator=node_filter._locator,
level=Issue.FIELD)
return None
constraint_key = constraint_clause._raw.keys()[0]
value_type = constraint_clause._get_type(context)
def coerce_constraint(constraint, value_type=value_type):
if value_type is not None:
return coerce_value(context, node_filter, value_type, None, None, constraint,
constraint_key)
else:
return constraint
def coerce_constraints(constraints, value_type=value_type):
if value_type is not None:
return tuple(coerce_constraint(constraint, value_type) for constraint in constraints)
else:
return constraints
if constraint_key == 'equal':
return Equal(property_name, capability_name,
coerce_constraint(constraint_clause.equal))
elif constraint_key == 'greater_than':
return GreaterThan(property_name, capability_name,
coerce_constraint(constraint_clause.greater_than))
elif constraint_key == 'greater_or_equal':
return GreaterOrEqual(property_name, capability_name,
coerce_constraint(constraint_clause.greater_or_equal))
elif constraint_key == 'less_than':
return LessThan(property_name, capability_name,
coerce_constraint(constraint_clause.less_than))
elif constraint_key == 'less_or_equal':
return LessOrEqual(property_name, capability_name,
coerce_constraint(constraint_clause.less_or_equal))
elif constraint_key == 'in_range':
return InRange(property_name, capability_name,
coerce_constraints(constraint_clause.in_range))
elif constraint_key == 'valid_values':
return ValidValues(property_name, capability_name,
coerce_constraints(constraint_clause.valid_values))
elif constraint_key == 'length':
return Length(property_name, capability_name,
coerce_constraint(constraint_clause.length, int))
elif constraint_key == 'min_length':
return MinLength(property_name, capability_name,
coerce_constraint(constraint_clause.min_length, int))
elif constraint_key == 'max_length':
return MaxLength(property_name, capability_name,
coerce_constraint(constraint_clause.max_length, int))
elif constraint_key == 'pattern':
return Pattern(property_name, capability_name,
coerce_constraint(constraint_clause.pattern, unicode))
else:
context.validation.report(
u'unsupported node_filter constraint: {0}'.format(constraint_key),
locator=node_filter._locator,
level=Issue.FIELD)
return None
def split_prefix(string):
"""
Splits the prefix on the first non-escaped ">".
"""
split = IMPLEMENTATION_PREFIX_REGEX.split(string, 1) if string is not None else ()
if len(split) < 2:
return None, None
return split[0].strip(), split[1].strip()
def set_nested(the_dict, keys, value):
"""
If the ``keys`` list has just one item, puts the value in the the dict. If there are more items,
puts the value in a sub-dict, creating sub-dicts as necessary for each key.
For example, if ``the_dict`` is an empty dict, keys is ``['first', 'second', 'third']`` and
value is ``'value'``, then the_dict will be: ``{'first':{'second':{'third':'value'}}}``.
:param the_dict: Dict to change
:type the_dict: {}
:param keys: Keys
:type keys: [basestring]
:param value: Value
"""
key = keys.pop(0)
if len(keys) == 0:
the_dict[key] = value
else:
if key not in the_dict:
the_dict[key] = StrictDict(key_class=basestring)
set_nested(the_dict[key], keys, value)
def extract_implementation_primary(context, service_template, presentation, model, primary):
prefix, postfix = split_prefix(primary)
if prefix:
# Special ARIA prefix
model.plugin_specification = service_template.plugin_specifications.get(prefix)
model.function = postfix
if model.plugin_specification is None:
context.validation.report(
u'no policy for plugin "{0}" specified in operation implementation: {1}'
.format(prefix, primary),
locator=presentation._get_child_locator('properties', 'implementation'),
level=Issue.BETWEEN_TYPES)
else:
# Standard TOSCA artifact with default plugin
model.implementation = primary