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