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