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