blob: bea0a010be8696fb70549e1d7332f86f9d6413c9 [file] [log] [blame]
peusterme26487b2016-03-08 14:00:21 +01001"""
peusterm79ef6ae2016-07-08 13:53:57 +02002Copyright (c) 2015 SONATA-NFV and Paderborn University
3ALL RIGHTS RESERVED.
4
5Licensed under the Apache License, Version 2.0 (the "License");
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8
9 http://www.apache.org/licenses/LICENSE-2.0
10
11Unless required by applicable law or agreed to in writing, software
12distributed under the License is distributed on an "AS IS" BASIS,
13WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14See the License for the specific language governing permissions and
15limitations under the License.
16
17Neither the name of the SONATA-NFV [, ANY ADDITIONAL AFFILIATION]
18nor the names of its contributors may be used to endorse or promote
19products derived from this software without specific prior written
20permission.
21
22This work has been performed in the framework of the SONATA project,
23funded by the European Commission under Grant number 671517 through
24the Horizon 2020 and 5G-PPP programmes. The authors would like to
25acknowledge the contributions of their colleagues of the SONATA
26partner consortium (www.sonata-nfv.eu).
27"""
28"""
peusterme26487b2016-03-08 14:00:21 +010029This module implements a simple REST API that behaves like SONATA's gatekeeper.
30
31It is only used to support the development of SONATA's SDK tools and to demonstrate
32the year 1 version of the emulator until the integration with WP4's orchestrator is done.
33"""
34
35import logging
36import os
37import uuid
38import hashlib
peusterm786cd542016-03-14 14:12:17 +010039import zipfile
peusterm7ec665d2016-03-14 15:20:44 +010040import yaml
peustermbdfab7e2016-03-14 16:03:30 +010041from docker import Client as DockerClient
peusterme26487b2016-03-08 14:00:21 +010042from flask import Flask, request
43import flask_restful as fr
wtaverni5b23b662016-06-20 12:26:21 +020044from collections import defaultdict
peusterme26487b2016-03-08 14:00:21 +010045
peusterm398cd3b2016-03-21 15:04:54 +010046logging.basicConfig()
peusterm786cd542016-03-14 14:12:17 +010047LOG = logging.getLogger("sonata-dummy-gatekeeper")
48LOG.setLevel(logging.DEBUG)
peusterme26487b2016-03-08 14:00:21 +010049logging.getLogger("werkzeug").setLevel(logging.WARNING)
50
peusterm92237dc2016-03-21 15:45:58 +010051GK_STORAGE = "/tmp/son-dummy-gk/"
52UPLOAD_FOLDER = os.path.join(GK_STORAGE, "uploads/")
53CATALOG_FOLDER = os.path.join(GK_STORAGE, "catalog/")
peusterme26487b2016-03-08 14:00:21 +010054
peusterm82d406e2016-05-02 20:52:06 +020055# Enable Dockerfile build functionality
56BUILD_DOCKERFILE = False
57
peusterm398cd3b2016-03-21 15:04:54 +010058# flag to indicate that we run without the emulator (only the bare API for integration testing)
59GK_STANDALONE_MODE = False
60
peusterm56356cb2016-05-03 10:43:43 +020061# should a new version of an image be pulled even if its available
wtaverni5b23b662016-06-20 12:26:21 +020062FORCE_PULL = False
peusterme26487b2016-03-08 14:00:21 +010063
64class Gatekeeper(object):
65
66 def __init__(self):
peusterm786cd542016-03-14 14:12:17 +010067 self.services = dict()
peusterm082378b2016-03-16 20:14:22 +010068 self.dcs = dict()
peusterm3444ae42016-03-16 20:46:41 +010069 self.vnf_counter = 0 # used to generate short names for VNFs (Mininet limitation)
peusterm786cd542016-03-14 14:12:17 +010070 LOG.info("Create SONATA dummy gatekeeper.")
peusterme26487b2016-03-08 14:00:21 +010071
peusterm786cd542016-03-14 14:12:17 +010072 def register_service_package(self, service_uuid, service):
73 """
74 register new service package
75 :param service_uuid
76 :param service object
77 """
78 self.services[service_uuid] = service
79 # lets perform all steps needed to onboard the service
80 service.onboard()
81
peusterm3444ae42016-03-16 20:46:41 +010082 def get_next_vnf_name(self):
83 self.vnf_counter += 1
peusterm398cd3b2016-03-21 15:04:54 +010084 return "vnf%d" % self.vnf_counter
peusterm3444ae42016-03-16 20:46:41 +010085
peusterm786cd542016-03-14 14:12:17 +010086
87class Service(object):
88 """
89 This class represents a NS uploaded as a *.son package to the
90 dummy gatekeeper.
91 Can have multiple running instances of this service.
92 """
93
94 def __init__(self,
95 service_uuid,
96 package_file_hash,
97 package_file_path):
98 self.uuid = service_uuid
99 self.package_file_hash = package_file_hash
100 self.package_file_path = package_file_path
101 self.package_content_path = os.path.join(CATALOG_FOLDER, "services/%s" % self.uuid)
peusterm7ec665d2016-03-14 15:20:44 +0100102 self.manifest = None
103 self.nsd = None
104 self.vnfds = dict()
peustermbdfab7e2016-03-14 16:03:30 +0100105 self.local_docker_files = dict()
peusterm82d406e2016-05-02 20:52:06 +0200106 self.remote_docker_image_urls = dict()
peusterm786cd542016-03-14 14:12:17 +0100107 self.instances = dict()
peusterm6b5224d2016-07-20 13:20:31 +0200108 self.vnf_name2docker_name = dict()
109 # lets generate a set of subnet configurations used for e-line chaining setup
110 self.eline_subnets_src = generate_subnet_strings(50, start=200, subnet_size=24, ip=1)
111 self.eline_subnets_dst = generate_subnet_strings(50, start=200, subnet_size=24, ip=2)
peusterme26487b2016-03-08 14:00:21 +0100112
peusterm786cd542016-03-14 14:12:17 +0100113 def onboard(self):
114 """
115 Do all steps to prepare this service to be instantiated
116 :return:
117 """
118 # 1. extract the contents of the package and store them in our catalog
119 self._unpack_service_package()
120 # 2. read in all descriptor files
peusterm7ec665d2016-03-14 15:20:44 +0100121 self._load_package_descriptor()
122 self._load_nsd()
123 self._load_vnfd()
peusterm786cd542016-03-14 14:12:17 +0100124 # 3. prepare container images (e.g. download or build Dockerfile)
peusterm82d406e2016-05-02 20:52:06 +0200125 if BUILD_DOCKERFILE:
126 self._load_docker_files()
127 self._build_images_from_dockerfiles()
128 else:
129 self._load_docker_urls()
130 self._pull_predefined_dockerimages()
peusterm7ec665d2016-03-14 15:20:44 +0100131 LOG.info("On-boarded service: %r" % self.manifest.get("package_name"))
132
peusterm082378b2016-03-16 20:14:22 +0100133 def start_service(self):
peusterm3444ae42016-03-16 20:46:41 +0100134 """
135 This methods creates and starts a new service instance.
136 It computes placements, iterates over all VNFDs, and starts
137 each VNFD as a Docker container in the data center selected
138 by the placement algorithm.
139 :return:
140 """
141 LOG.info("Starting service %r" % self.uuid)
stevenvanrossemd87fe472016-05-11 11:34:34 +0200142
peusterm3444ae42016-03-16 20:46:41 +0100143 # 1. each service instance gets a new uuid to identify it
peusterm082378b2016-03-16 20:14:22 +0100144 instance_uuid = str(uuid.uuid4())
peusterm3444ae42016-03-16 20:46:41 +0100145 # build a instances dict (a bit like a NSR :))
146 self.instances[instance_uuid] = dict()
147 self.instances[instance_uuid]["vnf_instances"] = list()
stevenvanrossemd87fe472016-05-11 11:34:34 +0200148
peusterm3444ae42016-03-16 20:46:41 +0100149 # 2. compute placement of this service instance (adds DC names to VNFDs)
peusterm398cd3b2016-03-21 15:04:54 +0100150 if not GK_STANDALONE_MODE:
151 self._calculate_placement(FirstDcPlacement)
peusterm3444ae42016-03-16 20:46:41 +0100152 # iterate over all vnfds that we have to start
peusterm082378b2016-03-16 20:14:22 +0100153 for vnfd in self.vnfds.itervalues():
peusterm398cd3b2016-03-21 15:04:54 +0100154 vnfi = None
155 if not GK_STANDALONE_MODE:
156 vnfi = self._start_vnfd(vnfd)
157 self.instances[instance_uuid]["vnf_instances"].append(vnfi)
stevenvanrossemd87fe472016-05-11 11:34:34 +0200158
159 # 3. Configure the chaining of the network functions (currently only E-Line links supported)
peusterm6b5224d2016-07-20 13:20:31 +0200160 vnf_id2vnf_name = defaultdict(lambda: "NotExistingNode",
161 reduce(lambda x, y: dict(x, **y),
162 map(lambda d: {d["vnf_id"]: d["vnf_name"]},
wtaverni5b23b662016-06-20 12:26:21 +0200163 self.nsd["network_functions"])))
164
stevenvanrossemd87fe472016-05-11 11:34:34 +0200165 vlinks = self.nsd["virtual_links"]
166 fwd_links = self.nsd["forwarding_graphs"][0]["constituent_virtual_links"]
167 eline_fwd_links = [l for l in vlinks if (l["id"] in fwd_links) and (l["connectivity_type"] == "E-Line")]
168
peusterm6b5224d2016-07-20 13:20:31 +0200169 cookie = 1 # not clear why this is needed - to check with Steven
stevenvanrossemd87fe472016-05-11 11:34:34 +0200170 for link in eline_fwd_links:
peusterm6b5224d2016-07-20 13:20:31 +0200171 src_id, src_if_name = link["connection_points_reference"][0].split(":")
172 dst_id, dst_if_name = link["connection_points_reference"][1].split(":")
stevenvanrossemd87fe472016-05-11 11:34:34 +0200173
peusterm6b5224d2016-07-20 13:20:31 +0200174 src_name = vnf_id2vnf_name[src_id]
175 dst_name = vnf_id2vnf_name[dst_id]
peusterm9fb74ec2016-06-16 11:30:55 +0200176
peusterm6b5224d2016-07-20 13:20:31 +0200177 LOG.debug(
178 "Setting up E-Line link. %s(%s:%s) -> %s(%s:%s)" % (
179 src_name, src_id, src_if_name, dst_name, dst_id, dst_if_name))
180
181 if (src_name in self.vnfds) and (dst_name in self.vnfds):
182 network = self.vnfds[src_name].get("dc").net # there should be a cleaner way to find the DCNetwork
183 src_docker_name = self.vnf_name2docker_name[src_name]
184 dst_docker_name = self.vnf_name2docker_name[dst_name]
185 LOG.debug(src_docker_name)
186 ret = network.setChain(
187 src_docker_name, dst_docker_name,
188 vnf_src_interface=src_if_name, vnf_dst_interface=dst_if_name,
189 bidirectional=True, cmd="add-flow", cookie=cookie)
wtaverni5b23b662016-06-20 12:26:21 +0200190 cookie += 1
stevenvanrossemd87fe472016-05-11 11:34:34 +0200191
peusterm6b5224d2016-07-20 13:20:31 +0200192 # re-configure the VNFs IP assignment and ensure that a new subnet is used for each E-Link
193 src_vnfi = self._get_vnf_instance(instance_uuid, src_name)
194 if src_vnfi is not None:
195 self._vnf_reconfigure_network(src_vnfi, src_if_name, self.eline_subnets_src.pop(0))
196 dst_vnfi = self._get_vnf_instance(instance_uuid, dst_name)
197 if dst_vnfi is not None:
198 self._vnf_reconfigure_network(dst_vnfi, dst_if_name, self.eline_subnets_dst.pop(0))
199
peusterm8484b902016-06-21 09:03:35 +0200200 # 4. run the emulator specific entrypoint scripts in the VNFIs of this service instance
201 self._trigger_emulator_start_scripts_in_vnfis(self.instances[instance_uuid]["vnf_instances"])
202
peusterm3444ae42016-03-16 20:46:41 +0100203 LOG.info("Service started. Instance id: %r" % instance_uuid)
peusterm082378b2016-03-16 20:14:22 +0100204 return instance_uuid
205
peusterm398cd3b2016-03-21 15:04:54 +0100206 def _start_vnfd(self, vnfd):
207 """
208 Start a single VNFD of this service
209 :param vnfd: vnfd descriptor dict
210 :return:
211 """
212 # iterate over all deployment units within each VNFDs
213 for u in vnfd.get("virtual_deployment_units"):
214 # 1. get the name of the docker image to start and the assigned DC
peusterm56356cb2016-05-03 10:43:43 +0200215 vnf_name = vnfd.get("name")
216 if vnf_name not in self.remote_docker_image_urls:
217 raise Exception("No image name for %r found. Abort." % vnf_name)
218 docker_name = self.remote_docker_image_urls.get(vnf_name)
peusterm398cd3b2016-03-21 15:04:54 +0100219 target_dc = vnfd.get("dc")
220 # 2. perform some checks to ensure we can start the container
221 assert(docker_name is not None)
222 assert(target_dc is not None)
223 if not self._check_docker_image_exists(docker_name):
224 raise Exception("Docker image %r not found. Abort." % docker_name)
225 # 3. do the dc.startCompute(name="foobar") call to run the container
226 # TODO consider flavors, and other annotations
stevenvanrossemd87fe472016-05-11 11:34:34 +0200227 intfs = vnfd.get("connection_points")
stevenvanrossemc59802b2016-08-05 14:59:58 +0200228 # mgmt connection points can be skipped, this is considered to be the connection the default docker0 bridge
229 intfs = [intf for intf in intfs if 'mgmt' not in intf['id']]
230
stevenvanrossem11a021f2016-08-05 13:43:00 +0200231 # use the vnf_id in the nsd as docker name
232 # so deployed containers can be easily mapped back to the nsd
233 vnf_name2id = defaultdict(lambda: "NotExistingNode",
234 reduce(lambda x, y: dict(x, **y),
235 map(lambda d: {d["vnf_name"]: d["vnf_id"]},
236 self.nsd["network_functions"])))
237 self.vnf_name2docker_name[vnf_name] = vnf_name2id[vnf_name]
238 # self.vnf_name2docker_name[vnf_name] = GK.get_next_vnf_name()
239
peusterm6b5224d2016-07-20 13:20:31 +0200240 LOG.info("Starting %r as %r in DC %r" % (vnf_name, self.vnf_name2docker_name[vnf_name], vnfd.get("dc")))
peusterm761c14d2016-07-19 09:31:19 +0200241 LOG.debug("Interfaces for %r: %r" % (vnf_name, intfs))
peusterm6b5224d2016-07-20 13:20:31 +0200242 vnfi = target_dc.startCompute(self.vnf_name2docker_name[vnf_name], network=intfs, image=docker_name, flavor_name="small")
peusterm398cd3b2016-03-21 15:04:54 +0100243 return vnfi
244
peusterm6b5224d2016-07-20 13:20:31 +0200245 def _get_vnf_instance(self, instance_uuid, name):
246 """
247 Returns the Docker object for the given VNF name (or Docker name).
248 :param instance_uuid: UUID of the service instance to search in.
249 :param name: VNF name or Docker name. We are fuzzy here.
250 :return:
251 """
252 dn = name
253 if name in self.vnf_name2docker_name:
254 dn = self.vnf_name2docker_name[name]
255 for vnfi in self.instances[instance_uuid]["vnf_instances"]:
256 if vnfi.name == dn:
257 return vnfi
258 LOG.warning("No container with name: %r found.")
259 return None
260
261 @staticmethod
262 def _vnf_reconfigure_network(vnfi, if_name, net_str):
263 """
264 Reconfigure the network configuration of a specific interface
265 of a running container.
266 :param vnfi: container instacne
267 :param if_name: interface name
268 :param net_str: network configuration string, e.g., 1.2.3.4/24
269 :return:
270 """
271 intf = vnfi.intf(intf=if_name)
272 if intf is not None:
273 intf.setIP(net_str)
274 LOG.debug("Reconfigured network of %s:%s to %r" % (vnfi.name, if_name, net_str))
275 else:
276 LOG.warning("Interface not found: %s:%s. Network reconfiguration skipped." % (vnfi.name, if_name))
277
278
peusterm8484b902016-06-21 09:03:35 +0200279 def _trigger_emulator_start_scripts_in_vnfis(self, vnfi_list):
280 for vnfi in vnfi_list:
281 config = vnfi.dcinfo.get("Config", dict())
282 env = config.get("Env", list())
283 for env_var in env:
284 if "SON_EMU_CMD=" in env_var:
285 cmd = str(env_var.split("=")[1])
286 LOG.info("Executing entrypoint script in %r: %r" % (vnfi.name, cmd))
287 vnfi.cmdPrint(cmd)
288
peusterm786cd542016-03-14 14:12:17 +0100289 def _unpack_service_package(self):
290 """
291 unzip *.son file and store contents in CATALOG_FOLDER/services/<service_uuid>/
292 """
peusterm82d406e2016-05-02 20:52:06 +0200293 LOG.info("Unzipping: %r" % self.package_file_path)
peusterm786cd542016-03-14 14:12:17 +0100294 with zipfile.ZipFile(self.package_file_path, "r") as z:
295 z.extractall(self.package_content_path)
296
peusterm82d406e2016-05-02 20:52:06 +0200297
peusterm7ec665d2016-03-14 15:20:44 +0100298 def _load_package_descriptor(self):
299 """
300 Load the main package descriptor YAML and keep it as dict.
301 :return:
302 """
303 self.manifest = load_yaml(
304 os.path.join(
305 self.package_content_path, "META-INF/MANIFEST.MF"))
306
307 def _load_nsd(self):
308 """
309 Load the entry NSD YAML and keep it as dict.
310 :return:
311 """
312 if "entry_service_template" in self.manifest:
313 nsd_path = os.path.join(
314 self.package_content_path,
315 make_relative_path(self.manifest.get("entry_service_template")))
316 self.nsd = load_yaml(nsd_path)
peusterm757fe9a2016-04-04 14:11:58 +0200317 LOG.debug("Loaded NSD: %r" % self.nsd.get("name"))
peusterm7ec665d2016-03-14 15:20:44 +0100318
319 def _load_vnfd(self):
320 """
321 Load all VNFD YAML files referenced in MANIFEST.MF and keep them in dict.
322 :return:
323 """
324 if "package_content" in self.manifest:
325 for pc in self.manifest.get("package_content"):
326 if pc.get("content-type") == "application/sonata.function_descriptor":
327 vnfd_path = os.path.join(
328 self.package_content_path,
329 make_relative_path(pc.get("name")))
330 vnfd = load_yaml(vnfd_path)
peusterm757fe9a2016-04-04 14:11:58 +0200331 self.vnfds[vnfd.get("name")] = vnfd
332 LOG.debug("Loaded VNFD: %r" % vnfd.get("name"))
peusterm7ec665d2016-03-14 15:20:44 +0100333
334 def _load_docker_files(self):
335 """
peusterm9d7d4b02016-03-23 19:56:44 +0100336 Get all paths to Dockerfiles from VNFDs and store them in dict.
peusterm7ec665d2016-03-14 15:20:44 +0100337 :return:
338 """
peusterm9d7d4b02016-03-23 19:56:44 +0100339 for k, v in self.vnfds.iteritems():
340 for vu in v.get("virtual_deployment_units"):
341 if vu.get("vm_image_format") == "docker":
342 vm_image = vu.get("vm_image")
peusterm7ec665d2016-03-14 15:20:44 +0100343 docker_path = os.path.join(
344 self.package_content_path,
peusterm9d7d4b02016-03-23 19:56:44 +0100345 make_relative_path(vm_image))
346 self.local_docker_files[k] = docker_path
peusterm56356cb2016-05-03 10:43:43 +0200347 LOG.debug("Found Dockerfile (%r): %r" % (k, docker_path))
peusterm7ec665d2016-03-14 15:20:44 +0100348
peusterm82d406e2016-05-02 20:52:06 +0200349 def _load_docker_urls(self):
350 """
351 Get all URLs to pre-build docker images in some repo.
352 :return:
353 """
354 for k, v in self.vnfds.iteritems():
355 for vu in v.get("virtual_deployment_units"):
356 if vu.get("vm_image_format") == "docker":
peusterm35ba4052016-05-02 21:21:14 +0200357 url = vu.get("vm_image")
358 if url is not None:
359 url = url.replace("http://", "")
360 self.remote_docker_image_urls[k] = url
peusterm56356cb2016-05-03 10:43:43 +0200361 LOG.debug("Found Docker image URL (%r): %r" % (k, self.remote_docker_image_urls[k]))
peusterm82d406e2016-05-02 20:52:06 +0200362
peustermbdfab7e2016-03-14 16:03:30 +0100363 def _build_images_from_dockerfiles(self):
364 """
365 Build Docker images for each local Dockerfile found in the package: self.local_docker_files
366 """
peusterm398cd3b2016-03-21 15:04:54 +0100367 if GK_STANDALONE_MODE:
368 return # do not build anything in standalone mode
peustermbdfab7e2016-03-14 16:03:30 +0100369 dc = DockerClient()
370 LOG.info("Building %d Docker images (this may take several minutes) ..." % len(self.local_docker_files))
371 for k, v in self.local_docker_files.iteritems():
372 for line in dc.build(path=v.replace("Dockerfile", ""), tag=k, rm=False, nocache=False):
373 LOG.debug("DOCKER BUILD: %s" % line)
374 LOG.info("Docker image created: %s" % k)
375
peusterm82d406e2016-05-02 20:52:06 +0200376 def _pull_predefined_dockerimages(self):
peustermbdfab7e2016-03-14 16:03:30 +0100377 """
378 If the package contains URLs to pre-build Docker images, we download them with this method.
379 """
peusterm35ba4052016-05-02 21:21:14 +0200380 dc = DockerClient()
381 for url in self.remote_docker_image_urls.itervalues():
peusterm56356cb2016-05-03 10:43:43 +0200382 if not FORCE_PULL: # only pull if not present (speedup for development)
383 if len(dc.images(name=url)) > 0:
384 LOG.debug("Image %r present. Skipping pull." % url)
385 continue
peusterm35ba4052016-05-02 21:21:14 +0200386 LOG.info("Pulling image: %r" % url)
387 dc.pull(url,
388 insecure_registry=True)
peusterm786cd542016-03-14 14:12:17 +0100389
peusterm3444ae42016-03-16 20:46:41 +0100390 def _check_docker_image_exists(self, image_name):
peusterm3f307142016-03-16 21:02:53 +0100391 """
392 Query the docker service and check if the given image exists
393 :param image_name: name of the docker image
394 :return:
395 """
396 return len(DockerClient().images(image_name)) > 0
peusterm3444ae42016-03-16 20:46:41 +0100397
peusterm082378b2016-03-16 20:14:22 +0100398 def _calculate_placement(self, algorithm):
399 """
400 Do placement by adding the a field "dc" to
401 each VNFD that points to one of our
402 data center objects known to the gatekeeper.
403 """
404 assert(len(self.vnfds) > 0)
405 assert(len(GK.dcs) > 0)
406 # instantiate algorithm an place
407 p = algorithm()
408 p.place(self.nsd, self.vnfds, GK.dcs)
409 LOG.info("Using placement algorithm: %r" % p.__class__.__name__)
410 # lets print the placement result
411 for name, vnfd in self.vnfds.iteritems():
412 LOG.info("Placed VNF %r on DC %r" % (name, str(vnfd.get("dc"))))
413
414
415"""
416Some (simple) placement algorithms
417"""
418
419
420class FirstDcPlacement(object):
421 """
422 Placement: Always use one and the same data center from the GK.dcs dict.
423 """
424 def place(self, nsd, vnfds, dcs):
425 for name, vnfd in vnfds.iteritems():
426 vnfd["dc"] = list(dcs.itervalues())[0]
427
peusterme26487b2016-03-08 14:00:21 +0100428
429"""
430Resource definitions and API endpoints
431"""
432
433
434class Packages(fr.Resource):
435
436 def post(self):
437 """
peusterm26455852016-03-08 14:23:53 +0100438 Upload a *.son service package to the dummy gatekeeper.
439
peusterme26487b2016-03-08 14:00:21 +0100440 We expect request with a *.son file and store it in UPLOAD_FOLDER
peusterm26455852016-03-08 14:23:53 +0100441 :return: UUID
peusterme26487b2016-03-08 14:00:21 +0100442 """
443 try:
444 # get file contents
wtavernib8d9ecb2016-03-25 15:18:31 +0100445 print(request.files)
peusterm593ca582016-03-30 19:55:01 +0200446 # lets search for the package in the request
447 if "package" in request.files:
448 son_file = request.files["package"]
449 # elif "file" in request.files:
450 # son_file = request.files["file"]
451 else:
452 return {"service_uuid": None, "size": 0, "sha1": None, "error": "upload failed. file not found."}, 500
peusterme26487b2016-03-08 14:00:21 +0100453 # generate a uuid to reference this package
454 service_uuid = str(uuid.uuid4())
peusterm786cd542016-03-14 14:12:17 +0100455 file_hash = hashlib.sha1(str(son_file)).hexdigest()
peusterme26487b2016-03-08 14:00:21 +0100456 # ensure that upload folder exists
457 ensure_dir(UPLOAD_FOLDER)
458 upload_path = os.path.join(UPLOAD_FOLDER, "%s.son" % service_uuid)
459 # store *.son file to disk
peusterm786cd542016-03-14 14:12:17 +0100460 son_file.save(upload_path)
peusterme26487b2016-03-08 14:00:21 +0100461 size = os.path.getsize(upload_path)
peusterm786cd542016-03-14 14:12:17 +0100462 # create a service object and register it
463 s = Service(service_uuid, file_hash, upload_path)
464 GK.register_service_package(service_uuid, s)
peusterme26487b2016-03-08 14:00:21 +0100465 # generate the JSON result
peusterm786cd542016-03-14 14:12:17 +0100466 return {"service_uuid": service_uuid, "size": size, "sha1": file_hash, "error": None}
peusterme26487b2016-03-08 14:00:21 +0100467 except Exception as ex:
peusterm786cd542016-03-14 14:12:17 +0100468 LOG.exception("Service package upload failed:")
peusterm593ca582016-03-30 19:55:01 +0200469 return {"service_uuid": None, "size": 0, "sha1": None, "error": "upload failed"}, 500
peusterme26487b2016-03-08 14:00:21 +0100470
471 def get(self):
peusterm26455852016-03-08 14:23:53 +0100472 """
473 Return a list of UUID's of uploaded service packages.
474 :return: dict/list
475 """
peusterm075b46a2016-07-20 17:08:00 +0200476 LOG.info("GET /packages")
peusterm786cd542016-03-14 14:12:17 +0100477 return {"service_uuid_list": list(GK.services.iterkeys())}
peusterme26487b2016-03-08 14:00:21 +0100478
479
480class Instantiations(fr.Resource):
481
482 def post(self):
peusterm26455852016-03-08 14:23:53 +0100483 """
484 Instantiate a service specified by its UUID.
485 Will return a new UUID to identify the running service instance.
486 :return: UUID
487 """
peusterm64b45502016-03-16 21:15:14 +0100488 # try to extract the service uuid from the request
peusterm26455852016-03-08 14:23:53 +0100489 json_data = request.get_json(force=True)
peusterm64b45502016-03-16 21:15:14 +0100490 service_uuid = json_data.get("service_uuid")
491
492 # lets be a bit fuzzy here to make testing easier
493 if service_uuid is None and len(GK.services) > 0:
494 # if we don't get a service uuid, we simple start the first service in the list
495 service_uuid = list(GK.services.iterkeys())[0]
496
peustermbea87372016-03-16 19:37:35 +0100497 if service_uuid in GK.services:
peusterm64b45502016-03-16 21:15:14 +0100498 # ok, we have a service uuid, lets start the service
peustermbea87372016-03-16 19:37:35 +0100499 service_instance_uuid = GK.services.get(service_uuid).start_service()
peusterm26455852016-03-08 14:23:53 +0100500 return {"service_instance_uuid": service_instance_uuid}
peustermbea87372016-03-16 19:37:35 +0100501 return "Service not found", 404
peusterme26487b2016-03-08 14:00:21 +0100502
503 def get(self):
peusterm26455852016-03-08 14:23:53 +0100504 """
505 Returns a list of UUIDs containing all running services.
506 :return: dict / list
507 """
peusterm075b46a2016-07-20 17:08:00 +0200508 LOG.info("GET /instantiations")
509 return {"service_instantiations_list": [
peusterm64b45502016-03-16 21:15:14 +0100510 list(s.instances.iterkeys()) for s in GK.services.itervalues()]}
peusterm786cd542016-03-14 14:12:17 +0100511
peusterme26487b2016-03-08 14:00:21 +0100512
513# create a single, global GK object
514GK = Gatekeeper()
515# setup Flask
516app = Flask(__name__)
517app.config['MAX_CONTENT_LENGTH'] = 512 * 1024 * 1024 # 512 MB max upload
518api = fr.Api(app)
519# define endpoints
peusterm593ca582016-03-30 19:55:01 +0200520api.add_resource(Packages, '/packages')
521api.add_resource(Instantiations, '/instantiations')
peusterme26487b2016-03-08 14:00:21 +0100522
523
peusterm082378b2016-03-16 20:14:22 +0100524def start_rest_api(host, port, datacenters=dict()):
peustermbea87372016-03-16 19:37:35 +0100525 GK.dcs = datacenters
peusterme26487b2016-03-08 14:00:21 +0100526 # start the Flask server (not the best performance but ok for our use case)
527 app.run(host=host,
528 port=port,
529 debug=True,
530 use_reloader=False # this is needed to run Flask in a non-main thread
531 )
532
533
534def ensure_dir(name):
535 if not os.path.exists(name):
peusterm7ec665d2016-03-14 15:20:44 +0100536 os.makedirs(name)
537
538
539def load_yaml(path):
540 with open(path, "r") as f:
541 try:
542 r = yaml.load(f)
543 except yaml.YAMLError as exc:
544 LOG.exception("YAML parse error")
545 r = dict()
546 return r
547
548
549def make_relative_path(path):
peusterm9d7d4b02016-03-23 19:56:44 +0100550 if path.startswith("file://"):
551 path = path.replace("file://", "", 1)
peusterm7ec665d2016-03-14 15:20:44 +0100552 if path.startswith("/"):
peusterm9d7d4b02016-03-23 19:56:44 +0100553 path = path.replace("/", "", 1)
peusterm7ec665d2016-03-14 15:20:44 +0100554 return path
555
556
peusterm6b5224d2016-07-20 13:20:31 +0200557def generate_subnet_strings(n, start=1, subnet_size=24, ip=0):
558 """
559 Helper to generate different network configuration strings.
560 """
561 r = list()
562 for i in range(start, start + n):
563 r.append("%d.0.0.%d/%d" % (i, ip, subnet_size))
564 return r
565
566
peusterme26487b2016-03-08 14:00:21 +0100567if __name__ == '__main__':
568 """
569 Lets allow to run the API in standalone mode.
570 """
peusterm398cd3b2016-03-21 15:04:54 +0100571 GK_STANDALONE_MODE = True
peusterme26487b2016-03-08 14:00:21 +0100572 logging.getLogger("werkzeug").setLevel(logging.INFO)
573 start_rest_api("0.0.0.0", 8000)
574