Synchronise the openstack bugs
[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 parent_type(self):
87         if self.type_definition:
88             return self.type_definition.parent_type
89
90     @property
91     def requirements(self):
92         if self._requirements is None:
93             self._requirements = self.type_definition.get_value(
94                 self.REQUIREMENTS,
95                 self.entity_tpl) or []
96         return self._requirements
97
98     def get_properties_objects(self):
99         '''Return properties objects for this template.'''
100         if self._properties is None:
101             self._properties = self._create_properties()
102         return self._properties
103
104     def get_properties(self):
105         '''Return a dictionary of property name-object pairs.'''
106         return {prop.name: prop
107                 for prop in self.get_properties_objects()}
108
109     def get_property_value(self, name):
110         '''Return the value of a given property name.'''
111         props = self.get_properties()
112         if props and name in props.keys():
113             return props[name].value
114
115     @property
116     def interfaces(self):
117         if self._interfaces is None:
118             self._interfaces = self._create_interfaces()
119         return self._interfaces
120
121     def get_capabilities_objects(self):
122         '''Return capabilities objects for this template.'''
123         if not self._capabilities:
124             self._capabilities = self._create_capabilities()
125         return self._capabilities
126
127     def get_capabilities(self):
128         '''Return a dictionary of capability name-object pairs.'''
129         return {cap.name: cap
130                 for cap in self.get_capabilities_objects()}
131
132     def is_derived_from(self, type_str):
133         '''Check if object inherits from the given type.
134
135         Returns true if this object is derived from 'type_str'.
136         False otherwise.
137         '''
138         if not self.type:
139             return False
140         elif self.type == type_str:
141             return True
142         elif self.parent_type:
143             return self.parent_type.is_derived_from(type_str)
144         else:
145             return False
146
147     def _create_capabilities(self):
148         capability = []
149         caps = self.type_definition.get_value(self.CAPABILITIES,
150                                               self.entity_tpl, True)
151         if caps:
152             for name, props in caps.items():
153                 capabilities = self.type_definition.get_capabilities()
154                 if name in capabilities.keys():
155                     c = capabilities[name]
156                     properties = {}
157                     # first use the definition default value
158                     if c.properties:
159                         for property_name in c.properties.keys():
160                             prop_def = c.properties[property_name]
161                             if 'default' in prop_def:
162                                 properties[property_name] = prop_def['default']
163                     # then update (if available) with the node properties
164                     if 'properties' in props and props['properties']:
165                         properties.update(props['properties'])
166
167                     cap = Capability(name, properties, c)
168                     capability.append(cap)
169         return capability
170
171     def _validate_properties(self, template, entitytype):
172         properties = entitytype.get_value(self.PROPERTIES, template)
173         self._common_validate_properties(entitytype, properties)
174
175     def _validate_capabilities(self):
176         type_capabilities = self.type_definition.get_capabilities()
177         allowed_caps = \
178             type_capabilities.keys() if type_capabilities else []
179         capabilities = self.type_definition.get_value(self.CAPABILITIES,
180                                                       self.entity_tpl)
181         if capabilities:
182             self._common_validate_field(capabilities, allowed_caps,
183                                         'capabilities')
184             self._validate_capabilities_properties(capabilities)
185
186     def _validate_capabilities_properties(self, capabilities):
187         for cap, props in capabilities.items():
188             capability = self.get_capability(cap)
189             if not capability:
190                 continue
191             capabilitydef = capability.definition
192             self._common_validate_properties(capabilitydef,
193                                              props[self.PROPERTIES])
194
195             # validating capability properties values
196             for prop in self.get_capability(cap).get_properties_objects():
197                 prop.validate()
198
199                 # TODO(srinivas_tadepalli): temporary work around to validate
200                 # default_instances until standardized in specification
201                 if cap == "scalable" and prop.name == "default_instances":
202                     prop_dict = props[self.PROPERTIES]
203                     min_instances = prop_dict.get("min_instances")
204                     max_instances = prop_dict.get("max_instances")
205                     default_instances = prop_dict.get("default_instances")
206                     if not (min_instances <= default_instances
207                             <= max_instances):
208                         err_msg = ('"properties" of template "%s": '
209                                    '"default_instances" value is not between '
210                                    '"min_instances" and "max_instances".' %
211                                    self.name)
212                         ExceptionCollector.appendException(
213                             ValidationError(message=err_msg))
214
215     def _common_validate_properties(self, entitytype, properties):
216         allowed_props = []
217         required_props = []
218         for p in entitytype.get_properties_def_objects():
219             allowed_props.append(p.name)
220             # If property is 'required' and has no 'default' value then record
221             if p.required and p.default is None:
222                 required_props.append(p.name)
223         # validate all required properties have values
224         if properties:
225             req_props_no_value_or_default = []
226             self._common_validate_field(properties, allowed_props,
227                                         'properties')
228             # make sure it's not missing any property required by a tosca type
229             for r in required_props:
230                 if r not in properties.keys():
231                     req_props_no_value_or_default.append(r)
232             # Required properties found without value or a default value
233             if req_props_no_value_or_default:
234                 ExceptionCollector.appendException(
235                     MissingRequiredFieldError(
236                         what='"properties" of template "%s"' % self.name,
237                         required=req_props_no_value_or_default))
238         else:
239             # Required properties in schema, but not in template
240             if required_props:
241                 ExceptionCollector.appendException(
242                     MissingRequiredFieldError(
243                         what='"properties" of template "%s"' % self.name,
244                         required=required_props))
245
246     def _validate_field(self, template):
247         if not isinstance(template, dict):
248             ExceptionCollector.appendException(
249                 MissingRequiredFieldError(
250                     what='Template "%s"' % self.name, required=self.TYPE))
251         try:
252             relationship = template.get('relationship')
253             if relationship and not isinstance(relationship, str):
254                 relationship[self.TYPE]
255             elif isinstance(relationship, str):
256                 template['relationship']
257             else:
258                 template[self.TYPE]
259         except KeyError:
260             ExceptionCollector.appendException(
261                 MissingRequiredFieldError(
262                     what='Template "%s"' % self.name, required=self.TYPE))
263
264     def _common_validate_field(self, schema, allowedlist, section):
265         for name in schema:
266             if name not in allowedlist:
267                 ExceptionCollector.appendException(
268                     UnknownFieldError(
269                         what=('"%(section)s" of template "%(nodename)s"'
270                               % {'section': section, 'nodename': self.name}),
271                         field=name))
272
273     def _create_properties(self):
274         props = []
275         properties = self.type_definition.get_value(self.PROPERTIES,
276                                                     self.entity_tpl) or {}
277         for name, value in properties.items():
278             props_def = self.type_definition.get_properties_def()
279             if props_def and name in props_def:
280                 prop = Property(name, value,
281                                 props_def[name].schema, self.custom_def)
282                 props.append(prop)
283         for p in self.type_definition.get_properties_def_objects():
284             if p.default is not None and p.name not in properties.keys():
285                 prop = Property(p.name, p.default, p.schema, self.custom_def)
286                 props.append(prop)
287         return props
288
289     def _create_interfaces(self):
290         interfaces = []
291         type_interfaces = None
292         if isinstance(self.type_definition, RelationshipType):
293             if isinstance(self.entity_tpl, dict):
294                 if self.INTERFACES in self.entity_tpl:
295                     type_interfaces = self.entity_tpl[self.INTERFACES]
296                 else:
297                     for rel_def, value in self.entity_tpl.items():
298                         if rel_def != 'type':
299                             rel_def = self.entity_tpl.get(rel_def)
300                             rel = None
301                             if isinstance(rel_def, dict):
302                                 rel = rel_def.get('relationship')
303                             if rel:
304                                 if self.INTERFACES in rel:
305                                     type_interfaces = rel[self.INTERFACES]
306                                     break
307         else:
308             type_interfaces = self.type_definition.get_value(self.INTERFACES,
309                                                              self.entity_tpl)
310         if type_interfaces:
311             for interface_type, value in type_interfaces.items():
312                 for op, op_def in value.items():
313                     iface = InterfacesDef(self.type_definition,
314                                           interfacetype=interface_type,
315                                           node_template=self,
316                                           name=op,
317                                           value=op_def)
318                     interfaces.append(iface)
319         return interfaces
320
321     def get_capability(self, name):
322         """Provide named capability
323
324         :param name: name of capability
325         :return: capability object if found, None otherwise
326         """
327         caps = self.get_capabilities()
328         if caps and name in caps.keys():
329             return caps[name]