# vim: tabstop=4 shiftwidth=4 softtabstop=4
#
# Copyright 2012 Cisco Systems, Inc.  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.
#
# @author: Shweta Padubidri, Cisco Systems, Inc.
#

import ConfigParser
import logging as LOG
import unittest
import sys
import os
import signal
from subprocess import *

import quantum.plugins.linuxbridge.agent.linuxbridge_quantum_agent\
                                                     as linux_agent
from quantum.plugins.linuxbridge.common import constants as lconst
from quantum.plugins.linuxbridge import LinuxBridgePlugin
from quantum.plugins.linuxbridge.db import l2network_db as cdb
import quantum.db.api as db


LOG.getLogger(__name__)


class LinuxBridgeAgentTest(unittest.TestCase):

    def test_add_gateway_interface(
                  self, tenant_id="test_tenant", network_name="test_network",
                  interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc',
                  mac_address='fe:16:3e:51:60:dd'):

        LOG.debug("test_tap_gateway_interface - START")
        new_network =\
               self._linuxbridge_plugin.create_network(tenant_id, network_name)
        new_port = self._linuxbridge_plugin.create_port(
                         tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
        self._linuxbridge_plugin.plug_interface(
                         tenant_id, new_network[lconst.NET_ID],
                         new_port[lconst.PORT_ID], interface_id)
        bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
        self.create_bridge(bridge_name)
        device_name = self.gw_name_prefix + new_network[lconst.NET_ID][0:11]
        self.create_device(device_name, mac_address)

        vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
        vlan_id = vlan_bind[lconst.VLANID]

        self._linuxbridge_quantum_agent.process_port_binding(
                         new_port[lconst.PORT_ID], new_network[lconst.NET_ID],
                         device_name, str(vlan_id))
        list_interface = self._linuxbridge_quantum_agent.linux_br.\
                               get_interfaces_on_bridge(bridge_name)

        self.assertTrue(device_name in list_interface)
        for interface in list_interface:
            self._linuxbridge_quantum_agent.linux_br.remove_interface(
                                               bridge_name, interface)
            self.delete_device(interface)
        self.delete_bridge(bridge_name)
        self.tearDownUnplugInterface(tenant_id, new_network[lconst.NET_ID],
                                     new_port[lconst.PORT_ID])

        LOG.debug("test_add_gateway_interface - END")

    def test_add_tap_interface(
                  self, tenant_id="test_tenant", network_name="test_network",
                  interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc',
                  mac_address='fe:16:3e:51:60:dd'):

        LOG.debug("test_add_tap_interface - START")
        new_network =\
               self._linuxbridge_plugin.create_network(tenant_id, network_name)
        new_port = self._linuxbridge_plugin.create_port(
                         tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
        self._linuxbridge_plugin.plug_interface(
                         tenant_id, new_network[lconst.NET_ID],
                         new_port[lconst.PORT_ID], interface_id)
        bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
        self.create_bridge(bridge_name)
        device_name = self.tap_name_prefix + interface_id[0:11]
        self.create_device(device_name, mac_address)

        vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
        vlan_id = vlan_bind[lconst.VLANID]

        self._linuxbridge_quantum_agent.process_port_binding(
                         new_port[lconst.PORT_ID], new_network[lconst.NET_ID],
                         interface_id, str(vlan_id))
        list_interface = self._linuxbridge_quantum_agent.linux_br.\
                                        get_interfaces_on_bridge(bridge_name)

        self.assertTrue(device_name in list_interface)
        for interface in list_interface:
            self._linuxbridge_quantum_agent.linux_br.remove_interface(
                                               bridge_name, interface)
            self.delete_device(interface)
        self.delete_bridge(bridge_name)
        self.tearDownUnplugInterface(tenant_id, new_network[lconst.NET_ID],
                                     new_port[lconst.PORT_ID])

        LOG.debug("test_add_tap_interface -END")

    def test_remove_interface(
                  self, tenant_id="test_tenant", network_name="test_network",
                  interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc',
                  mac_address='fe:16:3e:51:60:dd'):

        LOG.debug("test_remove_interface - START")
        new_network =\
               self._linuxbridge_plugin.create_network(tenant_id, network_name)
        new_port = self._linuxbridge_plugin.create_port(
                         tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
        self._linuxbridge_plugin.plug_interface(
                         tenant_id, new_network[lconst.NET_ID],
                         new_port[lconst.PORT_ID], interface_id)
        bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
        self.create_bridge(bridge_name)
        device_name = self.tap_name_prefix + interface_id[0:11]
        self.create_device(device_name, mac_address)

        vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
        vlan_id = vlan_bind[lconst.VLANID]

        self._linuxbridge_quantum_agent.process_port_binding(
                         new_port[lconst.PORT_ID], new_network[lconst.NET_ID],
                         interface_id, str(vlan_id))
        list_interface = self._linuxbridge_quantum_agent.linux_br.\
                              get_interfaces_on_bridge(bridge_name)

        self._linuxbridge_quantum_agent.linux_br.remove_interface(bridge_name,
                                                            device_name)
        list_interface = self._linuxbridge_quantum_agent.linux_br.\
                              get_interfaces_on_bridge(bridge_name)
        self.assertFalse(device_name in list_interface)
        for interface in list_interface:
            self._linuxbridge_quantum_agent.linux_br.remove_interface(
                                               bridge_name, interface)
            self.delete_device(interface)
        self.delete_device(device_name)
        self.delete_bridge(bridge_name)
        self.tearDownUnplugInterface(tenant_id, new_network[lconst.NET_ID],
                                     new_port[lconst.PORT_ID])

        LOG.debug("test_remove_interface -END")

    def test_ensure_vlan_bridge(
                  self, tenant_id="test_tenant", network_name="test_network",
                  interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc'):

        LOG.debug("test_ensure_vlan_bridge - START")
        new_network =\
               self._linuxbridge_plugin.create_network(tenant_id, network_name)
        new_port = self._linuxbridge_plugin.create_port(
                         tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
        self._linuxbridge_plugin.plug_interface(
                         tenant_id, new_network[lconst.NET_ID],
                         new_port[lconst.PORT_ID], interface_id)
        bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
        vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
        vlan_id = vlan_bind[lconst.VLANID]
        vlan_subinterface = self.physical_interface + '.' + str(vlan_id)

        self._linuxbridge_quantum_agent.linux_br.ensure_vlan_bridge(
                         new_network[lconst.NET_ID], str(vlan_id))
        list_quantum_bridges = self._linuxbridge_quantum_agent.linux_br.\
                                    get_all_quantum_bridges()
        self.assertTrue(bridge_name in list_quantum_bridges)
        list_interface = self._linuxbridge_quantum_agent.linux_br.\
                              get_interfaces_on_bridge(bridge_name)
        self.assertTrue(vlan_subinterface in list_interface)

        for interface in list_interface:
            self._linuxbridge_quantum_agent.linux_br.remove_interface(
                                               bridge_name, interface)
            self.delete_device(interface)
        self.delete_bridge(bridge_name)
        self.tearDownUnplugInterface(tenant_id, new_network[lconst.NET_ID],
                                     new_port[lconst.PORT_ID])

        LOG.debug("test_ensure_vlan_bridge -END")

    def test_delete_vlan_bridge(
                  self, tenant_id="test_tenant", network_name="test_network",
                  interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc'):

        LOG.debug("test_delete_vlan_bridge - START")
        new_network =\
               self._linuxbridge_plugin.create_network(tenant_id, network_name)
        new_port = self._linuxbridge_plugin.create_port(
                         tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
        self._linuxbridge_plugin.plug_interface(
                         tenant_id, new_network[lconst.NET_ID],
                         new_port[lconst.PORT_ID], interface_id)
        bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
        vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
        vlan_id = vlan_bind[lconst.VLANID]
        vlan_subinterface = self.physical_interface + '.' + str(vlan_id)

        self._linuxbridge_quantum_agent.linux_br.ensure_vlan_bridge(
                         new_network[lconst.NET_ID], str(vlan_id))
        self._linuxbridge_quantum_agent.linux_br.delete_vlan_bridge(
                                                 bridge_name)

        self.assertEquals(self.device_exists(vlan_subinterface), False)
        self.assertEquals(self.device_exists(bridge_name), False)
        self.tearDownUnplugInterface(tenant_id, new_network[lconst.NET_ID],
                                     new_port[lconst.PORT_ID])

        LOG.debug("test_delete_vlan_bridge - END")

    def test_process_deleted_networks(
                  self, tenant_id="test_tenant", network_name="test_network",
                  interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc'):

        LOG.debug("test_delete_vlan_bridge - START")
        new_network =\
               self._linuxbridge_plugin.create_network(tenant_id, network_name)
        new_port = self._linuxbridge_plugin.create_port(
                         tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
        self._linuxbridge_plugin.plug_interface(
                         tenant_id, new_network[lconst.NET_ID],
                         new_port[lconst.PORT_ID], interface_id)
        bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
        vlan_bindings = {}
        vlan_bindings[new_network[lconst.NET_ID]] =\
                       cdb.get_vlan_binding(new_network[lconst.NET_ID])
        vlan_id = vlan_bindings[new_network[lconst.NET_ID]][lconst.VLANID]
        vlan_subinterface = self.physical_interface + '.' + str(vlan_id)

        self._linuxbridge_quantum_agent.linux_br.ensure_vlan_bridge(
                         new_network[lconst.NET_ID], str(vlan_id))
        self.tearDownUnplugInterface(tenant_id, new_network[lconst.NET_ID],
                                     new_port[lconst.PORT_ID])
        vlan_bindings = {}
        self._linuxbridge_quantum_agent.process_deleted_networks(vlan_bindings)

        self.assertEquals(self.device_exists(vlan_subinterface), False)
        self.assertEquals(self.device_exists(bridge_name), False)
        LOG.debug("test_delete_vlan_bridge - END")

    def test_process_unplugged_tap_interface(
                  self, tenant_id="test_tenant", network_name="test_network",
                  interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc',
                  mac_address='fe:16:3e:51:60:dd'):

        LOG.debug("test_process_unplugged_tap_interface - START")
        new_network =\
               self._linuxbridge_plugin.create_network(tenant_id, network_name)
        new_port = self._linuxbridge_plugin.create_port(
                         tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
        self._linuxbridge_plugin.plug_interface(
                         tenant_id, new_network[lconst.NET_ID],
                         new_port[lconst.PORT_ID], interface_id)
        bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
        self.create_bridge(bridge_name)
        device_name = self.tap_name_prefix + interface_id[0:11]
        self.create_device(device_name, mac_address)

        vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
        vlan_id = vlan_bind[lconst.VLANID]

        self._linuxbridge_quantum_agent.process_port_binding(
                         new_port[lconst.PORT_ID], new_network[lconst.NET_ID],
                         interface_id, str(vlan_id))
        list_interface = self._linuxbridge_quantum_agent.linux_br.\
                              get_interfaces_on_bridge(bridge_name)
        self._linuxbridge_plugin.unplug_interface(tenant_id,
                                                  new_network[lconst.NET_ID],
                                                  new_port[lconst.PORT_ID])
        plugged_interface = []
        self._linuxbridge_quantum_agent.process_unplugged_interfaces(
                                                          plugged_interface)
        list_interface = self._linuxbridge_quantum_agent.linux_br.\
                              get_interfaces_on_bridge(bridge_name)
        self.assertFalse(device_name in list_interface)
        for interface in list_interface:
            self._linuxbridge_quantum_agent.linux_br.remove_interface(
                                               bridge_name, interface)
            self.delete_device(interface)
        self.delete_device(device_name)
        self.delete_bridge(bridge_name)
        self.tearDownNetworkPort(tenant_id, new_network[lconst.NET_ID],
                                     new_port[lconst.PORT_ID])

        LOG.debug("test_test_process_unplugged_tap_interface -END")

    def test_process_unplugged_gw_interface(
                  self, tenant_id="test_tenant", network_name="test_network",
                  interface_id='fe701ddf-26a2-42ea-b9e6-7313d1c522cc',
                  mac_address='fe:16:3e:51:60:dd'):

        LOG.debug("test_process_unplugged_gw_interface - START")
        new_network =\
               self._linuxbridge_plugin.create_network(tenant_id, network_name)
        new_port = self._linuxbridge_plugin.create_port(
                         tenant_id, new_network[lconst.NET_ID], lconst.PORT_UP)
        self._linuxbridge_plugin.plug_interface(
                         tenant_id, new_network[lconst.NET_ID],
                         new_port[lconst.PORT_ID], interface_id)
        bridge_name = self.br_name_prefix + new_network[lconst.NET_ID][0:11]
        self.create_bridge(bridge_name)
        device_name = self.gw_name_prefix + new_network[lconst.NET_ID][0:11]
        self.create_device(device_name, mac_address)

        vlan_bind = cdb.get_vlan_binding(new_network[lconst.NET_ID])
        vlan_id = vlan_bind[lconst.VLANID]

        self._linuxbridge_quantum_agent.process_port_binding(
                         new_port[lconst.PORT_ID], new_network[lconst.NET_ID],
                         interface_id, str(vlan_id))
        list_interface = self._linuxbridge_quantum_agent.linux_br.\
                              get_interfaces_on_bridge(bridge_name)
        self._linuxbridge_plugin.unplug_interface(tenant_id,
                                                  new_network[lconst.NET_ID],
                                                  new_port[lconst.PORT_ID])
        plugged_interface = []
        self._linuxbridge_quantum_agent.process_unplugged_interfaces(
                                                          plugged_interface)
        list_interface = self._linuxbridge_quantum_agent.linux_br.\
                              get_interfaces_on_bridge(bridge_name)
        self.assertFalse(device_name in list_interface)
        for interface in list_interface:
            self._linuxbridge_quantum_agent.linux_br.remove_interface(
                                               bridge_name, interface)
            self.delete_device(interface)
        self.delete_device(device_name)
        self.delete_bridge(bridge_name)
        self.tearDownNetworkPort(tenant_id, new_network[lconst.NET_ID],
                                     new_port[lconst.PORT_ID])

        LOG.debug("test_test_process_unplugged_gw_interface -END")

    def create_bridge(self, bridge_name):
        """
        Create a bridge
        """
        self.run_cmd(['brctl', 'addbr', bridge_name])
        self.run_cmd(['brctl', 'setfd', bridge_name, str(0)])
        self.run_cmd(['brctl', 'stp', bridge_name, 'off'])
        self.run_cmd(['ip', 'link', 'set', bridge_name, 'up'])

    def delete_bridge(self, bridge_name):
        """
        Delete a bridge
        """
        self.run_cmd(['ip', 'link', 'set', bridge_name, 'down'])
        self.run_cmd(['brctl', 'delbr', bridge_name])

    def create_device(self, dev, mac_address):
        self.run_cmd(['ip', 'tuntap', 'add', dev, 'mode', 'tap'])
        self.run_cmd(['ip', 'link', 'set', dev, "address", mac_address])
        self.run_cmd(['ip', 'link', 'set', dev, 'up'])

    def delete_device(self, dev):
        self.run_cmd(['ip', 'link', 'delete', dev])

    def setUp(self):
        """
        Set up function
        """
        self.tenant_id = "test_tenant"
        self.network_name = "test_network"
        self.config_file = os.path.join(os.path.dirname(__file__), os.pardir,
                                        os.pardir, os.pardir, os.pardir,
                                        os.pardir, "etc", "quantum",
                                        "plugins", "linuxbridge",
                                        "linuxbridge_conf.ini")

        config = ConfigParser.ConfigParser()
        self.br_name_prefix = "brq"
        self.gw_name_prefix = "gw-"
        self.tap_name_prefix = "tap"
        self._linuxbridge_plugin = LinuxBridgePlugin.LinuxBridgePlugin()
        try:
            fh = open(self.config_file)
            fh.close()
            config.read(self.config_file)
            self.physical_interface = config.get("LINUX_BRIDGE",
                                                 "physical_interface")
            self.polling_interval = config.get("AGENT", "polling_interval")
        except Exception, e:
            LOG.error("Unable to parse config file \"%s\": \nException%s"
                      % (self.config_file, str(e)))
            sys.exit(1)
        self._linuxbridge = linux_agent.LinuxBridge(self.br_name_prefix,
                                                    self.physical_interface)
        self._linuxbridge_quantum_agent = linux_agent.LinuxBridgeQuantumAgent(
                                                    self.br_name_prefix,
                                                    self.physical_interface,
                                                    self.polling_interval)

    def run_cmd(self, args):
        LOG.debug("Running command: " + " ".join(args))
        p = Popen(args, stdout=PIPE)
        retval = p.communicate()[0]
        if p.returncode == -(signal.SIGALRM):
            LOG.debug("Timeout running command: " + " ".join(args))
        if retval:
            LOG.debug("Command returned: %s" % retval)
        return retval

    def device_exists(self, device):
        """Check if ethernet device exists."""
        retval = self.run_cmd(['ip', 'link', 'show', 'dev', device])
        if retval:
            return True
        else:
            return False

    """
         Clean up functions after the tests
    """
    def tearDown(self):
        """Clear the test environment(Clean the Database)"""
        db.clear_db()

    def tearDownNetwork(self, tenant_id, network_dict_id):
        """
        Tear down the Network
        """
        self._linuxbridge_plugin.delete_network(tenant_id, network_dict_id)

    def tearDownUnplugInterface(self, tenant_id, network_dict_id, port_id):
        """
        Tear down the port
        """
        self._linuxbridge_plugin.unplug_interface(tenant_id, network_dict_id,
                                                  port_id)
        self.tearDownNetworkPort(tenant_id, network_dict_id, port_id)

    def tearDownNetworkPort(self, tenant_id, network_dict_id, port_id):
        """
        Tear down Network Port
        """
        self._linuxbridge_plugin.delete_port(tenant_id, network_dict_id,
                                             port_id)
        self.tearDownNetwork(tenant_id, network_dict_id)
