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