migration to python3 (#1)
[osm/vim-emu.git] / src / emuvim / api / openstack / heat_parser.py
1 # Copyright (c) 2015 SONATA-NFV and Paderborn University
2 # ALL RIGHTS RESERVED.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
7 #
8 # http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 #
16 # Neither the name of the SONATA-NFV, Paderborn University
17 # nor the names of its contributors may be used to endorse or promote
18 # products derived from this software without specific prior written
19 # permission.
20 #
21 # This work has been performed in the framework of the SONATA project,
22 # funded by the European Commission under Grant number 671517 through
23 # the Horizon 2020 and 5G-PPP programmes. The authors would like to
24 # acknowledge the contributions of their colleagues of the SONATA
25 # partner consortium (www.sonata-nfv.eu).
26 # TODO remove when print is no longer needed for debugging
27 from __future__ import print_function
28 from emuvim.api.openstack.resources.router import Router
29 from datetime import datetime
30 import re
31 import sys
32 import uuid
33 import logging
34 import emuvim.api.openstack.ip_handler as IP
35
36
37 LOG = logging.getLogger("api.openstack.heat.parser")
38
39
40 class HeatParser:
41 """
42 The HeatParser will parse a heat dictionary and create a stack and its components, to instantiate it within son-emu.
43 """
44
45 def __init__(self, compute):
46 self.description = None
47 self.parameter_groups = None
48 self.parameters = None
49 self.resources = None
50 self.outputs = None
51 self.compute = compute
52 self.bufferResource = list()
53
54 def parse_input(self, input_dict, stack, dc_label, stack_update=False):
55 """
56 It will parse the input dictionary into the corresponding classes, which are then stored within the stack.
57
58 :param input_dict: Dictionary with the template version and resources.
59 :type input_dict: ``dict``
60 :param stack: Reference of the stack that should finally contain all created classes.
61 :type stack: :class:`heat.resources.stack`
62 :param dc_label: String that contains the label of the used data center.
63 :type dc_label: ``str``
64 :param stack_update: Specifies if a new stack will be created or a older one will be updated
65 :type stack_update: ``bool``
66 :return: * *True*: If the template version is supported and all resources could be created.
67 * *False*: Else
68 :rtype: ``bool``
69 """
70 if not self.check_template_version(
71 str(input_dict['heat_template_version'])):
72 print('Unsupported template version: ' + input_dict['heat_template_version'], file=sys.stderr)
73 return False
74
75 self.description = input_dict.get('description', None)
76 self.parameter_groups = input_dict.get('parameter_groups', None)
77 self.parameters = input_dict.get('parameters', None)
78 self.resources = input_dict.get('resources', None)
79 self.outputs = input_dict.get('outputs', None)
80 # clear bufferResources
81 self.bufferResource = list()
82
83 for resource in self.resources.values():
84 self.handle_resource(resource, stack, dc_label,
85 stack_update=stack_update)
86
87 # This loop tries to create all classes which had unresolved
88 # dependencies.
89 unresolved_resources_last_round = len(self.bufferResource) + 1
90 while len(self.bufferResource) > 0 and unresolved_resources_last_round > len(
91 self.bufferResource):
92 unresolved_resources_last_round = len(self.bufferResource)
93 number_of_items = len(self.bufferResource)
94 while number_of_items > 0:
95 self.handle_resource(self.bufferResource.pop(
96 0), stack, dc_label, stack_update=stack_update)
97 number_of_items -= 1
98
99 if len(self.bufferResource) > 0:
100 print(str(len(self.bufferResource)) +
101 ' classes of the HOT could not be created, because the dependencies could not be found.')
102 print("the problem classes are:")
103 for br in self.bufferResource:
104 print("class: %s" % str(br))
105 return False
106 return True
107
108 def handle_resource(self, resource, stack, dc_label, stack_update=False):
109 """
110 This function will take a resource (from a heat template) and determines which type it is and creates
111 the corresponding class, with its required parameters, for further calculations (like deploying the stack).
112 If it is not possible to create the class, because of unresolved dependencies, it will buffer the resource
113 within the 'self.bufferResource' list.
114
115 :param resource: Dict which contains all important informations about the type and parameters.
116 :type resource: ``dict``
117 :param stack: Reference of the stack that should finally contain the created class.
118 :type stack: :class:`heat.resources.stack`
119 :param dc_label: String that contains the label of the used data center
120 :type dc_label: ``str``
121 :param stack_update: Specifies if a new stack will be created or a older one will be updated
122 :type stack_update: ``bool``
123 :return: void
124 :rtype: ``None``
125 """
126 if "OS::Neutron::Net" in resource['type']:
127 try:
128 net_name = resource['properties']['name']
129 if net_name not in stack.nets:
130 stack.nets[net_name] = self.compute.create_network(
131 net_name, True)
132
133 except Exception as e:
134 LOG.warning('Could not create Net: ' + str(e))
135 return
136
137 if 'OS::Neutron::Subnet' in resource['type'] and "Net" not in resource['type']:
138 try:
139 net_name = resource['properties']['network']['get_resource']
140 if net_name not in stack.nets:
141 net = self.compute.create_network(net_name, stack_update)
142 stack.nets[net_name] = net
143 else:
144 net = stack.nets[net_name]
145
146 net.subnet_name = resource['properties']['name']
147 if 'gateway_ip' in resource['properties']:
148 net.gateway_ip = resource['properties']['gateway_ip']
149 net.subnet_id = resource['properties'].get(
150 'id', str(uuid.uuid4()))
151 net.subnet_creation_time = str(datetime.now())
152 if not stack_update:
153 net.set_cidr(IP.get_new_cidr(net.subnet_id))
154 except Exception as e:
155 LOG.warning('Could not create Subnet: ' + str(e))
156 return
157
158 if 'OS::Neutron::Port' in resource['type']:
159 try:
160 port_name = resource['properties']['name']
161 if port_name not in stack.ports:
162 port = self.compute.create_port(port_name, stack_update)
163 stack.ports[port_name] = port
164 else:
165 port = stack.ports[port_name]
166
167 if str(resource['properties']['network']
168 ['get_resource']) in stack.nets:
169 net = stack.nets[resource['properties']
170 ['network']['get_resource']]
171 if net.subnet_id is not None:
172 port.net_name = net.name
173 port.ip_address = net.get_new_ip_address(port.name)
174 return
175 except Exception as e:
176 LOG.warning('Could not create Port: ' + str(e))
177 self.bufferResource.append(resource)
178 return
179
180 if 'OS::Nova::Server' in resource['type']:
181 try:
182 compute_name = str(dc_label) + '_' + str(stack.stack_name) + \
183 '_' + str(resource['properties']['name'])
184 shortened_name = str(dc_label) + '_' + str(stack.stack_name) + '_' + \
185 self.shorten_server_name(
186 str(resource['properties']['name']), stack)
187 nw_list = resource['properties']['networks']
188
189 if shortened_name not in stack.servers:
190 server = self.compute.create_server(
191 shortened_name, stack_update)
192 stack.servers[shortened_name] = server
193 else:
194 server = stack.servers[shortened_name]
195
196 server.full_name = compute_name
197 server.template_name = str(resource['properties']['name'])
198 server.command = resource['properties'].get(
199 'command', '/bin/sh')
200 server.image = resource['properties']['image']
201 server.flavor = resource['properties']['flavor']
202
203 for port in nw_list:
204 port_name = port['port']['get_resource']
205 # just create a port
206 # we don't know which network it belongs to yet, but the resource will appear later in a valid
207 # template
208 if port_name not in stack.ports:
209 stack.ports[port_name] = self.compute.create_port(
210 port_name, stack_update)
211 server.port_names.append(port_name)
212 return
213 except Exception as e:
214 LOG.warning('Could not create Server: ' + str(e))
215 return
216
217 if 'OS::Neutron::RouterInterface' in resource['type']:
218 try:
219 router_name = None
220 subnet_name = resource['properties']['subnet']['get_resource']
221
222 if 'get_resource' in resource['properties']['router']:
223 router_name = resource['properties']['router']['get_resource']
224 else:
225 router_name = resource['properties']['router']
226
227 if router_name not in stack.routers:
228 stack.routers[router_name] = Router(router_name)
229
230 for tmp_net in stack.nets.values():
231 if tmp_net.subnet_name == subnet_name:
232 stack.routers[router_name].add_subnet(subnet_name)
233 return
234 except Exception as e:
235 LOG.warning(
236 'Could not create RouterInterface: ' + e.__repr__())
237 self.bufferResource.append(resource)
238 return
239
240 if 'OS::Neutron::FloatingIP' in resource['type']:
241 try:
242 port_name = resource['properties']['port_id']['get_resource']
243 floating_network_id = resource['properties']['floating_network_id']
244 if port_name not in stack.ports:
245 stack.ports[port_name] = self.compute.create_port(
246 port_name, stack_update)
247
248 stack.ports[port_name].floating_ip = floating_network_id
249 except Exception as e:
250 LOG.warning('Could not create FloatingIP: ' + str(e))
251 return
252
253 if 'OS::Neutron::Router' in resource['type']:
254 try:
255 name = resource['properties']['name']
256 if name not in stack.routers:
257 stack.routers[name] = Router(name)
258 except Exception as e:
259 print('Could not create Router: ' + str(e))
260 return
261
262 if 'OS::Heat::ResourceGroup' in resource['type']:
263 try:
264 embedded_resource = resource['properties']['resource_def']
265 LOG.debug("Found resource in resource group: {}".format(
266 embedded_resource))
267 # recursively parse embedded resource
268 self.handle_resource(
269 embedded_resource, stack, dc_label, stack_update)
270 except Exception as e:
271 print('Could not create Router: ' + str(e))
272 return
273
274 LOG.warning(
275 'Could not determine resource type: {}'.format(resource['type']))
276 return
277
278 def shorten_server_name(self, server_name, stack):
279 """
280 Shortens the server name to a maximum of 12 characters plus the iterator string, if the original name was
281 used before.
282
283 :param server_name: The original server name.
284 :type server_name: ``str``
285 :param stack: A reference to the used stack.
286 :type stack: :class:`heat.resources.stack`
287 :return: A string with max. 12 characters plus iterator string.
288 :rtype: ``str``
289 """
290 server_name = self.shorten_name(server_name, 12)
291 iterator = 0
292 while server_name in stack.servers:
293 server_name = server_name[0:12] + str(iterator)
294 iterator += 1
295 return server_name
296
297 def shorten_name(self, name, max_size):
298 """
299 Shortens the name to max_size characters and replaces all '-' with '_'.
300
301 :param name: The original string.
302 :type name: ``str``
303 :param max_size: The number of allowed characters.
304 :type max_size: ``int``
305 :return: String with at most max_size characters and without '-'.
306 :rtype: ``str``
307 """
308 shortened_name = name.split(':', 1)[0]
309 shortened_name = shortened_name.replace("-", "_")
310 shortened_name = shortened_name[0:max_size]
311 return shortened_name
312
313 def check_template_version(self, version_string):
314 """
315 Checks if a version string is equal or later than 30-04-2015
316
317 :param version_string: String with the version.
318 :type version_string: ``str``
319 :return: * *True*: if the version is equal or later 30-04-2015.
320 * *False*: else
321 :rtype: ``bool``
322 """
323 r = re.compile('\d{4}-\d{2}-\d{2}')
324 if not r.match(version_string):
325 return False
326
327 year, month, day = map(int, version_string.split('-', 2))
328 if year < 2015:
329 return False
330 if year == 2015:
331 if month < 0o4:
332 return False
333 if month == 0o4 and day < 30:
334 return False
335 return True