Update tosca lib to version 0.5
[parser.git] / tosca2heat / tosca-parser / toscaparser / entity_template.py
1 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
2 #    not use this file except in compliance with the License. You may obtain
3 #    a copy of the License at
4 #
5 #         http://www.apache.org/licenses/LICENSE-2.0
6 #
7 #    Unless required by applicable law or agreed to in writing, software
8 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10 #    License for the specific language governing permissions and limitations
11 #    under the License.
12
13 from toscaparser.capabilities import Capability
14 from toscaparser.common.exception import ExceptionCollector
15 from toscaparser.common.exception import MissingRequiredFieldError
16 from toscaparser.common.exception import UnknownFieldError
17 from toscaparser.common.exception import ValidationError
18 from toscaparser.elements.grouptype import GroupType
19 from toscaparser.elements.interfaces import InterfacesDef
20 from toscaparser.elements.nodetype import NodeType
21 from toscaparser.elements.policytype import PolicyType
22 from toscaparser.elements.relationshiptype import RelationshipType
23 from toscaparser.properties import Property
24 from toscaparser.utils.gettextutils import _
25
26
27 class EntityTemplate(object):
28     '''Base class for TOSCA templates.'''
29
30     SECTIONS = (DERIVED_FROM, PROPERTIES, REQUIREMENTS,
31                 INTERFACES, CAPABILITIES, TYPE, DESCRIPTION, DIRECTIVES,
32                 ATTRIBUTES, ARTIFACTS, NODE_FILTER, COPY) = \
33                ('derived_from', 'properties', 'requirements', 'interfaces',
34                 'capabilities', 'type', 'description', 'directives',
35                 'attributes', 'artifacts', 'node_filter', 'copy')
36     REQUIREMENTS_SECTION = (NODE, CAPABILITY, RELATIONSHIP, OCCURRENCES, NODE_FILTER) = \
37                            ('node', 'capability', 'relationship',
38                             'occurrences', 'node_filter')
39     # Special key names
40     SPECIAL_SECTIONS = (METADATA) = ('metadata')
41
42     def __init__(self, name, template, entity_name, custom_def=None):
43         self.name = name
44         self.entity_tpl = template
45         self.custom_def = custom_def
46         self._validate_field(self.entity_tpl)
47         if entity_name == 'node_type':
48             type = self.entity_tpl.get('type')
49             self.type_definition = NodeType(type, custom_def) \
50                 if type is not None else None
51         if entity_name == 'relationship_type':
52             relationship = template.get('relationship')
53             type = None
54             if relationship and isinstance(relationship, dict):
55                 type = relationship.get('type')
56             elif isinstance(relationship, str):
57                 type = self.entity_tpl['relationship']
58             else:
59                 type = self.entity_tpl['type']
60             self.type_definition = RelationshipType(type,
61                                                     None, custom_def)
62         if entity_name == 'policy_type':
63             type = self.entity_tpl.get('type')
64             if not type:
65                 msg = (_('Policy definition of "%(pname)s" must have'
66                        ' a "type" ''attribute.') % dict(pname=name))
67                 ExceptionCollector.appendException(
68                     ValidationError(msg))
69
70             self.type_definition = PolicyType(type, custom_def)
71         if entity_name == 'group_type':
72             type = self.entity_tpl.get('type')
73             self.type_definition = GroupType(type, custom_def) \
74                 if type is not None else None
75         self._properties = None
76         self._interfaces = None
77         self._requirements = None
78         self._capabilities = None
79
80     @property
81     def type(self):
82         if self.type_definition:
83             return self.type_definition.type
84
85     @property
86     def requirements(self):
87         if self._requirements is None:
88             self._requirements = self.type_definition.get_value(
89                 self.REQUIREMENTS,
90                 self.entity_tpl) or []
91         return self._requirements
92
93     def get_properties_objects(self):
94         '''Return properties objects for this template.'''
95         if self._properties is None:
96             self._properties = self._create_properties()
97         return self._properties
98
99     def get_properties(self):
100         '''Return a dictionary of property name-object pairs.'''
101         return {prop.name: prop
102                 for prop in self.get_properties_objects()}
103
104     def get_property_value(self, name):
105         '''Return the value of a given property name.'''
106         props = self.get_properties()
107         if props and name in props.keys():
108             return props[name].value
109
110     @property
111     def interfaces(self):
112         if self._interfaces is None:
113             self._interfaces = self._create_interfaces()
114         return self._interfaces
115
116     def get_capabilities_objects(self):
117         '''Return capabilities objects for this template.'''
118         if not self._capabilities:
119             self._capabilities = self._create_capabilities()
120         return self._capabilities
121
122     def get_capabilities(self):
123         '''Return a dictionary of capability name-object pairs.'''
124         return {cap.name: cap
125                 for cap in self.get_capabilities_objects()}
126
127     def is_derived_from(self, type_str):
128         '''Check if object inherits from the given type.
129
130         Returns true if this object is derived from 'type_str'.
131         False otherwise.
132         '''
133         if not self.type:
134             return False
135         elif self.type == type_str:
136             return True
137         elif self.parent_type:
138             return self.parent_type.is_derived_from(type_str)
139         else:
140             return False
141
142     def _create_capabilities(self):
143         capability = []
144         caps = self.type_definition.get_value(self.CAPABILITIES,
145                                               self.entity_tpl, True)
146         if caps:
147             for name, props in caps.items():
148                 capabilities = self.type_definition.get_capabilities()
149                 if name in capabilities.keys():
150                     c = capabilities[name]
151                     properties = {}
152                     # first use the definition default value
153                     if c.properties:
154                         for property_name in c.properties.keys():
155                             prop_def = c.properties[property_name]
156                             if 'default' in prop_def:
157                                 properties[property_name] = prop_def['default']
158                     # then update (if available) with the node properties
159                     if 'properties' in props and props['properties']:
160                         properties.update(props['properties'])
161
162                     cap = Capability(name, properties, c)
163                     capability.append(cap)
164         return capability
165
166     def _validate_properties(self, template, entitytype):
167         properties = entitytype.get_value(self.PROPERTIES, template)
168         self._common_validate_properties(entitytype, properties)
169
170     def _validate_capabilities(self):
171         type_capabilities = self.type_definition.get_capabilities()
172         allowed_caps = \
173             type_capabilities.keys() if type_capabilities else []
174         capabilities = self.type_definition.get_value(self.CAPABILITIES,
175                                                       self.entity_tpl)
176         if capabilities:
177             self._common_validate_field(capabilities, allowed_caps,
178                                         'capabilities')
179             self._validate_capabilities_properties(capabilities)
180
181     def _validate_capabilities_properties(self, capabilities):
182         for cap, props in capabilities.items():
183             capabilitydef = self.get_capability(cap).definition
184             self._common_validate_properties(capabilitydef,
185                                              props[self.PROPERTIES])
186
187             # validating capability properties values
188             for prop in self.get_capability(cap).get_properties_objects():
189                 prop.validate()
190
191                 # TODO(srinivas_tadepalli): temporary work around to validate
192                 # default_instances until standardized in specification
193                 if cap == "scalable" and prop.name == "default_instances":
194                     prop_dict = props[self.PROPERTIES]
195                     min_instances = prop_dict.get("min_instances")
196                     max_instances = prop_dict.get("max_instances")
197                     default_instances = prop_dict.get("default_instances")
198                     if not (min_instances <= default_instances
199                             <= max_instances):
200                         err_msg = ('"properties" of template "%s": '
201                                    '"default_instances" value is not between '
202                                    '"min_instances" and "max_instances".' %
203                                    self.name)
204                         ExceptionCollector.appendException(
205                             ValidationError(message=err_msg))
206
207     def _common_validate_properties(self, entitytype, properties):
208         allowed_props = []
209         required_props = []
210         for p in entitytype.get_properties_def_objects():
211             allowed_props.append(p.name)
212             # If property is 'required' and has no 'default' value then record
213             if p.required and p.default is None:
214                 required_props.append(p.name)
215         # validate all required properties have values
216         if properties:
217             req_props_no_value_or_default = []
218             self._common_validate_field(properties, allowed_props,
219                                         'properties')
220             # make sure it's not missing any property required by a tosca type
221             for r in required_props:
222                 if r not in properties.keys():
223                     req_props_no_value_or_default.append(r)
224             # Required properties found without value or a default value
225             if req_props_no_value_or_default:
226                 ExceptionCollector.appendException(
227                     MissingRequiredFieldError(
228                         what='"properties" of template "%s"' % self.name,
229                         required=req_props_no_value_or_default))
230         else:
231             # Required properties in schema, but not in template
232             if required_props:
233                 ExceptionCollector.appendException(
234                     MissingRequiredFieldError(
235                         what='"properties" of template "%s"' % self.name,
236                         required=required_props))
237
238     def _validate_field(self, template):
239         if not isinstance(template, dict):
240             ExceptionCollector.appendException(
241                 MissingRequiredFieldError(
242                     what='Template "%s"' % self.name, required=self.TYPE))
243         try:
244             relationship = template.get('relationship')
245             if relationship and not isinstance(relationship, str):
246                 relationship[self.TYPE]
247             elif isinstance(relationship, str):
248                 template['relationship']
249             else:
250                 template[self.TYPE]
251         except KeyError:
252             ExceptionCollector.appendException(
253                 MissingRequiredFieldError(
254                     what='Template "%s"' % self.name, required=self.TYPE))
255
256     def _common_validate_field(self, schema, allowedlist, section):
257         for name in schema:
258             if name not in allowedlist:
259                 ExceptionCollector.appendException(
260                     UnknownFieldError(
261                         what=('"%(section)s" of template "%(nodename)s"'
262                               % {'section': section, 'nodename': self.name}),
263                         field=name))
264
265     def _create_properties(self):
266         props = []
267         properties = self.type_definition.get_value(self.PROPERTIES,
268                                                     self.entity_tpl) or {}
269         for name, value in properties.items():
270             props_def = self.type_definition.get_properties_def()
271             if props_def and name in props_def:
272                 prop = Property(name, value,
273                                 props_def[name].schema, self.custom_def)
274                 props.append(prop)
275         for p in self.type_definition.get_properties_def_objects():
276             if p.default is not None and p.name not in properties.keys():
277                 prop = Property(p.name, p.default, p.schema, self.custom_def)
278                 props.append(prop)
279         return props
280
281     def _create_interfaces(self):
282         interfaces = []
283         type_interfaces = None
284         if isinstance(self.type_definition, RelationshipType):
285             if isinstance(self.entity_tpl, dict):
286                 if self.INTERFACES in self.entity_tpl:
287                     type_interfaces = self.entity_tpl[self.INTERFACES]
288                 else:
289                     for rel_def, value in self.entity_tpl.items():
290                         if rel_def != 'type':
291                             rel_def = self.entity_tpl.get(rel_def)
292                             rel = None
293                             if isinstance(rel_def, dict):
294                                 rel = rel_def.get('relationship')
295                             if rel:
296                                 if self.INTERFACES in rel:
297                                     type_interfaces = rel[self.INTERFACES]
298                                     break
299         else:
300             type_interfaces = self.type_definition.get_value(self.INTERFACES,
301                                                              self.entity_tpl)
302         if type_interfaces:
303             for interface_type, value in type_interfaces.items():
304                 for op, op_def in value.items():
305                     iface = InterfacesDef(self.type_definition,
306                                           interfacetype=interface_type,
307                                           node_template=self,
308                                           name=op,
309                                           value=op_def)
310                     interfaces.append(iface)
311         return interfaces
312
313     def get_capability(self, name):
314         """Provide named capability
315
316         :param name: name of capability
317         :return: capability object if found, None otherwise
318         """
319         caps = self.get_capabilities()
320         if caps and name in caps.keys():
321             return caps[name]