blob: 6fc4c1bb82461a1c031c4d08afc22d7d69e958fd [file] [log] [blame]
peusterm72f09882018-05-15 17:10:27 +02001# 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).
peusterm00199782017-05-17 08:48:12 +020026from mininet.link import Link
splietker7b38ee12017-06-28 17:24:01 +020027
peusterm72f09882018-05-15 17:10:27 +020028from resources.instance_flavor import InstanceFlavor
29from resources.net import Net
30from resources.port import Port
31from resources.port_pair import PortPair
32from resources.port_pair_group import PortPairGroup
33from resources.flow_classifier import FlowClassifier
34from resources.port_chain import PortChain
35from resources.server import Server
36from resources.image import Image
37
peusterm00199782017-05-17 08:48:12 +020038from docker import DockerClient
39import logging
40import threading
41import uuid
42import time
43import ip_handler as IP
peusterm92190602017-12-19 15:18:11 +010044import hashlib
peusterm00199782017-05-17 08:48:12 +020045
46
peustermbbf4f742017-06-19 11:22:11 +020047LOG = logging.getLogger("api.openstack.compute")
48
49
peusterm00199782017-05-17 08:48:12 +020050class HeatApiStackInvalidException(Exception):
51 """
52 Exception thrown when a submitted stack is invalid.
53 """
54
55 def __init__(self, value):
56 self.value = value
57
58 def __str__(self):
59 return repr(self.value)
60
61
62class OpenstackCompute(object):
63 """
64 This class is a datacenter specific compute object that tracks all containers that are running in a datacenter,
65 as well as networks and configured ports.
66 It has some stack dependet logic and can check if a received stack is valid.
67
68 It also handles start and stop of containers.
69 """
70
71 def __init__(self):
72 self.dc = None
73 self.stacks = dict()
74 self.computeUnits = dict()
75 self.routers = dict()
76 self.flavors = dict()
77 self._images = dict()
78 self.nets = dict()
79 self.ports = dict()
splietker7b38ee12017-06-28 17:24:01 +020080 self.port_pairs = dict()
81 self.port_pair_groups = dict()
82 self.flow_classifiers = dict()
83 self.port_chains = dict()
peusterm00199782017-05-17 08:48:12 +020084 self.compute_nets = dict()
85 self.dcli = DockerClient(base_url='unix://var/run/docker.sock')
86
87 @property
88 def images(self):
89 """
90 Updates the known images. Asks the docker daemon for a list of all known images and returns
91 the new dictionary.
92
93 :return: Returns the new image dictionary.
94 :rtype: ``dict``
95 """
96 for image in self.dcli.images.list():
97 if len(image.tags) > 0:
98 for t in image.tags:
peusterm72f09882018-05-15 17:10:27 +020099 # only use short tag names for OSM compatibility
100 t = t.replace(":latest", "")
peusterm00199782017-05-17 08:48:12 +0200101 if t not in self._images:
102 self._images[t] = Image(t)
103 return self._images
104
105 def add_stack(self, stack):
106 """
107 Adds a new stack to the compute node.
108
109 :param stack: Stack dictionary.
110 :type stack: :class:`heat.resources.stack`
111 """
112 if not self.check_stack(stack):
113 self.clean_broken_stack(stack)
peusterm72f09882018-05-15 17:10:27 +0200114 raise HeatApiStackInvalidException(
115 "Stack did not pass validity checks")
peusterm00199782017-05-17 08:48:12 +0200116 self.stacks[stack.id] = stack
117
118 def clean_broken_stack(self, stack):
119 for port in stack.ports.values():
120 if port.id in self.ports:
121 del self.ports[port.id]
122 for server in stack.servers.values():
123 if server.id in self.computeUnits:
124 del self.computeUnits[server.id]
125 for net in stack.nets.values():
126 if net.id in self.nets:
127 del self.nets[net.id]
128
129 def check_stack(self, stack):
130 """
131 Checks all dependencies of all servers, ports and routers and their most important parameters.
132
133 :param stack: A reference of the stack that should be checked.
134 :type stack: :class:`heat.resources.stack`
135 :return: * *True*: If the stack is completely fine.
136 * *False*: Else
137 :rtype: ``bool``
138 """
139 everything_ok = True
140 for server in stack.servers.values():
141 for port_name in server.port_names:
142 if port_name not in stack.ports:
peustermbbf4f742017-06-19 11:22:11 +0200143 LOG.warning("Server %s of stack %s has a port named %s that is not known." %
peusterm72f09882018-05-15 17:10:27 +0200144 (server.name, stack.stack_name, port_name))
peusterm00199782017-05-17 08:48:12 +0200145 everything_ok = False
146 if server.image is None:
peustermbbf4f742017-06-19 11:22:11 +0200147 LOG.warning("Server %s holds no image." % (server.name))
peusterm00199782017-05-17 08:48:12 +0200148 everything_ok = False
149 if server.command is None:
peustermbbf4f742017-06-19 11:22:11 +0200150 LOG.warning("Server %s holds no command." % (server.name))
peusterm00199782017-05-17 08:48:12 +0200151 everything_ok = False
152 for port in stack.ports.values():
153 if port.net_name not in stack.nets:
peustermbbf4f742017-06-19 11:22:11 +0200154 LOG.warning("Port %s of stack %s has a network named %s that is not known." %
peusterm72f09882018-05-15 17:10:27 +0200155 (port.name, stack.stack_name, port.net_name))
peusterm00199782017-05-17 08:48:12 +0200156 everything_ok = False
157 if port.intf_name is None:
peustermbbf4f742017-06-19 11:22:11 +0200158 LOG.warning("Port %s has no interface name." % (port.name))
peusterm00199782017-05-17 08:48:12 +0200159 everything_ok = False
160 if port.ip_address is None:
peustermbbf4f742017-06-19 11:22:11 +0200161 LOG.warning("Port %s has no IP address." % (port.name))
peusterm00199782017-05-17 08:48:12 +0200162 everything_ok = False
163 for router in stack.routers.values():
164 for subnet_name in router.subnet_names:
165 found = False
166 for net in stack.nets.values():
167 if net.subnet_name == subnet_name:
168 found = True
169 break
170 if not found:
peustermbbf4f742017-06-19 11:22:11 +0200171 LOG.warning("Router %s of stack %s has a network named %s that is not known." %
peusterm72f09882018-05-15 17:10:27 +0200172 (router.name, stack.stack_name, subnet_name))
peusterm00199782017-05-17 08:48:12 +0200173 everything_ok = False
174 return everything_ok
175
peusterm72f09882018-05-15 17:10:27 +0200176 def add_flavor(self, name, cpu, memory,
177 memory_unit, storage, storage_unit):
peusterm00199782017-05-17 08:48:12 +0200178 """
179 Adds a flavor to the stack.
180
181 :param name: Specifies the name of the flavor.
182 :type name: ``str``
183 :param cpu:
184 :type cpu: ``str``
185 :param memory:
186 :type memory: ``str``
187 :param memory_unit:
188 :type memory_unit: ``str``
189 :param storage:
190 :type storage: ``str``
191 :param storage_unit:
192 :type storage_unit: ``str``
193 """
peusterm72f09882018-05-15 17:10:27 +0200194 flavor = InstanceFlavor(
195 name, cpu, memory, memory_unit, storage, storage_unit)
peusterm00199782017-05-17 08:48:12 +0200196 self.flavors[flavor.name] = flavor
197 return flavor
198
199 def deploy_stack(self, stackid):
200 """
201 Deploys the stack and starts the emulation.
202
203 :param stackid: An UUID str of the stack
204 :type stackid: ``str``
205 :return: * *False*: If the Datacenter is None
206 * *True*: Else
207 :rtype: ``bool``
208 """
209 if self.dc is None:
210 return False
211
212 stack = self.stacks[stackid]
213 self.update_compute_dicts(stack)
214
215 # Create the networks first
216 for server in stack.servers.values():
217 self._start_compute(server)
218 return True
219
220 def delete_stack(self, stack_id):
221 """
222 Delete a stack and all its components.
223
224 :param stack_id: An UUID str of the stack
225 :type stack_id: ``str``
226 :return: * *False*: If the Datacenter is None
227 * *True*: Else
228 :rtype: ``bool``
229 """
230 if self.dc is None:
231 return False
232
233 # Stop all servers and their links of this stack
234 for server in self.stacks[stack_id].servers.values():
235 self.stop_compute(server)
236 self.delete_server(server)
237 for net in self.stacks[stack_id].nets.values():
238 self.delete_network(net.id)
239 for port in self.stacks[stack_id].ports.values():
240 self.delete_port(port.id)
241
242 del self.stacks[stack_id]
243 return True
244
245 def update_stack(self, old_stack_id, new_stack):
246 """
247 Determines differences within the old and the new stack and deletes, create or changes only parts that
248 differ between the two stacks.
249
250 :param old_stack_id: The ID of the old stack.
251 :type old_stack_id: ``str``
252 :param new_stack: A reference of the new stack.
253 :type new_stack: :class:`heat.resources.stack`
254 :return: * *True*: if the old stack could be updated to the new stack without any error.
255 * *False*: else
256 :rtype: ``bool``
257 """
peusterm72f09882018-05-15 17:10:27 +0200258 LOG.debug("updating stack {} with new_stack {}".format(
259 old_stack_id, new_stack))
peusterm00199782017-05-17 08:48:12 +0200260 if old_stack_id not in self.stacks:
261 return False
262 old_stack = self.stacks[old_stack_id]
263
264 # Update Stack IDs
265 for server in old_stack.servers.values():
266 if server.name in new_stack.servers:
267 new_stack.servers[server.name].id = server.id
268 for net in old_stack.nets.values():
269 if net.name in new_stack.nets:
270 new_stack.nets[net.name].id = net.id
271 for subnet in new_stack.nets.values():
272 if subnet.subnet_name == net.subnet_name:
273 subnet.subnet_id = net.subnet_id
274 break
275 for port in old_stack.ports.values():
276 if port.name in new_stack.ports:
277 new_stack.ports[port.name].id = port.id
278 for router in old_stack.routers.values():
279 if router.name in new_stack.routers:
280 new_stack.routers[router.name].id = router.id
281
282 # Update the compute dicts to now contain the new_stack components
283 self.update_compute_dicts(new_stack)
284
285 self.update_ip_addresses(old_stack, new_stack)
286
287 # Update all interface names - after each port has the correct UUID!!
288 for port in new_stack.ports.values():
289 port.create_intf_name()
290
291 if not self.check_stack(new_stack):
292 return False
293
294 # Remove unnecessary networks
295 for net in old_stack.nets.values():
peusterm72f09882018-05-15 17:10:27 +0200296 if net.name not in new_stack.nets:
peusterm00199782017-05-17 08:48:12 +0200297 self.delete_network(net.id)
298
299 # Remove all unnecessary servers
300 for server in old_stack.servers.values():
301 if server.name in new_stack.servers:
peusterm72f09882018-05-15 17:10:27 +0200302 if not server.compare_attributes(
303 new_stack.servers[server.name]):
peusterm00199782017-05-17 08:48:12 +0200304 self.stop_compute(server)
305 else:
306 # Delete unused and changed links
307 for port_name in server.port_names:
308 if port_name in old_stack.ports and port_name in new_stack.ports:
peusterm72f09882018-05-15 17:10:27 +0200309 if not old_stack.ports.get(
310 port_name) == new_stack.ports.get(port_name):
peusterm00199782017-05-17 08:48:12 +0200311 my_links = self.dc.net.links
312 for link in my_links:
313 if str(link.intf1) == old_stack.ports[port_name].intf_name and \
peusterm72f09882018-05-15 17:10:27 +0200314 str(link.intf1.ip) == \
315 old_stack.ports[port_name].ip_address.split('/')[0]:
peusterm00199782017-05-17 08:48:12 +0200316 self._remove_link(server.name, link)
317
318 # Add changed link
319 self._add_link(server.name,
320 new_stack.ports[port_name].ip_address,
321 new_stack.ports[port_name].intf_name,
322 new_stack.ports[port_name].net_name)
323 break
324 else:
325 my_links = self.dc.net.links
326 for link in my_links:
327 if str(link.intf1) == old_stack.ports[port_name].intf_name and \
328 str(link.intf1.ip) == old_stack.ports[port_name].ip_address.split('/')[0]:
329 self._remove_link(server.name, link)
330 break
331
332 # Create new links
333 for port_name in new_stack.servers[server.name].port_names:
334 if port_name not in server.port_names:
335 self._add_link(server.name,
336 new_stack.ports[port_name].ip_address,
337 new_stack.ports[port_name].intf_name,
338 new_stack.ports[port_name].net_name)
339 else:
340 self.stop_compute(server)
341
342 # Start all new servers
343 for server in new_stack.servers.values():
344 if server.name not in self.dc.containers:
345 self._start_compute(server)
346 else:
347 server.emulator_compute = self.dc.containers.get(server.name)
348
349 del self.stacks[old_stack_id]
350 self.stacks[new_stack.id] = new_stack
351 return True
352
353 def update_ip_addresses(self, old_stack, new_stack):
354 """
355 Updates the subnet and the port IP addresses - which should always be in this order!
356
357 :param old_stack: The currently running stack
358 :type old_stack: :class:`heat.resources.stack`
359 :param new_stack: The new created stack
360 :type new_stack: :class:`heat.resources.stack`
361 """
362 self.update_subnet_cidr(old_stack, new_stack)
363 self.update_port_addresses(old_stack, new_stack)
364
365 def update_port_addresses(self, old_stack, new_stack):
366 """
367 Updates the port IP addresses. First resets all issued addresses. Then get all IP addresses from the old
368 stack and sets them to the same ports in the new stack. Finally all new or changed instances will get new
369 IP addresses.
370
371 :param old_stack: The currently running stack
372 :type old_stack: :class:`heat.resources.stack`
373 :param new_stack: The new created stack
374 :type new_stack: :class:`heat.resources.stack`
375 """
376 for net in new_stack.nets.values():
377 net.reset_issued_ip_addresses()
378
379 for old_port in old_stack.ports.values():
380 for port in new_stack.ports.values():
381 if port.compare_attributes(old_port):
382 for net in new_stack.nets.values():
383 if net.name == port.net_name:
peusterm72f09882018-05-15 17:10:27 +0200384 if net.assign_ip_address(
385 old_port.ip_address, port.name):
peusterm00199782017-05-17 08:48:12 +0200386 port.ip_address = old_port.ip_address
387 port.mac_address = old_port.mac_address
388 else:
peusterm72f09882018-05-15 17:10:27 +0200389 port.ip_address = net.get_new_ip_address(
390 port.name)
peusterm00199782017-05-17 08:48:12 +0200391
392 for port in new_stack.ports.values():
393 for net in new_stack.nets.values():
peusterm72f09882018-05-15 17:10:27 +0200394 if port.net_name == net.name and not net.is_my_ip(
395 port.ip_address, port.name):
peusterm00199782017-05-17 08:48:12 +0200396 port.ip_address = net.get_new_ip_address(port.name)
397
398 def update_subnet_cidr(self, old_stack, new_stack):
399 """
400 Updates the subnet IP addresses. If the new stack contains subnets from the old stack it will take those
401 IP addresses. Otherwise it will create new IP addresses for the subnet.
402
403 :param old_stack: The currently running stack
404 :type old_stack: :class:`heat.resources.stack`
405 :param new_stack: The new created stack
406 :type new_stack: :class:`heat.resources.stack`
407 """
408 for old_subnet in old_stack.nets.values():
409 IP.free_cidr(old_subnet.get_cidr(), old_subnet.subnet_id)
410
411 for subnet in new_stack.nets.values():
412 subnet.clear_cidr()
413 for old_subnet in old_stack.nets.values():
414 if subnet.subnet_name == old_subnet.subnet_name:
415 if IP.assign_cidr(old_subnet.get_cidr(), subnet.subnet_id):
416 subnet.set_cidr(old_subnet.get_cidr())
417
418 for subnet in new_stack.nets.values():
419 if IP.is_cidr_issued(subnet.get_cidr()):
420 continue
421
422 cird = IP.get_new_cidr(subnet.subnet_id)
423 subnet.set_cidr(cird)
424 return
425
426 def update_compute_dicts(self, stack):
427 """
428 Update and add all stack components tho the compute dictionaries.
429
430 :param stack: A stack reference, to get all required components.
431 :type stack: :class:`heat.resources.stack`
432 """
433 for server in stack.servers.values():
434 self.computeUnits[server.id] = server
435 if isinstance(server.flavor, dict):
436 self.add_flavor(server.flavor['flavorName'],
437 server.flavor['vcpu'],
438 server.flavor['ram'], 'MB',
439 server.flavor['storage'], 'GB')
440 server.flavor = server.flavor['flavorName']
441 for router in stack.routers.values():
442 self.routers[router.id] = router
443 for net in stack.nets.values():
444 self.nets[net.id] = net
445 for port in stack.ports.values():
446 self.ports[port.id] = port
447
448 def _start_compute(self, server):
449 """
450 Starts a new compute object (docker container) inside the emulator.
451 Should only be called by stack modifications and not directly.
452
453 :param server: Specifies the compute resource.
454 :type server: :class:`heat.resources.server`
455 """
peustermbbf4f742017-06-19 11:22:11 +0200456 LOG.debug("Starting new compute resources %s" % server.name)
peusterm00199782017-05-17 08:48:12 +0200457 network = list()
peusterma7161aa2017-06-29 15:32:20 +0200458 network_dict = dict()
peusterm00199782017-05-17 08:48:12 +0200459
460 for port_name in server.port_names:
461 network_dict = dict()
462 port = self.find_port_by_name_or_id(port_name)
463 if port is not None:
464 network_dict['id'] = port.intf_name
465 network_dict['ip'] = port.ip_address
peusterm72f09882018-05-15 17:10:27 +0200466 network_dict[network_dict['id']] = self.find_network_by_name_or_id(
467 port.net_name).name
peusterm00199782017-05-17 08:48:12 +0200468 network.append(network_dict)
peusterma7161aa2017-06-29 15:32:20 +0200469 # default network dict
470 if len(network) < 1:
471 network_dict['id'] = server.name + "-eth0"
472 network_dict[network_dict['id']] = network_dict['id']
473 network.append(network_dict)
peusterm6e354f12017-06-27 12:44:13 +0200474
peusterm00199782017-05-17 08:48:12 +0200475 self.compute_nets[server.name] = network
peusterma7161aa2017-06-29 15:32:20 +0200476 LOG.debug("Network dict: {}".format(network))
peusterm00199782017-05-17 08:48:12 +0200477 c = self.dc.startCompute(server.name, image=server.image, command=server.command,
splietker7b38ee12017-06-28 17:24:01 +0200478 network=network, flavor_name=server.flavor,
479 properties=server.properties)
peusterm00199782017-05-17 08:48:12 +0200480 server.emulator_compute = c
481
482 for intf in c.intfs.values():
483 for port_name in server.port_names:
484 port = self.find_port_by_name_or_id(port_name)
485 if port is not None:
486 if intf.name == port.intf_name:
487 # wait up to one second for the intf to come up
488 self.timeout_sleep(intf.isUp, 1)
489 if port.mac_address is not None:
490 intf.setMAC(port.mac_address)
491 else:
492 port.mac_address = intf.MAC()
493
494 # Start the real emulator command now as specified in the dockerfile
495 # ENV SON_EMU_CMD
496 config = c.dcinfo.get("Config", dict())
497 env = config.get("Env", list())
498 for env_var in env:
499 if "SON_EMU_CMD=" in env_var:
500 cmd = str(env_var.split("=")[1])
501 server.son_emu_command = cmd
peusterm72f09882018-05-15 17:10:27 +0200502 # execute command in new thread to ensure that GK is not
503 # blocked by VNF
peusterm00199782017-05-17 08:48:12 +0200504 t = threading.Thread(target=c.cmdPrint, args=(cmd,))
505 t.daemon = True
506 t.start()
507
508 def stop_compute(self, server):
509 """
510 Determines which links should be removed before removing the server itself.
511
512 :param server: The server that should be removed
513 :type server: ``heat.resources.server``
514 """
peusterm72f09882018-05-15 17:10:27 +0200515 LOG.debug("Stopping container %s with full name %s" %
516 (server.name, server.full_name))
peusterm00199782017-05-17 08:48:12 +0200517 link_names = list()
518 for port_name in server.port_names:
peusterm832a0b82017-07-11 16:08:35 +0200519 prt = self.find_port_by_name_or_id(port_name)
520 if prt is not None:
521 link_names.append(prt.intf_name)
peusterm00199782017-05-17 08:48:12 +0200522 my_links = self.dc.net.links
523 for link in my_links:
524 if str(link.intf1) in link_names:
peusterm72f09882018-05-15 17:10:27 +0200525 # Remove all self created links that connect the server to the
526 # main switch
peusterm00199782017-05-17 08:48:12 +0200527 self._remove_link(server.name, link)
528
529 # Stop the server and the remaining connection to the datacenter switch
530 self.dc.stopCompute(server.name)
531 # Only now delete all its ports and the server itself
532 for port_name in server.port_names:
533 self.delete_port(port_name)
534 self.delete_server(server)
535
536 def find_server_by_name_or_id(self, name_or_id):
537 """
538 Tries to find the server by ID and if this does not succeed then tries to find it via name.
539
540 :param name_or_id: UUID or name of the server.
541 :type name_or_id: ``str``
542 :return: Returns the server reference if it was found or None
543 :rtype: :class:`heat.resources.server`
544 """
545 if name_or_id in self.computeUnits:
546 return self.computeUnits[name_or_id]
547
peusterm3f2f5652017-06-23 15:00:21 +0200548 if self._shorten_server_name(name_or_id) in self.computeUnits:
549 return self.computeUnits[name_or_id]
550
peusterm00199782017-05-17 08:48:12 +0200551 for server in self.computeUnits.values():
peusterm72f09882018-05-15 17:10:27 +0200552 if (server.name == name_or_id or
553 server.template_name == name_or_id or
554 server.full_name == name_or_id):
peusterm00199782017-05-17 08:48:12 +0200555 return server
peusterm72f09882018-05-15 17:10:27 +0200556 if (server.name == self._shorten_server_name(name_or_id) or
557 server.template_name == self._shorten_server_name(name_or_id) or
558 server.full_name == self._shorten_server_name(name_or_id)):
peusterm3f2f5652017-06-23 15:00:21 +0200559 return server
peusterm00199782017-05-17 08:48:12 +0200560 return None
561
562 def create_server(self, name, stack_operation=False):
563 """
564 Creates a server with the specified name. Raises an exception when a server with the given name already
565 exists!
566
567 :param name: Name of the new server.
568 :type name: ``str``
569 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
570 :type stack_operation: ``bool``
571 :return: Returns the created server.
572 :rtype: :class:`heat.resources.server`
573 """
peusterm72f09882018-05-15 17:10:27 +0200574 if self.find_server_by_name_or_id(
575 name) is not None and not stack_operation:
peusterm00199782017-05-17 08:48:12 +0200576 raise Exception("Server with name %s already exists." % name)
peusterm3f2f5652017-06-23 15:00:21 +0200577 safe_name = self._shorten_server_name(name)
578 server = Server(safe_name)
peusterm00199782017-05-17 08:48:12 +0200579 server.id = str(uuid.uuid4())
580 if not stack_operation:
581 self.computeUnits[server.id] = server
582 return server
583
peusterm646b5852017-06-26 09:59:12 +0200584 def _shorten_server_name(self, name, char_limit=9):
peusterm3f2f5652017-06-23 15:00:21 +0200585 """
586 Docker does not like too long instance names.
587 This function provides a shorter name if needed
588 """
peusterm3f2f5652017-06-23 15:00:21 +0200589 if len(name) > char_limit:
peusterm6e354f12017-06-27 12:44:13 +0200590 LOG.info("Long server name: {}".format(name))
peusterm3f2f5652017-06-23 15:00:21 +0200591 # construct a short name
peusterm92190602017-12-19 15:18:11 +0100592 h = hashlib.sha224(name).hexdigest()
593 h = h[0:char_limit]
594 LOG.info("Short server name: {}".format(h))
peusterm3f2f5652017-06-23 15:00:21 +0200595 return name
596
peusterm00199782017-05-17 08:48:12 +0200597 def delete_server(self, server):
598 """
599 Deletes the given server from the stack dictionary and the computeUnits dictionary.
600
601 :param server: Reference of the server that should be deleted.
602 :type server: :class:`heat.resources.server`
603 :return: * *False*: If the server name is not in the correct format ('datacentername_stackname_servername') \
604 or when no stack with the correct stackname was found.
605 * *True*: Else
606 :rtype: ``bool``
607 """
608 if server is None:
609 return False
610 name_parts = server.name.split('_')
peusterm543430b2018-04-24 16:52:51 +0200611 if len(name_parts) > 1:
612 for stack in self.stacks.values():
613 if stack.stack_name == name_parts[1]:
614 stack.servers.pop(server.id, None)
peusterm00199782017-05-17 08:48:12 +0200615 if self.computeUnits.pop(server.id, None) is None:
616 return False
617 return True
618
619 def find_network_by_name_or_id(self, name_or_id):
620 """
621 Tries to find the network by ID and if this does not succeed then tries to find it via name.
622
623 :param name_or_id: UUID or name of the network.
624 :type name_or_id: ``str``
625 :return: Returns the network reference if it was found or None
626 :rtype: :class:`heat.resources.net`
627 """
628 if name_or_id in self.nets:
629 return self.nets[name_or_id]
630 for net in self.nets.values():
631 if net.name == name_or_id:
632 return net
peusterm7c16ebc2018-04-25 15:36:15 +0200633 LOG.warning("Could not find net '{}' in {} or {}"
634 .format(name_or_id,
635 self.nets.keys(),
636 [n.name for n in self.nets.values()]))
peusterm00199782017-05-17 08:48:12 +0200637 return None
638
639 def create_network(self, name, stack_operation=False):
640 """
641 Creates a new network with the given name. Raises an exception when a network with the given name already
642 exists!
643
644 :param name: Name of the new network.
645 :type name: ``str``
646 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
647 :type stack_operation: ``bool``
648 :return: :class:`heat.resources.net`
649 """
peustermbbf4f742017-06-19 11:22:11 +0200650 LOG.debug("Creating network with name %s" % name)
peusterm72f09882018-05-15 17:10:27 +0200651 if self.find_network_by_name_or_id(
652 name) is not None and not stack_operation:
653 LOG.warning(
654 "Creating network with name %s failed, as it already exists" % name)
peusterm00199782017-05-17 08:48:12 +0200655 raise Exception("Network with name %s already exists." % name)
656 network = Net(name)
657 network.id = str(uuid.uuid4())
658 if not stack_operation:
659 self.nets[network.id] = network
660 return network
661
662 def delete_network(self, name_or_id):
663 """
664 Deletes the given network.
665
666 :param name_or_id: Name or UUID of the network.
667 :type name_or_id: ``str``
668 """
669 net = self.find_network_by_name_or_id(name_or_id)
670 if net is None:
peusterm72f09882018-05-15 17:10:27 +0200671 raise Exception(
672 "Network with name or id %s does not exists." % name_or_id)
peusterm00199782017-05-17 08:48:12 +0200673
674 for stack in self.stacks.values():
675 stack.nets.pop(net.name, None)
676
677 self.nets.pop(net.id, None)
678
679 def create_port(self, name, stack_operation=False):
680 """
681 Creates a new port with the given name. Raises an exception when a port with the given name already
682 exists!
683
684 :param name: Name of the new port.
685 :type name: ``str``
686 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
687 :type stack_operation: ``bool``
688 :return: Returns the created port.
689 :rtype: :class:`heat.resources.port`
690 """
691 port = self.find_port_by_name_or_id(name)
692 if port is not None and not stack_operation:
peusterm72f09882018-05-15 17:10:27 +0200693 LOG.warning(
694 "Creating port with name %s failed, as it already exists" % name)
peusterm00199782017-05-17 08:48:12 +0200695 raise Exception("Port with name %s already exists." % name)
peustermbbf4f742017-06-19 11:22:11 +0200696 LOG.debug("Creating port with name %s" % name)
peusterm00199782017-05-17 08:48:12 +0200697 port = Port(name)
698 if not stack_operation:
699 self.ports[port.id] = port
700 port.create_intf_name()
701 return port
702
703 def find_port_by_name_or_id(self, name_or_id):
704 """
705 Tries to find the port by ID and if this does not succeed then tries to find it via name.
706
707 :param name_or_id: UUID or name of the network.
708 :type name_or_id: ``str``
709 :return: Returns the port reference if it was found or None
710 :rtype: :class:`heat.resources.port`
711 """
712 if name_or_id in self.ports:
713 return self.ports[name_or_id]
714 for port in self.ports.values():
715 if port.name == name_or_id or port.template_name == name_or_id:
716 return port
717
718 return None
719
720 def delete_port(self, name_or_id):
721 """
722 Deletes the given port. Raises an exception when the port was not found!
723
724 :param name_or_id: UUID or name of the port.
725 :type name_or_id: ``str``
726 """
727 port = self.find_port_by_name_or_id(name_or_id)
728 if port is None:
peusterm72f09882018-05-15 17:10:27 +0200729 LOG.warning(
730 "Port with name or id %s does not exist. Can't delete it." % name_or_id)
peusterm832a0b82017-07-11 16:08:35 +0200731 return
peusterm00199782017-05-17 08:48:12 +0200732
733 my_links = self.dc.net.links
734 for link in my_links:
735 if str(link.intf1) == port.intf_name and \
peusterm72f09882018-05-15 17:10:27 +0200736 str(link.intf1.ip) == port.ip_address.split('/')[0]:
peusterm00199782017-05-17 08:48:12 +0200737 self._remove_link(link.intf1.node.name, link)
738 break
739
740 self.ports.pop(port.id, None)
741 for stack in self.stacks.values():
742 stack.ports.pop(port.name, None)
743
splietker7b38ee12017-06-28 17:24:01 +0200744 def create_port_pair(self, name, stack_operation=False):
745 """
746 Creates a new port pair with the given name. Raises an exception when a port pair with the given name already
747 exists!
748
749 :param name: Name of the new port pair.
750 :type name: ``str``
751 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
752 :type stack_operation: ``bool``
753 :return: Returns the created port pair.
754 :rtype: :class:`openstack.resources.port_pair`
755 """
756 port_pair = self.find_port_pair_by_name_or_id(name)
757 if port_pair is not None and not stack_operation:
peusterm72f09882018-05-15 17:10:27 +0200758 logging.warning(
759 "Creating port pair with name %s failed, as it already exists" % name)
splietker7b38ee12017-06-28 17:24:01 +0200760 raise Exception("Port pair with name %s already exists." % name)
761 logging.debug("Creating port pair with name %s" % name)
762 port_pair = PortPair(name)
763 if not stack_operation:
764 self.port_pairs[port_pair.id] = port_pair
765 return port_pair
766
767 def find_port_pair_by_name_or_id(self, name_or_id):
768 """
769 Tries to find the port pair by ID and if this does not succeed then tries to find it via name.
770
771 :param name_or_id: UUID or name of the port pair.
772 :type name_or_id: ``str``
773 :return: Returns the port pair reference if it was found or None
774 :rtype: :class:`openstack.resources.port_pair`
775 """
776 if name_or_id in self.port_pairs:
777 return self.port_pairs[name_or_id]
778 for port_pair in self.port_pairs.values():
779 if port_pair.name == name_or_id:
780 return port_pair
781
782 return None
783
784 def delete_port_pair(self, name_or_id):
785 """
786 Deletes the given port pair. Raises an exception when the port pair was not found!
787
788 :param name_or_id: UUID or name of the port pair.
789 :type name_or_id: ``str``
790 """
791 port_pair = self.find_port_pair_by_name_or_id(name_or_id)
792 if port_pair is None:
peusterm72f09882018-05-15 17:10:27 +0200793 raise Exception(
794 "Port pair with name or id %s does not exists." % name_or_id)
splietker7b38ee12017-06-28 17:24:01 +0200795
796 self.port_pairs.pop(port_pair.id, None)
797
798 def create_port_pair_group(self, name, stack_operation=False):
799 """
800 Creates a new port pair group with the given name. Raises an exception when a port pair group
801 with the given name already exists!
802
803 :param name: Name of the new port pair group.
804 :type name: ``str``
805 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
806 :type stack_operation: ``bool``
807 :return: Returns the created port pair group .
808 :rtype: :class:`openstack.resources.port_pair_group`
809 """
810 port_pair_group = self.find_port_pair_group_by_name_or_id(name)
811 if port_pair_group is not None and not stack_operation:
peusterm72f09882018-05-15 17:10:27 +0200812 logging.warning(
813 "Creating port pair group with name %s failed, as it already exists" % name)
814 raise Exception(
815 "Port pair group with name %s already exists." % name)
splietker7b38ee12017-06-28 17:24:01 +0200816 logging.debug("Creating port pair group with name %s" % name)
817 port_pair_group = PortPairGroup(name)
818 if not stack_operation:
819 self.port_pair_groups[port_pair_group.id] = port_pair_group
820 return port_pair_group
821
822 def find_port_pair_group_by_name_or_id(self, name_or_id):
823 """
824 Tries to find the port pair group by ID and if this does not succeed then tries to find it via name.
825
826 :param name_or_id: UUID or name of the port pair group.
827 :type name_or_id: ``str``
828 :return: Returns the port pair group reference if it was found or None
829 :rtype: :class:`openstack.resources.port_pair_group`
830 """
831 if name_or_id in self.port_pair_groups:
832 return self.port_pair_groups[name_or_id]
833 for port_pair_group in self.port_pair_groups.values():
834 if port_pair_group.name == name_or_id:
835 return port_pair_group
836
837 return None
838
839 def delete_port_pair_group(self, name_or_id):
840 """
841 Deletes the given port pair group. Raises an exception when the port pair group was not found!
842
843 :param name_or_id: UUID or name of the port pair group.
844 :type name_or_id: ``str``
845 """
846 port_pair_group = self.find_port_pair_group_by_name_or_id(name_or_id)
847 if port_pair_group is None:
peusterm72f09882018-05-15 17:10:27 +0200848 raise Exception(
849 "Port pair with name or id %s does not exists." % name_or_id)
splietker7b38ee12017-06-28 17:24:01 +0200850
851 self.port_pair_groups.pop(port_pair_group.id, None)
852
853 def create_port_chain(self, name, stack_operation=False):
854 """
855 Creates a new port chain with the given name. Raises an exception when a port chain with the given name already
856 exists!
857
858 :param name: Name of the new port chain
859 :type name: ``str``
860 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
861 :type stack_operation: ``bool``
862 :return: Returns the created port chain.
863 :rtype: :class:`openstack.resources.port_chain.PortChain`
864 """
865 port_chain = self.find_port_chain_by_name_or_id(name)
866 if port_chain is not None and not stack_operation:
peusterm72f09882018-05-15 17:10:27 +0200867 logging.warning(
868 "Creating port chain with name %s failed, as it already exists" % name)
splietker7b38ee12017-06-28 17:24:01 +0200869 raise Exception("Port chain with name %s already exists." % name)
870 logging.debug("Creating port chain with name %s" % name)
871 port_chain = PortChain(name)
872 if not stack_operation:
873 self.port_chains[port_chain.id] = port_chain
874 return port_chain
875
876 def find_port_chain_by_name_or_id(self, name_or_id):
877 """
878 Tries to find the port chain by ID and if this does not succeed then tries to find it via name.
879
880 :param name_or_id: UUID or name of the port chain.
881 :type name_or_id: ``str``
882 :return: Returns the port chain reference if it was found or None
883 :rtype: :class:`openstack.resources.port_chain.PortChain`
884 """
885 if name_or_id in self.port_chains:
886 return self.port_chains[name_or_id]
887 for port_chain in self.port_chains.values():
888 if port_chain.name == name_or_id:
889 return port_chain
890 return None
891
892 def delete_port_chain(self, name_or_id):
893 """
894 Deletes the given port chain. Raises an exception when the port chain was not found!
895
896 :param name_or_id: UUID or name of the port chain.
897 :type name_or_id: ``str``
898 """
899 port_chain = self.find_port_chain_by_name_or_id(name_or_id)
900 port_chain.uninstall(self)
901 if port_chain is None:
peusterm72f09882018-05-15 17:10:27 +0200902 raise Exception(
903 "Port chain with name or id %s does not exists." % name_or_id)
splietker7b38ee12017-06-28 17:24:01 +0200904
905 self.port_chains.pop(port_chain.id, None)
906
907 def create_flow_classifier(self, name, stack_operation=False):
908 """
909 Creates a new flow classifier with the given name. Raises an exception when a flow classifier with the given name already
910 exists!
911
912 :param name: Name of the new flow classifier.
913 :type name: ``str``
914 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
915 :type stack_operation: ``bool``
916 :return: Returns the created flow classifier.
917 :rtype: :class:`openstack.resources.flow_classifier`
918 """
919 flow_classifier = self.find_flow_classifier_by_name_or_id(name)
920 if flow_classifier is not None and not stack_operation:
peusterm72f09882018-05-15 17:10:27 +0200921 logging.warning(
922 "Creating flow classifier with name %s failed, as it already exists" % name)
923 raise Exception(
924 "Flow classifier with name %s already exists." % name)
splietker7b38ee12017-06-28 17:24:01 +0200925 logging.debug("Creating flow classifier with name %s" % name)
926 flow_classifier = FlowClassifier(name)
927 if not stack_operation:
928 self.flow_classifiers[flow_classifier.id] = flow_classifier
929 return flow_classifier
930
931 def find_flow_classifier_by_name_or_id(self, name_or_id):
932 """
933 Tries to find the flow classifier by ID and if this does not succeed then tries to find it via name.
934
935 :param name_or_id: UUID or name of the flow classifier.
936 :type name_or_id: ``str``
937 :return: Returns the flow classifier reference if it was found or None
938 :rtype: :class:`openstack.resources.flow_classifier`
939 """
940 if name_or_id in self.flow_classifiers:
941 return self.flow_classifiers[name_or_id]
942 for flow_classifier in self.flow_classifiers.values():
943 if flow_classifier.name == name_or_id:
944 return flow_classifier
945
946 return None
947
948 def delete_flow_classifier(self, name_or_id):
949 """
950 Deletes the given flow classifier. Raises an exception when the flow classifier was not found!
951
952 :param name_or_id: UUID or name of the flow classifier.
953 :type name_or_id: ``str``
954 """
955 flow_classifier = self.find_flow_classifier_by_name_or_id(name_or_id)
956 if flow_classifier is None:
peusterm72f09882018-05-15 17:10:27 +0200957 raise Exception(
958 "Flow classifier with name or id %s does not exists." % name_or_id)
splietker7b38ee12017-06-28 17:24:01 +0200959
960 self.flow_classifiers.pop(flow_classifier.id, None)
961
peusterm00199782017-05-17 08:48:12 +0200962 def _add_link(self, node_name, ip_address, link_name, net_name):
963 """
964 Adds a new link between datacenter switch and the node with the given name.
965
966 :param node_name: Name of the required node.
967 :type node_name: ``str``
968 :param ip_address: IP-Address of the node.
969 :type ip_address: ``str``
970 :param link_name: Link name.
971 :type link_name: ``str``
972 :param net_name: Network name.
973 :type net_name: ``str``
974 """
975 node = self.dc.net.get(node_name)
976 params = {'params1': {'ip': ip_address,
977 'id': link_name,
978 link_name: net_name},
979 'intfName1': link_name,
980 'cls': Link}
981 link = self.dc.net.addLink(node, self.dc.switch, **params)
982 OpenstackCompute.timeout_sleep(link.intf1.isUp, 1)
983
984 def _remove_link(self, server_name, link):
985 """
986 Removes a link between server and datacenter switch.
987
988 :param server_name: Specifies the server where the link starts.
989 :type server_name: ``str``
990 :param link: A reference of the link which should be removed.
991 :type link: :class:`mininet.link`
992 """
993 self.dc.switch.detach(link.intf2)
994 del self.dc.switch.intfs[self.dc.switch.ports[link.intf2]]
995 del self.dc.switch.ports[link.intf2]
996 del self.dc.switch.nameToIntf[link.intf2.name]
997 self.dc.net.removeLink(link=link)
peusterm00199782017-05-17 08:48:12 +0200998 for intf_key in self.dc.net[server_name].intfs.keys():
999 if self.dc.net[server_name].intfs[intf_key].link == link:
1000 self.dc.net[server_name].intfs[intf_key].delete()
1001 del self.dc.net[server_name].intfs[intf_key]
1002
1003 @staticmethod
1004 def timeout_sleep(function, max_sleep):
1005 """
1006 This function will execute a function all 0.1 seconds until it successfully returns.
1007 Will return after `max_sleep` seconds if not successful.
1008
1009 :param function: The function to execute. Should return true if done.
1010 :type function: ``function``
1011 :param max_sleep: Max seconds to sleep. 1 equals 1 second.
1012 :type max_sleep: ``float``
1013 """
1014 current_time = time.time()
1015 stop_time = current_time + max_sleep
1016 while not function() and current_time < stop_time:
1017 current_time = time.time()
1018 time.sleep(0.1)