# Copyright 2012 VMware, 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.
#

import contextlib
import time

import mock
from oslo_config import cfg
from oslo_log import log

from neutron.agent.common import ovs_lib
from neutron.agent.linux import ip_lib
from neutron.plugins.common import constants as p_const
from neutron.plugins.openvswitch.agent import ovs_neutron_agent
from neutron.plugins.openvswitch.common import constants
from neutron.tests import base


# Useful global dummy variables.
NET_UUID = '3faeebfe-5d37-11e1-a64b-000c29d5f0a7'
LS_ID = 420
LV_ID = 42
LV_IDS = [42, 43]
VIF_ID = '404deaec-5d37-11e1-a64b-000c29d5f0a8'
VIF_MAC = '3c:09:24:1e:78:23'
OFPORT_NUM = 1
VIF_PORT = ovs_lib.VifPort('port', OFPORT_NUM,
                           VIF_ID, VIF_MAC, 'switch')
VIF_PORTS = {VIF_ID: VIF_PORT}
LVM = ovs_neutron_agent.LocalVLANMapping(LV_ID, 'gre', None, LS_ID, VIF_PORTS)
LVM_FLAT = ovs_neutron_agent.LocalVLANMapping(
    LV_ID, 'flat', 'net1', LS_ID, VIF_PORTS)
LVM_VLAN = ovs_neutron_agent.LocalVLANMapping(
    LV_ID, 'vlan', 'net1', LS_ID, VIF_PORTS)
FIXED_IPS = [{'subnet_id': 'my-subnet-uuid',
              'ip_address': '1.1.1.1'}]
VM_DEVICE_OWNER = "compute:None"

TUN_OFPORTS = {p_const.TYPE_GRE: {'ip1': '11', 'ip2': '12'}}

BCAST_MAC = "01:00:00:00:00:00/01:00:00:00:00:00"
UCAST_MAC = "00:00:00:00:00:00/01:00:00:00:00:00"


class DummyPort(object):
    def __init__(self, interface_id):
        self.interface_id = interface_id


class DummyVlanBinding(object):
    def __init__(self, network_id, vlan_id):
        self.network_id = network_id
        self.vlan_id = vlan_id


class TunnelTest(base.BaseTestCase):
    USE_VETH_INTERCONNECTION = False
    VETH_MTU = None

    def setUp(self):
        super(TunnelTest, self).setUp()
        cfg.CONF.set_default('firewall_driver',
                             'neutron.agent.firewall.NoopFirewallDriver',
                             group='SECURITYGROUP')
        cfg.CONF.set_override('report_interval', 0, 'AGENT')

        self.INT_BRIDGE = 'integration_bridge'
        self.TUN_BRIDGE = 'tunnel_bridge'
        self.MAP_TUN_BRIDGE = 'tun_br_map'
        self.NET_MAPPING = {'net1': self.MAP_TUN_BRIDGE}
        self.INT_OFPORT = 11111
        self.TUN_OFPORT = 22222
        self.MAP_TUN_INT_OFPORT = 33333
        self.MAP_TUN_PHY_OFPORT = 44444

        self.inta = mock.Mock()
        self.intb = mock.Mock()

        self.ovs_bridges = {self.INT_BRIDGE: mock.Mock(),
                            self.TUN_BRIDGE: mock.Mock(),
                            self.MAP_TUN_BRIDGE: mock.Mock(),
                            }
        self.ovs_int_ofports = {
            'patch-tun': self.TUN_OFPORT,
            'int-%s' % self.MAP_TUN_BRIDGE: self.MAP_TUN_INT_OFPORT
        }

        self.mock_bridge = mock.patch.object(ovs_lib, 'OVSBridge').start()
        self.mock_bridge.side_effect = (lambda br_name:
                                        self.ovs_bridges[br_name])

        self.mock_int_bridge = self.ovs_bridges[self.INT_BRIDGE]
        self.mock_int_bridge.add_port.return_value = self.MAP_TUN_INT_OFPORT
        self.mock_int_bridge.add_patch_port.side_effect = (
            lambda tap, peer: self.ovs_int_ofports[tap])

        self.mock_map_tun_bridge = self.ovs_bridges[self.MAP_TUN_BRIDGE]
        self.mock_map_tun_bridge.br_name = self.MAP_TUN_BRIDGE
        self.mock_map_tun_bridge.add_port.return_value = (
            self.MAP_TUN_PHY_OFPORT)
        self.mock_map_tun_bridge.add_patch_port.return_value = (
            self.MAP_TUN_PHY_OFPORT)

        self.mock_tun_bridge = self.ovs_bridges[self.TUN_BRIDGE]
        self.mock_tun_bridge.add_port.return_value = self.INT_OFPORT
        self.mock_tun_bridge.add_patch_port.return_value = self.INT_OFPORT

        self.device_exists = mock.patch.object(ip_lib, 'device_exists').start()
        self.device_exists.return_value = True

        self.ipdevice = mock.patch.object(ip_lib, 'IPDevice').start()

        self.ipwrapper = mock.patch.object(ip_lib, 'IPWrapper').start()
        add_veth = self.ipwrapper.return_value.add_veth
        add_veth.return_value = [self.inta, self.intb]

        self.get_bridges = mock.patch.object(ovs_lib.BaseOVS,
                                             'get_bridges').start()
        self.get_bridges.return_value = [self.INT_BRIDGE,
                                         self.TUN_BRIDGE,
                                         self.MAP_TUN_BRIDGE]

        self.execute = mock.patch('neutron.agent.common.utils.execute').start()

        self._define_expected_calls()

    def _define_expected_calls(self):
        self.mock_bridge_expected = [
            mock.call(self.INT_BRIDGE),
            mock.call(self.MAP_TUN_BRIDGE),
            mock.call(self.TUN_BRIDGE),
        ]

        self.mock_int_bridge = self.ovs_bridges[self.INT_BRIDGE]
        self.mock_int_bridge_expected = [
            mock.call.create(),
            mock.call.set_secure_mode(),
            mock.call.delete_port('patch-tun'),
            mock.call.remove_all_flows(),
            mock.call.add_flow(priority=1, actions='normal'),
            mock.call.add_flow(priority=0, table=constants.CANARY_TABLE,
                               actions='drop'),
        ]

        self.mock_map_tun_bridge_expected = [
            mock.call.remove_all_flows(),
            mock.call.add_flow(priority=1, actions='normal'),
            mock.call.delete_port('phy-%s' % self.MAP_TUN_BRIDGE),
            mock.call.add_patch_port('phy-%s' % self.MAP_TUN_BRIDGE,
                                     constants.NONEXISTENT_PEER),
        ]
        self.mock_int_bridge_expected += [
            mock.call.delete_port('int-%s' % self.MAP_TUN_BRIDGE),
            mock.call.add_patch_port('int-%s' % self.MAP_TUN_BRIDGE,
                                     constants.NONEXISTENT_PEER),
        ]

        self.mock_int_bridge_expected += [
            mock.call.add_flow(priority=2,
                               in_port=self.MAP_TUN_INT_OFPORT,
                               actions='drop'),
            mock.call.set_db_attribute(
                'Interface', 'int-%s' % self.MAP_TUN_BRIDGE,
                'options:peer', 'phy-%s' % self.MAP_TUN_BRIDGE),
        ]
        self.mock_map_tun_bridge_expected += [
            mock.call.add_flow(priority=2,
                               in_port=self.MAP_TUN_PHY_OFPORT,
                               actions='drop'),
            mock.call.set_db_attribute(
                'Interface', 'phy-%s' % self.MAP_TUN_BRIDGE,
                'options:peer', 'int-%s' % self.MAP_TUN_BRIDGE),
        ]

        self.mock_tun_bridge_expected = [
            mock.call.reset_bridge(secure_mode=True),
            mock.call.add_patch_port('patch-int', 'patch-tun'),
        ]
        self.mock_int_bridge_expected += [
            mock.call.add_patch_port('patch-tun', 'patch-int')
        ]

        self.mock_tun_bridge_expected += [
            mock.call.remove_all_flows(),
            mock.call.add_flow(priority=1,
                               actions="resubmit(,%s)" %
                               constants.PATCH_LV_TO_TUN,
                               in_port=self.INT_OFPORT),
            mock.call.add_flow(priority=0, actions="drop"),
            mock.call.add_flow(priority=0, table=constants.PATCH_LV_TO_TUN,
                               dl_dst=UCAST_MAC,
              
                 actions="resubmit(,%s)" %
                               constants.UCAST_TO_TUN),
            mock.call.add_flow(priority=0, table=constants.PATCH_LV_TO_TUN,
                    
           dl_dst=B