def __init__(self, name, template, entity_name, custom_def=None): self.name = name self.entity_tpl = template self.custom_def = custom_def self._validate_field(self.entity_tpl) type = self.entity_tpl.get('type') UnsupportedType.validate_type(type) if '__typename' not in template: self._validate_fields(template) if entity_name == 'node_type': self.type_definition = NodeType(type, custom_def) \ if type is not None else None self._validate_directives(self.entity_tpl) if entity_name == 'relationship_type': self.type_definition = RelationshipType(type, custom_def) if entity_name == 'policy_type': if not type: msg = (_('Policy definition of "%(pname)s" must have' ' a "type" ' 'attribute.') % dict(pname=name)) ExceptionCollector.appendException( ValidationError(message=msg)) self.type_definition = PolicyType(type, custom_def) if entity_name == 'group_type': self.type_definition = GroupType(type, custom_def) \ if type is not None else None if entity_name == 'artifact_type': self.type_definition = ArtifactTypeDef(type, custom_def) \ if type is not None else None self._properties = None self._interfaces = None self._requirements = None self._capabilities = None if not self.type_definition: msg = "no type found %s for %s" % (entity_name, template) ExceptionCollector.appendException(ValidationError(message=msg)) return metadata = self.type_definition.get_definition('metadata') if metadata and 'additionalProperties' in metadata: self.additionalProperties = metadata['additionalProperties'] self._properties_tpl = self._validate_properties() for prop in self.get_properties_objects(): prop.validate() self._validate_interfaces()
from toscaparser.common import exception from toscaparser.elements.artifacttype import ArtifactTypeDef from toscaparser.elements.entity_type import EntityType import toscaparser.elements.interfaces as ifaces from toscaparser.elements.nodetype import NodeType from toscaparser.elements.policytype import PolicyType from toscaparser.tests.base import TestCase compute_type = NodeType('tosca.nodes.Compute') component_type = NodeType('tosca.nodes.SoftwareComponent') network_type = NodeType('tosca.nodes.network.Network') network_port_type = NodeType('tosca.nodes.network.Port') webserver_type = NodeType('tosca.nodes.WebServer') database_type = NodeType('tosca.nodes.Database') artif_root_type = ArtifactTypeDef('tosca.artifacts.Root') artif_file_type = ArtifactTypeDef('tosca.artifacts.File') artif_bash_type = ArtifactTypeDef('tosca.artifacts.Implementation.Bash') artif_python_type = ArtifactTypeDef('tosca.artifacts.Implementation.Python') artif_container_docker_type = ArtifactTypeDef('tosca.artifacts.' 'Deployment.Image.' 'Container.Docker') artif_vm_iso_type = ArtifactTypeDef('tosca.artifacts.' 'Deployment.Image.VM.ISO') artif_vm_qcow2_type = ArtifactTypeDef('tosca.artifacts.' 'Deployment.Image.VM.QCOW2') policy_root_type = PolicyType('tosca.policies.Root') policy_placement_type = PolicyType('tosca.policies.Placement') policy_scaling_type = PolicyType('tosca.policies.Scaling') policy_update_type = PolicyType('tosca.policies.Update') policy_performance_type = PolicyType('tosca.policies.Performance')
class EntityTemplate(object): '''Base class for TOSCA templates.''' SECTIONS = (DERIVED_FROM, PROPERTIES, REQUIREMENTS, INTERFACES, CAPABILITIES, TYPE, DESCRIPTION, DIRECTIVES, INSTANCE_KEYS, ATTRIBUTES, ARTIFACTS, NODE_FILTER, COPY, DEPENDENCIES) = \ ('derived_from', 'properties', 'requirements', 'interfaces', 'capabilities', 'type', 'description', 'directives', "instance_keys", 'attributes', 'artifacts', 'node_filter', 'copy', 'dependencies') REQUIREMENTS_SECTION = (NODE, CAPABILITY, RELATIONSHIP, OCCURRENCES, NODE_FILTER, DESCRIPTION, METADATA) = \ ('node', 'capability', 'relationship', 'occurrences', 'node_filter', 'description', 'metadata') # Special key names SPECIAL_SECTIONS = (METADATA) = ('metadata') additionalProperties = True _source = None def __init__(self, name, template, entity_name, custom_def=None): self.name = name self.entity_tpl = template self.custom_def = custom_def self._validate_field(self.entity_tpl) type = self.entity_tpl.get('type') UnsupportedType.validate_type(type) if '__typename' not in template: self._validate_fields(template) if entity_name == 'node_type': self.type_definition = NodeType(type, custom_def) \ if type is not None else None self._validate_directives(self.entity_tpl) if entity_name == 'relationship_type': self.type_definition = RelationshipType(type, custom_def) if entity_name == 'policy_type': if not type: msg = (_('Policy definition of "%(pname)s" must have' ' a "type" ' 'attribute.') % dict(pname=name)) ExceptionCollector.appendException( ValidationError(message=msg)) self.type_definition = PolicyType(type, custom_def) if entity_name == 'group_type': self.type_definition = GroupType(type, custom_def) \ if type is not None else None if entity_name == 'artifact_type': self.type_definition = ArtifactTypeDef(type, custom_def) \ if type is not None else None self._properties = None self._interfaces = None self._requirements = None self._capabilities = None if not self.type_definition: msg = "no type found %s for %s" % (entity_name, template) ExceptionCollector.appendException(ValidationError(message=msg)) return metadata = self.type_definition.get_definition('metadata') if metadata and 'additionalProperties' in metadata: self.additionalProperties = metadata['additionalProperties'] self._properties_tpl = self._validate_properties() for prop in self.get_properties_objects(): prop.validate() self._validate_interfaces() @property def type(self): if self.type_definition: return self.type_definition.type @property def parent_type(self): if self.type_definition: return self.type_definition.parent_type @property def types(self): if not self.type_definition: return [] types = {self.type_definition.type: self.type_definition} for p in self.type_definition.parent_types(): if p.type not in types: types[p.type] = p return list(types.values()) @property def directives(self): return self.entity_tpl.get('directives', []) @property def requirements(self): if self._requirements is None: # alternative syntax for requirements dependencies = self.entity_tpl.get(self.DEPENDENCIES) if dependencies: self._requirements = [{ dep['name']: dict(node=dep['match']) } for dep in dependencies] else: self._requirements = self.type_definition.get_value( self.REQUIREMENTS, self.entity_tpl) or [] return self._requirements def get_properties_objects(self): '''Return properties objects for this template.''' if self._properties is None: self._properties = self._create_properties() return self._properties def get_properties(self): '''Return a dictionary of property name-object pairs.''' return {prop.name: prop for prop in self.get_properties_objects()} def get_property_value(self, name): '''Return the value of a given property name.''' props = self.get_properties() if props and name in props.keys(): return props[name].value @property def interfaces(self): if self._interfaces is None: self._interfaces = self._create_interfaces() return self._interfaces def get_capabilities_objects(self): '''Return capabilities objects for this template.''' if self._capabilities is None: self._capabilities = self._create_capabilities() return self._capabilities def get_capabilities(self): '''Return a dictionary of capability name-object pairs.''' return {cap.name: cap for cap in self.get_capabilities_objects()} def is_derived_from(self, type_str): '''Check if object inherits from the given type. Returns true if this object is derived from 'type_str'. False otherwise. ''' if not self.type: return False elif self.type == type_str: return True elif self.parent_type: return self.parent_type.is_derived_from(type_str) else: return False def _create_capability(self, capabilitydefs, name, ctype, props): c = capabilitydefs.get(name) if ctype and (not c or ctype != c.type): c = CapabilityTypeDef(name, ctype, self.type_definition.type, self.type_definition.custom_def) properties = {} # first use the definition default value if c.properties: for property_name in c.properties.keys(): prop_def = c.properties[property_name] if 'default' in prop_def: properties[property_name] = prop_def['default'] # then update (if available) with the node properties if props: properties.update(props) return Capability(name, properties, c, self.custom_def) def _create_capabilities_from_properties(self, capabilities): capabilitydefs = self.type_definition.get_capabilities_def() for name, capdef in capabilitydefs.items(): if name in self._properties_tpl: cap = self._create_capability(capabilitydefs, name, capdef.ctype, self._properties_tpl[name]) capabilities.append(cap) def _create_capabilities(self): capabilities = [] caps = self.type_definition.get_value(self.CAPABILITIES, self.entity_tpl, parent=True) if caps: for name, props in caps.items(): if props is None: continue capabilitydefs = self.type_definition.get_capabilities_def() if name in capabilitydefs: cap = self._create_capability(capabilitydefs, name, props.get('type'), props.get('properties')) capabilities.append(cap) self._create_capabilities_from_properties(capabilities) return capabilities def _validate_directives(self, template): msg = (_('directives of "%s" must be a list of strings') % self.name) keys = template.get("directives", []) if not isinstance(keys, list): ExceptionCollector.appendException(ValidationError(message=msg)) for key in keys: if not isinstance(key, str): ExceptionCollector.appendException( ValidationError(message=msg)) def _validate_properties(self): properties = self.type_definition.get_value(self.PROPERTIES, self.entity_tpl) if isinstance(properties, list): properties = {p["title"]: p.get('value') for p in properties} if not properties: properties = {} if not isinstance(properties, dict): ExceptionCollector.appendException( TypeMismatchError(what='"properties" of template "%s"' % self.name, type='dict')) return {} self._common_validate_properties(self.type_definition, properties, self.additionalProperties) return properties def _validate_capabilities(self): type_capabilities = self.type_definition.get_capabilities_def() allowed_caps = \ type_capabilities.keys() if type_capabilities else [] capabilities = self.type_definition.get_value(self.CAPABILITIES, self.entity_tpl) if capabilities: self._common_validate_field(capabilities, allowed_caps, 'capabilities') self._validate_capabilities_properties(capabilities) def _validate_capabilities_properties(self, capabilities): for cap, props in capabilities.items(): capability = self.get_capability(cap) if not capability: continue capabilitydef = capability.type_definition self._common_validate_properties(capabilitydef, props.get(self.PROPERTIES) or {}) # validating capability properties values for prop in self.get_capability(cap).get_properties_objects(): prop.validate() # TODO(srinivas_tadepalli): temporary work around to validate # default_instances until standardized in specification if cap == "scalable" and prop.name == "default_instances": prop_dict = props[self.PROPERTIES] min_instances = prop_dict.get("min_instances") max_instances = prop_dict.get("max_instances") default_instances = prop_dict.get("default_instances") if not (min_instances <= default_instances <= max_instances): err_msg = ('"properties" of template "%s": ' '"default_instances" value is not between ' '"min_instances" and "max_instances".' % self.name) ExceptionCollector.appendException( ValidationError(message=err_msg)) def _common_validate_properties(self, entitytype, properties, allowUndefined=False): allowed_props = [] required_props = [] for p in entitytype.get_properties_def_objects(): allowed_props.append(p.name) # If property is 'required' and has no 'default' value then record if p.required and p.default is None: required_props.append(p.name) # validate all required properties have values if properties: req_props_no_value_or_default = [] if not allowUndefined: self._common_validate_field(properties, allowed_props, 'properties') # make sure it's not missing any property required by a tosca type for r in required_props: if r not in properties.keys(): req_props_no_value_or_default.append(r) # Required properties found without value or a default value if req_props_no_value_or_default: ExceptionCollector.appendException( MissingRequiredFieldError( what='"properties" of template "%s"' % self.name, required=req_props_no_value_or_default)) else: # Required properties in schema, but not in template if required_props: ExceptionCollector.appendException( MissingRequiredFieldError( what='"properties" of template "%s"' % self.name, required=required_props)) def _validate_field(self, template): if not isinstance(template, dict): ExceptionCollector.appendException( MissingRequiredFieldError(what='Template "%s"' % self.name, required=self.TYPE)) try: template[self.TYPE] except KeyError: ExceptionCollector.appendException( MissingRequiredFieldError(what='Template "%s"' % self.name, required=self.TYPE)) def _common_validate_field(self, schema, allowedlist, section): if schema is None: ExceptionCollector.appendException( ValidationError(message=( 'Missing value for "%s". Must contain one of: "%s"' % (section, ", ".join(allowedlist))))) else: for name in schema: if name not in allowedlist: ExceptionCollector.appendException( UnknownFieldError( what=('"%(section)s" of template "%(nodename)s"' % { 'section': section, 'nodename': self.name }), field=name)) def _validate_fields(self, template): for name in template.keys(): if name not in self.SECTIONS and name not in self.SPECIAL_SECTIONS: ExceptionCollector.appendException( UnknownFieldError(what='template "%s"' % self.name, field=name)) def _create_properties(self): props = [] properties = self._properties_tpl or {} props_def = self.type_definition.get_properties_def() if isinstance(self.type_definition, NodeType): capabilitydefs = self.type_definition.get_capabilities_def() else: capabilitydefs = {} for name, value in properties.items(): if name in capabilitydefs: continue if props_def and name in props_def: prop = Property(name, value, props_def[name].schema, self.custom_def) props.append(prop) elif self.additionalProperties: prop = Property(name, value, dict(type='any'), self.custom_def) props.append(prop) for p in props_def.values(): if p.default is not None and p.name not in properties: prop = Property(p.name, p.default, p.schema, self.custom_def) props.append(prop) return props def _create_interfaces(self): interfacesDefs = self._create_interfacedefs() if not interfacesDefs: return [] return self._create_operations(interfacesDefs) def _create_interfacedefs(self): # get a copy of the interfaces directy defined on the entity template tpl_interfaces = self.type_definition.get_value( self.INTERFACES, self.entity_tpl) _source = None if self.type_definition.interfaces: interfacesDefs = self.type_definition.interfaces.copy() _source = self.type_definition._source if tpl_interfaces: # merge the interfaces defined on the type with the template's interface definitions for iName, defs in tpl_interfaces.items(): # for each interface, see if base defines it too defs = defs.copy() inputs = defs.get('inputs', {}) if 'operations' in defs: defs = defs.get('operations', {}) baseDefs = interfacesDefs.get(iName) if baseDefs: # add in base's ops and merge interface-level inputs baseInputs = baseDefs.get('inputs') if baseInputs: # merge shared inputs inputs = dict(baseInputs, **inputs) defs['inputs'] = inputs implementation = baseDefs.get('implementation') # set shared implementation if implementation and 'implementation' not in defs: defs['implementation'] = implementation if isinstance(implementation, dict) and _source: # if implementation might be an inline artifact, save the baseDir of the source implementation['_source'] = _source if 'operations' in baseDefs: baseDefs = baseDefs.get('operations') or {} for op, baseDef in baseDefs.items(): if op in ['inputs', 'notifications', '_source']: continue if op in defs: # op in both, merge currentiDef = defs[op] if isinstance(baseDef, dict): if not isinstance(currentiDef, dict): currentiDef = dict( implementation=currentiDef) if isinstance( baseDef.get('implementation'), dict) and _source: # if implementation might be an inline artifact, save the baseDir of the source baseDef['implementation'][ '_source'] = _source defs[op] = dict(baseDef, **currentiDef) if 'inputs' in baseDef and 'inputs' in currentiDef: # merge inputs defs[op]['inputs'] = dict( baseDef['inputs'], **currentiDef['inputs']) else: defs[op] = baseDef # add or replace: interfacesDefs[iName] = defs else: interfacesDefs = tpl_interfaces return interfacesDefs def _create_operations(self, interfacesDefs): interfaces = [] defaults = interfacesDefs.pop('defaults', {}) for interface_type, value in interfacesDefs.items(): # merge in shared: # shared inputs inputs = value.get('inputs') defaultInputs = defaults.get('inputs') if inputs and defaultInputs: # merge shared inputs inputs = dict(defaultInputs, **inputs) else: inputs = inputs or defaultInputs # shared outputs outputs = value.get('outputs') defaultOutputs = defaults.get('outputs') if outputs and defaultOutputs: # merge shared inputs outputs = dict(defaultOutputs, **outputs) else: outputs = outputs or defaultOutputs # shared implementation implementation = value.get('implementation') or defaults.get( 'implementation') # create an InterfacesDef for each operation _source = value.pop('_source', None) if 'operations' in value: defs = value.get('operations') or {} else: defs = value for op in list(defs): op_def = defs[op] if op in INTERFACE_DEF_RESERVED_WORDS: continue if not isinstance(op_def, dict): op_def = dict(implementation=op_def or implementation) elif implementation and not op_def.get('implementation'): op_def['implementation'] = implementation if _source: op_def['_source'] = _source iface = InterfacesDef( self.type_definition, interface_type, node_template=self, name=op, value=op_def, inputs=inputs.copy() if inputs else None, outputs=outputs.copy() if outputs else None) interfaces.append(iface) # add a "default" operation that has the shared inputs and implementation if inputs or implementation: iface = InterfacesDef(self.type_definition, interface_type, node_template=self, name='default', value=dict(implementation=implementation, _source=_source), inputs=inputs, outputs=outputs) interfaces.append(iface) return interfaces def _validate_interfaces(self): ifaces = self.type_definition.get_value(self.INTERFACES, self.entity_tpl) if ifaces: for name, value in ifaces.items(): if name == 'defaults': self._common_validate_field( value, ['implementation', 'inputs', 'outputs'], 'interfaces') elif name in (LIFECYCLE, LIFECYCLE_SHORTNAME): self._common_validate_field( value, INTERFACE_DEF_RESERVED_WORDS + InterfacesDef.interfaces_node_lifecycle_operations, 'interfaces') elif name in (CONFIGURE, CONFIGURE_SHORTNAME): self._common_validate_field( value, INTERFACE_DEF_RESERVED_WORDS + InterfacesDef. interfaces_relationship_configure_operations, 'interfaces') elif (name in self.type_definition.interfaces or name in self.type_definition.TOSCA_DEF): self._common_validate_field( value, INTERFACE_DEF_RESERVED_WORDS + self._collect_custom_iface_operations(name), 'interfaces') else: ExceptionCollector.appendException( UnknownFieldError( what='"interfaces" of template "%s"' % self.name, field=name)) def _collect_custom_iface_operations(self, name): allowed_operations = [] nodetype_iface_def = self.type_definition.interfaces.get( name, self.type_definition.TOSCA_DEF.get(name)) allowed_operations.extend(nodetype_iface_def.keys()) if 'type' in nodetype_iface_def: iface_type = nodetype_iface_def['type'] if iface_type in self.type_definition.custom_def: iface_type_def = self.type_definition.custom_def[iface_type] else: iface_type_def = self.type_definition.TOSCA_DEF[iface_type] allowed_operations.extend(iface_type_def.keys()) allowed_operations = [ op for op in allowed_operations if op not in INTERFACE_DEF_RESERVED_WORDS ] return allowed_operations def get_capability(self, name): """Provide named capability :param name: name of capability :return: capability object if found, None otherwise """ return self.get_capabilities().get(name)