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