+
# Copyright (c) 2018 SONATA-NFV, 5GTANGO and Paderborn University
# ALL RIGHTS RESERVED.
#
import zipfile
import yaml
import threading
+import datetime
from docker import DockerClient
from flask import Flask, request
import flask_restful as fr
MULTI_INSTANCE_PORT_OFFSET = 1000
+# Selected Placement Algorithm: Points to the class of the selected
+# placement algorithm.
+PLACEMENT_ALGORITHM_OBJ = None
+
+
class OnBoardingException(BaseException):
pass
self.remote_docker_image_urls = dict()
self.instances = dict()
self._instance_counter = 0
+ self.created_at = str(datetime.datetime.now())
def onboard(self):
"""
self.instances[instance_uuid]["ssiid"] = self._instance_counter
self.instances[instance_uuid]["name"] = get_triple_id(self.nsd)
self.instances[instance_uuid]["vnf_instances"] = list()
+ self.instances[instance_uuid]["created_at"] = str(datetime.datetime.now())
# increase for next instance
self._instance_counter += 1
- # 2. compute placement of this service instance (adds DC names to
- # VNFDs)
- # self._calculate_placement(FirstDcPlacement)
- self._calculate_placement(RoundRobinDcPlacement)
# 3. start all vnfds that we have in the service
for vnf_id in self.vnfds:
vnfd = self.vnfds[vnf_id]
raise Exception("No image name for %r found. Abort." % vnf_container_name)
docker_image_name = self.remote_docker_image_urls.get(vnf_container_name)
# 2. select datacenter to start the VNF in
- target_dc = vnfd.get("dc")
+ target_dc = self._place(vnfd, vnf_id, u, ssiid)
# 3. perform some checks to ensure we can start the container
assert(docker_image_name is not None)
assert(target_dc is not None)
# 6. Start the container
LOG.info("Starting %r as %r in DC %r" %
- (vnf_name, vnf_container_instance_name, vnfd.get("dc")))
+ (vnf_name, vnf_container_instance_name, target_dc))
LOG.debug("Interfaces for %r: %r" % (vnf_id, intfs))
# start the container
vnfi = target_dc.startCompute(
"""
return len(DockerClient().images.list(name=image_name)) > 0
- def _calculate_placement(self, algorithm):
+ def _place(self, vnfd, vnfid, vdu, ssiid):
"""
- Do placement by adding the a field "dc" to
- each VNFD that points to one of our
- data center objects known to the gatekeeper.
+ Do placement. Return the name of the DC to place
+ the given VDU.
"""
assert(len(self.vnfds) > 0)
assert(len(GK.dcs) > 0)
- # instantiate algorithm an place
- p = algorithm()
- p.place(self.nsd, self.vnfds, GK.dcs)
- LOG.info("Using placement algorithm: %r" % p.__class__.__name__)
- # lets print the placement result
- for name, vnfd in self.vnfds.iteritems():
- LOG.info("Placed VNF %r on DC %r" % (name, str(vnfd.get("dc"))))
+ if PLACEMENT_ALGORITHM_OBJ is None:
+ LOG.error("No placement algorithm given. Using FirstDcPlacement!")
+ p = FirstDcPlacement()
+ else:
+ p = PLACEMENT_ALGORITHM_OBJ
+ cname = get_container_name(vnfid, vdu.get("id"), ssiid)
+ rdc = p.place(GK.dcs, vnfd, vnfid, vdu, ssiid, cname)
+ LOG.info("Placement: '{}' --> '{}'".format(cname, rdc))
+ return rdc
def _calculate_cpu_cfs_values(self, cpu_time_percentage):
"""
Placement: Always use one and the same data center from the GK.dcs dict.
"""
- def place(self, nsd, vnfds, dcs):
- for id, vnfd in vnfds.iteritems():
- vnfd["dc"] = list(dcs.itervalues())[0]
+ def place(self, dcs, vnfd, vnfid, vdu, ssiid, cname):
+ return list(dcs.itervalues())[0]
class RoundRobinDcPlacement(object):
Placement: Distribute VNFs across all available DCs in a round robin fashion.
"""
- def place(self, nsd, vnfds, dcs):
- c = 0
+ def __init__(self):
+ self.count = 0
+
+ def place(self, dcs, vnfd, vnfid, vdu, ssiid, cname):
dcs_list = list(dcs.itervalues())
- for id, vnfd in vnfds.iteritems():
- vnfd["dc"] = dcs_list[c % len(dcs_list)]
- c += 1 # inc. c to use next DC
+ rdc = dcs_list[self.count % len(dcs_list)]
+ self.count += 1 # inc. count to use next DC
+ return rdc
+
+
+class StaticConfigPlacement(object):
+ """
+ Placement: Fixed assignment based on config file.
+ """
+
+ def __init__(self, path=None):
+ if path is None:
+ path = "static_placement.yml"
+ path = os.path.expanduser(path)
+ self.static_placement = dict()
+ try:
+ self.static_placement = load_yaml(path)
+ except BaseException as ex:
+ LOG.error(ex)
+ LOG.error("Couldn't load placement from {}"
+ .format(path))
+ LOG.info("Loaded static placement: {}"
+ .format(self.static_placement))
+
+ def place(self, dcs, vnfd, vnfid, vdu, ssiid, cname):
+ # check for container name entry
+ if cname not in self.static_placement:
+ LOG.error("Coudn't find {} in placement".format(cname))
+ LOG.error("Using first DC as fallback!")
+ return list(dcs.itervalues())[0]
+ # lookup
+ candidate_dc = self.static_placement.get(cname)
+ # check if DC exsits
+ if candidate_dc not in dcs:
+ LOG.error("Coudn't find DC {}".format(candidate_dc))
+ LOG.error("Using first DC as fallback!")
+ return list(dcs.itervalues())[0]
+ # return correct DC
+ return dcs.get(candidate_dc)
"""
def get(self):
"""
- Return a list of UUID's of uploaded service packages.
- :return: dict/list
+ Return a list of package descriptor headers.
+ Fakes the behavior of 5GTANGO's GK API to be
+ compatible with tng-cli.
+ :return: list
"""
LOG.info("GET /packages")
- return {"service_uuid_list": list(GK.services.iterkeys())}
+ result = list()
+ for suuid, sobj in GK.services.iteritems():
+ pkg = dict()
+ pkg["pd"] = dict()
+ pkg["uuid"] = suuid
+ pkg["pd"]["name"] = sobj.manifest.get("name")
+ pkg["pd"]["version"] = sobj.manifest.get("version")
+ pkg["created_at"] = sobj.created_at
+ result.append(pkg)
+ return result, 200
+
+
+class Services(fr.Resource):
+
+ def get(self):
+ """
+ Return a list of services.
+ Fakes the behavior of 5GTANGO's GK API to be
+ compatible with tng-cli.
+ :return: list
+ """
+ LOG.info("GET /services")
+ result = list()
+ for suuid, sobj in GK.services.iteritems():
+ service = dict()
+ service["nsd"] = dict()
+ service["uuid"] = suuid
+ service["nsd"]["name"] = sobj.nsd.get("name")
+ service["nsd"]["version"] = sobj.nsd.get("version")
+ service["created_at"] = sobj.created_at
+ result.append(service)
+ return result, 200
class Instantiations(fr.Resource):
json_data = request.get_json(force=True)
service_uuid = json_data.get("service_uuid")
service_name = json_data.get("service_name")
-
+ if service_name is None:
+ # lets be fuzzy
+ service_name = service_uuid
# first try to find by service_name
if service_name is not None:
for s_uuid, s in GK.services.iteritems():
# ok, we have a service uuid, lets start the service
service_instance_uuid = GK.services.get(
service_uuid).start_service()
- return {"service_instance_uuid": service_instance_uuid}, 201
+ # multiple ID fields to be compatible with tng-bench and tng-cli
+ return ({"service_instance_uuid": service_instance_uuid,
+ "id": service_instance_uuid}, 201)
+ LOG.error("Service not found: {}/{}".format(service_uuid, service_name))
return "Service not found", 404
def get(self):
Returns a list of UUIDs containing all running services.
:return: dict / list
"""
- LOG.info("GET /instantiations")
- return {"service_instantiations_list": [
- list(s.instances.iterkeys()) for s in GK.services.itervalues()]}
+ LOG.info("GET /instantiations or /api/v3/records/services")
+ # return {"service_instantiations_list": [
+ # list(s.instances.iterkeys()) for s in GK.services.itervalues()]}
+ result = list()
+ for suuid, sobj in GK.services.iteritems():
+ for iuuid, iobj in sobj.instances.iteritems():
+ inst = dict()
+ inst["uuid"] = iobj.get("uuid")
+ inst["instance_name"] = "{}-inst.{}".format(
+ iobj.get("name"), iobj.get("ssiid"))
+ inst["status"] = "running"
+ inst["created_at"] = iobj.get("created_at")
+ result.append(inst)
+ return result, 200
def delete(self):
"""
app.config['MAX_CONTENT_LENGTH'] = 512 * 1024 * 1024 # 512 MB max upload
api = fr.Api(app)
# define endpoints
-api.add_resource(Packages, '/packages', '/api/v2/packages')
+api.add_resource(Packages, '/packages', '/api/v2/packages', '/api/v3/packages')
+api.add_resource(Services, '/services', '/api/v2/services', '/api/v3/services')
api.add_resource(Instantiations, '/instantiations',
- '/api/v2/instantiations', '/api/v2/requests')
+ '/api/v2/instantiations', '/api/v2/requests', '/api/v3/requests',
+ '/api/v3/records/services')
api.add_resource(Exit, '/emulator/exit')