1 # Licensed to the Apache Software Foundation (ASF) under one or more
2 # contributor license agreements. See the NOTICE file distributed with
3 # this work for additional information regarding copyright ownership.
4 # The ASF licenses this file to You under the Apache License, Version 2.0
5 # (the "License"); you may not use this file except in compliance with
6 # the License. You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
17 Creates ARIA service template models based on the TOSCA presentation.
19 Relies on many helper methods in the presentation classes.
22 #pylint: disable=unsubscriptable-object
26 from types import FunctionType
27 from datetime import datetime
29 from ruamel import yaml
31 from aria.parser.validation import Issue
32 from aria.utils.formatting import string_list_as_string
33 from aria.utils.collections import (StrictDict, OrderedDict)
34 from aria.orchestrator import WORKFLOW_DECORATOR_RESERVED_ARGUMENTS
35 from aria.modeling.models import (Type, ServiceTemplate, NodeTemplate,
36 RequirementTemplate, RelationshipTemplate, CapabilityTemplate,
37 GroupTemplate, PolicyTemplate, SubstitutionTemplate,
38 SubstitutionTemplateMapping, InterfaceTemplate, OperationTemplate,
39 ArtifactTemplate, Metadata, Input, Output, Property,
40 Attribute, Configuration, PluginSpecification)
42 from .parameters import coerce_parameter_value
43 from .constraints import (Equal, GreaterThan, GreaterOrEqual, LessThan, LessOrEqual, InRange,
44 ValidValues, Length, MinLength, MaxLength, Pattern)
45 from ..data_types import coerce_value
48 # These match the first un-escaped ">"
49 # See: http://stackoverflow.com/a/11819111/849021
50 IMPLEMENTATION_PREFIX_REGEX = re.compile(r'(?<!\\)(?:\\\\)*>')
53 def create_service_template_model(context): # pylint: disable=too-many-locals,too-many-branches
54 model = ServiceTemplate(created_at=datetime.now(),
55 main_file_name=os.path.basename(str(context.presentation.location)))
57 model.description = context.presentation.get('service_template', 'description', 'value')
60 metadata = context.presentation.get('service_template', 'metadata')
61 if metadata is not None:
62 create_metadata_models(context, model, metadata)
65 model.node_types = Type(variant='node')
68 context.presentation.get('service_template', 'node_types'))
69 model.group_types = Type(variant='group')
72 context.presentation.get('service_template', 'group_types'))
73 model.policy_types = Type(variant='policy')
76 context.presentation.get('service_template', 'policy_types'))
77 model.relationship_types = Type(variant='relationship')
79 model.relationship_types,
80 context.presentation.get('service_template', 'relationship_types'))
81 model.capability_types = Type(variant='capability')
83 model.capability_types,
84 context.presentation.get('service_template', 'capability_types'))
85 model.interface_types = Type(variant='interface')
87 model.interface_types,
88 context.presentation.get('service_template', 'interface_types'))
89 model.artifact_types = Type(variant='artifact')
92 context.presentation.get('service_template', 'artifact_types'))
95 topology_template = context.presentation.get('service_template', 'topology_template')
96 if topology_template is not None:
98 create_input_models_from_values(topology_template._get_input_values(context)))
100 create_output_models_from_values(topology_template._get_output_values(context)))
102 # Plugin specifications
103 policies = context.presentation.get('service_template', 'topology_template', 'policies')
105 for policy in policies.itervalues():
106 role = model.policy_types.get_descendant(policy.type).role
108 plugin_specification = create_plugin_specification_model(context, policy)
109 model.plugin_specifications[plugin_specification.name] = plugin_specification
110 elif role == 'workflow':
111 operation_template = create_workflow_operation_template_model(context,
113 model.workflow_templates[operation_template.name] = operation_template
116 node_templates = context.presentation.get('service_template', 'topology_template',
119 for node_template in node_templates.itervalues():
120 node_template_model = create_node_template_model(context, model, node_template)
121 model.node_templates[node_template_model.name] = node_template_model
122 for node_template in node_templates.itervalues():
123 fix_node_template_model(context, model, node_template)
126 groups = context.presentation.get('service_template', 'topology_template', 'groups')
128 for group in groups.itervalues():
129 group_template_model = create_group_template_model(context, model, group)
130 model.group_templates[group_template_model.name] = group_template_model
133 policies = context.presentation.get('service_template', 'topology_template', 'policies')
135 for policy in policies.itervalues():
136 policy_template_model = create_policy_template_model(context, model, policy)
137 model.policy_templates[policy_template_model.name] = policy_template_model
139 # Substitution template
140 substitution_mappings = context.presentation.get('service_template', 'topology_template',
141 'substitution_mappings')
142 if substitution_mappings:
143 model.substitution_template = create_substitution_template_model(context, model,
144 substitution_mappings)
149 def create_metadata_models(context, service_template, metadata):
150 service_template.meta_data['template_name'] = Metadata(name='template_name',
151 value=metadata.template_name)
152 service_template.meta_data['template_author'] = Metadata(name='template_author',
153 value=metadata.template_author)
154 service_template.meta_data['template_version'] = Metadata(name='template_version',
155 value=metadata.template_version)
156 custom = metadata.custom
158 for name, value in custom.iteritems():
159 service_template.meta_data[name] = Metadata(name=name,
163 def create_node_template_model(context, service_template, node_template):
164 node_type = node_template._get_type(context)
165 node_type = service_template.node_types.get_descendant(node_type._name)
166 model = NodeTemplate(name=node_template._name, type=node_type)
168 if node_template.description:
169 model.description = node_template.description.value
171 if node_template.directives:
172 model.directives = node_template.directives
174 model.properties.update(create_property_models_from_values(
175 template_properties=node_template._get_property_values(context)))
176 model.attributes.update(create_attribute_models_from_values(
177 template_attributes=node_template._get_attribute_default_values(context)))
179 create_interface_template_models(context, service_template, model.interface_templates,
180 node_template._get_interfaces(context))
182 artifacts = node_template._get_artifacts(context)
184 for artifact_name, artifact in artifacts.iteritems():
185 model.artifact_templates[artifact_name] = \
186 create_artifact_template_model(context, service_template, artifact)
188 capabilities = node_template._get_capabilities(context)
190 for capability_name, capability in capabilities.iteritems():
191 model.capability_templates[capability_name] = \
192 create_capability_template_model(context, service_template, capability)
194 if node_template.node_filter:
195 model.target_node_template_constraints = []
196 create_node_filter_constraints(context, node_template.node_filter,
197 model.target_node_template_constraints)
202 def fix_node_template_model(context, service_template, node_template):
203 # Requirements have to be created after all node templates have been created, because
204 # requirements might reference another node template
205 model = service_template.node_templates[node_template._name]
206 requirements = node_template._get_requirements(context)
208 for _, requirement in requirements:
209 model.requirement_templates.append(create_requirement_template_model(context,
214 def create_group_template_model(context, service_template, group):
215 group_type = group._get_type(context)
216 group_type = service_template.group_types.get_descendant(group_type._name)
217 model = GroupTemplate(name=group._name,
220 if group.description:
221 model.description = group.description.value
223 model.properties.update(create_property_models_from_values(group._get_property_values(context)))
225 create_interface_template_models(context, service_template, model.interface_templates,
226 group._get_interfaces(context))
227 members = group.members
229 for member in members:
230 node_template = service_template.node_templates[member]
232 model.node_templates.append(node_template)
237 def create_policy_template_model(context, service_template, policy):
238 policy_type = policy._get_type(context)
239 policy_type = service_template.policy_types.get_descendant(policy_type._name)
240 model = PolicyTemplate(name=policy._name,
243 if policy.description:
244 model.description = policy.description.value
246 model.properties.update(
247 create_property_models_from_values(policy._get_property_values(context)))
249 node_templates, groups = policy._get_targets(context)
251 for target in node_templates:
252 node_template = service_template.node_templates[target._name]
254 model.node_templates.append(node_template)
256 for target in groups:
257 group_template = service_template.group_templates[target._name]
258 assert group_template
259 model.group_templates.append(group_template)
264 def create_requirement_template_model(context, service_template, requirement):
265 model = {'name': requirement._name}
267 node, node_variant = requirement._get_node(context)
269 if node_variant == 'node_type':
270 node_type = service_template.node_types.get_descendant(node._name)
271 model['target_node_type'] = node_type
273 node_template = service_template.node_templates[node._name]
274 model['target_node_template'] = node_template
276 capability, capability_variant = requirement._get_capability(context)
277 if capability is not None:
278 if capability_variant == 'capability_type':
280 service_template.capability_types.get_descendant(capability._name)
281 model['target_capability_type'] = capability_type
283 model['target_capability_name'] = capability._name
285 model = RequirementTemplate(**model)
287 if requirement.node_filter:
288 model.target_node_template_constraints = []
289 create_node_filter_constraints(context, requirement.node_filter,
290 model.target_node_template_constraints)
292 relationship = requirement.relationship
293 if relationship is not None:
294 model.relationship_template = \
295 create_relationship_template_model(context, service_template, relationship)
296 model.relationship_template.name = requirement._name
301 def create_relationship_template_model(context, service_template, relationship):
302 relationship_type, relationship_type_variant = relationship._get_type(context)
303 if relationship_type_variant == 'relationship_type':
304 relationship_type = service_template.relationship_types.get_descendant(
305 relationship_type._name)
306 model = RelationshipTemplate(type=relationship_type)
308 relationship_template = relationship_type
309 relationship_type = relationship_template._get_type(context)
310 relationship_type = service_template.relationship_types.get_descendant(
311 relationship_type._name)
312 model = RelationshipTemplate(type=relationship_type)
313 if relationship_template.description:
314 model.description = relationship_template.description.value
316 create_parameter_models_from_assignments(model.properties,
317 relationship.properties,
319 create_interface_template_models(context, service_template, model.interface_templates,
320 relationship.interfaces)
325 def create_capability_template_model(context, service_template, capability):
326 capability_type = capability._get_type(context)
327 capability_type = service_template.capability_types.get_descendant(capability_type._name)
328 model = CapabilityTemplate(name=capability._name,
329 type=capability_type)
331 capability_definition = capability._get_definition(context)
332 if capability_definition.description:
333 model.description = capability_definition.description.value
334 occurrences = capability_definition.occurrences
335 if occurrences is not None:
336 model.min_occurrences = occurrences.value[0]
337 if occurrences.value[1] != 'UNBOUNDED':
338 model.max_occurrences = occurrences.value[1]
340 valid_source_types = capability_definition.valid_source_types
341 if valid_source_types:
342 for valid_source_type in valid_source_types:
343 # TODO: handle shortcut type names
344 node_type = service_template.node_types.get_descendant(valid_source_type)
345 model.valid_source_node_types.append(node_type)
347 create_parameter_models_from_assignments(model.properties,
348 capability.properties,
354 def create_interface_template_model(context, service_template, interface):
355 interface_type = interface._get_type(context)
356 interface_type = service_template.interface_types.get_descendant(interface_type._name)
357 model = InterfaceTemplate(name=interface._name, type=interface_type)
359 if interface_type.description:
360 model.description = interface_type.description
362 create_parameter_models_from_assignments(model.inputs, interface.inputs, model_cls=Input)
364 operations = interface.operations
366 for operation_name, operation in operations.iteritems():
367 model.operation_templates[operation_name] = \
368 create_operation_template_model(context, service_template, operation)
370 return model if model.operation_templates else None
373 def create_operation_template_model(context, service_template, operation):
374 model = OperationTemplate(name=operation._name)
376 if operation.description:
377 model.description = operation.description.value
379 implementation = operation.implementation
380 if implementation is not None:
381 primary = implementation.primary
382 extract_implementation_primary(context, service_template, operation, model, primary)
383 relationship_edge = operation._get_extensions(context).get('relationship_edge')
384 if relationship_edge is not None:
385 if relationship_edge == 'source':
386 model.relationship_edge = False
387 elif relationship_edge == 'target':
388 model.relationship_edge = True
390 dependencies = implementation.dependencies
391 configuration = OrderedDict()
393 for dependency in dependencies:
394 key, value = split_prefix(dependency)
396 # Special ARIA prefix: signifies configuration parameters
400 value = yaml.load(value)
401 except yaml.parser.MarkedYAMLError as e:
402 context.validation.report(
403 'YAML parser {0} in operation configuration: {1}'
404 .format(e.problem, value),
405 locator=implementation._locator,
409 # Coerce to intrinsic functions, if there are any
410 value = coerce_parameter_value(context, implementation, None, value).value
412 # Support dot-notation nesting
413 set_nested(configuration, key.split('.'), value)
415 if model.dependencies is None:
416 model.dependencies = []
417 model.dependencies.append(dependency)
419 # Convert configuration to Configuration models
420 for key, value in configuration.iteritems():
421 model.configurations[key] = Configuration.wrap(key, value,
422 description='Operation configuration.')
424 create_parameter_models_from_assignments(model.inputs, operation.inputs, model_cls=Input)
428 def create_artifact_template_model(context, service_template, artifact):
429 artifact_type = artifact._get_type(context)
430 artifact_type = service_template.artifact_types.get_descendant(artifact_type._name)
431 model = ArtifactTemplate(name=artifact._name,
433 source_path=artifact.file)
435 if artifact.description:
436 model.description = artifact.description.value
438 model.target_path = artifact.deploy_path
440 repository = artifact._get_repository(context)
441 if repository is not None:
442 model.repository_url = repository.url
443 credential = repository._get_credential(context)
445 model.repository_credential = {}
446 for k, v in credential.iteritems():
447 model.repository_credential[k] = v
449 model.properties.update(
450 create_property_models_from_values(artifact._get_property_values(context)))
455 def create_substitution_template_model(context, service_template, substitution_mappings):
456 node_type = service_template.node_types.get_descendant(substitution_mappings.node_type)
457 model = SubstitutionTemplate(node_type=node_type)
459 capabilities = substitution_mappings.capabilities
461 for mapped_capability_name, capability in capabilities.iteritems():
462 name = 'capability.' + mapped_capability_name
463 node_template_model = service_template.node_templates[capability.node_template]
464 capability_template_model = \
465 node_template_model.capability_templates[capability.capability]
466 model.mappings[name] = \
467 SubstitutionTemplateMapping(name=name,
468 capability_template=capability_template_model)
470 requirements = substitution_mappings.requirements
472 for mapped_requirement_name, requirement in requirements.iteritems():
473 name = 'requirement.' + mapped_requirement_name
474 node_template_model = service_template.node_templates[requirement.node_template]
475 requirement_template_model = None
476 for a_model in node_template_model.requirement_templates:
477 if a_model.name == requirement.requirement:
478 requirement_template_model = a_model
480 model.mappings[name] = \
481 SubstitutionTemplateMapping(name=name,
482 requirement_template=requirement_template_model)
487 def create_plugin_specification_model(context, policy):
488 properties = policy.properties
490 def get(name, default=None):
491 prop = properties.get(name)
492 return prop.value if prop is not None else default
494 model = PluginSpecification(name=policy._name,
495 version=get('version'),
496 enabled=get('enabled', True))
501 def create_workflow_operation_template_model(context, service_template, policy):
502 model = OperationTemplate(name=policy._name)
503 # since we use backpopulates, these fields are populated upon commit, we get a weird(temporary)
504 # behavior where in previous code service_template.workflow_templates is a dict which has None
505 # as key for the value of model.
506 service_template.workflow_templates[model.name] = model
508 if policy.description:
509 model.description = policy.description.value
511 properties = policy._get_property_values(context)
512 for prop_name, prop in properties.iteritems():
513 if prop_name == 'implementation':
514 model.function = prop.value
516 input_model = create_parameter_model_from_value(prop, prop_name, model_cls=Input)
517 input_model.required = prop.required
518 model.inputs[prop_name] = input_model
520 used_reserved_names = WORKFLOW_DECORATOR_RESERVED_ARGUMENTS.intersection(model.inputs.keys())
521 if used_reserved_names:
522 context.validation.report('using reserved arguments in workflow policy "{0}": {1}'
525 string_list_as_string(used_reserved_names)),
526 locator=policy._locator,
527 level=Issue.EXTERNAL)
535 def create_types(context, root, types):
541 if root.get_descendant(name) is None:
545 while not added_all():
546 for name, the_type in types.iteritems():
547 if root.get_descendant(name) is None:
548 parent_type = the_type._get_parent(context)
549 model = Type(name=the_type._name,
550 role=the_type._get_extension('role'))
551 if the_type.description:
552 model.description = the_type.description.value
553 if parent_type is None:
555 model.variant = root.variant
556 root.children.append(model)
558 container = root.get_descendant(parent_type._name)
559 if container is not None:
560 model.parent = container
561 model.variant = container.variant
562 container.children.append(model)
565 def create_input_models_from_values(template_inputs):
568 for template_input_name, template_input in template_inputs.iteritems():
569 model_input = create_parameter_model_from_value(template_input, template_input_name,
571 model_input.required = template_input.required
572 model_inputs[model_input.name] = model_input
575 def create_output_models_from_values(template_outputs):
577 for template_output_name, template_output in template_outputs.iteritems():
578 model_outputs[template_output_name] = \
579 create_parameter_model_from_value(template_output,
580 template_output_name,
585 def create_property_models_from_values(template_properties):
586 model_properties = {}
587 for template_property_name, template_property in template_properties.iteritems():
588 model_properties[template_property_name] = \
589 create_parameter_model_from_value(template_property,
590 template_property_name,
592 return model_properties
594 def create_attribute_models_from_values(template_attributes):
595 model_attributes = {}
596 for template_attribute_name, template_attribute in template_attributes.iteritems():
597 model_attributes[template_attribute_name] = \
598 create_parameter_model_from_value(template_attribute,
599 template_attribute_name,
601 return model_attributes
604 def create_parameter_model_from_value(template_parameter, template_parameter_name, model_cls):
605 return model_cls(name=template_parameter_name,
606 type_name=template_parameter.type,
607 value=template_parameter.value,
608 description=template_parameter.description)
611 def create_parameter_models_from_assignments(properties, source_properties, model_cls):
612 if source_properties:
613 for property_name, prop in source_properties.iteritems():
614 properties[property_name] = model_cls(name=property_name, # pylint: disable=unexpected-keyword-arg
615 type_name=prop.value.type,
616 value=prop.value.value,
617 description=prop.value.description)
620 def create_interface_template_models(context, service_template, interfaces, source_interfaces):
621 if source_interfaces:
622 for interface_name, interface in source_interfaces.iteritems():
623 interface = create_interface_template_model(context, service_template, interface)
624 if interface is not None:
625 interfaces[interface_name] = interface
628 def create_node_filter_constraints(context, node_filter, target_node_template_constraints):
629 properties = node_filter.properties
630 if properties is not None:
631 for property_name, constraint_clause in properties:
632 constraint = create_constraint(context, node_filter, constraint_clause, property_name,
634 target_node_template_constraints.append(constraint)
636 capabilities = node_filter.capabilities
637 if capabilities is not None:
638 for capability_name, capability in capabilities:
639 properties = capability.properties
640 if properties is not None:
641 for property_name, constraint_clause in properties:
642 constraint = create_constraint(context, node_filter, constraint_clause,
643 property_name, capability_name)
644 target_node_template_constraints.append(constraint)
647 def create_constraint(context, node_filter, constraint_clause, property_name, capability_name): # pylint: disable=too-many-return-statements
648 constraint_key = constraint_clause._raw.keys()[0]
650 the_type = constraint_clause._get_type(context)
652 def coerce_constraint(constraint):
653 if the_type is not None:
654 return coerce_value(context, node_filter, the_type, None, None, constraint,
659 def coerce_constraints(constraints):
660 if the_type is not None:
661 return tuple(coerce_constraint(constraint) for constraint in constraints)
665 if constraint_key == 'equal':
666 return Equal(property_name, capability_name,
667 coerce_constraint(constraint_clause.equal))
668 elif constraint_key == 'greater_than':
669 return GreaterThan(property_name, capability_name,
670 coerce_constraint(constraint_clause.greater_than))
671 elif constraint_key == 'greater_or_equal':
672 return GreaterOrEqual(property_name, capability_name,
673 coerce_constraint(constraint_clause.greater_or_equal))
674 elif constraint_key == 'less_than':
675 return LessThan(property_name, capability_name,
676 coerce_constraint(constraint_clause.less_than))
677 elif constraint_key == 'less_or_equal':
678 return LessOrEqual(property_name, capability_name,
679 coerce_constraint(constraint_clause.less_or_equal))
680 elif constraint_key == 'in_range':
681 return InRange(property_name, capability_name,
682 coerce_constraints(constraint_clause.in_range))
683 elif constraint_key == 'valid_values':
684 return ValidValues(property_name, capability_name,
685 coerce_constraints(constraint_clause.valid_values))
686 elif constraint_key == 'length':
687 return Length(property_name, capability_name,
688 coerce_constraint(constraint_clause.length))
689 elif constraint_key == 'min_length':
690 return MinLength(property_name, capability_name,
691 coerce_constraint(constraint_clause.min_length))
692 elif constraint_key == 'max_length':
693 return MaxLength(property_name, capability_name,
694 coerce_constraint(constraint_clause.max_length))
695 elif constraint_key == 'pattern':
696 return Pattern(property_name, capability_name,
697 coerce_constraint(constraint_clause.pattern))
699 raise ValueError('malformed node_filter: {0}'.format(constraint_key))
702 def split_prefix(string):
704 Splits the prefix on the first non-escaped ">".
707 split = IMPLEMENTATION_PREFIX_REGEX.split(string, 1)
710 return split[0].strip(), split[1].strip()
713 def set_nested(the_dict, keys, value):
715 If the ``keys`` list has just one item, puts the value in the the dict. If there are more items,
716 puts the value in a sub-dict, creating sub-dicts as necessary for each key.
718 For example, if ``the_dict`` is an empty dict, keys is ``['first', 'second', 'third']`` and
719 value is ``'value'``, then the_dict will be: ``{'first':{'second':{'third':'value'}}}``.
721 :param the_dict: Dict to change
724 :type keys: [basestring]
729 the_dict[key] = value
731 if key not in the_dict:
732 the_dict[key] = StrictDict(key_class=basestring)
733 set_nested(the_dict[key], keys, value)
736 def extract_implementation_primary(context, service_template, presentation, model, primary):
737 prefix, postfix = split_prefix(primary)
739 # Special ARIA prefix
740 model.plugin_specification = service_template.plugin_specifications.get(prefix)
741 model.function = postfix
742 if model.plugin_specification is None:
743 context.validation.report(
744 'no policy for plugin "{0}" specified in operation implementation: {1}'
745 .format(prefix, primary),
746 locator=presentation._get_child_locator('properties', 'implementation'),
747 level=Issue.BETWEEN_TYPES)
749 # Standard TOSCA artifact with default plugin
750 model.implementation = primary