1 from mininet
.link
import Link
2 from resources
import *
3 from docker
import DockerClient
8 import ip_handler
as IP
11 LOG
= logging
.getLogger("api.openstack.compute")
14 class HeatApiStackInvalidException(Exception):
16 Exception thrown when a submitted stack is invalid.
19 def __init__(self
, value
):
23 return repr(self
.value
)
26 class OpenstackCompute(object):
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.
32 It also handles start and stop of containers.
38 self
.computeUnits
= dict()
44 self
.compute_nets
= dict()
45 self
.dcli
= DockerClient(base_url
='unix://var/run/docker.sock')
50 Updates the known images. Asks the docker daemon for a list of all known images and returns
53 :return: Returns the new image dictionary.
56 for image
in self
.dcli
.images
.list():
57 if len(image
.tags
) > 0:
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
)
64 def add_stack(self
, stack
):
66 Adds a new stack to the compute node.
68 :param stack: Stack dictionary.
69 :type stack: :class:`heat.resources.stack`
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
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
:
87 def check_stack(self
, stack
):
89 Checks all dependencies of all servers, ports and routers and their most important parameters.
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.
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
:
124 for net
in stack
.nets
.values():
125 if net
.subnet_name
== subnet_name
:
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
134 def add_flavor(self
, name
, cpu
, memory
, memory_unit
, storage
, storage_unit
):
136 Adds a flavor to the stack.
138 :param name: Specifies the name of the flavor.
143 :type memory: ``str``
145 :type memory_unit: ``str``
147 :type storage: ``str``
149 :type storage_unit: ``str``
151 flavor
= InstanceFlavor(name
, cpu
, memory
, memory_unit
, storage
, storage_unit
)
152 self
.flavors
[flavor
.name
] = flavor
155 def deploy_stack(self
, stackid
):
157 Deploys the stack and starts the emulation.
159 :param stackid: An UUID str of the stack
160 :type stackid: ``str``
161 :return: * *False*: If the Datacenter is None
168 stack
= self
.stacks
[stackid
]
169 self
.update_compute_dicts(stack
)
171 # Create the networks first
172 for server
in stack
.servers
.values():
173 self
._start
_compute
(server
)
176 def delete_stack(self
, stack_id
):
178 Delete a stack and all its components.
180 :param stack_id: An UUID str of the stack
181 :type stack_id: ``str``
182 :return: * *False*: If the Datacenter is None
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)
198 del self
.stacks
[stack_id
]
201 def update_stack(self
, old_stack_id
, new_stack
):
203 Determines differences within the old and the new stack and deletes, create or changes only parts that
204 differ between the two stacks.
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.
214 LOG
.debug("updating stack {} with new_stack {}".format(old_stack_id
, new_stack
))
215 if old_stack_id
not in self
.stacks
:
217 old_stack
= self
.stacks
[old_stack_id
]
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
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
237 # Update the compute dicts to now contain the new_stack components
238 self
.update_compute_dicts(new_stack
)
240 self
.update_ip_addresses(old_stack
, new_stack
)
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()
246 if not self
.check_stack(new_stack
):
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)
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
)
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
)
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
)
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
)
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
)
293 self
.stop_compute(server
)
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
)
300 server
.emulator_compute
= self
.dc
.containers
.get(server
.name
)
302 del self
.stacks
[old_stack_id
]
303 self
.stacks
[new_stack
.id] = new_stack
306 def update_ip_addresses(self
, old_stack
, new_stack
):
308 Updates the subnet and the port IP addresses - which should always be in this order!
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`
315 self
.update_subnet_cidr(old_stack
, new_stack
)
316 self
.update_port_addresses(old_stack
, new_stack
)
318 def update_port_addresses(self
, old_stack
, new_stack
):
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
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`
329 for net
in new_stack
.nets
.values():
330 net
.reset_issued_ip_addresses()
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
341 port
.ip_address
= net
.get_new_ip_address(port
.name
)
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
)
348 def update_subnet_cidr(self
, old_stack
, new_stack
):
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.
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`
358 for old_subnet
in old_stack
.nets
.values():
359 IP
.free_cidr(old_subnet
.get_cidr(), old_subnet
.subnet_id
)
361 for subnet
in new_stack
.nets
.values():
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())
368 for subnet
in new_stack
.nets
.values():
369 if IP
.is_cidr_issued(subnet
.get_cidr()):
372 cird
= IP
.get_new_cidr(subnet
.subnet_id
)
373 subnet
.set_cidr(cird
)
376 def update_compute_dicts(self
, stack
):
378 Update and add all stack components tho the compute dictionaries.
380 :param stack: A stack reference, to get all required components.
381 :type stack: :class:`heat.resources.stack`
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
398 def _start_compute(self
, server
):
400 Starts a new compute object (docker container) inside the emulator.
401 Should only be called by stack modifications and not directly.
403 :param server: Specifies the compute resource.
404 :type server: :class:`heat.resources.server`
406 LOG
.debug("Starting new compute resources %s" % server
.name
)
409 for port_name
in server
.port_names
:
410 network_dict
= dict()
411 port
= self
.find_port_by_name_or_id(port_name
)
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 self
.compute_nets
[server
.name
] = network
418 c
= self
.dc
.startCompute(server
.name
, image
=server
.image
, command
=server
.command
,
419 network
=network
, flavor_name
=server
.flavor
)
420 server
.emulator_compute
= c
422 for intf
in c
.intfs
.values():
423 for port_name
in server
.port_names
:
424 port
= self
.find_port_by_name_or_id(port_name
)
426 if intf
.name
== port
.intf_name
:
427 # wait up to one second for the intf to come up
428 self
.timeout_sleep(intf
.isUp
, 1)
429 if port
.mac_address
is not None:
430 intf
.setMAC(port
.mac_address
)
432 port
.mac_address
= intf
.MAC()
434 # Start the real emulator command now as specified in the dockerfile
436 config
= c
.dcinfo
.get("Config", dict())
437 env
= config
.get("Env", list())
439 if "SON_EMU_CMD=" in env_var
:
440 cmd
= str(env_var
.split("=")[1])
441 server
.son_emu_command
= cmd
442 # execute command in new thread to ensure that GK is not blocked by VNF
443 t
= threading
.Thread(target
=c
.cmdPrint
, args
=(cmd
,))
447 def stop_compute(self
, server
):
449 Determines which links should be removed before removing the server itself.
451 :param server: The server that should be removed
452 :type server: ``heat.resources.server``
454 LOG
.debug("Stopping container %s with full name %s" % (server
.name
, server
.full_name
))
456 for port_name
in server
.port_names
:
457 link_names
.append(self
.find_port_by_name_or_id(port_name
).intf_name
)
458 my_links
= self
.dc
.net
.links
459 for link
in my_links
:
460 if str(link
.intf1
) in link_names
:
461 # Remove all self created links that connect the server to the main switch
462 self
._remove
_link
(server
.name
, link
)
464 # Stop the server and the remaining connection to the datacenter switch
465 self
.dc
.stopCompute(server
.name
)
466 # Only now delete all its ports and the server itself
467 for port_name
in server
.port_names
:
468 self
.delete_port(port_name
)
469 self
.delete_server(server
)
471 def find_server_by_name_or_id(self
, name_or_id
):
473 Tries to find the server by ID and if this does not succeed then tries to find it via name.
475 :param name_or_id: UUID or name of the server.
476 :type name_or_id: ``str``
477 :return: Returns the server reference if it was found or None
478 :rtype: :class:`heat.resources.server`
480 if name_or_id
in self
.computeUnits
:
481 return self
.computeUnits
[name_or_id
]
483 if self
._shorten
_server
_name
(name_or_id
) in self
.computeUnits
:
484 return self
.computeUnits
[name_or_id
]
486 for server
in self
.computeUnits
.values():
487 if server
.name
== name_or_id
or server
.template_name
== name_or_id
or server
.full_name
== name_or_id
:
489 if (server
.name
== self
._shorten
_server
_name
(name_or_id
)
490 or server
.template_name
== self
._shorten
_server
_name
(name_or_id
)
491 or server
.full_name
== self
._shorten
_server
_name
(name_or_id
)):
495 def create_server(self
, name
, stack_operation
=False):
497 Creates a server with the specified name. Raises an exception when a server with the given name already
500 :param name: Name of the new server.
502 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
503 :type stack_operation: ``bool``
504 :return: Returns the created server.
505 :rtype: :class:`heat.resources.server`
507 if self
.find_server_by_name_or_id(name
) is not None and not stack_operation
:
508 raise Exception("Server with name %s already exists." % name
)
509 safe_name
= self
._shorten
_server
_name
(name
)
510 server
= Server(safe_name
)
511 server
.id = str(uuid
.uuid4())
512 if not stack_operation
:
513 self
.computeUnits
[server
.id] = server
516 def _shorten_server_name(self
, name
, char_limit
=64):
518 Docker does not like too long instance names.
519 This function provides a shorter name if needed
521 LOG
.debug("Long server name: {}".format(name
))
522 if len(name
) > char_limit
:
523 # construct a short name
524 name
= name
[-char_limit
:].strip("-_ .")
525 LOG
.debug("Short server name: {}".format(name
))
528 def delete_server(self
, server
):
530 Deletes the given server from the stack dictionary and the computeUnits dictionary.
532 :param server: Reference of the server that should be deleted.
533 :type server: :class:`heat.resources.server`
534 :return: * *False*: If the server name is not in the correct format ('datacentername_stackname_servername') \
535 or when no stack with the correct stackname was found.
541 name_parts
= server
.name
.split('_')
542 if len(name_parts
) < 3:
545 for stack
in self
.stacks
.values():
546 if stack
.stack_name
== name_parts
[1]:
547 stack
.servers
.pop(server
.id, None)
548 if self
.computeUnits
.pop(server
.id, None) is None:
552 def find_network_by_name_or_id(self
, name_or_id
):
554 Tries to find the network by ID and if this does not succeed then tries to find it via name.
556 :param name_or_id: UUID or name of the network.
557 :type name_or_id: ``str``
558 :return: Returns the network reference if it was found or None
559 :rtype: :class:`heat.resources.net`
561 if name_or_id
in self
.nets
:
562 return self
.nets
[name_or_id
]
563 for net
in self
.nets
.values():
564 if net
.name
== name_or_id
:
569 def create_network(self
, name
, stack_operation
=False):
571 Creates a new network with the given name. Raises an exception when a network with the given name already
574 :param name: Name of the new network.
576 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
577 :type stack_operation: ``bool``
578 :return: :class:`heat.resources.net`
580 LOG
.debug("Creating network with name %s" % name
)
581 if self
.find_network_by_name_or_id(name
) is not None and not stack_operation
:
582 LOG
.warning("Creating network with name %s failed, as it already exists" % name
)
583 raise Exception("Network with name %s already exists." % name
)
585 network
.id = str(uuid
.uuid4())
586 if not stack_operation
:
587 self
.nets
[network
.id] = network
590 def delete_network(self
, name_or_id
):
592 Deletes the given network.
594 :param name_or_id: Name or UUID of the network.
595 :type name_or_id: ``str``
597 net
= self
.find_network_by_name_or_id(name_or_id
)
599 raise Exception("Network with name or id %s does not exists." % name_or_id
)
601 for stack
in self
.stacks
.values():
602 stack
.nets
.pop(net
.name
, None)
604 self
.nets
.pop(net
.id, None)
606 def create_port(self
, name
, stack_operation
=False):
608 Creates a new port with the given name. Raises an exception when a port with the given name already
611 :param name: Name of the new port.
613 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
614 :type stack_operation: ``bool``
615 :return: Returns the created port.
616 :rtype: :class:`heat.resources.port`
618 port
= self
.find_port_by_name_or_id(name
)
619 if port
is not None and not stack_operation
:
620 LOG
.warning("Creating port with name %s failed, as it already exists" % name
)
621 raise Exception("Port with name %s already exists." % name
)
622 LOG
.debug("Creating port with name %s" % name
)
624 if not stack_operation
:
625 self
.ports
[port
.id] = port
626 port
.create_intf_name()
629 def find_port_by_name_or_id(self
, name_or_id
):
631 Tries to find the port by ID and if this does not succeed then tries to find it via name.
633 :param name_or_id: UUID or name of the network.
634 :type name_or_id: ``str``
635 :return: Returns the port reference if it was found or None
636 :rtype: :class:`heat.resources.port`
638 if name_or_id
in self
.ports
:
639 return self
.ports
[name_or_id
]
640 for port
in self
.ports
.values():
641 if port
.name
== name_or_id
or port
.template_name
== name_or_id
:
646 def delete_port(self
, name_or_id
):
648 Deletes the given port. Raises an exception when the port was not found!
650 :param name_or_id: UUID or name of the port.
651 :type name_or_id: ``str``
653 port
= self
.find_port_by_name_or_id(name_or_id
)
655 raise Exception("Port with name or id %s does not exists." % name_or_id
)
657 my_links
= self
.dc
.net
.links
658 for link
in my_links
:
659 if str(link
.intf1
) == port
.intf_name
and \
660 str(link
.intf1
.ip
) == port
.ip_address
.split('/')[0]:
661 self
._remove
_link
(link
.intf1
.node
.name
, link
)
664 self
.ports
.pop(port
.id, None)
665 for stack
in self
.stacks
.values():
666 stack
.ports
.pop(port
.name
, None)
668 def _add_link(self
, node_name
, ip_address
, link_name
, net_name
):
670 Adds a new link between datacenter switch and the node with the given name.
672 :param node_name: Name of the required node.
673 :type node_name: ``str``
674 :param ip_address: IP-Address of the node.
675 :type ip_address: ``str``
676 :param link_name: Link name.
677 :type link_name: ``str``
678 :param net_name: Network name.
679 :type net_name: ``str``
681 node
= self
.dc
.net
.get(node_name
)
682 params
= {'params1': {'ip': ip_address
,
684 link_name
: net_name
},
685 'intfName1': link_name
,
687 link
= self
.dc
.net
.addLink(node
, self
.dc
.switch
, **params
)
688 OpenstackCompute
.timeout_sleep(link
.intf1
.isUp
, 1)
690 def _remove_link(self
, server_name
, link
):
692 Removes a link between server and datacenter switch.
694 :param server_name: Specifies the server where the link starts.
695 :type server_name: ``str``
696 :param link: A reference of the link which should be removed.
697 :type link: :class:`mininet.link`
699 self
.dc
.switch
.detach(link
.intf2
)
700 del self
.dc
.switch
.intfs
[self
.dc
.switch
.ports
[link
.intf2
]]
701 del self
.dc
.switch
.ports
[link
.intf2
]
702 del self
.dc
.switch
.nameToIntf
[link
.intf2
.name
]
703 self
.dc
.net
.removeLink(link
=link
)
704 for intf_key
in self
.dc
.net
[server_name
].intfs
.keys():
705 if self
.dc
.net
[server_name
].intfs
[intf_key
].link
== link
:
706 self
.dc
.net
[server_name
].intfs
[intf_key
].delete()
707 del self
.dc
.net
[server_name
].intfs
[intf_key
]
710 def timeout_sleep(function
, max_sleep
):
712 This function will execute a function all 0.1 seconds until it successfully returns.
713 Will return after `max_sleep` seconds if not successful.
715 :param function: The function to execute. Should return true if done.
716 :type function: ``function``
717 :param max_sleep: Max seconds to sleep. 1 equals 1 second.
718 :type max_sleep: ``float``
720 current_time
= time
.time()
721 stop_time
= current_time
+ max_sleep
722 while not function() and current_time
< stop_time
:
723 current_time
= time
.time()