Merge "EntityTemplate has no property of parent_type"
[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         else:
85             return None
86
87     @property
88     def parent_type(self):
89         if self.type_definition:
90             return self.type_definition.parent_type
91         else:
92             return None
93
94     @property
95     def requirements(self):
96         if self._requirements is None:
97             self._requirements = self.type_definition.get_value(
98                 self.REQUIREMENTS,
99                 self.entity_tpl) or []
100         return self._requirements
101
102     def get_properties_objects(self):
103         '''Return properties objects for this template.'''
104         if self._properties is None:
105             self._properties = self._create_properties()
106         return self._properties
107
108     def get_properties(self):
109         '''Return a dictionary of property name-object pairs.'''
110         return {prop.name: prop
111                 for prop in self.get_properties_objects()}
112
113     def get_property_value(self, name):
114         '''Return the value of a given property name.'''
115         props = self.get_properties()
116         if props and name in props.keys():
117             return props[name].value
118
119     @property
120     def interfaces(self):
121         if self._interfaces is None:
122             self._interfaces = self._create_interfaces()
123         return self._interfaces
124
125     def get_capabilities_objects(self):
126         '''Return capabilities objects for this template.'''
127         if not self._capabilities:
128             self._capabilities = self._create_capabilities()
129         return self._capabilities
130
131     def get_capabilities(self):
132         '''Return a dictionary of capability name-object pairs.'''
133         return {cap.name: cap
134                 for cap in self.get_capabilities_objects()}
135
136     def is_derived_from(self, type_str):
137         '''Check if object inherits from the given type.
138
139         Returns true if this object is derived from 'type_str'.
140         False otherwise.
141         '''
142         if not self.type:
143             return False
144         elif self.type == type_str:
145             return True
146         elif self.parent_type:
147             return self.parent_type.is_derived_from(type_str)
148         else:
149             return False
150
151     def _create_capabilities(self):
152         capability = []
153         caps = self.type_definition.get_value(self.CAPABILITIES,
154                                               self.entity_tpl, True)
155         if caps:
156             for name, props in caps.items():
157                 capabilities = self.type_definition.get_capabilities()
158                 if name in capabilities.keys():
159                     c = capabilities[name]
160                     properties = {}
161                     # first use the definition default value
162                     if c.properties:
163                         for property_name in c.properties.keys():
164                             prop_def = c.properties[property_name]
165                             if 'default' in prop_def:
166                                 properties[property_name] = prop_def['default']
167                     # then update (if available) with the node properties
168                     if 'properties' in props and props['properties']:
169                         properties.update(props['properties'])
170
171                     cap = Capability(name, properties, c)
172                     capability.append(cap)
173         return capability
174
175     def _validate_properties(self, template, entitytype):
176         properties = entitytype.get_value(self.PROPERTIES, template)
177         self._common_validate_properties(entitytype, properties)
178
179     def _validate_capabilities(self):
180         type_capabilities = self.type_definition.get_capabilities()
181         allowed_caps = \
182             type_capabilities.keys() if type_capabilities else []
183         capabilities = self.type_definition.get_value(self.CAPABILITIES,
184                                                       self.entity_tpl)
185         if capabilities:
186             self._common_validate_field(capabilities, allowed_caps,
187                                         'capabilities')
188             self._validate_capabilities_properties(capabilities)
189
190     def _validate_capabilities_properties(self, capabilities):
191         for cap, props in capabilities.items():
192             capabilitydef = self.get_capability(cap).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]