Update tosca lib to version 0.5
[parser.git] / tosca2heat / heat-translator / translator / hot / syntax / hot_resource.py
1 #
2 # Licensed under the Apache License, Version 2.0 (the "License"); you may
3 # not use this file except in compliance with the License. You may obtain
4 # a copy of the License at
5 #
6 # http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
10 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
11 # License for the specific language governing permissions and limitations
12 # under the License.
13
14 from collections import OrderedDict
15 import logging
16 import six
17
18 from toscaparser.elements.interfaces import InterfacesDef
19 from toscaparser.functions import GetInput
20 from toscaparser.nodetemplate import NodeTemplate
21 from toscaparser.utils.gettextutils import _
22
23
24 SECTIONS = (TYPE, PROPERTIES, MEDADATA, DEPENDS_ON, UPDATE_POLICY,
25             DELETION_POLICY) = \
26            ('type', 'properties', 'metadata',
27             'depends_on', 'update_policy', 'deletion_policy')
28 log = logging.getLogger('heat-translator')
29
30
31 class HotResource(object):
32     '''Base class for TOSCA node type translation to Heat resource type.'''
33
34     def __init__(self, nodetemplate, name=None, type=None, properties=None,
35                  metadata=None, depends_on=None,
36                  update_policy=None, deletion_policy=None):
37         log.debug(_('Translating TOSCA node type to HOT resource type.'))
38         self.nodetemplate = nodetemplate
39         if name:
40             self.name = name
41         else:
42             self.name = nodetemplate.name
43         self.type = type
44         self.properties = properties or {}
45         # special case for HOT softwareconfig
46         if type == 'OS::Heat::SoftwareConfig':
47             self.properties['group'] = 'script'
48         self.metadata = metadata
49
50         # The difference between depends_on and depends_on_nodes is
51         # that depends_on defines dependency in the context of the
52         # HOT template and it is used during the template output.
53         # Depends_on_nodes defines the direct dependency between the
54         # tosca nodes and is not used during the output of the
55         # HOT template but for internal processing only. When a tosca
56         # node depends on another node it will be always added to
57         # depends_on_nodes but not always to depends_on. For example
58         # if the source of dependency is a server, the dependency will
59         # be added as properties.get_resource and not depends_on
60         if depends_on:
61             self.depends_on = depends_on
62             self.depends_on_nodes = depends_on
63         else:
64             self.depends_on = []
65             self.depends_on_nodes = []
66         self.update_policy = update_policy
67         self.deletion_policy = deletion_policy
68         self.group_dependencies = {}
69         # if hide_resource is set to true, then this resource will not be
70         # generated in the output yaml.
71         self.hide_resource = False
72
73     def handle_properties(self):
74         # the property can hold a value or the intrinsic function get_input
75         # for value, copy it
76         # for get_input, convert to get_param
77         for prop in self.nodetemplate.get_properties_objects():
78             pass
79
80     def handle_life_cycle(self):
81         hot_resources = []
82         deploy_lookup = {}
83         # TODO(anyone):  sequence for life cycle needs to cover different
84         # scenarios and cannot be fixed or hard coded here
85         operations_deploy_sequence = ['create', 'configure', 'start']
86
87         operations = HotResource._get_all_operations(self.nodetemplate)
88
89         # create HotResource for each operation used for deployment:
90         # create, start, configure
91         # ignore the other operations
92         # observe the order:  create, start, configure
93         # use the current HotResource for the first operation in this order
94
95         # hold the original name since it will be changed during
96         # the transformation
97         node_name = self.name
98         reserve_current = 'NONE'
99
100         for operation in operations_deploy_sequence:
101             if operation in operations.keys():
102                 reserve_current = operation
103                 break
104
105         # create the set of SoftwareDeployment and SoftwareConfig for
106         # the interface operations
107         hosting_server = None
108         if self.nodetemplate.requirements is not None:
109             hosting_server = self._get_hosting_server()
110         for operation in operations.values():
111             if operation.name in operations_deploy_sequence:
112                 config_name = node_name + '_' + operation.name + '_config'
113                 deploy_name = node_name + '_' + operation.name + '_deploy'
114                 hot_resources.append(
115                     HotResource(self.nodetemplate,
116                                 config_name,
117                                 'OS::Heat::SoftwareConfig',
118                                 {'config':
119                                     {'get_file': operation.implementation}}))
120
121                 # hosting_server is None if requirements is None
122                 hosting_on_server = (hosting_server.name if
123                                      hosting_server else None)
124                 if operation.name == reserve_current:
125                     deploy_resource = self
126                     self.name = deploy_name
127                     self.type = 'OS::Heat::SoftwareDeployment'
128                     self.properties = {'config': {'get_resource': config_name},
129                                        'server': {'get_resource':
130                                                   hosting_on_server}}
131                     deploy_lookup[operation.name] = self
132                 else:
133                     sd_config = {'config': {'get_resource': config_name},
134                                  'server': {'get_resource':
135                                             hosting_on_server}}
136                     deploy_resource = \
137                         HotResource(self.nodetemplate,
138                                     deploy_name,
139                                     'OS::Heat::SoftwareDeployment',
140                                     sd_config)
141                     hot_resources.append(deploy_resource)
142                     deploy_lookup[operation.name] = deploy_resource
143                 lifecycle_inputs = self._get_lifecycle_inputs(operation)
144                 if lifecycle_inputs:
145                     deploy_resource.properties['input_values'] = \
146                         lifecycle_inputs
147
148         # Add dependencies for the set of HOT resources in the sequence defined
149         # in operations_deploy_sequence
150         # TODO(anyone): find some better way to encode this implicit sequence
151         group = {}
152         for op, hot in deploy_lookup.items():
153             # position to determine potential preceding nodes
154             op_index = operations_deploy_sequence.index(op)
155             for preceding_op in \
156                     reversed(operations_deploy_sequence[:op_index]):
157                 preceding_hot = deploy_lookup.get(preceding_op)
158                 if preceding_hot:
159                     hot.depends_on.append(preceding_hot)
160                     hot.depends_on_nodes.append(preceding_hot)
161                     group[preceding_hot] = hot
162                     break
163
164         # save this dependency chain in the set of HOT resources
165         self.group_dependencies.update(group)
166         for hot in hot_resources:
167             hot.group_dependencies.update(group)
168
169         return hot_resources
170
171     def handle_connectsto(self, tosca_source, tosca_target, hot_source,
172                           hot_target, config_location, operation):
173         # The ConnectsTo relationship causes a configuration operation in
174         # the target.
175         # This hot resource is the software config portion in the HOT template
176         # This method adds the matching software deployment with the proper
177         # target server and dependency
178         if config_location == 'target':
179             hosting_server = hot_target._get_hosting_server()
180             hot_depends = hot_target
181         elif config_location == 'source':
182             hosting_server = self._get_hosting_server()
183             hot_depends = hot_source
184         deploy_name = tosca_source.name + '_' + tosca_target.name + \
185             '_connect_deploy'
186         sd_config = {'config': {'get_resource': self.name},
187                      'server': {'get_resource': hosting_server.name}}
188         deploy_resource = \
189             HotResource(self.nodetemplate,
190                         deploy_name,
191                         'OS::Heat::SoftwareDeployment',
192                         sd_config,
193                         depends_on=[hot_depends])
194         connect_inputs = self._get_connect_inputs(config_location, operation)
195         if connect_inputs:
196             deploy_resource.properties['input_values'] = connect_inputs
197
198         return deploy_resource
199
200     def handle_expansion(self):
201         pass
202
203     def handle_hosting(self):
204         # handle hosting server for the OS:HEAT::SoftwareDeployment
205         # from the TOSCA nodetemplate, traverse the relationship chain
206         # down to the server
207         if self.type == 'OS::Heat::SoftwareDeployment':
208             # skip if already have hosting
209             # If type is NodeTemplate, look up corresponding HotResrouce
210             host_server = self.properties.get('server')
211             if host_server is None or not host_server['get_resource']:
212                 raise Exception(_("Internal Error: expecting host "
213                                   "in software deployment"))
214             elif isinstance(host_server['get_resource'], NodeTemplate):
215                 self.properties['server']['get_resource'] = \
216                     host_server['get_resource'].name
217
218     def top_of_chain(self):
219         dependent = self.group_dependencies.get(self)
220         if dependent is None:
221             return self
222         else:
223             return dependent.top_of_chain()
224
225     def get_dict_output(self):
226         resource_sections = OrderedDict()
227         resource_sections[TYPE] = self.type
228         if self.properties:
229             resource_sections[PROPERTIES] = self.properties
230         if self.metadata:
231             resource_sections[MEDADATA] = self.metadata
232         if self.depends_on:
233             resource_sections[DEPENDS_ON] = []
234             for depend in self.depends_on:
235                 resource_sections[DEPENDS_ON].append(depend.name)
236         if self.update_policy:
237             resource_sections[UPDATE_POLICY] = self.update_policy
238         if self.deletion_policy:
239             resource_sections[DELETION_POLICY] = self.deletion_policy
240
241         return {self.name: resource_sections}
242
243     def _get_lifecycle_inputs(self, operation):
244         # check if this lifecycle operation has input values specified
245         # extract and convert to HOT format
246         if isinstance(operation.value, six.string_types):
247             # the operation has a static string
248             return {}
249         else:
250             # the operation is a dict {'implemenation': xxx, 'input': yyy}
251             inputs = operation.value.get('inputs')
252             deploy_inputs = {}
253             if inputs:
254                 for name, value in six.iteritems(inputs):
255                     deploy_inputs[name] = value
256             return deploy_inputs
257
258     def _get_connect_inputs(self, config_location, operation):
259         if config_location == 'target':
260             inputs = operation.get('pre_configure_target').get('inputs')
261         elif config_location == 'source':
262             inputs = operation.get('pre_configure_source').get('inputs')
263         deploy_inputs = {}
264         if inputs:
265             for name, value in six.iteritems(inputs):
266                 deploy_inputs[name] = value
267         return deploy_inputs
268
269     def _get_hosting_server(self, node_template=None):
270         # find the server that hosts this software by checking the
271         # requirements and following the hosting chain
272         this_node_template = self.nodetemplate \
273             if node_template is None else node_template
274         for requirement in this_node_template.requirements:
275             for requirement_name, assignment in six.iteritems(requirement):
276                 for check_node in this_node_template.related_nodes:
277                     # check if the capability is Container
278                     if isinstance(assignment, dict):
279                         node_name = assignment.get('node')
280                     else:
281                         node_name = assignment
282                     if node_name and node_name == check_node.name:
283                         if self._is_container_type(requirement_name,
284                                                    check_node):
285                             return check_node
286                         elif check_node.related_nodes:
287                             return self._get_hosting_server(check_node)
288         return None
289
290     def _is_container_type(self, requirement_name, node):
291         # capability is a list of dict
292         # For now just check if it's type tosca.nodes.Compute
293         # TODO(anyone): match up requirement and capability
294         base_type = HotResource.get_base_type(node.type_definition)
295         if base_type.type == 'tosca.nodes.Compute':
296             return True
297         else:
298             return False
299
300     def get_hot_attribute(self, attribute, args):
301         # this is a place holder and should be implemented by the subclass
302         # if translation is needed for the particular attribute
303         raise Exception(_("No translation in TOSCA type {0} for attribute "
304                           "{1}").format(self.nodetemplate.type, attribute))
305
306     def get_tosca_props(self):
307         tosca_props = {}
308         for prop in self.nodetemplate.get_properties_objects():
309             if isinstance(prop.value, GetInput):
310                 tosca_props[prop.name] = {'get_param': prop.value.input_name}
311             else:
312                 tosca_props[prop.name] = prop.value
313         return tosca_props
314
315     @staticmethod
316     def _get_all_operations(node):
317         operations = {}
318         for operation in node.interfaces:
319             operations[operation.name] = operation
320
321         node_type = node.type_definition
322         if isinstance(node_type, str) or \
323             node_type.type == "tosca.policies.Placement":
324                 return operations
325
326         while True:
327             type_operations = HotResource._get_interface_operations_from_type(
328                 node_type, node, 'Standard')
329             type_operations.update(operations)
330             operations = type_operations
331
332             if node_type.parent_type is not None:
333                 node_type = node_type.parent_type
334             else:
335                 return operations
336
337     @staticmethod
338     def _get_interface_operations_from_type(node_type, node, lifecycle_name):
339         operations = {}
340         if isinstance(node_type, str) or \
341             node_type.type == "tosca.policies.Placement":
342                 return operations
343         if node_type.interfaces and lifecycle_name in node_type.interfaces:
344             for name, elems in node_type.interfaces[lifecycle_name].items():
345                 # ignore empty operations (only type)
346                 # ignore global interface inputs,
347                 # concrete inputs are on the operations themselves
348                 if name != 'type' and name != 'inputs':
349                     operations[name] = InterfacesDef(node_type,
350                                                      lifecycle_name,
351                                                      node, name, elems)
352         return operations
353
354     @staticmethod
355     def get_base_type(node_type):
356         if node_type.parent_type is not None:
357             if node_type.parent_type.type.endswith('.Root'):
358                 return node_type
359             else:
360                 return HotResource.get_base_type(node_type.parent_type)
361         else:
362             return node_type