"""
Copyright (c) 2017 SONATA-NFV and Paderborn University
ALL RIGHTS RESERVED.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Neither the name of the SONATA-NFV, Paderborn University
nor the names of its contributors may be used to endorse or promote
products derived from this software without specific prior written
permission.

This work has been performed in the framework of the SONATA project,
funded by the European Commission under Grant number 671517 through
the Horizon 2020 and 5G-PPP programmes. The authors would like to
acknowledge the contributions of their colleagues of the SONATA
partner consortium (www.sonata-nfv.eu).
"""
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")
