# 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 mock
from oslo_config import cfg
from oslo_utils import uuidutils
import testtools

from neutron.agent.linux import ip_lib
from neutron.common import utils
from neutron.objects import trunk
from neutron.plugins.ml2.drivers.linuxbridge.agent import \
    linuxbridge_neutron_agent
from neutron.services.trunk.drivers.linuxbridge.agent import trunk_plumber
from neutron.tests.functional.agent.linux import test_ip_lib

lba = linuxbridge_neutron_agent


class LinuxBridgeAgentTests(test_ip_lib.IpLibTestFramework):

    def setUp(self):
        super(LinuxBridgeAgentTests, self).setUp()
        agent_rpc = ('neutron.agent.rpc.PluginApi')
        mock.patch(agent_rpc).start()
        mock.patch('neutron.agent.rpc.PluginReportStateAPI').start()
        cfg.CONF.set_override('enable_vxlan', False, 'VXLAN')

    def test_validate_interface_mappings(self):
        mappings = {'physnet1': 'int1', 'physnet2': 'int2'}
        with testtools.ExpectedException(SystemExit):
            lba.LinuxBridgeManager({}, mappings)
        self.manage_device(
            self.generate_device_details()._replace(namespace=None,
                                                    name='int1'))
        with testtools.ExpectedException(SystemExit):
            lba.LinuxBridgeManager({}, mappings)
        self.manage_device(
            self.generate_device_details()._replace(namespace=None,
                                                    name='int2'))
        lba.LinuxBridgeManager({}, mappings)

    def test_validate_bridge_mappings(self):
        mappings = {'physnet1': 'br-eth1'}
        with testtools.ExpectedException(SystemExit):
            lba.LinuxBridgeManager(mappings, {})
        self.manage_device(
            self.generate_device_details()._replace(namespace=None,
                                                    name='br-eth1'))
        lba.LinuxBridgeManager(mappings, {})

    def test_set_port_mac(self):
        attr = self.generate_device_details()
        self.manage_device(attr)
        plumber = trunk_plumber.Plumber(namespace=attr.namespace)
        # force it to return name of above
        plumber._get_tap_device_name = lambda x: attr.name
        new_mac = utils.get_random_mac('fa:16:3e:00:00:00'.split(':'))
        self.assertTrue(plumber.set_port_mac('port_id', new_mac))
        self.assertFalse(plumber.set_port_mac('port_id', new_mac))
        new_mac = utils.get_random_mac('fa:16:3e:00:00:00'.split(':'))
        self.assertTrue(plumber.set_port_mac('port_id', new_mac))
        self.assertFalse(plumber.set_port_mac('port_id', new_mac))

    def test_vlan_subinterfaces(self):
        attr = self.generate_device_details()
        device = self.manage_device(attr)
        devname = device.name
        plumber = trunk_plumber.Plumber(namespace=attr.namespace)
        for i in range(20):
            subname = 'vtest-%s' % i
            plumber._create_vlan_subint(devname, subname, i)
            # ensure no addresses were assigned (e.g. ipv6)
            vlan_int = ip_lib.IPDevice(subname, namespace=attr.namespace)
            self.assertFalse(vlan_int.addr.list())
        children = plumber._get_vlan_children(devname)
        expected = {('vtest-%s' % i, i) for i in range(20)}
        self.assertEqual(expected, children)

        # delete one
        plumber._safe_delete_device('vtest-19')
        children = plumber._get_vlan_children(devname)
        expected = {('vtest-%s' % i, i) for i in range(19)}
        self.assertEqual(expected, children)
        # ensure they are removed by parent removal
        self._safe_delete_device(device)
        self.assertFalse(plumber._get_vlan_children(devname))

    def test_vlan_QinQ_subinterfaces(self):
        # the trunk model does not support this right now, but this is to
        # the plumber on the agent side doesn't explode in their presense
        # in case an operator does something fancy or we have a race where
        # a trunk's parent port is converted to a subport while the agent
        # is offline.
        attr = self.generate_device_details()
        device = self.manage_device(attr)
        devname = device.name
        plumber = trunk_plumber.Plumber(namespace=attr.namespace)
        for i in range(20):
            plumber._create_vlan_subint(devname, 'vtest-%s' % i, i)
            plumber._create_vlan_subint('vtest-%s' % i, 'qinq-%s' % i, 2)
        top_level = {('vtest-%s' % i, i) for i in range(20)}
        for i in range(20):
            # as we iterate, we delete a vlan from each dev and ensure it
            # didn't break the top-level vlans
            self.assertEqual({('qinq-%s' % i, 2)},
                             plumber._get_vlan_children('vtest-%s' % i))
            plumber._safe_delete_device('qinq-%s' % i)
            self.assertEqual(set(), plumber._get_vlan_children('vtest-%i' % i))
            self.assertEqual(top_level, plumber._get_vlan_children(devname))

    def test_ensure_trunk_subports(self):
        attr = self.generate_device_details()
        device = self.manage_device(attr)
        devname = device.name
        plumber = trunk_plumber.Plumber(namespace=attr.namespace)
        plumber._trunk_device_name = lambda x: devname
        trunk_obj = self._gen_trunk()
        plumber.ensure_trunk_subports(trunk_obj)
        # ensure no mutation the second time
        with mock.patch.object(plumber, '_safe_delete_device',
                               side_effect=RuntimeError()):
            plumber.ensure_trunk_subports(trunk_obj)

        while trunk_obj.sub_ports:
            # drain down the sub-ports and make sure it keeps
            # them equal
            trunk_obj.sub_ports.pop()
            plumber.ensure_trunk_subports(trunk_obj)
            expected = {(plumber._get_tap_device_name(sp.port_id),
                         sp.segmentation_id)
                        for sp in trunk_obj.sub_ports}
            wired = plumber._get_vlan_children(devname)
            self.assertEqual(expected, wired)

    def _gen_trunk(self):
        trunk_obj = trunk.Trunk(id=uuidutils.generate_uuid(),
                                port_id=uuidutils.generate_uuid(),
                                tenant_id=uuidutils.generate_uuid())
        subports = [trunk.SubPort(id=uuidutils.generate_uuid(),
                                  port_id=uuidutils.generate_uuid(),
                                  segmentation_type='vlan',
                                  trunk_id=trunk_obj.id,
                                  segmentation_id=i)
                    for i in range(20, 40)]
        trunk_obj.sub_ports = subports
        return trunk_obj
