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

# Copyright 2014 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 unittest import mock
import yaml

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


_PCI_OUTPUT = '''driver: e1000e
version: 3.2.6-k
firmware-version: 0.13-3
expansion-rom-version:
bus-info: 0000:00:19.0
supports-statistics: yes
supports-test: yes
supports-eeprom-access: yes
supports-register-dump: yes
supports-priv-flags: no
'''

_VPPCTL_OUTPUT = '''
            Name               Idx       State          Counter          Count
GigabitEthernet0/9/0              1        down
local0                            0        down

'''

_VPPBOND_OUTPUT = """
              Name                Idx   Link  Hardware
BondEthernet0                      3     up   Slave-Idx: 1 2
TenGigabitEthernet2/0/0            1    slave TenGigabitEthernet2/0/0
TenGigabitEthernet2/0/1            2    slave TenGigabitEthernet2/0/1
"""

_INITIAL_VPP_CONFIG = '''
unix {
  nodaemon
  log /tmp/vpp.log
  full-coredump
}


api-trace {
  on
}

api-segment {
  gid vpp
}

dpdk {
}
'''

udev_content = '# This file is autogenerated by os-net-config\n'\
               'KERNEL=="eth1", RUN+="/bin/os-net-config-sriov -n %k:10"\n' \
               'KERNEL=="eth2", RUN+="/bin/os-net-config-sriov -n %k:10"\n'
UDEV_FILE = '/tmp/70-os-net-config-sriov.'


def generate_random_mac(name):
    # Generate 6 random bytes
    mac = [random.randint(0, 255) for _ in range(6)]
    mac[0] &= 0xFE
    mac_address = ':'.join(f'{byte:02x}' for byte in mac)
    return mac_address


class TestUtils(base.TestCase):

    def setUp(self):
        super(TestUtils, self).setUp()
        rand = str(int(random.random() * 100000))
        common.DPDK_MAPPING_FILE = '/tmp/dpdk_mapping_' + rand + '.yaml'
        common.SRIOV_CONFIG_FILE = '/tmp/sriov_config_' + rand + '.yaml'
        common._LOG_FILE = '/tmp/' + rand + 'os_net_config.log'
        sriov_config._UDEV_LEGACY_RULE_FILE = UDEV_FILE + rand
        common.set_noop(False)

    def tearDown(self):
        super(TestUtils, self).tearDown()
        if os.path.isfile(common.DPDK_MAPPING_FILE):
            os.remove(common.DPDK_MAPPING_FILE)
        if os.path.isfile(common._LOG_FILE):
            os.remove(common._LOG_FILE)
        if os.path.isfile(common.SRIOV_CONFIG_FILE):
            os.remove(common.SRIOV_CONFIG_FILE)
        if os.path.isfile(sriov_config._UDEV_LEGACY_RULE_FILE):
            os.remove(sriov_config._UDEV_LEGACY_RULE_FILE)

    def prepare_sysfs(self, nic, pci_addr, driver):
        net_dir = tempfile.mkdtemp()
        pci_dir = tempfile.mkdtemp()
        tmp_dir = tempfile.mkdtemp()
        drv_dir = os.path.join(tmp_dir, driver)
        self.stub_out('os_net_config.common.SYS_CLASS_NET', net_dir)
        self.stub_out('os_net_config.common._SYS_BUS_PCI_DEV', pci_dir)
        net_path = os.path.join(common.SYS_CLASS_NET, nic)
        pci_path = os.path.join(common._SYS_BUS_PCI_DEV, pci_addr)
        drv_link = os.path.join(common._SYS_BUS_PCI_DEV, f"{pci_addr}/driver")
        dev_link = os.path.join(common.SYS_CLASS_NET, f"{nic}/device")
        os.makedirs(net_path)
        os.makedirs(pci_path)
        if driver:
            os.makedirs(drv_dir)
            os.symlink(drv_dir, drv_link)
        os.symlink(pci_path, dev_link)

    def test_ordered_active_nics(self):

        tmpdir = tempfile.mkdtemp()
        self.stub_out('os_net_config.common.SYS_CLASS_NET', tmpdir)

        def test_is_available_nic(interface_name, check_active):
            return True
        self.stub_out('os_net_config.utils._is_available_nic',
                      test_is_available_nic)

        for nic in ['a1', 'em1', 'em2', 'eth2', 'z1',
                    'enp8s0', 'enp10s0', 'enp1s0f0']:
            with open(os.path.join(tmpdir, nic), 'w') as f:
                f.write(nic)

        nics = utils.ordered_active_nics()
        self.assertEqual('em1', nics[0])
        self.assertEqual('em2', nics[1])
        self.assertEqual('eth2', nics[2])
        self.assertEqual('a1', nics[3])
        self.assertEqual('enp1s0f0', nics[4])
        self.assertEqual('enp8s0', nics[5])
        self.assertEqual('enp10s0', nics[6])
        self.assertEqual('z1', nics[7])

        shutil.rmtree(tmpdir)

    def test_is_pf_attached_to_guest(self):
        def stub_get_sriov_pci_address(iface):
            return "0000:8a:00.1"
        self.stub_out('os_net_config.common.get_sriov_pci_address',
                      stub_get_sriov_pci_address)

        self.prepare_sysfs("eth1", "0000:8a:00.1", "i40e")
        self.assertEqual(common.is_pf_attached_to_guest("eth1"), False)

    def test_is_pf_detached_to_guest(self):
        def stub_get_sriov_pci_address(iface):
            return "0000:8a:00.1"
        self.stub_out('os_net_config.common.get_sriov_pci_address',
                      stub_get_sriov_pci_address)

        common.set_noop(False)
        self.prepare_sysfs("eth1", "0000:8a:00.1", "vfio-pci")
        self.assertEqual(common.is_pf_attached_to_guest("eth1"), True)

    def test_update_sriov_pf_map_new(self):
        def get_numvfs_stub(pf_name):
            return 0
        self.prepare_sysfs("eth1", "0000:8a:00.1", "i40e")
        self.stub_out('os_net_config.sriov_config.get_numvfs',
                      get_numvfs_stub)
        utils.update_sriov_pf_map("eth1", 10, False,
                                  pci_address="0000:8a:00.1",
                                  mac_address="AA:BB:CC:DD:EE:FF")
        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)
        sriov_pf_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(sriov_pf_map))
        test_sriov_pf_map = [{'device_type': 'pf', 'link_mode': 'legacy',
                              'drivers_autoprobe': True,
                              'name': 'eth1', 'numvfs': 10, 'vdpa': False,
                              "pci_address": "0000:8a:00.1",
                              "mac_address": "AA:BB:CC:DD:EE:FF"}]
        self.assertListEqual(test_sriov_pf_map, sriov_pf_map)

    def test_update_sriov_pf_map_with_same_numvfs_pf_not_attached(self):
        def get_numvfs_stub(pf_name):
            return 10
        self.stub_out('os_net_config.sriov_config.get_numvfs',
                      get_numvfs_stub)
        self.prepare_sysfs("eth1", "0000:8a:00.1", "i40e")
        utils.update_sriov_pf_map('eth1', 10, False)
        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)
        sriov_pf_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(sriov_pf_map))
        test_sriov_pf_map = [{'device_type': 'pf', 'link_mode': 'legacy',
                              'drivers_autoprobe': True,
                              'name': 'eth1', 'numvfs': 10, 'vdpa': False}]
        self.assertListEqual(test_sriov_pf_map, sriov_pf_map)

    def test_update_sriov_pf_map_with_same_numvfs_pf_attached(self):
        def get_numvfs_stub(pf_name):
            return 10
        self.stub_out('os_net_config.sriov_config.get_numvfs',
                      get_numvfs_stub)
        self.prepare_sysfs("eth1", "0000:8a:00.1", "vfio-pci")
        utils.update_sriov_pf_map('eth1', 10, False)
        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)
        sriov_pf_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(sriov_pf_map))
        test_sriov_pf_map = [{'device_type': 'pf', 'link_mode': 'legacy',
                              'drivers_autoprobe': True,
                              'name': 'eth1', 'numvfs': 10, 'vdpa': False}]
        self.assertListEqual(test_sriov_pf_map, sriov_pf_map)

    def test_update_sriov_pf_map_with_diff_numvfs(self):
        def get_numvfs_stub(pf_name):
            return 12
        self.stub_out('os_net_config.sriov_config.get_numvfs',
                      get_numvfs_stub)
        self.assertRaises(sriov_config.SRIOVNumvfsException,
                          utils.update_sriov_pf_map, 'eth1', 10, False)

    def test_update_sriov_pf_map_new_with_promisc(self):
        def get_numvfs_stub(pf_name):
            return 0
        self.stub_out('os_net_config.sriov_config.get_numvfs',
                      get_numvfs_stub)
        utils.update_sriov_pf_map('eth1', 10, False, promisc='off')
        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)
        sriov_pf_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(sriov_pf_map))
        test_sriov_pf_map = [{'device_type': 'pf', 'link_mode': 'legacy',
                              'name': 'eth1', 'numvfs': 10, 'promisc': 'off',
                              'drivers_autoprobe': True, 'vdpa': False}]
        self.assertListEqual(test_sriov_pf_map, sriov_pf_map)

    def test_update_sriov_pf_map_new_with_vdpa(self):
        def get_numvfs_stub(pf_name):
            return 0
        self.stub_out('os_net_config.sriov_config.get_numvfs',
                      get_numvfs_stub)
        utils.update_sriov_pf_map('eth1', 10, False, vdpa=True)
        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)
        sriov_pf_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(sriov_pf_map))
        test_sriov_pf_map = [{'device_type': 'pf', 'link_mode': 'legacy',
                              'drivers_autoprobe': True,
                              'name': 'eth1', 'numvfs': 10, 'vdpa': True}]
        self.assertListEqual(test_sriov_pf_map, sriov_pf_map)

    def test_update_sriov_pf_map_exist(self):
        def get_numvfs_stub(pf_name):
            return 10
        self.stub_out('os_net_config.sriov_config.get_numvfs',
                      get_numvfs_stub)
        pf_initial = [{'device_type': 'pf', 'link_mode': 'legacy',
                       'drivers_autoprobe': True,
                       'name': 'eth1', 'numvfs': 10}]
        common.write_yaml_config(common.SRIOV_CONFIG_FILE, pf_initial)
        self.assertRaises(sriov_config.SRIOVNumvfsException,
                          utils.update_sriov_pf_map, 'eth1', 20, False)

    def test_update_sriov_pf_map_exist_with_promisc(self):
        def get_numvfs_stub(pf_name):
            return 10
        self.stub_out('os_net_config.sriov_config.get_numvfs',
                      get_numvfs_stub)
        pf_initial = [{'device_type': 'pf', 'link_mode': 'legacy',
                       'name': 'eth1', 'numvfs': 10, 'promisc': 'on',
                       'drivers_autoprobe': True, 'vdpa': False}]
        common.write_yaml_config(common.SRIOV_CONFIG_FILE, pf_initial)

        utils.update_sriov_pf_map('eth1', 10, False, promisc='off')
        pf_final = [{'device_type': 'pf', 'link_mode': 'legacy',
                     'name': 'eth1', 'numvfs': 10, 'promisc': 'off',
                     'drivers_autoprobe': True, 'vdpa': False}]
        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)

        pf_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(pf_map))
        self.assertListEqual(pf_final, pf_map)

    def test_update_sriov_pf_map_exist_with_vdpa(self):
        def get_numvfs_stub(pf_name):
            return 10
        self.stub_out('os_net_config.sriov_config.get_numvfs',
                      get_numvfs_stub)
        pf_initial = [{'device_type': 'pf', 'link_mode': 'legacy',
                       'name': 'eth1', 'numvfs': 10, 'promisc': 'on',
                       'drivers_autoprobe': True, 'vdpa': False}]
        common.write_yaml_config(common.SRIOV_CONFIG_FILE, pf_initial)

        utils.update_sriov_pf_map('eth1', 10, False, vdpa=True)
        pf_final = [{'device_type': 'pf', 'link_mode': 'legacy',
                     'name': 'eth1', 'numvfs': 10, 'promisc': 'on',
                     'drivers_autoprobe': True, 'vdpa': True}]
        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)

        pf_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(pf_map))
        self.assertListEqual(pf_final, pf_map)

    def test_update_sriov_pf_map_new_with_lag_candidate(self):
        def get_numvfs_stub(pf_name):
            return 0
        self.stub_out('os_net_config.sriov_config.get_numvfs',
                      get_numvfs_stub)
        utils.update_sriov_pf_map('eth1', 10, False, lag_candidate=True)
        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)
        sriov_pf_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(sriov_pf_map))
        test_sriov_pf_map = [{'device_type': 'pf', 'link_mode': 'legacy',
                              'name': 'eth1', 'numvfs': 10, 'vdpa': False,
                              'drivers_autoprobe': True,
                              'lag_candidate': True}]
        self.assertListEqual(test_sriov_pf_map, sriov_pf_map)

    def test_update_sriov_pf_map_exist_with_lag_candidate(self):
        def get_numvfs_stub(pf_name):
            return 10
        self.stub_out('os_net_config.sriov_config.get_numvfs',
                      get_numvfs_stub)
        pf_initial = [{'device_type': 'pf', 'link_mode': 'legacy',
                       'name': 'eth1', 'numvfs': 10, 'promisc': 'on',
                       'drivers_autoprobe': True,
                       'vdpa': False, 'lag_candidate': False}]
        common.write_yaml_config(common.SRIOV_CONFIG_FILE, pf_initial)

        utils.update_sriov_pf_map('eth1', 10, False, lag_candidate=True)
        pf_final = [{'device_type': 'pf', 'link_mode': 'legacy',
                     'name': 'eth1', 'numvfs': 10, 'promisc': 'on',
                     'drivers_autoprobe': True,
                     'vdpa': False, 'lag_candidate': True}]
        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)

        pf_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(pf_map))
        self.assertListEqual(pf_final, pf_map)

    def test_update_sriov_pf_map_exist_with_lag_candidate_not_exist_true(
            self):
        def get_numvfs_stub(pf_name):
            return 10
        self.stub_out('os_net_config.sriov_config.get_numvfs',
                      get_numvfs_stub)
        pf_initial = [{'device_type': 'pf', 'link_mode': 'legacy',
                       'name': 'eth1', 'numvfs': 10, 'promisc': 'on',
                       'drivers_autoprobe': True, 'vdpa': False}]
        common.write_yaml_config(common.SRIOV_CONFIG_FILE, pf_initial)

        utils.update_sriov_pf_map('eth1', 10, False, lag_candidate=True)
        pf_final = [{'device_type': 'pf', 'link_mode': 'legacy',
                     'name': 'eth1', 'numvfs': 10, 'promisc': 'on',
                     'drivers_autoprobe': True,
                     'vdpa': False, 'lag_candidate': True}]
        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)

        pf_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(pf_map))
        self.assertListEqual(pf_final, pf_map)

    def test_update_sriov_pf_map_exist_with_lag_candidate_not_exist_false(
            self):
        def get_numvfs_stub(pf_name):
            return 10
        self.stub_out('os_net_config.sriov_config.get_numvfs',
                      get_numvfs_stub)
        pf_initial = [{'device_type': 'pf', 'link_mode': 'legacy',
                       'name': 'eth1', 'numvfs': 10, 'promisc': 'on',
                       'drivers_autoprobe': True, 'vdpa': False}]
        common.write_yaml_config(common.SRIOV_CONFIG_FILE, pf_initial)

        utils.update_sriov_pf_map('eth1', 10, False, lag_candidate=False)
        pf_final = [{'device_type': 'pf', 'link_mode': 'legacy',
                     'name': 'eth1', 'numvfs': 10, 'promisc': 'on',
                     'drivers_autoprobe': True,
                     'vdpa': False, 'lag_candidate': False}]
        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)

        pf_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(pf_map))
        self.assertListEqual(pf_final, pf_map)

    def test_update_sriov_pf_map_exist_with_pci_mac(
            self):
        def get_numvfs_stub(pf_name):
            return 10
        self.stub_out('os_net_config.sriov_config.get_numvfs',
                      get_numvfs_stub)
        pf_initial = [{'device_type': 'pf', 'link_mode': 'legacy',
                       'name': 'eth1', 'numvfs': 10, 'promisc': 'on',
                       'drivers_autoprobe': True, 'vdpa': False}]
        common.write_yaml_config(common.SRIOV_CONFIG_FILE, pf_initial)

        utils.update_sriov_pf_map('eth1', 10, False,
                                  pci_address='0000:8a:07.1',
                                  mac_address='aa:bb:cc:dd:ee:ff')
        pf_final = [{'device_type': 'pf', 'link_mode': 'legacy',
                     'name': 'eth1', 'numvfs': 10, 'promisc': 'on',
                     'drivers_autoprobe': True, 'vdpa': False,
                     'pci_address': '0000:8a:07.1',
                     'mac_address': 'aa:bb:cc:dd:ee:ff'}]
        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)

        pf_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(pf_map))
        self.assertListEqual(pf_final, pf_map)

    def test_update_sriov_vf_map_minimal_new(self):
        utils.update_sriov_vf_map('eth1', 2, 'eth1_2')
        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)
        sriov_vf_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(sriov_vf_map))
        test_sriov_vf_map = [{'device_type': 'vf', 'name': 'eth1_2',
                              'min_tx_rate': 0, 'max_tx_rate': 0,
                              'device': {"name": "eth1", "vfid": 2}}]
        self.assertListEqual(test_sriov_vf_map, sriov_vf_map)

    def test_udev_rule_for_sriov_vf(self):
        def get_numvfs_stub(pf_name):
            return 10
        self.stub_out('os_net_config.sriov_config.get_numvfs',
                      get_numvfs_stub)
        utils.update_sriov_pf_map('eth1', 10, False)
        utils.update_sriov_pf_map('eth2', 10, False)
        utils.update_sriov_vf_map('eth1', 2, 'eth1_2')
        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)
        sriov_vf_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(3, len(sriov_vf_map))

        test_sriov_vf_map = {'device': {'name': 'eth1', 'vfid': 2},
                             'device_type': 'vf', 'max_tx_rate': 0,
                             'min_tx_rate': 0, 'name': 'eth1_2'}
        udev_file = open(sriov_config._UDEV_LEGACY_RULE_FILE, "w")
        udev_file.write(udev_content)
        udev_file.close()

        # sriov_config.add_udev_rule_for_legacy_sriov_pf("eth1", 10)
        for dev in sriov_vf_map:
            if(dev.keys() == test_sriov_vf_map.keys()):
                self.assertEqual(test_sriov_vf_map, dev)

        expect = '# This file is autogenerated by os-net-config\n'\
                 'KERNEL=="eth2", RUN+="/bin/os-net-config-sriov -n %k:10"\n'
        utils.nicpart_udev_rules_check()
        f = open(sriov_config._UDEV_LEGACY_RULE_FILE, 'r')
        self.assertEqual(expect, f.read())
        f.close()

    def test_update_sriov_vf_map_complete_new(self):
        utils.update_sriov_vf_map('eth1', 2, 'eth1_2', vlan_id=10, qos=5,
                                  spoofcheck="on", trust="on", state="enable",
                                  macaddr="AA:BB:CC:DD:EE:FF", promisc="off",
                                  pci_address="0000:80:00.1", max_tx_rate=10)
        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)
        sriov_vf_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(sriov_vf_map))
        test_sriov_vf_map = [{'device_type': 'vf', 'name': 'eth1_2',
                              'device': {'name': 'eth1', 'vfid': 2},
                              'vlan_id': 10, 'qos': 5,
                              'min_tx_rate': 0, 'max_tx_rate': 10,
                              'spoofcheck': 'on', 'trust': 'on',
                              'state': 'enable',
                              'macaddr': 'AA:BB:CC:DD:EE:FF',
                              'promisc': 'off',
                              'pci_address': "0000:80:00.1"}]
        self.assertListEqual(test_sriov_vf_map, sriov_vf_map)

    def test_update_sriov_vf_map_exist(self):
        vf_initial = [{'device_type': 'vf', 'name': 'eth1_2',
                       'device': {"name": "eth1", "vfid": 2}}]
        common.write_yaml_config(common.SRIOV_CONFIG_FILE, vf_initial)

        utils.update_sriov_vf_map('eth1', 2, 'eth1_2', vlan_id=10, qos=5,
                                  spoofcheck="on", trust="on", state="enable",
                                  macaddr="AA:BB:CC:DD:EE:FF", promisc="off",
                                  pci_address="0000:80:00.1", max_tx_rate=10)
        vf_final = [{'device_type': 'vf', 'name': 'eth1_2',
                     'device': {'name': 'eth1', 'vfid': 2},
                     'vlan_id': 10, 'qos': 5,
                     'min_tx_rate': 0, 'max_tx_rate': 10,
                     'spoofcheck': 'on', 'trust': 'on',
                     'state': 'enable',
                     'macaddr': 'AA:BB:CC:DD:EE:FF',
                     'promisc': 'off',
                     'pci_address': '0000:80:00.1'}]
        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)

        vf_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(vf_map))
        self.assertListEqual(vf_final, vf_map)

    def test_update_sriov_vf_map_exist_complete(self):
        vf_initial = [{'device_type': 'vf', 'name': 'eth1_2',
                       'device': {'name': 'eth1', 'vfid': 2},
                       'vlan_id': 10, 'qos': 5,
                       'min_tx_rate': 0, 'max_tx_rate': 00,
                       'spoofcheck': 'on', 'trust': 'on',
                       'state': 'enable',
                       'macaddr': 'AA:BB:CC:DD:EE:FF',
                       'promisc': 'off',
                       'pci_address': "0000:80:00.1"}]
        common.write_yaml_config(common.SRIOV_CONFIG_FILE, vf_initial)

        utils.update_sriov_vf_map('eth1', 2, 'eth1_2', vlan_id=100, qos=15,
                                  spoofcheck="off", trust="off", state="auto",
                                  macaddr="BB:BB:CC:DD:EE:FF", promisc="on",
                                  pci_address="0000:80:00.1", max_tx_rate=40)
        vf_final = [{'device_type': 'vf', 'name': 'eth1_2',
                     'device': {'name': 'eth1', 'vfid': 2},
                     'vlan_id': 100, 'qos': 15,
                     'min_tx_rate': 0, 'max_tx_rate': 40,
                     'spoofcheck': 'off', 'trust': 'off',
                     'state': 'auto',
                     'macaddr': 'BB:BB:CC:DD:EE:FF',
                     'promisc': 'on',
                     'pci_address': '0000:80:00.1'}]
        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)

        vf_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(vf_map))
        self.assertListEqual(vf_final, vf_map)

    def test_get_vf_devname_net_dir_not_found(self):
        tmpdir = tempfile.mkdtemp()
        self.stub_out('os_net_config.common.SYS_CLASS_NET', tmpdir)

        self.assertRaises(common.SriovVfNotFoundException,
                          utils.get_vf_devname, "eth1", 1)
        shutil.rmtree(tmpdir)

    def test_get_vf_devname_vf_dir_found_in_map(self):
        tmpdir = tempfile.mkdtemp()
        self.stub_out('os_net_config.common.SYS_CLASS_NET', tmpdir)

        def test_get_vf_name_from_map(pf_name, vfid):
            return pf_name + '_' + str(vfid)
        self.stub_out('os_net_config.utils._get_vf_name_from_map',
                      test_get_vf_name_from_map)

        vf_path = os.path.join(common.SYS_CLASS_NET, 'eth1/device/virtfn1')
        os.makedirs(vf_path)

        self.assertEqual(utils.get_vf_devname("eth1", 1), "eth1_1")
        shutil.rmtree(tmpdir)

    def test_get_vf_devname_vf_dir_not_found(self):
        tmpdir = tempfile.mkdtemp()
        self.stub_out('os_net_config.common.SYS_CLASS_NET', tmpdir)

        def test_get_vf_name_from_map(pf_name, vfid):
            return None
        self.stub_out('os_net_config.utils._get_vf_name_from_map',
                      test_get_vf_name_from_map)

        vf_path = os.path.join(common.SYS_CLASS_NET, 'eth1/device/virtfn1')
        os.makedirs(vf_path)

        self.assertRaises(common.SriovVfNotFoundException,
                          utils.get_vf_devname, "eth1", 1)
        shutil.rmtree(tmpdir)

    def test_get_vf_devname_vf_dir_found(self):
        tmpdir = tempfile.mkdtemp()
        self.stub_out('os_net_config.common.SYS_CLASS_NET', tmpdir)

        vf_path = os.path.join(common.SYS_CLASS_NET,
                               'eth1/device/virtfn1/net/eth1_1')
        os.makedirs(vf_path)

        self.assertEqual(utils.get_vf_devname("eth1", 1), "eth1_1")
        shutil.rmtree(tmpdir)

    def test_get_pci_address_success(self):
        self.prepare_sysfs("eth1", "0000:8a:00.1", "i40e")
        pci = common.get_pci_address("eth1")
        self.assertEqual("0000:8a:00.1", pci)

    def test_get_pci_address_fail(self):
        self.prepare_sysfs("eth1", "0000:8a:00.1", "i40e")
        pci = common.get_pci_address("eth2")
        self.assertEqual(None, pci)

    def test_get_pci_address_error(self):
        pci = common.get_pci_address('nic2')
        self.assertEqual(None, pci)

    def test_get_dpdk_pci_address_success(self):
        def test_get_dpdk_map():
            return [{"name": "eth1", "pci_address": "0000:00:09.0",
                     "mac_address": "01:02:03:04:05:06",
                     "driver": "vfio-pci"}]

        self.stub_out("os_net_config.common.get_dpdk_map", test_get_dpdk_map)
        pci = common.get_dpdk_pci_address("eth1")
        self.assertEqual("0000:00:09.0", pci)

    def test_get_dpdk_pci_address_empty(self):
        def test_get_dpdk_map():
            return []

        self.stub_out('os_net_config.common.get_dpdk_map', test_get_dpdk_map)
        pci = common.get_dpdk_pci_address("eth1")
        self.assertEqual(None, pci)

    def test_get_vendor_id_success(self):
        mocked_open = mock.mock_open(read_data='0x15b3\n')
        with mock.patch('os_net_config.common.open', mocked_open, create=True):
            vendor = common.get_vendor_id('nic2')
            self.assertEqual('0x15b3', vendor)

    def test_get_vendor_id_exception(self):
        mocked_open = mock.mock_open()
        mocked_open.side_effect = IOError
        with mock.patch('os_net_config.common.open', mocked_open, create=True):
            vendor = common.get_vendor_id('nic2')
            self.assertEqual(None, vendor)

    def test_get_device_id_success(self):
        mocked_open = mock.mock_open(read_data='0x1003\n')
        with mock.patch('os_net_config.common.open', mocked_open, create=True):
            device = common.get_device_id('nic2')
            self.assertEqual('0x1003', device)

    def test_get_device_id_exception(self):
        mocked_open = mock.mock_open()
        mocked_open.side_effect = IOError
        with mock.patch('os_net_config.utils.open', mocked_open, create=True):
            device = common.get_device_id('nic2')
            self.assertEqual(None, device)

    def test_bind_dpdk_interfaces(self):
        def test_execute(name, dummy1, dummy2=None, dummy3=None):
            if 'driverctl' in name:
                return None, None

        def test_get_dpdk_mac_address(name):
            return '01:02:03:04:05:06'

        def test_get_dpdk_pci_address(name):
            return '0000:85:00.1'
        self.prepare_sysfs("nic2", "0000:8a:00.1", "i40e")
        self.stub_out('oslo_concurrency.processutils.execute', test_execute)
        self.stub_out('os_net_config.common._get_dpdk_mac_address',
                      test_get_dpdk_mac_address)
        self.stub_out('os_net_config.common.get_dpdk_pci_address',
                      test_get_dpdk_pci_address)
        try:
            utils.bind_dpdk_interfaces('nic2', 'vfio-pci', False)
        except common.OvsDpdkBindException:
            self.fail("Received OvsDpdkBindException unexpectedly")

    def test_bind_dpdk_interfaces_fail(self):
        def test_execute(name, dummy1, dummy2=None, dummy3=None):
            if 'driverctl' in name:
                return None, 'Error'

        def test_get_dpdk_mac_address(name):
            return '01:02:03:04:05:06'
        self.stub_out('oslo_concurrency.processutils.execute', test_execute)
        self.stub_out('os_net_config.common._get_dpdk_mac_address',
                      test_get_dpdk_mac_address)

        self.assertRaises(common.OvsDpdkBindException,
                          utils.bind_dpdk_interfaces, 'eth1', 'vfio-pci',
                          False)

    def test_bind_dpdk_interfaces_skip_valid_device(self):
        def test_execute(name, dummy1, dummy2=None, dummy3=None):
            if 'driverctl' in name:
                return None, None

        def test_get_dpdk_mac_address(name):
            return '01:02:03:04:05:06'

        def test_get_dpdk_map():
            return [{'name': 'eth1', 'pci_address': '0000:00:09.0',
                     'mac_address': '01:02:03:04:05:06',
                     'driver': 'vfio-pci'}]

        pci_dir = tempfile.mkdtemp()
        tmp_dir = tempfile.mkdtemp()
        drv_dir = os.path.join(tmp_dir, "i40e")
        self.stub_out('os_net_config.common._SYS_BUS_PCI_DEV', pci_dir)
        pci_path = os.path.join(common._SYS_BUS_PCI_DEV, "0000:00:09.0")
        drv_link = os.path.join(common._SYS_BUS_PCI_DEV, "0000:00:09.0/driver")
        os.makedirs(pci_path)
        os.makedirs(drv_dir)
        os.symlink(drv_dir, drv_link)
        self.stub_out('os_net_config.common.get_dpdk_map', test_get_dpdk_map)
        self.stub_out('oslo_concurrency.processutils.execute', test_execute)
        self.stub_out('os_net_config.common._get_dpdk_mac_address',
                      test_get_dpdk_mac_address)
        try:
            utils.bind_dpdk_interfaces('eth1', 'vfio-pci', False)
        except common.OvsDpdkBindException:
            self.fail("Received OvsDpdkBindException unexpectedly")

    def test_bind_dpdk_interfaces_fail_invalid_device(self):
        def test_execute(name, dummy1, dummy2=None, dummy3=None):
            if 'driverctl' in name:
                return None, None

        def test_get_dpdk_mac_address(name):
            return '01:02:03:04:05:06'

        def test_get_dpdk_map():
            return [{'name': 'eth1', 'pci_address': '0000:00:09.0',
                     'mac_address': '01:02:03:04:05:06',
                     'driver': 'vfio-pci'}]

        self.stub_out('os_net_config.common.get_dpdk_map',
                      test_get_dpdk_map)
        self.stub_out('oslo_concurrency.processutils.execute',
                      test_execute)
        self.stub_out('os_net_config.utils._get_dpdk_mac_address',
                      test_get_dpdk_mac_address)

        self.assertRaises(common.OvsDpdkBindException,
                          utils.bind_dpdk_interfaces, 'eth2', 'vfio-pci',
                          False)

    def test_bind_dpdk_interfaces_same_driver(self):
        self.stub_out('os_net_config.common.interface_mac',
                      generate_random_mac)

        def stub_set_driverctl_override(pci_address, driver):
            return
        self.stub_out('os_net_config.common.set_driverctl_override',
                      stub_set_driverctl_override)

        mocked_logger = mock.Mock()
        self.stub_out('os_net_config.utils.logger.info', mocked_logger)
        self.prepare_sysfs("eth1", "0000:8a:00.1", "vfio-pci")
        common.set_noop(False)
        try:
            utils.bind_dpdk_interfaces('eth1', 'vfio-pci', False)
        except common.OvsDpdkBindException:
            self.fail("Received OvsDpdkBindException unexpectedly")
        msg = ["%s: Driver %s is already bound", "eth1", "vfio-pci"]
        mocked_logger.assert_called_with(*msg)

    def test_get_pci_device_driver(self):
        common.set_noop(False)
        self.prepare_sysfs("eth1", "0000:8a:00.1", "vfio-pci")
        driver = common.get_pci_device_driver("0000:8a:00.1")
        self.assertEqual(driver, 'vfio-pci')

    def test_get_interface_driver_fail_empty(self):
        self.prepare_sysfs("eth2", "0000:8a:00.2", "")
        driver = common.get_pci_device_driver('0000:8a:00.2')
        self.assertEqual(driver, None)

    def test__update_dpdk_map_new(self):
        utils._update_dpdk_map('eth1', '0000:03:00.0', '01:02:03:04:05:06',
                               'vfio-pci')
        contents = common.get_file_data(common.DPDK_MAPPING_FILE)

        dpdk_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(dpdk_map))
        dpdk_test = [{'name': 'eth1', 'pci_address': '0000:03:00.0',
                      'mac_address': '01:02:03:04:05:06',
                      'driver': 'vfio-pci'}]
        self.assertListEqual(dpdk_test, dpdk_map)

    def test_update_dpdk_map_exist(self):
        dpdk_test = [{'name': 'eth1', 'pci_address': '0000:03:00.0',
                      'mac_address': '01:02:03:04:05:06',
                      'driver': 'vfio-pci'}]
        common.write_yaml_config(common.DPDK_MAPPING_FILE, dpdk_test)

        utils._update_dpdk_map('eth1', '0000:03:00.0', '01:02:03:04:05:06',
                               'vfio-pci')
        contents = common.get_file_data(common.DPDK_MAPPING_FILE)

        dpdk_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(dpdk_map))
        self.assertListEqual(dpdk_test, dpdk_map)

    def test_update_dpdk_map_value_change(self):
        dpdk_test = [{'name': 'eth1', 'pci_address': '0000:03:00.0',
                      'driver': 'vfio-pci'}]
        common.write_yaml_config(common.DPDK_MAPPING_FILE, dpdk_test)

        dpdk_test = [{'name': 'eth1', 'pci_address': '0000:03:00.0',
                      'mac_address': '01:02:03:04:05:06',
                      'driver': 'vfio-pci'}]
        utils._update_dpdk_map('eth1', '0000:03:00.0', '01:02:03:04:05:06',
                               'vfio-pci')
        try:
            contents = common.get_file_data(common.DPDK_MAPPING_FILE)
        except IOError:
            pass

        dpdk_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(dpdk_map))
        self.assertListEqual(dpdk_test, dpdk_map)

    def test_ordered_active_nics_with_dpdk_mapping(self):

        tmpdir = tempfile.mkdtemp()
        self.stub_out('os_net_config.common.SYS_CLASS_NET', tmpdir)
        tmp_pci_dir = tempfile.mkdtemp()
        self.stub_out('os_net_config.common._SYS_BUS_PCI_DEV', tmp_pci_dir)
        physfn_path = common._SYS_BUS_PCI_DEV + '/0000:05:01.1/physfn'
        os.makedirs(physfn_path)

        def test_is_available_nic(interface_name, check_active):
            return True
        self.stub_out('os_net_config.utils._is_available_nic',
                      test_is_available_nic)

        for nic in ['a1', 'em1', 'em2', 'eth2', 'z1',
                    'enp8s0', 'enp10s0', 'enp1s0f0']:
            with open(os.path.join(tmpdir, nic), 'w') as f:
                f.write(nic)

        utils._update_dpdk_map('eth1', '0000:03:00.0', '01:02:03:04:05:06',
                               'vfio-pci')
        utils._update_dpdk_map('p3p1', '0000:04:00.0', '01:02:03:04:05:07',
                               'igb_uio')
        utils._update_dpdk_map('p3p0_0', '0000:05:01.1', 'AA:02:03:04:05:FF',
                               'vfio-pci')

        nics = utils.ordered_active_nics()

        self.assertEqual('em1', nics[0])
        self.assertEqual('em2', nics[1])
        self.assertEqual('eth1', nics[2])  # DPDK bound nic
        self.assertEqual('eth2', nics[3])
        self.assertEqual('a1', nics[4])
        self.assertEqual('enp1s0f0', nics[5])
        self.assertEqual('enp8s0', nics[6])
        self.assertEqual('enp10s0', nics[7])
        self.assertEqual('p3p1', nics[8])  # DPDK bound nic
        self.assertEqual('z1', nics[9])

    def test_ordered_active_nics_with_sriov_mapping(self):

        tmpdir = tempfile.mkdtemp()
        self.stub_out('os_net_config.common.SYS_CLASS_NET', tmpdir)

        def test_is_available_nic(interface_name, check_active):
            return True
        self.stub_out('os_net_config.utils._is_available_nic',
                      test_is_available_nic)

        sriov_map = """
        - device_type: pf
          drivers_autoprobe: true
          link_mode: legacy
          mac_address: 6c:fe:54:3f:8a:01
          name: ens1f1
          numvfs: 5
          pci_address: '0000:19:00.1'
          promisc: 'on'
          vdpa: false
        - device_type: pf
          drivers_autoprobe: true
          link_mode: legacy
          mac_address: 6c:fe:54:3f:8a:02
          name: ens1f2
          numvfs: 5
          pci_address: '0000:19:00.2'
          promisc: 'on'"""
        utils.write_config(common.SRIOV_CONFIG_FILE, sriov_map)

        utils._update_dpdk_map('eth2', '0000:03:00.0', '01:02:03:04:05:06',
                               'vfio-pci')

        for nic in ['a1', 'em1', 'em2', 'eth1', 'z1',
                    'enp8s0', 'enp10s0', 'enp1s0f0']:
            with open(os.path.join(tmpdir, nic), 'w') as f:
                f.write(nic)

        nics = utils.ordered_active_nics()
        expected_order = ["em1", "em2", "eth1", "eth2", "a1", "enp1s0f0",
                          "enp8s0", "enp10s0", "ens1f1", "ens1f2", "z1"]

        self.assertEqual(expected_order, nics)

    def test_ordered_active_nics_with_dpdk_and_sriov(self):

        tmpdir = tempfile.mkdtemp()
        self.stub_out('os_net_config.common.SYS_CLASS_NET', tmpdir)
        tmp_pci_dir = tempfile.mkdtemp()
        self.stub_out('os_net_config.common._SYS_BUS_PCI_DEV', tmp_pci_dir)
        physfn_path = common._SYS_BUS_PCI_DEV + '/0000:05:01.1/physfn'
        os.makedirs(physfn_path)

        sriov_map = """
        - device_type: pf
          drivers_autoprobe: true
          link_mode: legacy
          mac_address: 6c:fe:54:3f:8a:01
          name: ens1f1
          numvfs: 5
          pci_address: '0000:19:00.1'
          promisc: 'on'
          vdpa: false
        - device_type: pf
          drivers_autoprobe: true
          link_mode: legacy
          mac_address: 6c:fe:54:3f:8a:02
          name: ens1f2
          numvfs: 5
          pci_address: '0000:19:00.2'
          promisc: 'on'
          vdpa: false
        - device:
            name: ens1f1
            vfid: 0
          device_type: vf
          max_tx_rate: 0
          min_tx_rate: 0
          name: ens1f1v0
          pci_address: '0000:19:06.0'
          promisc: 'off'
          spoofcheck: 'off'
          trust: 'on'
          vlan_id: 72
        - device:
              name: ens1f2
              vfid: 0
          device_type: vf
          max_tx_rate: 0
          min_tx_rate: 0
          name: ens1f2v0
          pci_address: 0000:19:0a.0
          promisc: 'off'
          spoofcheck: 'off'
          trust: 'on'
          vlan_id: 72"""
        utils.write_config(common.SRIOV_CONFIG_FILE, sriov_map)

        def test_is_available_nic(interface_name, check_active):
            return True
        self.stub_out('os_net_config.utils._is_available_nic',
                      test_is_available_nic)

        for nic in ['a1', 'em1', 'em2', 'eth2', 'z1',
                    'enp8s0', 'enp10s0', 'enp1s0f0']:
            with open(os.path.join(tmpdir, nic), 'w') as f:
                f.write(nic)

        utils._update_dpdk_map('eth1', '0000:03:00.0', '01:02:03:04:05:06',
                               'vfio-pci')
        utils._update_dpdk_map('p3p1', '0000:04:00.0', '01:02:03:04:05:07',
                               'igb_uio')
        utils._update_dpdk_map('p3p0_0', '0000:05:01.1', 'AA:02:03:04:05:FF',
                               'vfio-pci')

        nics = utils.ordered_active_nics()

        self.assertEqual('em1', nics[0])
        self.assertEqual('em2', nics[1])
        self.assertEqual('eth1', nics[2])  # DPDK bound nic
        self.assertEqual('eth2', nics[3])
        self.assertEqual('a1', nics[4])
        self.assertEqual('enp1s0f0', nics[5])
        self.assertEqual('enp8s0', nics[6])
        self.assertEqual('enp10s0', nics[7])
        self.assertEqual('ens1f1', nics[8])
        self.assertEqual('ens1f2', nics[9])
        self.assertEqual('p3p1', nics[10])  # DPDK bound nic
        self.assertEqual('z1', nics[11])

    def test_ordered_active_nics_with_dpdk_mapping_of_vf(self):
        tmpdir = tempfile.mkdtemp()
        self.stub_out('os_net_config.common.SYS_CLASS_NET', tmpdir)
        tmp_pci_dir = tempfile.mkdtemp()
        self.stub_out('os_net_config.common._SYS_BUS_PCI_DEV', tmp_pci_dir)
        physfn_path = common._SYS_BUS_PCI_DEV + '/0000:05:01.1/physfn'
        os.makedirs(physfn_path)

        def test_is_available_nic(interface_name, check_active):
            return True
        self.stub_out('os_net_config.utils._is_available_nic',
                      test_is_available_nic)

        utils._update_dpdk_map('eth2_0', '0000:06:01.1', 'AA:02:03:04:05:FE',
                               'vfio-pci')
        utils.update_sriov_vf_map('eth2', 0, 'eth2_0')

        nics = utils.ordered_active_nics()

        self.assertEqual(len(nics), 0)

        shutil.rmtree(tmpdir)
        shutil.rmtree(tmp_pci_dir)

    def test_interface_mac_raises(self):
        self.assertRaises(IOError, common.interface_mac, 'ens20f2p3')

    def test_interface_mac_dpdk_and_sriov(self):

        tmpdir = tempfile.mkdtemp()
        self.stub_out('os_net_config.common.SYS_CLASS_NET', tmpdir)
        tmp_pci_dir = tempfile.mkdtemp()
        self.stub_out('os_net_config.common._SYS_BUS_PCI_DEV', tmp_pci_dir)
        physfn_path = common._SYS_BUS_PCI_DEV + '/0000:05:01.1/physfn'
        os.makedirs(physfn_path)

        sriov_map = """
        - device_type: pf
          drivers_autoprobe: true
          link_mode: legacy
          mac_address: 6c:fe:54:3f:8a:01
          name: ens1f1
          numvfs: 5
          pci_address: '0000:19:00.1'
          promisc: 'on'
          vdpa: false
        - device_type: pf
          drivers_autoprobe: true
          link_mode: legacy
          mac_address: 6c:fe:54:3f:8a:02
          name: ens1f2
          numvfs: 5
          pci_address: '0000:19:00.2'
          promisc: 'on'
          vdpa: false
        - device:
            name: ens1f1
            vfid: 0
          device_type: vf
          max_tx_rate: 0
          min_tx_rate: 0
          name: ens1f1v0
          pci_address: '0000:19:06.0'
          promisc: 'off'
          spoofcheck: 'off'
          trust: 'on'
          vlan_id: 72
        - device:
              name: ens1f2
              vfid: 0
          device_type: vf
          max_tx_rate: 0
          min_tx_rate: 0
          name: ens1f2v0
          pci_address: 0000:19:0a.0
          promisc: 'off'
          spoofcheck: 'off'
          trust: 'on'
          vlan_id: 72"""
        utils.write_config(common.SRIOV_CONFIG_FILE, sriov_map)

        def test_is_available_nic(interface_name, check_active):
            return True
        self.stub_out('os_net_config.utils._is_available_nic',
                      test_is_available_nic)

        for nic in ['a1', 'em1', 'em2', 'eth2', 'z1',
                    'enp8s0', 'enp10s0', 'enp1s0f0']:
            with open(os.path.join(tmpdir, nic), 'w') as f:
                f.write(nic)

        utils._update_dpdk_map('eth1', '0000:03:00.0', '01:02:03:04:05:06',
                               'vfio-pci')
        utils._update_dpdk_map('p3p1', '0000:04:00.0', '01:02:03:04:05:07',
                               'igb_uio')
        utils._update_dpdk_map('p3p0_0', '0000:05:01.1', 'AA:02:03:04:05:FF',
                               'vfio-pci')
        self.assertEqual("01:02:03:04:05:07", common.interface_mac("p3p1"))
        self.assertEqual("6c:fe:54:3f:8a:02", common.interface_mac("ens1f2"))
        self.assertEqual("AA:02:03:04:05:FF", common.interface_mac("p3p0_0"))

    def test_get_dpdk_devargs_mlnx(self):
        def test_get_pci_address(ifname):
            return "0000:00:07.0"

        self.stub_out('os_net_config.common.get_pci_address',
                      test_get_pci_address)
        tmpdir = tempfile.mkdtemp()
        self.stub_out('os_net_config.common.SYS_CLASS_NET', tmpdir)
        nic = 'p4p1'
        nic_path = os.path.join(tmpdir, nic)
        os.makedirs(nic_path)
        os.makedirs(os.path.join(nic_path, 'device'))
        # Testing standard Mellanox Connect-X cards
        with open(os.path.join(nic_path, 'operstate'), 'w') as f:
            f.write('up')
        with open(os.path.join(nic_path, 'address'), 'w') as f:
            f.write('00:0f:21:69:39:14')
        with open(os.path.join(nic_path, 'device', 'vendor'), 'w') as f:
            f.write('0x15b3')
        self.assertEqual(utils.get_dpdk_devargs(nic, False),
                         '0000:00:07.0')
        # Testing VFs of Mellanox Connect-X cards
        os.makedirs(os.path.join(nic_path, 'device', 'physfn'))
        self.assertEqual(utils.get_dpdk_devargs(nic, False),
                         '0000:00:07.0')

        # Check if devargs is derived, when the operstate is down
        os.rmdir(os.path.join(nic_path, 'device', 'physfn'))
        with open(os.path.join(nic_path, 'operstate'), 'w') as f:
            f.write('down')
        self.assertEqual(utils.get_dpdk_devargs(nic, False),
                         '0000:00:07.0')

        # now testing the Mellanox CX3
        with open(os.path.join(nic_path, 'device', 'device'), 'w') as f:
            f.write('0x1007')
        self.assertEqual(utils.get_dpdk_devargs(nic, False),
                         'class=eth,mac=00:0f:21:69:39:14')
        with open(os.path.join(nic_path, 'device', 'vendor'), 'w') as f:
            f.write('0x15b4')
        self.assertEqual(utils.get_dpdk_devargs(nic, False),
                         '0000:00:07.0')
        shutil.rmtree(tmpdir)

    def test_is_active_nic_for_sriov_vf(self):

        tmpdir = tempfile.mkdtemp()
        self.stub_out('os_net_config.common.SYS_CLASS_NET', tmpdir)

        # SR-IOV PF = ens802f0
        # SR-IOV VF = enp129s2
        for nic in ['ens802f0', 'enp129s2']:
            nic_path = os.path.join(tmpdir, nic)
            os.makedirs(nic_path)
            os.makedirs(os.path.join(nic_path, 'device'))
            with open(os.path.join(nic_path, 'operstate'), 'w') as f:
                f.write('up')
            with open(os.path.join(nic_path, 'address'), 'w') as f:
                f.write('1.2.3.4')

        nic_path = os.path.join(tmpdir, 'enp129s2', 'device', 'physfn')
        os.makedirs(nic_path)

        self.assertEqual(utils.is_active_nic('ens802f0'), True)
        self.assertEqual(utils.is_active_nic('enp129s2'), False)

        shutil.rmtree(tmpdir)

    def test_get_vpp_interface(self):
        def test_execute(name, *args, **kwargs):
            if 'systemctl' in name:
                return None, None
            if 'vppctl' in name:
                return _VPPCTL_OUTPUT, None

        self.stub_out('oslo_concurrency.processutils.execute',
                      test_execute)

        int_info = utils._get_vpp_interface('0000:00:09.0')
        self.assertIsNotNone(int_info)
        self.assertEqual('GigabitEthernet0/9/0', int_info['name'])
        self.assertEqual('1', int_info['index'])
        self.assertIsNone(utils._get_vpp_interface(None))
        self.assertIsNone(utils._get_vpp_interface('0000:01:09.0'))
        self.assertRaises(utils.VppException,
                          utils._get_vpp_interface, '0000:09.0')

    def test_remove_dpdk_interface_by_name(self):

        def fake_detach(pci):
            return 0

        def fake_unbind(pci):
            return 0

        def fake_get_dpdk_pci_address(name):
            # Mock the PCI address lookup for interface names
            if name == 'eth1':
                return '0000:03:00.0'
            elif name == 'eth2':
                return '0000:04:00.0'
            return None

        self.stub_out('os_net_config.utils.detach_dpdk_interfaces',
                      fake_detach)
        self.stub_out('os_net_config.utils.unbind_dpdk_interfaces',
                      fake_unbind)
        self.stub_out('os_net_config.common.get_dpdk_pci_address',
                      fake_get_dpdk_pci_address)

        dpdk_map = [
            {'name': 'eth1', 'pci_address': '0000:03:00.0',
             'mac_address': '01:02:03:04:05:06', 'driver': 'vfio-pci'},
            {'name': 'eth2', 'pci_address': '0000:04:00.0',
             'mac_address': '01:02:03:04:05:07', 'driver': 'igb_uio'}
        ]
        common.write_yaml_config(common.DPDK_MAPPING_FILE, dpdk_map)
        utils.remove_dpdk_interface('eth1')
        contents = common.get_file_data(common.DPDK_MAPPING_FILE)
        new_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(new_map))
        self.assertEqual(new_map[0]['name'], 'eth2')

    def test_remove_dpdk_interface_by_pci(self):

        def fake_detach(pci):
            return 0

        def fake_unbind(pci):
            return 0

        self.stub_out('os_net_config.utils.detach_dpdk_interfaces',
                      fake_detach)
        self.stub_out('os_net_config.utils.unbind_dpdk_interfaces',
                      fake_unbind)

        dpdk_map = [
            {'name': 'eth1', 'pci_address': '0000:03:00.0',
             'mac_address': '01:02:03:04:05:06', 'driver': 'vfio-pci'},
            {'name': 'eth2', 'pci_address': '0000:04:00.0',
             'mac_address': '01:02:03:04:05:07', 'driver': 'igb_uio'}
        ]
        common.write_yaml_config(common.DPDK_MAPPING_FILE, dpdk_map)
        utils.remove_dpdk_interface('0000:04:00.0')
        contents = common.get_file_data(common.DPDK_MAPPING_FILE)
        new_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(new_map))
        self.assertEqual(new_map[0]['pci_address'], '0000:03:00.0')

    def test_remove_dpdk_interface_not_found(self):
        def fake_detach(pci):
            return 0

        def fake_unbind(pci):
            return 0

        def fake_get_dpdk_pci_address(name):
            # Return None for non-existent interface
            return None

        self.stub_out('os_net_config.utils.detach_dpdk_interfaces',
                      fake_detach)
        self.stub_out('os_net_config.utils.unbind_dpdk_interfaces',
                      fake_unbind)
        self.stub_out('os_net_config.common.get_dpdk_pci_address',
                      fake_get_dpdk_pci_address)

        dpdk_map = [
            {'name': 'eth1', 'pci_address': '0000:03:00.0',
             'mac_address': '01:02:03:04:05:06', 'driver': 'vfio-pci'}
        ]
        common.write_yaml_config(common.DPDK_MAPPING_FILE, dpdk_map)

        # Try to remove non-existent interface
        utils.remove_dpdk_interface('eth999')

        # Map should remain unchanged
        contents = common.get_file_data(common.DPDK_MAPPING_FILE)
        new_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(new_map))

    def test_remove_dpdk_interface_unbind_fails(self):
        def fake_detach(pci):
            return 0

        def fake_unbind(pci):
            return 1  # Simulate unbind failure

        def fake_get_dpdk_pci_address(name):
            if name == 'eth1':
                return '0000:03:00.0'
            return None

        self.stub_out('os_net_config.utils.detach_dpdk_interfaces',
                      fake_detach)
        self.stub_out('os_net_config.utils.unbind_dpdk_interfaces',
                      fake_unbind)
        self.stub_out('os_net_config.common.get_dpdk_pci_address',
                      fake_get_dpdk_pci_address)

        dpdk_map = [
            {'name': 'eth1', 'pci_address': '0000:03:00.0',
             'mac_address': '01:02:03:04:05:06', 'driver': 'vfio-pci'}
        ]
        common.write_yaml_config(common.DPDK_MAPPING_FILE, dpdk_map)

        # Remove should fail but not crash
        utils.remove_dpdk_interface('eth1')

        # Map should remain unchanged due to unbind failure
        contents = common.get_file_data(common.DPDK_MAPPING_FILE)
        new_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(new_map))

    def test_is_pci_address_format_valid(self):
        # Test valid PCI address formats
        self.assertTrue(utils.is_pci_address_format('0000:03:00.0'))
        self.assertTrue(utils.is_pci_address_format('0000:04:00.1'))
        self.assertTrue(utils.is_pci_address_format('0001:82:00.2'))
        self.assertTrue(utils.is_pci_address_format('FFFF:FF:1F.7'))
        self.assertTrue(utils.is_pci_address_format('abcd:ef:12.3'))

    def test_is_pci_address_format_invalid(self):
        # Test invalid formats
        self.assertFalse(utils.is_pci_address_format('eth1'))
        self.assertFalse(utils.is_pci_address_format('enp3s0f0'))
        # Wrong domain length
        self.assertFalse(utils.is_pci_address_format('000:03:00.0'))
        # Wrong bus length
        self.assertFalse(utils.is_pci_address_format('0000:3:00.0'))
        # Wrong device length
        self.assertFalse(utils.is_pci_address_format('0000:03:0.0'))
        # Missing function
        self.assertFalse(utils.is_pci_address_format('0000:03:00.'))
        # Missing function separator
        self.assertFalse(utils.is_pci_address_format('0000:03:00'))
        # Function too long
        self.assertFalse(utils.is_pci_address_format('0000:03:00.10'))
        # Empty string
        self.assertFalse(utils.is_pci_address_format(''))
        # None
        self.assertFalse(utils.is_pci_address_format(None))
        # Non-string
        self.assertFalse(utils.is_pci_address_format(123))
        # Device field out of range (max 1F)
        self.assertFalse(utils.is_pci_address_format('0000:03:20.0'))
        self.assertFalse(utils.is_pci_address_format('0000:03:FF.0'))
        # Function field out of range (max 7)
        self.assertFalse(utils.is_pci_address_format('0000:03:00.8'))
        self.assertFalse(utils.is_pci_address_format('0000:03:00.F'))

    def test_delete_dpdk_map_entry_removes_file_when_empty(self):
        dpdk_map = [
            {'name': 'eth1', 'pci_address': '0000:03:00.0',
             'mac_address': '01:02:03:04:05:06', 'driver': 'vfio-pci'}
        ]
        common.write_yaml_config(common.DPDK_MAPPING_FILE, dpdk_map)
        utils.delete_dpdk_map_entry('eth1')
        self.assertFalse(os.path.exists(common.DPDK_MAPPING_FILE))

    def test_delete_dpdk_map_entry_by_pci(self):
        dpdk_map = [
            {'name': 'eth1', 'pci_address': '0000:03:00.0',
             'mac_address': '01:02:03:04:05:06', 'driver': 'vfio-pci'},
            {'name': 'eth2', 'pci_address': '0000:04:00.0',
             'mac_address': '01:02:03:04:05:07', 'driver': 'igb_uio'}
        ]
        common.write_yaml_config(common.DPDK_MAPPING_FILE, dpdk_map)
        utils.delete_dpdk_map_entry('0000:03:00.0')
        contents = common.get_file_data(common.DPDK_MAPPING_FILE)
        new_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(new_map))
        self.assertEqual(new_map[0]['name'], 'eth2')

    def test_delete_dpdk_map_entry_not_found(self):
        dpdk_map = [
            {'name': 'eth1', 'pci_address': '0000:03:00.0',
             'mac_address': '01:02:03:04:05:06', 'driver': 'vfio-pci'}
        ]
        common.write_yaml_config(common.DPDK_MAPPING_FILE, dpdk_map)

        # Try to delete non-existent entry
        utils.delete_dpdk_map_entry('eth999')

        # Map should remain unchanged but warning should be logged
        contents = common.get_file_data(common.DPDK_MAPPING_FILE)
        new_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(1, len(new_map))

    @mock.patch('os_net_config.utils.processutils.execute',
                return_value=('', None))
    def test_get_vpp_interface_name_multiple_iterations(self, mock_execute):
        self.assertIsNone(utils._get_vpp_interface('0000:00:09.0', 2, 1))
        self.assertEqual(4, mock_execute.call_count)

    def test_get_vpp_bond(self):
        def test_execute(name, *args, **kwargs):
            if 'systemctl' in name:
                return None, None
            if 'vppctl' in name:
                return _VPPBOND_OUTPUT, None

        self.stub_out('oslo_concurrency.processutils.execute', test_execute)
        bond_info = utils._get_vpp_bond(['1', '2'])
        self.assertIsNotNone(bond_info)
        self.assertEqual('BondEthernet0', bond_info['name'])
        self.assertEqual('3', bond_info['index'])
        self.assertIsNone(utils._get_vpp_bond(['1']))
        self.assertIsNone(utils._get_vpp_bond(['1', '2', '3']))
        self.assertIsNone(utils._get_vpp_bond([]))

    def test_generate_vpp_config(self):
        tmpdir = tempfile.mkdtemp()
        config_path = os.path.join(tmpdir, 'startup.conf')
        with open(config_path, 'w') as f:
            f.write(_INITIAL_VPP_CONFIG)
        vpp_exec_path = os.path.join(tmpdir, 'vpp-exec')
        utils._VPP_EXEC_FILE = vpp_exec_path

        int1 = objects.VppInterface('em1', options="vlan-strip-offload off")
        int1.pci_dev = '0000:00:09.0'
        int2 = objects.VppInterface('em2')
        int2.pci_dev = '0000:00:09.1'
        interfaces = [int1, int2]
        bonds = []
        expected_config = '''
unix {
  exec %s
  nodaemon
  log /tmp/vpp.log
  full-coredump
}


api-trace {
  on
}

api-segment {
  gid vpp
}

dpdk {
  dev 0000:00:09.1
  uio-driver vfio-pci
  dev 0000:00:09.0 {vlan-strip-offload off}

}
''' % vpp_exec_path
        self.assertEqual(expected_config,
                         utils.generate_vpp_config(config_path, interfaces,
                                                   bonds))

        bonds = [objects.VppBond('net_bonding0', members=interfaces,
                                 bonding_options='mode=2,xmit_policy=l3')]
        expected_config = '''
unix {
  exec %s
  nodaemon
  log /tmp/vpp.log
  full-coredump
}


api-trace {
  on
}

api-segment {
  gid vpp
}

dpdk {
  vdev net_bonding0,slave=0000:00:09.0,slave=0000:00:09.1,mode=2,xmit_policy=l3
  dev 0000:00:09.1
  uio-driver vfio-pci
  dev 0000:00:09.0 {vlan-strip-offload off}

}
''' % vpp_exec_path
        self.assertEqual(expected_config,
                         utils.generate_vpp_config(config_path, interfaces,
                                                   bonds))

    def test_update_vpp_mapping(self):
        tmpdir = tempfile.mkdtemp()
        vpp_exec_path = os.path.join(tmpdir, 'vpp-exec')
        utils._VPP_EXEC_FILE = vpp_exec_path

        def test_get_dpdk_map():
            return [{'name': 'eth1', 'pci_address': '0000:00:09.0',
                     'mac_address': '01:02:03:04:05:06',
                     'driver': 'vfio-pci'}]

        self.stub_out('os_net_config.common.get_dpdk_map', test_get_dpdk_map)

        def test_execute(name, *args, **kwargs):
            return None, None
        self.stub_out('oslo_concurrency.processutils.execute', test_execute)

        def test_get_vpp_interface(pci_dev, tries, timeout):
            return {'name': 'GigabitEthernet0/9/0', 'index': '1'}

        self.stub_out('os_net_config.utils._get_vpp_interface',
                      test_get_vpp_interface)

        int1 = objects.VppInterface('eth1', options="vlan-strip-offload off")
        int1.pci_dev = '0000:00:09.0'
        int1.hwaddr = '01:02:03:04:05:06'
        int2 = objects.VppInterface('eth2')
        int2.pci_dev = '0000:00:09.1'
        int2.hwaddr = '01:02:03:04:05:07'
        interfaces = [int1, int2]

        utils.update_vpp_mapping(interfaces, [])

        contents = common.get_file_data(common.DPDK_MAPPING_FILE)

        dpdk_test = [{'name': 'eth1', 'pci_address': '0000:00:09.0',
                      'mac_address': '01:02:03:04:05:06',
                      'driver': 'vfio-pci'},
                     {'name': 'eth2', 'pci_address': '0000:00:09.1',
                      'mac_address': '01:02:03:04:05:07',
                      'driver': 'vfio-pci'}]
        dpdk_map = yaml.safe_load(contents) if contents else []
        self.assertEqual(2, len(dpdk_map))
        self.assertListEqual(dpdk_test, dpdk_map)

    def test_interface_maxmtu_success(self):
        """Test get_interface_maxmtu when max_mtu is valid."""
        def test_execute(*args, **kwargs):
            json_output = '[{"ifname": "eth0", "max_mtu": 9000}]'
            return json_output, None

        def test_get_noop():
            return False

        self.stub_out('oslo_concurrency.processutils.execute', test_execute)
        self.stub_out('os_net_config.common.get_noop', test_get_noop)

        result = utils.get_interface_maxmtu("eth0")
        self.assertEqual(9000, result)

    def test_get_interface_maxmtu_empty_output(self):
        """Test get_interface_maxmtu when max_mtu is empty."""
        def test_execute(name, *args, **kwargs):
            return "", ""

        def test_get_noop():
            return False

        self.stub_out('oslo_concurrency.processutils.execute', test_execute)
        self.stub_out('os_net_config.common.get_noop', test_get_noop)

        result = utils.get_interface_maxmtu("eth0")
        self.assertEqual(-1, result)

    def test_get_interface_maxmtu_command_stderr_error(self):
        """Test get_interface_maxmtu for error."""
        def test_execute(name, *args, **kwargs):
            return "", "Device not found"

        def test_get_noop():
            return False

        self.stub_out('oslo_concurrency.processutils.execute', test_execute)
        self.stub_out('os_net_config.common.get_noop', test_get_noop)

        result = utils.get_interface_maxmtu("eth0")
        self.assertEqual(-1, result)

    def test_remove_dpdk_interface_detach_device_not_found(self):
        def fake_detach(pci):
            return 0  # Device not found now returns 0 (already detached)

        def fake_unbind(pci):
            return 0

        def fake_get_dpdk_pci_address(name):
            if name == 'eth1':
                return '0000:03:00.0'
            return None

        self.stub_out('os_net_config.utils.detach_dpdk_interfaces',
                      fake_detach)
        self.stub_out('os_net_config.utils.unbind_dpdk_interfaces',
                      fake_unbind)
        self.stub_out('os_net_config.common.get_dpdk_pci_address',
                      fake_get_dpdk_pci_address)

        dpdk_map = [
            {'name': 'eth1', 'pci_address': '0000:03:00.0',
             'mac_address': '01:02:03:04:05:06', 'driver': 'vfio-pci'}
        ]
        common.write_yaml_config(common.DPDK_MAPPING_FILE, dpdk_map)

        # Should succeed since device is already detached
        utils.remove_dpdk_interface('eth1')

        # Entry should be removed from map since unbind succeeded
        self.assertFalse(os.path.exists(common.DPDK_MAPPING_FILE))

    def test_remove_dpdk_interface_detach_device_in_use_with_retries(self):
        def fake_detach(pci):
            return 1  # Device is being used by interfaces (after retries)

        def fake_unbind(pci):
            return 0

        def fake_get_dpdk_pci_address(name):
            if name == 'eth1':
                return '0000:03:00.0'
            return None

        self.stub_out('os_net_config.utils.detach_dpdk_interfaces',
                      fake_detach)
        self.stub_out('os_net_config.utils.unbind_dpdk_interfaces',
                      fake_unbind)
        self.stub_out('os_net_config.common.get_dpdk_pci_address',
                      fake_get_dpdk_pci_address)

        dpdk_map = [
            {'name': 'eth1', 'pci_address': '0000:03:00.0',
             'mac_address': '01:02:03:04:05:06', 'driver': 'vfio-pci'}
        ]
        common.write_yaml_config(common.DPDK_MAPPING_FILE, dpdk_map)

        # Should abort without proceeding to unbind (after retries failed)
        utils.remove_dpdk_interface('eth1')

        # Entry should be removed from map since unbind succeeded
        self.assertFalse(os.path.exists(common.DPDK_MAPPING_FILE))

    def test_detach_dpdk_interfaces_retry_mechanism(self):
        """Test that detach retries when device is in use but succeeds"""
        retry_count = 0

        def fake_execute(*cmd):
            nonlocal retry_count
            retry_count += 1
            if retry_count <= 3:
                # First 3 attempts return "device in use" error
                return "", ("Device '0000:03:00.0' is being used by the "
                            "following interfaces: dpdk1. Remove them before "
                            "detaching")
            else:
                # 4th attempt succeeds
                return "", ""

        def fake_sleep(seconds):
            pass  # Mock sleep to avoid delays in tests

        self.stub_out('oslo_concurrency.processutils.execute', fake_execute)
        self.stub_out('time.sleep', fake_sleep)

        result = utils.detach_dpdk_interfaces('0000:03:00.0')

        # Should succeed after retries
        self.assertEqual(0, result)
        # Should have made 4 attempts (1 initial + 3 retries)
        self.assertEqual(4, retry_count)

    def test_detach_dpdk_interfaces_retry_mechanism_exception(self):
        """Test retry mechanism when device in use error occurs."""
        from oslo_concurrency import processutils
        retry_count = 0

        def fake_execute(*cmd):
            nonlocal retry_count
            retry_count += 1
            if retry_count <= 2:
                # First 2 attempts raise exception with "device in use" error
                raise processutils.ProcessExecutionError(
                    cmd=cmd,
                    stderr="Device '0000:03:00.0' is being used by the "
                           "following interfaces: dpdk1. Remove them before "
                           "detaching"
                )
            else:
                # 3rd attempt succeeds
                return "", ""

        def fake_sleep(seconds):
            pass  # Mock sleep to avoid delays in tests

        self.stub_out('oslo_concurrency.processutils.execute', fake_execute)
        self.stub_out('time.sleep', fake_sleep)

        result = utils.detach_dpdk_interfaces('0000:03:00.0')

        # Should succeed after retries
        self.assertEqual(0, result)
        # Should have made 3 attempts (1 initial + 2 retries)
        self.assertEqual(3, retry_count)

    def test_detach_dpdk_interfaces_max_retries_exceeded_exception(self):
        """Test returns error when max retries exceeded."""
        from oslo_concurrency import processutils
        retry_count = 0

        def fake_execute(*cmd):
            nonlocal retry_count
            retry_count += 1
            # Always raise exception with "device in use" error
            raise processutils.ProcessExecutionError(
                cmd=cmd,
                stderr="Device '0000:03:00.0' is being used by the "
                       "following interfaces: dpdk1. Remove them before "
                       "detaching"
            )

        def fake_sleep(seconds):
            pass  # Mock sleep to avoid delays in tests

        self.stub_out('oslo_concurrency.processutils.execute', fake_execute)
        self.stub_out('time.sleep', fake_sleep)

        result = utils.detach_dpdk_interfaces('0000:03:00.0')

        # Should fail after max retries
        self.assertEqual(1, result)
        # Should have made 6 attempts (1 initial + 5 retries)
        self.assertEqual(6, retry_count)

    def test_handle_dpdk_detach_error_device_not_found(self):
        """Test _handle_dpdk_detach_error for device not found"""
        result = utils._handle_dpdk_detach_error(
            '0000:03:00.0', 'not found in DPDK', 0, 5, 2
        )
        self.assertEqual('success', result)

    def test_handle_dpdk_detach_error_device_in_use_retry(self):
        """Test for device in use within retry limit"""
        def fake_sleep(seconds):
            pass  # Mock sleep to avoid delays in tests

        self.stub_out('time.sleep', fake_sleep)

        result = utils._handle_dpdk_detach_error(
            '0000:03:00.0',
            "Device '0000:03:00.0' is being used by the following "
            "interfaces: dpdk1",
            0, 5, 2
        )
        self.assertEqual('retry', result)

    def test_handle_dpdk_detach_error_device_in_use_max_retries(self):
        """Test _handle_dpdk_detach_error for device in use at max retries"""
        result = utils._handle_dpdk_detach_error(
            '0000:03:00.0',
            "Device '0000:03:00.0' is being used by the following "
            "interfaces: dpdk1",
            5, 5, 2
        )
        self.assertEqual('error', result)

    def test_handle_dpdk_detach_error_other_error(self):
        """Test _handle_dpdk_detach_error for other types of errors"""
        result = utils._handle_dpdk_detach_error(
            '0000:03:00.0', 'Some other error message', 0, 5, 2
        )
        self.assertEqual('error', result)

    def test_remove_sriov_entries_for_pf_with_entries(self):
        sriov_map = [
            {'device_type': 'pf', 'name': 'eth1', 'numvfs': 5},
            {'device_type': 'vf', 'device': {'name': 'eth1', 'vfid': 0},
             'name': 'eth1_0'},
            {'device_type': 'vf', 'device': {'name': 'eth1', 'vfid': 1},
             'name': 'eth1_1'},
            {'device_type': 'pf', 'name': 'eth2', 'numvfs': 3},
            {'device_type': 'vf', 'device': {'name': 'eth2', 'vfid': 0},
             'name': 'eth2_0'}
        ]
        common.write_yaml_config(common.SRIOV_CONFIG_FILE, sriov_map)

        utils.remove_entries_for_sriov_dev('eth1')

        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)
        remaining_map = yaml.safe_load(contents)
        # eth1 and it's vf entries removed
        self.assertEqual(2, len(remaining_map))
        # Should only have eth2 entries left
        self.assertEqual('eth2', remaining_map[0]['name'])
        self.assertEqual('eth2', remaining_map[1]['device']['name'])
        self.assertEqual('eth2_0', remaining_map[1]['name'])

    def test_remove_sriov_entries_pciaddr_empty_map(self):
        def fake_disable_service():
            pass
        self.stub_out('os_net_config.utils.disable_sriov_config_service',
                      fake_disable_service)

        sriov_map = [{
            'device_type': 'pf', 'link_mode': 'legacy',
            'name': 'eth1', 'numvfs': 10, 'promisc': 'on',
            'drivers_autoprobe': True, 'vdpa': False,
            'pci_address': '0000:8a:07.1',
            'mac_address': 'aa:bb:cc:dd:ee:ff'}]
        common.write_yaml_config(common.SRIOV_CONFIG_FILE, sriov_map)

        utils.remove_entries_for_sriov_dev('0000:8a:07.1')

        # File should be removed when map becomes empty
        self.assertFalse(os.path.exists(common.SRIOV_CONFIG_FILE))

    def test_remove_sriov_entries_for_pf_nonexistent_pf(self):
        sriov_map = [
            {'device_type': 'pf', 'name': 'eth1', 'numvfs': 5},
            {'device_type': 'vf', 'device': {'name': 'eth1', 'vfid': 0},
             'name': 'eth1_0'}
        ]
        common.write_yaml_config(common.SRIOV_CONFIG_FILE, sriov_map)

        utils.remove_entries_for_sriov_dev('eth999')

        # Original map should remain unchanged
        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)
        remaining_map = yaml.safe_load(contents)
        self.assertEqual(2, len(remaining_map))

    def test_remove_sriov_entries_for_vf_only(self):
        sriov_map = [
            {'device_type': 'pf', 'name': 'eth1', 'numvfs': 5},
            {'device_type': 'vf', 'device': {'name': 'eth1', 'vfid': 0},
             'name': 'eth1_0'},
            {'device_type': 'vf', 'device': {'name': 'eth1', 'vfid': 1},
             'name': 'eth1_1'},
            {'device_type': 'pf', 'name': 'eth2', 'numvfs': 3},
            {'device_type': 'vf', 'device': {'name': 'eth2', 'vfid': 0},
             'name': 'eth2_0'}
        ]
        common.write_yaml_config(common.SRIOV_CONFIG_FILE, sriov_map)

        utils.remove_entries_for_sriov_dev('sriov:eth1:0')

        contents = common.get_file_data(common.SRIOV_CONFIG_FILE)
        remaining_map = yaml.safe_load(contents)
        self.assertEqual(4, len(remaining_map))
        # Should only have eth2 and eth1 PF, eth1-vf0 entries left
        self.assertEqual('eth1', remaining_map[0]['name'])
        self.assertEqual('eth2', remaining_map[2]['name'])

    def test_disable_sriov_config_service_success(self):
        def fake_execute(*args, **kwargs):
            pass
        self.stub_out('oslo_concurrency.processutils.execute',
                      fake_execute)

        # Should not raise any exceptions
        utils.disable_sriov_config_service()

    def test_disable_sriov_config_service_failure(self):
        from oslo_concurrency import processutils

        def fake_execute(*args, **kwargs):
            raise processutils.ProcessExecutionError(
                'Service disable failed')
        self.stub_out('oslo_concurrency.processutils.execute',
                      fake_execute)

        # Should log warning but not raise exception
        utils.disable_sriov_config_service()

    def test_unset_driverctl_override_success(self):
        """Test successful unset_driverctl_override operation."""
        def fake_execute(*args, **kwargs):
            return ('', '')
        self.stub_out('oslo_concurrency.processutils.execute',
                      fake_execute)

        result = common.unset_driverctl_override('0000:03:00.0')
        self.assertEqual(0, result)

    def test_unset_driverctl_override_error_but_override_removed(self):
        """Test unset_driverctl_override succeeds when override removed."""
        from oslo_concurrency import processutils

        call_count = [0]

        def fake_execute(*args, **kwargs):
            call_count[0] += 1
            if call_count[0] == 1:
                # First call (unset-override) fails
                raise processutils.ProcessExecutionError(
                    'driverctl: failed to bind device 0000:03:00.0 to '
                    'driver i40e')
            else:
                # Second call (list-override) shows no override exists
                return ('', '')
        self.stub_out('oslo_concurrency.processutils.execute',
                      fake_execute)

        # Mock logger to verify the info message is logged
        mocked_logger = mock.Mock()
        self.stub_out('os_net_config.common.logger.info', mocked_logger)

        result = common.unset_driverctl_override('0000:03:00.0')
        self.assertEqual(0, result)

        # Verify the expected log message was called
        expected_calls = [
            mock.call('%s: running %s', '0000:03:00.0',
                      'driverctl unset-override 0000:03:00.0'),
            mock.call('%s: Driver override successfully removed despite '
                      'driverctl error (likely bind-back failure when '
                      'sriov_drivers_autoprobe=0)', '0000:03:00.0')
        ]
        mocked_logger.assert_has_calls(expected_calls)

    def test_unset_driverctl_override_error_and_override_still_exists(self):
        """Test unset_driverctl_override fails when override exists."""
        from oslo_concurrency import processutils

        call_count = [0]

        def fake_execute(*args, **kwargs):
            call_count[0] += 1
            if call_count[0] == 1:
                # First call (unset-override) fails
                raise processutils.ProcessExecutionError(
                    'driverctl: some other error occurred')
            else:
                # Second call (list-override) shows override still exists
                return ('0000:03:00.0 vfio-pci\n', '')
        self.stub_out('oslo_concurrency.processutils.execute',
                      fake_execute)

        # Mock logger to verify the error message is logged
        mocked_logger = mock.Mock()
        self.stub_out('os_net_config.common.logger.error', mocked_logger)

        result = common.unset_driverctl_override('0000:03:00.0')
        self.assertEqual(1, result)

        # Verify the error was logged
        expected_msg = '%s: Failed to remove driver override. %s'
        mocked_logger.assert_called_with(expected_msg, '0000:03:00.0',
                                         mock.ANY)

    def test_unset_driverctl_override_list_command_fails(self):
        """Test unset_driverctl_override when list-override command fails."""
        from oslo_concurrency import processutils

        call_count = [0]

        def fake_execute(*args, **kwargs):
            call_count[0] += 1
            if call_count[0] == 1:
                # First call (unset-override) fails
                raise processutils.ProcessExecutionError(
                    'driverctl: failed to bind device')
            else:
                # Second call (list-override) also fails
                raise processutils.ProcessExecutionError(
                    'driverctl: list command failed')
        self.stub_out('oslo_concurrency.processutils.execute',
                      fake_execute)

        # Mock logger
        mocked_logger = mock.Mock()
        self.stub_out('os_net_config.common.logger.warning', mocked_logger)
        mocked_error_logger = mock.Mock()
        self.stub_out('os_net_config.common.logger.error', mocked_error_logger)

        result = common.unset_driverctl_override('0000:03:00.0')
        self.assertEqual(1, result)

        # Verify warning was logged for list-override failure
        self.assertTrue(mocked_logger.called)
        # Verify error was logged for overall failure
        self.assertTrue(mocked_error_logger.called)
