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