# -*- coding: utf-8 -*-

# Copyright 2019 Red Hat, Inc.
#
# 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 os
import os.path
import random
import shutil
import tempfile


from os_net_config import sriov_config
from os_net_config.tests import base
from os_net_config import utils


class TestSriovConfig(base.TestCase):
    """Unit tests for methods defined in sriov_config.py"""

    def setUp(self):
        super(TestSriovConfig, self).setUp()
        rand = str(int(random.random() * 100000))

        tmpdir = tempfile.mkdtemp()
        self.stub_out('os_net_config.sriov_config._SYS_CLASS_NET', tmpdir)

        sriov_config._UDEV_RULE_FILE = '/tmp/' + rand + 'etc_udev_rules.d'\
            '80-persistent-os-net-config.rules'
        sriov_config._UDEV_LEGACY_RULE_FILE = '/tmp/' + rand + 'etc_udev_'\
            'rules.d_70-os-net-config-sriov.rules'
        sriov_config._IFUP_LOCAL_FILE = '/tmp/' + rand + 'sbin_ifup-local'
        sriov_config._RESET_SRIOV_RULES_FILE = '/tmp/' + rand + 'etc_udev_'\
            'rules.d_70-tripleo-reset-sriov.rules'
        sriov_config._ALLOCATE_VFS_FILE = '/tmp/' + rand + 'etc_sysconfig_'\
            'allocate_vfs'
        sriov_config._SRIOV_CONFIG_FILE = '/tmp/' + rand + 'sriov_config'\
                                          '.yaml'

    def tearDown(self):
        super(TestSriovConfig, self).tearDown()
        if os.path.isfile(sriov_config._SRIOV_CONFIG_FILE):
            os.remove(sriov_config._SRIOV_CONFIG_FILE)
        if os.path.isfile(sriov_config._IFUP_LOCAL_FILE):
            os.remove(sriov_config._IFUP_LOCAL_FILE)
        shutil.rmtree(sriov_config._SYS_CLASS_NET)
        if os.path.isfile(sriov_config._RESET_SRIOV_RULES_FILE):
            os.remove(sriov_config._RESET_SRIOV_RULES_FILE)
        if os.path.isfile(sriov_config._ALLOCATE_VFS_FILE):
            os.remove(sriov_config._ALLOCATE_VFS_FILE)
        if os.path.isfile(sriov_config._UDEV_LEGACY_RULE_FILE):
            os.remove(sriov_config._UDEV_LEGACY_RULE_FILE)

    def test_configure_sriov_pf(self):
        """Test the numvfs setting for SR-IOV PF

        Test the udev rules created for legacy mode of SR-IOV PF
        """

        exp_udev_content = '# This file is autogenerated by os-net-config\n'\
            'KERNEL=="p2p1", RUN+="/bin/os-net-config-sriov -n %k:10"\n'\
            'KERNEL=="p2p2", RUN+="/bin/os-net-config-sriov -n %k:12"\n'

        run_cmd = []

        def run_ip_config_cmd_stub(*args, **kwargs):
            run_cmd.append(' '.join(args))
        self.stub_out('os_net_config.sriov_config.run_ip_config_cmd',
                      run_ip_config_cmd_stub)

        def udev_monitor_setup_stub():
            return
        self.stub_out('os_net_config.sriov_config.udev_monitor_setup',
                      udev_monitor_setup_stub)

        def udev_monitor_start_stub(observer):
            return
        self.stub_out('os_net_config.sriov_config.udev_monitor_start',
                      udev_monitor_start_stub)

        def udev_monitor_stop_stub(observer):
            return
        self.stub_out('os_net_config.sriov_config.udev_monitor_stop',
                      udev_monitor_stop_stub)

        def cleanup_puppet_config_stub():
            return
        self.stub_out('os_net_config.sriov_config.cleanup_puppet_config',
                      cleanup_puppet_config_stub)

        def trigger_udev_rules_stub():
            return
        self.stub_out('os_net_config.sriov_config.trigger_udev_rules',
                      trigger_udev_rules_stub)

        def reload_udev_rules_stub():
            return
        self.stub_out('os_net_config.sriov_config.reload_udev_rules',
                      reload_udev_rules_stub)

        def _wait_for_vf_creation_stub(pf_name, numvfs):
            numvfs_pair = {"p2p1": 10, "p2p2": 12}
            self.assertEqual(numvfs_pair[pf_name], numvfs)
        self.stub_out('os_net_config.sriov_config._wait_for_vf_creation',
                      _wait_for_vf_creation_stub)

        def get_vendor_id_stub(ifname):
            return "0x8086"
        self.stub_out('os_net_config.sriov_config.get_vendor_id',
                      get_vendor_id_stub)

        pf_config = [{"device_type": "pf", "name": "p2p1", "numvfs": 10,
                      "promisc": "on", "link_mode": "legacy"},
                     {"device_type": "pf", "name": "p2p2", "numvfs": 12,
                      "promisc": "off", "link_mode": "legacy"}]

        os.makedirs(sriov_config._SYS_CLASS_NET + "/p2p1/device")
        os.makedirs(sriov_config._SYS_CLASS_NET + "/p2p2/device")
        f = open(sriov_config._SYS_CLASS_NET + "/p2p1/device/sriov_numvfs",
                 "w+")
        f.write("0")
        f.close()

        f = open(sriov_config._SYS_CLASS_NET + "/p2p2/device/sriov_numvfs",
                 "w+")
        f.write("0")
        f.close()

        utils.write_yaml_config(sriov_config._SRIOV_CONFIG_FILE, pf_config)
        sriov_config.configure_logger(debug=True)
        sriov_config.configure_sriov_pf()

        f = open(sriov_config._UDEV_LEGACY_RULE_FILE, 'r')
        self.assertEqual(exp_udev_content, f.read())
        self.assertEqual(10, sriov_config.get_numvfs('p2p1'))
        self.assertEqual(12, sriov_config.get_numvfs('p2p2'))

    def test_cleanup_puppet_config_deprecation(self):
        """Test the cleanup of puppet-tripleo generated config file.

        Usecase: The ifup-local has the default content generated by
        puppet-tripleo
        """

        content = '#!/bin/bash\n'\
                  '/etc/sysconfig/allocate_vfs $1'
        f = open(sriov_config._RESET_SRIOV_RULES_FILE, "w+")
        f.close()
        f = open(sriov_config._ALLOCATE_VFS_FILE, "w+")
        f.close()
        f = open(sriov_config._IFUP_LOCAL_FILE, "w+")
        f.write(content)
        f.close()

        sriov_config.cleanup_puppet_config()
        self.assertEqual(False,
                         os.path.exists(sriov_config._RESET_SRIOV_RULES_FILE))
        self.assertEqual(False,
                         os.path.exists(sriov_config._ALLOCATE_VFS_FILE))
        self.assertEqual(False,
                         os.path.exists(sriov_config._IFUP_LOCAL_FILE))

    def test_cleanup_puppet_config_new(self):
        """Test the cleanup of puppet-tripleo generated config file.

        Usecase: When os-net-config is run on fresh deployments, all these
        files will not exist.
        """

        sriov_config.cleanup_puppet_config()
        self.assertEqual(False,
                         os.path.exists(sriov_config._RESET_SRIOV_RULES_FILE))
        self.assertEqual(False,
                         os.path.exists(sriov_config._ALLOCATE_VFS_FILE))
        self.assertEqual(False,
                         os.path.exists(sriov_config._IFUP_LOCAL_FILE))

    def test_cleanup_puppet_config_modified(self):
        """Test the cleanup of puppet-tripleo generated config file

        Usecase: When os-net-config is run first time after the deprecation
        of NeutronSriovNumVFs and ifup-local has contents other than invoking
        allocate_vfs.
        """

        content = '#!/bin/bash\n'\
                  '/etc/sysconfig/allocate_vfs $1\n'\
                  '/usr/sbin/ifup eth0'
        mod_content = '#!/bin/bash\n'\
                      '/usr/sbin/ifup eth0'
        f = open(sriov_config._IFUP_LOCAL_FILE, "w+")
        f.write(content)
        f.close()

        sriov_config.cleanup_puppet_config()
        self.assertEqual(False,
                         os.path.exists(sriov_config._RESET_SRIOV_RULES_FILE))
        self.assertEqual(False,
                         os.path.exists(sriov_config._ALLOCATE_VFS_FILE))
        self.assertEqual(True,
                         os.path.exists(sriov_config._IFUP_LOCAL_FILE))

        f = open(sriov_config._IFUP_LOCAL_FILE, "r")
        self.assertEqual(mod_content, f.read())

    def test_numvfs_config(self):
        """Test the numvfs config with valid arguments"""

        os.makedirs(sriov_config._SYS_CLASS_NET + "/p2p1/device")
        f = open(sriov_config._SYS_CLASS_NET + "/p2p1/device/sriov_numvfs",
                 "w+")
        f.write("0")
        f.close()
        self.assertEqual(None, sriov_config.main(['ARG0', '-n', 'p2p1:15']))
        self.assertEqual(15, sriov_config.get_numvfs('p2p1'))

    def test_numvfs_invalid_params(self):
        """Test the numvfs config with invalid arguments"""

        os.makedirs(sriov_config._SYS_CLASS_NET + "/p2p1/device")
        f = open(sriov_config._SYS_CLASS_NET + "/p2p1/device/sriov_numvfs",
                 "w+")
        f.write("0")
        f.close()
        self.assertEqual(1, sriov_config.main(['ARG0', '-n', 'p2p1:15a']))
        self.assertEqual(0, sriov_config.get_numvfs('p2p1'))

    def test_numvfs_preconfigured(self):
        """Test the numvfs config while its already configured"""

        os.makedirs(sriov_config._SYS_CLASS_NET + "/p2p1/device")
        f = open(sriov_config._SYS_CLASS_NET + "/p2p1/device/sriov_numvfs",
                 "w+")
        f.write("10")
        f.close()
        self.assertEqual(None, sriov_config.main(['ARG0', '-n', 'p2p1:15']))
        self.assertEqual(10, sriov_config.get_numvfs('p2p1'))

    def test_configure_sriov_vf(self):
        """Test configuration of SR-IOV VF settings"""

        vf_config = [{"device_type": "vf", "device": {"name": "p2p1",
                      "vfid": 1}, "promisc": "on", "vlan_id": 101,
                      "qos": 5, "macaddr": "AA:BB:CC:DD:EE:FF",
                      "spoofcheck": "on", "state": "auto", "trust": "on",
                      "min_tx_rate": 0, "max_tx_rate": 100,
                      "name": "p2p1_1"}]
        exp_cmds = ["ip link set dev p2p1 vf 1 mac AA:BB:CC:DD:EE:FF",
                    "ip link set dev p2p1 vf 1 vlan 101 qos 5",
                    "ip link set dev p2p1 vf 1 min_tx_rate 0",
                    "ip link set dev p2p1 vf 1 max_tx_rate 100",
                    "ip link set dev p2p1_1 promisc on",
                    "ip link set dev p2p1 vf 1 spoofchk on",
                    "ip link set dev p2p1 vf 1 state auto",
                    "ip link set dev p2p1 vf 1 trust on"]
        run_cmd = []

        def run_ip_config_cmd_stub(*args, **kwargs):
            run_cmd.append(' '.join(args))
        self.stub_out('os_net_config.sriov_config.run_ip_config_cmd',
                      run_ip_config_cmd_stub)

        utils.write_yaml_config(sriov_config._SRIOV_CONFIG_FILE, vf_config)
        sriov_config.configure_sriov_vf()

        for cmd in exp_cmds:
            self.assertIn(cmd, run_cmd)
