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.
16 from ... parser.modeling import context
17 from ... modeling import models, functions
18 from ... utils import formatting
19 from .. import execution_plugin
20 from .. import decorators
24 class Artifact(common.InstanceHandlerBase):
26 def coerce(self, **kwargs):
27 self._topology.coerce(self._model.properties, **kwargs)
29 def validate(self, **kwargs):
30 self._topology.validate(self._model.properties, **kwargs)
32 def dump(self, out_stream):
33 with out_stream.indent():
34 out_stream.write(out_stream.node_style(self._model.name))
35 out_stream.write(out_stream.meta_style(self._model.description))
36 with out_stream.indent():
37 out_stream.write('Artifact type: {0}'.format(out_stream.type_style(
38 self._model.type.name)))
39 out_stream.write('Source path: {0}'.format(
40 out_stream.literal_style(self._model.source_path)))
41 if self._model.target_path is not None:
42 out_stream.write('Target path: {0}'.format(
43 out_stream.literal_style(self._model.target_path)))
44 if self._model.repository_url is not None:
45 out_stream.write('Repository URL: {0}'.format(
46 out_stream.literal_style(self._model.repository_url)))
47 if self._model.repository_credential:
48 out_stream.write('Repository credential: {0}'.format(
49 out_stream.literal_style(self._model.repository_credential)))
50 self._topology.dump(self._model.properties, out_stream, title='Properties')
53 class Capability(common.InstanceHandlerBase):
54 def coerce(self, **kwargs):
55 self._topology.coerce(self._model.properties, **kwargs)
57 def validate(self, **kwargs):
58 self._topology.validate(self._model.properties, **kwargs)
60 def dump(self, out_stream):
61 out_stream.write(out_stream.node_style(self._model.name))
62 with out_stream.indent():
63 out_stream.write('Type: {0}'.format(out_stream.type_style(self._model.type.name)))
64 out_stream.write('Occurrences: {0:d} ({1:d}{2})'.format(
65 self._model.occurrences,
66 self._model.min_occurrences or 0,
67 ' to {0:d}'.format(self._model.max_occurrences)
68 if self._model.max_occurrences is not None
70 self._topology.dump(self._model.properties, out_stream, title='Properties')
73 class Group(common.ActorHandlerBase):
75 def coerce(self, **kwargs):
76 self._coerce(self._model.properties, self._model.interfaces, **kwargs)
78 def validate(self, **kwargs):
79 self._validate(self._model.properties,
80 self._model.interfaces,
83 def dump(self, out_stream):
84 out_stream.write('Group: {0}'.format(out_stream.node_style(self._model.name)))
85 with out_stream.indent():
86 out_stream.write('Type: {0}'.format(out_stream.type_style(self._model.type.name)))
87 self._topology.dump(self._model.properties, out_stream, title='Properties')
88 self._topology.dump(self._model.interfaces, out_stream, title='Interfaces')
90 out_stream.write('Member nodes:')
91 with out_stream.indent():
92 for node in self._model.nodes:
93 out_stream.write(out_stream.node_style(node.name))
95 def configure_operations(self):
96 for interface in self._model.interfaces.values():
97 self._topology.configure_operations(interface)
100 class Interface(common.ActorHandlerBase):
101 def coerce(self, **kwargs):
102 self._coerce(self._model.inputs, self._model.operations, **kwargs)
104 def validate(self, **kwargs):
105 self._validate(self._model.inputs,
106 self._model.operations,
109 def dump(self, out_stream):
110 out_stream.write(out_stream.node_style(self._model.name))
111 if self._model.description:
112 out_stream.write(out_stream.meta_style(self._model.description))
113 with out_stream.indent():
114 out_stream.write('Interface type: {0}'.format(
115 out_stream.type_style(self._model.type.name)))
116 self._topology.dump(self._model.inputs, out_stream, title='Inputs')
117 self._topology.dump(self._model.operations, out_stream, title='Operations')
119 def configure_operations(self):
120 for operation in self._model.operations.values():
121 self._topology.configure_operations(operation)
124 class Node(common.ActorHandlerBase):
125 def coerce(self, **kwargs):
126 self._coerce(self._model.properties,
127 self._model.attributes,
128 self._model.interfaces,
129 self._model.artifacts,
130 self._model.capabilities,
131 self._model.outbound_relationships,
134 def validate(self, **kwargs):
135 if len(self._model.name) > context.ID_MAX_LENGTH:
136 self._topology.report(
137 '"{0}" has an ID longer than the limit of {1:d} characters: {2:d}'.format(
138 self._model.name, context.ID_MAX_LENGTH, len(self._model.name)),
139 level=self._topology.Issue.BETWEEN_INSTANCES)
141 self._validate(self._model.properties,
142 self._model.attributes,
143 self._model.interfaces,
144 self._model.artifacts,
145 self._model.capabilities,
146 self._model.outbound_relationships)
148 def dump(self, out_stream):
149 out_stream.write('Node: {0}'.format(out_stream.node_style(self._model.name)))
150 with out_stream.indent():
151 out_stream.write('Type: {0}'.format(out_stream.type_style(self._model.type.name)))
152 out_stream.write('Template: {0}'.format(
153 out_stream.node_style(self._model.node_template.name)))
154 self._topology.dump(self._model.properties, out_stream, title='Properties')
155 self._topology.dump(self._model.attributes, out_stream, title='Attributes')
156 self._topology.dump(self._model.interfaces, out_stream, title='Interfaces')
157 self._topology.dump(self._model.artifacts, out_stream, title='Artifacts')
158 self._topology.dump(self._model.capabilities, out_stream, title='Capabilities')
159 self._topology.dump(self._model.outbound_relationships, out_stream,
160 title='Relationships')
162 def configure_operations(self):
163 for interface in self._model.interfaces.values():
164 self._topology.configure_operations(interface)
165 for relationship in self._model.outbound_relationships:
166 self._topology.configure_operations(relationship)
168 def validate_capabilities(self):
170 for capability in self._model.capabilities.itervalues():
171 if not capability.has_enough_relationships:
172 self._topology.report(
173 'capability "{0}" of node "{1}" requires at least {2:d} '
174 'relationships but has {3:d}'.format(capability.name,
176 capability.min_occurrences,
177 capability.occurrences),
178 level=self._topology.Issue.BETWEEN_INSTANCES)
182 def satisfy_requirements(self):
184 for requirement_template in self._model.node_template.requirement_templates:
186 # Since we try and satisfy requirements, which are node template bound, and use that
187 # information in the creation of the relationship, Some requirements may have been
188 # satisfied by a previous run on that node template.
189 # The entire mechanism of satisfying requirements needs to be refactored.
190 if any(rel.requirement_template == requirement_template
191 for rel in self._model.outbound_relationships):
194 # Find target template
195 target_node_template, target_node_capability = self._find_target(requirement_template)
196 if target_node_template is not None:
197 satisfied = self._satisfy_capability(
198 target_node_capability, target_node_template, requirement_template)
200 self._topology.report('requirement "{0}" of node "{1}" has no target node template'.
201 format(requirement_template.name, self._model.name),
202 level=self._topology.Issue.BETWEEN_INSTANCES)
206 def _satisfy_capability(self, target_node_capability, target_node_template,
207 requirement_template):
209 target_nodes = target_node_template.nodes
212 target_capability = None
214 if target_node_capability is not None:
215 # Relate to the first target node that has capacity
216 for node in target_nodes:
217 a_target_capability = node.capabilities.get(target_node_capability.name)
218 if a_target_capability.relate():
220 target_capability = a_target_capability
223 # Use first target node
224 target_node = target_nodes[0]
226 if target_node is not None:
227 if requirement_template.relationship_template is not None:
228 relationship_model = self._topology.instantiate(
229 requirement_template.relationship_template)
231 relationship_model = models.Relationship()
232 relationship_model.name = requirement_template.name
233 relationship_model.requirement_template = requirement_template
234 relationship_model.target_node = target_node
235 relationship_model.target_capability = target_capability
236 self._model.outbound_relationships.append(relationship_model)
239 self._topology.report(
240 'requirement "{0}" of node "{1}" targets node '
241 'template "{2}" but its instantiated nodes do not '
242 'have enough capacity'.format(
243 requirement_template.name, self._model.name, target_node_template.name),
244 level=self._topology.Issue.BETWEEN_INSTANCES)
247 self._topology.report(
248 'requirement "{0}" of node "{1}" targets node template '
249 '"{2}" but it has no instantiated nodes'.format(
250 requirement_template.name, self._model.name, target_node_template.name),
251 level=self._topology.Issue.BETWEEN_INSTANCES)
254 def _find_target(self, requirement_template):
255 # We might already have a specific node template from the requirement template, so
256 # we'll just verify it
257 if requirement_template.target_node_template is not None:
258 if not self._model.node_template.is_target_node_template_valid(
259 requirement_template.target_node_template):
260 self._topology.report(
261 'requirement "{0}" of node template "{1}" is for node '
262 'template "{2}" but it does not match constraints'.format(
263 requirement_template.name,
264 requirement_template.target_node_template.name,
265 self._model.node_template.name),
266 level=self._topology.Issue.BETWEEN_TYPES)
267 if (requirement_template.target_capability_type is not None or
268 requirement_template.target_capability_name is not None):
269 target_node_capability = self._get_capability(requirement_template)
270 if target_node_capability is None:
273 target_node_capability = None
275 return requirement_template.target_node_template, target_node_capability
277 # Find first node that matches the type
278 elif requirement_template.target_node_type is not None:
279 for target_node_template in \
280 self._model.node_template.service_template.node_templates.itervalues():
281 if requirement_template.target_node_type.get_descendant(
282 target_node_template.type.name) is None:
285 if not self._model.node_template.is_target_node_template_valid(
286 target_node_template):
289 target_node_capability = self._get_capability(requirement_template,
290 target_node_template)
292 if target_node_capability is None:
295 return target_node_template, target_node_capability
297 # Find the first node which has a capability of the required type
298 elif requirement_template.target_capability_type is not None:
299 for target_node_template in \
300 self._model.node_template.service_template.node_templates.itervalues():
301 target_node_capability = \
302 self._get_capability(requirement_template, target_node_template)
303 if target_node_capability:
304 return target_node_template, target_node_capability
308 def _get_capability(self, requirement_template, target_node_template=None):
309 target_node_template = target_node_template or requirement_template.target_node_template
311 for capability_template in target_node_template.capability_templates.values():
312 if self._satisfies_requirement(
313 capability_template, requirement_template, target_node_template):
314 return capability_template
318 def _satisfies_requirement(
319 self, capability_template, requirement_template, target_node_template):
320 # Do we match the required capability type?
321 if (requirement_template.target_capability_type and
322 requirement_template.target_capability_type.get_descendant(
323 capability_template.type.name) is None):
326 # Are we in valid_source_node_types?
327 if capability_template.valid_source_node_types:
328 for valid_source_node_type in capability_template.valid_source_node_types:
329 if valid_source_node_type.get_descendant(
330 self._model.node_template.type.name) is None:
333 # Apply requirement constraints
334 if requirement_template.target_node_template_constraints:
335 for node_template_constraint in requirement_template.target_node_template_constraints:
336 if not node_template_constraint.matches(
337 self._model.node_template, target_node_template):
343 class Operation(common.ActorHandlerBase):
344 def coerce(self, **kwargs):
345 self._coerce(self._model.inputs,
346 self._model.configurations,
347 self._model.arguments,
350 def validate(self, **kwargs):
351 self._validate(self._model.inputs,
352 self._model.configurations,
353 self._model.arguments,
356 def dump(self, out_stream):
357 out_stream.write(out_stream.node_style(self._model.name))
358 if self._model.description:
359 out_stream.write(out_stream.meta_style(self._model.description))
360 with out_stream.indent():
361 if self._model.implementation is not None:
362 out_stream.write('Implementation: {0}'.format(
363 out_stream.literal_style(self._model.implementation)))
364 if self._model.dependencies:
366 'Dependencies: {0}'.format(', '.join((str(out_stream.literal_style(v))
367 for v in self._model.dependencies))))
368 self._topology.dump(self._model.inputs, out_stream, title='Inputs')
369 if self._model.executor is not None:
370 out_stream.write('Executor: {0}'.format(out_stream.literal_style(
371 self._model.executor)))
372 if self._model.max_attempts is not None:
373 out_stream.write('Max attempts: {0}'.format(out_stream.literal_style(
374 self._model.max_attempts)))
375 if self._model.retry_interval is not None:
376 out_stream.write('Retry interval: {0}'.format(
377 out_stream.literal_style(self._model.retry_interval)))
378 if self._model.plugin is not None:
379 out_stream.write('Plugin: {0}'.format(
380 out_stream.literal_style(self._model.plugin.name)))
381 self._topology.dump(self._model.configurations, out_stream, title='Configuration')
382 if self._model.function is not None:
383 out_stream.write('Function: {0}'.format(out_stream.literal_style(
384 self._model.function)))
385 self._topology.dump(self._model.arguments, out_stream, title='Arguments')
387 def configure_operations(self):
388 if self._model.implementation is None and self._model.function is None:
391 if (self._model.interface is not None and
392 self._model.plugin is None and
393 self._model.function is None):
394 # ("interface" is None for workflow operations, which do not currently use "plugin")
395 # The default (None) plugin is the execution plugin
396 execution_plugin.instantiation.configure_operation(self._model, self._topology)
398 # In the future plugins may be able to add their own "configure_operation" hook that
399 # can validate the configuration and otherwise create specially derived arguments. For
400 # now, we just send all configuration parameters as arguments without validation.
401 for key, conf in self._model.configurations.items():
402 self._model.arguments[key] = self._topology.instantiate(conf.as_argument())
404 if self._model.interface is not None:
405 # Send all interface inputs as extra arguments
406 # ("interface" is None for workflow operations)
407 # Note that they will override existing arguments of the same names
408 for key, input in self._model.interface.inputs.items():
409 self._model.arguments[key] = self._topology.instantiate(input.as_argument())
411 # Send all inputs as extra arguments
412 # Note that they will override existing arguments of the same names
413 for key, input in self._model.inputs.items():
414 self._model.arguments[key] = self._topology.instantiate(input.as_argument())
416 # Check for reserved arguments
417 used_reserved_names = set(decorators.OPERATION_DECORATOR_RESERVED_ARGUMENTS).intersection(
418 self._model.arguments.keys())
419 if used_reserved_names:
420 self._topology.report(
421 'using reserved arguments in operation "{0}": {1}'.format(
422 self._model.name, formatting.string_list_as_string(used_reserved_names)),
423 level=self._topology.Issue.EXTERNAL)
426 class Policy(common.InstanceHandlerBase):
427 def coerce(self, **kwargs):
428 self._topology.coerce(self._model.properties, **kwargs)
430 def validate(self, **kwargs):
431 self._topology.validate(self._model.properties, **kwargs)
433 def dump(self, out_stream):
434 out_stream.write('Policy: {0}'.format(out_stream.node_style(self._model.name)))
435 with out_stream.indent():
436 out_stream.write('Type: {0}'.format(out_stream.type_style(self._model.type.name)))
437 self._topology.dump(self._model.properties, out_stream, title='Properties')
438 if self._model.nodes:
439 out_stream.write('Target nodes:')
440 with out_stream.indent():
441 for node in self._model.nodes:
442 out_stream.write(out_stream.node_style(node.name))
443 if self._model.groups:
444 out_stream.write('Target groups:')
445 with out_stream.indent():
446 for group in self._model.groups:
447 out_stream.write(out_stream.node_style(group.name))
450 class Relationship(common.ActorHandlerBase):
451 def coerce(self, **kwargs):
452 self._coerce(self._model.properties,
453 self._model.interfaces,
456 def validate(self, **kwargs):
457 self._validate(self._model.properties,
458 self._model.interfaces,
461 def dump(self, out_stream):
463 out_stream.write('{0} ->'.format(out_stream.node_style(self._model.name)))
465 out_stream.write('->')
466 with out_stream.indent():
467 out_stream.write('Node: {0}'.format(out_stream.node_style(
468 self._model.target_node.name)))
469 if self._model.target_capability:
470 out_stream.write('Capability: {0}'.format(out_stream.node_style(
471 self._model.target_capability.name)))
472 if self._model.type is not None:
473 out_stream.write('Relationship type: {0}'.format(
474 out_stream.type_style(self._model.type.name)))
475 if (self._model.relationship_template is not None and
476 self._model.relationship_template.name):
477 out_stream.write('Relationship template: {0}'.format(
478 out_stream.node_style(self._model.relationship_template.name)))
479 self._topology.dump(self._model.properties, out_stream, title='Properties')
480 self._topology.dump(self._model.interfaces, out_stream, title='Interfaces')
482 def configure_operations(self):
483 for interface in self._model.interfaces.values():
484 self._topology.configure_operations(interface)
487 class Service(common.ActorHandlerBase):
488 def coerce(self, **kwargs):
489 self._coerce(self._model.meta_data,
492 self._model.policies,
493 self._model.substitution,
496 self._model.workflows,
499 def validate(self, **kwargs):
500 self._validate(self._model.meta_data,
503 self._model.policies,
504 self._model.substitution,
507 self._model.workflows,
510 def dump(self, out_stream):
511 if self._model.description is not None:
512 out_stream.write(out_stream.meta_style(self._model.description))
513 self._topology.dump(self._model.meta_data, out_stream, title='Metadata')
514 self._topology.dump(self._model.nodes, out_stream)
515 self._topology.dump(self._model.groups, out_stream)
516 self._topology.dump(self._model.policies, out_stream)
517 self._topology.dump(self._model.substitution, out_stream)
518 self._topology.dump(self._model.inputs, out_stream, title='Inputs')
519 self._topology.dump(self._model.outputs, out_stream, title='Outputs')
520 self._topology.dump(self._model.workflows, out_stream, title='Workflows')
522 def configure_operations(self):
523 for node in self._model.nodes.itervalues():
524 self._topology.configure_operations(node)
525 for group in self._model.groups.itervalues():
526 self._topology.configure_operations(group)
527 for operation in self._model.workflows.itervalues():
528 self._topology.configure_operations(operation)
530 def validate_capabilities(self):
532 for node in self._model.nodes.values():
533 if not self._topology.validate_capabilities(node):
537 def satisfy_requirements(self):
538 return all(self._topology.satisfy_requirements(node)
539 for node in self._model.nodes.values())
542 class Substitution(common.InstanceHandlerBase):
543 def coerce(self, **kwargs):
544 self._topology.coerce(self._model.mappings, **kwargs)
546 def validate(self, **kwargs):
547 self._topology.validate(self._model.mappings, **kwargs)
549 def dump(self, out_stream):
550 out_stream.write('Substitution:')
551 with out_stream.indent():
552 out_stream.write('Node type: {0}'.format(out_stream.type_style(
553 self._model.node_type.name)))
554 self._topology.dump(self._model.mappings, out_stream, title='Mappings')
557 class SubstitutionMapping(common.InstanceHandlerBase):
559 def coerce(self, **kwargs):
562 def validate(self, **_):
563 if (self._model.capability is None) and (self._model.requirement_template is None):
564 self._topology.report(
565 'mapping "{0}" refers to neither capability nor a requirement'
566 ' in node: {1}'.format(
567 self._model.name, formatting.safe_repr(self._model.node_style.name)),
568 level=self._topology.Issue.BETWEEN_TYPES)
570 def dump(self, out_stream):
571 if self._model.capability is not None:
572 out_stream.write('{0} -> {1}.{2}'.format(
573 out_stream.node_style(self._model.name),
574 out_stream.node_style(self._model.capability.node.name),
575 out_stream.node_style(self._model.capability.name)))
577 out_stream.write('{0} -> {1}.{2}'.format(
578 out_stream.node_style(self._model.name),
579 out_stream.node_style(self._model.node.name),
580 out_stream.node_style(self._model.requirement_template.name)))
583 class Metadata(common.InstanceHandlerBase):
585 def dump(self, out_stream):
586 out_stream.write('{0}: {1}'.format(
587 out_stream.property_style(self._model.name),
588 out_stream.literal_style(self._model.value)))
590 def coerce(self, **_):
593 def instantiate(self, instance_cls):
594 return instance_cls(name=self._model.name, value=self._model.value)
600 class _Parameter(common.InstanceHandlerBase):
602 def dump(self, out_stream):
603 if self._model.type_name is not None:
604 out_stream.write('{0}: {1} ({2})'.format(
605 out_stream.property_style(self._model.name),
606 out_stream.literal_style(formatting.as_raw(self._model.value)),
607 out_stream.type_style(self._model.type_name)))
609 out_stream.write('{0}: {1}'.format(
610 out_stream.property_style(self._model.name),
611 out_stream.literal_style(formatting.as_raw(self._model.value))))
612 if self._model.description:
613 out_stream.write(out_stream.meta_style(self._model.description))
615 def instantiate(self, instance_cls, **kwargs):
617 name=self._model.name, # pylint: disable=unexpected-keyword-arg
618 type_name=self._model.type_name,
619 _value=self._model._value,
620 description=self._model.description
626 def coerce(self, report_issues): # pylint: disable=arguments-differ
627 value = self._model._value
628 if value is not None:
629 evaluation = functions.evaluate(value, self._model, report_issues)
630 if (evaluation is not None) and evaluation.final:
631 # A final evaluation can safely replace the existing value
632 self._model._value = evaluation.value
635 class Attribute(_Parameter):
639 class Input(_Parameter):
643 class Output(_Parameter):
647 class Argument(_Parameter):
651 class Property(_Parameter):
655 class Configuration(_Parameter):
659 class Type(common.InstanceHandlerBase):
660 def coerce(self, **_):
663 def dump(self, out_stream):
665 out_stream.write(out_stream.type_style(self._model.name))
666 with out_stream.indent():
667 for child in self._model.children:
668 self._topology.dump(child, out_stream)
670 def validate(self, **kwargs):