Fix: Name shortening. Still an ugly fix but ok for now.
[osm/vim-emu.git] / src / emuvim / api / openstack / compute.py
1 from mininet.link import Link
2
3 from resources import *
4 from docker import DockerClient
5 import logging
6 import threading
7 import uuid
8 import time
9 import ip_handler as IP
10
11
12 LOG = logging.getLogger("api.openstack.compute")
13
14
15 class HeatApiStackInvalidException(Exception):
16 """
17 Exception thrown when a submitted stack is invalid.
18 """
19
20 def __init__(self, value):
21 self.value = value
22
23 def __str__(self):
24 return repr(self.value)
25
26
27 class OpenstackCompute(object):
28 """
29 This class is a datacenter specific compute object that tracks all containers that are running in a datacenter,
30 as well as networks and configured ports.
31 It has some stack dependet logic and can check if a received stack is valid.
32
33 It also handles start and stop of containers.
34 """
35
36 def __init__(self):
37 self.dc = None
38 self.stacks = dict()
39 self.computeUnits = dict()
40 self.routers = dict()
41 self.flavors = dict()
42 self._images = dict()
43 self.nets = dict()
44 self.ports = dict()
45 self.port_pairs = dict()
46 self.port_pair_groups = dict()
47 self.flow_classifiers = dict()
48 self.port_chains = dict()
49 self.compute_nets = dict()
50 self.dcli = DockerClient(base_url='unix://var/run/docker.sock')
51
52 @property
53 def images(self):
54 """
55 Updates the known images. Asks the docker daemon for a list of all known images and returns
56 the new dictionary.
57
58 :return: Returns the new image dictionary.
59 :rtype: ``dict``
60 """
61 for image in self.dcli.images.list():
62 if len(image.tags) > 0:
63 for t in image.tags:
64 t = t.replace(":latest", "") # only use short tag names for OSM compatibility
65 if t not in self._images:
66 self._images[t] = Image(t)
67 return self._images
68
69 def add_stack(self, stack):
70 """
71 Adds a new stack to the compute node.
72
73 :param stack: Stack dictionary.
74 :type stack: :class:`heat.resources.stack`
75 """
76 if not self.check_stack(stack):
77 self.clean_broken_stack(stack)
78 raise HeatApiStackInvalidException("Stack did not pass validity checks")
79 self.stacks[stack.id] = stack
80
81 def clean_broken_stack(self, stack):
82 for port in stack.ports.values():
83 if port.id in self.ports:
84 del self.ports[port.id]
85 for server in stack.servers.values():
86 if server.id in self.computeUnits:
87 del self.computeUnits[server.id]
88 for net in stack.nets.values():
89 if net.id in self.nets:
90 del self.nets[net.id]
91
92 def check_stack(self, stack):
93 """
94 Checks all dependencies of all servers, ports and routers and their most important parameters.
95
96 :param stack: A reference of the stack that should be checked.
97 :type stack: :class:`heat.resources.stack`
98 :return: * *True*: If the stack is completely fine.
99 * *False*: Else
100 :rtype: ``bool``
101 """
102 everything_ok = True
103 for server in stack.servers.values():
104 for port_name in server.port_names:
105 if port_name not in stack.ports:
106 LOG.warning("Server %s of stack %s has a port named %s that is not known." %
107 (server.name, stack.stack_name, port_name))
108 everything_ok = False
109 if server.image is None:
110 LOG.warning("Server %s holds no image." % (server.name))
111 everything_ok = False
112 if server.command is None:
113 LOG.warning("Server %s holds no command." % (server.name))
114 everything_ok = False
115 for port in stack.ports.values():
116 if port.net_name not in stack.nets:
117 LOG.warning("Port %s of stack %s has a network named %s that is not known." %
118 (port.name, stack.stack_name, port.net_name))
119 everything_ok = False
120 if port.intf_name is None:
121 LOG.warning("Port %s has no interface name." % (port.name))
122 everything_ok = False
123 if port.ip_address is None:
124 LOG.warning("Port %s has no IP address." % (port.name))
125 everything_ok = False
126 for router in stack.routers.values():
127 for subnet_name in router.subnet_names:
128 found = False
129 for net in stack.nets.values():
130 if net.subnet_name == subnet_name:
131 found = True
132 break
133 if not found:
134 LOG.warning("Router %s of stack %s has a network named %s that is not known." %
135 (router.name, stack.stack_name, subnet_name))
136 everything_ok = False
137 return everything_ok
138
139 def add_flavor(self, name, cpu, memory, memory_unit, storage, storage_unit):
140 """
141 Adds a flavor to the stack.
142
143 :param name: Specifies the name of the flavor.
144 :type name: ``str``
145 :param cpu:
146 :type cpu: ``str``
147 :param memory:
148 :type memory: ``str``
149 :param memory_unit:
150 :type memory_unit: ``str``
151 :param storage:
152 :type storage: ``str``
153 :param storage_unit:
154 :type storage_unit: ``str``
155 """
156 flavor = InstanceFlavor(name, cpu, memory, memory_unit, storage, storage_unit)
157 self.flavors[flavor.name] = flavor
158 return flavor
159
160 def deploy_stack(self, stackid):
161 """
162 Deploys the stack and starts the emulation.
163
164 :param stackid: An UUID str of the stack
165 :type stackid: ``str``
166 :return: * *False*: If the Datacenter is None
167 * *True*: Else
168 :rtype: ``bool``
169 """
170 if self.dc is None:
171 return False
172
173 stack = self.stacks[stackid]
174 self.update_compute_dicts(stack)
175
176 # Create the networks first
177 for server in stack.servers.values():
178 self._start_compute(server)
179 return True
180
181 def delete_stack(self, stack_id):
182 """
183 Delete a stack and all its components.
184
185 :param stack_id: An UUID str of the stack
186 :type stack_id: ``str``
187 :return: * *False*: If the Datacenter is None
188 * *True*: Else
189 :rtype: ``bool``
190 """
191 if self.dc is None:
192 return False
193
194 # Stop all servers and their links of this stack
195 for server in self.stacks[stack_id].servers.values():
196 self.stop_compute(server)
197 self.delete_server(server)
198 for net in self.stacks[stack_id].nets.values():
199 self.delete_network(net.id)
200 for port in self.stacks[stack_id].ports.values():
201 self.delete_port(port.id)
202
203 del self.stacks[stack_id]
204 return True
205
206 def update_stack(self, old_stack_id, new_stack):
207 """
208 Determines differences within the old and the new stack and deletes, create or changes only parts that
209 differ between the two stacks.
210
211 :param old_stack_id: The ID of the old stack.
212 :type old_stack_id: ``str``
213 :param new_stack: A reference of the new stack.
214 :type new_stack: :class:`heat.resources.stack`
215 :return: * *True*: if the old stack could be updated to the new stack without any error.
216 * *False*: else
217 :rtype: ``bool``
218 """
219 LOG.debug("updating stack {} with new_stack {}".format(old_stack_id, new_stack))
220 if old_stack_id not in self.stacks:
221 return False
222 old_stack = self.stacks[old_stack_id]
223
224 # Update Stack IDs
225 for server in old_stack.servers.values():
226 if server.name in new_stack.servers:
227 new_stack.servers[server.name].id = server.id
228 for net in old_stack.nets.values():
229 if net.name in new_stack.nets:
230 new_stack.nets[net.name].id = net.id
231 for subnet in new_stack.nets.values():
232 if subnet.subnet_name == net.subnet_name:
233 subnet.subnet_id = net.subnet_id
234 break
235 for port in old_stack.ports.values():
236 if port.name in new_stack.ports:
237 new_stack.ports[port.name].id = port.id
238 for router in old_stack.routers.values():
239 if router.name in new_stack.routers:
240 new_stack.routers[router.name].id = router.id
241
242 # Update the compute dicts to now contain the new_stack components
243 self.update_compute_dicts(new_stack)
244
245 self.update_ip_addresses(old_stack, new_stack)
246
247 # Update all interface names - after each port has the correct UUID!!
248 for port in new_stack.ports.values():
249 port.create_intf_name()
250
251 if not self.check_stack(new_stack):
252 return False
253
254 # Remove unnecessary networks
255 for net in old_stack.nets.values():
256 if not net.name in new_stack.nets:
257 self.delete_network(net.id)
258
259 # Remove all unnecessary servers
260 for server in old_stack.servers.values():
261 if server.name in new_stack.servers:
262 if not server.compare_attributes(new_stack.servers[server.name]):
263 self.stop_compute(server)
264 else:
265 # Delete unused and changed links
266 for port_name in server.port_names:
267 if port_name in old_stack.ports and port_name in new_stack.ports:
268 if not old_stack.ports.get(port_name) == new_stack.ports.get(port_name):
269 my_links = self.dc.net.links
270 for link in my_links:
271 if str(link.intf1) == old_stack.ports[port_name].intf_name and \
272 str(link.intf1.ip) == \
273 old_stack.ports[port_name].ip_address.split('/')[0]:
274 self._remove_link(server.name, link)
275
276 # Add changed link
277 self._add_link(server.name,
278 new_stack.ports[port_name].ip_address,
279 new_stack.ports[port_name].intf_name,
280 new_stack.ports[port_name].net_name)
281 break
282 else:
283 my_links = self.dc.net.links
284 for link in my_links:
285 if str(link.intf1) == old_stack.ports[port_name].intf_name and \
286 str(link.intf1.ip) == old_stack.ports[port_name].ip_address.split('/')[0]:
287 self._remove_link(server.name, link)
288 break
289
290 # Create new links
291 for port_name in new_stack.servers[server.name].port_names:
292 if port_name not in server.port_names:
293 self._add_link(server.name,
294 new_stack.ports[port_name].ip_address,
295 new_stack.ports[port_name].intf_name,
296 new_stack.ports[port_name].net_name)
297 else:
298 self.stop_compute(server)
299
300 # Start all new servers
301 for server in new_stack.servers.values():
302 if server.name not in self.dc.containers:
303 self._start_compute(server)
304 else:
305 server.emulator_compute = self.dc.containers.get(server.name)
306
307 del self.stacks[old_stack_id]
308 self.stacks[new_stack.id] = new_stack
309 return True
310
311 def update_ip_addresses(self, old_stack, new_stack):
312 """
313 Updates the subnet and the port IP addresses - which should always be in this order!
314
315 :param old_stack: The currently running stack
316 :type old_stack: :class:`heat.resources.stack`
317 :param new_stack: The new created stack
318 :type new_stack: :class:`heat.resources.stack`
319 """
320 self.update_subnet_cidr(old_stack, new_stack)
321 self.update_port_addresses(old_stack, new_stack)
322
323 def update_port_addresses(self, old_stack, new_stack):
324 """
325 Updates the port IP addresses. First resets all issued addresses. Then get all IP addresses from the old
326 stack and sets them to the same ports in the new stack. Finally all new or changed instances will get new
327 IP addresses.
328
329 :param old_stack: The currently running stack
330 :type old_stack: :class:`heat.resources.stack`
331 :param new_stack: The new created stack
332 :type new_stack: :class:`heat.resources.stack`
333 """
334 for net in new_stack.nets.values():
335 net.reset_issued_ip_addresses()
336
337 for old_port in old_stack.ports.values():
338 for port in new_stack.ports.values():
339 if port.compare_attributes(old_port):
340 for net in new_stack.nets.values():
341 if net.name == port.net_name:
342 if net.assign_ip_address(old_port.ip_address, port.name):
343 port.ip_address = old_port.ip_address
344 port.mac_address = old_port.mac_address
345 else:
346 port.ip_address = net.get_new_ip_address(port.name)
347
348 for port in new_stack.ports.values():
349 for net in new_stack.nets.values():
350 if port.net_name == net.name and not net.is_my_ip(port.ip_address, port.name):
351 port.ip_address = net.get_new_ip_address(port.name)
352
353 def update_subnet_cidr(self, old_stack, new_stack):
354 """
355 Updates the subnet IP addresses. If the new stack contains subnets from the old stack it will take those
356 IP addresses. Otherwise it will create new IP addresses for the subnet.
357
358 :param old_stack: The currently running stack
359 :type old_stack: :class:`heat.resources.stack`
360 :param new_stack: The new created stack
361 :type new_stack: :class:`heat.resources.stack`
362 """
363 for old_subnet in old_stack.nets.values():
364 IP.free_cidr(old_subnet.get_cidr(), old_subnet.subnet_id)
365
366 for subnet in new_stack.nets.values():
367 subnet.clear_cidr()
368 for old_subnet in old_stack.nets.values():
369 if subnet.subnet_name == old_subnet.subnet_name:
370 if IP.assign_cidr(old_subnet.get_cidr(), subnet.subnet_id):
371 subnet.set_cidr(old_subnet.get_cidr())
372
373 for subnet in new_stack.nets.values():
374 if IP.is_cidr_issued(subnet.get_cidr()):
375 continue
376
377 cird = IP.get_new_cidr(subnet.subnet_id)
378 subnet.set_cidr(cird)
379 return
380
381 def update_compute_dicts(self, stack):
382 """
383 Update and add all stack components tho the compute dictionaries.
384
385 :param stack: A stack reference, to get all required components.
386 :type stack: :class:`heat.resources.stack`
387 """
388 for server in stack.servers.values():
389 self.computeUnits[server.id] = server
390 if isinstance(server.flavor, dict):
391 self.add_flavor(server.flavor['flavorName'],
392 server.flavor['vcpu'],
393 server.flavor['ram'], 'MB',
394 server.flavor['storage'], 'GB')
395 server.flavor = server.flavor['flavorName']
396 for router in stack.routers.values():
397 self.routers[router.id] = router
398 for net in stack.nets.values():
399 self.nets[net.id] = net
400 for port in stack.ports.values():
401 self.ports[port.id] = port
402
403 def _start_compute(self, server):
404 """
405 Starts a new compute object (docker container) inside the emulator.
406 Should only be called by stack modifications and not directly.
407
408 :param server: Specifies the compute resource.
409 :type server: :class:`heat.resources.server`
410 """
411 LOG.debug("Starting new compute resources %s" % server.name)
412 network = list()
413
414 for port_name in server.port_names:
415 network_dict = dict()
416 port = self.find_port_by_name_or_id(port_name)
417 if port is not None:
418 network_dict['id'] = port.intf_name
419 network_dict['ip'] = port.ip_address
420 network_dict[network_dict['id']] = self.find_network_by_name_or_id(port.net_name).name
421 network.append(network_dict)
422 self.compute_nets[server.name] = network
423 c = self.dc.startCompute(server.name, image=server.image, command=server.command,
424 network=network, flavor_name=server.flavor,
425 properties=server.properties)
426 server.emulator_compute = c
427
428 for intf in c.intfs.values():
429 for port_name in server.port_names:
430 port = self.find_port_by_name_or_id(port_name)
431 if port is not None:
432 if intf.name == port.intf_name:
433 # wait up to one second for the intf to come up
434 self.timeout_sleep(intf.isUp, 1)
435 if port.mac_address is not None:
436 intf.setMAC(port.mac_address)
437 else:
438 port.mac_address = intf.MAC()
439
440 # Start the real emulator command now as specified in the dockerfile
441 # ENV SON_EMU_CMD
442 config = c.dcinfo.get("Config", dict())
443 env = config.get("Env", list())
444 for env_var in env:
445 if "SON_EMU_CMD=" in env_var:
446 cmd = str(env_var.split("=")[1])
447 server.son_emu_command = cmd
448 # execute command in new thread to ensure that GK is not blocked by VNF
449 t = threading.Thread(target=c.cmdPrint, args=(cmd,))
450 t.daemon = True
451 t.start()
452
453 def stop_compute(self, server):
454 """
455 Determines which links should be removed before removing the server itself.
456
457 :param server: The server that should be removed
458 :type server: ``heat.resources.server``
459 """
460 LOG.debug("Stopping container %s with full name %s" % (server.name, server.full_name))
461 link_names = list()
462 for port_name in server.port_names:
463 link_names.append(self.find_port_by_name_or_id(port_name).intf_name)
464 my_links = self.dc.net.links
465 for link in my_links:
466 if str(link.intf1) in link_names:
467 # Remove all self created links that connect the server to the main switch
468 self._remove_link(server.name, link)
469
470 # Stop the server and the remaining connection to the datacenter switch
471 self.dc.stopCompute(server.name)
472 # Only now delete all its ports and the server itself
473 for port_name in server.port_names:
474 self.delete_port(port_name)
475 self.delete_server(server)
476
477 def find_server_by_name_or_id(self, name_or_id):
478 """
479 Tries to find the server by ID and if this does not succeed then tries to find it via name.
480
481 :param name_or_id: UUID or name of the server.
482 :type name_or_id: ``str``
483 :return: Returns the server reference if it was found or None
484 :rtype: :class:`heat.resources.server`
485 """
486 if name_or_id in self.computeUnits:
487 return self.computeUnits[name_or_id]
488
489 if self._shorten_server_name(name_or_id) in self.computeUnits:
490 return self.computeUnits[name_or_id]
491
492 for server in self.computeUnits.values():
493 if server.name == name_or_id or server.template_name == name_or_id or server.full_name == name_or_id:
494 return server
495 if (server.name == self._shorten_server_name(name_or_id)
496 or server.template_name == self._shorten_server_name(name_or_id)
497 or server.full_name == self._shorten_server_name(name_or_id)):
498 return server
499 return None
500
501 def create_server(self, name, stack_operation=False):
502 """
503 Creates a server with the specified name. Raises an exception when a server with the given name already
504 exists!
505
506 :param name: Name of the new server.
507 :type name: ``str``
508 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
509 :type stack_operation: ``bool``
510 :return: Returns the created server.
511 :rtype: :class:`heat.resources.server`
512 """
513 if self.find_server_by_name_or_id(name) is not None and not stack_operation:
514 raise Exception("Server with name %s already exists." % name)
515 safe_name = self._shorten_server_name(name)
516 server = Server(safe_name)
517 server.id = str(uuid.uuid4())
518 if not stack_operation:
519 self.computeUnits[server.id] = server
520 return server
521
522 def _shorten_server_name(self, name, char_limit=9):
523 """
524 Docker does not like too long instance names.
525 This function provides a shorter name if needed
526 """
527 # TODO this is a ugly hack and needs to be fixed
528 LOG.debug("Long server name: {}".format(name))
529 if len(name) > char_limit:
530 # construct a short name
531 parts = name.split("_")
532 name = name.replace("_vnf", "")
533 name = name[-char_limit:].strip("-_ .")
534 LOG.debug("Short server name: {}".format(name))
535 return name
536
537 def delete_server(self, server):
538 """
539 Deletes the given server from the stack dictionary and the computeUnits dictionary.
540
541 :param server: Reference of the server that should be deleted.
542 :type server: :class:`heat.resources.server`
543 :return: * *False*: If the server name is not in the correct format ('datacentername_stackname_servername') \
544 or when no stack with the correct stackname was found.
545 * *True*: Else
546 :rtype: ``bool``
547 """
548 if server is None:
549 return False
550 name_parts = server.name.split('_')
551 if len(name_parts) < 3:
552 return False
553
554 for stack in self.stacks.values():
555 if stack.stack_name == name_parts[1]:
556 stack.servers.pop(server.id, None)
557 if self.computeUnits.pop(server.id, None) is None:
558 return False
559 return True
560
561 def find_network_by_name_or_id(self, name_or_id):
562 """
563 Tries to find the network by ID and if this does not succeed then tries to find it via name.
564
565 :param name_or_id: UUID or name of the network.
566 :type name_or_id: ``str``
567 :return: Returns the network reference if it was found or None
568 :rtype: :class:`heat.resources.net`
569 """
570 if name_or_id in self.nets:
571 return self.nets[name_or_id]
572 for net in self.nets.values():
573 if net.name == name_or_id:
574 return net
575
576 return None
577
578 def create_network(self, name, stack_operation=False):
579 """
580 Creates a new network with the given name. Raises an exception when a network with the given name already
581 exists!
582
583 :param name: Name of the new network.
584 :type name: ``str``
585 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
586 :type stack_operation: ``bool``
587 :return: :class:`heat.resources.net`
588 """
589 LOG.debug("Creating network with name %s" % name)
590 if self.find_network_by_name_or_id(name) is not None and not stack_operation:
591 LOG.warning("Creating network with name %s failed, as it already exists" % name)
592 raise Exception("Network with name %s already exists." % name)
593 network = Net(name)
594 network.id = str(uuid.uuid4())
595 if not stack_operation:
596 self.nets[network.id] = network
597 return network
598
599 def delete_network(self, name_or_id):
600 """
601 Deletes the given network.
602
603 :param name_or_id: Name or UUID of the network.
604 :type name_or_id: ``str``
605 """
606 net = self.find_network_by_name_or_id(name_or_id)
607 if net is None:
608 raise Exception("Network with name or id %s does not exists." % name_or_id)
609
610 for stack in self.stacks.values():
611 stack.nets.pop(net.name, None)
612
613 self.nets.pop(net.id, None)
614
615 def create_port(self, name, stack_operation=False):
616 """
617 Creates a new port with the given name. Raises an exception when a port with the given name already
618 exists!
619
620 :param name: Name of the new port.
621 :type name: ``str``
622 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
623 :type stack_operation: ``bool``
624 :return: Returns the created port.
625 :rtype: :class:`heat.resources.port`
626 """
627 port = self.find_port_by_name_or_id(name)
628 if port is not None and not stack_operation:
629 LOG.warning("Creating port with name %s failed, as it already exists" % name)
630 raise Exception("Port with name %s already exists." % name)
631 LOG.debug("Creating port with name %s" % name)
632 port = Port(name)
633 if not stack_operation:
634 self.ports[port.id] = port
635 port.create_intf_name()
636 return port
637
638 def find_port_by_name_or_id(self, name_or_id):
639 """
640 Tries to find the port by ID and if this does not succeed then tries to find it via name.
641
642 :param name_or_id: UUID or name of the network.
643 :type name_or_id: ``str``
644 :return: Returns the port reference if it was found or None
645 :rtype: :class:`heat.resources.port`
646 """
647 if name_or_id in self.ports:
648 return self.ports[name_or_id]
649 for port in self.ports.values():
650 if port.name == name_or_id or port.template_name == name_or_id:
651 return port
652
653 return None
654
655 def delete_port(self, name_or_id):
656 """
657 Deletes the given port. Raises an exception when the port was not found!
658
659 :param name_or_id: UUID or name of the port.
660 :type name_or_id: ``str``
661 """
662 port = self.find_port_by_name_or_id(name_or_id)
663 if port is None:
664 raise Exception("Port with name or id %s does not exists." % name_or_id)
665
666 my_links = self.dc.net.links
667 for link in my_links:
668 if str(link.intf1) == port.intf_name and \
669 str(link.intf1.ip) == port.ip_address.split('/')[0]:
670 self._remove_link(link.intf1.node.name, link)
671 break
672
673 self.ports.pop(port.id, None)
674 for stack in self.stacks.values():
675 stack.ports.pop(port.name, None)
676
677 def create_port_pair(self, name, stack_operation=False):
678 """
679 Creates a new port pair with the given name. Raises an exception when a port pair with the given name already
680 exists!
681
682 :param name: Name of the new port pair.
683 :type name: ``str``
684 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
685 :type stack_operation: ``bool``
686 :return: Returns the created port pair.
687 :rtype: :class:`openstack.resources.port_pair`
688 """
689 port_pair = self.find_port_pair_by_name_or_id(name)
690 if port_pair is not None and not stack_operation:
691 logging.warning("Creating port pair with name %s failed, as it already exists" % name)
692 raise Exception("Port pair with name %s already exists." % name)
693 logging.debug("Creating port pair with name %s" % name)
694 port_pair = PortPair(name)
695 if not stack_operation:
696 self.port_pairs[port_pair.id] = port_pair
697 return port_pair
698
699 def find_port_pair_by_name_or_id(self, name_or_id):
700 """
701 Tries to find the port pair by ID and if this does not succeed then tries to find it via name.
702
703 :param name_or_id: UUID or name of the port pair.
704 :type name_or_id: ``str``
705 :return: Returns the port pair reference if it was found or None
706 :rtype: :class:`openstack.resources.port_pair`
707 """
708 if name_or_id in self.port_pairs:
709 return self.port_pairs[name_or_id]
710 for port_pair in self.port_pairs.values():
711 if port_pair.name == name_or_id:
712 return port_pair
713
714 return None
715
716 def delete_port_pair(self, name_or_id):
717 """
718 Deletes the given port pair. Raises an exception when the port pair was not found!
719
720 :param name_or_id: UUID or name of the port pair.
721 :type name_or_id: ``str``
722 """
723 port_pair = self.find_port_pair_by_name_or_id(name_or_id)
724 if port_pair is None:
725 raise Exception("Port pair with name or id %s does not exists." % name_or_id)
726
727 self.port_pairs.pop(port_pair.id, None)
728
729 def create_port_pair_group(self, name, stack_operation=False):
730 """
731 Creates a new port pair group with the given name. Raises an exception when a port pair group
732 with the given name already exists!
733
734 :param name: Name of the new port pair group.
735 :type name: ``str``
736 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
737 :type stack_operation: ``bool``
738 :return: Returns the created port pair group .
739 :rtype: :class:`openstack.resources.port_pair_group`
740 """
741 port_pair_group = self.find_port_pair_group_by_name_or_id(name)
742 if port_pair_group is not None and not stack_operation:
743 logging.warning("Creating port pair group with name %s failed, as it already exists" % name)
744 raise Exception("Port pair group with name %s already exists." % name)
745 logging.debug("Creating port pair group with name %s" % name)
746 port_pair_group = PortPairGroup(name)
747 if not stack_operation:
748 self.port_pair_groups[port_pair_group.id] = port_pair_group
749 return port_pair_group
750
751 def find_port_pair_group_by_name_or_id(self, name_or_id):
752 """
753 Tries to find the port pair group by ID and if this does not succeed then tries to find it via name.
754
755 :param name_or_id: UUID or name of the port pair group.
756 :type name_or_id: ``str``
757 :return: Returns the port pair group reference if it was found or None
758 :rtype: :class:`openstack.resources.port_pair_group`
759 """
760 if name_or_id in self.port_pair_groups:
761 return self.port_pair_groups[name_or_id]
762 for port_pair_group in self.port_pair_groups.values():
763 if port_pair_group.name == name_or_id:
764 return port_pair_group
765
766 return None
767
768 def delete_port_pair_group(self, name_or_id):
769 """
770 Deletes the given port pair group. Raises an exception when the port pair group was not found!
771
772 :param name_or_id: UUID or name of the port pair group.
773 :type name_or_id: ``str``
774 """
775 port_pair_group = self.find_port_pair_group_by_name_or_id(name_or_id)
776 if port_pair_group is None:
777 raise Exception("Port pair with name or id %s does not exists." % name_or_id)
778
779 self.port_pair_groups.pop(port_pair_group.id, None)
780
781 def create_port_chain(self, name, stack_operation=False):
782 """
783 Creates a new port chain with the given name. Raises an exception when a port chain with the given name already
784 exists!
785
786 :param name: Name of the new port chain
787 :type name: ``str``
788 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
789 :type stack_operation: ``bool``
790 :return: Returns the created port chain.
791 :rtype: :class:`openstack.resources.port_chain.PortChain`
792 """
793 port_chain = self.find_port_chain_by_name_or_id(name)
794 if port_chain is not None and not stack_operation:
795 logging.warning("Creating port chain with name %s failed, as it already exists" % name)
796 raise Exception("Port chain with name %s already exists." % name)
797 logging.debug("Creating port chain with name %s" % name)
798 port_chain = PortChain(name)
799 if not stack_operation:
800 self.port_chains[port_chain.id] = port_chain
801 return port_chain
802
803 def find_port_chain_by_name_or_id(self, name_or_id):
804 """
805 Tries to find the port chain by ID and if this does not succeed then tries to find it via name.
806
807 :param name_or_id: UUID or name of the port chain.
808 :type name_or_id: ``str``
809 :return: Returns the port chain reference if it was found or None
810 :rtype: :class:`openstack.resources.port_chain.PortChain`
811 """
812 if name_or_id in self.port_chains:
813 return self.port_chains[name_or_id]
814 for port_chain in self.port_chains.values():
815 if port_chain.name == name_or_id:
816 return port_chain
817 return None
818
819 def delete_port_chain(self, name_or_id):
820 """
821 Deletes the given port chain. Raises an exception when the port chain was not found!
822
823 :param name_or_id: UUID or name of the port chain.
824 :type name_or_id: ``str``
825 """
826 port_chain = self.find_port_chain_by_name_or_id(name_or_id)
827 port_chain.uninstall(self)
828 if port_chain is None:
829 raise Exception("Port chain with name or id %s does not exists." % name_or_id)
830
831 self.port_chains.pop(port_chain.id, None)
832
833 def create_flow_classifier(self, name, stack_operation=False):
834 """
835 Creates a new flow classifier with the given name. Raises an exception when a flow classifier with the given name already
836 exists!
837
838 :param name: Name of the new flow classifier.
839 :type name: ``str``
840 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
841 :type stack_operation: ``bool``
842 :return: Returns the created flow classifier.
843 :rtype: :class:`openstack.resources.flow_classifier`
844 """
845 flow_classifier = self.find_flow_classifier_by_name_or_id(name)
846 if flow_classifier is not None and not stack_operation:
847 logging.warning("Creating flow classifier with name %s failed, as it already exists" % name)
848 raise Exception("Flow classifier with name %s already exists." % name)
849 logging.debug("Creating flow classifier with name %s" % name)
850 flow_classifier = FlowClassifier(name)
851 if not stack_operation:
852 self.flow_classifiers[flow_classifier.id] = flow_classifier
853 return flow_classifier
854
855 def find_flow_classifier_by_name_or_id(self, name_or_id):
856 """
857 Tries to find the flow classifier by ID and if this does not succeed then tries to find it via name.
858
859 :param name_or_id: UUID or name of the flow classifier.
860 :type name_or_id: ``str``
861 :return: Returns the flow classifier reference if it was found or None
862 :rtype: :class:`openstack.resources.flow_classifier`
863 """
864 if name_or_id in self.flow_classifiers:
865 return self.flow_classifiers[name_or_id]
866 for flow_classifier in self.flow_classifiers.values():
867 if flow_classifier.name == name_or_id:
868 return flow_classifier
869
870 return None
871
872 def delete_flow_classifier(self, name_or_id):
873 """
874 Deletes the given flow classifier. Raises an exception when the flow classifier was not found!
875
876 :param name_or_id: UUID or name of the flow classifier.
877 :type name_or_id: ``str``
878 """
879 flow_classifier = self.find_flow_classifier_by_name_or_id(name_or_id)
880 if flow_classifier is None:
881 raise Exception("Flow classifier with name or id %s does not exists." % name_or_id)
882
883 self.flow_classifiers.pop(flow_classifier.id, None)
884
885 def _add_link(self, node_name, ip_address, link_name, net_name):
886 """
887 Adds a new link between datacenter switch and the node with the given name.
888
889 :param node_name: Name of the required node.
890 :type node_name: ``str``
891 :param ip_address: IP-Address of the node.
892 :type ip_address: ``str``
893 :param link_name: Link name.
894 :type link_name: ``str``
895 :param net_name: Network name.
896 :type net_name: ``str``
897 """
898 node = self.dc.net.get(node_name)
899 params = {'params1': {'ip': ip_address,
900 'id': link_name,
901 link_name: net_name},
902 'intfName1': link_name,
903 'cls': Link}
904 link = self.dc.net.addLink(node, self.dc.switch, **params)
905 OpenstackCompute.timeout_sleep(link.intf1.isUp, 1)
906
907 def _remove_link(self, server_name, link):
908 """
909 Removes a link between server and datacenter switch.
910
911 :param server_name: Specifies the server where the link starts.
912 :type server_name: ``str``
913 :param link: A reference of the link which should be removed.
914 :type link: :class:`mininet.link`
915 """
916 self.dc.switch.detach(link.intf2)
917 del self.dc.switch.intfs[self.dc.switch.ports[link.intf2]]
918 del self.dc.switch.ports[link.intf2]
919 del self.dc.switch.nameToIntf[link.intf2.name]
920 self.dc.net.removeLink(link=link)
921 for intf_key in self.dc.net[server_name].intfs.keys():
922 if self.dc.net[server_name].intfs[intf_key].link == link:
923 self.dc.net[server_name].intfs[intf_key].delete()
924 del self.dc.net[server_name].intfs[intf_key]
925
926 @staticmethod
927 def timeout_sleep(function, max_sleep):
928 """
929 This function will execute a function all 0.1 seconds until it successfully returns.
930 Will return after `max_sleep` seconds if not successful.
931
932 :param function: The function to execute. Should return true if done.
933 :type function: ``function``
934 :param max_sleep: Max seconds to sleep. 1 equals 1 second.
935 :type max_sleep: ``float``
936 """
937 current_time = time.time()
938 stop_time = current_time + max_sleep
939 while not function() and current_time < stop_time:
940 current_time = time.time()
941 time.sleep(0.1)