2 This module implements a simple REST API that behaves like SONATA's gatekeeper.
4 It is only used to support the development of SONATA's SDK tools and to demonstrate
5 the year 1 version of the emulator until the integration with WP4's orchestrator is done.
14 from flask
import Flask
, request
15 import flask_restful
as fr
17 LOG
= logging
.getLogger("sonata-dummy-gatekeeper")
18 LOG
.setLevel(logging
.DEBUG
)
19 logging
.getLogger("werkzeug").setLevel(logging
.WARNING
)
22 UPLOAD_FOLDER
= "/tmp/son-dummy-gk/uploads/"
23 CATALOG_FOLDER
= "/tmp/son-dummy-gk/catalog/"
26 class Gatekeeper(object):
29 self
.services
= dict()
30 LOG
.info("Create SONATA dummy gatekeeper.")
32 def register_service_package(self
, service_uuid
, service
):
34 register new service package
38 self
.services
[service_uuid
] = service
39 # lets perform all steps needed to onboard the service
43 class Service(object):
45 This class represents a NS uploaded as a *.son package to the
47 Can have multiple running instances of this service.
54 self
.uuid
= service_uuid
55 self
.package_file_hash
= package_file_hash
56 self
.package_file_path
= package_file_path
57 self
.package_content_path
= os
.path
.join(CATALOG_FOLDER
, "services/%s" % self
.uuid
)
61 self
.docker_files
= dict()
62 self
.instances
= dict()
64 def start_service(self
, service_uuid
):
65 # TODO implement method
66 # 1. parse descriptors
67 # 2. do the corresponding dc.startCompute(name="foobar") calls
68 # 3. store references to the compute objects in self.instantiations
73 Do all steps to prepare this service to be instantiated
76 # 1. extract the contents of the package and store them in our catalog
77 self
._unpack
_service
_package
()
78 # 2. read in all descriptor files
79 self
._load
_package
_descriptor
()
82 self
._load
_docker
_files
()
83 # 3. prepare container images (e.g. download or build Dockerfile)
85 LOG
.info("On-boarded service: %r" % self
.manifest
.get("package_name"))
87 def _unpack_service_package(self
):
89 unzip *.son file and store contents in CATALOG_FOLDER/services/<service_uuid>/
91 with zipfile
.ZipFile(self
.package_file_path
, "r") as z
:
92 z
.extractall(self
.package_content_path
)
94 def _load_package_descriptor(self
):
96 Load the main package descriptor YAML and keep it as dict.
99 self
.manifest
= load_yaml(
101 self
.package_content_path
, "META-INF/MANIFEST.MF"))
105 Load the entry NSD YAML and keep it as dict.
108 if "entry_service_template" in self
.manifest
:
109 nsd_path
= os
.path
.join(
110 self
.package_content_path
,
111 make_relative_path(self
.manifest
.get("entry_service_template")))
112 self
.nsd
= load_yaml(nsd_path
)
113 LOG
.debug("Loaded NSD: %r" % self
.nsd
.get("ns_name"))
115 def _load_vnfd(self
):
117 Load all VNFD YAML files referenced in MANIFEST.MF and keep them in dict.
120 if "package_content" in self
.manifest
:
121 for pc
in self
.manifest
.get("package_content"):
122 if pc
.get("content-type") == "application/sonata.function_descriptor":
123 vnfd_path
= os
.path
.join(
124 self
.package_content_path
,
125 make_relative_path(pc
.get("name")))
126 vnfd
= load_yaml(vnfd_path
)
127 self
.vnfds
[vnfd
.get("vnf_name")] = vnfd
128 LOG
.debug("Loaded VNFD: %r" % vnfd
.get("vnf_name"))
130 def _load_docker_files(self
):
132 Get all paths to Dockerfiles from MANIFEST.MF and store them in dict.
135 if "package_content" in self
.manifest
:
136 for df
in self
.manifest
.get("package_content"):
137 if df
.get("content-type") == "application/sonata.docker_files":
138 docker_path
= os
.path
.join(
139 self
.package_content_path
,
140 make_relative_path(df
.get("name")))
141 # FIXME: Mapping to docker image names is hardcoded because of the missing mapping in the example package
142 self
.docker_files
[helper_map_docker_name(df
.get("name"))] = docker_path
143 LOG
.debug("Found Dockerfile: %r" % docker_path
)
145 def _build_images_from_dockerfile(self
):
151 Resource definitions and API endpoints
155 class Packages(fr
.Resource
):
159 Upload a *.son service package to the dummy gatekeeper.
161 We expect request with a *.son file and store it in UPLOAD_FOLDER
166 son_file
= request
.files
['file']
167 # generate a uuid to reference this package
168 service_uuid
= str(uuid
.uuid4())
169 file_hash
= hashlib
.sha1(str(son_file
)).hexdigest()
170 # ensure that upload folder exists
171 ensure_dir(UPLOAD_FOLDER
)
172 upload_path
= os
.path
.join(UPLOAD_FOLDER
, "%s.son" % service_uuid
)
173 # store *.son file to disk
174 son_file
.save(upload_path
)
175 size
= os
.path
.getsize(upload_path
)
176 # create a service object and register it
177 s
= Service(service_uuid
, file_hash
, upload_path
)
178 GK
.register_service_package(service_uuid
, s
)
179 # generate the JSON result
180 return {"service_uuid": service_uuid
, "size": size
, "sha1": file_hash
, "error": None}
181 except Exception as ex
:
182 LOG
.exception("Service package upload failed:")
183 return {"service_uuid": None, "size": 0, "sha1": None, "error": "upload failed"}
187 Return a list of UUID's of uploaded service packages.
190 return {"service_uuid_list": list(GK
.services
.iterkeys())}
193 class Instantiations(fr
.Resource
):
197 Instantiate a service specified by its UUID.
198 Will return a new UUID to identify the running service instance.
201 # TODO implement method (start real service)
202 json_data
= request
.get_json(force
=True)
203 service_uuid
= json_data
.get("service_uuid")
204 if service_uuid
is not None:
205 service_instance_uuid
= str(uuid
.uuid4())
206 LOG
.info("Starting service %r" % service_uuid
)
207 return {"service_instance_uuid": service_instance_uuid
}
212 Returns a list of UUIDs containing all running services.
215 # TODO implement method
216 return {"service_instance_uuid_list": list()}
219 # create a single, global GK object
222 app
= Flask(__name__
)
223 app
.config
['MAX_CONTENT_LENGTH'] = 512 * 1024 * 1024 # 512 MB max upload
226 api
.add_resource(Packages
, '/api/packages')
227 api
.add_resource(Instantiations
, '/api/instantiations')
230 def start_rest_api(host
, port
):
231 # start the Flask server (not the best performance but ok for our use case)
235 use_reloader
=False # this is needed to run Flask in a non-main thread
239 def ensure_dir(name
):
240 if not os
.path
.exists(name
):
245 with
open(path
, "r") as f
:
248 except yaml
.YAMLError
as exc
:
249 LOG
.exception("YAML parse error")
254 def make_relative_path(path
):
255 if path
.startswith("/"):
256 return path
.replace("/", "", 1)
260 def helper_map_docker_name(name
):
262 Quick hack to fix missing dependency in example package.
264 # TODO remove this when package description is fixed
266 "/docker_files/iperf/Dockerfile": "iperf_docker",
267 "/docker_files/firewall/Dockerfile": "fw_docker",
268 "/docker_files/tcpdump/Dockerfile": "tcpdump_docker"
270 return mapping
.get(name
)
273 if __name__
== '__main__':
275 Lets allow to run the API in standalone mode.
277 logging
.getLogger("werkzeug").setLevel(logging
.INFO
)
278 start_rest_api("0.0.0.0", 8000)