Manually added OpenStack API code
[osm/vim-emu.git] / src / emuvim / api / openstack / chain_api.py
diff --git a/src/emuvim/api/openstack/chain_api.py b/src/emuvim/api/openstack/chain_api.py
new file mode 100644 (file)
index 0000000..7c65e56
--- /dev/null
@@ -0,0 +1,842 @@
+import json
+import logging
+import copy
+
+from mininet.node import OVSSwitch
+
+from flask import Flask
+from flask import Response, request
+from flask_restful import Api, Resource
+from mininet.link import Link
+import uuid
+
+
+class ChainApi(Resource):
+    """
+    The chain API is a component that is not used in OpenStack.
+    It is a custom built REST API that can be used to create network chains and loadbalancers.
+    """
+
+    def __init__(self, inc_ip, inc_port, manage):
+        # setup Flask
+        self.app = Flask(__name__)
+        self.api = Api(self.app)
+        self.ip = inc_ip
+        self.port = inc_port
+        self.manage = manage
+        self.playbook_file = '/tmp/son-emu-requests.log'
+        self.api.add_resource(ChainVersionsList, "/",
+                              resource_class_kwargs={'api': self})
+        self.api.add_resource(ChainList, "/v1/chain/list",
+                              resource_class_kwargs={'api': self})
+        self.api.add_resource(ChainVnfInterfaces, "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>",
+                              resource_class_kwargs={'api': self})
+        self.api.add_resource(ChainVnfDcStackInterfaces,
+                              "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>",
+                              resource_class_kwargs={'api': self})
+        self.api.add_resource(BalanceHostList, "/v1/lb/list",
+                              resource_class_kwargs={'api': self})
+        self.api.add_resource(BalanceHost, "/v1/lb/<vnf_src_name>/<vnf_src_interface>",
+                              resource_class_kwargs={'api': self})
+        self.api.add_resource(BalanceHostDcStack, "/v1/lb/<src_dc>/<src_stack>/<vnf_src_name>/<vnf_src_interface>",
+                              resource_class_kwargs={'api': self})
+        self.api.add_resource(QueryTopology, "/v1/topo",
+                              resource_class_kwargs={'api': self})
+        self.api.add_resource(Shutdown, "/shutdown")
+
+        @self.app.after_request
+        def add_access_control_header(response):
+            response.headers['Access-Control-Allow-Origin'] = '*'
+            return response
+
+    def _start_flask(self):
+        logging.info("Starting %s endpoint @ http://%s:%d" % ("ChainDummyApi", self.ip, self.port))
+        if self.app is not None:
+            self.app.before_request(self.dump_playbook)
+            self.app.run(self.ip, self.port, debug=True, use_reloader=False)
+
+    def dump_playbook(self):
+        with self.manage.lock:
+            with open(self.playbook_file, 'a') as logfile:
+                if len(request.data) > 0:
+                    data = "# CHAIN API\n"
+                    data += "curl -X {type} -H \"Content-type: application/json\" -d '{data}' {url}".format(type=request.method,
+                                                                                            data=request.data,
+                                                                                            url=request.url)
+                    logfile.write(data + "\n")
+
+
+class Shutdown(Resource):
+    def get(self):
+        logging.debug(("%s is beeing shut down") % (__name__))
+        func = request.environ.get('werkzeug.server.shutdown')
+        if func is None:
+            raise RuntimeError('Not running with the Werkzeug Server')
+        func()
+
+
+class ChainVersionsList(Resource):
+    '''
+    Entrypoint to find versions of the chain api.
+    '''
+
+    def __init__(self, api):
+        self.api = api
+
+    def get(self):
+        '''
+        :return: flask.Response containing the openstack like description of the chain api
+        '''
+        # at least let it look like an open stack function
+        try:
+            resp = """
+                {
+                    "versions": [
+                        {
+                            "id": "v1",
+                            "links": [
+                                {
+                                    "href": "http://%s:%d/v1/",
+                                    "rel": "self"
+                                }
+                            ],
+                            "status": "CURRENT",
+                            "version": "1",
+                            "min_version": "1",
+                            "updated": "2013-07-23T11:33:21Z"
+                        }
+                    ]
+                }
+            """ % (self.api.ip, self.api.port)
+
+            return Response(resp, status=200, mimetype="application/json")
+
+        except Exception as ex:
+            logging.exception(u"%s: Could not show list of versions." % __name__)
+            return ex.message, 500
+
+
+class ChainList(Resource):
+    '''
+    Will retrieve all chains including their paths.
+    '''
+
+    def __init__(self, api):
+        self.api = api
+
+    def get(self):
+        '''
+        :return: flask.Response containing all live chains
+        '''
+        # at least let it look like an open stack function
+        try:
+            resp = {"chains": list()}
+
+            for chain in self.api.manage.full_chain_data.values():
+                resp["chains"].append(chain)
+
+            return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+        except Exception as ex:
+            logging.exception(u"%s: Could not list all network chains." % __name__)
+            return ex.message, 500
+
+
+class BalanceHostList(Resource):
+    '''
+    Will retrieve all loadbalance rules including their paths.
+    '''
+
+    def __init__(self, api):
+        self.api = api
+
+    def get(self):
+        '''
+        :return: flask.Response containing all live loadbalancer rules
+        '''
+        # at least let it look like an open stack function
+        try:
+            resp = {"loadbalancers": list()}
+
+            for lb in self.api.manage.full_lb_data.values():
+                resp["loadbalancers"].append(lb)
+
+            return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+        except Exception as ex:
+            logging.exception(u"%s: Could not list all live loadbalancers." % __name__)
+            return ex.message, 500
+
+
+class ChainVnfInterfaces(Resource):
+    """
+    Handles requests targeted at: "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>"
+    Requests are for tearing down or setting up a chain between two vnfs
+    """
+
+    def __init__(self, api):
+        self.api = api
+
+    def put(self, src_vnf, src_intfs, dst_vnf, dst_intfs):
+        """
+        A put request to "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>"
+        will create a chain between two interfaces at the specified vnfs.
+
+        Note:
+           Does not allow a custom path. Uses ``.post``
+           Internally just makes a POST request with no POST data!
+
+        :param src_vnf: Name of the source VNF
+        :type src_vnf: ``str``
+        :param src_intfs: Name of the source VNF interface to chain on
+        :type src_intfs: ``str``
+        :param dst_vnf: Name of the destination VNF
+        :type dst_vnf: ``str``
+        :param dst_intfs: Name of the destination VNF interface to chain on
+        :type dst_intfs: ``str``
+        :return: flask.Response 200 if set up correctly else 500 also returns the cookie as dict {'cookie': value}
+         501 if one of the VNF / intfs does not exist
+        :rtype: :class:`flask.Response`
+        """
+        return self.post(src_vnf, src_intfs, dst_vnf, dst_intfs)
+
+    def post(self, src_vnf, src_intfs, dst_vnf, dst_intfs):
+        """
+         A post request to "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>"
+         will create a chain between two interfaces at the specified vnfs.
+         The POST data contains the path like this.
+         { "path": ["dc1.s1", "s1", "dc4.s1"]}
+         path specifies the destination vnf and interface and contains a list of switches
+         that the path traverses. The path may not contain single hop loops like:
+         [s1, s2, s1].
+         This is a limitation of Ryu, as Ryu does not allow the `INPUT_PORT` action!
+
+        :param src_vnf: Name of the source VNF
+        :type src_vnf: ``str``
+        :param src_intfs: Name of the source VNF interface to chain on
+        :type src_intfs: ``str``
+        :param dst_vnf: Name of the destination VNF
+        :type dst_vnf: ``str``
+        :param dst_intfs: Name of the destination VNF interface to chain on
+        :type dst_intfs: ``str``
+        :return: flask.Response 200 if set up correctly else 500 also returns the cookie as dict {'cookie': value}
+         501 if one of the VNF / intfs does not exist
+        :rtype: :class:`flask.Response`
+
+        """
+
+        if request.is_json:
+            path = request.json.get('path')
+            layer2 = request.json.get('layer2', True)
+        else:
+            path = None
+            layer2 = True
+
+        # check if both VNFs exist
+        if not self.api.manage.check_vnf_intf_pair(src_vnf, src_intfs):
+            return Response(u"VNF %s or intfs %s does not exist" % (src_vnf, src_intfs), status=501,
+                            mimetype="application/json")
+        if not self.api.manage.check_vnf_intf_pair(dst_vnf, dst_intfs):
+            return Response(u"VNF %s or intfs %s does not exist" % (dst_vnf, dst_intfs), status=501,
+                            mimetype="application/json")
+        try:
+            cookie = self.api.manage.network_action_start(src_vnf, dst_vnf, vnf_src_interface=src_intfs,
+                                                          vnf_dst_interface=dst_intfs, bidirectional=True,
+                                                          path=path, layer2=layer2)
+            resp = {'cookie': cookie}
+            return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+        except Exception as e:
+            logging.exception(u"%s: Error setting up the chain.\n %s" % (__name__, e))
+            return Response(u"Error setting up the chain", status=500, mimetype="application/json")
+
+    def delete(self, src_vnf, src_intfs, dst_vnf, dst_intfs):
+        """
+        A DELETE request to "/v1/chain/<src_vnf>/<src_intfs>/<dst_vnf>/<dst_intfs>"
+        will delete a previously created chain.
+
+        :param src_vnf: Name of the source VNF
+        :type src_vnf: ``str``
+        :param src_intfs: Name of the source VNF interface to chain on
+        :type src_intfs: ``str``
+        :param dst_vnf: Name of the destination VNF
+        :type dst_vnf: ``str``
+        :param dst_intfs: Name of the destination VNF interface to chain on
+        :type dst_intfs: ``str``
+        :return: flask.Response 200 if set up correctly else 500\
+         also returns the cookie as dict {'cookie': value}
+         501 if one of the VNF / intfs does not exist
+        :rtype: :class:`flask.Response`
+
+        """
+        # check if both VNFs exist
+        # check if both VNFs exist
+        if not self.api.manage.check_vnf_intf_pair(src_vnf, src_intfs):
+            return Response(u"VNF %s or intfs %s does not exist" % (src_vnf, src_intfs), status=501,
+                            mimetype="application/json")
+        if not self.api.manage.check_vnf_intf_pair(dst_vnf, dst_intfs):
+            return Response(u"VNF %s or intfs %s does not exist" % (dst_vnf, dst_intfs), status=501,
+                            mimetype="application/json")
+        try:
+            cookie = self.api.manage.network_action_stop(src_vnf, dst_vnf, vnf_src_interface=src_intfs,
+                                                         vnf_dst_interface=dst_intfs, bidirectional=True)
+            return Response(json.dumps(cookie), status=200, mimetype="application/json")
+        except Exception as e:
+            logging.exception(u"%s: Error deleting the chain.\n %s" % (__name__, e))
+            return Response(u"Error deleting the chain", status=500, mimetype="application/json")
+
+
+class ChainVnfDcStackInterfaces(Resource):
+    '''
+    Handles requests targeted at: "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>"
+    Handles tearing down or setting up a chain between two vnfs for stacks.
+    '''
+
+    def __init__(self, api):
+        self.api = api
+
+    def put(self, src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs):
+        """
+        A PUT request to "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>"
+        will set up chain.
+
+        :Note: PUT Requests can not set up custom paths!
+
+        :param src_dc: Name of the source datacenter
+        :type src_dc: `str`
+        :param src_stack: Name of the source stack
+        :type src_stack: `str`
+        :param src_vnf: Name of the source VNF
+        :type src_vnf: ``str``
+        :param src_intfs: Name of the source VNF interface to chain on
+        :type src_intfs: ``str``
+        :param dst_dc: Name of the destination datacenter
+        :type dst_dc: ``str``
+        :param dst_stack: Name of the destination stack
+        :type dst_stack: ``str``
+        :param dst_vnf: Name of the destination VNF
+        :type dst_vnf: ``str``
+        :param dst_intfs: Name of the destination VNF interface to chain on
+        :type dst_intfs: ``str``
+        :return: flask.Response 200 if set up correctly else 500\
+         also returns the cookie as dict {'cookie': value}
+         501 if VNF or intfs does not exist
+        :rtype: :class:`flask.Response`
+
+        """
+        # search for real names
+        real_names = self._findNames(src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs)
+        if type(real_names) is not tuple:
+            # something went wrong
+            return real_names
+
+        container_src, container_dst, interface_src, interface_dst = real_names
+
+        # check if both VNFs exist
+        if not self.api.manage.check_vnf_intf_pair(container_src, interface_src):
+            return Response(u"VNF %s or intfs %s does not exist" % (container_src, interface_src), status=501,
+                            mimetype="application/json")
+        if not self.api.manage.check_vnf_intf_pair(container_dst, interface_dst):
+            return Response(u"VNF %s or intfs %s does not exist" % (container_dst, interface_dst), status=501,
+                            mimetype="application/json")
+
+        try:
+            cookie = self.api.manage.network_action_start(container_src, container_dst, vnf_src_interface=interface_src,
+                                                          vnf_dst_interface=interface_dst, bidirectional=True,
+                                                          layer2=True)
+            resp = {'cookie': cookie}
+            return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+        except Exception as e:
+            logging.exception(u"%s: Error setting up the chain.\n %s" % (__name__, e))
+            return Response(u"Error setting up the chain", status=500, mimetype="application/json")
+
+    def post(self, src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs):
+        """
+         A post request to "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>"
+         will create a chain between two interfaces at the specified vnfs.
+         The POST data contains the path like this.
+         { "path": ["dc1.s1", "s1", "dc4.s1"]}
+         path specifies the destination vnf and interface and contains a list of switches
+         that the path traverses. The path may not contain single hop loops like:
+         [s1, s2, s1].
+         This is a limitation of Ryu, as Ryu does not allow the `INPUT_PORT` action!
+
+        :param src_vnf: Name of the source VNF
+        :type src_vnf: ``str``
+        :param src_intfs: Name of the source VNF interface to chain on
+        :type src_intfs: ``str``
+        :param dst_vnf: Name of the destination VNF
+        :type dst_vnf: ``str``
+        :param dst_intfs: Name of the destination VNF interface to chain on
+        :type dst_intfs: ``str``
+        :return: flask.Response 200 if set up correctly else 500 also returns the cookie as dict {'cookie': value}
+         501 if vnf / intfs do not exist
+        :rtype: :class:`flask.Response`
+
+        """
+        if request.is_json:
+            path = request.json.get('path')
+            layer2 = request.json.get('layer2', True)
+        else:
+            path = None
+            layer2 = True
+
+        # search for real names
+        real_names = self._findNames(src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs)
+        if type(real_names) is not tuple:
+            # something went wrong
+            return real_names
+
+        container_src, container_dst, interface_src, interface_dst = real_names
+
+        try:
+            cookie = self.api.manage.network_action_start(container_src, container_dst, vnf_src_interface=interface_src,
+                                                          vnf_dst_interface=interface_dst, bidirectional=True,
+                                                          path=path, layer2=layer2)
+            resp = {'cookie': cookie}
+            return Response(json.dumps(resp), status=200, mimetype="application/json")
+
+        except Exception as e:
+            logging.exception(u"%s: Error setting up the chain.\n %s" % (__name__, e))
+            return Response(u"Error setting up the chain", status=500, mimetype="application/json")
+
+    def delete(self, src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs):
+        """
+        A DELETE request to "/v1/chain/<src_dc>/<src_stack>/<src_vnf>/<src_intfs>/<dst_dc>/<dst_stack>/<dst_vnf>/<dst_intfs>"
+        will delete a previously created chain.
+
+        :param src_dc: Name of the source datacenter
+        :type src_dc: `str`
+        :param src_stack: Name of the source stack
+        :type src_stack: `str`
+        :param src_vnf: Name of the source VNF
+        :type src_vnf: ``str``
+        :param src_intfs: Name of the source VNF interface to chain on
+        :type src_intfs: ``str``
+        :param dst_dc: Name of the destination datacenter
+        :type dst_dc: ``str``
+        :param dst_stack: Name of the destination stack
+        :type dst_stack: ``str``
+        :param dst_vnf: Name of the destination VNF
+        :type dst_vnf: ``str``
+        :param dst_intfs: Name of the destination VNF interface to chain on
+        :type dst_intfs: ``str``
+        :return: flask.Response 200 if set up correctly else 500\
+         also returns the cookie as dict {'cookie': value}
+         501 if one of the VNF / intfs does not exist
+        :rtype: :class:`flask.Response`
+
+        """
+        # search for real names
+        real_names = self._findNames(src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs)
+        if type(real_names) is not tuple:
+            # something went wrong, real_names is a Response object
+            return real_names
+
+        container_src, container_dst, interface_src, interface_dst = real_names
+
+        try:
+            cookie = self.api.manage.network_action_stop(container_src, container_dst, vnf_src_interface=interface_src,
+                                                         vnf_dst_interface=interface_dst, bidirectional=True)
+            return Response(json.dumps(cookie), status=200, mimetype="application/json")
+        except Exception as e:
+            logging.exception(u"%s: Error deleting the chain.\n %s" % (__name__, e))
+            return Response(u"Error deleting the chain", status=500, mimetype="application/json")
+
+    # Tries to find real container and interface names according to heat template names
+    # Returns a tuple of 4 or a Response object
+    def _findNames(self, src_dc, src_stack, src_vnf, src_intfs, dst_dc, dst_stack, dst_vnf, dst_intfs):
+        # search for datacenters
+        if src_dc not in self.api.manage.net.dcs or dst_dc not in self.api.manage.net.dcs:
+            return Response(u"At least one DC does not exist", status=500, mimetype="application/json")
+        dc_src = self.api.manage.net.dcs[src_dc]
+        dc_dst = self.api.manage.net.dcs[dst_dc]
+        # search for related OpenStackAPIs
+        api_src = None
+        api_dst = None
+        from openstack_api_endpoint import OpenstackApiEndpoint
+        for api in OpenstackApiEndpoint.dc_apis:
+            if api.compute.dc == dc_src:
+                api_src = api
+            if api.compute.dc == dc_dst:
+                api_dst = api
+        if api_src is None or api_dst is None:
+            return Response(u"At least one OpenStackAPI does not exist", status=500, mimetype="application/json")
+        # search for stacks
+        stack_src = None
+        stack_dst = None
+        for stack in api_src.compute.stacks.values():
+            if stack.stack_name == src_stack:
+                stack_src = stack
+        for stack in api_dst.compute.stacks.values():
+            if stack.stack_name == dst_stack:
+                stack_dst = stack
+        if stack_src is None or stack_dst is None:
+            return Response(u"At least one Stack does not exist", status=500, mimetype="application/json")
+        # search for servers
+        server_src = None
+        server_dst = None
+        for server in stack_src.servers.values():
+            if server.template_name == src_vnf:
+                server_src = server
+                break
+        for server in stack_dst.servers.values():
+            if server.template_name == dst_vnf:
+                server_dst = server
+                break
+        if server_src is None or server_dst is None:
+            return Response(u"At least one VNF does not exist", status=500, mimetype="application/json")
+
+        container_src = server_src.name
+        container_dst = server_dst.name
+
+        # search for ports
+        port_src = None
+        port_dst = None
+        if src_intfs in server_src.port_names:
+            port_src = stack_src.ports[src_intfs]
+        if dst_intfs in server_dst.port_names:
+            port_dst = stack_dst.ports[dst_intfs]
+        if port_src is None or port_dst is None:
+            return Response(u"At least one Port does not exist", status=500, mimetype="application/json")
+
+        interface_src = port_src.intf_name
+        interface_dst = port_dst.intf_name
+
+        return container_src, container_dst, interface_src, interface_dst
+
+
+class BalanceHostDcStack(Resource):
+    """
+    Handles requests to "/v1/lb/<src_dc>/<src_stack>/<vnf_src_name>/<vnf_src_interface>"
+    Sets up LoadBalancers for VNFs that are belonging to a certain stack.
+    """
+
+    def __init__(self, api):
+        self.api = api
+
+    def post(self, src_dc, src_stack, vnf_src_name, vnf_src_interface):
+        """
+        A POST request to "/v1/lb/<src_dc>/<src_stack>/<vnf_src_name>/<vnf_src_interface>"
+        will set up a loadbalancer. The target VNFs and interfaces are in the post data.
+
+        :Example:
+            See :class:`heat.chain_api.BalanceHost.post`
+
+        :param src_dc: Name of the source VNF
+        :type src_dc: ``str``
+        :param src_stack: Name of the source VNF interface to chain on
+        :type src_stack: ``str``
+         * src_stack == "floating" sets up a new floating node, so only use this name if you know what you are doing.
+        :param vnf_src_name:
+        :type vnf_src_name: ``str``
+        :param vnf_src_interface:
+        :type vnf_src_interface: ``str``
+        :return: flask.Response 200 if set up correctly else 500
+        :rtype: :class:`flask.Response`
+
+        """
+        try:
+            req = request.json
+            if req is None or len(req) == 0 or "dst_vnf_interfaces" not in req:
+                return Response(u"You have to specify destination vnfs via the POST data.",
+                                status=500, mimetype="application/json")
+
+            dst_vnfs = req.get('dst_vnf_interfaces')
+            container_src = None
+            interface_src = None
+
+            # check src vnf/port
+            if src_stack != "floating":
+                real_src = self._findName(src_dc, src_stack, vnf_src_name, vnf_src_interface)
+                if type(real_src) is not tuple:
+                    # something went wrong, real_src is a Response object
+                    return real_src
+
+                container_src, interface_src = real_src
+
+            real_dst_dict = {}
+            for dst_vnf in dst_vnfs:
+                dst_dc = dst_vnf.get('pop', None)
+                dst_stack = dst_vnf.get('stack', None)
+                dst_server = dst_vnf.get('server', None)
+                dst_port = dst_vnf.get('port', None)
+                if dst_dc is not None and dst_stack is not None and dst_server is not None and dst_port is not None:
+                    real_dst = self._findName(dst_dc, dst_stack, dst_server, dst_port)
+                    if type(real_dst) is not tuple:
+                        # something went wrong, real_dst is a Response object
+                        return real_dst
+                    real_dst_dict[real_dst[0]] = real_dst[1]
+
+            input_object = {"dst_vnf_interfaces": real_dst_dict, "path": req.get("path", None)}
+
+            if src_stack != "floating":
+                self.api.manage.add_loadbalancer(container_src, interface_src, lb_data=input_object)
+                return Response(u"Loadbalancer set up at %s:%s" % (container_src, interface_src),
+                                status=200, mimetype="application/json")
+            else:
+                cookie, floating_ip = self.api.manage.add_floating_lb(src_dc, lb_data=input_object)
+
+                return Response(json.dumps({"cookie": "%d" % cookie, "floating_ip": "%s" % floating_ip}),
+                                status=200, mimetype="application/json")
+
+        except Exception as e:
+            logging.exception(u"%s: Error setting up the loadbalancer at %s %s %s:%s.\n %s" %
+                              (__name__, src_dc, src_stack, vnf_src_name, vnf_src_interface, e))
+            return Response(u"%s: Error setting up the loadbalancer at %s %s %s:%s.\n %s" %
+                            (__name__, src_dc, src_stack, vnf_src_name, vnf_src_interface, e), status=500,
+                            mimetype="application/json")
+
+    def delete(self, src_dc, src_stack, vnf_src_name, vnf_src_interface):
+        """
+        Will delete a load balancer that sits behind a specified interface at a vnf for a specific stack
+
+        :param src_dc: Name of the source VNF
+        :type src_dc: ``str``
+        :param src_stack: Name of the source VNF interface to chain on
+        :type src_stack: ``str``
+        :param vnf_src_name:
+        :type vnf_src_name: ``str``
+        :param vnf_src_interface:
+        :type vnf_src_interface: ``str``
+        :return: flask.Response 200 if set up correctly else 500
+        :rtype: :class:`flask.Response`
+
+        """
+        try:
+            # check src vnf/port
+            if src_stack != "floating":
+                real_src = self._findName(src_dc, src_stack, vnf_src_name, vnf_src_interface)
+                if type(real_src) is not tuple:
+                    # something went wrong, real_src is a Response object
+                    return real_src
+
+                container_src, interface_src = real_src
+
+                self.api.manage.delete_loadbalancer(container_src, interface_src)
+                return Response(u"Loadbalancer deleted at %s:%s" % (vnf_src_name, vnf_src_interface),
+                                status=200, mimetype="application/json")
+            else:
+                cookie = vnf_src_name
+                self.api.manage.delete_floating_lb(cookie)
+                return Response(u"Floating loadbalancer with cookie %s deleted" % (cookie),
+                                status=200, mimetype="application/json")
+
+        except Exception as e:
+            logging.exception(u"%s: Error deleting the loadbalancer at %s %s %s%s.\n %s" %
+                              (__name__, src_dc, src_stack, vnf_src_name, vnf_src_interface, e))
+            return Response(u"%s: Error deleting the loadbalancer at %s %s %s%s." %
+                            (__name__, src_dc, src_stack, vnf_src_name, vnf_src_interface), status=500,
+                            mimetype="application/json")
+
+    # Tries to find real container and port name according to heat template names
+    # Returns a string or a Response object
+    def _findName(self, dc, stack, vnf, port):
+        # search for datacenters
+        if dc not in self.api.manage.net.dcs:
+            return Response(u"DC does not exist", status=500, mimetype="application/json")
+        dc_real = self.api.manage.net.dcs[dc]
+        # search for related OpenStackAPIs
+        api_real = None
+        from openstack_api_endpoint import OpenstackApiEndpoint
+        for api in OpenstackApiEndpoint.dc_apis:
+            if api.compute.dc == dc_real:
+                api_real = api
+        if api_real is None:
+            return Response(u"OpenStackAPI does not exist", status=500, mimetype="application/json")
+        # search for stacks
+        stack_real = None
+        for stackObj in api_real.compute.stacks.values():
+            if stackObj.stack_name == stack:
+                stack_real = stackObj
+        if stack_real is None:
+            return Response(u"Stack does not exist", status=500, mimetype="application/json")
+        # search for servers
+        server_real = None
+        for server in stack_real.servers.values():
+            if server.template_name == vnf:
+                server_real = server
+                break
+        if server_real is None:
+            return Response(u"VNF does not exist", status=500, mimetype="application/json")
+
+        container_real = server_real.name
+
+        # search for ports
+        port_real = None
+        if port in server_real.port_names:
+            port_real = stack_real.ports[port]
+        if port_real is None:
+            return Response(u"At least one Port does not exist", status=500, mimetype="application/json")
+
+        interface_real = port_real.intf_name
+
+        return container_real, interface_real
+
+
+class BalanceHost(Resource):
+    """
+    Handles requests at "/v1/lb/<vnf_src_name>/<vnf_src_interface>"
+    to set up or delete Load Balancers.
+    """
+
+    def __init__(self, api):
+        self.api = api
+
+    def post(self, vnf_src_name, vnf_src_interface):
+        """
+        Will set up a Load balancer behind an interface at a specified vnf
+        We need both to avoid naming conflicts as interface names are not unique
+
+        :param vnf_src_name: Name of the source VNF
+        :type vnf_src_name: ``str``
+        :param vnf_src_interface: Name of the source VNF interface to chain on
+        :type vnf_src_interface: ``str``
+        :return: flask.Response 200 if set up correctly else 500
+         501 if VNF or intfs does not exist
+        :rtype: :class:`flask.Response`
+
+        """
+        try:
+            req = request.json
+            if req is None or len(req) == 0 or "dst_vnf_interfaces" not in req:
+                return Response(u"You have to specify destination vnfs via the POST data.",
+                                status=500, mimetype="application/json")
+
+            if vnf_src_name != "floating":
+                # check if VNF exist
+                if not self.api.manage.check_vnf_intf_pair(vnf_src_name, vnf_src_interface):
+                    return Response(u"VNF %s or intfs %s does not exist" % (vnf_src_name, vnf_src_interface),
+                                    status=501,
+                                    mimetype="application/json")
+                self.api.manage.add_loadbalancer(vnf_src_name, vnf_src_interface, lb_data=req)
+
+                return Response(u"Loadbalancer set up at %s:%s" % (vnf_src_name, vnf_src_interface),
+                                status=200, mimetype="application/json")
+            else:
+                cookie, floating_ip = self.api.manage.add_floating_lb(vnf_src_interface, lb_data=req)
+
+                return Response(json.dumps({"cookie": "%d" % cookie, "floating_ip": "%s" % floating_ip}),
+                                status=200, mimetype="application/json")
+        except Exception as e:
+            logging.exception(u"%s: Error setting up the loadbalancer at %s:%s.\n %s" %
+                              (__name__, vnf_src_name, vnf_src_interface, e))
+            return Response(u"%s: Error setting up the loadbalancer at %s:%s.\n %s" %
+                            (__name__, vnf_src_name, vnf_src_interface, e), status=500, mimetype="application/json")
+
+    def delete(self, vnf_src_name, vnf_src_interface):
+        """
+        Will delete a load balancer that sits behind a specified interface at a vnf
+
+        :param vnf_src_name: Name of the source VNF
+        :type vnf_src_name: ``str``
+        :param vnf_src_interface: Name of the source VNF interface to chain on
+        :type vnf_src_interface: ``str``
+        :return: flask.Response 200 if set up correctly else 500
+         501 if VNF or intfs does not exist
+        :rtype: :class:`flask.Response`
+
+        """
+        # check if VNF exist
+        if not self.api.manage.check_vnf_intf_pair(vnf_src_name, vnf_src_interface):
+            return Response(u"VNF %s or intfs %s does not exist" % (vnf_src_name, vnf_src_interface), status=501,
+                            mimetype="application/json")
+        try:
+            logging.debug("Deleting loadbalancer at %s: interface: %s" % (vnf_src_name, vnf_src_interface))
+            net = self.api.manage.net
+
+            if vnf_src_name != "floating":
+                # check if VNF exists
+                if vnf_src_name not in net:
+                    return Response(u"Source VNF or interface can not be found." % vnf_src_name,
+                                    status=404, mimetype="application/json")
+
+                self.api.manage.delete_loadbalancer(vnf_src_name, vnf_src_interface)
+
+                return Response(u"Loadbalancer deleted at %s:%s" % (vnf_src_name, vnf_src_interface),
+                                status=200, mimetype="application/json")
+            else:
+                cookie = vnf_src_name
+                self.api.manage.delete_floating_lb(cookie)
+                return Response(u"Floating loadbalancer with cookie %s removed" % (cookie),
+                                status=200, mimetype="application/json")
+        except Exception as e:
+            logging.exception(u"%s: Error deleting the loadbalancer at %s%s.\n %s" %
+                              (__name__, vnf_src_name, vnf_src_interface, e))
+            return Response(u"%s: Error deleting the loadbalancer at %s%s." %
+                            (__name__, vnf_src_name, vnf_src_interface), status=500, mimetype="application/json")
+
+
+class QueryTopology(Resource):
+    """
+    Handles requests at "/v1/topo/"
+    """
+
+    def __init__(self, api):
+        self.api = api
+
+    def get(self):
+        """
+        Answers GET requests for the current network topology at "/v1/topo".
+        This will only return switches and datacenters and ignore currently deployed VNFs.
+
+        :return: 200 if successful with the network graph as json dict, else 500
+
+        """
+        try:
+            logging.debug("Querying topology")
+            graph = self.api.manage.net.DCNetwork_graph
+            net = self.api.manage.net
+            # root node is nodes
+            topology = {"nodes": list()}
+
+            for n in graph:
+                # remove root node as well as the floating switch fs1
+                if n != "root" and n != "fs1":
+                    # we only want to return switches!
+                    if not isinstance(net[n], OVSSwitch):
+                        continue
+                    node = dict()
+
+                    # get real datacenter label
+                    for dc in self.api.manage.net.dcs.values():
+                        if str(dc.switch) == str(n):
+                            node["name"] = str(n)
+                            node["type"] = "Datacenter"
+                            node["label"] = str(dc.label)
+                            break
+
+                    # node is not a datacenter. It has to be a switch
+                    if node.get("type", "") != "Datacenter":
+                        node["name"] = str(n)
+                        node["type"] = "Switch"
+
+                    node["links"] = list()
+                    # add links to the topology
+                    for graph_node, data in graph[n].items():
+                        # only add links to the topology that connect switches
+                        if isinstance(net[graph_node], OVSSwitch):
+                            # we allow multiple edges between switches, so add them all
+                            # with their unique keys
+                            link = copy.copy(data)
+                            for edge in link:
+                                # do not add any links to the floating switch to the topology!
+                                if graph_node == "fs1":
+                                    continue
+                                # the translator wants everything as a string!
+                                for key, value in link[edge].items():
+                                    link[edge][key] = str(value)
+                                # name of the destination
+                                link[edge]["name"] = graph_node
+                                node["links"].append(link)
+
+                    topology["nodes"].append(node)
+
+            return Response(json.dumps(topology),
+                            status=200, mimetype="application/json")
+        except Exception as e:
+            logging.exception(u"%s: Error querying topology.\n %s" %
+                              (__name__, e))
+            return Response(u"%s: Error querying topology.\n %s" %
+                            (__name__, e), status=500, mimetype="application/json")