# 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.

from unittest import mock
import uuid

from kuryr_kubernetes.controller.drivers import sriov as drvs
from kuryr_kubernetes.tests import base as test_base
from kuryr_kubernetes.tests import fake
from kuryr_kubernetes.tests.unit import kuryr_fixtures as k_fix

from kuryr_kubernetes import constants as k_const
from kuryr_kubernetes import os_vif_util as ovu
from kuryr_kubernetes import utils

from oslo_config import cfg as oslo_cfg


SRIOV_RESOURCE_NAME_A = "sriov_a"
SRIOV_RESOURCE_NAME_B = "sriov_b"

AMOUNT_FOR_SUBNET_A = 2
AMOUNT_FOR_SUBNET_B = 3

SRIOV_PHYSNET_A = "physnet_a"
SRIOV_PHYSNET_B = "physnet_b"


class TestSriovVIFDriver(test_base.TestCase):

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

        self._res_map = {SRIOV_PHYSNET_A: SRIOV_RESOURCE_NAME_A,
                         SRIOV_PHYSNET_B: SRIOV_RESOURCE_NAME_B}
        sriov_request = {drvs.sriov_make_resource(k_const.K8S_SRIOV_PREFIX,
                                                  SRIOV_RESOURCE_NAME_A): (
                         str(AMOUNT_FOR_SUBNET_A)),
                         drvs.sriov_make_resource(k_const.K8S_SRIOV_PREFIX,
                                                  SRIOV_RESOURCE_NAME_B): (
                         str(AMOUNT_FOR_SUBNET_B))}
        self._pod = fake.get_k8s_pod()
        self._pod['status'] = {'phase': k_const.K8S_POD_STATUS_PENDING}
        self._pod['spec'] = {
            'hostNetwork': False,
            'nodeName': 'hostname',
            'containers': [
                {
                    'resources': {
                        'requests': sriov_request
                    }
                }
            ]
        }

    def test_activate_vif(self):
        cls = drvs.SriovVIFDriver
        m_driver = mock.Mock(spec=cls)

        vif = mock.Mock()
        vif.active = False

        cls.activate_vif(m_driver, vif)
        self.assertEqual(True, vif.active)

    @mock.patch('kuryr_kubernetes.os_vif_util.osvif_to_neutron_fixed_ips')
    @mock.patch.object(ovu, 'neutron_to_osvif_vif')
    def test_request_vif(self, m_to_vif, m_to_fips):
        cls = drvs.SriovVIFDriver
        cls._tag_on_creation = True
        m_driver = mock.Mock(spec=cls)

        os_net = self.useFixture(k_fix.MockNetworkClient()).client
        project_id = mock.sentinel.project_id
        fixed_ips = mock.sentinel.fixed_ips
        m_to_fips.return_value = fixed_ips
        network = mock.sentinel.Network
        subnet_id = str(uuid.uuid4())
        subnets = {subnet_id: network}
        security_groups = mock.sentinel.security_groups
        port_fixed_ips = mock.sentinel.port_fixed_ips
        port_id = mock.sentinel.port_id
        port = {
            'fixed_ips': port_fixed_ips,
            'id': port_id
        }
        port_request = {'fake_req': mock.sentinel.port_request}
        m_driver._get_port_request.return_value = port_request
        vif = mock.sentinel.vif
        m_to_vif.return_value = vif
        os_net.create_port.return_value = port
        utils.get_subnet.return_value = subnets

        self.assertEqual(vif, cls.request_vif(m_driver, self._pod, project_id,
                                              subnets, security_groups))

        os_net.create_port.assert_called_once_with(**port_request)

    @mock.patch('kuryr_kubernetes.os_vif_util.osvif_to_neutron_fixed_ips')
    @mock.patch.object(ovu, 'neutron_to_osvif_vif')
    def test_request_vif_not_enough_vfs(self, m_to_vif, m_to_fips):
        cls = drvs.SriovVIFDriver
        m_driver = mock.Mock(spec=cls)

        m_driver._get_remaining_sriov_vfs.return_value = 0
        os_net = self.useFixture(k_fix.MockNetworkClient()).client
        project_id = mock.sentinel.project_id
        network = mock.sentinel.Network
        subnet_id = str(uuid.uuid4())
        subnets = {subnet_id: network}
        security_groups = mock.sentinel.security_groups

        self.assertIsNone(cls.request_vif(m_driver, self._pod, project_id,
                                          subnets, security_groups))

        os_net.create_port.assert_not_called()

    def test_get_sriov_num_vf(self):
        cls = drvs.SriovVIFDriver
        m_driver = mock.Mock(spec=cls)

        m_driver._physnet_resname_mapping = self._res_map
        m_driver._res_prefix = k_const.K8S_SRIOV_PREFIX
        amount = cls._get_remaining_sriov_vfs(m_driver, self._pod,
                                              SRIOV_PHYSNET_A)
        self.assertEqual(amount, AMOUNT_FOR_SUBNET_A)

        amount = cls._get_remaining_sriov_vfs(m_driver, self._pod,
                                              SRIOV_PHYSNET_B)
        self.assertEqual(amount, AMOUNT_FOR_SUBNET_B)

    def test_reduce_remaining_sriov_vfs(self):
        cls = drvs.SriovVIFDriver
        m_driver = mock.Mock(spec=cls)

        m_driver._physnet_resname_mapping = self._res_map
        m_driver._res_prefix = k_const.K8S_SRIOV_PREFIX

        cls._reduce_remaining_sriov_vfs(m_driver, self._pod, SRIOV_PHYSNET_A)
        amount = cls._get_remaining_sriov_vfs(m_driver, self._pod,
                                              SRIOV_PHYSNET_A)
        self.assertEqual(amount, AMOUNT_FOR_SUBNET_A - 1)

        cls._reduce_remaining_sriov_vfs(m_driver, self._pod, SRIOV_PHYSNET_B)
        amount = cls._get_remaining_sriov_vfs(m_driver, self._pod,
                                              SRIOV_PHYSNET_B)
        self.assertEqual(amount, AMOUNT_FOR_SUBNET_B - 1)

    def test_get_physnet_subnet_mapping(self):
        cls = drvs.SriovVIFDriver
        m_driver = mock.Mock(spec=cls)

        subnet_id = str(uuid.uuid4())
        oslo_cfg.CONF.set_override('default_physnet_subnets',
                                   'physnet10_4:'+str(subnet_id),
                                   group='sriov')

        mapping = cls._get_physnet_subnet_mapping(m_driver)
        self.assertEqual(mapping, {subnet_id: 'physnet10_4'})

    def test_get_physnet_resname_mapping(self):
        cls = drvs.SriovVIFDriver
        m_driver = mock.Mock(spec=cls)

        oslo_cfg.CONF.set_override('physnet_resource_mappings',
                                   SRIOV_PHYSNET_A + ':' +
                                   SRIOV_RESOURCE_NAME_A + ',' +
                                   SRIOV_PHYSNET_B + ':' +
                                   SRIOV_RESOURCE_NAME_B,
                                   group='sriov')

        mapping = cls._get_physnet_resname_mapping(m_driver)
        self.assertEqual(mapping, self._res_map)

    def test_empty_physnet_resname_mapping(self):
        cls = drvs.SriovVIFDriver
        m_driver = mock.Mock(spec=cls)

        empty_res_map = {SRIOV_PHYSNET_A: SRIOV_PHYSNET_A,
                         SRIOV_PHYSNET_B: SRIOV_PHYSNET_B}
        subnet_id = str(uuid.uuid4())
        subnet_id_2 = str(uuid.uuid4())
        m_driver._physnet_subnet_mapping = {subnet_id: SRIOV_PHYSNET_A,
                                            subnet_id_2: SRIOV_PHYSNET_B}
        mapping = cls._get_physnet_resname_mapping(m_driver)
        self.assertEqual(mapping, empty_res_map)

    def test_get_physnet_for_subnet_id(self):
        cls = drvs.SriovVIFDriver
        m_driver = mock.Mock(spec=cls)

        subnet_id = str(uuid.uuid4())
        m_driver._physnet_subnet_mapping = {subnet_id: 'physnet10_4'}

        physnet = cls._get_physnet_for_subnet_id(m_driver, subnet_id)
        self.assertEqual(physnet, 'physnet10_4')

    def test_get_physnet_for_subnet_id_error(self):
        cls = drvs.SriovVIFDriver
        m_driver = mock.Mock(spec=cls)

        subnet_id = str(uuid.uuid4())
        m_driver._physnet_subnet_mapping = {}

        self.assertRaises(KeyError, cls._get_physnet_for_subnet_id,
                          m_driver, subnet_id)
