# Copyright 2013 Openstack Foundation
# 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.

import contextlib

import datetime
import mock
import netaddr
from neutron.common import exceptions as ex

from quark.db import models
from quark import exceptions as q_ex
from quark.plugin_modules import floating_ips
from quark.tests import test_quark_plugin


class TestRemoveFloatingIPs(test_quark_plugin.TestQuarkPlugin):
    @contextlib.contextmanager
    def _stubs(self, flip=None):
        flip_model = None
        if flip:
            flip_model = models.IPAddress()
            flip_model.update(flip)

        with contextlib.nested(
            mock.patch("quark.db.api.floating_ip_find"),
            mock.patch("quark.db.api.floating_ip_disassociate_fixed_ip"),
            mock.patch("quark.db.api.port_disassociate_ip"),
            mock.patch("quark.db.api.ip_address_deallocate"),
            mock.patch("quark.ipam.QuarkIpam.deallocate_ip_address"),
            mock.patch("quark.drivers.unicorn_driver.UnicornDriver"
                       ".remove_floating_ip")
        ) as (flip_find, db_fixed_ip_disassoc, db_port_disassoc, db_dealloc,
              mock_dealloc, mock_remove_flip):
            flip_find.return_value = flip_model
            yield

    def test_delete_floating_by_ip_address_id(self):
        flip = dict(id=1, address=3232235876, address_readable="192.168.1.100",
                    subnet_id=1, network_id=2, version=4, used_by_tenant_id=1,
                    network=dict(ipam_strategy="ANY"))
        with self._stubs(flip=flip):
            self.plugin.delete_floatingip(self.context, 1)

    def test_delete_floating_by_when_ip_address_does_not_exists_fails(self):
        with self._stubs():
            with self.assertRaises(q_ex.FloatingIpNotFound):
                self.plugin.delete_floatingip(self.context, 1)


class TestFloatingIPUtilityMethods(test_quark_plugin.TestQuarkPlugin):
    def test_get_next_available_fixed_ip_with_single_fixed_ip(self):
        port = models.Port()
        port.update(dict(id=1))

        fixed_ip_addr = netaddr.IPAddress('192.168.0.1')
        fixed_ip = models.IPAddress()
        fixed_ip.update(dict(address_type="fixed", address=int(fixed_ip_addr),
                             version=4, address_readable=str(fixed_ip_addr),
                             allocated_at=datetime.datetime.now()))

        port.ip_addresses.append(fixed_ip)

        next_fixed_ip = floating_ips._get_next_available_fixed_ip(port)

        self.assertEqual(next_fixed_ip["address_readable"], '192.168.0.1')

    def test_get_next_available_fixed_ip_with_mult_fixed_ips(self):
        port = models.Port()
        port.update(dict(id=1))

        for ip_addr in ["192.168.0.1", "192.168.0.2", "192.168.0.3"]:
            fixed_ip_addr = netaddr.IPAddress(i
p_addr)
            fixed_ip = models.IPAddress()
            fixed_ip.update(dict(address_type="fixed",
             
                    address=int(fixed_ip_addr),
                                 version=4,
                                 address_readable=str(fixed_ip_addr),
                                 allocated_at=datetime.datetime.now()))

            port.ip_addresses.append(fixed_ip)

        next_fixed_ip = floating_ips._get_next_available_fixed_ip(port)

        self.assertEqual(next_fixed_ip["address_readable"], '192.168.0.1')

    def test_get_next_available_fixed_ip_with_no_avail_fixed_ips(self):
        port = models.Port()
        port.update(dict(id=1))

        fixed_ip_addr = netaddr.IPAddress("192.168.0.1")
        fixed_ip = models.IPAddress()
        fixed_ip.update(dict(address_type="fixed",
                             address=int(fixed_ip_addr),
                             version=4,
                             address_readable=str(fixed_ip_addr),
                             allocated_at=datetime.datetime.now()))

        flip_addr = netaddr.IPAddress("10.0.0.1")
        flip = models.IPAddress()
        flip.update(dict(address_type="floating",
                         address=int(flip_addr),
                         version=4,
                         address_readable=str(flip_addr),
                         allocated_at=datetime.datetime.now()))
        flip.fixed_ip = fixed_ip

        port.ip_addresses.append(fixed_ip)
        port.ip_addresses.append(flip)

        fixed_ip_addr = netaddr.IPAddress("192.168.0.2")
        fixed_ip = models.IPAddress()
        fixed_ip.update(dict(address_type="fixed",
                             address=int(fixed_ip_addr),
                             version=4,
                             address_readable=str(fixed_ip_addr),
                             allocated_at=datetime.datetime.now()))

        flip_addr = netaddr.IPAddress("10.0.0.2")
        flip = models.IPAddress()
        flip.update(dict(address_type="floating",
                         address=int(flip_addr),
                         version=4,
                         address_readable=str(flip_addr),
                         allocated_at=datetime.datetime.now()))
        flip.fixed_ip = fixed_ip

        port.ip_addresses.append(fixed_ip)
        port.ip_addresses.append(flip)

        next_fixed_ip = floating_ips._get_next_available_fixed_ip(port)

        self.assertEqual(next_fixed_ip, None)

    def test_get_next_available_fixed_ip_with_avail_fixed_ips(self):
        port = models.Port()
        port.update(dict(id=1))

        fixed_ip_addr = netaddr.IPAddress("192.168.0.1")
        fixed_ip = models.IPAddress()
        fixed_ip.update(dict(address_type="fixed",
                             address=int(fixed_ip_addr),
                             version=4,
                             address_readable=str(fixed_ip_addr),
                             allocated_at=datetime.datetime.now()))

        flip_addr = netaddr.IPAddress("10.0.0.1")
        flip = models.IPAddress()
        flip.update(dict(address_type="floating",
                         address=int(flip_addr),
                         version=4,
                         address_readable=str(flip_addr),
                         allocated_at=datetime.datetime.now()))
        flip.fixed_ip = fixed_ip

        port.ip_addresses.append(fixed_ip)
        port.ip_addresses.append(flip)

        fixed_ip_addr = netaddr.IPAddress("192.168.0.2")
        fixed_ip = models.IPAddress()
        fixed_ip.update(dict(address_type="fixed",
                             address=int(fixed_ip_addr),
                             version=4,
                             address_readable=str(fixed_ip_addr),
                             allocated_at=datetime.datetime.now()))

        port.ip_addresses.append(fixed_ip)
        port.ip_addresses.append(flip)

        next_fixed_ip = floating_ips._get_next_available_fixed_ip(port)

        self.assertEqual(next_fixed_ip["address_readable"], "192.168.0.2")


class TestCreateFloatingIPs(test_quark_plugin.TestQuarkPlugin):
    @contextlib.contextmanager
    def _stubs(self, flip=None, port=None, ips=None, network=None):
        port_model = None
        if port:
            port_model = models.Port()
            port_model.update(dict(port=port))
            if ips:
                for ip in ips:
                    ip_model = models.IPAddress()
                    ip_model.update(ip)
                    addr_type = ip.get("address_type")
                    if addr_type == "floating" and "fixed_ip_addr" in ip:
                        fixed_ip = models.IPAddress()
                        fixed_ip.update(next(ip_addr for ip_addr in ips
                                             if (ip_addr["address_readable"] ==
                 