sailesh1993 / rpms / cloud-init

Forked from rpms/cloud-init 10 months ago
Clone
fc6e82
From b48dda73da94782d7ab0c455fa382d3a5ef3c419 Mon Sep 17 00:00:00 2001
fc6e82
From: Daniel Watkins <oddbloke@ubuntu.com>
fc6e82
Date: Mon, 8 Mar 2021 12:50:57 -0500
fc6e82
Subject: net: exclude OVS internal interfaces in get_interfaces (#829)
fc6e82
fc6e82
`get_interfaces` is used to in two ways, broadly: firstly, to determine
fc6e82
the available interfaces when converting cloud network configuration
fc6e82
formats to cloud-init's network configuration formats; and, secondly, to
fc6e82
ensure that any interfaces which are specified in network configuration
fc6e82
are (a) available, and (b) named correctly.  The first of these is
fc6e82
unaffected by this commit, as no clouds support Open vSwitch
fc6e82
configuration in their network configuration formats.
fc6e82
fc6e82
For the second, we check that MAC addresses of physical devices are
fc6e82
unique.  In some OVS configurations, there are OVS-created devices which
fc6e82
have duplicate MAC addresses, either with each other or with physical
fc6e82
devices.  As these interfaces are created by OVS, we can be confident
fc6e82
that (a) they will be available when appropriate, and (b) that OVS will
fc6e82
name them correctly.  As such, this commit excludes any OVS-internal
fc6e82
interfaces from the set of interfaces returned by `get_interfaces`.
fc6e82
fc6e82
LP: #1912844
fc6e82
---
fc6e82
 cloudinit/net/__init__.py                     |  62 +++++++++
fc6e82
 cloudinit/net/tests/test_init.py              | 119 ++++++++++++++++++
fc6e82
 .../sources/helpers/tests/test_openstack.py   |   5 +
fc6e82
 cloudinit/sources/tests/test_oracle.py        |   4 +
fc6e82
 .../integration_tests/bugs/test_lp1912844.py  | 103 +++++++++++++++
fc6e82
 .../test_datasource/test_configdrive.py       |   8 ++
fc6e82
 tests/unittests/test_net.py                   |  20 +++
fc6e82
 7 files changed, 321 insertions(+)
fc6e82
 create mode 100644 tests/integration_tests/bugs/test_lp1912844.py
fc6e82
fc6e82
diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py
fc6e82
index de65e7af..385b7bcc 100644
fc6e82
--- a/cloudinit/net/__init__.py
fc6e82
+++ b/cloudinit/net/__init__.py
fc6e82
@@ -6,6 +6,7 @@
fc6e82
 # This file is part of cloud-init. See LICENSE file for license information.
fc6e82
 
fc6e82
 import errno
fc6e82
+import functools
fc6e82
 import ipaddress
fc6e82
 import logging
fc6e82
 import os
fc6e82
@@ -19,6 +20,19 @@ from cloudinit.url_helper import UrlError, readurl
fc6e82
 LOG = logging.getLogger(__name__)
fc6e82
 SYS_CLASS_NET = "/sys/class/net/"
fc6e82
 DEFAULT_PRIMARY_INTERFACE = 'eth0'
fc6e82
+OVS_INTERNAL_INTERFACE_LOOKUP_CMD = [
fc6e82
+    "ovs-vsctl",
fc6e82
+    "--format",
fc6e82
+    "csv",
fc6e82
+    "--no-headings",
fc6e82
+    "--timeout",
fc6e82
+    "10",
fc6e82
+    "--columns",
fc6e82
+    "name",
fc6e82
+    "find",
fc6e82
+    "interface",
fc6e82
+    "type=internal",
fc6e82
+]
fc6e82
 
fc6e82
 
fc6e82
 def natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
fc6e82
@@ -133,6 +147,52 @@ def master_is_openvswitch(devname):
fc6e82
     return os.path.exists(ovs_path)
fc6e82
 
fc6e82
 
fc6e82
+@functools.lru_cache(maxsize=None)
fc6e82
+def openvswitch_is_installed() -> bool:
fc6e82
+    """Return a bool indicating if Open vSwitch is installed in the system."""
fc6e82
+    ret = bool(subp.which("ovs-vsctl"))
fc6e82
+    if not ret:
fc6e82
+        LOG.debug(
fc6e82
+            "ovs-vsctl not in PATH; not detecting Open vSwitch interfaces"
fc6e82
+        )
fc6e82
+    return ret
fc6e82
+
fc6e82
+
fc6e82
+@functools.lru_cache(maxsize=None)
fc6e82
+def get_ovs_internal_interfaces() -> list:
fc6e82
+    """Return a list of the names of OVS internal interfaces on the system.
fc6e82
+
fc6e82
+    These will all be strings, and are used to exclude OVS-specific interface
fc6e82
+    from cloud-init's network configuration handling.
fc6e82
+    """
fc6e82
+    try:
fc6e82
+        out, _err = subp.subp(OVS_INTERNAL_INTERFACE_LOOKUP_CMD)
fc6e82
+    except subp.ProcessExecutionError as exc:
fc6e82
+        if "database connection failed" in exc.stderr:
fc6e82
+            LOG.info(
fc6e82
+                "Open vSwitch is not yet up; no interfaces will be detected as"
fc6e82
+                " OVS-internal"
fc6e82
+            )
fc6e82
+            return []
fc6e82
+        raise
fc6e82
+    else:
fc6e82
+        return out.splitlines()
fc6e82
+
fc6e82
+
fc6e82
+def is_openvswitch_internal_interface(devname: str) -> bool:
fc6e82
+    """Returns True if this is an OVS internal interface.
fc6e82
+
fc6e82
+    If OVS is not installed or not yet running, this will return False.
fc6e82
+    """
fc6e82
+    if not openvswitch_is_installed():
fc6e82
+        return False
fc6e82
+    ovs_bridges = get_ovs_internal_interfaces()
fc6e82
+    if devname in ovs_bridges:
fc6e82
+        LOG.debug("Detected %s as an OVS interface", devname)
fc6e82
+        return True
fc6e82
+    return False
fc6e82
+
fc6e82
+
fc6e82
 def is_netfailover(devname, driver=None):
fc6e82
     """ netfailover driver uses 3 nics, master, primary and standby.
fc6e82
         this returns True if the device is either the primary or standby
fc6e82
@@ -884,6 +944,8 @@ def get_interfaces(blacklist_drivers=None) -> list:
fc6e82
         # skip nics that have no mac (00:00....)
fc6e82
         if name != 'lo' and mac == zero_mac[:len(mac)]:
fc6e82
             continue
fc6e82
+        if is_openvswitch_internal_interface(name):
fc6e82
+            continue
fc6e82
         # skip nics that have drivers blacklisted
fc6e82
         driver = device_driver(name)
fc6e82
         if driver in blacklist_drivers:
fc6e82
diff --git a/cloudinit/net/tests/test_init.py b/cloudinit/net/tests/test_init.py
fc6e82
index 0535387a..946f8ee2 100644
fc6e82
--- a/cloudinit/net/tests/test_init.py
fc6e82
+++ b/cloudinit/net/tests/test_init.py
fc6e82
@@ -391,6 +391,10 @@ class TestGetDeviceList(CiTestCase):
fc6e82
         self.assertCountEqual(['eth0', 'eth1'], net.get_devicelist())
fc6e82
 
fc6e82
 
fc6e82
+@mock.patch(
fc6e82
+    "cloudinit.net.is_openvswitch_internal_interface",
fc6e82
+    mock.Mock(return_value=False),
fc6e82
+)
fc6e82
 class TestGetInterfaceMAC(CiTestCase):
fc6e82
 
fc6e82
     def setUp(self):
fc6e82
@@ -1224,6 +1228,121 @@ class TestNetFailOver(CiTestCase):
fc6e82
         self.assertFalse(net.is_netfailover(devname, driver))
fc6e82
 
fc6e82
 
fc6e82
+class TestOpenvswitchIsInstalled:
fc6e82
+    """Test cloudinit.net.openvswitch_is_installed.
fc6e82
+
fc6e82
+    Uses the ``clear_lru_cache`` local autouse fixture to allow us to test
fc6e82
+    despite the ``lru_cache`` decorator on the unit under test.
fc6e82
+    """
fc6e82
+
fc6e82
+    @pytest.fixture(autouse=True)
fc6e82
+    def clear_lru_cache(self):
fc6e82
+        net.openvswitch_is_installed.cache_clear()
fc6e82
+
fc6e82
+    @pytest.mark.parametrize(
fc6e82
+        "expected,which_return", [(True, "/some/path"), (False, None)]
fc6e82
+    )
fc6e82
+    @mock.patch("cloudinit.net.subp.which")
fc6e82
+    def test_mirrors_which_result(self, m_which, expected, which_return):
fc6e82
+        m_which.return_value = which_return
fc6e82
+        assert expected == net.openvswitch_is_installed()
fc6e82
+
fc6e82
+    @mock.patch("cloudinit.net.subp.which")
fc6e82
+    def test_only_calls_which_once(self, m_which):
fc6e82
+        net.openvswitch_is_installed()
fc6e82
+        net.openvswitch_is_installed()
fc6e82
+        assert 1 == m_which.call_count
fc6e82
+
fc6e82
+
fc6e82
+@mock.patch("cloudinit.net.subp.subp", return_value=("", ""))
fc6e82
+class TestGetOVSInternalInterfaces:
fc6e82
+    """Test cloudinit.net.get_ovs_internal_interfaces.
fc6e82
+
fc6e82
+    Uses the ``clear_lru_cache`` local autouse fixture to allow us to test
fc6e82
+    despite the ``lru_cache`` decorator on the unit under test.
fc6e82
+    """
fc6e82
+    @pytest.fixture(autouse=True)
fc6e82
+    def clear_lru_cache(self):
fc6e82
+        net.get_ovs_internal_interfaces.cache_clear()
fc6e82
+
fc6e82
+    def test_command_used(self, m_subp):
fc6e82
+        """Test we use the correct command when we call subp"""
fc6e82
+        net.get_ovs_internal_interfaces()
fc6e82
+
fc6e82
+        assert [
fc6e82
+            mock.call(net.OVS_INTERNAL_INTERFACE_LOOKUP_CMD)
fc6e82
+        ] == m_subp.call_args_list
fc6e82
+
fc6e82
+    def test_subp_contents_split_and_returned(self, m_subp):
fc6e82
+        """Test that the command output is appropriately mangled."""
fc6e82
+        stdout = "iface1\niface2\niface3\n"
fc6e82
+        m_subp.return_value = (stdout, "")
fc6e82
+
fc6e82
+        assert [
fc6e82
+            "iface1",
fc6e82
+            "iface2",
fc6e82
+            "iface3",
fc6e82
+        ] == net.get_ovs_internal_interfaces()
fc6e82
+
fc6e82
+    def test_database_connection_error_handled_gracefully(self, m_subp):
fc6e82
+        """Test that the error indicating OVS is down is handled gracefully."""
fc6e82
+        m_subp.side_effect = ProcessExecutionError(
fc6e82
+            stderr="database connection failed"
fc6e82
+        )
fc6e82
+
fc6e82
+        assert [] == net.get_ovs_internal_interfaces()
fc6e82
+
fc6e82
+    def test_other_errors_raised(self, m_subp):
fc6e82
+        """Test that only database connection errors are handled."""
fc6e82
+        m_subp.side_effect = ProcessExecutionError()
fc6e82
+
fc6e82
+        with pytest.raises(ProcessExecutionError):
fc6e82
+            net.get_ovs_internal_interfaces()
fc6e82
+
fc6e82
+    def test_only_runs_once(self, m_subp):
fc6e82
+        """Test that we cache the value."""
fc6e82
+        net.get_ovs_internal_interfaces()
fc6e82
+        net.get_ovs_internal_interfaces()
fc6e82
+
fc6e82
+        assert 1 == m_subp.call_count
fc6e82
+
fc6e82
+
fc6e82
+@mock.patch("cloudinit.net.get_ovs_internal_interfaces")
fc6e82
+@mock.patch("cloudinit.net.openvswitch_is_installed")
fc6e82
+class TestIsOpenVSwitchInternalInterface:
fc6e82
+    def test_false_if_ovs_not_installed(
fc6e82
+        self, m_openvswitch_is_installed, _m_get_ovs_internal_interfaces
fc6e82
+    ):
fc6e82
+        """Test that OVS' absence returns False."""
fc6e82
+        m_openvswitch_is_installed.return_value = False
fc6e82
+
fc6e82
+        assert not net.is_openvswitch_internal_interface("devname")
fc6e82
+
fc6e82
+    @pytest.mark.parametrize(
fc6e82
+        "detected_interfaces,devname,expected_return",
fc6e82
+        [
fc6e82
+            ([], "devname", False),
fc6e82
+            (["notdevname"], "devname", False),
fc6e82
+            (["devname"], "devname", True),
fc6e82
+            (["some", "other", "devices", "and", "ours"], "ours", True),
fc6e82
+        ],
fc6e82
+    )
fc6e82
+    def test_return_value_based_on_detected_interfaces(
fc6e82
+        self,
fc6e82
+        m_openvswitch_is_installed,
fc6e82
+        m_get_ovs_internal_interfaces,
fc6e82
+        detected_interfaces,
fc6e82
+        devname,
fc6e82
+        expected_return,
fc6e82
+    ):
fc6e82
+        """Test that the detected interfaces are used correctly."""
fc6e82
+        m_openvswitch_is_installed.return_value = True
fc6e82
+        m_get_ovs_internal_interfaces.return_value = detected_interfaces
fc6e82
+        assert expected_return == net.is_openvswitch_internal_interface(
fc6e82
+            devname
fc6e82
+        )
fc6e82
+
fc6e82
+
fc6e82
 class TestIsIpAddress:
fc6e82
     """Tests for net.is_ip_address.
fc6e82
 
fc6e82
diff --git a/cloudinit/sources/helpers/tests/test_openstack.py b/cloudinit/sources/helpers/tests/test_openstack.py
fc6e82
index 2bde1e3f..95fb9743 100644
fc6e82
--- a/cloudinit/sources/helpers/tests/test_openstack.py
fc6e82
+++ b/cloudinit/sources/helpers/tests/test_openstack.py
fc6e82
@@ -1,10 +1,15 @@
fc6e82
 # This file is part of cloud-init. See LICENSE file for license information.
fc6e82
 # ./cloudinit/sources/helpers/tests/test_openstack.py
fc6e82
+from unittest import mock
fc6e82
 
fc6e82
 from cloudinit.sources.helpers import openstack
fc6e82
 from cloudinit.tests import helpers as test_helpers
fc6e82
 
fc6e82
 
fc6e82
+@mock.patch(
fc6e82
+    "cloudinit.net.is_openvswitch_internal_interface",
fc6e82
+    mock.Mock(return_value=False)
fc6e82
+)
fc6e82
 class TestConvertNetJson(test_helpers.CiTestCase):
fc6e82
 
fc6e82
     def test_phy_types(self):
fc6e82
diff --git a/cloudinit/sources/tests/test_oracle.py b/cloudinit/sources/tests/test_oracle.py
fc6e82
index a7bbdfd9..dcf33b9b 100644
fc6e82
--- a/cloudinit/sources/tests/test_oracle.py
fc6e82
+++ b/cloudinit/sources/tests/test_oracle.py
fc6e82
@@ -173,6 +173,10 @@ class TestIsPlatformViable(test_helpers.CiTestCase):
fc6e82
         m_read_dmi_data.assert_has_calls([mock.call('chassis-asset-tag')])
fc6e82
 
fc6e82
 
fc6e82
+@mock.patch(
fc6e82
+    "cloudinit.net.is_openvswitch_internal_interface",
fc6e82
+    mock.Mock(return_value=False)
fc6e82
+)
fc6e82
 class TestNetworkConfigFromOpcImds:
fc6e82
     def test_no_secondary_nics_does_not_mutate_input(self, oracle_ds):
fc6e82
         oracle_ds._vnics_data = [{}]
fc6e82
diff --git a/tests/integration_tests/bugs/test_lp1912844.py b/tests/integration_tests/bugs/test_lp1912844.py
fc6e82
new file mode 100644
fc6e82
index 00000000..efafae50
fc6e82
--- /dev/null
fc6e82
+++ b/tests/integration_tests/bugs/test_lp1912844.py
fc6e82
@@ -0,0 +1,103 @@
fc6e82
+"""Integration test for LP: #1912844
fc6e82
+
fc6e82
+cloud-init should ignore OVS-internal interfaces when performing its own
fc6e82
+interface determination: these interfaces are handled fully by OVS, so
fc6e82
+cloud-init should never need to touch them.
fc6e82
+
fc6e82
+This test is a semi-synthetic reproducer for the bug.  It uses a similar
fc6e82
+network configuration, tweaked slightly to DHCP in a way that will succeed even
fc6e82
+on "failed" boots.  The exact bug doesn't reproduce with the NoCloud
fc6e82
+datasource, because it runs at init-local time (whereas the MAAS datasource,
fc6e82
+from the report, runs only at init (network) time): this means that the
fc6e82
+networking code runs before OVS creates its interfaces (which happens after
fc6e82
+init-local but, of course, before networking is up), and so doesn't generate
fc6e82
+the traceback that they cause.  We work around this by calling
fc6e82
+``get_interfaces_by_mac` directly in the test code.
fc6e82
+"""
fc6e82
+import pytest
fc6e82
+
fc6e82
+from tests.integration_tests import random_mac_address
fc6e82
+
fc6e82
+MAC_ADDRESS = random_mac_address()
fc6e82
+
fc6e82
+NETWORK_CONFIG = """\
fc6e82
+bonds:
fc6e82
+    bond0:
fc6e82
+        interfaces:
fc6e82
+            - enp5s0
fc6e82
+        macaddress: {0}
fc6e82
+        mtu: 1500
fc6e82
+bridges:
fc6e82
+        ovs-br:
fc6e82
+            interfaces:
fc6e82
+            - bond0
fc6e82
+            macaddress: {0}
fc6e82
+            mtu: 1500
fc6e82
+            openvswitch: {{}}
fc6e82
+            dhcp4: true
fc6e82
+ethernets:
fc6e82
+    enp5s0:
fc6e82
+      mtu: 1500
fc6e82
+      set-name: enp5s0
fc6e82
+      match:
fc6e82
+          macaddress: {0}
fc6e82
+version: 2
fc6e82
+vlans:
fc6e82
+  ovs-br.100:
fc6e82
+    id: 100
fc6e82
+    link: ovs-br
fc6e82
+    mtu: 1500
fc6e82
+  ovs-br.200:
fc6e82
+    id: 200
fc6e82
+    link: ovs-br
fc6e82
+    mtu: 1500
fc6e82
+""".format(MAC_ADDRESS)
fc6e82
+
fc6e82
+
fc6e82
+SETUP_USER_DATA = """\
fc6e82
+#cloud-config
fc6e82
+packages:
fc6e82
+- openvswitch-switch
fc6e82
+"""
fc6e82
+
fc6e82
+
fc6e82
+@pytest.fixture
fc6e82
+def ovs_enabled_session_cloud(session_cloud):
fc6e82
+    """A session_cloud wrapper, to use an OVS-enabled image for tests.
fc6e82
+
fc6e82
+    This implementation is complicated by wanting to use ``session_cloud``s
fc6e82
+    snapshot cleanup/retention logic, to avoid having to reimplement that here.
fc6e82
+    """
fc6e82
+    old_snapshot_id = session_cloud.snapshot_id
fc6e82
+    with session_cloud.launch(
fc6e82
+        user_data=SETUP_USER_DATA,
fc6e82
+    ) as instance:
fc6e82
+        instance.instance.clean()
fc6e82
+        session_cloud.snapshot_id = instance.snapshot()
fc6e82
+
fc6e82
+    yield session_cloud
fc6e82
+
fc6e82
+    try:
fc6e82
+        session_cloud.delete_snapshot()
fc6e82
+    finally:
fc6e82
+        session_cloud.snapshot_id = old_snapshot_id
fc6e82
+
fc6e82
+
fc6e82
+@pytest.mark.lxd_vm
fc6e82
+def test_get_interfaces_by_mac_doesnt_traceback(ovs_enabled_session_cloud):
fc6e82
+    """Launch our OVS-enabled image and confirm the bug doesn't reproduce."""
fc6e82
+    launch_kwargs = {
fc6e82
+        "config_dict": {
fc6e82
+            "user.network-config": NETWORK_CONFIG,
fc6e82
+            "volatile.eth0.hwaddr": MAC_ADDRESS,
fc6e82
+        },
fc6e82
+    }
fc6e82
+    with ovs_enabled_session_cloud.launch(
fc6e82
+        launch_kwargs=launch_kwargs,
fc6e82
+    ) as client:
fc6e82
+        result = client.execute(
fc6e82
+            "python3 -c"
fc6e82
+            "'from cloudinit.net import get_interfaces_by_mac;"
fc6e82
+            "get_interfaces_by_mac()'"
fc6e82
+        )
fc6e82
+        assert result.ok
fc6e82
diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py
fc6e82
index 6f830cc6..2e2b7847 100644
fc6e82
--- a/tests/unittests/test_datasource/test_configdrive.py
fc6e82
+++ b/tests/unittests/test_datasource/test_configdrive.py
fc6e82
@@ -494,6 +494,10 @@ class TestConfigDriveDataSource(CiTestCase):
fc6e82
         self.assertEqual('config-disk (/dev/anything)', cfg_ds.subplatform)
fc6e82
 
fc6e82
 
fc6e82
+@mock.patch(
fc6e82
+    "cloudinit.net.is_openvswitch_internal_interface",
fc6e82
+    mock.Mock(return_value=False)
fc6e82
+)
fc6e82
 class TestNetJson(CiTestCase):
fc6e82
     def setUp(self):
fc6e82
         super(TestNetJson, self).setUp()
fc6e82
@@ -654,6 +658,10 @@ class TestNetJson(CiTestCase):
fc6e82
             self.assertEqual(out_data, conv_data)
fc6e82
 
fc6e82
 
fc6e82
+@mock.patch(
fc6e82
+    "cloudinit.net.is_openvswitch_internal_interface",
fc6e82
+    mock.Mock(return_value=False)
fc6e82
+)
fc6e82
 class TestConvertNetworkData(CiTestCase):
fc6e82
 
fc6e82
     with_logs = True
fc6e82
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
fc6e82
index c67b5fcc..14d3462f 100644
fc6e82
--- a/tests/unittests/test_net.py
fc6e82
+++ b/tests/unittests/test_net.py
fc6e82
@@ -2908,6 +2908,10 @@ iface eth1 inet dhcp
fc6e82
         self.assertEqual(0, mock_settle.call_count)
fc6e82
 
fc6e82
 
fc6e82
+@mock.patch(
fc6e82
+    "cloudinit.net.is_openvswitch_internal_interface",
fc6e82
+    mock.Mock(return_value=False)
fc6e82
+)
fc6e82
 class TestRhelSysConfigRendering(CiTestCase):
fc6e82
 
fc6e82
     with_logs = True
fc6e82
@@ -3592,6 +3596,10 @@ USERCTL=no
fc6e82
                 expected, self._render_and_read(network_config=v2data))
fc6e82
 
fc6e82
 
fc6e82
+@mock.patch(
fc6e82
+    "cloudinit.net.is_openvswitch_internal_interface",
fc6e82
+    mock.Mock(return_value=False)
fc6e82
+)
fc6e82
 class TestOpenSuseSysConfigRendering(CiTestCase):
fc6e82
 
fc6e82
     with_logs = True
fc6e82
@@ -5009,6 +5017,10 @@ class TestNetRenderers(CiTestCase):
fc6e82
             self.assertTrue(result)
fc6e82
 
fc6e82
 
fc6e82
+@mock.patch(
fc6e82
+    "cloudinit.net.is_openvswitch_internal_interface",
fc6e82
+    mock.Mock(return_value=False)
fc6e82
+)
fc6e82
 class TestGetInterfaces(CiTestCase):
fc6e82
     _data = {'bonds': ['bond1'],
fc6e82
              'bridges': ['bridge1'],
fc6e82
@@ -5158,6 +5170,10 @@ class TestInterfaceHasOwnMac(CiTestCase):
fc6e82
         self.assertFalse(interface_has_own_mac("eth0"))
fc6e82
 
fc6e82
 
fc6e82
+@mock.patch(
fc6e82
+    "cloudinit.net.is_openvswitch_internal_interface",
fc6e82
+    mock.Mock(return_value=False)
fc6e82
+)
fc6e82
 class TestGetInterfacesByMac(CiTestCase):
fc6e82
     _data = {'bonds': ['bond1'],
fc6e82
              'bridges': ['bridge1'],
fc6e82
@@ -5314,6 +5330,10 @@ class TestInterfacesSorting(CiTestCase):
fc6e82
             ['enp0s3', 'enp0s8', 'enp0s13', 'enp1s2', 'enp2s0', 'enp2s3'])
fc6e82
 
fc6e82
 
fc6e82
+@mock.patch(
fc6e82
+    "cloudinit.net.is_openvswitch_internal_interface",
fc6e82
+    mock.Mock(return_value=False)
fc6e82
+)
fc6e82
 class TestGetIBHwaddrsByInterface(CiTestCase):
fc6e82
 
fc6e82
     _ib_addr = '80:00:00:28:fe:80:00:00:00:00:00:00:00:11:22:03:00:33:44:56'
fc6e82
-- 
fc6e82
2.27.0
fc6e82