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