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
6 # http://www.apache.org/licenses/LICENSE-2.0
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
14 from collections import OrderedDict
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 _
24 SECTIONS = (TYPE, PROPERTIES, MEDADATA, DEPENDS_ON, UPDATE_POLICY,
26 ('type', 'properties', 'metadata',
27 'depends_on', 'update_policy', 'deletion_policy')
28 log = logging.getLogger('heat-translator')
31 class HotResource(object):
32 '''Base class for TOSCA node type translation to Heat resource type.'''
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
42 self.name = nodetemplate.name
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
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
61 self.depends_on = depends_on
62 self.depends_on_nodes = 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
73 def handle_properties(self):
74 # the property can hold a value or the intrinsic function get_input
76 # for get_input, convert to get_param
77 for prop in self.nodetemplate.get_properties_objects():
80 def handle_life_cycle(self):
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']
87 operations = HotResource._get_all_operations(self.nodetemplate)
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
95 # hold the original name since it will be changed during
98 reserve_current = 'NONE'
100 for operation in operations_deploy_sequence:
101 if operation in operations.keys():
102 reserve_current = operation
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,
117 'OS::Heat::SoftwareConfig',
119 {'get_file': operation.implementation}}))
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':
131 deploy_lookup[operation.name] = self
133 sd_config = {'config': {'get_resource': config_name},
134 'server': {'get_resource':
137 HotResource(self.nodetemplate,
139 'OS::Heat::SoftwareDeployment',
141 hot_resources.append(deploy_resource)
142 deploy_lookup[operation.name] = deploy_resource
143 lifecycle_inputs = self._get_lifecycle_inputs(operation)
145 deploy_resource.properties['input_values'] = \
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
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)
159 hot.depends_on.append(preceding_hot)
160 hot.depends_on_nodes.append(preceding_hot)
161 group[preceding_hot] = hot
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)
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
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 + \
186 sd_config = {'config': {'get_resource': self.name},
187 'server': {'get_resource': hosting_server.name}}
189 HotResource(self.nodetemplate,
191 'OS::Heat::SoftwareDeployment',
193 depends_on=[hot_depends])
194 connect_inputs = self._get_connect_inputs(config_location, operation)
196 deploy_resource.properties['input_values'] = connect_inputs
198 return deploy_resource
200 def handle_expansion(self):
203 def handle_hosting(self):
204 # handle hosting server for the OS:HEAT::SoftwareDeployment
205 # from the TOSCA nodetemplate, traverse the relationship chain
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
218 def top_of_chain(self):
219 dependent = self.group_dependencies.get(self)
220 if dependent is None:
223 return dependent.top_of_chain()
225 def get_dict_output(self):
226 resource_sections = OrderedDict()
227 resource_sections[TYPE] = self.type
229 resource_sections[PROPERTIES] = self.properties
231 resource_sections[MEDADATA] = self.metadata
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
241 return {self.name: resource_sections}
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
250 # the operation is a dict {'implemenation': xxx, 'input': yyy}
251 inputs = operation.value.get('inputs')
254 for name, value in six.iteritems(inputs):
255 deploy_inputs[name] = value
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')
265 for name, value in six.iteritems(inputs):
266 deploy_inputs[name] = value
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')
281 node_name = assignment
282 if node_name and node_name == check_node.name:
283 if self._is_container_type(requirement_name,
286 elif check_node.related_nodes:
287 return self._get_hosting_server(check_node)
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':
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))
306 def get_tosca_props(self):
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}
312 tosca_props[prop.name] = prop.value
316 def _get_all_operations(node):
318 for operation in node.interfaces:
319 operations[operation.name] = operation
321 node_type = node.type_definition
322 if isinstance(node_type, str) or \
323 node_type.type == "tosca.policies.Placement"or \
324 node_type.type == "tosca.policies.Colocate" or \
325 node_type.type == "tosca.policies.Antilocate":
329 type_operations = HotResource._get_interface_operations_from_type(
330 node_type, node, 'Standard')
331 type_operations.update(operations)
332 operations = type_operations
334 if node_type.parent_type is not None:
335 node_type = node_type.parent_type
340 def _get_interface_operations_from_type(node_type, node, lifecycle_name):
342 if isinstance(node_type, str) or \
343 node_type.type == "tosca.policies.Placement" or \
344 node_type.type == "tosca.policies.Colocate" or \
345 node_type.type == "tosca.policies.Antilocate":
347 if node_type.interfaces and lifecycle_name in node_type.interfaces:
348 for name, elems in node_type.interfaces[lifecycle_name].items():
349 # ignore empty operations (only type)
350 # ignore global interface inputs,
351 # concrete inputs are on the operations themselves
352 if name != 'type' and name != 'inputs':
353 operations[name] = InterfacesDef(node_type,
359 def get_base_type(node_type):
360 if node_type.parent_type is not None:
361 if node_type.parent_type.type.endswith('.Root') or \
362 node_type.type == "tosca.policies.Colocate" or \
363 node_type.type == "tosca.policies.Antilocate":
366 return HotResource.get_base_type(node_type.parent_type)