removed all unnecessary method from models into the topology package
Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/4ae0e5f9 Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/4ae0e5f9 Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/4ae0e5f9 Branch: refs/heads/ARIA-174-Refactor-instantiation-phase Commit: 4ae0e5f99549655a074ef3a5653180339655a921 Parents: a964076 Author: max-orlov <[email protected]> Authored: Tue Jul 25 15:34:41 2017 +0300 Committer: max-orlov <[email protected]> Committed: Tue Jul 25 15:34:41 2017 +0300 ---------------------------------------------------------------------- aria/modeling/service_instance.py | 247 +------- aria/modeling/service_template.py | 135 +---- .../execution_plugin/instantiation.py | 3 - aria/orchestrator/topology/__init__.py | 152 +++-- aria/orchestrator/topology/common.py | 7 +- aria/orchestrator/topology/instance.py | 593 +++++++++++++------ aria/orchestrator/topology/template.py | 590 +++++++++--------- aria/parser/consumption/modeling.py | 10 +- 8 files changed, 866 insertions(+), 871 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/4ae0e5f9/aria/modeling/service_instance.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_instance.py b/aria/modeling/service_instance.py index 15cf8fd..8bd3b9e 100644 --- a/aria/modeling/service_instance.py +++ b/aria/modeling/service_instance.py @@ -32,16 +32,13 @@ from sqlalchemy.ext.orderinglist import ordering_list from . import ( relationship, - utils, types as modeling_types ) from .mixins import InstanceModelMixin -from ..orchestrator import execution_plugin -from ..parser import validation + from ..utils import ( collections, formatting, - console ) @@ -231,50 +228,6 @@ class ServiceBase(InstanceModelMixin): :type: :class:`~datetime.datetime` """) - def satisfy_requirements(self): - satisfied = True - for node in self.nodes.itervalues(): - if not node.satisfy_requirements(): - satisfied = False - return satisfied - - def validate_capabilities(self): - satisfied = True - for node in self.nodes.itervalues(): - if not node.validate_capabilities(): - satisfied = False - return satisfied - - def find_hosts(self): - for node in self.nodes.itervalues(): - node.find_host() - - def configure_operations(self): - for node in self.nodes.itervalues(): - node.configure_operations() - for group in self.groups.itervalues(): - group.configure_operations() - for operation in self.workflows.itervalues(): - operation.configure() - - def is_node_a_target(self, target_node): - for node in self.nodes.itervalues(): - if self._is_node_a_target(node, target_node): - return True - return False - - def _is_node_a_target(self, source_node, target_node): - if source_node.outbound_relationships: - for relationship_model in source_node.outbound_relationships: - if relationship_model.target_node.name == target_node.name: - return True - else: - node = relationship_model.target_node - if node is not None: - if self._is_node_a_target(node, target_node): - return True - return False - @property def as_raw(self): return collections.OrderedDict(( @@ -288,33 +241,6 @@ class ServiceBase(InstanceModelMixin): ('outputs', formatting.as_raw_dict(self.outputs)), ('workflows', formatting.as_raw_list(self.workflows)))) - def dump_graph(self): - for node in self.nodes.itervalues(): - if not self.is_node_a_target(node): - self._dump_graph_node(node) - - def _dump_graph_node(self, node, capability=None): - from ..parser.consumption import ConsumptionContext - - context = ConsumptionContext.get_thread_local() - console.puts(context.style.node(node.name)) - if capability is not None: - console.puts('{0} ({1})'.format(context.style.property(capability.name), - context.style.type(capability.type.name))) - if node.outbound_relationships: - with context.style.indent: - for relationship_model in node.outbound_relationships: - relationship_name = context.style.property(relationship_model.name) - if relationship_model.type is not None: - console.puts('-> {0} ({1})'.format(relationship_name, - context.style.type( - relationship_model.type.name))) - else: - console.puts('-> {0}'.format(relationship_name)) - with console.indent(3): - self._dump_graph_node(relationship_model.target_node, - relationship_model.target_capability) - class NodeBase(InstanceModelMixin): """ @@ -578,123 +504,6 @@ class NodeBase(InstanceModelMixin): return attribute.value if attribute else None return None - def satisfy_requirements(self): - node_template = self.node_template - satisfied = True - for requirement_template in node_template.requirement_templates: - # Find target template - target_node_template, target_node_capability = \ - requirement_template.find_target(node_template) - if target_node_template is not None: - satisfied = self._satisfy_capability(target_node_capability, - target_node_template, - requirement_template) - else: - # TODO: fix - - # context = ConsumptionContext.get_thread_local() - # context.validation.report('requirement "{0}" of node "{1}" has no target node ' - # 'template'.format(requirement_template.name, self.name), - # level=validation.Issue.BETWEEN_INSTANCES) - satisfied = False - return satisfied - - def _satisfy_capability(self, target_node_capability, target_node_template, - requirement_template): - from . import models - # TODO: fix reporting - # context = ConsumptionContext.get_thread_local() - # 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: - from aria.orchestrator import topology - relationship_model = topology.handler.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.outbound_relationships.append(relationship_model) - return True - else: - # context.validation.report('requirement "{0}" of node "{1}" targets node ' - # 'template "{2}" but its instantiated nodes do not ' - # 'have enough capacity'.format( - # requirement_template.name, - # self.name, - # target_node_template.name), - # level=validation.Issue.BETWEEN_INSTANCES) - return False - else: - # context.validation.report('requirement "{0}" of node "{1}" targets node template ' - # '"{2}" but it has no instantiated nodes'.format( - # requirement_template.name, - # self.name, - # target_node_template.name), - # level=validation.Issue.BETWEEN_INSTANCES) - return False - - def validate_capabilities(self): - # TODO: fix - # context = ConsumptionContext.get_thread_local() - satisfied = False - for capability in self.capabilities.itervalues(): - if not capability.has_enough_relationships: - # context.validation.report('capability "{0}" of node "{1}" requires at least {2:d} ' - # 'relationships but has {3:d}'.format( - # capability.name, - # self.name, - # capability.min_occurrences, - # capability.occurrences), - # level=validation.Issue.BETWEEN_INSTANCES) - satisfied = False - return satisfied - - def find_host(self): - def _find_host(node): - if node.type.role == 'host': - return node - for the_relationship in node.outbound_relationships: - if (the_relationship.target_capability is not None) and \ - the_relationship.target_capability.type.role == 'host': - host = _find_host(the_relationship.target_node) - if host is not None: - return host - for the_relationship in node.inbound_relationships: - if (the_relationship.target_capability is not None) and \ - the_relationship.target_capability.type.role == 'feature': - host = _find_host(the_relationship.source_node) - if host is not None: - return host - return None - - self.host = _find_host(self) - - def configure_operations(self): - for interface in self.interfaces.itervalues(): - interface.configure_operations() - for the_relationship in self.outbound_relationships: - the_relationship.configure_operations() - @property def as_raw(self): return collections.OrderedDict(( @@ -812,10 +621,6 @@ class GroupBase(InstanceModelMixin): :type: :obj:`basestring` """) - def configure_operations(self): - for interface in self.interfaces.itervalues(): - interface.configure_operations() - @property def as_raw(self): return collections.OrderedDict(( @@ -1281,10 +1086,6 @@ class RelationshipBase(InstanceModelMixin): :type: :obj:`int` """) - def configure_operations(self): - for interface in self.interfaces.itervalues(): - interface.configure_operations() - @property def as_raw(self): return collections.OrderedDict(( @@ -1537,10 +1338,6 @@ class InterfaceBase(InstanceModelMixin): :type: :obj:`basestring` """) - def configure_operations(self): - for operation in self.operations.itervalues(): - operation.configure() - @property def as_raw(self): return collections.OrderedDict(( @@ -1725,47 +1522,6 @@ class OperationBase(InstanceModelMixin): :type: :obj:`float` """) - def configure(self): - from aria.orchestrator import topology - if (self.implementation is None) and (self.function is None): - return - - if (self.interface is not None) and (self.plugin is None) and (self.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) - 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.configurations.items(): - self.arguments[key] = topology.handler.instantiate(conf.as_argument()) - - if self.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.interface.inputs.items(): - self.arguments[key] = topology.handler.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.inputs.items(): - self.arguments[key] = topology.handler.instantiate(input.as_argument()) - - # Check for reserved arguments - from ..orchestrator.decorators import OPERATION_DECORATOR_RESERVED_ARGUMENTS - used_reserved_names = \ - OPERATION_DECORATOR_RESERVED_ARGUMENTS.intersection(self.arguments.keys()) - # if used_reserved_names: - # # context = ConsumptionContext.get_thread_local() - # context.validation.report('using reserved arguments in operation "{0}": {1}' - # .format( - # self.name, - # formatting.string_list_as_string(used_reserved_names)), - # level=validation.Issue.EXTERNAL) - - @property def as_raw(self): return collections.OrderedDict(( @@ -1776,7 +1532,6 @@ class OperationBase(InstanceModelMixin): ('inputs', formatting.as_raw_dict(self.inputs)))) - class ArtifactBase(InstanceModelMixin): """ Typed file, either provided in a CSAR or downloaded from a repository. http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/4ae0e5f9/aria/modeling/service_template.py ---------------------------------------------------------------------- diff --git a/aria/modeling/service_template.py b/aria/modeling/service_template.py index 80a5dbb..f2e1e78 100644 --- a/aria/modeling/service_template.py +++ b/aria/modeling/service_template.py @@ -31,11 +31,10 @@ from sqlalchemy import ( ) from sqlalchemy.ext.declarative import declared_attr -from ..utils import (collections, formatting, console) +from ..utils import (collections, formatting) from .mixins import TemplateModelMixin from . import ( relationship, - utils, types as modeling_types ) @@ -495,63 +494,6 @@ class NodeTemplateBase(TemplateModelMixin): ('capability_templates', formatting.as_raw_list(self.capability_templates)), ('requirement_templates', formatting.as_raw_list(self.requirement_templates)))) - @property - def scaling(self): - scaling = {} - - def extract_property(properties, name): - if name in scaling: - return - prop = properties.get(name) - if (prop is not None) and (prop.type_name == 'integer') and (prop.value is not None): - scaling[name] = prop.value - - def extract_properties(properties): - extract_property(properties, 'min_instances') - extract_property(properties, 'max_instances') - extract_property(properties, 'default_instances') - - def default_property(name, value): - if name not in scaling: - scaling[name] = value - - # From our scaling capabilities - for capability_template in self.capability_templates.itervalues(): - if capability_template.type.role == 'scaling': - extract_properties(capability_template.properties) - - # From service scaling policies - for policy_template in self.service_template.policy_templates.itervalues(): - if policy_template.type.role == 'scaling': - if policy_template.is_for_node_template(self.name): - extract_properties(policy_template.properties) - - # Defaults - default_property('min_instances', 0) - default_property('max_instances', 1) - default_property('default_instances', 1) - - # Validate - # pylint: disable=too-many-boolean-expressions - if ((scaling['min_instances'] < 0) or - (scaling['max_instances'] < 0) or - (scaling['default_instances'] < 0) or - (scaling['max_instances'] < scaling['min_instances']) or - (scaling['default_instances'] < scaling['min_instances']) or - (scaling['default_instances'] > scaling['max_instances'])): - pass - # TODO: fix this - # context = ConsumptionContext.get_thread_local() - # context.validation.report('invalid scaling parameters for node template "{0}": ' - # 'min={1}, max={2}, default={3}'.format( - # self.name, - # scaling['min_instances'], - # scaling['max_instances'], - # scaling['default_instances']), - # level=validation.Issue.BETWEEN_TYPES) - - return scaling - def is_target_node_template_valid(self, target_node_template): """ Checks if ``target_node_template`` matches all our ``target_node_template_constraints``. @@ -1100,58 +1042,6 @@ class RequirementTemplateBase(TemplateModelMixin): :type: [:class:`NodeTemplateConstraint`] """) - def find_target(self, source_node_template): - # context = ConsumptionContext.get_thread_local() - - # We might already have a specific node template, so we'll just verify it - if self.target_node_template is not None: - if not source_node_template.is_target_node_template_valid(self.target_node_template): - # TODO: fix - pass - # context.validation.report('requirement "{0}" of node template "{1}" is for node ' - # 'template "{2}" but it does not match constraints'.format( - # self.name, - # self.target_node_template.name, - # source_node_template.name), - # level=validation.Issue.BETWEEN_TYPES) - if (self.target_capability_type is not None) \ - or (self.target_capability_name is not None): - target_node_capability = self.find_target_capability(source_node_template, - self.target_node_template) - if target_node_capability is None: - return None, None - else: - target_node_capability = None - - return self.target_node_template, target_node_capability - - # Find first node that matches the type - elif self.target_node_type is not None: - for target_node_template in \ - self.node_template.service_template.node_templates.itervalues(): - if self.target_node_type.get_descendant(target_node_template.type.name) is None: - continue - - if not source_node_template.is_target_node_template_valid(target_node_template): - continue - - target_node_capability = self.find_target_capability(source_node_template, - target_node_template) - if target_node_capability is None: - continue - - return target_node_template, target_node_capability - - return None, None - - def find_target_capability(self, source_node_template, target_node_template): - for capability_template in target_node_template.capability_templates.itervalues(): - if capability_template.satisfies_requirement(source_node_template, - self, - target_node_template): - return capability_template - return None - @property def as_raw(self): return collections.OrderedDict(( @@ -1349,29 +1239,6 @@ class CapabilityTemplateBase(TemplateModelMixin): :type: :obj:`int` """) - def satisfies_requirement(self, - source_node_template, - requirement, - target_node_template): - # Do we match the required capability type? - if requirement.target_capability_type and \ - requirement.target_capability_type.get_descendant(self.type.name) is None: - return False - - # Are we in valid_source_node_types? - if self.valid_source_node_types: - for valid_source_node_type in self.valid_source_node_types: - if valid_source_node_type.get_descendant(source_node_template.type.name) is None: - return False - - # Apply requirement constraints - if requirement.target_node_template_constraints: - for node_template_constraint in requirement.target_node_template_constraints: - if not node_template_constraint.matches(source_node_template, target_node_template): - return False - - return True - @property def as_raw(self): return collections.OrderedDict(( http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/4ae0e5f9/aria/orchestrator/execution_plugin/instantiation.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/execution_plugin/instantiation.py b/aria/orchestrator/execution_plugin/instantiation.py index 4390252..0e3a441 100644 --- a/aria/orchestrator/execution_plugin/instantiation.py +++ b/aria/orchestrator/execution_plugin/instantiation.py @@ -19,10 +19,7 @@ Instantiation of :class:`~aria.modeling.models.Operation` models. # TODO: this module will eventually be moved to a new "aria.instantiation" package -from ...utils.type import full_type_name -from ...utils.formatting import safe_repr from ...utils.collections import OrderedDict -from ...parser import validation from ...modeling.functions import Function http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/4ae0e5f9/aria/orchestrator/topology/__init__.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/topology/__init__.py b/aria/orchestrator/topology/__init__.py index aa9e306..9003c1e 100644 --- a/aria/orchestrator/topology/__init__.py +++ b/aria/orchestrator/topology/__init__.py @@ -83,63 +83,87 @@ class Handler(object): if attribute_name.startswith('_'): continue attribute = getattr(module_, attribute_name) - if isinstance(attribute, type) and issubclass(attribute, (common._TemplateHandler, - common._InstanceHandler)): + if isinstance(attribute, type) and issubclass(attribute, common._Handler): handlers[getattr(models, attribute_name)] = attribute return handlers - def instantiate(self, template, **kwargs): + def instantiate(self, model, **kwargs): """ all handlers used by instantiate should hold a tuple as value (handler, instnace_cls) - :param template: + :param model: :param kwargs: :return: """ - if isinstance(template, dict): + if isinstance(model, dict): return dict((name, self.instantiate(value, **kwargs)) - for name, value in template.iteritems()) - elif isinstance(template, list): - return list(self.instantiate(value, **kwargs) for value in template) - elif template is not None: - handler = self._handlers.get(template.__class__) - instance_cls = self._init_map.get(template.__class__) - return handler(self, template).instantiate(instance_cls, **kwargs) - - def validate(self, template, **kwargs): - if isinstance(template, dict): - return self.validate(template.values()) - elif isinstance(template, list): - return all(self.validate(value) for value in template) - elif template is not None: - handler = self._handlers.get(template.__class__) - return handler(self, template).validate(**kwargs) - - def dump(self, template, context, **kwargs): + for name, value in model.iteritems()) + elif isinstance(model, list): + return list(self.instantiate(value, **kwargs) for value in model) + elif model is not None: + handler = self._handlers.get(model.__class__) + instance_cls = self._init_map.get(model.__class__) + return handler(self, model).instantiate(instance_cls, **kwargs) + + def validate(self, model, **kwargs): + if isinstance(model, dict): + return self.validate(model.values()) + elif isinstance(model, list): + return all(self.validate(value) for value in model) + elif model is not None: + handler = self._handlers.get(model.__class__) + return handler(self, model).validate(**kwargs) + + def dump(self, model, context, **kwargs): if not isinstance(context, self.TopologyStylizer): # Wrap the context to contain a stringIO object context = self.TopologyStylizer(context) - if isinstance(template, dict): - return self.dump(template.values(), context, **kwargs) - elif isinstance(template, list): - context.write('%s:' % template) + if isinstance(model, dict): + return self.dump(model.values(), context, **kwargs) + elif isinstance(model, list): + context.write('%s:' % model) with context.style.indent: - for value in template: + for value in model: self.dump(value, context, **kwargs) - elif template is not None: - handler = self._handlers.get(template.__class__) - handler(self, template).dump(context, **kwargs) + elif model is not None: + handler = self._handlers.get(model.__class__) + handler(self, model).dump(context, **kwargs) return str(context) - def coerce(self, template, **kwargs): - if isinstance(template, dict): - return self.validate(template.values()) - elif isinstance(template, list): - return all(self.validate(value) for value in template) - elif template is not None: - handler = self._handlers.get(template.__class__) - return handler(self, template).coerce(**kwargs) + def dump_graph(self, context, service, **kwargs): + context = self.TopologyStylizer(context) + for node in service.nodes.itervalues(): + if not node.inbound_relationships: + self._dump_graph_node(context, node) + + def _dump_graph_node(self, context, node, capability=None): + context.write(context.style.node(node.name)) + if capability is not None: + context.write('{0} ({1})'.format(context.style.property(capability.name), + context.style.type(capability.type.name))) + if node.outbound_relationships: + with context.style.indent: + for relationship_model in node.outbound_relationships: + relationship_name = context.style.property(relationship_model.name) + if relationship_model.type is not None: + context.write('-> {0} ({1})'.format( + relationship_name, context.style.type(relationship_model.type.name))) + else: + context.write('-> {0}'.format(relationship_name)) + # TODO: this needs to be fixed + # with console.indent(3): + # self._dump_graph_node(relationship_model.target_node, + # relationship_model.target_capability) + + def coerce(self, model, **kwargs): + if isinstance(model, dict): + return self.validate(model.values()) + elif isinstance(model, list): + return all(self.validate(value) for value in model) + elif model is not None: + handler = self._handlers.get(model.__class__) + return handler(self, model).coerce(**kwargs) def dump_types(self, context, service_template): self.dump(service_template.node_types, context) @@ -150,5 +174,55 @@ class Handler(object): self.dump(service_template.artifact_types, context) self.dump(service_template.interface_types, context) + def satisfy_requirements(self, model, **kwargs): + if isinstance(model, dict): + return self.satisfy_requirements(model.values()) + elif isinstance(model, list): + return all(self.satisfy_requirements(value) for value in model) + elif model is not None: + handler = self._handlers.get(model.__class__) + return handler(self, model).satisfy_requirements(**kwargs) + + def validate_capabilities(self, model, **kwargs): + if isinstance(model, dict): + return self.validate_capabilities(model.values()) + elif isinstance(model, list): + return all(self.validate_capabilities(value) for value in model) + elif model is not None: + handler = self._handlers.get(model.__class__) + return handler(self, model).validate_capabilities(**kwargs) + + def _find_host(self, node): + if node.type.role == 'host': + return node + + has_role = lambda rel, role: \ + rel.target_capability is not None and rel.target_capability.type.role == role + + for relationship in node.outbound_relationships: + if has_role(relationship, 'host'): + host = self._find_host(relationship.target_node) + if host is not None: + return host + for relationship in node.inbound_relationships: + if has_role(relationship, 'feature'): + host = self._find_host(relationship.source_node) + if host is not None: + return host + return None + + def find_hosts(self, service): + for node in service.nodes.values(): + service.host = self._find_host(node) + + def configure_operations(self, model, **kwargs): + if isinstance(model, dict): + return self.configure_operations(model.values()) + elif isinstance(model, list): + return all(self.configure_operations(value) for value in model) + elif model is not None: + handler = self._handlers.get(model.__class__) + return handler(self, model).configure_operations(**kwargs) + handler = Handler() http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/4ae0e5f9/aria/orchestrator/topology/common.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/topology/common.py b/aria/orchestrator/topology/common.py index 9b00ce6..9437d56 100644 --- a/aria/orchestrator/topology/common.py +++ b/aria/orchestrator/topology/common.py @@ -17,7 +17,7 @@ class _Handler(object): def __init__(self, topology, template): self._topology = topology - self._template = template + self._model = template def _coerce(self, *templates, **kwargs): for template in templates: @@ -48,3 +48,8 @@ class _TemplateHandler(_Handler): class _InstanceHandler(_Handler): pass + + +class _OperatorHolderHandler(_Handler): + def configure_operations(self): + raise NotImplementedError http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/4ae0e5f9/aria/orchestrator/topology/instance.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/topology/instance.py b/aria/orchestrator/topology/instance.py index cb3f747..3040b3b 100644 --- a/aria/orchestrator/topology/instance.py +++ b/aria/orchestrator/topology/instance.py @@ -13,6 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +from ... modeling import models +from .. import execution_plugin +from .. import decorators from . import common # TODO: this should this be here? @@ -22,101 +25,109 @@ from aria.utils import formatting class Artifact(common._InstanceHandler): def coerce(self, **kwargs): - self._topology.coerce(self._template.properties, **kwargs) + self._topology.coerce(self._model.properties, **kwargs) def validate(self, **kwargs): - self._topology.validate(self._template.properties) + self._topology.validate(self._model.properties) def dump(self, context): - context.write(context.style.node(self._template.name)) - if self._template.description: - context.write(context.style.meta(self._template.description)) + context.write(context.style.node(self._model.name)) + if self._model.description: + context.write(context.style.meta(self._model.description)) with context.style.indent: - context.write('Artifact type: {0}'.format(context.style.type(self._template.type.name))) + context.write('Artifact type: {0}'.format(context.style.type(self._model.type.name))) context.write('Source path: {0}'.format( - context.style.literal(self._template.source_path))) - if self._template.target_path is not None: + context.style.literal(self._model.source_path))) + if self._model.target_path is not None: context.write('Target path: {0}'.format( - context.style.literal(self._template.target_path))) - if self._template.repository_url is not None: + context.style.literal(self._model.target_path))) + if self._model.repository_url is not None: context.write('Repository URL: {0}'.format( - context.style.literal(self._template.repository_url))) - if self._template.repository_credential: + context.style.literal(self._model.repository_url))) + if self._model.repository_credential: context.write('Repository credential: {0}'.format( - context.style.literal(self._template.repository_credential))) - self._topology.dump(self._template.properties, context, 'Properties') + context.style.literal(self._model.repository_credential))) + self._topology.dump(self._model.properties, context, 'Properties') class Capability(common._InstanceHandler): def coerce(self, **kwargs): - self._topology.coerce(self._template.properties, **kwargs) + self._topology.coerce(self._model.properties, **kwargs) def validate(self, **kwargs): - self._topology.validate(self._template.properties) + self._topology.validate(self._model.properties) def dump(self, context): - context.write(context.style.node(self._template.name)) + context.write(context.style.node(self._model.name)) with context.style.indent: - context.write('Type: {0}'.format(context.style.type(self._template.type.name))) + context.write('Type: {0}'.format(context.style.type(self._model.type.name))) context.write('Occurrences: {0:d} ({1:d}{2})'.format( - self._template.occurrences, - self._template.min_occurrences or 0, - ' to {0:d}'.format(self._template.max_occurrences) - if self._template.max_occurrences is not None + 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._template.properties, context, 'Properties') + self._topology.dump(self._model.properties, context, 'Properties') -class Group(common._InstanceHandler): +class Group(common._OperatorHolderHandler): + def coerce(self, **kwargs): - self._coerce(self._template.properties, self._template.interfaces, **kwargs) + self._coerce(self._model.properties, self._model.interfaces, **kwargs) def validate(self, **kwargs): - self._validate(self._template.properties, - self._template.interfaces) + self._validate(self._model.properties, + self._model.interfaces) def dump(self, context): - context.write('Group: {0}'.format(context.style.node(self._template.name))) + context.write('Group: {0}'.format(context.style.node(self._model.name))) with context.style.indent: - context.write('Type: {0}'.format(context.style.type(self._template.type.name))) - self._topology.dump(self._template.properties, context, 'Properties') - self._topology.dump(self._template.interfaces, context, 'Interfaces') - if self._template.nodes: + context.write('Type: {0}'.format(context.style.type(self._model.type.name))) + self._topology.dump(self._model.properties, context, 'Properties') + self._topology.dump(self._model.interfaces, context, 'Interfaces') + if self._model.nodes: context.write('Member nodes:') with context.style.indent: - for node in self._template.nodes: + for node in self._model.nodes: context.write(context.style.node(node.name)) + def configure_operations(self): + for interface in self._model.interfaces.values(): + self._topology.configure_operations(interface) + -class Interface(common._InstanceHandler): +class Interface(common._OperatorHolderHandler): def coerce(self, **kwargs): - self._coerce(self._template.inputs, self._template.operations, **kwargs) + self._coerce(self._model.inputs, self._model.operations, **kwargs) def validate(self, **kwargs): - self._validate(self._template.inputs, - self._template.operations) + self._validate(self._model.inputs, + self._model.operations) def dump(self, context): - context.write(context.style.node(self._template.name)) - if self._template.description: - context.write(context.style.meta(self._template.description)) + context.write(context.style.node(self._model.name)) + if self._model.description: + context.write(context.style.meta(self._model.description)) with context.style.indent: - context.write('Interface type: {0}'.format(context.style.type(self._template.type.name))) - self._topology.dump(self._template.inputs, context, 'Inputs') - self._topology.dump(self._template.operations, context, 'Operations') + context.write('Interface type: {0}'.format(context.style.type(self._model.type.name))) + self._topology.dump(self._model.inputs, context, 'Inputs') + self._topology.dump(self._model.operations, context, 'Operations') + def configure_operations(self): + for operation in self._model.operations.values(): + self._topology.configure_operations(operation) -class Node(common._InstanceHandler): + +class Node(common._OperatorHolderHandler): def coerce(self, **kwargs): - self._coerce(self._template.properties, - self._template.attributes, - self._template.interfaces, - self._template.artifacts, - self._template.capabilities, - self._template.outbound_relationships, + 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): # TODO: fix the context # context = ConsumptionContext.get_thread_local() @@ -129,180 +140,407 @@ class Node(common._InstanceHandler): # len(self.name)), # level=validation.Issue.BETWEEN_INSTANCES) - self._validate(self._template.properties, - self._template.attributes, - self._template.interfaces, - self._template.artifacts, - self._template.capabilities, - self._template.outbound_relationships) + self._validate(self._model.properties, + self._model.attributes, + self._model.interfaces, + self._model.artifacts, + self._model.capabilities, + self._model.outbound_relationships) def dump(self, context): - context.write('Node: {0}'.format(context.style.node(self._template.name))) + context.write('Node: {0}'.format(context.style.node(self._model.name))) with context.style.indent: - context.write('Type: {0}'.format(context.style.type(self._template.type.name))) - context.write('Template: {0}'.format(context.style.node(self._template.node_template.name))) - self._topology.dump(self._template.properties, context, 'Properties') - self._topology.dump(self._template.attributes, context, 'Attributes') - self._topology.dump(self._template.interfaces, context, 'Interfaces') - self._topology.dump(self._template.artifacts, context, 'Artifacts') - self._topology.dump(self._template.capabilities, context, 'Capabilities') - self._topology.dump(self._template.outbound_relationships, context, 'Relationships') - - -class Operation(common._InstanceHandler): + context.write('Type: {0}'.format(context.style.type(self._model.type.name))) + context.write('Template: {0}'.format(context.style.node(self._model.node_template.name))) + self._topology.dump(self._model.properties, context, 'Properties') + self._topology.dump(self._model.attributes, context, 'Attributes') + self._topology.dump(self._model.interfaces, context, 'Interfaces') + self._topology.dump(self._model.artifacts, context, 'Artifacts') + self._topology.dump(self._model.capabilities, context, 'Capabilities') + self._topology.dump(self._model.outbound_relationships, context, '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): + # TODO: fix + # context = ConsumptionContext.get_thread_local() + satisfied = False + for capability in self._model.capabilities.itervalues(): + if not capability.has_enough_relationships: + # context.validation.report('capability "{0}" of node "{1}" requires at least {2:d} ' + # 'relationships but has {3:d}'.format( + # capability.name, + # self.name, + # capability.min_occurrences, + # capability.occurrences), + # level=validation.Issue.BETWEEN_INSTANCES) + satisfied = False + return satisfied + + def satisfy_requirements(self): + satisfied = True + for requirement_template in self._model.node_template.requirement_templates: + # 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: + # TODO: fix + + # context = ConsumptionContext.get_thread_local() + # context.validation.report('requirement "{0}" of node "{1}" has no target node ' + # 'template'.format(requirement_template.name, self.name), + # level=validation.Issue.BETWEEN_INSTANCES) + satisfied = False + return satisfied + + def _satisfy_capability(self, target_node_capability, target_node_template, + requirement_template): + # TODO: fix reporting + # context = ConsumptionContext.get_thread_local() + # 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: + from aria.orchestrator import topology + relationship_model = topology.handler.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: + # context.validation.report('requirement "{0}" of node "{1}" targets node ' + # 'template "{2}" but its instantiated nodes do not ' + # 'have enough capacity'.format( + # requirement_template.name, + # self.name, + # target_node_template.name), + # level=validation.Issue.BETWEEN_INSTANCES) + return False + else: + # context.validation.report('requirement "{0}" of node "{1}" targets node template ' + # '"{2}" but it has no instantiated nodes'.format( + # requirement_template.name, + # self.name, + # target_node_template.name), + # level=validation.Issue.BETWEEN_INSTANCES) + return False + + def _find_target(self, requirement_template): + # We might already have a specific node 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): + # TODO: fix + pass + # context.validation.report('requirement "{0}" of node template "{1}" is for node ' + # 'template "{2}" but it does not match constraints'.format( + # self.name, + # self.target_node_template.name, + # source_node_template.name), + # level=validation.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_from_requirement(requirement_template) + if target_node_capability is None: + return None, None + else: + target_node_capability = None + + return self._model.node_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 + + if requirement_template.target_node_template: + target_node_capability = self._get_capability_from_requirement(requirement_template) + if target_node_capability is None: + continue + else: + return target_node_template, target_node_capability + + return None, None + + def _get_capability_from_requirement(self, requirement_template): + for capability_template in requirement_template.target_node_template.capability_templates.values(): + if self._satisfies_requirement(capability_template, requirement_template): + return capability_template + return None + + def _satisfies_requirement(self, capability_template, requirement_template): + # Do we match the required capability type? + if (requirement_template.target_capability_type and + requirement_template.target_capability_type.get_descendant( + self._model.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, requirement_template.target_node_template): + return False + + return True + + +class Operation(common._OperatorHolderHandler): def coerce(self, **kwargs): - self._coerce(self._template.inputs, - self._template.configurations, - self._template.arguments, + self._coerce(self._model.inputs, + self._model.configurations, + self._model.arguments, **kwargs) def validate(self, **kwargs): - self._validate(self._template.inputs, - self._template.configurations, - self._template.arguments) + self._validate(self._model.inputs, + self._model.configurations, + self._model.arguments) def dump(self, context): - context.write(context.style.node(self._template.name)) - if self._template.description: - context.write(context.style.meta(self._template.description)) + context.write(context.style.node(self._model.name)) + if self._model.description: + context.write(context.style.meta(self._model.description)) with context.style.indent: - if self._template.implementation is not None: + if self._model.implementation is not None: context.write('Implementation: {0}'.format( - context.style.literal(self._template.implementation))) - if self._template.dependencies: + context.style.literal(self._model.implementation))) + if self._model.dependencies: context.write( 'Dependencies: {0}'.format( - ', '.join((str(context.style.literal(v)) for v in self._template.dependencies)))) - self._topology.dump(self._template.inputs, context, 'Inputs') - if self._template.executor is not None: - context.write('Executor: {0}'.format(context.style.literal(self._template.executor))) - if self._template.max_attempts is not None: - context.write('Max attempts: {0}'.format(context.style.literal(self._template.max_attempts))) - if self._template.retry_interval is not None: + ', '.join((str(context.style.literal(v)) for v in self._model.dependencies)))) + self._topology.dump(self._model.inputs, context, 'Inputs') + if self._model.executor is not None: + context.write('Executor: {0}'.format(context.style.literal(self._model.executor))) + if self._model.max_attempts is not None: + context.write('Max attempts: {0}'.format(context.style.literal(self._model.max_attempts))) + if self._model.retry_interval is not None: context.write('Retry interval: {0}'.format( - context.style.literal(self._template.retry_interval))) - if self._template.plugin is not None: + context.style.literal(self._model.retry_interval))) + if self._model.plugin is not None: context.write('Plugin: {0}'.format( - context.style.literal(self._template.plugin.name))) - self._topology.dump(self._template.configurations, context, 'Configuration') - if self._template.function is not None: - context.write('Function: {0}'.format(context.style.literal(self._template.function))) - self._topology.dump(self._template.arguments, context, 'Arguments') + context.style.literal(self._model.plugin.name))) + self._topology.dump(self._model.configurations, context, 'Configuration') + if self._model.function is not None: + context.write('Function: {0}'.format(context.style.literal(self._model.function))) + self._topology.dump(self._model.arguments, context, '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) + 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 = decorators.OPERATION_DECORATOR_RESERVED_ARGUMENTS.intersection( + self._model.arguments.keys()) + # if used_reserved_names: + # # context = ConsumptionContext.get_thread_local() + # context.validation.report('using reserved arguments in operation "{0}": {1}' + # .format( + # self.name, + # formatting.string_list_as_string(used_reserved_names)), + # level=validation.Issue.EXTERNAL) class Policy(common._InstanceHandler): def coerce(self, **kwargs): - self._topology.coerce(self._template.properties, **kwargs) + self._topology.coerce(self._model.properties, **kwargs) def validate(self, **kwargs): - self._topology.validate(self._template.properties) + self._topology.validate(self._model.properties) def dump(self, context): - context.write('Policy: {0}'.format(context.style.node(self._template.name))) + context.write('Policy: {0}'.format(context.style.node(self._model.name))) with context.style.indent: - context.write('Type: {0}'.format(context.style.type(self._template.type.name))) - self._topology.dump(self._template.properties, context, 'Properties') - if self._template.nodes: + context.write('Type: {0}'.format(context.style.type(self._model.type.name))) + self._topology.dump(self._model.properties, context, 'Properties') + if self._model.nodes: context.write('Target nodes:') with context.style.indent: - for node in self._template.nodes: + for node in self._model.nodes: context.write(context.style.node(node.name)) - if self._template.groups: + if self._model.groups: context.write('Target groups:') with context.style.indent: - for group in self._template.groups: + for group in self._model.groups: context.write(context.style.node(group.name)) -class Relationship(common._InstanceHandler): +class Relationship(common._OperatorHolderHandler): def coerce(self, **kwargs): - self._coerce(self._template.properties, - self._template.interfaces, + self._coerce(self._model.properties, + self._model.interfaces, **kwargs) def validate(self, **kwargs): - self._validate(self._template.properties, - self._template.interfaces) + self._validate(self._model.properties, + self._model.interfaces) def dump(self, context): - if self._template.name: - context.write('{0} ->'.format(context.style.node(self._template.name))) + if self._model.name: + context.write('{0} ->'.format(context.style.node(self._model.name))) else: context.write('->') with context.style.indent: - context.write('Node: {0}'.format(context.style.node(self._template.target_node.name))) - if self._template.target_capability: + context.write('Node: {0}'.format(context.style.node(self._model.target_node.name))) + if self._model.target_capability: context.write('Capability: {0}'.format(context.style.node( - self._template.target_capability.name))) - if self._template.type is not None: + self._model.target_capability.name))) + if self._model.type is not None: context.write('Relationship type: {0}'.format( - context.style.type(self._template.type.name))) - if (self._template.relationship_template is not None and - self._template.relationship_template.name): + context.style.type(self._model.type.name))) + if (self._model.relationship_template is not None and + self._model.relationship_template.name): context.write('Relationship template: {0}'.format( - context.style.node(self._template.relationship_template.name))) - self._topology.dump(self._template.properties, context, 'Properties') - self._topology.dump(self._template.interfaces, context, 'Interfaces') + context.style.node(self._model.relationship_template.name))) + self._topology.dump(self._model.properties, context, 'Properties') + self._topology.dump(self._model.interfaces, context, 'Interfaces') + + def configure_operations(self): + for interface in self._model.interfaces.values(): + self._topology.configure_operations(interface) -class Service(common._InstanceHandler): +class Service(common._OperatorHolderHandler): def coerce(self, **kwargs): - self._coerce(self._template.meta_data, - self._template.nodes, - self._template.groups, - self._template.policies, - self._template.substitution, - self._template.inputs, - self._template.outputs, - self._template.workflows, + 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._template.meta_data, - self._template.nodes, - self._template.groups, - self._template.policies, - self._template.substitution, - self._template.inputs, - self._template.outputs, - self._template.workflows) + 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) def dump(self, context): - if self._template.description is not None: - context.write(context.style.meta(self._template.description)) - self._topology.dump(self._template.meta_data, context, 'Metadata') - for node in self._template.nodes.itervalues(): + if self._model.description is not None: + context.write(context.style.meta(self._model.description)) + self._topology.dump(self._model.meta_data, context, 'Metadata') + for node in self._model.nodes.itervalues(): node.dump() - for group in self._template.groups.itervalues(): + for group in self._model.groups.itervalues(): group.dump() - for policy in self._template.policies.itervalues(): + for policy in self._model.policies.itervalues(): policy.dump() - if self._template.substitution is not None: - self._template.substitution.dump() - self._topology.dump(self._template.inputs, context, 'Inputs') - self._topology.dump(self._template.outputs, context, 'Outputs') - self._topology.dump(self._template.workflows, context, 'Workflows') + if self._model.substitution is not None: + self._model.substitution.dump() + self._topology.dump(self._model.inputs, context, 'Inputs') + self._topology.dump(self._model.outputs, context, 'Outputs') + self._topology.dump(self._model.workflows, context, '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._InstanceHandler): def coerce(self, **kwargs): - self._topology.coerce(self._template.mappings, **kwargs) + self._topology.coerce(self._model.mappings, **kwargs) def validate(self, **kwargs): - self._topology.validate(self._template.mappings) + self._topology.validate(self._model.mappings) def dump(self, context): context.write('Substitution:') with context.style.indent: - context.write('Node type: {0}'.format(context.style.type(self._template.node_type.name))) - self._topology.dump(self._template.mappings, context, 'Mappings') + context.write('Node type: {0}'.format(context.style.type(self._model.node_type.name))) + self._topology.dump(self._model.mappings, context, 'Mappings') class SubstitutionMapping(common._InstanceHandler): def validate(self, **kwargs): # context = ConsumptionContext.get_thread_local() - if (self._template.capability is None) and (self._template.requirement_template is None): + if (self._model.capability is None) and (self._model.requirement_template is None): pass # TODO: handler reports # context.validation.report('mapping "{0}" refers to neither capability nor a requirement' @@ -312,16 +550,17 @@ class SubstitutionMapping(common._InstanceHandler): # level=validation.Issue.BETWEEN_TYPES) def dump(self, context): - if self._template.capability is not None: + if self._model.capability is not None: context.write('{0} -> {1}.{2}'.format( - context.style.node(self._template.name), - context.style.node(self._template.capability.node.name), - context.style.node(self._template.capability.name))) + context.style.node(self._model.name), + context.style.node(self._model.capability.node.name), + context.style.node(self._model.capability.name))) else: context.write('{0} -> {1}.{2}'.format( - context.style.node(self._template.name), - context.style.node(self._template.node.name), - context.style.node(self._template.requirement_template.name))) + context.style.node(self._model.name), + context.style.node(self._model.node.name), + context.style.node(self._model.requirement_template.name))) + class Metadata(common._InstanceHandler): @@ -334,7 +573,7 @@ class Metadata(common._InstanceHandler): pass def instantiate(self, instance_cls, **kwargs): - return instance_cls(name=self._template.name, value=self._template.value) + return instance_cls(name=self._model.name, value=self._model.value) def validate(self): pass @@ -343,24 +582,24 @@ class Metadata(common._InstanceHandler): class _Parameter(common._InstanceHandler): def dump(self, context): - if self._template.type_name is not None: + if self._model.type_name is not None: context.write('{0}: {1} ({2})'.format( - context.style.property(self._template.name), - context.style.literal(formatting.as_raw(self._template.value)), - context.style.type(self._template.type_name))) + context.style.property(self._model.name), + context.style.literal(formatting.as_raw(self._model.value)), + context.style.type(self._model.type_name))) else: context.write('{0}: {1}'.format( - context.style.property(self._template.name), - context.style.literal(formatting.as_raw(self._template.value)))) - if self._template.description: - context.write(context.style.meta(self._template.description)) + context.style.property(self._model.name), + context.style.literal(formatting.as_raw(self._model.value)))) + if self._model.description: + context.write(context.style.meta(self._model.description)) def instantiate(self, instance_cls, **kwargs): return instance_cls( - name=self._template.name, # pylint: disable=unexpected-keyword-arg - type_name=self._template.type_name, - _value=self._template._value, - description=self._template.description + 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): @@ -396,8 +635,8 @@ class Type(common._InstanceHandler): pass def dump(self, context): - if self._template.name: - context.write(context.style.type(self._template.name)) + if self._model.name: + context.write(context.style.type(self._model.name)) with context.style.indent: - for child in self._template.children: + for child in self._model.children: self._topology.dump(context, child)
