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