# Copyright (c) 2013 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 mock

from nova.network import model as network_model
from nova import test
from novadocker.virt.docker import driver as docker_driver
from novadocker.virt.docker import vifs


class DockerGenericVIFDriverTestCase(test.TestCase):

    REQUIRES_LOCKING = True
    gateway_bridge_4 = network_model.IP(address='10.11.12.1', type='gateway')
    dns_bridge_4 = network_model.IP(address='8.8.8.8', type=None)
    ips_bridge_4 = [network_model.IP(address='101.168.1.9', type='fixed',
                                     version=4)]

    subnet_bridge_4 = network_model.Subnet(cidr='10.11.1.0/24',
                                           dns=[dns_bridge_4],
                                           gateway=gateway_bridge_4,
                                           ips=ips_bridge_4,
                                           routes=None)

    network_bridge = network_model.Network(id='network-id-xxx-yyy-zzz',
                                           bridge='br100',
                                           label=None,
                                           subnets=[subnet_bridge_4],
                                           bridge_interface='eth0')

    vif_bridge = network_model.VIF(id='920be2f4-2b98-411e-890a-69bcabb2a5a0',
                                   address='00:11:22:33:44:55',
                                   network=network_bridge,
                                   type=network_model.VIF_TYPE_BRIDGE)

    network_iovisor = network_model.Network(id='network-id-xxx-yyy-zzz',
                                            bridge='br100',
                                            label='net1',
                                            subnets=[subnet_bridge_4],
                                            bridge_interface='eth0')

    vif_iovisor = network_model.VIF(id='920be2f4-2b98-411e-890a-69bcabb2a5a0',
                                    address='00:11:22:33:44:55',
                                    network=network_iovisor,
                                    type=network_model.VIF_TYPE_IOVISOR)

    def setUp(self):
        super(DockerGenericVIFDriverTestCase, self).setUp()

        def fake_fe_random_mac(self):
            return 'fe:16:3e:ff:ff:ff'

        self.stubs.Set(vifs.DockerGenericVIFDriver,
                       '_fe_random_mac',
                       fake_fe_random_mac)

    def test_plug_vifs_bridge(self):
        calls = [
            mock.call('ip', 'link', 'add', 'name', 'tap920be2f4-2b',
                      'type', 'veth', 'peer', 'name', 'ns920be2f4-2b',
                      run_as_root=True),
            mock.call('ip', 'link', 'set', 'tap920be2f4-2b', 'address',
                      'fe:16:3e:ff:ff:ff', run_as_root=True),
            mock.call('brctl', 'addif', 'br100', 'tap920be2f4-2b',
                      run_as_root=True),
            mock.call('ip', 'link', 'set', 'tap920be2f4-2b', 'up',
                      run_as_root=True)
        ]
        network_info = [self.vif_bridge]
        with mock.patch('nova.utils.execute') as ex:
            driver = docker_driver.DockerDriver(object)
            driver.plug_vifs({'name': 'fake_instance'}, network_info)
            ex.assert_has_calls(calls)

    def test_plug_vifs_bridge_two_interfaces(self):
        calls = [
            # interface 1
            mock.call('ip', 'link', 'add', 'name', 'tap920be2f4-2b',
                      'type', 'veth', 'peer', 'name', 'ns920be2f4-2b',
                      run_as_root=True),
            mock.call('ip', 'link', 'set', 'tap920be2f4-2b', 'address',
                      'fe:16:3e:ff:ff:ff', run_as_root=True),
            mock.call('brctl', 'addif', 'br100', 'tap920be2f4-2b',
                      run_as_root=True),
            mock.call('ip', 'link', 'set', 'tap920be2f4-2b', 'up',
                      run_as_root=True),
            # interface 2
            mock.call('ip', 'link', 'add', 'name', 'tap920be2f4-2b',
                      'type', 'veth', 'peer', 'name', 'ns920be2f4-2b',
                      run_as_root=True),
            mock.call('ip', 'link', 'set', 'tap920be2f4-2b', 'address',
                      'fe:16:3e:ff:ff:ff', run_as_root=True),
            mock.call('brctl', 'addif', 'br100', 'tap920be2f4-2b',
                      run_as_root=True),
            mock.call('ip', 'link', 'set', 'tap920be2f4-2b', 'up',
                      run_as_root=True),
        ]
        network_info = [self.vif_bridge, self.vif_bridge]
        with mock.patch('nova.utils.execute') as ex:
            driver = docker_driver.DockerDriver(object)
            driver.plug_vifs({'name': 'fake_instance'}, network_info)
            ex.assert_has_calls(calls)

    def test_plug_vifs_ovs(self):
        iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0'
        calls = [
            mock.call('ip', 'link', 'add', 'name', 'tap920be2f4-2b',
                      'type', 'veth', 'peer', 'name', 'ns920be2f4-2b',
                      run_as_root=True),

            mock.call('ovs-vsctl', '--timeout=120', '--', '--if-exists',
                      'del-port', 'tap920be2f4-2b', '--', 'add-port',
                      'br-int', 'tap920be2f4-2b',
                      '--', 'set', 'Interface', 'tap920be2f4-2b',
                      'external-ids:iface-id=%s' % iface_id,
                      'external-ids:iface-status=active',
                      'external-ids:attached-mac=00:11:22:33:44:55',
                      'external-ids:vm-uuid=instance_uuid',
                      run_as_root=True),
            mock.call('ip', 'link', 'set', 'tap920be2f4-2b', 'up',
                      run_as_root=True),
        ]
        network_info = [
            {'network': {'bridge': 'br-int',
                         'subnets': [{'gateway': {'address': '10.11.12.1'},
                                      'cidr': '10.11.12.0/24',
                                      'ips': [{'address': '10.11.12.3',
                                               'type': 'fixed', 'version': 4}]
                                      }]},
             'address': '00:11:22:33:44:55',
             'id': iface_id,
             'type': network_model.VIF_TYPE_OVS}]
        with mock.patch('nova.utils.execute') as ex:
            driver = docker_driver.DockerDriver(object)
            driver.plug_vifs({'name': 'fake_instance',
                              'uuid': 'instance_uuid'}, network_info)
            ex.assert_has_calls(calls)

    def test_plug_vifs_ovs_hybrid(self):
        iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0'
        calls = [
            mock.call('brctl', 'addbr', 'qbr920be2f4-2b', run_as_root=True),

            mock.call('brctl', 'setfd', 'qbr920be2f4-2b', 0, run_as_root=True),
            mock.call('brctl', 'stp', 'qbr920be2f4-2b', 'off',
                      run_as_root=True),
            mock.call('tee', "/sys/class/net/qbr920be2f4-2b/bridge/multicast_"
                      "snooping", run_as_root=True, process_input='0',
                      check_exit_code=[0, 1]),
            mock.call('ip', 'link', 'add', 'qvb920be2f4-2b', 'type',
                      'veth', 'peer', 'name', 'qvo920be2f4-2b',
                      run_as_root=True),
            mock.call('ip', 'link', 'set', 'qvb920be2f4-2b', 'up',
                      run_as_root=True),
            mock.call('ip', 'link', 'set', 'qvb920be2f4-2b', 'promisc', 'on',
                      run_as_root=True),
            mock.call('ip', 'link', 'set', 'qvo920be2f4-2b', 'up',
                      run_as_root=True),
            mock.call('ip', 'link', 'set', 'qvo920be2f4-2b', 'promisc', 'on',
                      run_as_root=True),
            mock.call('ip', 'link', 'set', 'qbr920be2f4-2b', 'up',
                      run_as_root=True),
            mock.call('brctl', 'addif', 'qbr920be2f4-2b', 'qvb920be2f4-2b',
                      run_as_root=True),
            mock.call('ovs-vsctl', '--timeout=120', '--', '--if-exists',
                      'del-port', 'qvo920be2f4-2b', '--', 'add-port',
                      'br-int', 'qvo920be2f4-2b', '--', 'set', 'Interface',
                      'qvo920be2f4-2b', "external-ids:iface-id=920be2f4-2b98-"
                      "411e-890a-69bcabb2a5a0",
                      'external-ids:iface-status=active',
                      'external-ids:attached-mac=00:11:22:33:44:55',
                      'external-ids:vm-uuid=instance_uuid', run_as_root=True),
            mock.call('ip', 'link', 'add', 'name', 'tap920be2f4-2b', 'type',
                      'veth', 'peer', 'name', 'ns920be2f4-2b',
                      run_as_root=True),
            mock.call('brctl', 'addif', 'qbr920be2f4-2b', 'tap920be2f4-2b',
                      run_as_root=True),
            mock.call('ip', 'link', 'set', 'tap920be2f4-2b', 'up',
                      run_as_root=True),
        ]
        network_info = [
            {'network': {'bridge': 'br-int',
                         'subnets': [{'gateway': {'address': '10.11.12.1'},
                                      'cidr': '10.11.12.0/24',
                                      'ips': [{'address': '10.11.12.3',
                                               'type': 'fixed', 'version': 4}]
                                      }]},
             'address': '00:11:22:33:44:55',
             'id': iface_id,
             'details': {'port_filter': True, 'ovs_hybrid_plug': True},
             'type': network_model.VIF_TYPE_OVS}]
        with mock.patch('nova.utils.execute') as ex:
            driver = docker_driver.DockerDriver(object)
            driver.plug_vifs({'name': 'fake_instance',
                              'uuid': 'instance_uuid'}, network_info)
            ex.assert_has_calls(calls)

    def test_unplug_vifs_ovs(self):
        iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0'
        calls = [
            mock.call('ovs-vsctl', '--timeout=120', '--', '--if-exists',
                      'del-port', 'br-int', 'tap920be2f4-2b',
                      run_as_root=True)
        ]
        network_info = [
            {'network': {'bridge': 'br-int',
                         'subnets': [{'gateway': {'address': '10.11.12.1'},
                                      'cidr': '10.11.12.0/24',
                                      'ips': [{'address': '10.11.12.3',
                                               'type': 'fixed', 'version': 4}]
                                      }]},
             'devname': 'tap920be2f4-2b',
             'address': '00:11:22:33:44:55',
             'id': iface_id,
             'type': network_model.VIF_TYPE_OVS}]
        with mock.patch('nova.utils.execute') as ex:
            driver = docker_driver.DockerDriver(object)
            driver.unplug_vifs({'name': 'fake_instance',
                                'uuid': 'instance_uuid'}, network_info)
            ex.assert_has_calls(calls)

    def test_plug_vifs_midonet(self):
        iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0'
        host_side_veth = 'tap920be2f4-2b'
        calls = [
            mock.call('ip', 'link', 'add', 'name', host_side_veth,
                      'type', 'veth', 'peer', 'name', 'ns920be2f4-2b',
                      run_as_root=True),

            mock.call('ip', 'link', 'set', host_side_veth, 'up',
                      run_as_root=True),

            mock.call('mm-ctl', '--bind-port', iface_id, host_side_veth,
                      run_as_root=True),
        ]

        network_info = [{'id': iface_id,
                         'type': network_model.VIF_TYPE_MIDONET}]

        with mock.patch('nova.utils.execute') as ex:
            driver = docker_driver.DockerDriver(object)
            driver.plug_vifs({'name': 'fake_instance',
                              'uuid': 'instance_uuid'}, network_info)
            ex.assert_has_calls(calls)

    def test_unplug_vifs_midonet(self):
        iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0'
        calls = [
            mock.call('mm-ctl', '--unbind-port', iface_id, run_as_root=True)
        ]
        network_info = [{'id': iface_id,
                         'type': network_model.VIF_TYPE_MIDONET}]
        with mock.patch('nova.utils.execute') as ex:
            driver = docker_driver.DockerDriver(object)
            driver.unplug_vifs({'name': 'fake_instance',
                                'uuid': 'instance_uuid'}, network_info)
            ex.assert_has_calls(calls)

    def test_plug_vifs_iovisor(self):
        iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0'
        tenant_id = 'tenant1'
        calls = [
            mock.call('ip', 'link', 'add', 'name', 'tap920be2f4-2b', 'type',
                      'veth', 'peer', 'name', 'ns920be2f4-2b',
                      run_as_root=True),

            mock.call('ifc_ctl', 'gateway', 'add_port', 'tap920be2f4-2b',
                      run_as_root=True),

            mock.call('ifc_ctl', 'gateway', 'ifup', 'tap920be2f4-2b',
                      'access_vm', "net1_" + iface_id, '00:11:22:33:44:55',
                      'pgtag2=network-id-xxx-yyy-zzz',
                      'pgtag1=%s' % tenant_id, run_as_root=True),

            mock.call('ip', 'link', 'set', 'tap920be2f4-2b', 'up',
                      run_as_root=True),
        ]

        network_info = [self.vif_iovisor]

        with mock.patch('nova.utils.execute') as ex:
            driver = docker_driver.DockerDriver(object)
            driver.plug_vifs({'name': 'fake_instance',
                              'uuid': 'instance_uuid',
                              'project_id': tenant_id}, network_info)
            ex.assert_has_calls(calls)

    def test_unplug_vifs_iovisor(self):
        iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0'
        tenant_id = 'tenant1'
        calls = [
            mock.call('ifc_ctl', 'gateway', 'ifdown', 'tap920be2f4-2b',
                      'access_vm', 'net1_' + iface_id, '00:11:22:33:44:55',
                      run_as_root=True),
            mock.call('ifc_ctl', 'gateway', 'del_port', 'tap920be2f4-2b',
                      run_as_root=True)
        ]
        network_info = [self.vif_iovisor]
        with mock.patch('nova.utils.execute') as ex:
            driver = docker_driver.DockerDriver(object)
            driver.unplug_vifs({'name': 'fake_instance',
                                'uuid': 'instance_uuid',
                                'project_id': tenant_id}, network_info)
            ex.assert_has_calls(calls)

    def test_unplug_vifs_ovs_hybrid(self):
        iface_id = '920be2f4-2b98-411e-890a-69bcabb2a5a0'
        calls = [
            mock.call('ovs-vsctl', '--timeout=120', '--', '--if-exists',
                      'del-port', 'br-int', 'qvo920be2f4-2b',
                      run_as_root=True)
        ]
        network_info = [
            {'network': {'bridge': 'br-int',
                         'subnets': [{'gateway': {'address': '10.11.12.1'},
                                      'cidr': '10.11.12.0/24',
                                      'ips': [{'address': '10.11.12.3',
                                               'type': 'fixed', 'version': 4}]
                                      }]},
             'devname': 'tap920be2f4-2b',
             'address': '00:11:22:33:44:55',
             'id': iface_id,
             'details': {'port_filter': True, 'ovs_hybrid_plug': True},
             'type': network_model.VIF_TYPE_OVS}]
        with mock.patch('nova.utils.execute') as ex:
            driver = docker_driver.DockerDriver(object)
            driver.unplug_vifs({'name': 'fake_instance',
                                'uuid': 'instance_uuid'}, network_info)
            ex.assert_has_calls(calls)

    @mock.patch.object(docker_driver.DockerDriver,
                       '_find_container_by_uuid',
                       return_value={'id': 'fake_id'})
    @mock.patch.object(docker_driver.DockerDriver,
                       '_find_container_pid',
                       return_value=1234)
    def test_attach_vifs(self, mock_find_by_uuid, mock_find_pid):
        calls = [
            mock.call('ln', '-sf', '/proc/1234/ns/net',
                      '/var/run/netns/fake_id', run_as_root=True),
            mock.call('ip', 'netns', 'exec', 'fake_id', 'ip', 'link',
                      'set', 'lo', 'up', run_as_root=True),
            mock.call('ip', 'link', 'set', 'ns920be2f4-2b', 'netns',
                      'fake_id', run_as_root=True),
            mock.call('ip', 'netns', 'exec', 'fake_id', 'ip', 'link',
                      'set', 'ns920be2f4-2b', 'address', '00:11:22:33:44:55',
                      run_as_root=True),
            mock.call('ip', 'netns', 'exec', 'fake_id',
                      'ip', 'addr', 'add', '10.11.12.3/24', 'dev',
                      'ns920be2f4-2b', run_as_root=True),
            mock.call('ip', 'netns', 'exec', 'fake_id',
                      'ip', 'link', 'set',
                      'ns920be2f4-2b', 'up', run_as_root=True),
            mock.call('ip', 'netns', 'exec', 'fake_id', 'ip', 'route',
                      'replace', 'default', 'via', '10.11.12.1', 'dev',
                      'ns920be2f4-2b', run_as_root=True),
            mock.call('ip', 'netns', 'exec', 'fake_id', 'ethtool', '--offload',
                      'ns920be2f4-2b', 'tso', 'off', run_as_root=True)
        ]
        network_info = [
            {'network': {'bridge': 'br100',
                         'subnets': [{'gateway': {'address': '10.11.12.1'},
                                      'cidr': '10.11.12.0/24',
                                      'ips': [{'address': '10.11.12.3',
                                               'type': 'fixed', 'version': 4}]
                                      }]},
             'address': '00:11:22:33:44:55',
             'id': '920be2f4-2b98-411e-890a-69bcabb2a5a0',
             'type': network_model.VIF_TYPE_BRIDGE}]
        with mock.patch('nova.utils.execute') as ex:
            driver = docker_driver.DockerDriver(object)
            driver._attach_vifs({'uuid': 'fake_uuid'}, network_info)
            ex.assert_has_calls(calls)

    @mock.patch.object(docker_driver.DockerDriver,
                       '_find_container_by_uuid',
                       return_value={'id': 'fake_id'})
    @mock.patch.object(docker_driver.DockerDriver,
                       '_find_container_pid',
                       return_value=1234)
    def test_attach_vifs_two_interfaces(self, mock_find_by_uuid,
                                        mock_find_pid):
        calls = [
            mock.call('ln', '-sf', '/proc/1234/ns/net',
                      '/var/run/netns/fake_id', run_as_root=True),
            mock.call('ip', 'netns', 'exec', 'fake_id', 'ip', 'link',
                      'set', 'lo', 'up', run_as_root=True),
            # interface 1
            mock.call('ip', 'link', 'set', 'ns920be2f4-2b', 'netns', 'fake_id',
                      run_as_root=True),
            mock.call('ip', 'netns', 'exec', 'fake_id', 'ip', 'link',
                      'set', 'ns920be2f4-2b', 'address', '00:11:22:33:44:55',
                      run_as_root=True),
            mock.call('ip', 'netns', 'exec', 'fake_id',
                      'ip', 'addr', 'add', '10.11.12.3/24', 'dev',
                      'ns920be2f4-2b', run_as_root=True),
            mock.call('ip', 'netns', 'exec', 'fake_id',
                      'ip', 'link', 'set',
                      'ns920be2f4-2b', 'up', run_as_root=True),
            mock.call('ip', 'netns', 'exec', 'fake_id', 'ip', 'route',
                      'replace', 'default', 'via', '10.11.12.1', 'dev',
                      'ns920be2f4-2b', run_as_root=True),
            mock.call('ip', 'netns', 'exec', 'fake_id', 'ethtool', '--offload',
                      'ns920be2f4-2b', 'tso', 'off', run_as_root=True),
            # interface 2
            mock.call('ip', 'link', 'set', 'ns920be2f4-2b', 'netns', 'fake_id',
                      run_as_root=True),
            mock.call('ip', 'netns', 'exec', 'fake_id', 'ip', 'link',
                      'set', 'ns920be2f4-2b', 'address', '00:11:22:33:44:66',
                      run_as_root=True),
            mock.call('ip', 'netns', 'exec', 'fake_id',
                      'ip', 'addr', 'add', '10.13.12.3/24', 'dev',
                      'ns920be2f4-2b', run_as_root=True),
            mock.call('ip', 'netns', 'exec', 'fake_id',
                      'ip', 'link', 'set',
                      'ns920be2f4-2b', 'up', run_as_root=True),
            mock.call('ip', 'netns', 'exec', 'fake_id', 'ip', 'route',
                      'replace', 'default', 'via', '10.13.12.1', 'dev',
                      'ns920be2f4-2b', run_as_root=True),
            mock.call('ip', 'netns', 'exec', 'fake_id', 'ethtool', '--offload',
                      'ns920be2f4-2b', 'tso', 'off', run_as_root=True)
        ]
        network_info = [
            {'network': {'bridge': 'br100',
                         'subnets': [{'gateway': {'address': '10.11.12.1'},
                                      'cidr': '10.11.12.0/24',
                                      'ips': [{'address': '10.11.12.3',
                                               'type': 'fixed', 'version': 4}],
                                      }]},
             'address': '00:11:22:33:44:55',
             'type': network_model.VIF_TYPE_BRIDGE,
             'id': '920be2f4-2b98-411e-890a-69bcabb2a5a0'},
            {'network': {'bridge': 'br100',
                         'subnets': [{'gateway': {'address': '10.13.12.1'},
                                      'cidr': '10.13.12.0/24',
                                      'ips': [{'address': '10.13.12.3',
                                               'type': 'fixed', 'version': 4}]
                                      }]},
             'address': '00:11:22:33:44:66',
             'type': network_model.VIF_TYPE_BRIDGE,
             'id': '920be2f4-2b98-411e-890a-69bcabb2a5a0'}]
        with mock.patch('nova.utils.execute') as ex:
            driver = docker_driver.DockerDriver(object)
            driver._attach_vifs({'uuid': 'fake_uuid'}, network_info)
            ex.assert_has_calls(calls)
