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