Merge remote-tracking branch 'upstream/master'
[osm/vim-emu.git] / src / emuvim / api / sonata / dummygatekeeper.py
index 55191b2..5936d94 100755 (executable)
@@ -38,6 +38,7 @@ import uuid
 import hashlib
 import zipfile
 import yaml
+import threading
 from docker import Client as DockerClient
 from flask import Flask, request
 import flask_restful as fr
@@ -63,13 +64,18 @@ GK_STANDALONE_MODE = False
 FORCE_PULL = False
 
 # Automatically deploy SAPs (endpoints) of the service as new containers
+# Attention: This is not a configuration switch but a global variable! Don't change its default value.
 DEPLOY_SAP = False
 
+# flag to indicate if we use bidirectional forwarding rules in the automatic chaining process
+BIDIRECTIONAL_CHAIN = False
+
 class Gatekeeper(object):
 
     def __init__(self):
         self.services = dict()
         self.dcs = dict()
+        self.net = None
         self.vnf_counter = 0  # used to generate short names for VNFs (Mininet limitation)
         LOG.info("Create SONATA dummy gatekeeper.")
 
@@ -115,7 +121,6 @@ class Service(object):
         self.eline_subnets_src = generate_subnet_strings(50, start=200, subnet_size=24, ip=1)
         self.eline_subnets_dst = generate_subnet_strings(50, start=200, subnet_size=24, ip=2)
 
-
     def onboard(self):
         """
         Do all steps to prepare this service to be instantiated
@@ -162,7 +167,8 @@ class Service(object):
 
         # 3. compute placement of this service instance (adds DC names to VNFDs)
         if not GK_STANDALONE_MODE:
-            self._calculate_placement(FirstDcPlacement)
+            #self._calculate_placement(FirstDcPlacement)
+            self._calculate_placement(RoundRobinDcPlacement)
         # iterate over all vnfds that we have to start
         for vnfd in self.vnfds.itervalues():
             vnfi = None
@@ -175,6 +181,9 @@ class Service(object):
         eline_fwd_links = [l for l in vlinks if (l["id"] in fwd_links) and (l["connectivity_type"] == "E-Line")]
         elan_fwd_links = [l for l in vlinks if (l["id"] in fwd_links) and (l["connectivity_type"] == "E-LAN")]
 
+        GK.net.deployed_elines.extend(eline_fwd_links)
+        GK.net.deployed_elans.extend(elan_fwd_links)
+
         # 4a. deploy E-Line links
         # cookie is used as identifier for the flowrules installed by the dummygatekeeper
         # eg. different services get a unique cookie for their flowrules
@@ -209,7 +218,7 @@ class Service(object):
                 ret = network.setChain(
                     src_docker_name, dst_docker_name,
                     vnf_src_interface=src_if_name, vnf_dst_interface=dst_if_name,
-                    bidirectional=True, cmd="add-flow", cookie=cookie, priority=10)
+                    bidirectional=BIDIRECTIONAL_CHAIN, cmd="add-flow", cookie=cookie, priority=10)
 
                 # re-configure the VNFs IP assignment and ensure that a new subnet is used for each E-Link
                 src_vnfi = self._get_vnf_instance(instance_uuid, src_name)
@@ -222,6 +231,9 @@ class Service(object):
         # 4b. deploy E-LAN links
         base = 10
         for link in elan_fwd_links:
+
+            elan_vnf_list=[]
+
             # generate lan ip address
             ip = 1
             for intf in link["connection_points_reference"]:
@@ -230,6 +242,8 @@ class Service(object):
                 if vnf_id in self.sap_identifiers:
                     src_docker_name = "{0}_{1}".format(vnf_id, intf_name)
                     vnf_id = src_docker_name
+                else:
+                    src_docker_name = vnf_id
                 vnf_name = vnf_id2vnf_name[vnf_id]
                 LOG.debug(
                     "Setting up E-LAN link. %s(%s:%s) -> %s" % (
@@ -237,13 +251,21 @@ class Service(object):
 
                 if vnf_name in self.vnfds:
                     # re-configure the VNFs IP assignment and ensure that a new subnet is used for each E-LAN
-                    # E-LAN relies on the learning switch capability of the infrastructure switch in dockernet,
-                    # so no explicit chaining is necessary
+                    # E-LAN relies on the learning switch capability of Ryu which has to be turned on in the topology
+                    # (DCNetwork(controller=RemoteController, enable_learning=True)), so no explicit chaining is necessary.
                     vnfi = self._get_vnf_instance(instance_uuid, vnf_name)
                     if vnfi is not None:
                         self._vnf_reconfigure_network(vnfi, intf_name, ip_address)
                         # increase for the next ip address on this E-LAN
                         ip += 1
+
+                        # add this vnf and interface to the E-LAN for tagging
+                        network = self.vnfds[vnf_name].get("dc").net  # there should be a cleaner way to find the DCNetwork
+                        elan_vnf_list.append({'name':src_docker_name,'interface':intf_name})
+
+
+            # install the VLAN tags for this E-LAN
+            network.setLAN(elan_vnf_list)
             # increase the base ip address for the next E-LAN
             base += 1
 
@@ -253,6 +275,30 @@ class Service(object):
         LOG.info("Service started. Instance id: %r" % instance_uuid)
         return instance_uuid
 
+    def stop_service(self, instance_uuid):
+        """
+        This method stops a running service instance.
+        It iterates over all VNF instances, stopping them each
+        and removing them from their data center.
+
+        :param instance_uuid: the uuid of the service instance to be stopped
+        """
+        LOG.info("Stopping service %r" % self.uuid)
+        # get relevant information
+        # instance_uuid = str(self.uuid.uuid4())
+        vnf_instances = self.instances[instance_uuid]["vnf_instances"]
+
+        for v in vnf_instances:
+            self._stop_vnfi(v)
+
+        if not GK_STANDALONE_MODE:
+            # remove placement?
+            # self._remove_placement(RoundRobinPlacement)
+            None
+
+        # last step: remove the instance from the list of all instances
+        del self.instances[instance_uuid]
+
     def _start_vnfd(self, vnfd):
         """
         Start a single VNFD of this service
@@ -291,6 +337,19 @@ class Service(object):
             vnfi = target_dc.startCompute(self.vnf_name2docker_name[vnf_name], network=intfs, image=docker_name, flavor_name="small")
             return vnfi
 
+    def _stop_vnfi(self, vnfi):
+        """
+        Stop a VNF instance.
+
+        :param vnfi: vnf instance to be stopped
+        """
+        # Find the correct datacenter
+        status = vnfi.getStatus()
+        dc = vnfi.datacenter
+        # stop the vnfi
+        LOG.info("Stopping the vnf instance contained in %r in DC %r" % (status["name"], dc))
+        dc.stopCompute(status["name"])
+
     def _get_vnf_instance(self, instance_uuid, name):
         """
         Returns the Docker object for the given VNF name (or Docker name).
@@ -332,8 +391,11 @@ class Service(object):
             for env_var in env:
                 if "SON_EMU_CMD=" in env_var:
                     cmd = str(env_var.split("=")[1])
-                    LOG.info("Executing entrypoint script in %r: %r" % (vnfi.name, cmd))
-                    vnfi.cmdPrint(cmd)
+                    LOG.info("Executing entry point script in %r: %r" % (vnfi.name, cmd))
+                    # execute command in new thread to ensure that GK is not blocked by VNF
+                    t = threading.Thread(target=vnfi.cmdPrint, args=(cmd,))
+                    t.daemon = True
+                    t.start()
 
     def _unpack_service_package(self):
         """
@@ -363,6 +425,7 @@ class Service(object):
                 self.package_content_path,
                 make_relative_path(self.manifest.get("entry_service_template")))
             self.nsd = load_yaml(nsd_path)
+            GK.net.deployed_nsds.append(self.nsd)
             LOG.debug("Loaded NSD: %r" % self.nsd.get("name"))
 
     def _load_vnfd(self):
@@ -389,7 +452,7 @@ class Service(object):
             # set of the connection_point ids found in the nsd (in the examples this is 'ns')
             self.sap_identifiers.add(sap_vnf_id)
 
-            sap_docker_name = sap.replace(':', '_')
+            sap_docker_name = "%s_%s" % (sap_vnf_id, sap_vnf_interface)
 
             # add SAP to self.vnfds
             sapfile = pkg_resources.resource_filename(__name__, "sap_vnfd.yml")
@@ -497,6 +560,20 @@ class FirstDcPlacement(object):
             vnfd["dc"] = 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
+        dcs_list = list(dcs.itervalues())
+        for name, vnfd in vnfds.iteritems():
+            vnfd["dc"] = dcs_list[c % len(dcs_list)]
+            c += 1  # inc. c to use next DC
+
+
+
+
 """
 Resource definitions and API endpoints
 """
@@ -534,7 +611,7 @@ class Packages(fr.Resource):
             s = Service(service_uuid, file_hash, upload_path)
             GK.register_service_package(service_uuid, s)
             # generate the JSON result
-            return {"service_uuid": service_uuid, "size": size, "sha1": file_hash, "error": None}
+            return {"service_uuid": service_uuid, "size": size, "sha1": file_hash, "error": None}, 201
         except Exception as ex:
             LOG.exception("Service package upload failed:")
             return {"service_uuid": None, "size": 0, "sha1": None, "error": "upload failed"}, 500
@@ -568,7 +645,7 @@ class Instantiations(fr.Resource):
         if service_uuid in GK.services:
             # 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}
+            return {"service_instance_uuid": service_instance_uuid}, 201
         return "Service not found", 404
 
     def get(self):
@@ -580,9 +657,47 @@ class Instantiations(fr.Resource):
         return {"service_instantiations_list": [
             list(s.instances.iterkeys()) for s in GK.services.itervalues()]}
 
+    def delete(self):
+        """
+        Stops a running service specified by its service and instance UUID.
+        """
+        # try to extract the service  and instance UUID from the request
+        json_data = request.get_json(force=True)
+        service_uuid = json_data.get("service_uuid")
+        instance_uuid = json_data.get("service_instance_uuid")
+
+        # try to be fuzzy
+        if service_uuid is None and len(GK.services) > 0:
+            #if we don't get a service uuid, we simply stop the last service in the list
+            service_uuid = list(GK.services.iterkeys())[0]
+        if instance_uuid is None and len(GK.services[service_uuid].instances) > 0:
+            instance_uuid = list(GK.services[service_uuid].instances.iterkeys())[0]
+
+        if service_uuid in GK.services and instance_uuid in GK.services[service_uuid].instances:
+            # valid service and instance UUID, stop service
+            GK.services.get(service_uuid).stop_service(instance_uuid)
+            del GK.services.get(service_uuid).instances[instance_uuid]
+            return
+        return "Service not found", 404
+
+class Exit(fr.Resource):
+
+    def put(self):
+        """
+        Stop the running Containernet instance regardless of data transmitted
+        """
+        GK.net.stop()
+
+
+def initialize_GK():
+    global GK
+    GK = Gatekeeper()
+
+
 
 # create a single, global GK object
-GK = Gatekeeper()
+GK = None
+initialize_GK()
 # setup Flask
 app = Flask(__name__)
 app.config['MAX_CONTENT_LENGTH'] = 512 * 1024 * 1024  # 512 MB max upload
@@ -590,10 +705,17 @@ api = fr.Api(app)
 # define endpoints
 api.add_resource(Packages, '/packages')
 api.add_resource(Instantiations, '/instantiations')
+api.add_resource(Exit, '/emulator/exit')
+
+
+#def initialize_GK():
+#    global GK
+#    GK = Gatekeeper()
 
 
 def start_rest_api(host, port, datacenters=dict()):
     GK.dcs = datacenters
+    GK.net = get_dc_network()
     # start the Flask server (not the best performance but ok for our use case)
     app.run(host=host,
             port=port,
@@ -642,6 +764,14 @@ def generate_subnet_strings(n, start=1, subnet_size=24, ip=0):
         r.append("%d.0.0.%d/%d" % (i, ip, subnet_size))
     return r
 
+def get_dc_network():
+    """
+    retrieve the DCnetwork where this dummygatekeeper (GK) connects to.
+    Assume at least 1 datacenter is connected to this GK, and that all datacenters belong to the same DCNetwork
+    :return:
+    """
+    assert (len(GK.dcs) > 0)
+    return GK.dcs.values()[0].net
 
 if __name__ == '__main__':
     """