blob: 916e168769a5a65f152c34bd6f20db8af5affc98 [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
stevenvanrossemaa6d3a72016-08-10 13:23:24 +0200169 # cookie is used as identifier for the flowrules installed by the dummygatekeeper
170 # eg. different services get a unique cookie for their flowrules
171 cookie = 1
stevenvanrossemd87fe472016-05-11 11:34:34 +0200172 for link in eline_fwd_links:
peusterm6b5224d2016-07-20 13:20:31 +0200173 src_id, src_if_name = link["connection_points_reference"][0].split(":")
174 dst_id, dst_if_name = link["connection_points_reference"][1].split(":")
stevenvanrossemd87fe472016-05-11 11:34:34 +0200175
peusterm6b5224d2016-07-20 13:20:31 +0200176 src_name = vnf_id2vnf_name[src_id]
177 dst_name = vnf_id2vnf_name[dst_id]
peusterm9fb74ec2016-06-16 11:30:55 +0200178
peusterm6b5224d2016-07-20 13:20:31 +0200179 LOG.debug(
180 "Setting up E-Line link. %s(%s:%s) -> %s(%s:%s)" % (
181 src_name, src_id, src_if_name, dst_name, dst_id, dst_if_name))
182
183 if (src_name in self.vnfds) and (dst_name in self.vnfds):
184 network = self.vnfds[src_name].get("dc").net # there should be a cleaner way to find the DCNetwork
185 src_docker_name = self.vnf_name2docker_name[src_name]
186 dst_docker_name = self.vnf_name2docker_name[dst_name]
187 LOG.debug(src_docker_name)
188 ret = network.setChain(
189 src_docker_name, dst_docker_name,
190 vnf_src_interface=src_if_name, vnf_dst_interface=dst_if_name,
191 bidirectional=True, cmd="add-flow", cookie=cookie)
stevenvanrossemd87fe472016-05-11 11:34:34 +0200192
peusterm6b5224d2016-07-20 13:20:31 +0200193 # re-configure the VNFs IP assignment and ensure that a new subnet is used for each E-Link
194 src_vnfi = self._get_vnf_instance(instance_uuid, src_name)
195 if src_vnfi is not None:
196 self._vnf_reconfigure_network(src_vnfi, src_if_name, self.eline_subnets_src.pop(0))
197 dst_vnfi = self._get_vnf_instance(instance_uuid, dst_name)
198 if dst_vnfi is not None:
199 self._vnf_reconfigure_network(dst_vnfi, dst_if_name, self.eline_subnets_dst.pop(0))
200
peusterm8484b902016-06-21 09:03:35 +0200201 # 4. run the emulator specific entrypoint scripts in the VNFIs of this service instance
202 self._trigger_emulator_start_scripts_in_vnfis(self.instances[instance_uuid]["vnf_instances"])
203
peusterm3444ae42016-03-16 20:46:41 +0100204 LOG.info("Service started. Instance id: %r" % instance_uuid)
peusterm082378b2016-03-16 20:14:22 +0100205 return instance_uuid
206
peusterm398cd3b2016-03-21 15:04:54 +0100207 def _start_vnfd(self, vnfd):
208 """
209 Start a single VNFD of this service
210 :param vnfd: vnfd descriptor dict
211 :return:
212 """
213 # iterate over all deployment units within each VNFDs
214 for u in vnfd.get("virtual_deployment_units"):
215 # 1. get the name of the docker image to start and the assigned DC
peusterm56356cb2016-05-03 10:43:43 +0200216 vnf_name = vnfd.get("name")
217 if vnf_name not in self.remote_docker_image_urls:
218 raise Exception("No image name for %r found. Abort." % vnf_name)
219 docker_name = self.remote_docker_image_urls.get(vnf_name)
peusterm398cd3b2016-03-21 15:04:54 +0100220 target_dc = vnfd.get("dc")
221 # 2. perform some checks to ensure we can start the container
222 assert(docker_name is not None)
223 assert(target_dc is not None)
224 if not self._check_docker_image_exists(docker_name):
225 raise Exception("Docker image %r not found. Abort." % docker_name)
226 # 3. do the dc.startCompute(name="foobar") call to run the container
227 # TODO consider flavors, and other annotations
stevenvanrossemd87fe472016-05-11 11:34:34 +0200228 intfs = vnfd.get("connection_points")
stevenvanrossemeae73082016-08-05 16:22:12 +0200229
stevenvanrossem11a021f2016-08-05 13:43:00 +0200230 # use the vnf_id in the nsd as docker name
231 # so deployed containers can be easily mapped back to the nsd
232 vnf_name2id = defaultdict(lambda: "NotExistingNode",
233 reduce(lambda x, y: dict(x, **y),
234 map(lambda d: {d["vnf_name"]: d["vnf_id"]},
235 self.nsd["network_functions"])))
236 self.vnf_name2docker_name[vnf_name] = vnf_name2id[vnf_name]
237 # self.vnf_name2docker_name[vnf_name] = GK.get_next_vnf_name()
238
peusterm6b5224d2016-07-20 13:20:31 +0200239 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 +0200240 LOG.debug("Interfaces for %r: %r" % (vnf_name, intfs))
peusterm6b5224d2016-07-20 13:20:31 +0200241 vnfi = target_dc.startCompute(self.vnf_name2docker_name[vnf_name], network=intfs, image=docker_name, flavor_name="small")
peusterm398cd3b2016-03-21 15:04:54 +0100242 return vnfi
243
peusterm6b5224d2016-07-20 13:20:31 +0200244 def _get_vnf_instance(self, instance_uuid, name):
245 """
246 Returns the Docker object for the given VNF name (or Docker name).
247 :param instance_uuid: UUID of the service instance to search in.
248 :param name: VNF name or Docker name. We are fuzzy here.
249 :return:
250 """
251 dn = name
252 if name in self.vnf_name2docker_name:
253 dn = self.vnf_name2docker_name[name]
254 for vnfi in self.instances[instance_uuid]["vnf_instances"]:
255 if vnfi.name == dn:
256 return vnfi
257 LOG.warning("No container with name: %r found.")
258 return None
259
260 @staticmethod
261 def _vnf_reconfigure_network(vnfi, if_name, net_str):
262 """
263 Reconfigure the network configuration of a specific interface
264 of a running container.
265 :param vnfi: container instacne
266 :param if_name: interface name
267 :param net_str: network configuration string, e.g., 1.2.3.4/24
268 :return:
269 """
270 intf = vnfi.intf(intf=if_name)
271 if intf is not None:
272 intf.setIP(net_str)
273 LOG.debug("Reconfigured network of %s:%s to %r" % (vnfi.name, if_name, net_str))
274 else:
275 LOG.warning("Interface not found: %s:%s. Network reconfiguration skipped." % (vnfi.name, if_name))
276
277
peusterm8484b902016-06-21 09:03:35 +0200278 def _trigger_emulator_start_scripts_in_vnfis(self, vnfi_list):
279 for vnfi in vnfi_list:
280 config = vnfi.dcinfo.get("Config", dict())
281 env = config.get("Env", list())
282 for env_var in env:
283 if "SON_EMU_CMD=" in env_var:
284 cmd = str(env_var.split("=")[1])
285 LOG.info("Executing entrypoint script in %r: %r" % (vnfi.name, cmd))
286 vnfi.cmdPrint(cmd)
287
peusterm786cd542016-03-14 14:12:17 +0100288 def _unpack_service_package(self):
289 """
290 unzip *.son file and store contents in CATALOG_FOLDER/services/<service_uuid>/
291 """
peusterm82d406e2016-05-02 20:52:06 +0200292 LOG.info("Unzipping: %r" % self.package_file_path)
peusterm786cd542016-03-14 14:12:17 +0100293 with zipfile.ZipFile(self.package_file_path, "r") as z:
294 z.extractall(self.package_content_path)
295
peusterm82d406e2016-05-02 20:52:06 +0200296
peusterm7ec665d2016-03-14 15:20:44 +0100297 def _load_package_descriptor(self):
298 """
299 Load the main package descriptor YAML and keep it as dict.
300 :return:
301 """
302 self.manifest = load_yaml(
303 os.path.join(
304 self.package_content_path, "META-INF/MANIFEST.MF"))
305
306 def _load_nsd(self):
307 """
308 Load the entry NSD YAML and keep it as dict.
309 :return:
310 """
311 if "entry_service_template" in self.manifest:
312 nsd_path = os.path.join(
313 self.package_content_path,
314 make_relative_path(self.manifest.get("entry_service_template")))
315 self.nsd = load_yaml(nsd_path)
peusterm757fe9a2016-04-04 14:11:58 +0200316 LOG.debug("Loaded NSD: %r" % self.nsd.get("name"))
peusterm7ec665d2016-03-14 15:20:44 +0100317
318 def _load_vnfd(self):
319 """
320 Load all VNFD YAML files referenced in MANIFEST.MF and keep them in dict.
321 :return:
322 """
323 if "package_content" in self.manifest:
324 for pc in self.manifest.get("package_content"):
325 if pc.get("content-type") == "application/sonata.function_descriptor":
326 vnfd_path = os.path.join(
327 self.package_content_path,
328 make_relative_path(pc.get("name")))
329 vnfd = load_yaml(vnfd_path)
peusterm757fe9a2016-04-04 14:11:58 +0200330 self.vnfds[vnfd.get("name")] = vnfd
331 LOG.debug("Loaded VNFD: %r" % vnfd.get("name"))
peusterm7ec665d2016-03-14 15:20:44 +0100332
333 def _load_docker_files(self):
334 """
peusterm9d7d4b02016-03-23 19:56:44 +0100335 Get all paths to Dockerfiles from VNFDs and store them in dict.
peusterm7ec665d2016-03-14 15:20:44 +0100336 :return:
337 """
peusterm9d7d4b02016-03-23 19:56:44 +0100338 for k, v in self.vnfds.iteritems():
339 for vu in v.get("virtual_deployment_units"):
340 if vu.get("vm_image_format") == "docker":
341 vm_image = vu.get("vm_image")
peusterm7ec665d2016-03-14 15:20:44 +0100342 docker_path = os.path.join(
343 self.package_content_path,
peusterm9d7d4b02016-03-23 19:56:44 +0100344 make_relative_path(vm_image))
345 self.local_docker_files[k] = docker_path
peusterm56356cb2016-05-03 10:43:43 +0200346 LOG.debug("Found Dockerfile (%r): %r" % (k, docker_path))
peusterm7ec665d2016-03-14 15:20:44 +0100347
peusterm82d406e2016-05-02 20:52:06 +0200348 def _load_docker_urls(self):
349 """
350 Get all URLs to pre-build docker images in some repo.
351 :return:
352 """
353 for k, v in self.vnfds.iteritems():
354 for vu in v.get("virtual_deployment_units"):
355 if vu.get("vm_image_format") == "docker":
peusterm35ba4052016-05-02 21:21:14 +0200356 url = vu.get("vm_image")
357 if url is not None:
358 url = url.replace("http://", "")
359 self.remote_docker_image_urls[k] = url
peusterm56356cb2016-05-03 10:43:43 +0200360 LOG.debug("Found Docker image URL (%r): %r" % (k, self.remote_docker_image_urls[k]))
peusterm82d406e2016-05-02 20:52:06 +0200361
peustermbdfab7e2016-03-14 16:03:30 +0100362 def _build_images_from_dockerfiles(self):
363 """
364 Build Docker images for each local Dockerfile found in the package: self.local_docker_files
365 """
peusterm398cd3b2016-03-21 15:04:54 +0100366 if GK_STANDALONE_MODE:
367 return # do not build anything in standalone mode
peustermbdfab7e2016-03-14 16:03:30 +0100368 dc = DockerClient()
369 LOG.info("Building %d Docker images (this may take several minutes) ..." % len(self.local_docker_files))
370 for k, v in self.local_docker_files.iteritems():
371 for line in dc.build(path=v.replace("Dockerfile", ""), tag=k, rm=False, nocache=False):
372 LOG.debug("DOCKER BUILD: %s" % line)
373 LOG.info("Docker image created: %s" % k)
374
peusterm82d406e2016-05-02 20:52:06 +0200375 def _pull_predefined_dockerimages(self):
peustermbdfab7e2016-03-14 16:03:30 +0100376 """
377 If the package contains URLs to pre-build Docker images, we download them with this method.
378 """
peusterm35ba4052016-05-02 21:21:14 +0200379 dc = DockerClient()
380 for url in self.remote_docker_image_urls.itervalues():
peusterm56356cb2016-05-03 10:43:43 +0200381 if not FORCE_PULL: # only pull if not present (speedup for development)
382 if len(dc.images(name=url)) > 0:
383 LOG.debug("Image %r present. Skipping pull." % url)
384 continue
peusterm35ba4052016-05-02 21:21:14 +0200385 LOG.info("Pulling image: %r" % url)
386 dc.pull(url,
387 insecure_registry=True)
peusterm786cd542016-03-14 14:12:17 +0100388
peusterm3444ae42016-03-16 20:46:41 +0100389 def _check_docker_image_exists(self, image_name):
peusterm3f307142016-03-16 21:02:53 +0100390 """
391 Query the docker service and check if the given image exists
392 :param image_name: name of the docker image
393 :return:
394 """
395 return len(DockerClient().images(image_name)) > 0
peusterm3444ae42016-03-16 20:46:41 +0100396
peusterm082378b2016-03-16 20:14:22 +0100397 def _calculate_placement(self, algorithm):
398 """
399 Do placement by adding the a field "dc" to
400 each VNFD that points to one of our
401 data center objects known to the gatekeeper.
402 """
403 assert(len(self.vnfds) > 0)
404 assert(len(GK.dcs) > 0)
405 # instantiate algorithm an place
406 p = algorithm()
407 p.place(self.nsd, self.vnfds, GK.dcs)
408 LOG.info("Using placement algorithm: %r" % p.__class__.__name__)
409 # lets print the placement result
410 for name, vnfd in self.vnfds.iteritems():
411 LOG.info("Placed VNF %r on DC %r" % (name, str(vnfd.get("dc"))))
412
413
414"""
415Some (simple) placement algorithms
416"""
417
418
419class FirstDcPlacement(object):
420 """
421 Placement: Always use one and the same data center from the GK.dcs dict.
422 """
423 def place(self, nsd, vnfds, dcs):
424 for name, vnfd in vnfds.iteritems():
425 vnfd["dc"] = list(dcs.itervalues())[0]
426
peusterme26487b2016-03-08 14:00:21 +0100427
428"""
429Resource definitions and API endpoints
430"""
431
432
433class Packages(fr.Resource):
434
435 def post(self):
436 """
peusterm26455852016-03-08 14:23:53 +0100437 Upload a *.son service package to the dummy gatekeeper.
438
peusterme26487b2016-03-08 14:00:21 +0100439 We expect request with a *.son file and store it in UPLOAD_FOLDER
peusterm26455852016-03-08 14:23:53 +0100440 :return: UUID
peusterme26487b2016-03-08 14:00:21 +0100441 """
442 try:
443 # get file contents
wtavernib8d9ecb2016-03-25 15:18:31 +0100444 print(request.files)
peusterm593ca582016-03-30 19:55:01 +0200445 # lets search for the package in the request
446 if "package" in request.files:
447 son_file = request.files["package"]
448 # elif "file" in request.files:
449 # son_file = request.files["file"]
450 else:
451 return {"service_uuid": None, "size": 0, "sha1": None, "error": "upload failed. file not found."}, 500
peusterme26487b2016-03-08 14:00:21 +0100452 # generate a uuid to reference this package
453 service_uuid = str(uuid.uuid4())
peusterm786cd542016-03-14 14:12:17 +0100454 file_hash = hashlib.sha1(str(son_file)).hexdigest()
peusterme26487b2016-03-08 14:00:21 +0100455 # ensure that upload folder exists
456 ensure_dir(UPLOAD_FOLDER)
457 upload_path = os.path.join(UPLOAD_FOLDER, "%s.son" % service_uuid)
458 # store *.son file to disk
peusterm786cd542016-03-14 14:12:17 +0100459 son_file.save(upload_path)
peusterme26487b2016-03-08 14:00:21 +0100460 size = os.path.getsize(upload_path)
peusterm786cd542016-03-14 14:12:17 +0100461 # create a service object and register it
462 s = Service(service_uuid, file_hash, upload_path)
463 GK.register_service_package(service_uuid, s)
peusterme26487b2016-03-08 14:00:21 +0100464 # generate the JSON result
peusterm786cd542016-03-14 14:12:17 +0100465 return {"service_uuid": service_uuid, "size": size, "sha1": file_hash, "error": None}
peusterme26487b2016-03-08 14:00:21 +0100466 except Exception as ex:
peusterm786cd542016-03-14 14:12:17 +0100467 LOG.exception("Service package upload failed:")
peusterm593ca582016-03-30 19:55:01 +0200468 return {"service_uuid": None, "size": 0, "sha1": None, "error": "upload failed"}, 500
peusterme26487b2016-03-08 14:00:21 +0100469
470 def get(self):
peusterm26455852016-03-08 14:23:53 +0100471 """
472 Return a list of UUID's of uploaded service packages.
473 :return: dict/list
474 """
peusterm075b46a2016-07-20 17:08:00 +0200475 LOG.info("GET /packages")
peusterm786cd542016-03-14 14:12:17 +0100476 return {"service_uuid_list": list(GK.services.iterkeys())}
peusterme26487b2016-03-08 14:00:21 +0100477
478
479class Instantiations(fr.Resource):
480
481 def post(self):
peusterm26455852016-03-08 14:23:53 +0100482 """
483 Instantiate a service specified by its UUID.
484 Will return a new UUID to identify the running service instance.
485 :return: UUID
486 """
peusterm64b45502016-03-16 21:15:14 +0100487 # try to extract the service uuid from the request
peusterm26455852016-03-08 14:23:53 +0100488 json_data = request.get_json(force=True)
peusterm64b45502016-03-16 21:15:14 +0100489 service_uuid = json_data.get("service_uuid")
490
491 # lets be a bit fuzzy here to make testing easier
492 if service_uuid is None and len(GK.services) > 0:
493 # if we don't get a service uuid, we simple start the first service in the list
494 service_uuid = list(GK.services.iterkeys())[0]
495
peustermbea87372016-03-16 19:37:35 +0100496 if service_uuid in GK.services:
peusterm64b45502016-03-16 21:15:14 +0100497 # ok, we have a service uuid, lets start the service
peustermbea87372016-03-16 19:37:35 +0100498 service_instance_uuid = GK.services.get(service_uuid).start_service()
peusterm26455852016-03-08 14:23:53 +0100499 return {"service_instance_uuid": service_instance_uuid}
peustermbea87372016-03-16 19:37:35 +0100500 return "Service not found", 404
peusterme26487b2016-03-08 14:00:21 +0100501
502 def get(self):
peusterm26455852016-03-08 14:23:53 +0100503 """
504 Returns a list of UUIDs containing all running services.
505 :return: dict / list
506 """
peusterm075b46a2016-07-20 17:08:00 +0200507 LOG.info("GET /instantiations")
508 return {"service_instantiations_list": [
peusterm64b45502016-03-16 21:15:14 +0100509 list(s.instances.iterkeys()) for s in GK.services.itervalues()]}
peusterm786cd542016-03-14 14:12:17 +0100510
peusterme26487b2016-03-08 14:00:21 +0100511
512# create a single, global GK object
513GK = Gatekeeper()
514# setup Flask
515app = Flask(__name__)
516app.config['MAX_CONTENT_LENGTH'] = 512 * 1024 * 1024 # 512 MB max upload
517api = fr.Api(app)
518# define endpoints
peusterm593ca582016-03-30 19:55:01 +0200519api.add_resource(Packages, '/packages')
520api.add_resource(Instantiations, '/instantiations')
peusterme26487b2016-03-08 14:00:21 +0100521
522
peusterm082378b2016-03-16 20:14:22 +0100523def start_rest_api(host, port, datacenters=dict()):
peustermbea87372016-03-16 19:37:35 +0100524 GK.dcs = datacenters
peusterme26487b2016-03-08 14:00:21 +0100525 # start the Flask server (not the best performance but ok for our use case)
526 app.run(host=host,
527 port=port,
528 debug=True,
529 use_reloader=False # this is needed to run Flask in a non-main thread
530 )
531
532
533def ensure_dir(name):
534 if not os.path.exists(name):
peusterm7ec665d2016-03-14 15:20:44 +0100535 os.makedirs(name)
536
537
538def load_yaml(path):
539 with open(path, "r") as f:
540 try:
541 r = yaml.load(f)
542 except yaml.YAMLError as exc:
543 LOG.exception("YAML parse error")
544 r = dict()
545 return r
546
547
548def make_relative_path(path):
peusterm9d7d4b02016-03-23 19:56:44 +0100549 if path.startswith("file://"):
550 path = path.replace("file://", "", 1)
peusterm7ec665d2016-03-14 15:20:44 +0100551 if path.startswith("/"):
peusterm9d7d4b02016-03-23 19:56:44 +0100552 path = path.replace("/", "", 1)
peusterm7ec665d2016-03-14 15:20:44 +0100553 return path
554
555
peusterm6b5224d2016-07-20 13:20:31 +0200556def generate_subnet_strings(n, start=1, subnet_size=24, ip=0):
557 """
558 Helper to generate different network configuration strings.
559 """
560 r = list()
561 for i in range(start, start + n):
562 r.append("%d.0.0.%d/%d" % (i, ip, subnet_size))
563 return r
564
565
peusterme26487b2016-03-08 14:00:21 +0100566if __name__ == '__main__':
567 """
568 Lets allow to run the API in standalone mode.
569 """
peusterm398cd3b2016-03-21 15:04:54 +0100570 GK_STANDALONE_MODE = True
peusterme26487b2016-03-08 14:00:21 +0100571 logging.getLogger("werkzeug").setLevel(logging.INFO)
572 start_rest_api("0.0.0.0", 8000)
573