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