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