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