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