Repository: incubator-ariatosca Updated Branches: refs/heads/ARIA-44-Merge-parser-and-storage-models 23f6c030a -> 05b1cab24
First Phase: Artifact/Template, Operation/Template, Capability/Template, Node/Template Project: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/commit/09f1f737 Tree: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/tree/09f1f737 Diff: http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/diff/09f1f737 Branch: refs/heads/ARIA-44-Merge-parser-and-storage-models Commit: 09f1f737cd051122604198ad7bf047793fbc5c91 Parents: 23f6c03 Author: mxmrlv <mxm...@gmail.com> Authored: Thu Jan 19 14:54:31 2017 +0200 Committer: mxmrlv <mxm...@gmail.com> Committed: Thu Jan 19 14:54:31 2017 +0200 ---------------------------------------------------------------------- aria/modeling/elements.py | 29 ++- aria/modeling/instance_elements.py | 419 +++++++++++++++++++++++++++++++- aria/modeling/model_elements.py | 363 ++++++++++++++++++++++++++- aria/storage/type.py | 9 +- 4 files changed, 799 insertions(+), 21 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/09f1f737/aria/modeling/elements.py ---------------------------------------------------------------------- diff --git a/aria/modeling/elements.py b/aria/modeling/elements.py index 62bc7b8..465b0c0 100644 --- a/aria/modeling/elements.py +++ b/aria/modeling/elements.py @@ -13,10 +13,17 @@ # See the License for the specific language governing permissions and # limitations under the License. -from ...utils.collections import StrictDict, OrderedDict -from ...utils.console import puts +from sqlalchemy import ( + Column, + Text +) + +from ..utils.collections import OrderedDict +from ..utils.console import puts from .utils import coerce_value +from ..storage import structure, type + class Function(object): """ @@ -71,17 +78,18 @@ class ModelElement(Element): raise NotImplementedError -class Parameter(ModelElement): +class Parameter(ModelElement, structure.ModelMixin): """ Represents a typed value. This class is used by both service model and service instance elements. """ - def __init__(self, type_name, value, description): - self.type_name = type_name - self.value = value - self.description = description + type_name = Column(Text) + value = Column() + description = Column(Text) + # NOTE: there is no need to hold an owner. (prob no backtracking to the owner is needed) + # CHECK: what is the deal with the self instantiation @property def as_raw(self): @@ -98,7 +106,7 @@ class Parameter(ModelElement): self.value = coerce_value(context, container, self.value, report_issues) -class Metadata(ModelElement): +class Metadata(ModelElement, structure.ModelMixin): """ Custom values associated with the deployment template and its plans. @@ -108,9 +116,8 @@ class Metadata(ModelElement): * :code:`values`: Dict of custom values """ - - def __init__(self): - self.values = StrictDict(key_class=basestring) + values = Column(type.StrictDict(key_cls=basestring)) + # CHECK: what is the deal with the self instantiation @property def as_raw(self): http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/09f1f737/aria/modeling/instance_elements.py ---------------------------------------------------------------------- diff --git a/aria/modeling/instance_elements.py b/aria/modeling/instance_elements.py index f0becd4..82d2f7e 100644 --- a/aria/modeling/instance_elements.py +++ b/aria/modeling/instance_elements.py @@ -2,19 +2,21 @@ from sqlalchemy import ( Column, Text, + Integer ) -from . import elements +from . import elements, utils from ..storage import structure, type -from ..utils import collections +from ..utils import collections, formatting, console +from ..parser import validation class ServiceInstance(elements.Element, structure.ModelMixin): description = Column(Text) metadata = Column() - # TODO: is the solution is really to create a list of foreign key - # (which would replace the dict) and filter according to the name etc...? + # NOTE: is the solution is really to create a list of foreign key + # NOTE: (which would replace the dict) and filter according to the name etc...? nodes = Column(type.StrictDict(str, "Node")) groups = Column(type.StrictDict(str, "Group")) policies = Column(type.StrictDict(str, "Policy")) @@ -73,4 +75,411 @@ class ServiceInstance(elements.Element, structure.ModelMixin): if node is not None: if self._is_node_a_target(context, node, target_node): return True - return False \ No newline at end of file + return False + + +class Operation(elements.Element, structure.ModelMixin): + """ + An operation in a :class:`Interface`. + + Properties: + + * :code:`name`: Name + * :code:`description`: Description + * :code:`implementation`: Implementation string (interpreted by the orchestrator) + * :code:`dependencies`: List of strings (interpreted by the orchestrator) + * :code:`executor`: Executor string (interpreted by the orchestrator) + * :code:`max_retries`: Maximum number of retries allowed in case of failure + * :code:`retry_interval`: Interval between retries + * :code:`inputs`: Dict of :class:`Parameter` + """ + + description = Column(Text) + # Check: what's this for? + implementation = Column() + dependencies = Column(type.StrictList(item_cls=basestring)) + executor = Column() + max_retries = Column(Integer, default=None) + retry_interval = Column(Integer, default=None) + inputs = Column(type.StrictDict(key_cls=basestring, value_cls=elements.Parameter)) + + @property + def as_raw(self): + return collections.OrderedDict(( + ('name', self.name), + ('description', self.description), + ('implementation', self.implementation), + ('dependencies', self.dependencies), + ('executor', self.executor), + ('max_retries', self.max_retries), + ('retry_interval', self.retry_interval), + ('inputs', formatting.as_raw_dict(self.inputs)))) + + def validate(self, context): + utils.validate_dict_values(context, self.inputs) + + def coerce_values(self, context, container, report_issues): + utils.coerce_dict_values(context, container, self.inputs, report_issues) + + def dump(self, context): + console.puts(context.style.node(self.name)) + if self.description: + console.puts(context.style.meta(self.description)) + with context.style.indent: + if self.implementation is not None: + console.puts('Implementation: %s' % context.style.literal(self.implementation)) + if self.dependencies: + console.puts('Dependencies: %s' + % ', '.join((str(context.style.literal(v)) for v in self.dependencies))) + if self.executor is not None: + console.puts('Executor: %s' % context.style.literal(self.executor)) + if self.max_retries is not None: + console.puts('Max retries: %s' % context.style.literal(self.max_retries)) + if self.retry_interval is not None: + console.puts('Retry interval: %s' % context.style.literal(self.retry_interval)) + utils.dump_parameters(context, self.inputs, 'Inputs') + + +class Interface(elements.Element, structure.ModelMixin): + """ + A typed set of :class:`Operation`. + + Properties: + + * :code:`name`: Name + * :code:`description`: Description + * :code:`type_name`: Must be represented in the :class:`ModelingContext` + * :code:`inputs`: Dict of :class:`Parameter` + * :code:`operations`: Dict of :class:`Operation` + """ + + description = Column(Text) + type_name = Column(Text) + inputs = Column(type.StrictDict(key_cls=str, value_cls=elements.Parameter)) + operations = Column(type.StrictDict(key_cls=str, value_cls=Operation)) + + @property + def as_raw(self): + return collections.OrderedDict(( + ('name', self.name), + ('description', self.description), + ('type_name', self.type_name), + ('inputs', formatting.as_raw_dict(self.inputs)), + ('operations', formatting.as_raw_list(self.operations)))) + + def validate(self, context): + if self.type_name: + if context.modeling.interface_types.get_descendant(self.type_name) is None: + context.validation.report('interface "%s" has an unknown type: %s' + % (self.name, + formatting.safe_repr(self.type_name)), + level=validation.Issue.BETWEEN_TYPES) + + utils.validate_dict_values(context, self.inputs) + utils.validate_dict_values(context, self.operations) + + def coerce_values(self, context, container, report_issues): + utils.coerce_dict_values(context, container, self.inputs, report_issues) + utils.coerce_dict_values(context, container, self.operations, report_issues) + + def dump(self, context): + console.puts(context.style.node(self.name)) + if self.description: + console.puts(context.style.meta(self.description)) + with context.style.indent: + console.puts('Interface type: %s' % context.style.type(self.type_name)) + utils.dump_parameters(context, self.inputs, 'Inputs') + utils.dump_dict_values(context, self.operations, 'Operations') + + +class Capability(elements.Element, structure.ModelMixin): + """ + A capability of a :class:`Node`. + + An instance of a :class:`CapabilityTemplate`. + + Properties: + + * :code:`name`: Name + * :code:`type_name`: Must be represented in the :class:`ModelingContext` + * :code:`min_occurrences`: Minimum number of requirement matches required + * :code:`max_occurrences`: Maximum number of requirement matches allowed + * :code:`properties`: Dict of :class:`Parameter` + """ + + type_name = Column(Text) + properties = Column(type.StrictDict(basestring, elements.Parameter)) + + min_occurrences = Column(Integer, default=None) # optional + max_occurrences = Column(Integer, default=None) # optional + occurrences = Column(Integer, default=0) + + @property + def has_enough_relationships(self): + if self.min_occurrences is not None: + return self.occurrences >= self.min_occurrences + return True + + def relate(self): + if self.max_occurrences is not None: + if self.occurrences == self.max_occurrences: + return False + self.occurrences += 1 + return True + + @property + def as_raw(self): + return collections.OrderedDict(( + ('name', self.name), + ('type_name', self.type_name), + ('properties', formatting.as_raw_dict(self.properties)))) + + def validate(self, context): + if context.modeling.capability_types.get_descendant(self.type_name) is None: + context.validation.report('capability "%s" has an unknown type: %s' + % (self.name, + formatting.safe_repr(self.type_name)), + level=validation.Issue.BETWEEN_TYPES) + + utils.validate_dict_values(context, self.properties) + + def coerce_values(self, context, container, report_issues): + utils.coerce_dict_values(context, container, self.properties, report_issues) + + def dump(self, context): + console.puts(context.style.node(self.name)) + with context.style.indent: + console.puts('Type: %s' % context.style.type(self.type_name)) + console.puts('Occurrences: %s (%s%s)' + % (self.occurrences, + self.min_occurrences or 0, + (' to %d' % self.max_occurrences) + if self.max_occurrences is not None + else ' or more')) + utils.dump_parameters(context, self.properties) + + +class Artifact(elements.Element, structure.ModelMixin): + """ + A file associated with a :class:`Node`. + + Properties: + + * :code:`name`: Name + * :code:`description`: Description + * :code:`type_name`: Must be represented in the :class:`ModelingContext` + * :code:`source_path`: Source path (CSAR or repository) + * :code:`target_path`: Path at destination machine + * :code:`repository_url`: Repository URL + * :code:`repository_credential`: Dict of string + * :code:`properties`: Dict of :class:`Parameter` + """ + + description = Column(Text) + type_name = Column(Text) + # Check: what's that? + source_path = Column() + target_path = Column() + repository_url = Column(Text) + repository_credential = Column(type.StrictDict(basestring, basestring)) + properties = Column(type.StrictDict(basestring, elements.Parameter)) + + @property + def as_raw(self): + return collections.OrderedDict(( + ('name', self.name), + ('description', self.description), + ('type_name', self.type_name), + ('source_path', self.source_path), + ('target_path', self.target_path), + ('repository_url', self.repository_url), + ('repository_credential', formatting.as_agnostic(self.repository_credential)), + ('properties', formatting.as_raw_dict(self.properties)))) + + def validate(self, context): + if context.modeling.artifact_types.get_descendant(self.type_name) is None: + context.validation.report('artifact "%s" has an unknown type: %s' + % (self.name, + formatting.safe_repr(self.type_name)), + level=validation.Issue.BETWEEN_TYPES) + utils.validate_dict_values(context, self.properties) + + def coerce_values(self, context, container, report_issues): + utils.coerce_dict_values(context, container, self.properties, report_issues) + + def dump(self, context): + console.puts(context.style.node(self.name)) + if self.description: + console.puts(context.style.meta(self.description)) + with context.style.indent: + console.puts('Artifact type: %s' % context.style.type(self.type_name)) + console.puts('Source path: %s' % context.style.literal(self.source_path)) + if self.target_path is not None: + console.puts('Target path: %s' % context.style.literal(self.target_path)) + if self.repository_url is not None: + console.puts('Repository URL: %s' % context.style.literal(self.repository_url)) + if self.repository_credential: + console.puts('Repository credential: %s' + % context.style.literal(self.repository_credential)) + utils.dump_parameters(context, self.properties) + + + +class Node(elements.Element, structure.ModelMixin): + """ + An instance of a :class:`NodeTemplate`. + + Nodes may have zero or more :class:`Relationship` instances to other nodes. + + Properties: + + * :code:`id`: Unique ID (prefixed with the template name) + * :code:`type_name`: Must be represented in the :class:`ModelingContext` + * :code:`template_name`: Must be represented in the :class:`ServiceModel` + * :code:`properties`: Dict of :class:`Parameter` + * :code:`interfaces`: Dict of :class:`Interface` + * :code:`artifacts`: Dict of :class:`Artifact` + * :code:`capabilities`: Dict of :class:`CapabilityTemplate` + * :code:`relationship`: List of :class:`Relationship` + """ + + type_name = Column(Text) + template_name = Column(Text) + properties = Column(type.StrictDict(basestring, elements.Parameter)) + interfaces = Column(type.StrictDict(basestring, Interface)) + artifacts = Column(type.StrictDict(basestring, Artifact)) + capabilities = Column(type.StrictDict(basestring, Capability)) + relationships = Column(type.StrictList(Relationship)) + + def satisfy_requirements(self, context): + node_template = context.modeling.model.node_templates.get(self.template_name) + satisfied = True + for i in range(len(node_template.requirement_templates)): + requirement_template = node_template.requirement_templates[i] + + # Find target template + target_node_template, target_node_capability = \ + requirement_template.find_target(context, node_template) + if target_node_template is not None: + satisfied = self._satisfy_capability(context, + target_node_capability, + target_node_template, + requirement_template, + requirement_template_index=i) + else: + context.validation.report('requirement "%s" of node "%s" has no target node ' + 'template' % (requirement_template.name, + self.id), + level=validation.Issue.BETWEEN_INSTANCES) + satisfied = False + return satisfied + + def _satisfy_capability(self, context, target_node_capability, target_node_template, + requirement_template, requirement_template_index): + # Find target nodes + target_nodes = context.modeling.instance.find_nodes(target_node_template.name) + 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: + target_capability = node.capabilities.get(target_node_capability.name) + if target_capability.relate(): + target_node = node + 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: + relationship = \ + requirement_template.relationship_template.instantiate(context, self) + else: + relationship = Relationship() + relationship.name = requirement_template.name + relationship.source_requirement_index = requirement_template_index + relationship.target_node_id = target_node.id + if target_capability is not None: + relationship.target_capability_name = target_capability.name + self.relationships.append(relationship) + else: + context.validation.report('requirement "%s" of node "%s" targets node ' + 'template "%s" but its instantiated nodes do not ' + 'have enough capacity' + % (requirement_template.name, + self.id, + target_node_template.name), + level=validation.Issue.BETWEEN_INSTANCES) + return False + else: + context.validation.report('requirement "%s" of node "%s" targets node template ' + '"%s" but it has no instantiated nodes' + % (requirement_template.name, + self.id, + target_node_template.name), + level=validation.Issue.BETWEEN_INSTANCES) + return False + + + def validate_capabilities(self, context): + satisfied = False + for capability in self.capabilities.itervalues(): + if not capability.has_enough_relationships: + context.validation.report('capability "%s" of node "%s" requires at least %d ' + 'relationships but has %d' + % (capability.name, + self.id, + capability.min_occurrences, + capability.occurrences), + level=validation.Issue.BETWEEN_INSTANCES) + satisfied = False + return satisfied + + @property + def as_raw(self): + return collections.OrderedDict(( + ('id', self.id), + ('type_name', self.type_name), + ('template_name', self.template_name), + ('properties', formatting.as_raw_dict(self.properties)), + ('interfaces', formatting.as_raw_list(self.interfaces)), + ('artifacts', formatting.as_raw_list(self.artifacts)), + ('capabilities', formatting.as_raw_list(self.capabilities)), + ('relationships', formatting.as_raw_list(self.relationships)))) + + def validate(self, context): + if len(self.id) > context.modeling.id_max_length: + context.validation.report('"%s" has an ID longer than the limit of %d characters: %d' + % (self.id, + context.modeling.id_max_length, + len(self.id)), + level=validation.Issue.BETWEEN_INSTANCES) + + # TODO: validate that node template is of type? + + utils.validate_dict_values(context, self.properties) + utils.validate_dict_values(context, self.interfaces) + utils.validate_dict_values(context, self.artifacts) + utils.validate_dict_values(context, self.capabilities) + utils.validate_list_values(context, self.relationships) + + def coerce_values(self, context, container, report_issues): + utils.coerce_dict_values(context, self, self.properties, report_issues) + utils.coerce_dict_values(context, self, self.interfaces, report_issues) + utils.coerce_dict_values(context, self, self.artifacts, report_issues) + utils.coerce_dict_values(context, self, self.capabilities, report_issues) + utils.coerce_list_values(context, self, self.relationships, report_issues) + + def dump(self, context): + console.puts('Node: %s' % context.style.node(self.id)) + with context.style.indent: + console.puts('Template: %s' % context.style.node(self.template_name)) + console.puts('Type: %s' % context.style.type(self.type_name)) + utils.dump_parameters(context, self.properties) + utils.dump_interfaces(context, self.interfaces) + utils.dump_dict_values(context, self.artifacts, 'Artifacts') + utils.dump_dict_values(context, self.capabilities, 'Capabilities') + utils.dump_list_values(context, self.relationships, 'Relationships') \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/09f1f737/aria/modeling/model_elements.py ---------------------------------------------------------------------- diff --git a/aria/modeling/model_elements.py b/aria/modeling/model_elements.py index ab68fbe..bf478bb 100644 --- a/aria/modeling/model_elements.py +++ b/aria/modeling/model_elements.py @@ -17,6 +17,7 @@ from copy import deepcopy from sqlalchemy import ( Column, Text, + Integer, DateTime ) @@ -24,9 +25,10 @@ from . import elements, instance_elements, utils from ..storage import structure, type from ..utils import collections from ..utils import formatting, console +from ..parser import validation -class ServiceTemplate(elements.ModelElement, structure.ModelMixin): +class ServiceModel(elements.ModelElement, structure.ModelMixin): __tablename__ = 'blueprints' @@ -133,6 +135,364 @@ class ServiceTemplate(elements.ModelElement, structure.ModelMixin): utils.dump_dict_values(context, self.operation_templates, 'Operation templates') +class InterfaceTemplate(elements.ModelElement, structure.ModelMixin): + description = Column(Text) + type_name = Column(Text) + inputs = Column(type.StrictDict(basestring, elements.Parameter)) + operation_templates = Column(type.StrictDict(basestring, OperationTemplate)) + + @property + def as_raw(self): + return collections.OrderedDict(( + ('name', self.name), + ('description', self.description), + ('type_name', self.type_name), + ('inputs', formatting.as_raw_dict(self.properties)), # pylint: disable=no-member + # TODO fix self.properties reference + ('operation_templates', formatting.as_raw_list(self.operation_templates)))) + + def instantiate(self, context, container): + interface = instance_elements.Interface(self.name, self.type_name) + interface.description = deepcopy_with_locators(self.description) + utils.instantiate_dict(context, container, interface.inputs, self.inputs) + utils.instantiate_dict(context, container, interface.operations, self.operation_templates) + return interface + + def validate(self, context): + if self.type_name: + if context.modeling.interface_types.get_descendant(self.type_name) is None: + context.validation.report('interface "%s" has an unknown type: %s' + % (self.name, formatting.safe_repr(self.type_name)), + level=validation.Issue.BETWEEN_TYPES) + + utils.validate_dict_values(context, self.inputs) + utils.validate_dict_values(context, self.operation_templates) + + def coerce_values(self, context, container, report_issues): + utils.coerce_dict_values(context, container, self.inputs, report_issues) + utils.coerce_dict_values(context, container, self.operation_templates, report_issues) + + def dump(self, context): + console.puts(context.style.node(self.name)) + if self.description: + console.puts(context.style.meta(self.description)) + with context.style.indent: + console.puts('Interface type: %s' % context.style.type(self.type_name)) + dump_parameters(context, self.inputs, 'Inputs') + utils.dump_dict_values(context, self.operation_templates, 'Operation templates') + + +class OperationTemplate(elements.ModelElement, structure.ModelMixin): + + description = Column(Text) + implementation = Column() + dependencies = Column(type.StrictList(item_cls=basestring)) + executor = Column() + max_retries = Column(Integer) + retry_interval = Column(Integer) + inputs = Column(type.StrictDict(key_cls=basestring, value_cls=elements.Parameter)) + + @property + def as_raw(self): + return collections.OrderedDict(( + ('name', self.name), + ('description', self.description), + ('implementation', self.implementation), + ('dependencies', self.dependencies), + ('executor', self.executor), + ('max_retries', self.max_retries), + ('retry_interval', self.retry_interval), + ('inputs', formatting.as_raw_dict(self.inputs)))) + + def instantiate(self, context, container): + operation = instance_elements.Operation(self.name) + operation.description = deepcopy_with_locators(self.description) + operation.implementation = self.implementation + operation.dependencies = self.dependencies + operation.executor = self.executor + operation.max_retries = self.max_retries + operation.retry_interval = self.retry_interval + utils.instantiate_dict(context, container, operation.inputs, self.inputs) + return operation + + def validate(self, context): + utils.validate_dict_values(context, self.inputs) + + def coerce_values(self, context, container, report_issues): + utils.coerce_dict_values(context, container, self.inputs, report_issues) + + def dump(self, context): + console.puts(context.style.node(self.name)) + if self.description: + console.puts(context.style.meta(self.description)) + with context.style.indent: + if self.implementation is not None: + console.puts('Implementation: %s' % context.style.literal(self.implementation)) + if self.dependencies: + console.puts('Dependencies: %s' % ', '.join( + (str(context.style.literal(v)) for v in self.dependencies))) + if self.executor is not None: + console.puts('Executor: %s' % context.style.literal(self.executor)) + if self.max_retries is not None: + console.puts('Max retries: %s' % context.style.literal(self.max_retries)) + if self.retry_interval is not None: + console.puts('Retry interval: %s' % context.style.literal(self.retry_interval)) + dump_parameters(context, self.inputs, 'Inputs') + + +class ArtifactTemplate(elements.ModelElement, structure.ModelMixin): + """ + A file associated with a :class:`NodeTemplate`. + + Properties: + + * :code:`name`: Name + * :code:`description`: Description + * :code:`type_name`: Must be represented in the :class:`ModelingContext` + * :code:`source_path`: Source path (CSAR or repository) + * :code:`target_path`: Path at destination machine + * :code:`repository_url`: Repository URL + * :code:`repository_credential`: Dict of string + * :code:`properties`: Dict of :class:`Parameter` + """ + + description = Column(Text) + type_name = Column(Text) + source_path = Column(Text) + # Check: what the hell is target path? + target_path = Column() + repository_url = Column(Text) + repository_credential = Column(type.StrictDict(basestring, basestring)) + properties = Column(type.StrictDict(basestring, elements.Parameter)) + + @property + def as_raw(self): + return collections.OrderedDict(( + ('name', self.name), + ('description', self.description), + ('type_name', self.type_name), + ('source_path', self.source_path), + ('target_path', self.target_path), + ('repository_url', self.repository_url), + ('repository_credential', formatting.as_agnostic(self.repository_credential)), + ('properties', formatting.as_raw_dict(self.properties.iteritems())))) + + def instantiate(self, context, container): + artifact = instance_elements.Artifact(self.name, self.type_name, self.source_path) + artifact.description = deepcopy_with_locators(self.description) + artifact.target_path = self.target_path + artifact.repository_url = self.repository_url + artifact.repository_credential = self.repository_credential + utils.instantiate_dict(context, container, artifact.properties, self.properties) + return artifact + + def validate(self, context): + if context.modeling.artifact_types.get_descendant(self.type_name) is None: + context.validation.report('artifact "%s" has an unknown type: %s' + % (self.name, formatting.safe_repr(self.type_name)), + level=validation.Issue.BETWEEN_TYPES) + + utils.validate_dict_values(context, self.properties) + + def coerce_values(self, context, container, report_issues): + utils.coerce_dict_values(context, container, self.properties, report_issues) + + def dump(self, context): + console.puts(context.style.node(self.name)) + if self.description: + console.puts(context.style.meta(self.description)) + with context.style.indent: + console.puts('Artifact type: %s' % context.style.type(self.type_name)) + console.puts('Source path: %s' % context.style.literal(self.source_path)) + if self.target_path is not None: + console.puts('Target path: %s' % context.style.literal(self.target_path)) + if self.repository_url is not None: + console.puts('Repository URL: %s' % context.style.literal(self.repository_url)) + if self.repository_credential: + console.puts('Repository credential: %s' + % context.style.literal(self.repository_credential)) + dump_parameters(context, self.properties) + + +class CapabilityTemplate(elements.ModelElement, structure.ModelMixin): + """ + A capability of a :class:`NodeTemplate`. Nodes expose zero or more capabilities that can be + matched with :class:`Requirement` instances of other nodes. + + Properties: + + * :code:`name`: Name + * :code:`description`: Description + * :code:`type_name`: Must be represented in the :class:`ModelingContext` + * :code:`min_occurrences`: Minimum number of requirement matches required + * :code:`max_occurrences`: Maximum number of requirement matches allowed + * :code:`valid_source_node_type_names`: Must be represented in the :class:`ModelingContext` + * :code:`properties`: Dict of :class:`Parameter` + """ + + description = Column(Text) + type_name = Column(Text) + min_occurrences = Column(Integer, default=None) # optional + max_occurrences = Column(Integer, default=None) # optional + # CHECK: type? + valid_source_node_type_names = Column() + properties = Column(type.StrictDict(basestring, elements.Parameter)) + + def satisfies_requirement(self, + context, + source_node_template, + requirement, + target_node_template): + # Do we match the required capability type? + capability_types = context.modeling.capability_types + if not capability_types.is_descendant(requirement.target_capability_type_name, + self.type_name): + return False + + # Are we in valid_source_node_type_names? + if self.valid_source_node_type_names: + for valid_source_node_type_name in self.valid_source_node_type_names: + if not context.modeling.node_types.is_descendant(valid_source_node_type_name, + source_node_template.type_name): + return False + + # Apply requirement constraints + if requirement.target_node_template_constraints: + for node_type_constraint in requirement.target_node_template_constraints: + if not node_type_constraint(target_node_template, source_node_template): + return False + + return True + + @property + def as_raw(self): + return collections.OrderedDict(( + ('name', self.name), + ('description', self.description), + ('type_name', self.type_name), + ('min_occurrences', self.min_occurrences), + ('max_occurrences', self.max_occurrences), + ('valid_source_node_type_names', self.valid_source_node_type_names), + ('properties', formatting.as_raw_dict(self.properties)))) + + def instantiate(self, context, container): + capability = instance_elements.Capability(self.name, self.type_name) + capability.min_occurrences = self.min_occurrences + capability.max_occurrences = self.max_occurrences + utils.instantiate_dict(context, container, capability.properties, self.properties) + return capability + + def validate(self, context): + if context.modeling.capability_types.get_descendant(self.type_name) is None: + context.validation.report( + 'capability "%s" refers to an unknown type: %s' + % (self.name, formatting.safe_repr(self.type)), # pylint: disable=no-member + # TODO fix self.type reference + level=validation.Issue.BETWEEN_TYPES) + + utils.validate_dict_values(context, self.properties) + + def coerce_values(self, context, container, report_issues): + utils.coerce_dict_values(context, self, self.properties, report_issues) + + def dump(self, context): + console.puts(context.style.node(self.name)) + if self.description: + console.puts(context.style.meta(self.description)) + with context.style.indent: + console.puts('Type: %s' % context.style.type(self.type_name)) + console.puts('Occurrences: %d%s' + % (self.min_occurrences or 0, (' to %d' % self.max_occurrences) + if self.max_occurrences is not None + else ' or more')) + if self.valid_source_node_type_names: + console.puts('Valid source node types: %s' + % ', '.join((str(context.style.type(v)) + for v in self.valid_source_node_type_names))) + dump_parameters(context, self.properties) + + +class NodeTemplate(elements.ModelElement, structure.ModelMixin): + description = Column(Text) + type_name = Column(Text) + default_instances = Column(Integer, default=1) + min_instances = Column(Integer, default=0) + man_instances = Column(Integer, default=None) + properties = Column(type.StrictDict(basestring, elements.Parameter)) + interface_templates = Column(type.StrictDict(basestring, InterfaceTemplate)) + artifact_templates = Column(type.StrictDict(basestring, ArtifactTemplate)) + capability_templataes = Column(type.StrictDict(basestring, CapabilityTemplate)) + target_node_template_constraints = Column(type.StrictList(elements.FunctionType)) + + def is_target_node_valid(self, target_node_template): + if self.target_node_template_constraints: + for node_type_constraint in self.target_node_template_constraints: + if not node_type_constraint(target_node_template, self): + return False + return True + + @property + def as_raw(self): + return collections.OrderedDict(( + ('name', self.name), + ('description', self.description), + ('type_name', self.type_name), + ('default_instances', self.default_instances), + ('min_instances', self.min_instances), + ('max_instances', self.max_instances), + ('properties', formatting.as_raw_dict(self.properties)), + ('interface_templates', formatting.as_raw_list(self.interface_templates)), + ('artifact_templates', formatting.as_raw_list(self.artifact_templates)), + ('capability_templates', formatting.as_raw_list(self.capability_templates)), + ('requirement_templates', formatting.as_raw_list(self.requirement_templates)))) + + def instantiate(self, context, container): + node = instance_elements.Node(context, self.type_name, self.name) + utils.instantiate_dict(context, node, node.properties, self.properties) + utils.instantiate_dict(context, node, node.interfaces, self.interface_templates) + utils.instantiate_dict(context, node, node.artifacts, self.artifact_templates) + utils.instantiate_dict(context, node, node.capabilities, self.capability_templates) + return node + + def validate(self, context): + if context.modeling.node_types.get_descendant(self.type_name) is None: + context.validation.report('node template "%s" has an unknown type: %s' + % (self.name, + formatting.safe_repr(self.type_name)), + level=validation.Issue.BETWEEN_TYPES) + + utils.validate_dict_values(context, self.properties) + utils.validate_dict_values(context, self.interface_templates) + utils.validate_dict_values(context, self.artifact_templates) + utils.validate_dict_values(context, self.capability_templates) + utils.validate_list_values(context, self.requirement_templates) + + def coerce_values(self, context, container, report_issues): + utils.coerce_dict_values(context, self, self.properties, report_issues) + utils.coerce_dict_values(context, self, self.interface_templates, report_issues) + utils.coerce_dict_values(context, self, self.artifact_templates, report_issues) + utils.coerce_dict_values(context, self, self.capability_templates, report_issues) + utils.coerce_list_values(context, self, self.requirement_templates, report_issues) + + def dump(self, context): + console.puts('Node template: %s' % context.style.node(self.name)) + if self.description: + console.puts(context.style.meta(self.description)) + with context.style.indent: + console.puts('Type: %s' % context.style.type(self.type_name)) + console.puts('Instances: %d (%d%s)' + % (self.default_instances, + self.min_instances, + (' to %d' % self.max_instances + if self.max_instances is not None + else ' or more'))) + dump_parameters(context, self.properties) + utils.dump_interfaces(context, self.interface_templates) + utils.dump_dict_values(context, self.artifact_templates, 'Artifact tempaltes') + utils.dump_dict_values(context, self.capability_templates, 'Capability templates') + utils.dump_list_values(context, self.requirement_templates, 'Requirement templates') + + def dump_parameters(context, parameters, name='Properties'): if not parameters: return @@ -160,6 +520,7 @@ def deepcopy_with_locators(value): copy_locators(res, value) return res + def copy_locators(target, source): """ Copies over :code:`_locator` for all elements, recursively. http://git-wip-us.apache.org/repos/asf/incubator-ariatosca/blob/09f1f737/aria/storage/type.py ---------------------------------------------------------------------- diff --git a/aria/storage/type.py b/aria/storage/type.py index 02b0ee3..728f8b1 100644 --- a/aria/storage/type.py +++ b/aria/storage/type.py @@ -14,6 +14,7 @@ # limitations under the License. import json from collections import namedtuple +from types import NoneType from sqlalchemy import ( TypeDecorator, @@ -106,14 +107,14 @@ class _StrictDictMixin(object): @classmethod def _assert_strict_key(cls, key): - if not isinstance(key, cls._key_cls): + if not isinstance(key, (cls._key_cls, NoneType)): raise exceptions.StorageError("Key type was set strictly to {0}, but was {1}".format( cls._key_cls, type(key) )) @classmethod def _assert_strict_value(cls, value): - if not isinstance(value, cls._value_cls): + if not isinstance(value, (cls._value_cls, NoneType)): raise exceptions.StorageError("Value type was set strictly to {0}, but was {1}".format( cls._value_cls, type(value) )) @@ -172,7 +173,7 @@ class _StrictListMixin(object): @classmethod def _assert_item(cls, item): - if not isinstance(item, cls._item_cls): + if not isinstance(item, (cls._item_cls, NoneType)): raise exceptions.StorageError("Key type was set strictly to {0}, but was {1}".format( cls._item_cls, type(item) )) @@ -200,7 +201,7 @@ _strict_dict_id = namedtuple('strict_dict_id', 'key_cls, value_cls') _strict_map = {} -def StrictDict(key_cls, value_cls): +def StrictDict(key_cls=NoneType, value_cls=NoneType): strict_dict_map_key = _strict_dict_id(key_cls=key_cls, value_cls=value_cls) if strict_dict_map_key not in _strict_map: strict_dict_cls = type(