8b4a35064f270f5b18e086e29d45a288b4fee455
[osm/vim-emu.git] / src / emuvim / api / tango / llcm.py
1
2 # Copyright (c) 2018 SONATA-NFV, 5GTANGO and Paderborn University
3 # ALL RIGHTS RESERVED.
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #
17 # Neither the name of the SONATA-NFV, 5GTANGO, Paderborn University
18 # nor the names of its contributors may be used to endorse or promote
19 # products derived from this software without specific prior written
20 # permission.
21 #
22 # This work has been performed in the framework of the SONATA project,
23 # funded by the European Commission under Grant number 671517 through
24 # the Horizon 2020 and 5G-PPP programmes. The authors would like to
25 # acknowledge the contributions of their colleagues of the SONATA
26 # partner consortium (www.sonata-nfv.eu).
27 #
28 # This work has also been performed in the framework of the 5GTANGO project,
29 # funded by the European Commission under Grant number 761493 through
30 # the Horizon 2020 and 5G-PPP programmes. The authors would like to
31 # acknowledge the contributions of their colleagues of the 5GTANGO
32 # partner consortium (www.5gtango.eu).
33 import logging
34 import os
35 import uuid
36 import hashlib
37 import zipfile
38 import yaml
39 import threading
40 import datetime
41 from docker import DockerClient
42 from flask import Flask, request
43 import flask_restful as fr
44 from gevent.pywsgi import WSGIServer
45 from subprocess import Popen
46 import ipaddress
47 import copy
48 import time
49
50
51 LOG = logging.getLogger("5gtango.llcm")
52 LOG.setLevel(logging.INFO)
53
54
55 GK_STORAGE = "/tmp/vim-emu-tango-llcm/"
56 UPLOAD_FOLDER = os.path.join(GK_STORAGE, "uploads/")
57 CATALOG_FOLDER = os.path.join(GK_STORAGE, "catalog/")
58
59 # Enable Dockerfile build functionality
60 BUILD_DOCKERFILE = False
61
62 # flag to indicate that we run without the emulator (only the bare API for
63 # integration testing)
64 GK_STANDALONE_MODE = False
65
66 # should a new version of an image be pulled even if its available
67 FORCE_PULL = False
68
69 # flag to indicate if we use bidirectional forwarding rules in the
70 # automatic chaining process
71 BIDIRECTIONAL_CHAIN = True
72
73 # override the management interfaces in the descriptors with default
74 # docker0 interfaces in the containers
75 USE_DOCKER_MGMT = False
76
77 # automatically deploy uploaded packages (no need to execute son-access
78 # deploy --latest separately)
79 AUTO_DEPLOY = False
80
81 # and also automatically terminate any other running services
82 AUTO_DELETE = False
83
84 # global subnet definitions (see reset_subnets())
85 ELAN_SUBNETS = None
86 ELINE_SUBNETS = None
87
88 # Time in seconds to wait for vnf stop scripts to execute fully
89 VNF_STOP_WAIT_TIME = 5
90
91 # If services are instantiated multiple times, the public port
92 # mappings need to be adapted to avoid colisions. We use this
93 # offset for this: NEW_PORT (SSIID * OFFSET) + ORIGINAL_PORT
94 MULTI_INSTANCE_PORT_OFFSET = 1000
95
96 # Selected Placement Algorithm: Points to the class of the selected
97 # placement algorithm.
98 PLACEMENT_ALGORITHM_OBJ = None
99
100 # Path to folder with <container_name>.env.yml files that contain
101 # environment variables injected into the specific container
102 # when it is started.
103 PER_INSTANCE_ENV_CONFIGURATION_FOLDER = None
104
105
106 class OnBoardingException(BaseException):
107 pass
108
109
110 class Gatekeeper(object):
111
112 def __init__(self):
113 self.services = dict()
114 self.dcs = dict()
115 self.net = None
116 # used to generate short names for VNFs (Mininet limitation)
117 self.vnf_counter = 0
118 reset_subnets()
119 LOG.info("Initialized 5GTANGO LLCM module.")
120
121 def register_service_package(self, service_uuid, service):
122 """
123 register new service package
124 :param service_uuid
125 :param service object
126 """
127 self.services[service_uuid] = service
128 # lets perform all steps needed to onboard the service
129 service.onboard()
130
131
132 class Service(object):
133 """
134 This class represents a NS uploaded as a *.son package to the
135 dummy gatekeeper.
136 Can have multiple running instances of this service.
137 """
138
139 def __init__(self,
140 service_uuid,
141 package_file_hash,
142 package_file_path):
143 self.uuid = service_uuid
144 self.package_file_hash = package_file_hash
145 self.package_file_path = package_file_path
146 self.package_content_path = os.path.join(
147 CATALOG_FOLDER, "services/%s" % self.uuid)
148 self.manifest = None
149 self.nsd = None
150 self.vnfds = dict()
151 self.local_docker_files = dict()
152 self.remote_docker_image_urls = dict()
153 self.instances = dict()
154 self._instance_counter = 0
155 self.created_at = str(datetime.datetime.now())
156
157 def onboard(self):
158 """
159 Do all steps to prepare this service to be instantiated
160 :return:
161 """
162 # 1. extract the contents of the package and store them in our catalog
163 self._unpack_service_package()
164 # 2. read in all descriptor files
165 self._load_package_descriptor()
166 self._load_nsd()
167 self._load_vnfd()
168 if self.nsd is None:
169 raise OnBoardingException("No NSD found.")
170 if len(self.vnfds) < 1:
171 raise OnBoardingException("No VNFDs found.")
172 # 3. prepare container images (e.g. download or build Dockerfile)
173 if BUILD_DOCKERFILE:
174 self._load_docker_files()
175 self._build_images_from_dockerfiles()
176 else:
177 self._load_docker_urls()
178 self._pull_predefined_dockerimages()
179 # 4. reserve subnets
180 eline_fwd_links, elan_fwd_links = self._get_elines_and_elans()
181 self.eline_subnets = [ELINE_SUBNETS.pop(0) for _ in eline_fwd_links]
182 self.elan_subnets = [ELAN_SUBNETS.pop(0) for _ in elan_fwd_links]
183 LOG.debug("Reserved subnets for service '{}': E-Line: {} / E-LAN: {}"
184 .format(self.manifest.get("name"),
185 self.eline_subnets, self.elan_subnets))
186 LOG.info("On-boarded service: {}".format(self.manifest.get("name")))
187
188 def start_service(self):
189 """
190 This methods creates and starts a new service instance.
191 It computes placements, iterates over all VNFDs, and starts
192 each VNFD as a Docker container in the data center selected
193 by the placement algorithm.
194 :return:
195 """
196 LOG.info("Starting service {} ({})"
197 .format(get_triple_id(self.nsd), self.uuid))
198
199 # 1. each service instance gets a new uuid to identify it
200 instance_uuid = str(uuid.uuid4())
201 # build a instances dict (a bit like a NSR :))
202 self.instances[instance_uuid] = dict()
203 self.instances[instance_uuid]["uuid"] = self.uuid
204 # SSIID = short service instance ID (to postfix Container names)
205 self.instances[instance_uuid]["ssiid"] = self._instance_counter
206 self.instances[instance_uuid]["name"] = get_triple_id(self.nsd)
207 self.instances[instance_uuid]["vnf_instances"] = list()
208 self.instances[instance_uuid]["created_at"] = str(datetime.datetime.now())
209 # increase for next instance
210 self._instance_counter += 1
211
212 # 3. start all vnfds that we have in the service
213 for vnf_id in self.vnfds:
214 vnfd = self.vnfds[vnf_id]
215 # attention: returns a list of started deployment units
216 vnfis = self._start_vnfd(
217 vnfd, vnf_id, self.instances[instance_uuid]["ssiid"])
218 # add list of VNFIs to total VNFI list
219 self.instances[instance_uuid]["vnf_instances"].extend(vnfis)
220
221 # 4. Deploy E-Line, E-Tree and E-LAN links
222 # Attention: Only done if ""forwarding_graphs" section in NSD exists,
223 # even if "forwarding_graphs" are not used directly.
224 # Attention2: Do a copy of *_subnets with list() is important here!
225 eline_fwd_links, elan_fwd_links = self._get_elines_and_elans()
226 # 5a. deploy E-Line links
227 GK.net.deployed_elines.extend(eline_fwd_links) # bookkeeping
228 self._connect_elines(eline_fwd_links, instance_uuid, list(self.eline_subnets))
229 # 5b. deploy E-Tree/E-LAN links
230 GK.net.deployed_elans.extend(elan_fwd_links) # bookkeeping
231 self._connect_elans(elan_fwd_links, instance_uuid, list(self.elan_subnets))
232
233 # 6. run the emulator specific entrypoint scripts in the VNFIs of this
234 # service instance
235 self._trigger_emulator_start_scripts_in_vnfis(
236 self.instances[instance_uuid]["vnf_instances"])
237 # done
238 LOG.info("Service '{}' started. Instance id: {} SSIID: {}"
239 .format(self.instances[instance_uuid]["name"],
240 instance_uuid,
241 self.instances[instance_uuid]["ssiid"]))
242 return instance_uuid
243
244 def stop_service(self, instance_uuid):
245 """
246 This method stops a running service instance.
247 It iterates over all VNF instances, stopping them each
248 and removing them from their data center.
249 :param instance_uuid: the uuid of the service instance to be stopped
250 """
251 LOG.info("Stopping service %r" % self.uuid)
252 # get relevant information
253 # instance_uuid = str(self.uuid.uuid4())
254 vnf_instances = self.instances[instance_uuid]["vnf_instances"]
255 # trigger stop skripts in vnf instances and wait a few seconds for
256 # completion
257 self._trigger_emulator_stop_scripts_in_vnfis(vnf_instances)
258 time.sleep(VNF_STOP_WAIT_TIME)
259 # stop all vnfs
260 for v in vnf_instances:
261 self._stop_vnfi(v)
262 # last step: remove the instance from the list of all instances
263 del self.instances[instance_uuid]
264
265 def _get_elines_and_elans(self):
266 """
267 Get the E-Line, E-LAN, E-Tree links from the NSD.
268 """
269 # Attention: Only done if ""forwarding_graphs" section in NSD exists,
270 # even if "forwarding_graphs" are not used directly.
271 eline_fwd_links = list()
272 elan_fwd_links = list()
273 if "virtual_links" in self.nsd and "forwarding_graphs" in self.nsd:
274 vlinks = self.nsd["virtual_links"]
275 # constituent virtual links are not checked
276 eline_fwd_links = [l for l in vlinks if (
277 l["connectivity_type"] == "E-Line")]
278 elan_fwd_links = [l for l in vlinks if (
279 l["connectivity_type"] == "E-LAN" or
280 l["connectivity_type"] == "E-Tree")] # Treat E-Tree as E-LAN
281 return eline_fwd_links, elan_fwd_links
282
283 def _get_resource_limits(self, deployment_unit):
284 """
285 Extract resource limits from deployment units.
286 """
287 # defaults
288 cpu_list = None
289 cpu_period, cpu_quota = self._calculate_cpu_cfs_values(float(1.0))
290 mem_limit = None
291 # update from descriptor
292 if "resource_requirements" in deployment_unit:
293 res_req = deployment_unit.get("resource_requirements")
294 cpu_list = res_req.get("cpu").get("cpuset")
295 if cpu_list is None:
296 cpu_list = res_req.get("cpu").get("vcpus")
297 if cpu_list is not None:
298 # attention: docker expects list as string w/o spaces:
299 cpu_list = str(cpu_list).replace(" ", "").strip()
300 cpu_bw = res_req.get("cpu").get("cpu_bw")
301 if cpu_bw is None:
302 cpu_bw = 1.0
303 cpu_period, cpu_quota = self._calculate_cpu_cfs_values(float(cpu_bw))
304 mem_limit = res_req.get("memory").get("size")
305 mem_unit = str(res_req.get("memory").get("size_unit", "GB"))
306 if mem_limit is not None:
307 mem_limit = int(mem_limit)
308 # to bytes
309 if "G" in mem_unit:
310 mem_limit = mem_limit * 1024 * 1024 * 1024
311 elif "M" in mem_unit:
312 mem_limit = mem_limit * 1024 * 1024
313 elif "K" in mem_unit:
314 mem_limit = mem_limit * 1024
315 return cpu_list, cpu_period, cpu_quota, mem_limit
316
317 def _start_vnfd(self, vnfd, vnf_id, ssiid, **kwargs):
318 """
319 Start a single VNFD of this service
320 :param vnfd: vnfd descriptor dict
321 :param vnf_id: unique id of this vnf in the nsd
322 :return:
323 """
324 vnfis = list()
325 # the vnf_name refers to the container image to be deployed
326 vnf_name = vnfd.get("name")
327 # combine VDUs and CDUs
328 deployment_units = (vnfd.get("virtual_deployment_units", []) +
329 vnfd.get("cloudnative_deployment_units", []))
330 # iterate over all deployment units within each VNFDs
331 for u in deployment_units:
332 # 0. vnf_container_name = vnf_id.vdu_id
333 vnf_container_name = get_container_name(vnf_id, u.get("id"))
334 vnf_container_instance_name = get_container_name(vnf_id, u.get("id"), ssiid)
335 # 1. get the name of the docker image to star
336 if vnf_container_name not in self.remote_docker_image_urls:
337 raise Exception("No image name for %r found. Abort." % vnf_container_name)
338 docker_image_name = self.remote_docker_image_urls.get(vnf_container_name)
339 # 2. select datacenter to start the VNF in
340 target_dc = self._place(vnfd, vnf_id, u, ssiid)
341 # 3. perform some checks to ensure we can start the container
342 assert(docker_image_name is not None)
343 assert(target_dc is not None)
344 if not self._check_docker_image_exists(docker_image_name):
345 raise Exception("Docker image {} not found. Abort."
346 .format(docker_image_name))
347
348 # 4. get the resource limits
349 cpu_list, cpu_period, cpu_quota, mem_limit = self._get_resource_limits(u)
350
351 # get connection points defined for the DU
352 intfs = u.get("connection_points", [])
353 # do some re-naming of fields to be compatible to containernet
354 for i in intfs:
355 if i.get("address"):
356 i["ip"] = i.get("address")
357
358 # get ports and port_bindings from the port and publish fields of CNFD
359 # see: https://github.com/containernet/containernet/wiki/Exposing-and-mapping-network-ports
360 ports = list() # Containernet naming
361 port_bindings = dict()
362 for i in intfs:
363 if i.get("port"):
364 if not isinstance(i.get("port"), int):
365 LOG.info("Field 'port' is no int CP: {}".format(i))
366 else:
367 ports.append(i.get("port"))
368 if i.get("publish"):
369 if not isinstance(i.get("publish"), dict):
370 LOG.info("Field 'publish' is no dict CP: {}".format(i))
371 else:
372 port_bindings.update(i.get("publish"))
373 # update port mapping for cases where service is deployed > 1 times
374 port_bindings = update_port_mapping_multi_instance(ssiid, port_bindings)
375 if len(ports) > 0:
376 LOG.info("{} exposes ports: {}".format(vnf_container_instance_name, ports))
377 if len(port_bindings) > 0:
378 LOG.info("{} publishes ports: {}".format(vnf_container_instance_name, port_bindings))
379
380 # 5. collect additional information to start container
381 volumes = list()
382 cenv = dict()
383 # 5.1 inject descriptor based start/stop commands into env (overwrite)
384 VNFD_CMD_START = u.get("vm_cmd_start")
385 VNFD_CMD_STOP = u.get("vm_cmd_stop")
386 if VNFD_CMD_START and not VNFD_CMD_START == "None":
387 LOG.info("Found 'vm_cmd_start'='{}' in VNFD.".format(VNFD_CMD_START) +
388 " Overwriting SON_EMU_CMD.")
389 cenv["SON_EMU_CMD"] = VNFD_CMD_START
390 if VNFD_CMD_STOP and not VNFD_CMD_STOP == "None":
391 LOG.info("Found 'vm_cmd_start'='{}' in VNFD.".format(VNFD_CMD_STOP) +
392 " Overwriting SON_EMU_CMD_STOP.")
393 cenv["SON_EMU_CMD_STOP"] = VNFD_CMD_STOP
394
395 # 5.2 inject per instance configurations based on envs
396 conf_envs = self._load_instance_conf_envs(vnf_container_instance_name)
397 cenv.update(conf_envs)
398
399 # 6. Start the container
400 LOG.info("Starting %r as %r in DC %r" %
401 (vnf_name, vnf_container_instance_name, target_dc))
402 LOG.debug("Interfaces for %r: %r" % (vnf_id, intfs))
403 # start the container
404 vnfi = target_dc.startCompute(
405 vnf_container_instance_name,
406 network=intfs,
407 image=docker_image_name,
408 cpu_quota=cpu_quota,
409 cpu_period=cpu_period,
410 cpuset_cpus=cpu_list,
411 mem_limit=mem_limit,
412 volumes=volumes,
413 properties=cenv, # environment
414 ports=ports,
415 port_bindings=port_bindings,
416 # only publish if explicitly stated in descriptor
417 publish_all_ports=False,
418 type=kwargs.get('type', 'docker'))
419 # add vnfd reference to vnfi
420 vnfi.vnfd = vnfd
421 # add container name
422 vnfi.vnf_container_name = vnf_container_name
423 vnfi.vnf_container_instance_name = vnf_container_instance_name
424 vnfi.ssiid = ssiid
425 # store vnfi
426 vnfis.append(vnfi)
427 return vnfis
428
429 def _stop_vnfi(self, vnfi):
430 """
431 Stop a VNF instance.
432 :param vnfi: vnf instance to be stopped
433 """
434 # Find the correct datacenter
435 status = vnfi.getStatus()
436 dc = vnfi.datacenter
437 # stop the vnfi
438 LOG.info("Stopping the vnf instance contained in %r in DC %r" %
439 (status["name"], dc))
440 dc.stopCompute(status["name"])
441
442 def _get_vnf_instance(self, instance_uuid, vnf_id):
443 """
444 Returns VNFI object for a given "vnf_id" or "vnf_container_name" taken from an NSD.
445 :return: single object
446 """
447 for vnfi in self.instances[instance_uuid]["vnf_instances"]:
448 if str(vnfi.name) == str(vnf_id):
449 return vnfi
450 LOG.warning("No container with name: {0} found.".format(vnf_id))
451 return None
452
453 def _get_vnf_instance_units(self, instance_uuid, vnf_id):
454 """
455 Returns a list of VNFI objects (all deployment units) for a given
456 "vnf_id" taken from an NSD.
457 :return: list
458 """
459 if vnf_id is None:
460 return None
461 r = list()
462 for vnfi in self.instances[instance_uuid]["vnf_instances"]:
463 if vnf_id in vnfi.name:
464 r.append(vnfi)
465 if len(r) > 0:
466 LOG.debug("Found units: {} for vnf_id: {}"
467 .format([i.name for i in r], vnf_id))
468 return r
469 LOG.warning("No container(s) with name: {0} found.".format(vnf_id))
470 return None
471
472 @staticmethod
473 def _vnf_reconfigure_network(vnfi, if_name, net_str=None, new_name=None):
474 """
475 Reconfigure the network configuration of a specific interface
476 of a running container.
477 :param vnfi: container instance
478 :param if_name: interface name
479 :param net_str: network configuration string, e.g., 1.2.3.4/24
480 :return:
481 """
482 # assign new ip address
483 if net_str is not None:
484 intf = vnfi.intf(intf=if_name)
485 if intf is not None:
486 intf.setIP(net_str)
487 LOG.debug("Reconfigured network of %s:%s to %r" %
488 (vnfi.name, if_name, net_str))
489 else:
490 LOG.warning("Interface not found: %s:%s. Network reconfiguration skipped." % (
491 vnfi.name, if_name))
492
493 if new_name is not None:
494 vnfi.cmd('ip link set', if_name, 'down')
495 vnfi.cmd('ip link set', if_name, 'name', new_name)
496 vnfi.cmd('ip link set', new_name, 'up')
497 LOG.debug("Reconfigured interface name of %s:%s to %s" %
498 (vnfi.name, if_name, new_name))
499
500 def _trigger_emulator_start_scripts_in_vnfis(self, vnfi_list):
501 for vnfi in vnfi_list:
502 config = vnfi.dcinfo.get("Config", dict())
503 env = config.get("Env", list())
504 for env_var in env:
505 var, cmd = map(str.strip, map(str, env_var.split('=', 1)))
506 if var == "SON_EMU_CMD" or var == "VIM_EMU_CMD":
507 LOG.info("Executing script in '{}': {}={}"
508 .format(vnfi.name, var, cmd))
509 # execute command in new thread to ensure that GK is not
510 # blocked by VNF
511 t = threading.Thread(target=vnfi.cmdPrint, args=(cmd,))
512 t.daemon = True
513 t.start()
514 break # only execute one command
515
516 def _trigger_emulator_stop_scripts_in_vnfis(self, vnfi_list):
517 for vnfi in vnfi_list:
518 config = vnfi.dcinfo.get("Config", dict())
519 env = config.get("Env", list())
520 for env_var in env:
521 var, cmd = map(str.strip, map(str, env_var.split('=', 1)))
522 if var == "SON_EMU_CMD_STOP" or var == "VIM_EMU_CMD_STOP":
523 LOG.info("Executing script in '{}': {}={}"
524 .format(vnfi.name, var, cmd))
525 # execute command in new thread to ensure that GK is not
526 # blocked by VNF
527 t = threading.Thread(target=vnfi.cmdPrint, args=(cmd,))
528 t.daemon = True
529 t.start()
530 break # only execute one command
531
532 def _load_instance_conf_envs(self, cname):
533 """
534 Try to load an instance-specific env file. If not found,
535 just return an empty dict.
536 """
537 if PER_INSTANCE_ENV_CONFIGURATION_FOLDER is None:
538 return dict()
539 try:
540 path = os.path.expanduser(PER_INSTANCE_ENV_CONFIGURATION_FOLDER)
541 path = os.path.join(path, "{}.env.yml".format(cname))
542 res = load_yaml(path)
543 LOG.info("Loaded instance-specific env file for '{}': {}"
544 .format(cname, res))
545 return res
546 except BaseException as ex:
547 LOG.info("No instance-specific env file found for: {}"
548 .format(cname))
549 del ex
550 return dict()
551
552 def _unpack_service_package(self):
553 """
554 unzip *.son file and store contents in CATALOG_FOLDER/services/<service_uuid>/
555 """
556 LOG.info("Unzipping: %r" % self.package_file_path)
557 with zipfile.ZipFile(self.package_file_path, "r") as z:
558 z.extractall(self.package_content_path)
559
560 def _load_package_descriptor(self):
561 """
562 Load the main package descriptor YAML and keep it as dict.
563 :return:
564 """
565 self.manifest = load_yaml(
566 os.path.join(
567 self.package_content_path, "TOSCA-Metadata/NAPD.yaml"))
568
569 def _load_nsd(self):
570 """
571 Load the entry NSD YAML and keep it as dict.
572 :return:
573 """
574 if "package_content" in self.manifest:
575 nsd_path = None
576 for f in self.manifest.get("package_content"):
577 if f.get("content-type") == "application/vnd.5gtango.nsd":
578 nsd_path = os.path.join(
579 self.package_content_path,
580 make_relative_path(f.get("source")))
581 break # always use the first NSD for now
582 if nsd_path is None:
583 raise OnBoardingException("No NSD with type 'application/vnd.5gtango.nsd' found.")
584 self.nsd = load_yaml(nsd_path)
585 GK.net.deployed_nsds.append(self.nsd) # TODO this seems strange (remove?)
586 LOG.debug("Loaded NSD: %r" % self.nsd.get("name"))
587 else:
588 raise OnBoardingException(
589 "No 'package_content' section in package manifest:\n{}"
590 .format(self.manifest))
591
592 def _load_vnfd(self):
593 """
594 Load all VNFD YAML files referenced in MANIFEST.MF and keep them in dict.
595 :return:
596 """
597 # first make a list of all the vnfds in the package
598 vnfd_set = dict()
599 if "package_content" in self.manifest:
600 for pc in self.manifest.get("package_content"):
601 if pc.get(
602 "content-type") == "application/vnd.5gtango.vnfd":
603 vnfd_path = os.path.join(
604 self.package_content_path,
605 make_relative_path(pc.get("source")))
606 vnfd = load_yaml(vnfd_path)
607 vnfd_set[vnfd.get("name")] = vnfd
608 if len(vnfd_set) < 1:
609 raise OnBoardingException("No VNFDs found.")
610 # then link each vnf_id in the nsd to its vnfd
611 for v in self.nsd.get("network_functions"):
612 if v.get("vnf_name") in vnfd_set:
613 self.vnfds[v.get("vnf_id")] = vnfd_set[v.get("vnf_name")]
614 LOG.debug("Loaded VNFD: {0} id: {1}"
615 .format(v.get("vnf_name"), v.get("vnf_id")))
616
617 def _connect_elines(self, eline_fwd_links, instance_uuid, subnets):
618 """
619 Connect all E-LINE links in the NSD
620 Attention: This method DOES NOT support multi V/CDU VNFs!
621 :param eline_fwd_links: list of E-LINE links in the NSD
622 :param: instance_uuid of the service
623 :param: subnets list of subnets to be used
624 :return:
625 """
626 # cookie is used as identifier for the flowrules installed by the dummygatekeeper
627 # eg. different services get a unique cookie for their flowrules
628 cookie = 1
629 for link in eline_fwd_links:
630 LOG.info("Found E-Line: {}".format(link))
631 src_id, src_if_name = parse_interface(
632 link["connection_points_reference"][0])
633 dst_id, dst_if_name = parse_interface(
634 link["connection_points_reference"][1])
635 LOG.info("Searching C/VDU for E-Line: src={}, src_if={}, dst={}, dst_if={}"
636 .format(src_id, src_if_name, dst_id, dst_if_name))
637 # handle C/VDUs (ugly hack, only one V/CDU per VNF for now)
638 src_units = self._get_vnf_instance_units(instance_uuid, src_id)
639 dst_units = self._get_vnf_instance_units(instance_uuid, dst_id)
640 if src_units is None or dst_units is None:
641 LOG.info("No VNF-VNF link. Skipping: src={}, src_if={}, dst={}, dst_if={}"
642 .format(src_id, src_if_name, dst_id, dst_if_name))
643 return
644 # we only support VNFs with one V/CDU right now
645 if len(src_units) != 1 or len(dst_units) != 1:
646 raise BaseException("LLCM does not support E-LINES for multi V/CDU VNFs.")
647 # get the full name from that C/VDU and use it as src_id and dst_id
648 src_id = src_units[0].name
649 dst_id = dst_units[0].name
650 # from here we have all info we need
651 LOG.info("Creating E-Line for C/VDU: src={}, src_if={}, dst={}, dst_if={}"
652 .format(src_id, src_if_name, dst_id, dst_if_name))
653 # get involved vnfis
654 src_vnfi = src_units[0]
655 dst_vnfi = dst_units[0]
656 # proceed with chaining setup
657 setChaining = False
658 if src_vnfi is not None and dst_vnfi is not None:
659 setChaining = True
660 # re-configure the VNFs IP assignment and ensure that a new
661 # subnet is used for each E-Link
662 eline_net = subnets.pop(0)
663 ip1 = "{0}/{1}".format(str(eline_net[1]),
664 eline_net.prefixlen)
665 ip2 = "{0}/{1}".format(str(eline_net[2]),
666 eline_net.prefixlen)
667 # check if VNFs have fixed IPs (ip/address field in VNFDs)
668 if (self._get_vnfd_cp_from_vnfi(
669 src_vnfi, src_if_name).get("ip") is None and
670 self._get_vnfd_cp_from_vnfi(
671 src_vnfi, src_if_name).get("address") is None):
672 self._vnf_reconfigure_network(src_vnfi, src_if_name, ip1)
673 # check if VNFs have fixed IPs (ip field in VNFDs)
674 if (self._get_vnfd_cp_from_vnfi(
675 dst_vnfi, dst_if_name).get("ip") is None and
676 self._get_vnfd_cp_from_vnfi(
677 dst_vnfi, dst_if_name).get("address") is None):
678 self._vnf_reconfigure_network(dst_vnfi, dst_if_name, ip2)
679 # set the chaining
680 if setChaining:
681 GK.net.setChain(
682 src_id, dst_id,
683 vnf_src_interface=src_if_name, vnf_dst_interface=dst_if_name,
684 bidirectional=BIDIRECTIONAL_CHAIN, cmd="add-flow", cookie=cookie, priority=10)
685
686 def _get_vnfd_cp_from_vnfi(self, vnfi, ifname):
687 """
688 Gets the connection point data structure from the VNFD
689 of the given VNFI using ifname.
690 """
691 if vnfi.vnfd is None:
692 return {}
693 cps = vnfi.vnfd.get("connection_points")
694 for cp in cps:
695 if cp.get("id") == ifname:
696 return cp
697
698 def _connect_elans(self, elan_fwd_links, instance_uuid, subnets):
699 """
700 Connect all E-LAN/E-Tree links in the NSD
701 This method supports multi-V/CDU VNFs if the connection
702 point names of the DUs are the same as the ones in the NSD.
703 :param elan_fwd_links: list of E-LAN links in the NSD
704 :param: instance_uuid of the service
705 :param: subnets list of subnets to be used
706 :return:
707 """
708 for link in elan_fwd_links:
709 # a new E-LAN/E-Tree
710 elan_vnf_list = []
711 lan_net = subnets.pop(0)
712 lan_hosts = list(lan_net.hosts())
713
714 # generate lan ip address for all interfaces (of all involved (V/CDUs))
715 for intf in link["connection_points_reference"]:
716 vnf_id, intf_name = parse_interface(intf)
717 if vnf_id is None:
718 continue # skip references to NS connection points
719 units = self._get_vnf_instance_units(instance_uuid, vnf_id)
720 if units is None:
721 continue # skip if no deployment unit is present
722 # iterate over all involved deployment units
723 for uvnfi in units:
724 # Attention: we apply a simplification for multi DU VNFs here:
725 # the connection points of all involved DUs have to have the same
726 # name as the connection points of the surrounding VNF to be mapped.
727 # This is because we do not consider links specified in the VNFds
728 container_name = uvnfi.name
729 ip_address = "{0}/{1}".format(str(lan_hosts.pop(0)),
730 lan_net.prefixlen)
731 LOG.debug(
732 "Setting up E-LAN/E-Tree interface. (%s:%s) -> %s" % (
733 container_name, intf_name, ip_address))
734 # re-configure the VNFs IP assignment and ensure that a new subnet is used for each E-LAN
735 # E-LAN relies on the learning switch capability of Ryu which has to be turned on in the topology
736 # (DCNetwork(controller=RemoteController, enable_learning=True)), so no explicit chaining is
737 # necessary.
738 vnfi = self._get_vnf_instance(instance_uuid, container_name)
739 if vnfi is not None:
740 self._vnf_reconfigure_network(vnfi, intf_name, ip_address)
741 # add this vnf and interface to the E-LAN for tagging
742 elan_vnf_list.append(
743 {'name': container_name, 'interface': intf_name})
744 # install the VLAN tags for this E-LAN
745 GK.net.setLAN(elan_vnf_list)
746
747 def _load_docker_files(self):
748 """
749 Get all paths to Dockerfiles from VNFDs and store them in dict.
750 :return:
751 """
752 for vnf_id, v in self.vnfds.iteritems():
753 for vu in v.get("virtual_deployment_units", []):
754 vnf_container_name = get_container_name(vnf_id, vu.get("id"))
755 if vu.get("vm_image_format") == "docker":
756 vm_image = vu.get("vm_image")
757 docker_path = os.path.join(
758 self.package_content_path,
759 make_relative_path(vm_image))
760 self.local_docker_files[vnf_container_name] = docker_path
761 LOG.debug("Found Dockerfile (%r): %r" % (vnf_container_name, docker_path))
762 for cu in v.get("cloudnative_deployment_units", []):
763 vnf_container_name = get_container_name(vnf_id, cu.get("id"))
764 image = cu.get("image")
765 docker_path = os.path.join(
766 self.package_content_path,
767 make_relative_path(image))
768 self.local_docker_files[vnf_container_name] = docker_path
769 LOG.debug("Found Dockerfile (%r): %r" % (vnf_container_name, docker_path))
770
771 def _load_docker_urls(self):
772 """
773 Get all URLs to pre-build docker images in some repo.
774 :return:
775 """
776 for vnf_id, v in self.vnfds.iteritems():
777 for vu in v.get("virtual_deployment_units", []):
778 vnf_container_name = get_container_name(vnf_id, vu.get("id"))
779 if vu.get("vm_image_format") == "docker":
780 url = vu.get("vm_image")
781 if url is not None:
782 url = url.replace("http://", "")
783 self.remote_docker_image_urls[vnf_container_name] = url
784 LOG.debug("Found Docker image URL (%r): %r" %
785 (vnf_container_name,
786 self.remote_docker_image_urls[vnf_container_name]))
787 for cu in v.get("cloudnative_deployment_units", []):
788 vnf_container_name = get_container_name(vnf_id, cu.get("id"))
789 url = cu.get("image")
790 if url is not None:
791 url = url.replace("http://", "")
792 self.remote_docker_image_urls[vnf_container_name] = url
793 LOG.debug("Found Docker image URL (%r): %r" %
794 (vnf_container_name,
795 self.remote_docker_image_urls[vnf_container_name]))
796
797 def _build_images_from_dockerfiles(self):
798 """
799 Build Docker images for each local Dockerfile found in the package: self.local_docker_files
800 """
801 if GK_STANDALONE_MODE:
802 return # do not build anything in standalone mode
803 dc = DockerClient()
804 LOG.info("Building %d Docker images (this may take several minutes) ..." % len(
805 self.local_docker_files))
806 for k, v in self.local_docker_files.iteritems():
807 for line in dc.build(path=v.replace(
808 "Dockerfile", ""), tag=k, rm=False, nocache=False):
809 LOG.debug("DOCKER BUILD: %s" % line)
810 LOG.info("Docker image created: %s" % k)
811
812 def _pull_predefined_dockerimages(self):
813 """
814 If the package contains URLs to pre-build Docker images, we download them with this method.
815 """
816 dc = DockerClient()
817 for url in self.remote_docker_image_urls.itervalues():
818 # only pull if not present (speedup for development)
819 if not FORCE_PULL:
820 if len(dc.images.list(name=url)) > 0:
821 LOG.debug("Image %r present. Skipping pull." % url)
822 continue
823 LOG.info("Pulling image: %r" % url)
824 # this seems to fail with latest docker api version 2.0.2
825 # dc.images.pull(url,
826 # insecure_registry=True)
827 # using docker cli instead
828 cmd = ["docker",
829 "pull",
830 url,
831 ]
832 Popen(cmd).wait()
833
834 def _check_docker_image_exists(self, image_name):
835 """
836 Query the docker service and check if the given image exists
837 :param image_name: name of the docker image
838 :return:
839 """
840 return len(DockerClient().images.list(name=image_name)) > 0
841
842 def _place(self, vnfd, vnfid, vdu, ssiid):
843 """
844 Do placement. Return the name of the DC to place
845 the given VDU.
846 """
847 assert(len(self.vnfds) > 0)
848 assert(len(GK.dcs) > 0)
849 if PLACEMENT_ALGORITHM_OBJ is None:
850 LOG.error("No placement algorithm given. Using FirstDcPlacement!")
851 p = FirstDcPlacement()
852 else:
853 p = PLACEMENT_ALGORITHM_OBJ
854 cname = get_container_name(vnfid, vdu.get("id"), ssiid)
855 rdc = p.place(GK.dcs, vnfd, vnfid, vdu, ssiid, cname)
856 LOG.info("Placement: '{}' --> '{}'".format(cname, rdc))
857 return rdc
858
859 def _calculate_cpu_cfs_values(self, cpu_time_percentage):
860 """
861 Calculate cpu period and quota for CFS
862 :param cpu_time_percentage: percentage of overall CPU to be used
863 :return: cpu_period, cpu_quota
864 """
865 if cpu_time_percentage is None:
866 return -1, -1
867 if cpu_time_percentage < 0:
868 return -1, -1
869 # (see: https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt)
870 # Attention minimum cpu_quota is 1ms (micro)
871 cpu_period = 1000000 # lets consider a fixed period of 1000000 microseconds for now
872 LOG.debug("cpu_period is %r, cpu_percentage is %r" %
873 (cpu_period, cpu_time_percentage))
874 # calculate the fraction of cpu time for this container
875 cpu_quota = cpu_period * cpu_time_percentage
876 # ATTENTION >= 1000 to avoid a invalid argument system error ... no
877 # idea why
878 if cpu_quota < 1000:
879 LOG.debug("cpu_quota before correcting: %r" % cpu_quota)
880 cpu_quota = 1000
881 LOG.warning("Increased CPU quota to avoid system error.")
882 LOG.debug("Calculated: cpu_period=%f / cpu_quota=%f" %
883 (cpu_period, cpu_quota))
884 return int(cpu_period), int(cpu_quota)
885
886
887 """
888 Some (simple) placement algorithms
889 """
890
891
892 class FirstDcPlacement(object):
893 """
894 Placement: Always use one and the same data center from the GK.dcs dict.
895 """
896
897 def place(self, dcs, vnfd, vnfid, vdu, ssiid, cname):
898 return list(dcs.itervalues())[0]
899
900
901 class RoundRobinDcPlacement(object):
902 """
903 Placement: Distribute VNFs across all available DCs in a round robin fashion.
904 """
905
906 def __init__(self):
907 self.count = 0
908
909 def place(self, dcs, vnfd, vnfid, vdu, ssiid, cname):
910 dcs_list = list(dcs.itervalues())
911 rdc = dcs_list[self.count % len(dcs_list)]
912 self.count += 1 # inc. count to use next DC
913 return rdc
914
915
916 class StaticConfigPlacement(object):
917 """
918 Placement: Fixed assignment based on config file.
919 """
920
921 def __init__(self, path=None):
922 if path is None:
923 path = "static_placement.yml"
924 path = os.path.expanduser(path)
925 self.static_placement = dict()
926 try:
927 self.static_placement = load_yaml(path)
928 except BaseException as ex:
929 LOG.error(ex)
930 LOG.error("Couldn't load placement from {}"
931 .format(path))
932 LOG.info("Loaded static placement: {}"
933 .format(self.static_placement))
934
935 def place(self, dcs, vnfd, vnfid, vdu, ssiid, cname):
936 # check for container name entry
937 if cname not in self.static_placement:
938 LOG.error("Coudn't find {} in placement".format(cname))
939 LOG.error("Using first DC as fallback!")
940 return list(dcs.itervalues())[0]
941 # lookup
942 candidate_dc = self.static_placement.get(cname)
943 # check if DC exsits
944 if candidate_dc not in dcs:
945 LOG.error("Coudn't find DC {}".format(candidate_dc))
946 LOG.error("Using first DC as fallback!")
947 return list(dcs.itervalues())[0]
948 # return correct DC
949 return dcs.get(candidate_dc)
950
951
952 """
953 Resource definitions and API endpoints
954 """
955
956
957 class Packages(fr.Resource):
958
959 def post(self):
960 """
961 Upload a *.son service package to the dummy gatekeeper.
962
963 We expect request with a *.son file and store it in UPLOAD_FOLDER
964 :return: UUID
965 """
966 try:
967 # get file contents
968 LOG.info("POST /packages called")
969 # lets search for the package in the request
970 is_file_object = False # make API more robust: file can be in data or in files field
971 if "package" in request.files:
972 son_file = request.files["package"]
973 is_file_object = True
974 elif len(request.data) > 0:
975 son_file = request.data
976 else:
977 return {"service_uuid": None, "size": 0, "sha1": None,
978 "error": "upload failed. file not found."}, 500
979 # generate a uuid to reference this package
980 service_uuid = str(uuid.uuid4())
981 file_hash = hashlib.sha1(str(son_file)).hexdigest()
982 # ensure that upload folder exists
983 ensure_dir(UPLOAD_FOLDER)
984 upload_path = os.path.join(UPLOAD_FOLDER, "%s.tgo" % service_uuid)
985 # store *.son file to disk
986 if is_file_object:
987 son_file.save(upload_path)
988 else:
989 with open(upload_path, 'wb') as f:
990 f.write(son_file)
991 size = os.path.getsize(upload_path)
992
993 # first stop and delete any other running services
994 if AUTO_DELETE:
995 service_list = copy.copy(GK.services)
996 for service_uuid in service_list:
997 instances_list = copy.copy(
998 GK.services[service_uuid].instances)
999 for instance_uuid in instances_list:
1000 # valid service and instance UUID, stop service
1001 GK.services.get(service_uuid).stop_service(
1002 instance_uuid)
1003 LOG.info("service instance with uuid %r stopped." %
1004 instance_uuid)
1005
1006 # create a service object and register it
1007 s = Service(service_uuid, file_hash, upload_path)
1008 GK.register_service_package(service_uuid, s)
1009
1010 # automatically deploy the service
1011 if AUTO_DEPLOY:
1012 # ok, we have a service uuid, lets start the service
1013 reset_subnets()
1014 GK.services.get(service_uuid).start_service()
1015
1016 # generate the JSON result
1017 return {"service_uuid": service_uuid, "size": size,
1018 "sha1": file_hash, "error": None}, 201
1019 except BaseException:
1020 LOG.exception("Service package upload failed:")
1021 return {"service_uuid": None, "size": 0,
1022 "sha1": None, "error": "upload failed"}, 500
1023
1024 def get(self):
1025 """
1026 Return a list of package descriptor headers.
1027 Fakes the behavior of 5GTANGO's GK API to be
1028 compatible with tng-cli.
1029 :return: list
1030 """
1031 LOG.info("GET /packages")
1032 result = list()
1033 for suuid, sobj in GK.services.iteritems():
1034 pkg = dict()
1035 pkg["pd"] = dict()
1036 pkg["uuid"] = suuid
1037 pkg["pd"]["name"] = sobj.manifest.get("name")
1038 pkg["pd"]["version"] = sobj.manifest.get("version")
1039 pkg["created_at"] = sobj.created_at
1040 result.append(pkg)
1041 return result, 200
1042
1043
1044 class Services(fr.Resource):
1045
1046 def get(self):
1047 """
1048 Return a list of services.
1049 Fakes the behavior of 5GTANGO's GK API to be
1050 compatible with tng-cli.
1051 :return: list
1052 """
1053 LOG.info("GET /services")
1054 result = list()
1055 for suuid, sobj in GK.services.iteritems():
1056 service = dict()
1057 service["nsd"] = dict()
1058 service["uuid"] = suuid
1059 service["nsd"]["name"] = sobj.nsd.get("name")
1060 service["nsd"]["version"] = sobj.nsd.get("version")
1061 service["created_at"] = sobj.created_at
1062 result.append(service)
1063 return result, 200
1064
1065
1066 class Instantiations(fr.Resource):
1067
1068 def post(self):
1069 """
1070 Instantiate a service specified by its UUID.
1071 Will return a new UUID to identify the running service instance.
1072 :return: UUID
1073 """
1074 LOG.info("POST /instantiations (or /requests) called")
1075 # try to extract the service uuid from the request
1076 json_data = request.get_json(force=True)
1077 service_uuid = json_data.get("service_uuid")
1078 service_name = json_data.get("service_name")
1079 if service_name is None:
1080 # lets be fuzzy
1081 service_name = service_uuid
1082 # first try to find by service_name
1083 if service_name is not None:
1084 for s_uuid, s in GK.services.iteritems():
1085 if s.manifest.get("name") == service_name:
1086 LOG.info("Searched for: {}. Found service w. UUID: {}"
1087 .format(service_name, s_uuid))
1088 service_uuid = s_uuid
1089 # lets be a bit fuzzy here to make testing easier
1090 if (service_uuid is None or service_uuid ==
1091 "latest") and len(GK.services) > 0:
1092 # if we don't get a service uuid, we simple start the first service
1093 # in the list
1094 service_uuid = list(GK.services.iterkeys())[0]
1095 if service_uuid in GK.services:
1096 # ok, we have a service uuid, lets start the service
1097 service_instance_uuid = GK.services.get(
1098 service_uuid).start_service()
1099 # multiple ID fields to be compatible with tng-bench and tng-cli
1100 return ({"service_instance_uuid": service_instance_uuid,
1101 "id": service_instance_uuid}, 201)
1102 LOG.error("Service not found: {}/{}".format(service_uuid, service_name))
1103 return "Service not found", 404
1104
1105 def get(self):
1106 """
1107 Returns a list of UUIDs containing all running services.
1108 :return: dict / list
1109 """
1110 LOG.info("GET /instantiations or /api/v3/records/services")
1111 # return {"service_instantiations_list": [
1112 # list(s.instances.iterkeys()) for s in GK.services.itervalues()]}
1113 result = list()
1114 for suuid, sobj in GK.services.iteritems():
1115 for iuuid, iobj in sobj.instances.iteritems():
1116 inst = dict()
1117 inst["uuid"] = iobj.get("uuid")
1118 inst["instance_name"] = "{}-inst.{}".format(
1119 iobj.get("name"), iobj.get("ssiid"))
1120 inst["status"] = "running"
1121 inst["created_at"] = iobj.get("created_at")
1122 result.append(inst)
1123 return result, 200
1124
1125 def delete(self):
1126 """
1127 Stops a running service specified by its service and instance UUID.
1128 """
1129 # try to extract the service and instance UUID from the request
1130 json_data = request.get_json(force=True)
1131 service_uuid_input = json_data.get("service_uuid")
1132 instance_uuid_input = json_data.get("service_instance_uuid")
1133 if len(GK.services) < 1:
1134 return "No service on-boarded.", 404
1135 # try to be fuzzy
1136 if service_uuid_input is None:
1137 # if we don't get a service uuid we stop all services
1138 service_uuid_list = list(GK.services.iterkeys())
1139 LOG.info("No service_uuid given, stopping all.")
1140 else:
1141 service_uuid_list = [service_uuid_input]
1142 # for each service
1143 for service_uuid in service_uuid_list:
1144 if instance_uuid_input is None:
1145 instance_uuid_list = list(
1146 GK.services[service_uuid].instances.iterkeys())
1147 else:
1148 instance_uuid_list = [instance_uuid_input]
1149 # for all service instances
1150 for instance_uuid in instance_uuid_list:
1151 if (service_uuid in GK.services and
1152 instance_uuid in GK.services[service_uuid].instances):
1153 # valid service and instance UUID, stop service
1154 GK.services.get(service_uuid).stop_service(instance_uuid)
1155 LOG.info("Service instance with uuid %r stopped." % instance_uuid)
1156 return "Service(s) stopped.", 200
1157
1158
1159 class Exit(fr.Resource):
1160
1161 def put(self):
1162 """
1163 Stop the running Containernet instance regardless of data transmitted
1164 """
1165 list(GK.dcs.values())[0].net.stop()
1166
1167
1168 def generate_subnets(prefix, base, subnet_size=50, mask=24):
1169 # Generate a list of ipaddress in subnets
1170 r = list()
1171 for net in range(base, base + subnet_size):
1172 subnet = "{0}.{1}.0/{2}".format(prefix, net, mask)
1173 r.append(ipaddress.ip_network(unicode(subnet)))
1174 return r
1175
1176
1177 def reset_subnets():
1178 global ELINE_SUBNETS
1179 global ELAN_SUBNETS
1180 # private subnet definitions for the generated interfaces
1181 # 30.0.xxx.0/24
1182 ELAN_SUBNETS = generate_subnets('30.0', 0, subnet_size=50, mask=24)
1183 # 20.0.xxx.0/24
1184 ELINE_SUBNETS = generate_subnets('20.0', 0, subnet_size=50, mask=24)
1185
1186
1187 def initialize_GK():
1188 global GK
1189 GK = Gatekeeper()
1190
1191
1192 # create a single, global GK object
1193 GK = None
1194 initialize_GK()
1195 # setup Flask
1196 http_server = None
1197 app = Flask(__name__)
1198 app.config['MAX_CONTENT_LENGTH'] = 512 * 1024 * 1024 # 512 MB max upload
1199 api = fr.Api(app)
1200 # define endpoints
1201 api.add_resource(Packages, '/packages', '/api/v2/packages', '/api/v3/packages')
1202 api.add_resource(Services, '/services', '/api/v2/services', '/api/v3/services')
1203 api.add_resource(Instantiations, '/instantiations',
1204 '/api/v2/instantiations', '/api/v2/requests', '/api/v3/requests',
1205 '/api/v3/records/services')
1206 api.add_resource(Exit, '/emulator/exit')
1207
1208
1209 def start_rest_api(host, port, datacenters=dict()):
1210 global http_server
1211 GK.dcs = datacenters
1212 GK.net = get_dc_network()
1213 # start the Flask server (not the best performance but ok for our use case)
1214 # app.run(host=host,
1215 # port=port,
1216 # debug=True,
1217 # use_reloader=False # this is needed to run Flask in a non-main thread
1218 # )
1219 http_server = WSGIServer((host, port), app, log=open("/dev/null", "w"))
1220 http_server.serve_forever()
1221
1222
1223 def stop_rest_api():
1224 if http_server:
1225 http_server.close()
1226
1227
1228 def ensure_dir(name):
1229 if not os.path.exists(name):
1230 os.makedirs(name)
1231
1232
1233 def load_yaml(path):
1234 with open(path, "r") as f:
1235 try:
1236 r = yaml.load(f)
1237 except yaml.YAMLError as exc:
1238 LOG.exception("YAML parse error: %r" % str(exc))
1239 r = dict()
1240 return r
1241
1242
1243 def make_relative_path(path):
1244 if path.startswith("file://"):
1245 path = path.replace("file://", "", 1)
1246 if path.startswith("/"):
1247 path = path.replace("/", "", 1)
1248 return path
1249
1250
1251 def get_dc_network():
1252 """
1253 retrieve the DCnetwork where this dummygatekeeper (GK) connects to.
1254 Assume at least 1 datacenter is connected to this GK, and that all datacenters belong to the same DCNetwork
1255 :return:
1256 """
1257 assert (len(GK.dcs) > 0)
1258 return GK.dcs.values()[0].net
1259
1260
1261 def parse_interface(interface_name):
1262 """
1263 convert the interface name in the nsd to the according vnf_id, vnf_interface names
1264 :param interface_name:
1265 :return:
1266 """
1267 if ':' in interface_name:
1268 vnf_id, vnf_interface = interface_name.split(':')
1269 else:
1270 vnf_id = None
1271 vnf_interface = interface_name
1272 return vnf_id, vnf_interface
1273
1274
1275 def get_container_name(vnf_id, vdu_id, ssiid=None):
1276 if ssiid is not None:
1277 return "{}.{}.{}".format(vnf_id, vdu_id, ssiid)
1278 return "{}.{}".format(vnf_id, vdu_id)
1279
1280
1281 def get_triple_id(descr):
1282 return "{}.{}.{}".format(
1283 descr.get("vendor"), descr.get("name"), descr.get("version"))
1284
1285
1286 def update_port_mapping_multi_instance(ssiid, port_bindings):
1287 """
1288 Port_bindings are used to expose ports of the deployed containers.
1289 They would collide if we deploy multiple service instances.
1290 This function adds a offset to them which is based on the
1291 short service instance id (SSIID).
1292 MULTI_INSTANCE_PORT_OFFSET
1293 """
1294 def _offset(p):
1295 return p + MULTI_INSTANCE_PORT_OFFSET * ssiid
1296
1297 port_bindings = {k: _offset(v) for k, v in port_bindings.iteritems()}
1298 return port_bindings
1299
1300
1301 if __name__ == '__main__':
1302 """
1303 Lets allow to run the API in standalone mode.
1304 """
1305 GK_STANDALONE_MODE = True
1306 logging.getLogger("werkzeug").setLevel(logging.INFO)
1307 start_rest_api("0.0.0.0", 8000)