Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id F075120049D for ; Wed, 9 Aug 2017 15:34:15 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id EEFAB169238; Wed, 9 Aug 2017 13:34:15 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 34D3A169236 for ; Wed, 9 Aug 2017 15:34:14 +0200 (CEST) Received: (qmail 42900 invoked by uid 500); 9 Aug 2017 13:34:13 -0000 Mailing-List: contact commits-help@ariatosca.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@ariatosca.incubator.apache.org Delivered-To: mailing list commits@ariatosca.incubator.apache.org Received: (qmail 42859 invoked by uid 99); 9 Aug 2017 13:34:12 -0000 Received: from pnap-us-west-generic-nat.apache.org (HELO spamd1-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 09 Aug 2017 13:34:11 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd1-us-west.apache.org (ASF Mail Server at spamd1-us-west.apache.org) with ESMTP id 76BEAC412C for ; Wed, 9 Aug 2017 13:34:11 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd1-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: -4.222 X-Spam-Level: X-Spam-Status: No, score=-4.222 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, RCVD_IN_DNSWL_HI=-5, RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01, RP_MATCHES_RCVD=-0.001, SPF_PASS=-0.001] autolearn=disabled Received: from mx1-lw-eu.apache.org ([10.40.0.8]) by localhost (spamd1-us-west.apache.org [10.40.0.7]) (amavisd-new, port 10024) with ESMTP id qrjkYSjO-87p for ; Wed, 9 Aug 2017 13:34:08 +0000 (UTC) Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx1-lw-eu.apache.org (ASF Mail Server at mx1-lw-eu.apache.org) with SMTP id 232295FD83 for ; Wed, 9 Aug 2017 13:34:05 +0000 (UTC) Received: (qmail 41506 invoked by uid 99); 9 Aug 2017 13:34:05 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 09 Aug 2017 13:34:05 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 0238CE10F8; Wed, 9 Aug 2017 13:34:05 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: emblemparade@apache.org To: commits@ariatosca.incubator.apache.org Date: Wed, 09 Aug 2017 13:34:04 -0000 Message-Id: X-Mailer: ASF-Git Admin Mailer Subject: [1/5] incubator-ariatosca git commit: ARIA-174 Refactor instantiation phase [Forced Update!] archived-at: Wed, 09 Aug 2017 13:34:16 -0000 Repository: incubator-ariatosca Updated Branches: refs/heads/ARIA-321-clearwater 531b6006d -> 40be30864 (forced update) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/aria/orchestrator/topology/topology.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/topology/topology.py b/aria/orchestrator/topology/topology.py new file mode 100644 index 0000000..8ee33d1 --- /dev/null +++ b/aria/orchestrator/topology/topology.py @@ -0,0 +1,223 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from ...parser.validation import issue +from ...modeling import models +from ...utils import console +from . import ( + template_handler, + instance_handler, + common +) + + +class Topology(issue.ReporterMixin): + + _init_map = { + models.ServiceTemplate: models.Service, + models.ArtifactTemplate: models.Artifact, + models.CapabilityTemplate: models.Capability, + models.GroupTemplate: models.Group, + models.InterfaceTemplate: models.Interface, + models.NodeTemplate: models.Node, + models.PolicyTemplate: models.Policy, + models.SubstitutionTemplate: models.Substitution, + models.RelationshipTemplate: models.Relationship, + models.OperationTemplate: models.Operation, + models.SubstitutionTemplateMapping: models.SubstitutionMapping, + + # Common + models.Metadata: models.Metadata, + models.Attribute: models.Attribute, + models.Property: models.Property, + models.Input: models.Input, + models.Output: models.Output, + models.Configuration: models.Configuration, + models.Argument: models.Argument, + models.Type: models.Type + } + + def __init__(self, *args, **kwargs): + super(Topology, self).__init__(*args, **kwargs) + self._model_cls_to_handler = dict(self._init_handlers(instance_handler), + **self._init_handlers(template_handler)) + + @staticmethod + def _init_handlers(module_): + """ + Register handlers from a handler module to the models + + :param module_: The module to look for handlers + :return: a dict where the key is the models class, and the value is the handler class + associated with it from the provided module + """ + handlers = {} + for attribute_name in dir(module_): + if attribute_name.startswith('_'): + continue + attribute = getattr(module_, attribute_name) + if isinstance(attribute, type) and issubclass(attribute, common.HandlerBase): + handlers[getattr(models, attribute_name)] = attribute + return handlers + + def instantiate(self, model, **kwargs): + """ + instantiate the provided model + + :param model: + :param kwargs: + :return: + """ + if isinstance(model, dict): + return dict((name, self.instantiate(value, **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._model_cls_to_handler[model.__class__] + model_instance_cls = self._init_map[model.__class__] + return _handler(self, model).instantiate(model_instance_cls, **kwargs) + + def validate(self, model, **kwargs): + if isinstance(model, dict): + return self.validate(model.values(), **kwargs) + elif isinstance(model, list): + return all(self.validate(value, **kwargs) for value in model) + elif model is not None: + _handler = self._model_cls_to_handler[model.__class__] + return _handler(self, model).validate(**kwargs) + + def dump(self, model, out_stream=None, title=None, **kwargs): + out_stream = out_stream or console.TopologyStylizer() + + # if model is empty, no need to print out the section name + if model and title: + out_stream.write('{0}:'.format(title)) + + if isinstance(model, dict): + if str(out_stream): + with out_stream.indent(): + return self.dump(model.values(), out_stream=out_stream, **kwargs) + else: + return self.dump(model.values(), out_stream=out_stream, **kwargs) + + elif isinstance(model, list): + for value in model: + self.dump(value, out_stream=out_stream, **kwargs) + + elif model is not None: + _handler = self._model_cls_to_handler[model.__class__] + _handler(self, model).dump(out_stream=out_stream, **kwargs) + + return out_stream + + def dump_graph(self, service): + out_stream = console.TopologyStylizer() + for node in service.nodes.itervalues(): + if not node.inbound_relationships: + self._dump_graph_node(out_stream, node) + return out_stream + + def _dump_graph_node(self, out_stream, node, capability=None): + out_stream.write(out_stream.node_style(node.name)) + if capability is not None: + out_stream.write('{0} ({1})'.format(out_stream.property_style(capability.name), + out_stream.type_style(capability.type.name))) + if node.outbound_relationships: + with out_stream.indent(): + for relationship_model in node.outbound_relationships: + styled_relationship_name = out_stream.property_style(relationship_model.name) + if relationship_model.type is not None: + out_stream.write('-> {0} ({1})'.format( + styled_relationship_name, + out_stream.type_style(relationship_model.type.name))) + else: + out_stream.write('-> {0}'.format(styled_relationship_name)) + with out_stream.indent(3): + self._dump_graph_node(out_stream, + relationship_model.target_node, + relationship_model.target_capability) + + def coerce(self, model, **kwargs): + if isinstance(model, dict): + return self.coerce(model.values(), **kwargs) + elif isinstance(model, list): + return all(self.coerce(value, **kwargs) for value in model) + elif model is not None: + _handler = self._model_cls_to_handler[model.__class__] + return _handler(self, model).coerce(**kwargs) + + def dump_types(self, service_template, out_stream=None): + out_stream = out_stream or console.TopologyStylizer() + self.dump(service_template.node_types, out_stream, 'Node types') + self.dump(service_template.group_types, out_stream, 'Group types') + self.dump(service_template.capability_types, out_stream, 'Capability types') + self.dump(service_template.relationship_types, out_stream, 'Relationship types') + self.dump(service_template.policy_types, out_stream, 'Policy types') + self.dump(service_template.artifact_types, out_stream, 'Artifact types') + self.dump(service_template.interface_types, out_stream, 'Interface types') + + return out_stream + + def satisfy_requirements(self, model, **kwargs): + if isinstance(model, dict): + return self.satisfy_requirements(model.values(), **kwargs) + elif isinstance(model, list): + return all(self.satisfy_requirements(value, **kwargs) for value in model) + elif model is not None: + _handler = self._model_cls_to_handler[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(), **kwargs) + elif isinstance(model, list): + return all(self.validate_capabilities(value, **kwargs) for value in model) + elif model is not None: + _handler = self._model_cls_to_handler[model.__class__] + return _handler(self, model).validate_capabilities(**kwargs) + + def _find_host(self, node): + if node.type.role == 'host': + return node + + def target_has_role(rel, role): + return (rel.target_capability is not None and + rel.target_capability.type.role == role) + + for outbound_relationship in node.outbound_relationships: + if target_has_role(outbound_relationship, 'host'): + host = self._find_host(outbound_relationship.target_node) + if host is not None: + return host + for inbound_relationship in node.inbound_relationships: + if target_has_role(inbound_relationship, 'feature'): + host = self._find_host(inbound_relationship.source_node) + if host is not None: + return host + return None + + def assign_hosts(self, service): + for node in service.nodes.values(): + node.host = self._find_host(node) + + def configure_operations(self, model, **kwargs): + if isinstance(model, dict): + return self.configure_operations(model.values(), **kwargs) + elif isinstance(model, list): + return all(self.configure_operations(value, **kwargs) for value in model) + elif model is not None: + _handler = self._model_cls_to_handler[model.__class__] + return _handler(self, model).configure_operations(**kwargs) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/aria/orchestrator/topology/utils.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/topology/utils.py b/aria/orchestrator/topology/utils.py new file mode 100644 index 0000000..ec74391 --- /dev/null +++ b/aria/orchestrator/topology/utils.py @@ -0,0 +1,48 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from copy import deepcopy + + +def deepcopy_with_locators(value): + """ + Like :func:`deepcopy`, but also copies over locators. + """ + + res = deepcopy(value) + copy_locators(res, value) + return res + + +def copy_locators(target, source): + """ + Copies over ``_locator`` for all elements, recursively. + + Assumes that target and source have exactly the same list/dict structure. + """ + + locator = getattr(source, '_locator', None) + if locator is not None: + try: + setattr(target, '_locator', locator) + except AttributeError: + pass + + if isinstance(target, list) and isinstance(source, list): + for i, _ in enumerate(target): + copy_locators(target[i], source[i]) + elif isinstance(target, dict) and isinstance(source, dict): + for k, v in target.items(): + copy_locators(v, source[k]) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/aria/orchestrator/workflow_runner.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/workflow_runner.py b/aria/orchestrator/workflow_runner.py index 0eac61c..4dbf29b 100644 --- a/aria/orchestrator/workflow_runner.py +++ b/aria/orchestrator/workflow_runner.py @@ -141,9 +141,8 @@ class WorkflowRunner(object): supplied_inputs=inputs or {}) modeling_utils.validate_required_inputs_are_supplied(declared_inputs=workflow_inputs, supplied_inputs=inputs or {}) - execution.inputs = modeling_utils.merge_parameter_values(inputs, - workflow_inputs, - model_cls=models.Input) + execution.inputs = modeling_utils.merge_parameter_values( + inputs, workflow_inputs, model_cls=models.Input) # TODO: these two following calls should execute atomically self._validate_no_active_executions(execution) self._model_storage.execution.put(execution) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/aria/orchestrator/workflows/api/task.py ---------------------------------------------------------------------- diff --git a/aria/orchestrator/workflows/api/task.py b/aria/orchestrator/workflows/api/task.py index ec96d27..6ce4a00 100644 --- a/aria/orchestrator/workflows/api/task.py +++ b/aria/orchestrator/workflows/api/task.py @@ -137,14 +137,13 @@ class OperationTask(BaseTask): operation = self.actor.interfaces[self.interface_name].operations[self.operation_name] self.plugin = operation.plugin self.function = operation.function - self.arguments = modeling_utils.merge_parameter_values(arguments, - operation.arguments, - model_cls=models.Argument) + self.arguments = modeling_utils.merge_parameter_values(arguments, operation.arguments) actor = self.actor if hasattr(actor, '_wrapped'): # Unwrap instrumented model actor = actor._wrapped + if isinstance(actor, models.Node): self._context_cls = context.operation.NodeOperationContext elif isinstance(actor, models.Relationship): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/aria/parser/consumption/__init__.py ---------------------------------------------------------------------- diff --git a/aria/parser/consumption/__init__.py b/aria/parser/consumption/__init__.py index bd4b29c..f9caf5f 100644 --- a/aria/parser/consumption/__init__.py +++ b/aria/parser/consumption/__init__.py @@ -20,7 +20,6 @@ Consumption package. :nosignatures: aria.parser.consumption.ConsumptionContext - aria.parser.consumption.Style Consumers --------- @@ -47,7 +46,6 @@ Consumers from .exceptions import ConsumerException from .context import ConsumptionContext -from .style import Style from .consumer import ( Consumer, ConsumerChain @@ -70,7 +68,6 @@ from .inputs import Inputs __all__ = ( 'ConsumerException', 'ConsumptionContext', - 'Style', 'Consumer', 'ConsumerChain', 'Read', http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/aria/parser/consumption/consumer.py ---------------------------------------------------------------------- diff --git a/aria/parser/consumption/consumer.py b/aria/parser/consumption/consumer.py index 4f4c614..878a161 100644 --- a/aria/parser/consumption/consumer.py +++ b/aria/parser/consumption/consumer.py @@ -27,6 +27,9 @@ class Consumer(object): """ def __init__(self, context): + from ...orchestrator import topology + + self.topology = topology.Topology() self.context = context def consume(self): @@ -73,6 +76,10 @@ class ConsumerChain(Consumer): handle_exception(consumer, e) else: raise e + + if consumer.topology.has_issues: + self.context.validation.extend_issues(consumer.topology.issues) + if self.context.validation.has_issues: break http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/aria/parser/consumption/context.py ---------------------------------------------------------------------- diff --git a/aria/parser/consumption/context.py b/aria/parser/consumption/context.py index 6fa61f4..9164984 100644 --- a/aria/parser/consumption/context.py +++ b/aria/parser/consumption/context.py @@ -16,12 +16,12 @@ import sys import threading +from ...utils import console from ..validation import ValidationContext from ..loading import LoadingContext from ..reading import ReadingContext from ..presentation import PresentationContext from ..modeling import ModelingContext -from .style import Style _thread_locals = threading.local() @@ -58,12 +58,12 @@ class ConsumptionContext(object): def __init__(self, set_thread_local=True): self.args = [] self.out = sys.stdout - self.style = Style() self.validation = ValidationContext() self.loading = LoadingContext() self.reading = ReadingContext() self.presentation = PresentationContext() self.modeling = ModelingContext() + self.style = console.TopologyStylizer() if set_thread_local: self.set_thread_local() http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/aria/parser/consumption/modeling.py ---------------------------------------------------------------------- diff --git a/aria/parser/consumption/modeling.py b/aria/parser/consumption/modeling.py index 44027b9..221b308 100644 --- a/aria/parser/consumption/modeling.py +++ b/aria/parser/consumption/modeling.py @@ -13,8 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ...utils.formatting import json_dumps, yaml_dumps from .consumer import Consumer, ConsumerChain +from ...utils.formatting import json_dumps, yaml_dumps class DeriveServiceTemplate(Consumer): @@ -42,7 +42,7 @@ class CoerceServiceTemplateValues(Consumer): """ def consume(self): - self.context.modeling.template.coerce_values(True) + self.topology.coerce(self.context.modeling.template, report_issues=True) class ValidateServiceTemplate(Consumer): @@ -51,7 +51,7 @@ class ValidateServiceTemplate(Consumer): """ def consume(self): - self.context.modeling.template.validate() + self.topology.validate(self.context.modeling.template) class ServiceTemplate(ConsumerChain): @@ -74,7 +74,7 @@ class ServiceTemplate(ConsumerChain): raw = self.context.modeling.template_as_raw self.context.write(json_dumps(raw, indent=indent)) else: - self.context.modeling.template.dump() + self.context.write(self.topology.dump(self.context.modeling.template)) class Types(Consumer): @@ -92,7 +92,7 @@ class Types(Consumer): raw = self.context.modeling.types_as_raw self.context.write(json_dumps(raw, indent=indent)) else: - self.context.modeling.template.dump_types() + self.topology.dump_types(self.context, self.context.modeling.template) class InstantiateServiceInstance(Consumer): @@ -105,9 +105,10 @@ class InstantiateServiceInstance(Consumer): self.context.validation.report('InstantiateServiceInstance consumer: missing service ' 'template') return - - self.context.modeling.template.instantiate(None, None, - inputs=dict(self.context.modeling.inputs)) + self.context.modeling.instance = self.topology.instantiate( + self.context.modeling.template, + inputs=dict(self.context.modeling.inputs) + ) class CoerceServiceInstanceValues(Consumer): @@ -116,7 +117,7 @@ class CoerceServiceInstanceValues(Consumer): """ def consume(self): - self.context.modeling.instance.coerce_values(True) + self.topology.coerce(self.context.modeling.instance, report_issues=True) class ValidateServiceInstance(Consumer): @@ -125,7 +126,7 @@ class ValidateServiceInstance(Consumer): """ def consume(self): - self.context.modeling.instance.validate() + self.topology.validate(self.context.modeling.instance) class SatisfyRequirements(Consumer): @@ -134,7 +135,7 @@ class SatisfyRequirements(Consumer): """ def consume(self): - self.context.modeling.instance.satisfy_requirements() + self.topology.satisfy_requirements(self.context.modeling.instance) class ValidateCapabilities(Consumer): @@ -143,7 +144,7 @@ class ValidateCapabilities(Consumer): """ def consume(self): - self.context.modeling.instance.validate_capabilities() + self.topology.validate_capabilities(self.context.modeling.instance) class FindHosts(Consumer): @@ -152,7 +153,7 @@ class FindHosts(Consumer): """ def consume(self): - self.context.modeling.instance.find_hosts() + self.topology.assign_hosts(self.context.modeling.instance) class ConfigureOperations(Consumer): @@ -161,7 +162,7 @@ class ConfigureOperations(Consumer): """ def consume(self): - self.context.modeling.instance.configure_operations() + self.topology.configure_operations(self.context.modeling.instance) class ServiceInstance(ConsumerChain): @@ -193,4 +194,5 @@ class ServiceInstance(ConsumerChain): raw = self.context.modeling.instance_as_raw self.context.write(json_dumps(raw, indent=indent)) else: - self.context.modeling.instance.dump() + str_rep = self.topology.dump(self.context.modeling.instance) + self.context.write(str_rep) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/aria/parser/consumption/style.py ---------------------------------------------------------------------- diff --git a/aria/parser/consumption/style.py b/aria/parser/consumption/style.py deleted file mode 100644 index 1b52cd3..0000000 --- a/aria/parser/consumption/style.py +++ /dev/null @@ -1,54 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -from ...utils.console import Colored, indent -from ...utils.formatting import safe_repr - - -class Style(object): - def __init__(self, indentation=2): - self.indentation = indentation - - @property - def indent(self): - return indent(self.indentation) - - @staticmethod - def section(value): - return Colored.cyan(value, bold=True) - - @staticmethod - def type(value): - return Colored.blue(value, bold=True) - - @staticmethod - def node(value): - return Colored.red(value, bold=True) - - @staticmethod - def property(value): - return Colored.magenta(value, bold=True) - - @staticmethod - def literal(value): - return Colored.magenta(safe_repr(value)) - - @staticmethod - def meta(value): - return Colored.green(value) - - @staticmethod - def required(value): - return Colored.white(value) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/aria/parser/modeling/context.py ---------------------------------------------------------------------- diff --git a/aria/parser/modeling/context.py b/aria/parser/modeling/context.py index 3d75617..d8a1f7a 100644 --- a/aria/parser/modeling/context.py +++ b/aria/parser/modeling/context.py @@ -19,6 +19,10 @@ from ...utils.collections import StrictDict, prune from ...utils.uuid import generate_uuid +# See: http://www.faqs.org/rfcs/rfc1035.html +ID_MAX_LENGTH = 63 + + class IdType(object): LOCAL_SERIAL = 0 """ @@ -61,7 +65,7 @@ class ModelingContext(object): #self.id_type = IdType.LOCAL_SERIAL #self.id_type = IdType.LOCAL_RANDOM self.id_type = IdType.UNIVERSAL_RANDOM - self.id_max_length = 63 # See: http://www.faqs.org/rfcs/rfc1035.html + self.id_max_length = ID_MAX_LENGTH self.inputs = StrictDict(key_class=basestring) self._serial_id_counter = itertools.count(1) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/aria/parser/presentation/fields.py ---------------------------------------------------------------------- diff --git a/aria/parser/presentation/fields.py b/aria/parser/presentation/fields.py index 959bad1..5c08d4a 100644 --- a/aria/parser/presentation/fields.py +++ b/aria/parser/presentation/fields.py @@ -571,7 +571,7 @@ class Field(object): def _dump_primitive(self, context, value): if hasattr(value, 'as_raw'): value = as_raw(value) - puts('%s: %s' % (self.name, context.style.literal(value))) + puts('%s: %s' % (self.name, context.style.literal_style(value))) # primitive list @@ -606,11 +606,11 @@ class Field(object): def _dump_primitive_list(self, context, value): puts('%s:' % self.name) - with context.style.indent: + with context.style.indent(): for primitive in value: if hasattr(primitive, 'as_raw'): primitive = as_raw(primitive) - puts(context.style.literal(primitive)) + puts(context.style.literal_style(primitive)) # primitive dict @@ -635,11 +635,11 @@ class Field(object): def _dump_primitive_dict(self, context, value): puts('%s:' % self.name) - with context.style.indent: + with context.style.indent(): for v in value.itervalues(): if hasattr(v, 'as_raw'): v = as_raw(v) - puts(context.style.literal(v)) + puts(context.style.literal_style(v)) # object @@ -654,7 +654,7 @@ class Field(object): def _dump_object(self, context, value): puts('%s:' % self.name) - with context.style.indent: + with context.style.indent(): if hasattr(value, '_dump'): value._dump(context) @@ -669,7 +669,7 @@ class Field(object): def _dump_object_list(self, context, value): puts('%s:' % self.name) - with context.style.indent: + with context.style.indent(): for v in value: if hasattr(v, '_dump'): v._dump(context) @@ -685,7 +685,7 @@ class Field(object): def _dump_object_dict(self, context, value): puts('%s:' % self.name) - with context.style.indent: + with context.style.indent(): for v in value.itervalues(): if hasattr(v, '_dump'): v._dump(context) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/aria/parser/presentation/presentation.py ---------------------------------------------------------------------- diff --git a/aria/parser/presentation/presentation.py b/aria/parser/presentation/presentation.py index fb71a1e..3f9f86d 100644 --- a/aria/parser/presentation/presentation.py +++ b/aria/parser/presentation/presentation.py @@ -37,13 +37,13 @@ class Value(object): def _dump(self, context): if self.type is not None: - puts(context.style.type(self.type)) + puts(context.style.type_style(self.type)) if self.value is not None: - puts(context.style.literal(self.value)) + puts(context.style.literal_style(self.value)) if self.description is not None: - puts(context.style.meta(self.description)) + puts(context.style.meta_style(self.description)) if self.required is not None: - puts(context.style.required(self.required)) + puts(context.style.required_style(self.required)) class PresentationBase(HasCachedMethods): @@ -142,7 +142,7 @@ class PresentationBase(HasCachedMethods): if self._name: puts(context.style.node(self._name)) - with context.style.indent: + with context.style.indent(): self._dump_content(context) else: self._dump_content(context) @@ -162,7 +162,7 @@ class PresentationBase(HasCachedMethods): for field_name in self._iter_field_names(): # pylint: disable=no-member self._dump_field(context, field_name) else: - puts(context.style.literal(self._raw)) + puts(context.style.literal_style(self._raw)) def _dump_field(self, context, field_name): """ @@ -242,7 +242,7 @@ class AsIsPresentation(PresentationBase): def _dump(self, context): if hasattr(self._raw, '_dump'): puts(context.style.node(self._name)) - with context.style.indent: + with context.style.indent(): self._raw._dump(context) else: super(AsIsPresentation, self)._dump(context) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/aria/parser/reading/__init__.py ---------------------------------------------------------------------- diff --git a/aria/parser/reading/__init__.py b/aria/parser/reading/__init__.py index 065ca56..c110585 100644 --- a/aria/parser/reading/__init__.py +++ b/aria/parser/reading/__init__.py @@ -24,8 +24,6 @@ Reading package. JinjaReader JsonReader Locator - deepcopy_with_locators - copy_locators RawReader Reader ReaderSource @@ -36,7 +34,7 @@ Reading package. from .raw import RawReader from .reader import Reader from .yaml import YamlReader -from .locator import (Locator, deepcopy_with_locators, copy_locators) +from .locator import Locator from .json import JsonReader from .jinja import JinjaReader from .context import ReadingContext @@ -57,8 +55,6 @@ __all__ = ( 'ReadingContext', 'RawReader', 'Locator', - 'deepcopy_with_locators', - 'copy_locators', 'YamlReader', 'JsonReader', 'JinjaReader') http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/aria/parser/reading/locator.py ---------------------------------------------------------------------- diff --git a/aria/parser/reading/locator.py b/aria/parser/reading/locator.py index 965164d..57b4d50 100644 --- a/aria/parser/reading/locator.py +++ b/aria/parser/reading/locator.py @@ -10,9 +10,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -from copy import deepcopy - - from ...utils.console import puts, Colored, indent @@ -120,35 +117,3 @@ class Locator(object): def __str__(self): # Should be in same format as Issue.locator_as_str return '"%s":%d:%d' % (self.location, self.line, self.column) - - -def deepcopy_with_locators(value): - """ - Like :func:`deepcopy`, but also copies over locators. - """ - - res = deepcopy(value) - copy_locators(res, value) - return res - - -def copy_locators(target, source): - """ - Copies over ``_locator`` for all elements, recursively. - - Assumes that target and source have exactly the same list/dict structure. - """ - - locator = getattr(source, '_locator', None) - if locator is not None: - try: - setattr(target, '_locator', locator) - except AttributeError: - pass - - if isinstance(target, list) and isinstance(source, list): - for i, _ in enumerate(target): - copy_locators(target[i], source[i]) - elif isinstance(target, dict) and isinstance(source, dict): - for k, v in target.items(): - copy_locators(v, source[k]) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/aria/parser/validation/context.py ---------------------------------------------------------------------- diff --git a/aria/parser/validation/context.py b/aria/parser/validation/context.py index ef641bd..da9eef6 100644 --- a/aria/parser/validation/context.py +++ b/aria/parser/validation/context.py @@ -13,15 +13,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from .issue import Issue -from ...utils.threading import LockedList -from ...utils.collections import FrozenList -from ...utils.exceptions import print_exception -from ...utils.console import puts, Colored, indent -from ...utils.formatting import as_raw +from . import issue -class ValidationContext(object): +class ValidationContext(issue.ReporterMixin): """ Validation context. @@ -35,53 +30,7 @@ class ValidationContext(object): :vartype max_level: int """ - def __init__(self): + def __init__(self, *args, **kwargs): + super(ValidationContext, self).__init__(*args, **kwargs) self.allow_unknown_fields = False self.allow_primitive_coersion = False - self.max_level = Issue.ALL - - self._issues = LockedList() - - def report(self, message=None, exception=None, location=None, line=None, - column=None, locator=None, snippet=None, level=Issue.PLATFORM, issue=None): - if issue is None: - issue = Issue(message, exception, location, line, column, locator, snippet, level) - - # Avoid duplicate issues - with self._issues: - for i in self._issues: - if str(i) == str(issue): - return - - self._issues.append(issue) - - @property - def has_issues(self): - return len(self._issues) > 0 - - @property - def issues(self): - issues = [i for i in self._issues if i.level <= self.max_level] - issues.sort(key=lambda i: (i.level, i.location, i.line, i.column, i.message)) - return FrozenList(issues) - - @property - def issues_as_raw(self): - return [as_raw(i) for i in self.issues] - - def dump_issues(self): - issues = self.issues - if issues: - puts(Colored.blue('Validation issues:', bold=True)) - with indent(2): - for issue in issues: - puts(Colored.blue(issue.heading_as_str)) - details = issue.details_as_str - if details: - with indent(3): - puts(details) - if issue.exception is not None: - with indent(3): - print_exception(issue.exception) - return True - return False http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/aria/parser/validation/issue.py ---------------------------------------------------------------------- diff --git a/aria/parser/validation/issue.py b/aria/parser/validation/issue.py index db8065d..42fc580 100644 --- a/aria/parser/validation/issue.py +++ b/aria/parser/validation/issue.py @@ -15,8 +15,14 @@ from __future__ import absolute_import # so we can import standard 'collections' -from ...utils.collections import OrderedDict -from ...utils.type import full_type_name +from ...utils import ( + collections, + type, + threading, + exceptions, + console, + formatting +) class Issue(object): @@ -82,14 +88,14 @@ class Issue(object): @property def as_raw(self): - return OrderedDict(( + return collections.OrderedDict(( ('level', self.level), ('message', self.message), ('location', self.location), ('line', self.line), ('column', self.column), ('snippet', self.snippet), - ('exception', full_type_name(self.exception) if self.exception else None))) + ('exception', type.full_type_name(self.exception) if self.exception else None))) @property def locator_as_str(self): @@ -124,3 +130,61 @@ class Issue(object): if details: heading_str += ', ' + details return heading_str + + +class ReporterMixin(object): + + Issue = Issue + + def __init__(self, *args, **kwargs): + super(ReporterMixin, self).__init__(*args, **kwargs) + self._issues = threading.LockedList() + self.max_level = self.Issue.ALL + + def report(self, message=None, exception=None, location=None, line=None, + column=None, locator=None, snippet=None, level=Issue.PLATFORM, issue=None): + if issue is None: + issue = self.Issue(message, exception, location, line, column, locator, snippet, level) + + # Avoid duplicate issues + with self._issues: + for i in self._issues: + if str(i) == str(issue): + return + + self._issues.append(issue) + + @property + def has_issues(self): + return len(self._issues) > 0 + + @property + def issues(self): + issues = [i for i in self._issues if i.level <= self.max_level] + issues.sort(key=lambda i: (i.level, i.location, i.line, i.column, i.message)) + return collections.FrozenList(issues) + + @property + def issues_as_raw(self): + return [formatting.as_raw(i) for i in self.issues] + + def extend_issues(self, *issues): + with self._issues: + self._issues.extend(*issues) + + def dump_issues(self): + issues = self.issues + if issues: + console.puts(console.Colored.blue('Validation issues:', bold=True)) + with console.indent(2): + for issue in issues: + console.puts(console.Colored.blue(issue.heading_as_str)) + details = issue.details_as_str + if details: + with console.indent(3): + console.puts(details) + if issue.exception is not None: + with console.indent(3): + exceptions.print_exception(issue.exception) + return True + return False http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/aria/utils/__init__.py ---------------------------------------------------------------------- diff --git a/aria/utils/__init__.py b/aria/utils/__init__.py index 2a957a9..43dd882 100644 --- a/aria/utils/__init__.py +++ b/aria/utils/__init__.py @@ -16,3 +16,50 @@ """ General-purpose utilities package. """ + +from . import ( + archive, + argparse, + caching, + collections, + console, + exceptions, + file, + formatting, + http, + imports, + openclose, + plugin, + process, + specification, + threading, + type, + uris, + uuid, + validation, + versions +) + + +__all__ = ( + 'archive', + 'argparse', + 'caching', + 'collections', + 'console', + 'exceptions', + 'file', + 'formatting', + 'http', + 'imports', + 'openclose', + 'plugin', + 'process', + 'specification', + 'threading', + 'type', + 'uris', + 'uuid', + 'validation', + 'versions' +) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/aria/utils/console.py ---------------------------------------------------------------------- diff --git a/aria/utils/console.py b/aria/utils/console.py index 2f6f622..81e8cf8 100644 --- a/aria/utils/console.py +++ b/aria/utils/console.py @@ -19,19 +19,64 @@ Abstraction API above terminal color libraries. import os import sys +from StringIO import StringIO from contextlib import contextmanager -from .formatting import safe_str from ..cli import color +from . import formatting _indent_string = '' +class TopologyStylizer(object): + def __init__(self, indentation=0): + self._str = StringIO() + self._indentation = indentation + + def write(self, string): + self._str.write(' ' * self._indentation) + self._str.write(string) + self._str.write(os.linesep) + + @contextmanager + def indent(self, indentation=2): + self._indentation += indentation + yield + self._indentation -= indentation + + @staticmethod + def type_style(value): + return Colored.blue(value, bold=True) + + @staticmethod + def node_style(value): + return Colored.red(value, bold=True) + + @staticmethod + def property_style(value): + return Colored.magenta(value, bold=True) + + @staticmethod + def literal_style(value): + return Colored.magenta(formatting.safe_repr(value)) + + @staticmethod + def required_style(value): + return Colored.white(value) + + @staticmethod + def meta_style(value): + return Colored.green(value) + + def __str__(self): + return self._str.getvalue() + + def puts(string='', newline=True, stream=sys.stdout): stream.write(_indent_string) - stream.write(safe_str(string)) + stream.write(formatting.safe_str(string)) if newline: stream.write(os.linesep) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/extensions/aria_extension_tosca/simple_v1_0/misc.py ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/simple_v1_0/misc.py b/extensions/aria_extension_tosca/simple_v1_0/misc.py index 23beb3c..a65ff41 100644 --- a/extensions/aria_extension_tosca/simple_v1_0/misc.py +++ b/extensions/aria_extension_tosca/simple_v1_0/misc.py @@ -52,7 +52,7 @@ class Description(AsIsPresentation): def _dump(self, context): value = as_raw(self.value) - puts(context.style.meta(value)) + puts(context.style.meta_style(value)) @allow_unknown_fields http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py ---------------------------------------------------------------------- diff --git a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py index 2620150..1f90d29 100644 --- a/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py +++ b/extensions/aria_extension_tosca/simple_v1_0/modeling/__init__.py @@ -497,8 +497,11 @@ def create_plugin_specification_model(context, policy): def create_workflow_operation_template_model(context, service_template, policy): - model = OperationTemplate(name=policy._name, - service_template=service_template) + model = OperationTemplate(name=policy._name) + # since we use backpopulates, these fields are populated upon commit, we get a weird(temporary) + # behavior where in previous code service_template.workflow_templates is a dict which has None + # as key for the value of model. + service_template.workflow_templates[model.name] = model if policy.description: model.description = policy.description.value @@ -606,7 +609,7 @@ def create_parameter_model_from_value(template_parameter, template_parameter_nam def create_parameter_models_from_assignments(properties, source_properties, model_cls): if source_properties: for property_name, prop in source_properties.iteritems(): - properties[property_name] = model_cls(name=property_name, # pylint: disable=unexpected-keyword-arg + properties[property_name] = model_cls(name=property_name, # pylint: disable=unexpected-keyword-arg type_name=prop.value.type, value=prop.value.value, description=prop.value.description) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/tests/parser/service_templates.py ---------------------------------------------------------------------- diff --git a/tests/parser/service_templates.py b/tests/parser/service_templates.py index 55cd4ad..9e8fcae 100644 --- a/tests/parser/service_templates.py +++ b/tests/parser/service_templates.py @@ -47,6 +47,7 @@ def consume_use_case(use_case_name, consumer_class_name='instance', cache=True): assert not context.validation.has_issues return context, dumper + def consume_types_use_case(use_case_name, consumer_class_name='instance', cache=True): cachedmethod.ENABLED = cache uri = get_service_template_uri('tosca-simple-1.0', 'types', use_case_name, @@ -61,12 +62,23 @@ def consume_types_use_case(use_case_name, consumer_class_name='instance', cache= assert not context.validation.has_issues return context, dumper + def consume_node_cellar(consumer_class_name='instance', cache=True): + consume_test_case( + get_service_template_uri('tosca-simple-1.0', 'node-cellar', 'node-cellar.yaml'), + consumer_class_name=consumer_class_name, + inputs_uri=get_service_template_uri('tosca-simple-1.0', 'node-cellar', 'inputs.yaml'), + cache=cache + + ) + + +def consume_test_case(uri, inputs_uri=None, consumer_class_name='instance', cache=True): cachedmethod.ENABLED = cache - uri = get_service_template_uri('tosca-simple-1.0', 'node-cellar', 'node-cellar.yaml') + uri = get_service_template_uri(uri) context = create_context(uri) - context.args.append('--inputs=' + get_service_template_uri('tosca-simple-1.0', 'node-cellar', - 'inputs.yaml')) + if inputs_uri: + context.args.append('--inputs=' + get_service_template_uri(inputs_uri)) consumer, dumper = create_consumer(context, consumer_class_name) consumer.consume() context.validation.dump_issues() http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/tests/parser/test_reqs_caps.py ---------------------------------------------------------------------- diff --git a/tests/parser/test_reqs_caps.py b/tests/parser/test_reqs_caps.py new file mode 100644 index 0000000..e92aec4 --- /dev/null +++ b/tests/parser/test_reqs_caps.py @@ -0,0 +1,29 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from .service_templates import consume_test_case +from ..helpers import get_service_template_uri + + +def test_satisfy_capability_type(): + consume_reqs_caps_template1('instance') + + +def consume_reqs_caps_template1(consumer_class_name, cache=True): + consume_test_case( + get_service_template_uri('tosca-simple-1.0', 'reqs_caps', 'reqs_caps1.yaml'), + consumer_class_name=consumer_class_name, + cache=cache + ) http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/tests/resources/service-templates/tosca-simple-1.0/reqs_caps/reqs_caps1.yaml ---------------------------------------------------------------------- diff --git a/tests/resources/service-templates/tosca-simple-1.0/reqs_caps/reqs_caps1.yaml b/tests/resources/service-templates/tosca-simple-1.0/reqs_caps/reqs_caps1.yaml new file mode 100644 index 0000000..466a78e --- /dev/null +++ b/tests/resources/service-templates/tosca-simple-1.0/reqs_caps/reqs_caps1.yaml @@ -0,0 +1,40 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +tosca_definitions_version: tosca_simple_yaml_1_0 + +capability_types: + Socket: + derived_from: tosca.capabilities.Root + +node_types: + Socket: + derived_from: tosca.nodes.Root + capabilities: + socket: Socket + + Plug: + derived_from: tosca.nodes.Root + requirements: + - plug: + capability: Socket + +topology_template: + node_templates: + socket: + type: Socket + + plug: + type: Plug \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/df2b916e/tests/storage/__init__.py ---------------------------------------------------------------------- diff --git a/tests/storage/__init__.py b/tests/storage/__init__.py index 8ca1480..8a4d613 100644 --- a/tests/storage/__init__.py +++ b/tests/storage/__init__.py @@ -23,8 +23,6 @@ from sqlalchemy import ( MetaData ) -from aria.modeling import models - class TestFileSystem(object):