def test_timestamp(self): test_property_schema = {'type': 'timestamp'} # canonical timestamp propertyInstance = Property('test_property', '2015-04-01T02:59:43.1Z', test_property_schema) self.assertIsNone(propertyInstance.validate()) self.assertEqual("2015-04-01T02:59:43.1Z", propertyInstance.value) # iso8601 timestamp propertyInstance = Property('test_property', '2015-04-01t21:59:43.10-05:00', test_property_schema) self.assertIsNone(propertyInstance.validate()) self.assertEqual("2015-04-01t21:59:43.10-05:00", propertyInstance.value) # space separated timestamp propertyInstance = Property('test_property', '2015-04-01 21:59:43.10 -5', test_property_schema) self.assertIsNone(propertyInstance.validate()) self.assertEqual("2015-04-01 21:59:43.10 -5", propertyInstance.value) # no time zone timestamp propertyInstance = Property('test_property', '2015-04-01 21:59:43.10', test_property_schema) self.assertIsNone(propertyInstance.validate()) self.assertEqual("2015-04-01 21:59:43.10", propertyInstance.value) # date (00:00:00Z) propertyInstance = Property('test_property', '2015-04-01', test_property_schema) self.assertIsNone(propertyInstance.validate()) self.assertEqual("2015-04-01", propertyInstance.value)
def _create_relationship_properties(self): props = [] properties = {} relationship = self.entity_tpl.get('relationship') if not relationship: for value in self.entity_tpl.values(): if isinstance(value, dict): relationship = value.get('relationship') break if relationship: properties = self.type_definition.get_value( self.PROPERTIES, relationship) or {} if not properties: properties = self.entity_tpl.get(self.PROPERTIES) or {} if properties: for name, value in properties.items(): props_def = self.type_definition.get_properties_def() if props_def and name in props_def: if name in properties.keys(): value = properties.get(name) prop = Property(name, value, props_def[name].schema, self.custom_def) props.append(prop) for p in self.type_definition.get_properties_def_objects(): if p.default is not None and p.name not in properties.keys(): prop = Property(p.name, p.default, p.schema, self.custom_def) props.append(prop) return props
def test_map_entry_schema(self): test_property_schema = {'type': 'map', 'entry_schema': {'type': 'boolean'}} propertyInstance = Property('test_property', {'valid': True, 'required': True}, test_property_schema) self.assertIsNone(propertyInstance.validate()) self.assertEqual({'valid': True, 'required': True}, propertyInstance.value)
def test_boolean(self): test_property_schema = {'type': 'boolean'} propertyInstance = Property('test_property', 'true', test_property_schema) self.assertIsNone(propertyInstance.validate()) propertyInstance = Property('test_property', True, test_property_schema) self.assertIsNone(propertyInstance.validate()) self.assertEqual(True, propertyInstance.value)
def __init__(self, toscaNodeTemplate, spec=None): self.toscaEntityTemplate = toscaNodeTemplate self.spec = spec self.name = toscaNodeTemplate.name if not validate_unfurl_identifier(self.name): ExceptionCollector.appendException( UnfurlValidationError( f'"{self.name}" is not a valid TOSCA template name', log=True, )) self.type = toscaNodeTemplate.type self._isReferencedBy = [ ] # this is referenced by another template or via property traversal # nodes have both properties and attributes # as do capability properties and relationships # but only property values are declared # XXX user should be able to declare default attribute values self.propertyDefs = toscaNodeTemplate.get_properties() self.attributeDefs = {} # XXX test_helm.py fails without making a deepcopy # some how chart_values is being modifying outside of a task transaction self.properties = copy.deepcopy( CommentedMap([(prop.name, prop.value) for prop in self.propertyDefs.values()])) if toscaNodeTemplate.type_definition: # add attributes definitions attrDefs = toscaNodeTemplate.type_definition.get_attributes_def() self.defaultAttributes = { prop.name: prop.default for prop in attrDefs.values() if prop.name not in ["tosca_id", "state", "tosca_name"] } for name, aDef in attrDefs.items(): prop = Property(name, aDef.default, aDef.schema, toscaNodeTemplate.custom_def) self.propertyDefs[name] = prop self.attributeDefs[name] = prop # now add any property definitions that haven't been defined yet # i.e. missing properties without a default and not required props_def = toscaNodeTemplate.type_definition.get_properties_def() for pDef in props_def.values(): if pDef.schema and pDef.name not in self.propertyDefs: self.propertyDefs[pDef.name] = Property( pDef.name, pDef.default, pDef.schema, toscaNodeTemplate.custom_def, ) else: self.defaultAttributes = {}
def _create_properties(self): props = [] properties = self.type_definition.get_value(self.PROPERTIES, self.entity_tpl) or {} for name, value in properties.items(): props_def = self.type_definition.get_properties_def() if props_def and name in props_def: prop = Property(name, value, props_def[name].schema, self.custom_def) props.append(prop) for p in self.type_definition.get_properties_def_objects(): if p.default is not None and p.name not in properties.keys(): prop = Property(p.name, p.default, p.schema, self.custom_def) props.append(prop) return props
def test_timestamp_invalid(self): test_property_schema = {'type': 'timestamp'} # invalid timestamp - day out of range propertyInstance = Property('test_property', '2015-04-115T02:59:43.1Z', test_property_schema) error = self.assertRaises(ValueError, propertyInstance.validate) self.assertEqual(_('day is out of range for month'), str(error))
def test_list_entry_schema_invalid(self): test_property_schema = {'type': 'list', 'entry_schema': {'type': 'integer'}} propertyInstance = Property('test_property', [1, 'b'], test_property_schema) error = self.assertRaises(ValueError, propertyInstance.validate) self.assertEqual(_('"b" is not an integer.'), str(error))
def test_type_invalid(self): test_property_schema = {'type': 'Fish'} propertyInstance = Property('test_property', 'Hughes', test_property_schema) error = self.assertRaises(exception.InvalidTypeError, propertyInstance.validate) self.assertEqual(_('Type "Fish" is not a valid type.'), str(error))
def post_process_template(template): for nt in template.nodetemplates: if (nt.type_definition.is_derived_from(MONITORING) or nt.type_definition.is_derived_from(FAILURE) or nt.type_definition.is_derived_from(PLACEMENT)): template.nodetemplates.remove(nt) continue if nt.type in delpropmap.keys(): for prop in delpropmap[nt.type]: for p in nt.get_properties_objects(): if prop == p.name: nt.get_properties_objects().remove(p) if nt.type in convert_prop: for prop in convert_prop[nt.type].keys(): for p in nt.get_properties_objects(): if prop == p.name: schema_dict = {'type': p.type} v = nt.get_property_value(p.name) newprop = Property(convert_prop[nt.type][prop], v, schema_dict) nt.get_properties_objects().append(newprop) nt.get_properties_objects().remove(p) if nt.type in convert_prop_values: for key in convert_prop_values[nt.type].keys(): for p in nt.get_properties_objects(): if key == p.value: v = convert_prop_values[nt.type][p.value] p.value = v
def test_map_entry_schema_invalid(self): test_property_schema = {'type': 'map', 'entry_schema': {'type': 'boolean'}} propertyInstance = Property('test_property', {'valid': True, 'contact_name': 123}, test_property_schema) error = self.assertRaises(ValueError, propertyInstance.validate) self.assertEqual(_('"123" is not a boolean.'), str(error))
def test_timestamp_invalid(self): test_property_schema = {'type': 'timestamp'} # invalid timestamp - day out of range value = '2015-04-115T02:59:43.1Z' propertyInstance = Property('test_property', value, test_property_schema) error = self.assertRaises(ValueError, propertyInstance.validate) expected_message = (_('"%s" is not a valid timestamp.') % value) self.assertThat(str(error), matchers.StartsWith(expected_message))
def test_list_entry_schema(self): test_property_schema = {'type': 'list', 'entry_schema': {'type': 'string'}} propertyInstance = Property('test_property', ['a', 'b'], test_property_schema) self.assertIsNone(propertyInstance.validate()) self.assertEqual(['a', 'b'], propertyInstance.value) schema_snippet = ''' type: list entry_schema: type: string constraints: - min_length: 2 ''' test_property_schema = yamlparser.simple_parse(schema_snippet) propertyInstance = Property('test_property', ['ab', 'cd'], test_property_schema) self.assertIsNone(propertyInstance.validate()) self.assertEqual(['ab', 'cd'], propertyInstance.value)
def get_properties_objects(self): '''Return a list of property objects.''' properties = [] props = self._properties if props: for name, value in props.items(): props_def = self.definition.get_properties_def() if props_def and name in props_def: properties.append( Property(name, value, props_def[name].schema)) return properties
def properties(self): if self._properties is None: from toscaparser.properties import Property values = self.value or {} self._properties = { name: Property( name, values.get(name, aDef.default), aDef.schema, self.custom_def ) for name, aDef in self.schema.items() } return self._properties
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 __init__(self, toscaNodeTemplate, spec=None): self.toscaEntityTemplate = toscaNodeTemplate self.spec = spec self.name = toscaNodeTemplate.name self.type = toscaNodeTemplate.type # nodes have both properties and attributes # as do capability properties and relationships # but only property values are declared self.attributeDefs = toscaNodeTemplate.get_properties() self.properties = { prop.name: prop.value for prop in self.attributeDefs.values() } if toscaNodeTemplate.type_definition: # add attributes definitions attrDefs = toscaNodeTemplate.type_definition.get_attributes_def() self.defaultAttributes = { prop.name: prop.default for prop in attrDefs.values() if prop.default is not None } for name, aDef in attrDefs.items(): self.attributeDefs[name] = Property( name, aDef.default, aDef.schema, toscaNodeTemplate.custom_def) # now add any property definitions that haven't been defined yet # i.e. missing properties without a default and not required props_def = toscaNodeTemplate.type_definition.get_properties_def() for pDef in props_def.values(): if pDef.name not in self.attributeDefs: self.attributeDefs[pDef.name] = Property( pDef.name, pDef.default, pDef.schema, toscaNodeTemplate.custom_def, ) else: self.defaultAttributes = {}
def _translate_nodetemplates(self): log.debug(_('Translating the node templates.')) suffix = 0 # Copy the TOSCA graph: nodetemplate for node in self.nodetemplates: base_type = HotResource.get_base_type(node.type_definition) hot_node = TOSCA_TO_HOT_TYPE[base_type.type](node) self.hot_resources.append(hot_node) self.hot_lookup[node] = hot_node # BlockStorage Attachment is a special case, # which doesn't match to Heat Resources 1 to 1. if base_type.type == "tosca.nodes.Compute": volume_name = None requirements = node.requirements if requirements: # Find the name of associated BlockStorage node for requires in requirements: for value in requires.values(): if isinstance(value, dict): for node_name in value.values(): for n in self.nodetemplates: if n.name == node_name: volume_name = node_name break else: # unreachable code ! for n in self.nodetemplates: if n.name == node_name: volume_name = node_name break suffix = suffix + 1 attachment_node = self._get_attachment_node( node, suffix, volume_name) if attachment_node: self.hot_resources.append(attachment_node) for i in self.tosca.inputs: if (i.name == 'key_name' and node.get_property_value('key_name') is None): schema = {'type': i.type, 'default': i.default} value = {"get_param": "key_name"} prop = Property(i.name, value, schema) node._properties.append(prop) for policy in self.policies: policy_type = policy.type_definition policy_node = TOSCA_TO_HOT_TYPE[policy_type.type](policy) self.hot_resources.append(policy_node) # Handle life cycle operations: this may expand each node # into multiple HOT resources and may change their name lifecycle_resources = [] for resource in self.hot_resources: expanded = resource.handle_life_cycle() if expanded: lifecycle_resources += expanded self.hot_resources += lifecycle_resources # Handle configuration from ConnectsTo relationship in the TOSCA node: # this will generate multiple HOT resources, set of 2 for each # configuration connectsto_resources = [] for node in self.nodetemplates: for requirement in node.requirements: for endpoint, details in six.iteritems(requirement): relation = None if isinstance(details, dict): target = details.get('node') relation = details.get('relationship') else: target = details if (target and relation and not isinstance(relation, six.string_types)): interfaces = relation.get('interfaces') connectsto_resources += \ self._create_connect_configs(node, target, interfaces) self.hot_resources += connectsto_resources # Copy the initial dependencies based on the relationship in # the TOSCA template for node in self.nodetemplates: for node_depend in node.related_nodes: # if the source of dependency is a server and the # relationship type is 'tosca.relationships.HostedOn', # add dependency as properties.server if node_depend.type == 'tosca.nodes.Compute' and \ node.related[node_depend].type == \ node.type_definition.HOSTEDON: self.hot_lookup[node].properties['server'] = \ {'get_resource': self.hot_lookup[node_depend].name} # for all others, add dependency as depends_on else: self.hot_lookup[node].depends_on.append( self.hot_lookup[node_depend].top_of_chain()) self.hot_lookup[node].depends_on_nodes.append( self.hot_lookup[node_depend].top_of_chain()) # handle hosting relationship for resource in self.hot_resources: resource.handle_hosting() # handle built-in properties of HOT resources # if a resource depends on other resources, # their properties need to be handled first. # Use recursion to handle the properties of the # dependent nodes in correct order self.processed_resources = [] for resource in self.hot_resources: self._recursive_handle_properties(resource) # handle resources that need to expand to more than one HOT resource expansion_resources = [] for resource in self.hot_resources: expanded = resource.handle_expansion() if expanded: expansion_resources += expanded self.hot_resources += expansion_resources # Resolve function calls: GetProperty, GetAttribute, GetInput # at this point, all the HOT resources should have been created # in the graph. for resource in self.hot_resources: # traverse the reference chain to get the actual value inputs = resource.properties.get('input_values') if inputs: for name, value in six.iteritems(inputs): inputs[name] = self._translate_input(value, resource) return self.hot_resources
def _translate_nodetemplates(self): log.debug(_('Translating the node templates.')) suffix = 0 # Copy the TOSCA graph: nodetemplate for node in self.nodetemplates: base_type = HotResource.get_base_type_str(node.type_definition) if base_type not in TOSCA_TO_HOT_TYPE: raise UnsupportedTypeError(type=_('%s') % base_type) hot_node = TOSCA_TO_HOT_TYPE[base_type](node, csar_dir=self.csar_dir) self.hot_resources.append(hot_node) self.hot_lookup[node] = hot_node # BlockStorage Attachment is a special case, # which doesn't match to Heat Resources 1 to 1. if base_type == "tosca.nodes.Compute": requirements = node.requirements if requirements: # Find the name of associated BlockStorage node for requires in requirements: volume_name = None for value in requires.values(): if isinstance(value, dict): for node_name in value.values(): for n in self.nodetemplates: if n.name == node_name and \ n.is_derived_from( "tosca.nodes.BlockStorage"): volume_name = node_name break else: for n in self.nodetemplates: if n.name == value and \ n.is_derived_from( "tosca.nodes.BlockStorage"): volume_name = node_name break if volume_name: suffix = suffix + 1 attachment_node = self._get_attachment_node( node, suffix, volume_name) if attachment_node: self.hot_resources.append(attachment_node) for i in self.tosca.inputs: if (i.name == 'key_name' and node.get_property_value('key_name') is None): schema = {'type': i.type, 'default': i.default} value = {"get_param": "key_name"} prop = Property(i.name, value, schema) node._properties.append(prop) for policy in self.policies: policy_type = policy.type_definition if policy.is_derived_from('tosca.policies.Scaling') and \ policy_type.type != 'tosca.policies.Scaling.Cluster': TOSCA_TO_HOT_TYPE[policy_type.type] = \ TOSCA_TO_HOT_TYPE['tosca.policies.Scaling'] if policy.is_derived_from('tosca.policies.Monitoring'): TOSCA_TO_HOT_TYPE[policy_type.type] = \ TOSCA_TO_HOT_TYPE['tosca.policies.Monitoring'] if policy.is_derived_from('tosca.policies.Placement'): TOSCA_TO_HOT_TYPE[policy_type.type] = \ TOSCA_TO_HOT_TYPE['tosca.policies.Placement'] if not policy.is_derived_from('tosca.policies.Monitoring') and \ not policy.is_derived_from('tosca.policies.Scaling') and \ policy_type.type not in TOSCA_TO_HOT_TYPE: raise UnsupportedTypeError(type=_('%s') % policy_type.type) elif policy_type.type == 'tosca.policies.Scaling.Cluster': self.hot_template_version = '2016-04-08' policy_node = TOSCA_TO_HOT_TYPE[policy_type.type](policy) self.hot_resources.append(policy_node) # Handle life cycle operations: this may expand each node # into multiple HOT resources and may change their name lifecycle_resources = [] for resource in self.hot_resources: expanded_resources, deploy_lookup, last_deploy = resource.\ handle_life_cycle() if expanded_resources: lifecycle_resources += expanded_resources if deploy_lookup: self.hot_lookup.update(deploy_lookup) if last_deploy: self.last_deploy_map[resource] = last_deploy self.hot_resources += lifecycle_resources # Handle configuration from ConnectsTo relationship in the TOSCA node: # this will generate multiple HOT resources, set of 2 for each # configuration connectsto_resources = [] for node in self.nodetemplates: for requirement in node.requirements: for endpoint, details in requirement.items(): relation = None if isinstance(details, dict): target = details.get('node') relation = details.get('relationship') else: target = details if (target and relation and not isinstance(relation, six.string_types)): interfaces = relation.get('interfaces') connectsto_resources += \ self._create_connect_configs(node, target, interfaces) self.hot_resources += connectsto_resources # Copy the initial dependencies based on the relationship in # the TOSCA template for node in self.nodetemplates: for node_depend in node.related_nodes: # if the source of dependency is a server and the # relationship type is 'tosca.relationships.HostedOn', # add dependency as properties.server base_type = HotResource.get_base_type_str( node_depend.type_definition) if base_type == 'tosca.nodes.Compute' and \ node.related[node_depend].type == \ node.type_definition.HOSTEDON: self.hot_lookup[node].properties['server'] = \ {'get_resource': self.hot_lookup[node_depend].name} # for all others, add dependency as depends_on else: self.hot_lookup[node].depends_on.append( self.hot_lookup[node_depend].top_of_chain()) self.hot_lookup[node].depends_on_nodes.append( self.hot_lookup[node_depend].top_of_chain()) last_deploy = self.last_deploy_map.get( self.hot_lookup[node_depend]) if last_deploy and \ last_deploy not in self.hot_lookup[node].depends_on: self.hot_lookup[node].depends_on.append(last_deploy) self.hot_lookup[node].depends_on_nodes.append(last_deploy) # handle hosting relationship for resource in self.hot_resources: resource.handle_hosting() # handle built-in properties of HOT resources # if a resource depends on other resources, # their properties need to be handled first. # Use recursion to handle the properties of the # dependent nodes in correct order self.processed_resources = [] for resource in self.hot_resources: if resource.type not in HOT_SCALING_POLICY_TYPE: self._recursive_handle_properties(resource) # handle resources that need to expand to more than one HOT resource expansion_resources = [] for resource in self.hot_resources: expanded = resource.handle_expansion() if expanded: expansion_resources += expanded self.hot_resources += expansion_resources # Resolve function calls: GetProperty, GetAttribute, GetInput # at this point, all the HOT resources should have been created # in the graph. for resource in self.hot_resources: # traverse the reference chain to get the actual value inputs = resource.properties.get('input_values') if inputs: for name, value in inputs.items(): inputs[name] = self.translate_param_value(value, resource) # remove resources without type defined # for example a SoftwareComponent without interfaces # would fall in this case to_remove = [] for resource in self.hot_resources: if resource.type is None: to_remove.append(resource) for resource in to_remove: self.hot_resources.remove(resource) for resource in self.hot_resources: for removed_resource in to_remove: if removed_resource in resource.depends_on: resource.depends_on.remove(removed_resource) return self.hot_resources
def test_float_invalid(self): test_property_schema = {'type': 'float'} propertyInstance = Property('test_property', 12, test_property_schema) error = self.assertRaises(ValueError, propertyInstance.validate) self.assertEqual(_('"12" is not a float.'), str(error))
def test_type_invalid(self): test_property_schema = {'type': 'Fish'} error = self.assertRaises( exception.MissingTypeError, lambda: Property('test_property', 'Hughes', test_property_schema)) self.assertIn("Fish", str(error))
def test_map(self): test_property_schema = {'type': 'map'} propertyInstance = Property('test_property', {'a': 'b'}, test_property_schema) self.assertIsNone(propertyInstance.validate()) self.assertEqual({'a': 'b'}, propertyInstance.value)
def test_required(self): test_property_schema = {'type': 'string'} propertyInstance = Property('test_property', 'Foo', test_property_schema) self.assertEqual(True, propertyInstance.required)
def test_list(self): test_property_schema = {'type': 'list'} propertyInstance = Property('test_property', ['a', 'b'], test_property_schema) self.assertIsNone(propertyInstance.validate()) self.assertEqual(['a', 'b'], propertyInstance.value)
def test_float(self): test_property_schema = {'type': 'float'} propertyInstance = Property('test_property', 0.1, test_property_schema) self.assertIsNone(propertyInstance.validate()) self.assertEqual(0.1, propertyInstance.value)
def test_type(self): test_property_schema = {'type': 'string'} propertyInstance = Property('test_property', 'Hughes', test_property_schema) self.assertEqual('string', propertyInstance.type)
def test_list_invalid(self): test_property_schema = {'type': 'list'} propertyInstance = Property('test_property', 'a', test_property_schema) error = self.assertRaises(ValueError, propertyInstance.validate) self.assertEqual(_('"a" is not a list.'), str(error))