blob: 4d6517ec8dd62c679c8e0f4b054e41ce3c84a725 [file] [log] [blame]
peusterm72f09882018-05-15 17:10:27 +02001# 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).
peusterm00199782017-05-17 08:48:12 +020026from mininet.link import Link
splietker7b38ee12017-06-28 17:24:01 +020027
peusterm72f09882018-05-15 17:10:27 +020028from resources.instance_flavor import InstanceFlavor
29from resources.net import Net
30from resources.port import Port
31from resources.port_pair import PortPair
32from resources.port_pair_group import PortPairGroup
33from resources.flow_classifier import FlowClassifier
34from resources.port_chain import PortChain
35from resources.server import Server
36from resources.image import Image
37
peusterm00199782017-05-17 08:48:12 +020038from docker import DockerClient
39import logging
40import threading
41import uuid
42import time
43import ip_handler as IP
peusterm92190602017-12-19 15:18:11 +010044import hashlib
peusterm00199782017-05-17 08:48:12 +020045
46
peustermbbf4f742017-06-19 11:22:11 +020047LOG = logging.getLogger("api.openstack.compute")
48
49
peusterm00199782017-05-17 08:48:12 +020050class 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
62class 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()
splietker7b38ee12017-06-28 17:24:01 +020080 self.port_pairs = dict()
81 self.port_pair_groups = dict()
82 self.flow_classifiers = dict()
83 self.port_chains = dict()
peusterm00199782017-05-17 08:48:12 +020084 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:
peusterm00199782017-05-17 08:48:12 +020099 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)
peusterm72f09882018-05-15 17:10:27 +0200112 raise HeatApiStackInvalidException(
113 "Stack did not pass validity checks")
peusterm00199782017-05-17 08:48:12 +0200114 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:
peustermbbf4f742017-06-19 11:22:11 +0200141 LOG.warning("Server %s of stack %s has a port named %s that is not known." %
peusterm72f09882018-05-15 17:10:27 +0200142 (server.name, stack.stack_name, port_name))
peusterm00199782017-05-17 08:48:12 +0200143 everything_ok = False
144 if server.image is None:
peustermbbf4f742017-06-19 11:22:11 +0200145 LOG.warning("Server %s holds no image." % (server.name))
peusterm00199782017-05-17 08:48:12 +0200146 everything_ok = False
147 if server.command is None:
peustermbbf4f742017-06-19 11:22:11 +0200148 LOG.warning("Server %s holds no command." % (server.name))
peusterm00199782017-05-17 08:48:12 +0200149 everything_ok = False
150 for port in stack.ports.values():
151 if port.net_name not in stack.nets:
peustermbbf4f742017-06-19 11:22:11 +0200152 LOG.warning("Port %s of stack %s has a network named %s that is not known." %
peusterm72f09882018-05-15 17:10:27 +0200153 (port.name, stack.stack_name, port.net_name))
peusterm00199782017-05-17 08:48:12 +0200154 everything_ok = False
155 if port.intf_name is None:
peustermbbf4f742017-06-19 11:22:11 +0200156 LOG.warning("Port %s has no interface name." % (port.name))
peusterm00199782017-05-17 08:48:12 +0200157 everything_ok = False
158 if port.ip_address is None:
peustermbbf4f742017-06-19 11:22:11 +0200159 LOG.warning("Port %s has no IP address." % (port.name))
peusterm00199782017-05-17 08:48:12 +0200160 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:
peustermbbf4f742017-06-19 11:22:11 +0200169 LOG.warning("Router %s of stack %s has a network named %s that is not known." %
peusterm72f09882018-05-15 17:10:27 +0200170 (router.name, stack.stack_name, subnet_name))
peusterm00199782017-05-17 08:48:12 +0200171 everything_ok = False
172 return everything_ok
173
peusterm72f09882018-05-15 17:10:27 +0200174 def add_flavor(self, name, cpu, memory,
175 memory_unit, storage, storage_unit):
peusterm00199782017-05-17 08:48:12 +0200176 """
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 """
peusterm72f09882018-05-15 17:10:27 +0200192 flavor = InstanceFlavor(
193 name, cpu, memory, memory_unit, storage, storage_unit)
peusterm00199782017-05-17 08:48:12 +0200194 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 for net in self.stacks[stack_id].nets.values():
236 self.delete_network(net.id)
237 for port in self.stacks[stack_id].ports.values():
238 self.delete_port(port.id)
239
240 del self.stacks[stack_id]
241 return True
242
243 def update_stack(self, old_stack_id, new_stack):
244 """
245 Determines differences within the old and the new stack and deletes, create or changes only parts that
246 differ between the two stacks.
247
248 :param old_stack_id: The ID of the old stack.
249 :type old_stack_id: ``str``
250 :param new_stack: A reference of the new stack.
251 :type new_stack: :class:`heat.resources.stack`
252 :return: * *True*: if the old stack could be updated to the new stack without any error.
253 * *False*: else
254 :rtype: ``bool``
255 """
peusterm72f09882018-05-15 17:10:27 +0200256 LOG.debug("updating stack {} with new_stack {}".format(
257 old_stack_id, new_stack))
peusterm00199782017-05-17 08:48:12 +0200258 if old_stack_id not in self.stacks:
259 return False
260 old_stack = self.stacks[old_stack_id]
261
262 # Update Stack IDs
263 for server in old_stack.servers.values():
264 if server.name in new_stack.servers:
265 new_stack.servers[server.name].id = server.id
266 for net in old_stack.nets.values():
267 if net.name in new_stack.nets:
268 new_stack.nets[net.name].id = net.id
269 for subnet in new_stack.nets.values():
270 if subnet.subnet_name == net.subnet_name:
271 subnet.subnet_id = net.subnet_id
272 break
273 for port in old_stack.ports.values():
274 if port.name in new_stack.ports:
275 new_stack.ports[port.name].id = port.id
276 for router in old_stack.routers.values():
277 if router.name in new_stack.routers:
278 new_stack.routers[router.name].id = router.id
279
280 # Update the compute dicts to now contain the new_stack components
281 self.update_compute_dicts(new_stack)
282
283 self.update_ip_addresses(old_stack, new_stack)
284
285 # Update all interface names - after each port has the correct UUID!!
286 for port in new_stack.ports.values():
287 port.create_intf_name()
288
289 if not self.check_stack(new_stack):
290 return False
291
292 # Remove unnecessary networks
293 for net in old_stack.nets.values():
peusterm72f09882018-05-15 17:10:27 +0200294 if net.name not in new_stack.nets:
peusterm00199782017-05-17 08:48:12 +0200295 self.delete_network(net.id)
296
297 # Remove all unnecessary servers
298 for server in old_stack.servers.values():
299 if server.name in new_stack.servers:
peusterm72f09882018-05-15 17:10:27 +0200300 if not server.compare_attributes(
301 new_stack.servers[server.name]):
peusterm00199782017-05-17 08:48:12 +0200302 self.stop_compute(server)
303 else:
304 # Delete unused and changed links
305 for port_name in server.port_names:
306 if port_name in old_stack.ports and port_name in new_stack.ports:
peusterm72f09882018-05-15 17:10:27 +0200307 if not old_stack.ports.get(
308 port_name) == new_stack.ports.get(port_name):
peusterm00199782017-05-17 08:48:12 +0200309 my_links = self.dc.net.links
310 for link in my_links:
311 if str(link.intf1) == old_stack.ports[port_name].intf_name and \
peusterm72f09882018-05-15 17:10:27 +0200312 str(link.intf1.ip) == \
313 old_stack.ports[port_name].ip_address.split('/')[0]:
peusterm00199782017-05-17 08:48:12 +0200314 self._remove_link(server.name, link)
315
316 # Add changed link
317 self._add_link(server.name,
318 new_stack.ports[port_name].ip_address,
319 new_stack.ports[port_name].intf_name,
320 new_stack.ports[port_name].net_name)
321 break
322 else:
323 my_links = self.dc.net.links
324 for link in my_links:
325 if str(link.intf1) == old_stack.ports[port_name].intf_name and \
326 str(link.intf1.ip) == old_stack.ports[port_name].ip_address.split('/')[0]:
327 self._remove_link(server.name, link)
328 break
329
330 # Create new links
331 for port_name in new_stack.servers[server.name].port_names:
332 if port_name not in server.port_names:
333 self._add_link(server.name,
334 new_stack.ports[port_name].ip_address,
335 new_stack.ports[port_name].intf_name,
336 new_stack.ports[port_name].net_name)
337 else:
338 self.stop_compute(server)
339
340 # Start all new servers
341 for server in new_stack.servers.values():
342 if server.name not in self.dc.containers:
343 self._start_compute(server)
344 else:
345 server.emulator_compute = self.dc.containers.get(server.name)
346
347 del self.stacks[old_stack_id]
348 self.stacks[new_stack.id] = new_stack
349 return True
350
351 def update_ip_addresses(self, old_stack, new_stack):
352 """
353 Updates the subnet and the port IP addresses - which should always be in this order!
354
355 :param old_stack: The currently running stack
356 :type old_stack: :class:`heat.resources.stack`
357 :param new_stack: The new created stack
358 :type new_stack: :class:`heat.resources.stack`
359 """
360 self.update_subnet_cidr(old_stack, new_stack)
361 self.update_port_addresses(old_stack, new_stack)
362
363 def update_port_addresses(self, old_stack, new_stack):
364 """
365 Updates the port IP addresses. First resets all issued addresses. Then get all IP addresses from the old
366 stack and sets them to the same ports in the new stack. Finally all new or changed instances will get new
367 IP addresses.
368
369 :param old_stack: The currently running stack
370 :type old_stack: :class:`heat.resources.stack`
371 :param new_stack: The new created stack
372 :type new_stack: :class:`heat.resources.stack`
373 """
374 for net in new_stack.nets.values():
375 net.reset_issued_ip_addresses()
376
377 for old_port in old_stack.ports.values():
378 for port in new_stack.ports.values():
379 if port.compare_attributes(old_port):
380 for net in new_stack.nets.values():
381 if net.name == port.net_name:
peusterm72f09882018-05-15 17:10:27 +0200382 if net.assign_ip_address(
383 old_port.ip_address, port.name):
peusterm00199782017-05-17 08:48:12 +0200384 port.ip_address = old_port.ip_address
385 port.mac_address = old_port.mac_address
386 else:
peusterm72f09882018-05-15 17:10:27 +0200387 port.ip_address = net.get_new_ip_address(
388 port.name)
peusterm00199782017-05-17 08:48:12 +0200389
390 for port in new_stack.ports.values():
391 for net in new_stack.nets.values():
peusterm72f09882018-05-15 17:10:27 +0200392 if port.net_name == net.name and not net.is_my_ip(
393 port.ip_address, port.name):
peusterm00199782017-05-17 08:48:12 +0200394 port.ip_address = net.get_new_ip_address(port.name)
395
396 def update_subnet_cidr(self, old_stack, new_stack):
397 """
398 Updates the subnet IP addresses. If the new stack contains subnets from the old stack it will take those
399 IP addresses. Otherwise it will create new IP addresses for the subnet.
400
401 :param old_stack: The currently running stack
402 :type old_stack: :class:`heat.resources.stack`
403 :param new_stack: The new created stack
404 :type new_stack: :class:`heat.resources.stack`
405 """
406 for old_subnet in old_stack.nets.values():
407 IP.free_cidr(old_subnet.get_cidr(), old_subnet.subnet_id)
408
409 for subnet in new_stack.nets.values():
410 subnet.clear_cidr()
411 for old_subnet in old_stack.nets.values():
412 if subnet.subnet_name == old_subnet.subnet_name:
413 if IP.assign_cidr(old_subnet.get_cidr(), subnet.subnet_id):
414 subnet.set_cidr(old_subnet.get_cidr())
415
416 for subnet in new_stack.nets.values():
417 if IP.is_cidr_issued(subnet.get_cidr()):
418 continue
419
420 cird = IP.get_new_cidr(subnet.subnet_id)
421 subnet.set_cidr(cird)
422 return
423
424 def update_compute_dicts(self, stack):
425 """
426 Update and add all stack components tho the compute dictionaries.
427
428 :param stack: A stack reference, to get all required components.
429 :type stack: :class:`heat.resources.stack`
430 """
431 for server in stack.servers.values():
432 self.computeUnits[server.id] = server
433 if isinstance(server.flavor, dict):
434 self.add_flavor(server.flavor['flavorName'],
435 server.flavor['vcpu'],
436 server.flavor['ram'], 'MB',
437 server.flavor['storage'], 'GB')
438 server.flavor = server.flavor['flavorName']
439 for router in stack.routers.values():
440 self.routers[router.id] = router
441 for net in stack.nets.values():
442 self.nets[net.id] = net
443 for port in stack.ports.values():
444 self.ports[port.id] = port
445
446 def _start_compute(self, server):
447 """
448 Starts a new compute object (docker container) inside the emulator.
449 Should only be called by stack modifications and not directly.
450
451 :param server: Specifies the compute resource.
452 :type server: :class:`heat.resources.server`
453 """
peustermbbf4f742017-06-19 11:22:11 +0200454 LOG.debug("Starting new compute resources %s" % server.name)
peusterm00199782017-05-17 08:48:12 +0200455 network = list()
peusterma7161aa2017-06-29 15:32:20 +0200456 network_dict = dict()
peusterm00199782017-05-17 08:48:12 +0200457
458 for port_name in server.port_names:
459 network_dict = dict()
460 port = self.find_port_by_name_or_id(port_name)
461 if port is not None:
462 network_dict['id'] = port.intf_name
463 network_dict['ip'] = port.ip_address
peusterm72f09882018-05-15 17:10:27 +0200464 network_dict[network_dict['id']] = self.find_network_by_name_or_id(
465 port.net_name).name
peusterm00199782017-05-17 08:48:12 +0200466 network.append(network_dict)
peusterma7161aa2017-06-29 15:32:20 +0200467 # default network dict
468 if len(network) < 1:
469 network_dict['id'] = server.name + "-eth0"
470 network_dict[network_dict['id']] = network_dict['id']
471 network.append(network_dict)
peusterm6e354f12017-06-27 12:44:13 +0200472
peusterm00199782017-05-17 08:48:12 +0200473 self.compute_nets[server.name] = network
peusterma7161aa2017-06-29 15:32:20 +0200474 LOG.debug("Network dict: {}".format(network))
peusterm00199782017-05-17 08:48:12 +0200475 c = self.dc.startCompute(server.name, image=server.image, command=server.command,
splietker7b38ee12017-06-28 17:24:01 +0200476 network=network, flavor_name=server.flavor,
477 properties=server.properties)
peusterm00199782017-05-17 08:48:12 +0200478 server.emulator_compute = c
479
480 for intf in c.intfs.values():
481 for port_name in server.port_names:
482 port = self.find_port_by_name_or_id(port_name)
483 if port is not None:
484 if intf.name == port.intf_name:
485 # wait up to one second for the intf to come up
486 self.timeout_sleep(intf.isUp, 1)
487 if port.mac_address is not None:
488 intf.setMAC(port.mac_address)
489 else:
490 port.mac_address = intf.MAC()
schillingeb87991a2018-12-03 11:34:01 +0100491 port.assigned_container = c
peusterm00199782017-05-17 08:48:12 +0200492
493 # Start the real emulator command now as specified in the dockerfile
494 # ENV SON_EMU_CMD
495 config = c.dcinfo.get("Config", dict())
496 env = config.get("Env", list())
497 for env_var in env:
498 if "SON_EMU_CMD=" in env_var:
499 cmd = str(env_var.split("=")[1])
500 server.son_emu_command = cmd
peusterm72f09882018-05-15 17:10:27 +0200501 # execute command in new thread to ensure that GK is not
502 # blocked by VNF
peusterm00199782017-05-17 08:48:12 +0200503 t = threading.Thread(target=c.cmdPrint, args=(cmd,))
504 t.daemon = True
505 t.start()
506
507 def stop_compute(self, server):
508 """
509 Determines which links should be removed before removing the server itself.
510
511 :param server: The server that should be removed
512 :type server: ``heat.resources.server``
513 """
peusterm72f09882018-05-15 17:10:27 +0200514 LOG.debug("Stopping container %s with full name %s" %
515 (server.name, server.full_name))
peusterm00199782017-05-17 08:48:12 +0200516 link_names = list()
517 for port_name in server.port_names:
peusterm832a0b82017-07-11 16:08:35 +0200518 prt = self.find_port_by_name_or_id(port_name)
519 if prt is not None:
520 link_names.append(prt.intf_name)
peusterm00199782017-05-17 08:48:12 +0200521 my_links = self.dc.net.links
522 for link in my_links:
523 if str(link.intf1) in link_names:
peusterm72f09882018-05-15 17:10:27 +0200524 # Remove all self created links that connect the server to the
525 # main switch
peusterm00199782017-05-17 08:48:12 +0200526 self._remove_link(server.name, link)
527
528 # Stop the server and the remaining connection to the datacenter switch
529 self.dc.stopCompute(server.name)
530 # Only now delete all its ports and the server itself
531 for port_name in server.port_names:
532 self.delete_port(port_name)
533 self.delete_server(server)
534
535 def find_server_by_name_or_id(self, name_or_id):
536 """
537 Tries to find the server by ID and if this does not succeed then tries to find it via name.
538
539 :param name_or_id: UUID or name of the server.
540 :type name_or_id: ``str``
541 :return: Returns the server reference if it was found or None
542 :rtype: :class:`heat.resources.server`
543 """
544 if name_or_id in self.computeUnits:
545 return self.computeUnits[name_or_id]
546
peusterm3f2f5652017-06-23 15:00:21 +0200547 if self._shorten_server_name(name_or_id) in self.computeUnits:
548 return self.computeUnits[name_or_id]
549
peusterm00199782017-05-17 08:48:12 +0200550 for server in self.computeUnits.values():
peusterm72f09882018-05-15 17:10:27 +0200551 if (server.name == name_or_id or
552 server.template_name == name_or_id or
553 server.full_name == name_or_id):
peusterm00199782017-05-17 08:48:12 +0200554 return server
peusterm72f09882018-05-15 17:10:27 +0200555 if (server.name == self._shorten_server_name(name_or_id) or
556 server.template_name == self._shorten_server_name(name_or_id) or
557 server.full_name == self._shorten_server_name(name_or_id)):
peusterm3f2f5652017-06-23 15:00:21 +0200558 return server
peusterm00199782017-05-17 08:48:12 +0200559 return None
560
561 def create_server(self, name, stack_operation=False):
562 """
563 Creates a server with the specified name. Raises an exception when a server with the given name already
564 exists!
565
566 :param name: Name of the new server.
567 :type name: ``str``
568 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
569 :type stack_operation: ``bool``
570 :return: Returns the created server.
571 :rtype: :class:`heat.resources.server`
572 """
peusterm72f09882018-05-15 17:10:27 +0200573 if self.find_server_by_name_or_id(
574 name) is not None and not stack_operation:
peusterm00199782017-05-17 08:48:12 +0200575 raise Exception("Server with name %s already exists." % name)
peusterm3f2f5652017-06-23 15:00:21 +0200576 safe_name = self._shorten_server_name(name)
577 server = Server(safe_name)
peusterm00199782017-05-17 08:48:12 +0200578 server.id = str(uuid.uuid4())
579 if not stack_operation:
580 self.computeUnits[server.id] = server
581 return server
582
peusterm646b5852017-06-26 09:59:12 +0200583 def _shorten_server_name(self, name, char_limit=9):
peusterm3f2f5652017-06-23 15:00:21 +0200584 """
585 Docker does not like too long instance names.
586 This function provides a shorter name if needed
587 """
peusterm3f2f5652017-06-23 15:00:21 +0200588 if len(name) > char_limit:
peusterm6e354f12017-06-27 12:44:13 +0200589 LOG.info("Long server name: {}".format(name))
peusterm3f2f5652017-06-23 15:00:21 +0200590 # construct a short name
peusterm92190602017-12-19 15:18:11 +0100591 h = hashlib.sha224(name).hexdigest()
592 h = h[0:char_limit]
593 LOG.info("Short server name: {}".format(h))
peusterm3f2f5652017-06-23 15:00:21 +0200594 return name
595
peusterm00199782017-05-17 08:48:12 +0200596 def delete_server(self, server):
597 """
598 Deletes the given server from the stack dictionary and the computeUnits dictionary.
599
600 :param server: Reference of the server that should be deleted.
601 :type server: :class:`heat.resources.server`
602 :return: * *False*: If the server name is not in the correct format ('datacentername_stackname_servername') \
603 or when no stack with the correct stackname was found.
604 * *True*: Else
605 :rtype: ``bool``
606 """
607 if server is None:
608 return False
609 name_parts = server.name.split('_')
peusterm543430b2018-04-24 16:52:51 +0200610 if len(name_parts) > 1:
611 for stack in self.stacks.values():
612 if stack.stack_name == name_parts[1]:
613 stack.servers.pop(server.id, None)
peusterm00199782017-05-17 08:48:12 +0200614 if self.computeUnits.pop(server.id, None) is None:
615 return False
616 return True
617
618 def find_network_by_name_or_id(self, name_or_id):
619 """
620 Tries to find the network by ID and if this does not succeed then tries to find it via name.
621
622 :param name_or_id: UUID or name of the network.
623 :type name_or_id: ``str``
624 :return: Returns the network reference if it was found or None
625 :rtype: :class:`heat.resources.net`
626 """
627 if name_or_id in self.nets:
628 return self.nets[name_or_id]
629 for net in self.nets.values():
630 if net.name == name_or_id:
631 return net
peusterm7c16ebc2018-04-25 15:36:15 +0200632 LOG.warning("Could not find net '{}' in {} or {}"
633 .format(name_or_id,
634 self.nets.keys(),
635 [n.name for n in self.nets.values()]))
peusterm00199782017-05-17 08:48:12 +0200636 return None
637
638 def create_network(self, name, stack_operation=False):
639 """
640 Creates a new network with the given name. Raises an exception when a network with the given name already
641 exists!
642
643 :param name: Name of the new network.
644 :type name: ``str``
645 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
646 :type stack_operation: ``bool``
647 :return: :class:`heat.resources.net`
648 """
peustermbbf4f742017-06-19 11:22:11 +0200649 LOG.debug("Creating network with name %s" % name)
peusterm72f09882018-05-15 17:10:27 +0200650 if self.find_network_by_name_or_id(
651 name) is not None and not stack_operation:
652 LOG.warning(
653 "Creating network with name %s failed, as it already exists" % name)
peusterm00199782017-05-17 08:48:12 +0200654 raise Exception("Network with name %s already exists." % name)
655 network = Net(name)
656 network.id = str(uuid.uuid4())
657 if not stack_operation:
658 self.nets[network.id] = network
659 return network
660
661 def delete_network(self, name_or_id):
662 """
663 Deletes the given network.
664
665 :param name_or_id: Name or UUID of the network.
666 :type name_or_id: ``str``
667 """
668 net = self.find_network_by_name_or_id(name_or_id)
669 if net is None:
peusterm72f09882018-05-15 17:10:27 +0200670 raise Exception(
671 "Network with name or id %s does not exists." % name_or_id)
peusterm00199782017-05-17 08:48:12 +0200672
673 for stack in self.stacks.values():
674 stack.nets.pop(net.name, None)
675
676 self.nets.pop(net.id, None)
677
678 def create_port(self, name, stack_operation=False):
679 """
680 Creates a new port with the given name. Raises an exception when a port with the given name already
681 exists!
682
683 :param name: Name of the new port.
684 :type name: ``str``
685 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
686 :type stack_operation: ``bool``
687 :return: Returns the created port.
688 :rtype: :class:`heat.resources.port`
689 """
peusterm00199782017-05-17 08:48:12 +0200690 port = Port(name)
691 if not stack_operation:
692 self.ports[port.id] = port
693 port.create_intf_name()
694 return port
695
696 def find_port_by_name_or_id(self, name_or_id):
697 """
698 Tries to find the port by ID and if this does not succeed then tries to find it via name.
699
700 :param name_or_id: UUID or name of the network.
701 :type name_or_id: ``str``
702 :return: Returns the port reference if it was found or None
703 :rtype: :class:`heat.resources.port`
704 """
schillinge0e108192019-01-30 17:48:27 +0100705 # find by id
peusterm00199782017-05-17 08:48:12 +0200706 if name_or_id in self.ports:
707 return self.ports[name_or_id]
schillinge0e108192019-01-30 17:48:27 +0100708 # find by name
709 matching_ports = filter(
710 lambda port: port.name == name_or_id or port.template_name == name_or_id,
711 self.ports.values()
712 )
713 matching_ports_count = len(matching_ports)
714 if matching_ports_count == 1:
715 return matching_ports[0]
716 if matching_ports_count > 1:
717 raise RuntimeError("Ambiguous port name %s" % name_or_id)
peusterm00199782017-05-17 08:48:12 +0200718 return None
719
720 def delete_port(self, name_or_id):
721 """
722 Deletes the given port. Raises an exception when the port was not found!
723
724 :param name_or_id: UUID or name of the port.
725 :type name_or_id: ``str``
726 """
727 port = self.find_port_by_name_or_id(name_or_id)
728 if port is None:
peusterm72f09882018-05-15 17:10:27 +0200729 LOG.warning(
730 "Port with name or id %s does not exist. Can't delete it." % name_or_id)
peusterm832a0b82017-07-11 16:08:35 +0200731 return
peusterm00199782017-05-17 08:48:12 +0200732
733 my_links = self.dc.net.links
734 for link in my_links:
peusterm9679e9a2018-05-16 13:10:30 +0200735 if str(link.intf1) == port.intf_name:
peusterm00199782017-05-17 08:48:12 +0200736 self._remove_link(link.intf1.node.name, link)
737 break
738
739 self.ports.pop(port.id, None)
740 for stack in self.stacks.values():
741 stack.ports.pop(port.name, None)
742
splietker7b38ee12017-06-28 17:24:01 +0200743 def create_port_pair(self, name, stack_operation=False):
744 """
745 Creates a new port pair with the given name. Raises an exception when a port pair with the given name already
746 exists!
747
748 :param name: Name of the new port pair.
749 :type name: ``str``
750 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
751 :type stack_operation: ``bool``
752 :return: Returns the created port pair.
753 :rtype: :class:`openstack.resources.port_pair`
754 """
755 port_pair = self.find_port_pair_by_name_or_id(name)
756 if port_pair is not None and not stack_operation:
peusterm72f09882018-05-15 17:10:27 +0200757 logging.warning(
758 "Creating port pair with name %s failed, as it already exists" % name)
splietker7b38ee12017-06-28 17:24:01 +0200759 raise Exception("Port pair with name %s already exists." % name)
760 logging.debug("Creating port pair with name %s" % name)
761 port_pair = PortPair(name)
762 if not stack_operation:
763 self.port_pairs[port_pair.id] = port_pair
764 return port_pair
765
766 def find_port_pair_by_name_or_id(self, name_or_id):
767 """
768 Tries to find the port pair by ID and if this does not succeed then tries to find it via name.
769
770 :param name_or_id: UUID or name of the port pair.
771 :type name_or_id: ``str``
772 :return: Returns the port pair reference if it was found or None
773 :rtype: :class:`openstack.resources.port_pair`
774 """
775 if name_or_id in self.port_pairs:
776 return self.port_pairs[name_or_id]
777 for port_pair in self.port_pairs.values():
778 if port_pair.name == name_or_id:
779 return port_pair
780
781 return None
782
783 def delete_port_pair(self, name_or_id):
784 """
785 Deletes the given port pair. Raises an exception when the port pair was not found!
786
787 :param name_or_id: UUID or name of the port pair.
788 :type name_or_id: ``str``
789 """
790 port_pair = self.find_port_pair_by_name_or_id(name_or_id)
791 if port_pair is None:
peusterm72f09882018-05-15 17:10:27 +0200792 raise Exception(
793 "Port pair with name or id %s does not exists." % name_or_id)
splietker7b38ee12017-06-28 17:24:01 +0200794
795 self.port_pairs.pop(port_pair.id, None)
796
797 def create_port_pair_group(self, name, stack_operation=False):
798 """
799 Creates a new port pair group with the given name. Raises an exception when a port pair group
800 with the given name already exists!
801
802 :param name: Name of the new port pair group.
803 :type name: ``str``
804 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
805 :type stack_operation: ``bool``
806 :return: Returns the created port pair group .
807 :rtype: :class:`openstack.resources.port_pair_group`
808 """
809 port_pair_group = self.find_port_pair_group_by_name_or_id(name)
810 if port_pair_group is not None and not stack_operation:
peusterm72f09882018-05-15 17:10:27 +0200811 logging.warning(
812 "Creating port pair group with name %s failed, as it already exists" % name)
813 raise Exception(
814 "Port pair group with name %s already exists." % name)
splietker7b38ee12017-06-28 17:24:01 +0200815 logging.debug("Creating port pair group with name %s" % name)
816 port_pair_group = PortPairGroup(name)
817 if not stack_operation:
818 self.port_pair_groups[port_pair_group.id] = port_pair_group
819 return port_pair_group
820
821 def find_port_pair_group_by_name_or_id(self, name_or_id):
822 """
823 Tries to find the port pair group by ID and if this does not succeed then tries to find it via name.
824
825 :param name_or_id: UUID or name of the port pair group.
826 :type name_or_id: ``str``
827 :return: Returns the port pair group reference if it was found or None
828 :rtype: :class:`openstack.resources.port_pair_group`
829 """
830 if name_or_id in self.port_pair_groups:
831 return self.port_pair_groups[name_or_id]
832 for port_pair_group in self.port_pair_groups.values():
833 if port_pair_group.name == name_or_id:
834 return port_pair_group
835
836 return None
837
838 def delete_port_pair_group(self, name_or_id):
839 """
840 Deletes the given port pair group. Raises an exception when the port pair group was not found!
841
842 :param name_or_id: UUID or name of the port pair group.
843 :type name_or_id: ``str``
844 """
845 port_pair_group = self.find_port_pair_group_by_name_or_id(name_or_id)
846 if port_pair_group is None:
peusterm72f09882018-05-15 17:10:27 +0200847 raise Exception(
848 "Port pair with name or id %s does not exists." % name_or_id)
splietker7b38ee12017-06-28 17:24:01 +0200849
850 self.port_pair_groups.pop(port_pair_group.id, None)
851
852 def create_port_chain(self, name, stack_operation=False):
853 """
854 Creates a new port chain with the given name. Raises an exception when a port chain with the given name already
855 exists!
856
857 :param name: Name of the new port chain
858 :type name: ``str``
859 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
860 :type stack_operation: ``bool``
861 :return: Returns the created port chain.
862 :rtype: :class:`openstack.resources.port_chain.PortChain`
863 """
864 port_chain = self.find_port_chain_by_name_or_id(name)
865 if port_chain is not None and not stack_operation:
peusterm72f09882018-05-15 17:10:27 +0200866 logging.warning(
867 "Creating port chain with name %s failed, as it already exists" % name)
splietker7b38ee12017-06-28 17:24:01 +0200868 raise Exception("Port chain with name %s already exists." % name)
869 logging.debug("Creating port chain with name %s" % name)
870 port_chain = PortChain(name)
871 if not stack_operation:
872 self.port_chains[port_chain.id] = port_chain
873 return port_chain
874
875 def find_port_chain_by_name_or_id(self, name_or_id):
876 """
877 Tries to find the port chain by ID and if this does not succeed then tries to find it via name.
878
879 :param name_or_id: UUID or name of the port chain.
880 :type name_or_id: ``str``
881 :return: Returns the port chain reference if it was found or None
882 :rtype: :class:`openstack.resources.port_chain.PortChain`
883 """
884 if name_or_id in self.port_chains:
885 return self.port_chains[name_or_id]
886 for port_chain in self.port_chains.values():
887 if port_chain.name == name_or_id:
888 return port_chain
889 return None
890
891 def delete_port_chain(self, name_or_id):
892 """
893 Deletes the given port chain. Raises an exception when the port chain was not found!
894
895 :param name_or_id: UUID or name of the port chain.
896 :type name_or_id: ``str``
897 """
898 port_chain = self.find_port_chain_by_name_or_id(name_or_id)
899 port_chain.uninstall(self)
900 if port_chain is None:
peusterm72f09882018-05-15 17:10:27 +0200901 raise Exception(
902 "Port chain with name or id %s does not exists." % name_or_id)
splietker7b38ee12017-06-28 17:24:01 +0200903
904 self.port_chains.pop(port_chain.id, None)
905
906 def create_flow_classifier(self, name, stack_operation=False):
907 """
908 Creates a new flow classifier with the given name. Raises an exception when a flow classifier with the given name already
909 exists!
910
911 :param name: Name of the new flow classifier.
912 :type name: ``str``
913 :param stack_operation: Allows the heat parser to create modules without adapting the current emulation.
914 :type stack_operation: ``bool``
915 :return: Returns the created flow classifier.
916 :rtype: :class:`openstack.resources.flow_classifier`
917 """
918 flow_classifier = self.find_flow_classifier_by_name_or_id(name)
919 if flow_classifier is not None and not stack_operation:
peusterm72f09882018-05-15 17:10:27 +0200920 logging.warning(
921 "Creating flow classifier with name %s failed, as it already exists" % name)
922 raise Exception(
923 "Flow classifier with name %s already exists." % name)
splietker7b38ee12017-06-28 17:24:01 +0200924 logging.debug("Creating flow classifier with name %s" % name)
925 flow_classifier = FlowClassifier(name)
926 if not stack_operation:
927 self.flow_classifiers[flow_classifier.id] = flow_classifier
928 return flow_classifier
929
930 def find_flow_classifier_by_name_or_id(self, name_or_id):
931 """
932 Tries to find the flow classifier by ID and if this does not succeed then tries to find it via name.
933
934 :param name_or_id: UUID or name of the flow classifier.
935 :type name_or_id: ``str``
936 :return: Returns the flow classifier reference if it was found or None
937 :rtype: :class:`openstack.resources.flow_classifier`
938 """
939 if name_or_id in self.flow_classifiers:
940 return self.flow_classifiers[name_or_id]
941 for flow_classifier in self.flow_classifiers.values():
942 if flow_classifier.name == name_or_id:
943 return flow_classifier
944
945 return None
946
947 def delete_flow_classifier(self, name_or_id):
948 """
949 Deletes the given flow classifier. Raises an exception when the flow classifier was not found!
950
951 :param name_or_id: UUID or name of the flow classifier.
952 :type name_or_id: ``str``
953 """
954 flow_classifier = self.find_flow_classifier_by_name_or_id(name_or_id)
955 if flow_classifier is None:
peusterm72f09882018-05-15 17:10:27 +0200956 raise Exception(
957 "Flow classifier with name or id %s does not exists." % name_or_id)
splietker7b38ee12017-06-28 17:24:01 +0200958
959 self.flow_classifiers.pop(flow_classifier.id, None)
960
peusterm00199782017-05-17 08:48:12 +0200961 def _add_link(self, node_name, ip_address, link_name, net_name):
962 """
963 Adds a new link between datacenter switch and the node with the given name.
964
965 :param node_name: Name of the required node.
966 :type node_name: ``str``
967 :param ip_address: IP-Address of the node.
968 :type ip_address: ``str``
969 :param link_name: Link name.
970 :type link_name: ``str``
971 :param net_name: Network name.
972 :type net_name: ``str``
973 """
974 node = self.dc.net.get(node_name)
975 params = {'params1': {'ip': ip_address,
976 'id': link_name,
977 link_name: net_name},
978 'intfName1': link_name,
979 'cls': Link}
980 link = self.dc.net.addLink(node, self.dc.switch, **params)
981 OpenstackCompute.timeout_sleep(link.intf1.isUp, 1)
982
983 def _remove_link(self, server_name, link):
984 """
985 Removes a link between server and datacenter switch.
986
987 :param server_name: Specifies the server where the link starts.
988 :type server_name: ``str``
989 :param link: A reference of the link which should be removed.
990 :type link: :class:`mininet.link`
991 """
992 self.dc.switch.detach(link.intf2)
993 del self.dc.switch.intfs[self.dc.switch.ports[link.intf2]]
994 del self.dc.switch.ports[link.intf2]
995 del self.dc.switch.nameToIntf[link.intf2.name]
996 self.dc.net.removeLink(link=link)
peusterm00199782017-05-17 08:48:12 +0200997 for intf_key in self.dc.net[server_name].intfs.keys():
998 if self.dc.net[server_name].intfs[intf_key].link == link:
999 self.dc.net[server_name].intfs[intf_key].delete()
1000 del self.dc.net[server_name].intfs[intf_key]
1001
1002 @staticmethod
1003 def timeout_sleep(function, max_sleep):
1004 """
1005 This function will execute a function all 0.1 seconds until it successfully returns.
1006 Will return after `max_sleep` seconds if not successful.
1007
1008 :param function: The function to execute. Should return true if done.
1009 :type function: ``function``
1010 :param max_sleep: Max seconds to sleep. 1 equals 1 second.
1011 :type max_sleep: ``float``
1012 """
1013 current_time = time.time()
1014 stop_time = current_time + max_sleep
1015 while not function() and current_time < stop_time:
1016 current_time = time.time()
1017 time.sleep(0.1)