"""
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).
"""
from flask_restful import Resource
from flask import request, Response
from emuvim.api.openstack.openstack_dummies.base_openstack_dummy import BaseOpenstackDummy
from emuvim.api.openstack.helper import get_host
from datetime import datetime
import neutron_sfc_dummy_api as SFC
import logging
import json
import uuid
import copy

LOG = logging.getLogger("api.openstack.neutron")


class NeutronDummyApi(BaseOpenstackDummy):
    def __init__(self, ip, port, compute):
        super(NeutronDummyApi, self).__init__(ip, port)
        self.compute = compute

        # create default networks (OSM usually assumes to have these pre-configured)
        self.compute.create_network("mgmt")
        self.compute.create_network("mgmtnet")

        self.api.add_resource(NeutronListAPIVersions, "/")
        self.api.add_resource(NeutronShowAPIv2Details, "/v2.0")
        self.api.add_resource(NeutronListNetworks, "/v2.0/networks.json", "/v2.0/networks",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(NeutronShowNetwork, "/v2.0/networks/<network_id>.json", "/v2.0/networks/<network_id>",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(NeutronCreateNetwork, "/v2.0/networks.json", "/v2.0/networks",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(NeutronUpdateNetwork, "/v2.0/networks/<network_id>.json", "/v2.0/networks/<network_id>",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(NeutronDeleteNetwork, "/v2.0/networks/<network_id>.json", "/v2.0/networks/<network_id>",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(NeutronListSubnets, "/v2.0/subnets.json", "/v2.0/subnets",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(NeutronShowSubnet, "/v2.0/subnets/<subnet_id>.json", "/v2.0/subnets/<subnet_id>",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(NeutronCreateSubnet, "/v2.0/subnets.json", "/v2.0/subnets",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(NeutronUpdateSubnet, "/v2.0/subnets/<subnet_id>.json", "/v2.0/subnets/<subnet_id>",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(NeutronDeleteSubnet, "/v2.0/subnets/<subnet_id>.json", "/v2.0/subnets/<subnet_id>",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(NeutronListPorts, "/v2.0/ports.json", "/v2.0/ports",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(NeutronShowPort, "/v2.0/ports/<port_id>.json", "/v2.0/ports/<port_id>",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(NeutronCreatePort, "/v2.0/ports.json", "/v2.0/ports",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(NeutronUpdatePort, "/v2.0/ports/<port_id>.json", "/v2.0/ports/<port_id>",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(NeutronDeletePort, "/v2.0/ports/<port_id>.json", "/v2.0/ports/<port_id>",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(NeutronAddFloatingIp, "/v2.0/floatingips.json", "/v2.0/floatingips",
                              resource_class_kwargs={'api': self})

        # Service Function Chaining (SFC) API
        self.api.add_resource(SFC.PortPairsCreate, "/v2.0/sfc/port_pairs.json", "/v2.0/sfc/port_pairs",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(SFC.PortPairsUpdate, "/v2.0/sfc/port_pairs/<pair_id>.json",
                              "/v2.0/sfc/port_pairs/<pair_id>",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(SFC.PortPairsDelete, "/v2.0/sfc/port_pairs/<pair_id>.json",
                              "/v2.0/sfc/port_pairs/<pair_id>",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(SFC.PortPairsList, "/v2.0/sfc/port_pairs.json", "/v2.0/sfc/port_pairs",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(SFC.PortPairsShow, "/v2.0/sfc/port_pairs/<pair_id>.json",
                              "/v2.0/sfc/port_pairs/<pair_id>",
                              resource_class_kwargs={'api': self})

        self.api.add_resource(SFC.PortPairGroupCreate, "/v2.0/sfc/port_pair_groups.json", "/v2.0/sfc/port_pair_groups",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(SFC.PortPairGroupUpdate, "/v2.0/sfc/port_pair_groups/<group_id>.json",
                              "/v2.0/sfc/port_pair_groups/<group_id>",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(SFC.PortPairGroupDelete, "/v2.0/sfc/port_pair_groups/<group_id>.json",
                              "/v2.0/sfc/port_pair_groups/<group_id>",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(SFC.PortPairGroupList, "/v2.0/sfc/port_pair_groups.json", "/v2.0/sfc/port_pair_groups",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(SFC.PortPairGroupShow, "/v2.0/sfc/port_pair_groups/<group_id>.json",
                              "/v2.0/sfc/port_pair_groups/<group_id>",
                              resource_class_kwargs={'api': self})

        self.api.add_resource(SFC.FlowClassifierCreate, "/v2.0/sfc/flow_classifiers.json", "/v2.0/sfc/flow_classifiers",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(SFC.FlowClassifierUpdate, "/v2.0/sfc/flow_classifiers/<flow_classifier_id>.json",
                              "/v2.0/sfc/flow_classifiers/<flow_classifier_id>",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(SFC.FlowClassifierDelete, "/v2.0/sfc/flow_classifiers/<flow_classifier_id>.json",
                              "/v2.0/sfc/flow_classifiers/<flow_classifier_id>",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(SFC.FlowClassifierList, "/v2.0/sfc/flow_classifiers.json", "/v2.0/sfc/flow_classifiers",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(SFC.FlowClassifierShow, "/v2.0/sfc/flow_classifiers/<flow_classifier_id>.json",
                              "/v2.0/sfc/flow_classifiers/<flow_classifier_id>",
                              resource_class_kwargs={'api': self})

        self.api.add_resource(SFC.PortChainCreate, "/v2.0/sfc/port_chains.json", "/v2.0/sfc/port_chains",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(SFC.PortChainUpdate, "/v2.0/sfc/port_chains/<chain_id>.json",
                              "/v2.0/sfc/port_chains/<chain_id>",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(SFC.PortChainDelete, "/v2.0/sfc/port_chains/<chain_id>.json",
                              "/v2.0/sfc/port_chains/<chain_id>",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(SFC.PortChainList, "/v2.0/sfc/port_chains.json", "/v2.0/sfc/port_chains",
                              resource_class_kwargs={'api': self})
        self.api.add_resource(SFC.PortChainShow, "/v2.0/sfc/port_chains/<chain_id>.json",
                              "/v2.0/sfc/port_chains/<chain_id>",
                              resource_class_kwargs={'api': self})


class NeutronListAPIVersions(Resource):
    def get(self):
        """
        Lists API versions.

        :return: Returns a json with API versions.
        :rtype: :class:`flask.response`
        """
        LOG.debug("API CALL: Neutron - List API Versions")
        resp = dict()
        resp['versions'] = dict()

        versions = [{
            "status": "CURRENT",
            "id": "v2.0",
            "links": [
                {
                    "href": request.url_root + '/v2.0',
                    "rel": "self"
                }
            ]
        }]
        resp['versions'] = versions

        return Response(json.dumps(resp), status=200, mimetype='application/json')


class NeutronShowAPIv2Details(Resource):
    def get(self):
        """
        Returns API details.

        :return: Returns a json with API details.
        :rtype: :class:`flask.response`
        """
        LOG.debug("API CALL: %s GET" % str(self.__class__.__name__))
        resp = dict()

        resp['resources'] = dict()
        resp['resources'] = [{
            "links": [
                {
                    "href": request.url_root + 'v2.0/subnets',
                    "rel": "self"
                }
            ],
            "name": "subnet",
            "collection": "subnets"
        },
            {
                "links": [
                    {
                        "href": request.url_root + 'v2.0/networks',
                        "rel": "self"
                    }
                ],
                "name": "network",
                "collection": "networks"
            },
            {
                "links": [
                    {
                        "href": request.url_root + 'v2.0/ports',
                        "rel": "self"
                    }
                ],
                "name": "ports",
                "collection": "ports"
            }
        ]

        return Response(json.dumps(resp), status=200, mimetype='application/json')


class NeutronListNetworks(Resource):
    def __init__(self, api):
        self.api = api

    def get(self):
        """
        Lists all networks, used in son-emu. If a 'name' or one or more 'id's are specified, it will only list the
        network with the name, or the networks specified via id.

        :return: Returns a json response, starting with 'networks' as root node.
        :rtype: :class:`flask.response`
        """
        LOG.debug("API CALL: %s GET" % str(self.__class__.__name__))
        # LOG.debug("ARGS: {}".format(request.args))
        try:
            if request.args.get('name'):
                tmp_network = NeutronShowNetwork(self.api)
                response = tmp_network.get_network(request.args.get('name'), True)
                LOG.debug("{} RESPONSE (1): {}".format(self.__class__.__name__, response))
                return response
            id_list = request.args.getlist('id')
            if len(id_list) == 1:
                tmp_network = NeutronShowNetwork(self.api)
                response = tmp_network.get_network(request.args.get('id'), True)
                LOG.debug("{} RESPONSE (2): {}".format(self.__class__.__name__, response))
                return response

            network_list = list()
            network_dict = dict()

            if len(id_list) == 0:
                for net in self.api.compute.nets.values():
                    tmp_network_dict = net.create_network_dict()
                    if tmp_network_dict not in network_list:
                        network_list.append(tmp_network_dict)
            else:
                for net in self.api.compute.nets.values():
                    if net.id in id_list:
                        tmp_network_dict = net.create_network_dict()
                        if tmp_network_dict not in network_list:
                            network_list.append(tmp_network_dict)

            network_dict["networks"] = network_list
            LOG.debug("{} RESPONSE (3): {}".format(self.__class__.__name__, network_dict))
            return Response(json.dumps(network_dict), status=200, mimetype='application/json')

        except Exception as ex:
            LOG.exception("Neutron: List networks exception.")
            return Response(ex.message, status=500, mimetype='application/json')


class NeutronShowNetwork(Resource):
    def __init__(self, api):
        self.api = api

    def get(self, network_id):
        """
        Returns the network, specified via 'network_id'.

        :param network_id: The unique ID string of the network.
        :type network_id: ``str``
        :return: Returns a json response, starting with 'network' as root node and one network description.
        :rtype: :class:`flask.response`
        """
        LOG.debug("API CALL: %s GET" % str(self.__class__.__name__))
        return self.get_network(network_id, False)

    def get_network(self, network_name_or_id, as_list):
        """
        Returns one network description of the network, specified via 'network_name_or_id'.

        :param network_name_or_id: The indicator string, which specifies the requested network.
        :type network_name_or_id: ``str``
        :param as_list: Determines if the network description should start with the root node 'network' or 'networks'.
        :type as_list: ``bool``
        :return: Returns a json response, with one network description.
        :rtype: :class:`flask.response`
        """
        try:
            net = self.api.compute.find_network_by_name_or_id(network_name_or_id)
            if net is None:
                return Response(u'Network not found.\n', status=404, mimetype='application/json')

            tmp_network_dict = net.create_network_dict()
            tmp_dict = dict()
            if as_list:
                tmp_dict["networks"] = [tmp_network_dict]
            else:
                tmp_dict["network"] = tmp_network_dict

            return Response(json.dumps(tmp_dict), status=200, mimetype='application/json')


        except Exception as ex:
            logging.exception("Neutron: Show network exception.")
            return Response(ex.message, status=500, mimetype='application/json')


class NeutronCreateNetwork(Resource):
    def __init__(self, api):
        self.api = api

    def post(self):
        """
        Creates a network with the name, specified within the request under ['network']['name'].

        :return: * 400, if the network already exists.
            * 500, if any exception occurred while creation.
            * 201, if everything worked out.
        :rtype: :class:`flask.response`
        """
        LOG.debug("API CALL: %s POST" % str(self.__class__.__name__))
        try:
            network_dict = json.loads(request.data)
            name = network_dict['network']['name']
            net = self.api.compute.find_network_by_name_or_id(name)
            if net is not None:
                return Response('Network already exists.\n', status=400, mimetype='application/json')

            net = self.api.compute.create_network(name)
            return Response(json.dumps({"network": net.create_network_dict()}), status=201, mimetype='application/json')
        except Exception as ex:
            LOG.exception("Neutron: Create network excepiton.")
            return Response(ex.message, status=500, mimetype='application/json')


class NeutronUpdateNetwork(Resource):
    def __init__(self, api):
        self.api = api

    def put(self, network_id):  # TODO currently only the name will be changed
        """
        Updates the existing network with the given parameters.

        :param network_id: The indicator string, which specifies the requested network.
        :type network_id: ``str``
        :return: * 404, if the network could not be found.
            * 500, if any exception occurred while updating the network.
            * 200, if everything worked out.
        :rtype: :class:`flask.response`
        """
        LOG.debug("API CALL: %s PUT" % str(self.__class__.__name__))
        try:
            if network_id in self.api.compute.nets:
                net = self.api.compute.nets[network_id]
                network_dict = json.loads(request.data)
                old_net = copy.copy(net)

                if "status" in network_dict["network"]:
                    net.status = network_dict["network"]["status"]
                if "subnets" in network_dict["network"]:
                    pass  # tmp_network_dict["subnets"] = None
                if "name" in network_dict["network"] and net.name != network_dict["network"]["name"]:
                    net.name = network_dict["network"]["name"]
                if "admin_state_up" in network_dict["network"]:
                    pass  # tmp_network_dict["admin_state_up"] = True
                if "tenant_id" in network_dict["network"]:
                    pass  # tmp_network_dict["tenant_id"] = "c1210485b2424d48804aad5d39c61b8f"
                if "shared" in network_dict["network"]:
                    pass  # tmp_network_dict["shared"] = False

                return Response(json.dumps(network_dict), status=200, mimetype='application/json')

            return Response('Network not found.\n', status=404, mimetype='application/json')

        except Exception as ex:
            LOG.exception("Neutron: Show networks exception.")
            return Response(ex.message, status=500, mimetype='application/json')


class NeutronDeleteNetwork(Resource):
    def __init__(self, api):
        self.api = api

    def delete(self, network_id):
        """
        Deletes the specified network and all its subnets.

        :param network_id: The indicator string, which specifies the requested network.
        :type network_id: ``str``
        :return: * 404, if the network or the subnet could not be removed.
            * 500, if any exception occurred while deletion.
            * 204, if everything worked out.
        :rtype: :class:`flask.response`
        """
        LOG.debug("API CALL: %s DELETE" % str(self.__class__.__name__))
        try:
            if network_id not in self.api.compute.nets:
                return Response('Could not find network. (' + network_id + ')\n',
                                status=404, mimetype='application/json')

            net = self.api.compute.nets[network_id]
            delete_subnet = NeutronDeleteSubnet(self.api)
            resp = delete_subnet.delete(net.subnet_id)

            if not '204' in resp.status and not '404' in resp.status:
                return resp

            self.api.compute.delete_network(network_id)

            return Response('', status=204, mimetype='application/json')
        except Exception as ex:
            LOG.exception("Neutron: Delete network exception.")
            return Response(ex.message, status=500, mimetype='application/json')


class NeutronListSubnets(Resource):
    def __init__(self, api):
        self.api = api

    def get(self):
        """
        Lists all subnets, used in son-emu. If a 'name' or one or more 'id's are specified, it will only list the
        subnet with the name, or the subnets specified via id.

        :return: Returns a json response, starting with 'subnets' as root node.
        :rtype: :class:`flask.response`
        """
        LOG.debug("API CALL: %s GET" % str(self.__class__.__name__))
        try:
            if request.args.get('name'):
                show_subnet = NeutronShowSubnet(self.api)
                return show_subnet.get_subnet(request.args.get('name'), True)
            id_list = request.args.getlist('id')
            if len(id_list) == 1:
                show_subnet = NeutronShowSubnet(self.api)
                return show_subnet.get_subnet(id_list[0], True)

            subnet_list = list()
            subnet_dict = dict()

            if len(id_list) == 0:
                for net in self.api.compute.nets.values():
                    if net.subnet_id is not None:
                        tmp_subnet_dict = net.create_subnet_dict()
                        subnet_list.append(tmp_subnet_dict)
            else:
                for net in self.api.compute.nets.values():
                    if net.subnet_id in id_list:
                        tmp_subnet_dict = net.create_subnet_dict()
                        subnet_list.append(tmp_subnet_dict)

            subnet_dict["subnets"] = subnet_list

            return Response(json.dumps(subnet_dict), status=200, mimetype='application/json')

        except Exception as ex:
            LOG.exception("Neutron: List subnets exception.")
            return Response(ex.message, status=500, mimetype='application/json')


class NeutronShowSubnet(Resource):
    def __init__(self, api):
        self.api = api

    def get(self, subnet_id):
        """
        Returns the subnet, specified via 'subnet_id'.

        :param subnet_id: The unique ID string of the subnet.
        :type subnet_id: ``str``
        :return: Returns a json response, starting with 'subnet' as root node and one subnet description.
        :rtype: :class:`flask.response`
        """
        LOG.debug("API CALL: %s GET" % str(self.__class__.__name__))
        return self.get_subnet(subnet_id, False)

    def get_subnet(self, subnet_name_or_id, as_list):
        """
        Returns one subnet description of the subnet, specified via 'subnet_name_or_id'.

        :param subnet_name_or_id: The indicator string, which specifies the requested subnet.
        :type subnet_name_or_id: ``str``
        :param as_list: Determines if the subnet description should start with the root node 'subnet' or 'subnets'.
        :type as_list: ``bool``
        :return: Returns a json response, with one subnet description.
        :rtype: :class:`flask.response`
        """
        try:
            for net in self.api.compute.nets.values():
                if net.subnet_id == subnet_name_or_id or net.subnet_name == subnet_name_or_id:
                    tmp_subnet_dict = net.create_subnet_dict()
                    tmp_dict = dict()
                    if as_list:
                        tmp_dict["subnets"] = [tmp_subnet_dict]
                    else:
                        tmp_dict["subnet"] = tmp_subnet_dict
                    return Response(json.dumps(tmp_dict), status=200, mimetype='application/json')

            return Response('Subnet not found. (' + subnet_name_or_id + ')\n', status=404, mimetype='application/json')

        except Exception as ex:
            LOG.exception("Neutron: Show subnet exception.")
            return Response(ex.message, status=500, mimetype='application/json')


class NeutronCreateSubnet(Resource):
    def __init__(self, api):
        self.api = api

    def post(self):
        """
        Creates a subnet with the name, specified within the request under ['subnet']['name'].

        :return: * 400, if the 'CIDR' format is wrong or it does not exist.
            * 404, if the network was not found.
            * 409, if the corresponding network already has one subnet.
            * 500, if any exception occurred while creation and
            * 201, if everything worked out.
        :rtype: :class:`flask.response`
        """
        LOG.debug("API CALL: %s POST" % str(self.__class__.__name__))
        try:
            subnet_dict = json.loads(request.data)
            net = self.api.compute.find_network_by_name_or_id(subnet_dict['subnet']['network_id'])

            if net is None:
                return Response('Could not find network.\n', status=404, mimetype='application/json')

            net.subnet_name = subnet_dict["subnet"].get('name', str(net.name) + '-sub')
            if net.subnet_id is not None:
                LOG.error("Only one subnet per network is supported: {}".format(net.subnet_id))
                return Response('Only one subnet per network is supported\n', status=409, mimetype='application/json')

            if "id" in subnet_dict["subnet"]:
                net.subnet_id = subnet_dict["subnet"]["id"]
            else:
                net.subnet_id = str(uuid.uuid4())
            import emuvim.api.openstack.ip_handler as IP
            net.set_cidr(IP.get_new_cidr(net.subnet_id))

            if "tenant_id" in subnet_dict["subnet"]:
                pass
            if "allocation_pools" in subnet_dict["subnet"]:
                pass
            if "gateway_ip" in subnet_dict["subnet"]:
                net.gateway_ip = subnet_dict["subnet"]["gateway_ip"]
            if "ip_version" in subnet_dict["subnet"]:
                pass
            if "enable_dhcp" in subnet_dict["subnet"]:
                pass

            return Response(json.dumps({'subnet': net.create_subnet_dict()}), status=201, mimetype='application/json')

        except Exception as ex:
            LOG.exception("Neutron: Create network excepiton.")
            return Response(ex.message, status=500, mimetype='application/json')


class NeutronUpdateSubnet(Resource):
    def __init__(self, api):
        self.api = api

    def put(self, subnet_id):
        """
        Updates the existing subnet with the given parameters.

        :param subnet_id: The indicator string, which specifies the requested subnet.
        :type subnet_id: ``str``
        :return: * 404, if the network could not be found.
            * 500, if any exception occurred while updating the network.
            * 200, if everything worked out.
        :rtype: :class:`flask.response`
        """
        LOG.debug("API CALL: %s PUT" % str(self.__class__.__name__))
        try:
            for net in self.api.compute.nets.values():
                if net.subnet_id == subnet_id:
                    subnet_dict = json.loads(request.data)

                    if "name" in subnet_dict["subnet"]:
                        net.subnet_name = subnet_dict["subnet"]["name"]
                    if "network_id" in subnet_dict["subnet"]:
                        net.id = subnet_dict["subnet"]["network_id"]
                    if "tenant_id" in subnet_dict["subnet"]:
                        pass
                    if "allocation_pools" in subnet_dict["subnet"]:
                        pass
                    if "gateway_ip" in subnet_dict["subnet"]:
                        net.gateway_ip = subnet_dict["subnet"]["gateway_ip"]
                    if "ip_version" in subnet_dict["subnet"]:
                        pass
                    if "cidr" in subnet_dict["subnet"]:
                        net.set_cidr(subnet_dict["subnet"]["cidr"])
                    if "id" in subnet_dict["subnet"]:
                        net.subnet_id = subnet_dict["subnet"]["id"]
                    if "enable_dhcp" in subnet_dict["subnet"]:
                        pass

                    net.subnet_update_time = str(datetime.now())
                    tmp_dict = {'subnet': net.create_subnet_dict()}
                    return Response(json.dumps(tmp_dict), status=200, mimetype='application/json')

            return Response('Network not found.\n', status=404, mimetype='application/json')

        except Exception as ex:
            LOG.exception("Neutron: Show networks exception.")
            return Response(ex.message, status=500, mimetype='application/json')


class NeutronDeleteSubnet(Resource):
    def __init__(self, api):
        self.api = api

    def delete(self, subnet_id):
        """
        Deletes the specified subnet.

        :param subnet_id: The indicator string, which specifies the requested subnet.
        :type subnet_id: ``str``
        :return: * 404, if the subnet could not be removed.
            * 500, if any exception occurred while deletion.
            * 204, if everything worked out.
        :rtype: :class:`flask.response`
        """
        LOG.debug("API CALL: %s DELETE" % str(self.__class__.__name__))
        try:
            for net in self.api.compute.nets.values():
                if net.subnet_id == subnet_id:
                    for server in self.api.compute.computeUnits.values():
                        for port_name in server.port_names:
                            port = self.api.compute.find_port_by_name_or_id(port_name)
                            if port is None:
                                LOG.warning("Port search for {} returned None.".format(port_name))
                                continue
                            if port.net_name == net.name:
                                port.ip_address = None
                                self.api.compute.dc.net.removeLink(
                                    link=None,
                                    node1=self.api.compute.dc.containers[server.name],
                                    node2=self.api.compute.dc.switch)
                                port.net_name = None

                    net.delete_subnet()

                    return Response('', status=204, mimetype='application/json')

            return Response('Could not find subnet.', status=404, mimetype='application/json')
        except Exception as ex:
            LOG.exception("Neutron: Delete subnet exception.")
            return Response(ex.message, status=500, mimetype='application/json')


class NeutronListPorts(Resource):
    def __init__(self, api):
        self.api = api

    def get(self):
        """
        Lists all ports, used in son-emu. If a 'name' or one or more 'id's are specified, it will only list the
        port with the name, or the ports specified via id.

        :return: Returns a json response, starting with 'ports' as root node.
        :rtype: :class:`flask.response`
        """
        LOG.debug("API CALL: %s GET" % str(self.__class__.__name__))
        try:
            if request.args.get('name'):
                show_port = NeutronShowPort(self.api)
                return show_port.get_port(request.args.get('name'), True)
            id_list = request.args.getlist('id')
            if len(id_list) == 1:
                show_port = NeutronShowPort(self.api)
                return show_port.get_port(request.args.get('id'), True)

            port_list = list()
            port_dict = dict()

            if len(id_list) == 0:
                for port in self.api.compute.ports.values():
                    tmp_port_dict = port.create_port_dict(self.api.compute)
                    port_list.append(tmp_port_dict)
            else:
                for port in self.api.compute.ports.values():
                    if port.id in id_list:
                        tmp_port_dict = port.create_port_dict(self.api.compute)
                        port_list.append(tmp_port_dict)

            port_dict["ports"] = port_list

            return Response(json.dumps(port_dict), status=200, mimetype='application/json')

        except Exception as ex:
            LOG.exception("Neutron: List ports exception.")
            return Response(ex.message, status=500, mimetype='application/json')


class NeutronShowPort(Resource):
    def __init__(self, api):
        self.api = api

    def get(self, port_id):
        """
        Returns the port, specified via 'port_id'.

        :param port_id: The unique ID string of the network.
        :type port_id: ``str``
        :return: Returns a json response, starting with 'port' as root node and one network description.
        :rtype: :class:`flask.response`
        """
        LOG.debug("API CALL: %s GET" % str(self.__class__.__name__))
        return self.get_port(port_id, False)

    def get_port(self, port_name_or_id, as_list):
        """
        Returns one network description of the port, specified via 'port_name_or_id'.

        :param port_name_or_id: The indicator string, which specifies the requested port.
        :type port_name_or_id: ``str``
        :param as_list: Determines if the port description should start with the root node 'port' or 'ports'.
        :type as_list: ``bool``
        :return: Returns a json response, with one port description.
        :rtype: :class:`flask.response`
        """
        try:
            port = self.api.compute.find_port_by_name_or_id(port_name_or_id)
            if port is None:
                return Response('Port not found. (' + port_name_or_id + ')\n', status=404, mimetype='application/json')
            tmp_port_dict = port.create_port_dict(self.api.compute)
            tmp_dict = dict()
            if as_list:
                tmp_dict["ports"] = [tmp_port_dict]
            else:
                tmp_dict["port"] = tmp_port_dict
            return Response(json.dumps(tmp_dict), status=200, mimetype='application/json')
        except Exception as ex:
            LOG.exception("Neutron: Show port exception.")
            return Response(ex.message, status=500, mimetype='application/json')


class NeutronCreatePort(Resource):
    def __init__(self, api):
        self.api = api

    def post(self):
        """
        Creates a port with the name, specified within the request under ['port']['name'].

        :return: * 404, if the network could not be found.
            * 500, if any exception occurred while creation and
            * 201, if everything worked out.
        :rtype: :class:`flask.response`
        """
        LOG.debug("API CALL: %s POST" % str(self.__class__.__name__))
        try:
            port_dict = json.loads(request.data)
            net_id = port_dict['port']['network_id']

            if net_id not in self.api.compute.nets:
                return Response('Could not find network.\n', status=404, mimetype='application/json')

            net = self.api.compute.nets[net_id]
            if 'name' in port_dict['port']:
                name = port_dict['port']['name']
            else:
                num_ports = len(self.api.compute.ports)
                name = "port:cp%s:man:%s" % (num_ports, str(uuid.uuid4()))

            if self.api.compute.find_port_by_name_or_id(name):
                return Response("Port with name %s already exists.\n" % name, status=500, mimetype='application/json')

            port = self.api.compute.create_port(name)

            port.net_name = net.name
            port.ip_address = net.get_new_ip_address(name)

            if "admin_state_up" in port_dict["port"]:
                pass
            if "device_id" in port_dict["port"]:
                pass
            if "device_owner" in port_dict["port"]:
                pass
            if "fixed_ips" in port_dict["port"]:
                pass
            if "mac_address" in port_dict["port"]:
                port.mac_address = port_dict["port"]["mac_address"]
            if "status" in port_dict["port"]:
                pass
            if "tenant_id" in port_dict["port"]:
                pass

            # add the port to a stack if the specified network is a stack network
            for stack in self.api.compute.stacks.values():
                for net in stack.nets.values():
                    if net.id == net_id:
                        stack.ports[name] = port

            return Response(json.dumps({'port': port.create_port_dict(self.api.compute)}), status=201,
                            mimetype='application/json')
        except Exception as ex:
            LOG.exception("Neutron: Show port exception.")
            return Response(ex.message, status=500, mimetype='application/json')


class NeutronUpdatePort(Resource):
    def __init__(self, api):
        self.api = api

    def put(self, port_id):
        """
        Updates the existing port with the given parameters.

        :param network_id: The indicator string, which specifies the requested port.
        :type network_id: ``str``
        :return: * 404, if the network could not be found.
            * 500, if any exception occurred while updating the network.
            * 200, if everything worked out.
        :rtype: :class:`flask.response`
        """
        LOG.debug("API CALL: %s PUT" % str(self.__class__.__name__))
        try:
            port_dict = json.loads(request.data)
            port = self.api.compute.find_port_by_name_or_id(port_id)
            if port is None:
                return Response("Port with id %s does not exists.\n" % port_id, status=404, mimetype='application/json')
            old_port = copy.copy(port)

            stack = None
            for s in self.api.compute.stacks.values():
                for port in s.ports.values():
                    if port.id == port_id:
                        stack = s
            if "admin_state_up" in port_dict["port"]:
                pass
            if "device_id" in port_dict["port"]:
                pass
            if "device_owner" in port_dict["port"]:
                pass
            if "fixed_ips" in port_dict["port"]:
                pass
            if "id" in port_dict["port"]:
                port.id = port_dict["port"]["id"]
            if "mac_address" in port_dict["port"]:
                port.mac_address = port_dict["port"]["mac_address"]
            if "name" in port_dict["port"] and port_dict["port"]["name"] != port.name:
                port.set_name(port_dict["port"]["name"])
                if stack is not None:
                    if port.net_name in stack.nets:
                        stack.nets[port.net_name].update_port_name_for_ip_address(port.ip_address, port.name)
                    stack.ports[port.name] = stack.ports[old_port.name]
                    del stack.ports[old_port.name]
            if "network_id" in port_dict["port"]:
                pass
            if "status" in port_dict["port"]:
                pass
            if "tenant_id" in port_dict["port"]:
                pass

            return Response(json.dumps({'port': port.create_port_dict(self.api.compute)}), status=200,
                            mimetype='application/json')
        except Exception as ex:
            LOG.exception("Neutron: Update port exception.")
            return Response(ex.message, status=500, mimetype='application/json')


class NeutronDeletePort(Resource):
    def __init__(self, api):
        self.api = api

    def delete(self, port_id):
        """
        Deletes the specified port.

        :param port_id: The indicator string, which specifies the requested port.
        :type port_id: ``str``
        :return: * 404, if the port could not be found.
            * 500, if any exception occurred while deletion.
            * 204, if everything worked out.
        :rtype: :class:`flask.response`
        """
        LOG.debug("API CALL: %s DELETE" % str(self.__class__.__name__))
        try:
            port = self.api.compute.find_port_by_name_or_id(port_id)
            if port is None:
                return Response("Port with id %s does not exists.\n" % port_id, status=404)
            stack = None
            for s in self.api.compute.stacks.values():
                for p in s.ports.values():
                    if p.id == port_id:
                        stack = s
            if stack is not None:
                if port.net_name in stack.nets:
                    stack.nets[port.net_name].withdraw_ip_address(port.ip_address)
                for server in stack.servers.values():
                    try:
                        server.port_names.remove(port.name)
                    except ValueError:
                        pass

            # delete the port
            self.api.compute.delete_port(port.id)

            return Response('', status=204, mimetype='application/json')

        except Exception as ex:
            LOG.exception("Neutron: Delete port exception.")
            return Response(ex.message, status=500, mimetype='application/json')


class NeutronAddFloatingIp(Resource):
    def __init__(self, api):
        self.api = api

    def get(self):
        """
        Added a quick and dirty fake for the OSM integration. Returns a list of
        floating IPs. Has nothing to do with the setup inside the emulator.
        But its enough to make the OSM driver happy.
        @PG Sandman: Feel free to improve this and let it do something meaningful.
        """
        resp = dict()
        resp["floatingips"] = list()
        # create a list of floting IP definitions and return it
        for i in range(100, 110):
            ip = dict()
            ip["router_id"] = "router_id"
            ip["description"] = "hardcoded in api"
            ip["created_at"] = "router_id"
            ip["updated_at"] = "router_id"
            ip["revision_number"] = 1
            ip["tenant_id"] = "tenant_id"
            ip["project_id"] = "project_id"
            ip["floating_network_id"] = str(i)
            ip["status"] = "ACTIVE"
            ip["id"] = str(i)
            ip["port_id"] = "port_id"
            ip["floating_ip_address"] = "172.0.0.%d" % i
            ip["fixed_ip_address"] = "10.0.0.%d" % i
            resp["floatingips"].append(ip)
        return Response(json.dumps(resp), status=200, mimetype='application/json')

    def post(self):
        """
        Adds a floating IP to neutron.

        :return: Returns a floating network description.
        :rtype: :class:`flask.response`
        """
        LOG.debug("API CALL: %s POST" % str(self.__class__.__name__))
        try:
            # Fiddle with floating_network !
            req = json.loads(request.data)

            network_id = req["floatingip"]["floating_network_id"]
            net = self.api.compute.find_network_by_name_or_id(network_id)
            if net != self.api.manage.floating_network:
                return Response("You have to specify the existing floating network\n",
                                status=400, mimetype='application/json')

            port_id = req["floatingip"].get("port_id", None)
            port = self.api.compute.find_port_by_name_or_id(port_id)
            if port is not None:
                if port.net_name != self.api.manage.floating_network.name:
                    return Response("You have to specify a port in the floating network\n",
                                    status=400, mimetype='application/json')

                if port.floating_ip is not None:
                    return Response("We allow only one floating ip per port\n", status=400, mimetype='application/json')
            else:
                num_ports = len(self.api.compute.ports)
                name = "port:cp%s:fl:%s" % (num_ports, str(uuid.uuid4()))
                port = self.api.compute.create_port(name)
                port.net_name = net.name
                port.ip_address = net.get_new_ip_address(name)

            port.floating_ip = port.ip_address

            response = dict()
            resp = response["floatingip"] = dict()

            resp["floating_network_id"] = net.id
            resp["status"] = "ACTIVE"
            resp["id"] = net.id
            resp["port_id"] = port.id
            resp["floating_ip_address"] = port.floating_ip
            resp["fixed_ip_address"] = port.floating_ip

            return Response(json.dumps(response), status=200, mimetype='application/json')
        except Exception as ex:
            LOG.exception("Neutron: Create FloatingIP exception %s.", ex)
            return Response(ex.message, status=500, mimetype='application/json')
