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