parent
5b0b552e23
commit
8706acdb01
@ -1 +1 @@
|
||||
2ae378aa2ae23b34b0ff123623ba5e2fbdc4928d SOURCES/cloud-init-21.1.tar.gz
|
||||
830185bb5ce87ad86e4d1c0c62329bb255ec1648 SOURCES/cloud-init-22.1.tar.gz
|
||||
|
@ -1 +1 @@
|
||||
SOURCES/cloud-init-21.1.tar.gz
|
||||
SOURCES/cloud-init-22.1.tar.gz
|
||||
|
@ -1,36 +0,0 @@
|
||||
From 699d37a6ff3e343e214943794aac09e4156c2b2b Mon Sep 17 00:00:00 2001
|
||||
From: Eduardo Otubo <otubo@redhat.com>
|
||||
Date: Fri, 7 May 2021 13:36:10 +0200
|
||||
Subject: sysconfig: Don't write BOOTPROTO=dhcp for ipv6 dhcp
|
||||
|
||||
Don't write BOOTPROTO=dhcp for ipv6 dhcp, as BOOTPROTO applies
|
||||
only to ipv4. Explicitly write IPV6_AUTOCONF=no for dhcp on ipv6.
|
||||
|
||||
X-downstream-only: yes
|
||||
|
||||
Resolves: rhbz#1519271
|
||||
Signed-off-by: Ryan McCabe <rmccabe@redhat.com>
|
||||
|
||||
Merged patches (19.4):
|
||||
- 6444df4 sysconfig: Don't disable IPV6_AUTOCONF
|
||||
|
||||
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
|
||||
---
|
||||
tests/unittests/test_net.py | 1 +
|
||||
1 file changed, 1 insertion(+)
|
||||
|
||||
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
|
||||
index c67b5fcc..4ea0e597 100644
|
||||
--- a/tests/unittests/test_net.py
|
||||
+++ b/tests/unittests/test_net.py
|
||||
@@ -1729,6 +1729,7 @@ pre-down route del -net 10.0.0.0/8 gw 11.0.0.1 metric 3 || true
|
||||
BOOTPROTO=none
|
||||
DEVICE=bond0
|
||||
DHCPV6C=yes
|
||||
+ IPV6_AUTOCONF=no
|
||||
IPV6INIT=yes
|
||||
MACADDR=aa:bb:cc:dd:ee:ff
|
||||
ONBOOT=yes
|
||||
--
|
||||
2.27.0
|
||||
|
@ -1,57 +0,0 @@
|
||||
From ccc75c1be3ae08d813193071c798fc905b5c03e5 Mon Sep 17 00:00:00 2001
|
||||
From: Eduardo Otubo <otubo@redhat.com>
|
||||
Date: Fri, 7 May 2021 13:36:12 +0200
|
||||
Subject: DataSourceAzure.py: use hostnamectl to set hostname
|
||||
|
||||
RH-Author: Vitaly Kuznetsov <vkuznets@redhat.com>
|
||||
Message-id: <20180417130754.12918-3-vkuznets@redhat.com>
|
||||
Patchwork-id: 79659
|
||||
O-Subject: [RHEL7.6/7.5.z cloud-init PATCH 2/2] DataSourceAzure.py: use hostnamectl to set hostname
|
||||
Bugzilla: 1568717
|
||||
RH-Acked-by: Eduardo Otubo <otubo@redhat.com>
|
||||
RH-Acked-by: Mohammed Gamal <mgamal@redhat.com>
|
||||
RH-Acked-by: Cathy Avery <cavery@redhat.com>
|
||||
|
||||
The right way to set hostname in RHEL7 is:
|
||||
|
||||
$ hostnamectl set-hostname HOSTNAME
|
||||
|
||||
DataSourceAzure, however, uses:
|
||||
$ hostname HOSTSNAME
|
||||
|
||||
instead and this causes problems. We can't simply change
|
||||
'BUILTIN_DS_CONFIG' in DataSourceAzure.py as 'hostname' is being used
|
||||
for both getting and setting the hostname.
|
||||
|
||||
Long term, this should be fixed in a different way. Cloud-init
|
||||
has distro-specific hostname setting/getting (see
|
||||
cloudinit/distros/rhel.py) and DataSourceAzure.py needs to be switched
|
||||
to use these.
|
||||
|
||||
Resolves: rhbz#1434109
|
||||
|
||||
X-downstream-only: yes
|
||||
|
||||
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
|
||||
Signed-off-by: Vitaly Kuznetsov <vkuznets@redhat.com>
|
||||
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
|
||||
---
|
||||
cloudinit/sources/DataSourceAzure.py | 2 +-
|
||||
1 file changed, 1 insertion(+), 1 deletion(-)
|
||||
|
||||
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
|
||||
index cee630f7..553b5a7e 100755
|
||||
--- a/cloudinit/sources/DataSourceAzure.py
|
||||
+++ b/cloudinit/sources/DataSourceAzure.py
|
||||
@@ -296,7 +296,7 @@ def get_hostname(hostname_command='hostname'):
|
||||
|
||||
|
||||
def set_hostname(hostname, hostname_command='hostname'):
|
||||
- subp.subp([hostname_command, hostname])
|
||||
+ util.subp(['hostnamectl', 'set-hostname', str(hostname)])
|
||||
|
||||
|
||||
@azure_ds_telemetry_reporter
|
||||
--
|
||||
2.27.0
|
||||
|
@ -1,496 +0,0 @@
|
||||
From b48dda73da94782d7ab0c455fa382d3a5ef3c419 Mon Sep 17 00:00:00 2001
|
||||
From: Daniel Watkins <oddbloke@ubuntu.com>
|
||||
Date: Mon, 8 Mar 2021 12:50:57 -0500
|
||||
Subject: net: exclude OVS internal interfaces in get_interfaces (#829)
|
||||
|
||||
`get_interfaces` is used to in two ways, broadly: firstly, to determine
|
||||
the available interfaces when converting cloud network configuration
|
||||
formats to cloud-init's network configuration formats; and, secondly, to
|
||||
ensure that any interfaces which are specified in network configuration
|
||||
are (a) available, and (b) named correctly. The first of these is
|
||||
unaffected by this commit, as no clouds support Open vSwitch
|
||||
configuration in their network configuration formats.
|
||||
|
||||
For the second, we check that MAC addresses of physical devices are
|
||||
unique. In some OVS configurations, there are OVS-created devices which
|
||||
have duplicate MAC addresses, either with each other or with physical
|
||||
devices. As these interfaces are created by OVS, we can be confident
|
||||
that (a) they will be available when appropriate, and (b) that OVS will
|
||||
name them correctly. As such, this commit excludes any OVS-internal
|
||||
interfaces from the set of interfaces returned by `get_interfaces`.
|
||||
|
||||
LP: #1912844
|
||||
---
|
||||
cloudinit/net/__init__.py | 62 +++++++++
|
||||
cloudinit/net/tests/test_init.py | 119 ++++++++++++++++++
|
||||
.../sources/helpers/tests/test_openstack.py | 5 +
|
||||
cloudinit/sources/tests/test_oracle.py | 4 +
|
||||
.../integration_tests/bugs/test_lp1912844.py | 103 +++++++++++++++
|
||||
.../test_datasource/test_configdrive.py | 8 ++
|
||||
tests/unittests/test_net.py | 20 +++
|
||||
7 files changed, 321 insertions(+)
|
||||
create mode 100644 tests/integration_tests/bugs/test_lp1912844.py
|
||||
|
||||
diff --git a/cloudinit/net/__init__.py b/cloudinit/net/__init__.py
|
||||
index de65e7af..385b7bcc 100644
|
||||
--- a/cloudinit/net/__init__.py
|
||||
+++ b/cloudinit/net/__init__.py
|
||||
@@ -6,6 +6,7 @@
|
||||
# This file is part of cloud-init. See LICENSE file for license information.
|
||||
|
||||
import errno
|
||||
+import functools
|
||||
import ipaddress
|
||||
import logging
|
||||
import os
|
||||
@@ -19,6 +20,19 @@ from cloudinit.url_helper import UrlError, readurl
|
||||
LOG = logging.getLogger(__name__)
|
||||
SYS_CLASS_NET = "/sys/class/net/"
|
||||
DEFAULT_PRIMARY_INTERFACE = 'eth0'
|
||||
+OVS_INTERNAL_INTERFACE_LOOKUP_CMD = [
|
||||
+ "ovs-vsctl",
|
||||
+ "--format",
|
||||
+ "csv",
|
||||
+ "--no-headings",
|
||||
+ "--timeout",
|
||||
+ "10",
|
||||
+ "--columns",
|
||||
+ "name",
|
||||
+ "find",
|
||||
+ "interface",
|
||||
+ "type=internal",
|
||||
+]
|
||||
|
||||
|
||||
def natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
|
||||
@@ -133,6 +147,52 @@ def master_is_openvswitch(devname):
|
||||
return os.path.exists(ovs_path)
|
||||
|
||||
|
||||
+@functools.lru_cache(maxsize=None)
|
||||
+def openvswitch_is_installed() -> bool:
|
||||
+ """Return a bool indicating if Open vSwitch is installed in the system."""
|
||||
+ ret = bool(subp.which("ovs-vsctl"))
|
||||
+ if not ret:
|
||||
+ LOG.debug(
|
||||
+ "ovs-vsctl not in PATH; not detecting Open vSwitch interfaces"
|
||||
+ )
|
||||
+ return ret
|
||||
+
|
||||
+
|
||||
+@functools.lru_cache(maxsize=None)
|
||||
+def get_ovs_internal_interfaces() -> list:
|
||||
+ """Return a list of the names of OVS internal interfaces on the system.
|
||||
+
|
||||
+ These will all be strings, and are used to exclude OVS-specific interface
|
||||
+ from cloud-init's network configuration handling.
|
||||
+ """
|
||||
+ try:
|
||||
+ out, _err = subp.subp(OVS_INTERNAL_INTERFACE_LOOKUP_CMD)
|
||||
+ except subp.ProcessExecutionError as exc:
|
||||
+ if "database connection failed" in exc.stderr:
|
||||
+ LOG.info(
|
||||
+ "Open vSwitch is not yet up; no interfaces will be detected as"
|
||||
+ " OVS-internal"
|
||||
+ )
|
||||
+ return []
|
||||
+ raise
|
||||
+ else:
|
||||
+ return out.splitlines()
|
||||
+
|
||||
+
|
||||
+def is_openvswitch_internal_interface(devname: str) -> bool:
|
||||
+ """Returns True if this is an OVS internal interface.
|
||||
+
|
||||
+ If OVS is not installed or not yet running, this will return False.
|
||||
+ """
|
||||
+ if not openvswitch_is_installed():
|
||||
+ return False
|
||||
+ ovs_bridges = get_ovs_internal_interfaces()
|
||||
+ if devname in ovs_bridges:
|
||||
+ LOG.debug("Detected %s as an OVS interface", devname)
|
||||
+ return True
|
||||
+ return False
|
||||
+
|
||||
+
|
||||
def is_netfailover(devname, driver=None):
|
||||
""" netfailover driver uses 3 nics, master, primary and standby.
|
||||
this returns True if the device is either the primary or standby
|
||||
@@ -884,6 +944,8 @@ def get_interfaces(blacklist_drivers=None) -> list:
|
||||
# skip nics that have no mac (00:00....)
|
||||
if name != 'lo' and mac == zero_mac[:len(mac)]:
|
||||
continue
|
||||
+ if is_openvswitch_internal_interface(name):
|
||||
+ continue
|
||||
# skip nics that have drivers blacklisted
|
||||
driver = device_driver(name)
|
||||
if driver in blacklist_drivers:
|
||||
diff --git a/cloudinit/net/tests/test_init.py b/cloudinit/net/tests/test_init.py
|
||||
index 0535387a..946f8ee2 100644
|
||||
--- a/cloudinit/net/tests/test_init.py
|
||||
+++ b/cloudinit/net/tests/test_init.py
|
||||
@@ -391,6 +391,10 @@ class TestGetDeviceList(CiTestCase):
|
||||
self.assertCountEqual(['eth0', 'eth1'], net.get_devicelist())
|
||||
|
||||
|
||||
+@mock.patch(
|
||||
+ "cloudinit.net.is_openvswitch_internal_interface",
|
||||
+ mock.Mock(return_value=False),
|
||||
+)
|
||||
class TestGetInterfaceMAC(CiTestCase):
|
||||
|
||||
def setUp(self):
|
||||
@@ -1224,6 +1228,121 @@ class TestNetFailOver(CiTestCase):
|
||||
self.assertFalse(net.is_netfailover(devname, driver))
|
||||
|
||||
|
||||
+class TestOpenvswitchIsInstalled:
|
||||
+ """Test cloudinit.net.openvswitch_is_installed.
|
||||
+
|
||||
+ Uses the ``clear_lru_cache`` local autouse fixture to allow us to test
|
||||
+ despite the ``lru_cache`` decorator on the unit under test.
|
||||
+ """
|
||||
+
|
||||
+ @pytest.fixture(autouse=True)
|
||||
+ def clear_lru_cache(self):
|
||||
+ net.openvswitch_is_installed.cache_clear()
|
||||
+
|
||||
+ @pytest.mark.parametrize(
|
||||
+ "expected,which_return", [(True, "/some/path"), (False, None)]
|
||||
+ )
|
||||
+ @mock.patch("cloudinit.net.subp.which")
|
||||
+ def test_mirrors_which_result(self, m_which, expected, which_return):
|
||||
+ m_which.return_value = which_return
|
||||
+ assert expected == net.openvswitch_is_installed()
|
||||
+
|
||||
+ @mock.patch("cloudinit.net.subp.which")
|
||||
+ def test_only_calls_which_once(self, m_which):
|
||||
+ net.openvswitch_is_installed()
|
||||
+ net.openvswitch_is_installed()
|
||||
+ assert 1 == m_which.call_count
|
||||
+
|
||||
+
|
||||
+@mock.patch("cloudinit.net.subp.subp", return_value=("", ""))
|
||||
+class TestGetOVSInternalInterfaces:
|
||||
+ """Test cloudinit.net.get_ovs_internal_interfaces.
|
||||
+
|
||||
+ Uses the ``clear_lru_cache`` local autouse fixture to allow us to test
|
||||
+ despite the ``lru_cache`` decorator on the unit under test.
|
||||
+ """
|
||||
+ @pytest.fixture(autouse=True)
|
||||
+ def clear_lru_cache(self):
|
||||
+ net.get_ovs_internal_interfaces.cache_clear()
|
||||
+
|
||||
+ def test_command_used(self, m_subp):
|
||||
+ """Test we use the correct command when we call subp"""
|
||||
+ net.get_ovs_internal_interfaces()
|
||||
+
|
||||
+ assert [
|
||||
+ mock.call(net.OVS_INTERNAL_INTERFACE_LOOKUP_CMD)
|
||||
+ ] == m_subp.call_args_list
|
||||
+
|
||||
+ def test_subp_contents_split_and_returned(self, m_subp):
|
||||
+ """Test that the command output is appropriately mangled."""
|
||||
+ stdout = "iface1\niface2\niface3\n"
|
||||
+ m_subp.return_value = (stdout, "")
|
||||
+
|
||||
+ assert [
|
||||
+ "iface1",
|
||||
+ "iface2",
|
||||
+ "iface3",
|
||||
+ ] == net.get_ovs_internal_interfaces()
|
||||
+
|
||||
+ def test_database_connection_error_handled_gracefully(self, m_subp):
|
||||
+ """Test that the error indicating OVS is down is handled gracefully."""
|
||||
+ m_subp.side_effect = ProcessExecutionError(
|
||||
+ stderr="database connection failed"
|
||||
+ )
|
||||
+
|
||||
+ assert [] == net.get_ovs_internal_interfaces()
|
||||
+
|
||||
+ def test_other_errors_raised(self, m_subp):
|
||||
+ """Test that only database connection errors are handled."""
|
||||
+ m_subp.side_effect = ProcessExecutionError()
|
||||
+
|
||||
+ with pytest.raises(ProcessExecutionError):
|
||||
+ net.get_ovs_internal_interfaces()
|
||||
+
|
||||
+ def test_only_runs_once(self, m_subp):
|
||||
+ """Test that we cache the value."""
|
||||
+ net.get_ovs_internal_interfaces()
|
||||
+ net.get_ovs_internal_interfaces()
|
||||
+
|
||||
+ assert 1 == m_subp.call_count
|
||||
+
|
||||
+
|
||||
+@mock.patch("cloudinit.net.get_ovs_internal_interfaces")
|
||||
+@mock.patch("cloudinit.net.openvswitch_is_installed")
|
||||
+class TestIsOpenVSwitchInternalInterface:
|
||||
+ def test_false_if_ovs_not_installed(
|
||||
+ self, m_openvswitch_is_installed, _m_get_ovs_internal_interfaces
|
||||
+ ):
|
||||
+ """Test that OVS' absence returns False."""
|
||||
+ m_openvswitch_is_installed.return_value = False
|
||||
+
|
||||
+ assert not net.is_openvswitch_internal_interface("devname")
|
||||
+
|
||||
+ @pytest.mark.parametrize(
|
||||
+ "detected_interfaces,devname,expected_return",
|
||||
+ [
|
||||
+ ([], "devname", False),
|
||||
+ (["notdevname"], "devname", False),
|
||||
+ (["devname"], "devname", True),
|
||||
+ (["some", "other", "devices", "and", "ours"], "ours", True),
|
||||
+ ],
|
||||
+ )
|
||||
+ def test_return_value_based_on_detected_interfaces(
|
||||
+ self,
|
||||
+ m_openvswitch_is_installed,
|
||||
+ m_get_ovs_internal_interfaces,
|
||||
+ detected_interfaces,
|
||||
+ devname,
|
||||
+ expected_return,
|
||||
+ ):
|
||||
+ """Test that the detected interfaces are used correctly."""
|
||||
+ m_openvswitch_is_installed.return_value = True
|
||||
+ m_get_ovs_internal_interfaces.return_value = detected_interfaces
|
||||
+ assert expected_return == net.is_openvswitch_internal_interface(
|
||||
+ devname
|
||||
+ )
|
||||
+
|
||||
+
|
||||
class TestIsIpAddress:
|
||||
"""Tests for net.is_ip_address.
|
||||
|
||||
diff --git a/cloudinit/sources/helpers/tests/test_openstack.py b/cloudinit/sources/helpers/tests/test_openstack.py
|
||||
index 2bde1e3f..95fb9743 100644
|
||||
--- a/cloudinit/sources/helpers/tests/test_openstack.py
|
||||
+++ b/cloudinit/sources/helpers/tests/test_openstack.py
|
||||
@@ -1,10 +1,15 @@
|
||||
# This file is part of cloud-init. See LICENSE file for license information.
|
||||
# ./cloudinit/sources/helpers/tests/test_openstack.py
|
||||
+from unittest import mock
|
||||
|
||||
from cloudinit.sources.helpers import openstack
|
||||
from cloudinit.tests import helpers as test_helpers
|
||||
|
||||
|
||||
+@mock.patch(
|
||||
+ "cloudinit.net.is_openvswitch_internal_interface",
|
||||
+ mock.Mock(return_value=False)
|
||||
+)
|
||||
class TestConvertNetJson(test_helpers.CiTestCase):
|
||||
|
||||
def test_phy_types(self):
|
||||
diff --git a/cloudinit/sources/tests/test_oracle.py b/cloudinit/sources/tests/test_oracle.py
|
||||
index a7bbdfd9..dcf33b9b 100644
|
||||
--- a/cloudinit/sources/tests/test_oracle.py
|
||||
+++ b/cloudinit/sources/tests/test_oracle.py
|
||||
@@ -173,6 +173,10 @@ class TestIsPlatformViable(test_helpers.CiTestCase):
|
||||
m_read_dmi_data.assert_has_calls([mock.call('chassis-asset-tag')])
|
||||
|
||||
|
||||
+@mock.patch(
|
||||
+ "cloudinit.net.is_openvswitch_internal_interface",
|
||||
+ mock.Mock(return_value=False)
|
||||
+)
|
||||
class TestNetworkConfigFromOpcImds:
|
||||
def test_no_secondary_nics_does_not_mutate_input(self, oracle_ds):
|
||||
oracle_ds._vnics_data = [{}]
|
||||
diff --git a/tests/integration_tests/bugs/test_lp1912844.py b/tests/integration_tests/bugs/test_lp1912844.py
|
||||
new file mode 100644
|
||||
index 00000000..efafae50
|
||||
--- /dev/null
|
||||
+++ b/tests/integration_tests/bugs/test_lp1912844.py
|
||||
@@ -0,0 +1,103 @@
|
||||
+"""Integration test for LP: #1912844
|
||||
+
|
||||
+cloud-init should ignore OVS-internal interfaces when performing its own
|
||||
+interface determination: these interfaces are handled fully by OVS, so
|
||||
+cloud-init should never need to touch them.
|
||||
+
|
||||
+This test is a semi-synthetic reproducer for the bug. It uses a similar
|
||||
+network configuration, tweaked slightly to DHCP in a way that will succeed even
|
||||
+on "failed" boots. The exact bug doesn't reproduce with the NoCloud
|
||||
+datasource, because it runs at init-local time (whereas the MAAS datasource,
|
||||
+from the report, runs only at init (network) time): this means that the
|
||||
+networking code runs before OVS creates its interfaces (which happens after
|
||||
+init-local but, of course, before networking is up), and so doesn't generate
|
||||
+the traceback that they cause. We work around this by calling
|
||||
+``get_interfaces_by_mac` directly in the test code.
|
||||
+"""
|
||||
+import pytest
|
||||
+
|
||||
+from tests.integration_tests import random_mac_address
|
||||
+
|
||||
+MAC_ADDRESS = random_mac_address()
|
||||
+
|
||||
+NETWORK_CONFIG = """\
|
||||
+bonds:
|
||||
+ bond0:
|
||||
+ interfaces:
|
||||
+ - enp5s0
|
||||
+ macaddress: {0}
|
||||
+ mtu: 1500
|
||||
+bridges:
|
||||
+ ovs-br:
|
||||
+ interfaces:
|
||||
+ - bond0
|
||||
+ macaddress: {0}
|
||||
+ mtu: 1500
|
||||
+ openvswitch: {{}}
|
||||
+ dhcp4: true
|
||||
+ethernets:
|
||||
+ enp5s0:
|
||||
+ mtu: 1500
|
||||
+ set-name: enp5s0
|
||||
+ match:
|
||||
+ macaddress: {0}
|
||||
+version: 2
|
||||
+vlans:
|
||||
+ ovs-br.100:
|
||||
+ id: 100
|
||||
+ link: ovs-br
|
||||
+ mtu: 1500
|
||||
+ ovs-br.200:
|
||||
+ id: 200
|
||||
+ link: ovs-br
|
||||
+ mtu: 1500
|
||||
+""".format(MAC_ADDRESS)
|
||||
+
|
||||
+
|
||||
+SETUP_USER_DATA = """\
|
||||
+#cloud-config
|
||||
+packages:
|
||||
+- openvswitch-switch
|
||||
+"""
|
||||
+
|
||||
+
|
||||
+@pytest.fixture
|
||||
+def ovs_enabled_session_cloud(session_cloud):
|
||||
+ """A session_cloud wrapper, to use an OVS-enabled image for tests.
|
||||
+
|
||||
+ This implementation is complicated by wanting to use ``session_cloud``s
|
||||
+ snapshot cleanup/retention logic, to avoid having to reimplement that here.
|
||||
+ """
|
||||
+ old_snapshot_id = session_cloud.snapshot_id
|
||||
+ with session_cloud.launch(
|
||||
+ user_data=SETUP_USER_DATA,
|
||||
+ ) as instance:
|
||||
+ instance.instance.clean()
|
||||
+ session_cloud.snapshot_id = instance.snapshot()
|
||||
+
|
||||
+ yield session_cloud
|
||||
+
|
||||
+ try:
|
||||
+ session_cloud.delete_snapshot()
|
||||
+ finally:
|
||||
+ session_cloud.snapshot_id = old_snapshot_id
|
||||
+
|
||||
+
|
||||
+@pytest.mark.lxd_vm
|
||||
+def test_get_interfaces_by_mac_doesnt_traceback(ovs_enabled_session_cloud):
|
||||
+ """Launch our OVS-enabled image and confirm the bug doesn't reproduce."""
|
||||
+ launch_kwargs = {
|
||||
+ "config_dict": {
|
||||
+ "user.network-config": NETWORK_CONFIG,
|
||||
+ "volatile.eth0.hwaddr": MAC_ADDRESS,
|
||||
+ },
|
||||
+ }
|
||||
+ with ovs_enabled_session_cloud.launch(
|
||||
+ launch_kwargs=launch_kwargs,
|
||||
+ ) as client:
|
||||
+ result = client.execute(
|
||||
+ "python3 -c"
|
||||
+ "'from cloudinit.net import get_interfaces_by_mac;"
|
||||
+ "get_interfaces_by_mac()'"
|
||||
+ )
|
||||
+ assert result.ok
|
||||
diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py
|
||||
index 6f830cc6..2e2b7847 100644
|
||||
--- a/tests/unittests/test_datasource/test_configdrive.py
|
||||
+++ b/tests/unittests/test_datasource/test_configdrive.py
|
||||
@@ -494,6 +494,10 @@ class TestConfigDriveDataSource(CiTestCase):
|
||||
self.assertEqual('config-disk (/dev/anything)', cfg_ds.subplatform)
|
||||
|
||||
|
||||
+@mock.patch(
|
||||
+ "cloudinit.net.is_openvswitch_internal_interface",
|
||||
+ mock.Mock(return_value=False)
|
||||
+)
|
||||
class TestNetJson(CiTestCase):
|
||||
def setUp(self):
|
||||
super(TestNetJson, self).setUp()
|
||||
@@ -654,6 +658,10 @@ class TestNetJson(CiTestCase):
|
||||
self.assertEqual(out_data, conv_data)
|
||||
|
||||
|
||||
+@mock.patch(
|
||||
+ "cloudinit.net.is_openvswitch_internal_interface",
|
||||
+ mock.Mock(return_value=False)
|
||||
+)
|
||||
class TestConvertNetworkData(CiTestCase):
|
||||
|
||||
with_logs = True
|
||||
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
|
||||
index c67b5fcc..14d3462f 100644
|
||||
--- a/tests/unittests/test_net.py
|
||||
+++ b/tests/unittests/test_net.py
|
||||
@@ -2908,6 +2908,10 @@ iface eth1 inet dhcp
|
||||
self.assertEqual(0, mock_settle.call_count)
|
||||
|
||||
|
||||
+@mock.patch(
|
||||
+ "cloudinit.net.is_openvswitch_internal_interface",
|
||||
+ mock.Mock(return_value=False)
|
||||
+)
|
||||
class TestRhelSysConfigRendering(CiTestCase):
|
||||
|
||||
with_logs = True
|
||||
@@ -3592,6 +3596,10 @@ USERCTL=no
|
||||
expected, self._render_and_read(network_config=v2data))
|
||||
|
||||
|
||||
+@mock.patch(
|
||||
+ "cloudinit.net.is_openvswitch_internal_interface",
|
||||
+ mock.Mock(return_value=False)
|
||||
+)
|
||||
class TestOpenSuseSysConfigRendering(CiTestCase):
|
||||
|
||||
with_logs = True
|
||||
@@ -5009,6 +5017,10 @@ class TestNetRenderers(CiTestCase):
|
||||
self.assertTrue(result)
|
||||
|
||||
|
||||
+@mock.patch(
|
||||
+ "cloudinit.net.is_openvswitch_internal_interface",
|
||||
+ mock.Mock(return_value=False)
|
||||
+)
|
||||
class TestGetInterfaces(CiTestCase):
|
||||
_data = {'bonds': ['bond1'],
|
||||
'bridges': ['bridge1'],
|
||||
@@ -5158,6 +5170,10 @@ class TestInterfaceHasOwnMac(CiTestCase):
|
||||
self.assertFalse(interface_has_own_mac("eth0"))
|
||||
|
||||
|
||||
+@mock.patch(
|
||||
+ "cloudinit.net.is_openvswitch_internal_interface",
|
||||
+ mock.Mock(return_value=False)
|
||||
+)
|
||||
class TestGetInterfacesByMac(CiTestCase):
|
||||
_data = {'bonds': ['bond1'],
|
||||
'bridges': ['bridge1'],
|
||||
@@ -5314,6 +5330,10 @@ class TestInterfacesSorting(CiTestCase):
|
||||
['enp0s3', 'enp0s8', 'enp0s13', 'enp1s2', 'enp2s0', 'enp2s3'])
|
||||
|
||||
|
||||
+@mock.patch(
|
||||
+ "cloudinit.net.is_openvswitch_internal_interface",
|
||||
+ mock.Mock(return_value=False)
|
||||
+)
|
||||
class TestGetIBHwaddrsByInterface(CiTestCase):
|
||||
|
||||
_ib_addr = '80:00:00:28:fe:80:00:00:00:00:00:00:00:11:22:03:00:33:44:56'
|
||||
--
|
||||
2.27.0
|
||||
|
@ -1,87 +0,0 @@
|
||||
From bec5fb60ffae3d1137c7261e5571c2751c5dda25 Mon Sep 17 00:00:00 2001
|
||||
From: James Falcon <TheRealFalcon@users.noreply.github.com>
|
||||
Date: Mon, 8 Mar 2021 14:09:47 -0600
|
||||
Subject: Fix requiring device-number on EC2 derivatives (#836)
|
||||
|
||||
#342 (70dbccbb) introduced the ability to determine route-metrics based on
|
||||
the `device-number` provided by the EC2 IMDS. Not all datasources that
|
||||
subclass EC2 will have this attribute, so allow the old behavior if
|
||||
`device-number` is not present.
|
||||
|
||||
LP: #1917875
|
||||
---
|
||||
cloudinit/sources/DataSourceEc2.py | 3 +-
|
||||
.../unittests/test_datasource/test_aliyun.py | 30 +++++++++++++++++++
|
||||
2 files changed, 32 insertions(+), 1 deletion(-)
|
||||
|
||||
diff --git a/cloudinit/sources/DataSourceEc2.py b/cloudinit/sources/DataSourceEc2.py
|
||||
index 1930a509..a2105dc7 100644
|
||||
--- a/cloudinit/sources/DataSourceEc2.py
|
||||
+++ b/cloudinit/sources/DataSourceEc2.py
|
||||
@@ -765,13 +765,14 @@ def convert_ec2_metadata_network_config(
|
||||
netcfg['ethernets'][nic_name] = dev_config
|
||||
return netcfg
|
||||
# Apply network config for all nics and any secondary IPv4/v6 addresses
|
||||
+ nic_idx = 0
|
||||
for mac, nic_name in sorted(macs_to_nics.items()):
|
||||
nic_metadata = macs_metadata.get(mac)
|
||||
if not nic_metadata:
|
||||
continue # Not a physical nic represented in metadata
|
||||
# device-number is zero-indexed, we want it 1-indexed for the
|
||||
# multiplication on the following line
|
||||
- nic_idx = int(nic_metadata['device-number']) + 1
|
||||
+ nic_idx = int(nic_metadata.get('device-number', nic_idx)) + 1
|
||||
dhcp_override = {'route-metric': nic_idx * 100}
|
||||
dev_config = {'dhcp4': True, 'dhcp4-overrides': dhcp_override,
|
||||
'dhcp6': False,
|
||||
diff --git a/tests/unittests/test_datasource/test_aliyun.py b/tests/unittests/test_datasource/test_aliyun.py
|
||||
index eb2828d5..cab1ac2b 100644
|
||||
--- a/tests/unittests/test_datasource/test_aliyun.py
|
||||
+++ b/tests/unittests/test_datasource/test_aliyun.py
|
||||
@@ -7,6 +7,7 @@ from unittest import mock
|
||||
|
||||
from cloudinit import helpers
|
||||
from cloudinit.sources import DataSourceAliYun as ay
|
||||
+from cloudinit.sources.DataSourceEc2 import convert_ec2_metadata_network_config
|
||||
from cloudinit.tests import helpers as test_helpers
|
||||
|
||||
DEFAULT_METADATA = {
|
||||
@@ -183,6 +184,35 @@ class TestAliYunDatasource(test_helpers.HttprettyTestCase):
|
||||
self.assertEqual(ay.parse_public_keys(public_keys),
|
||||
public_keys['key-pair-0']['openssh-key'])
|
||||
|
||||
+ def test_route_metric_calculated_without_device_number(self):
|
||||
+ """Test that route-metric code works without `device-number`
|
||||
+
|
||||
+ `device-number` is part of EC2 metadata, but not supported on aliyun.
|
||||
+ Attempting to access it will raise a KeyError.
|
||||
+
|
||||
+ LP: #1917875
|
||||
+ """
|
||||
+ netcfg = convert_ec2_metadata_network_config(
|
||||
+ {"interfaces": {"macs": {
|
||||
+ "06:17:04:d7:26:09": {
|
||||
+ "interface-id": "eni-e44ef49e",
|
||||
+ },
|
||||
+ "06:17:04:d7:26:08": {
|
||||
+ "interface-id": "eni-e44ef49f",
|
||||
+ }
|
||||
+ }}},
|
||||
+ macs_to_nics={
|
||||
+ '06:17:04:d7:26:09': 'eth0',
|
||||
+ '06:17:04:d7:26:08': 'eth1',
|
||||
+ }
|
||||
+ )
|
||||
+
|
||||
+ met0 = netcfg['ethernets']['eth0']['dhcp4-overrides']['route-metric']
|
||||
+ met1 = netcfg['ethernets']['eth1']['dhcp4-overrides']['route-metric']
|
||||
+
|
||||
+ # route-metric numbers should be 100 apart
|
||||
+ assert 100 == abs(met0 - met1)
|
||||
+
|
||||
|
||||
class TestIsAliYun(test_helpers.CiTestCase):
|
||||
ALIYUN_PRODUCT = 'Alibaba Cloud ECS'
|
||||
--
|
||||
2.27.0
|
||||
|
@ -1,295 +0,0 @@
|
||||
From 2a2a5cdec0de0b96d503f9357c1641043574f90a Mon Sep 17 00:00:00 2001
|
||||
From: Thomas Stringer <thstring@microsoft.com>
|
||||
Date: Wed, 3 Mar 2021 11:07:43 -0500
|
||||
Subject: [PATCH 1/7] Add flexibility to IMDS api-version (#793)
|
||||
|
||||
RH-Author: Eduardo Otubo <otubo@redhat.com>
|
||||
RH-MergeRequest: 45: Add support for userdata on Azure from IMDS
|
||||
RH-Commit: [1/7] 9aa42581c4ff175fb6f8f4a78d94cac9c9971062
|
||||
RH-Bugzilla: 2023940
|
||||
RH-Acked-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
|
||||
RH-Acked-by: Mohamed Gamal Morsy <mmorsy@redhat.com>
|
||||
|
||||
Add flexibility to IMDS api-version by having both a desired IMDS
|
||||
api-version and a minimum api-version. The desired api-version will
|
||||
be used first, and if that fails it will fall back to the minimum
|
||||
api-version.
|
||||
---
|
||||
cloudinit/sources/DataSourceAzure.py | 113 ++++++++++++++----
|
||||
tests/unittests/test_datasource/test_azure.py | 42 ++++++-
|
||||
2 files changed, 129 insertions(+), 26 deletions(-)
|
||||
|
||||
diff --git a/cloudinit/sources/DataSourceAzure.py b/cloudinit/sources/DataSourceAzure.py
|
||||
index 553b5a7e..de1452ce 100755
|
||||
--- a/cloudinit/sources/DataSourceAzure.py
|
||||
+++ b/cloudinit/sources/DataSourceAzure.py
|
||||
@@ -78,17 +78,15 @@ AGENT_SEED_DIR = '/var/lib/waagent'
|
||||
# In the event where the IMDS primary server is not
|
||||
# available, it takes 1s to fallback to the secondary one
|
||||
IMDS_TIMEOUT_IN_SECONDS = 2
|
||||
-IMDS_URL = "http://169.254.169.254/metadata/"
|
||||
-IMDS_VER = "2019-06-01"
|
||||
-IMDS_VER_PARAM = "api-version={}".format(IMDS_VER)
|
||||
+IMDS_URL = "http://169.254.169.254/metadata"
|
||||
+IMDS_VER_MIN = "2019-06-01"
|
||||
+IMDS_VER_WANT = "2020-09-01"
|
||||
|
||||
|
||||
class metadata_type(Enum):
|
||||
- compute = "{}instance?{}".format(IMDS_URL, IMDS_VER_PARAM)
|
||||
- network = "{}instance/network?{}".format(IMDS_URL,
|
||||
- IMDS_VER_PARAM)
|
||||
- reprovisiondata = "{}reprovisiondata?{}".format(IMDS_URL,
|
||||
- IMDS_VER_PARAM)
|
||||
+ compute = "{}/instance".format(IMDS_URL)
|
||||
+ network = "{}/instance/network".format(IMDS_URL)
|
||||
+ reprovisiondata = "{}/reprovisiondata".format(IMDS_URL)
|
||||
|
||||
|
||||
PLATFORM_ENTROPY_SOURCE = "/sys/firmware/acpi/tables/OEM0"
|
||||
@@ -349,6 +347,8 @@ class DataSourceAzure(sources.DataSource):
|
||||
self.update_events['network'].add(EventType.BOOT)
|
||||
self._ephemeral_dhcp_ctx = None
|
||||
|
||||
+ self.failed_desired_api_version = False
|
||||
+
|
||||
def __str__(self):
|
||||
root = sources.DataSource.__str__(self)
|
||||
return "%s [seed=%s]" % (root, self.seed)
|
||||
@@ -520,8 +520,10 @@ class DataSourceAzure(sources.DataSource):
|
||||
self._wait_for_all_nics_ready()
|
||||
ret = self._reprovision()
|
||||
|
||||
- imds_md = get_metadata_from_imds(
|
||||
- self.fallback_interface, retries=10)
|
||||
+ imds_md = self.get_imds_data_with_api_fallback(
|
||||
+ self.fallback_interface,
|
||||
+ retries=10
|
||||
+ )
|
||||
(md, userdata_raw, cfg, files) = ret
|
||||
self.seed = cdev
|
||||
crawled_data.update({
|
||||
@@ -652,6 +654,57 @@ class DataSourceAzure(sources.DataSource):
|
||||
self.ds_cfg['data_dir'], crawled_data['files'], dirmode=0o700)
|
||||
return True
|
||||
|
||||
+ @azure_ds_telemetry_reporter
|
||||
+ def get_imds_data_with_api_fallback(
|
||||
+ self,
|
||||
+ fallback_nic,
|
||||
+ retries,
|
||||
+ md_type=metadata_type.compute):
|
||||
+ """
|
||||
+ Wrapper for get_metadata_from_imds so that we can have flexibility
|
||||
+ in which IMDS api-version we use. If a particular instance of IMDS
|
||||
+ does not have the api version that is desired, we want to make
|
||||
+ this fault tolerant and fall back to a good known minimum api
|
||||
+ version.
|
||||
+ """
|
||||
+
|
||||
+ if not self.failed_desired_api_version:
|
||||
+ for _ in range(retries):
|
||||
+ try:
|
||||
+ LOG.info(
|
||||
+ "Attempting IMDS api-version: %s",
|
||||
+ IMDS_VER_WANT
|
||||
+ )
|
||||
+ return get_metadata_from_imds(
|
||||
+ fallback_nic=fallback_nic,
|
||||
+ retries=0,
|
||||
+ md_type=md_type,
|
||||
+ api_version=IMDS_VER_WANT
|
||||
+ )
|
||||
+ except UrlError as err:
|
||||
+ LOG.info(
|
||||
+ "UrlError with IMDS api-version: %s",
|
||||
+ IMDS_VER_WANT
|
||||
+ )
|
||||
+ if err.code == 400:
|
||||
+ log_msg = "Fall back to IMDS api-version: {}".format(
|
||||
+ IMDS_VER_MIN
|
||||
+ )
|
||||
+ report_diagnostic_event(
|
||||
+ log_msg,
|
||||
+ logger_func=LOG.info
|
||||
+ )
|
||||
+ self.failed_desired_api_version = True
|
||||
+ break
|
||||
+
|
||||
+ LOG.info("Using IMDS api-version: %s", IMDS_VER_MIN)
|
||||
+ return get_metadata_from_imds(
|
||||
+ fallback_nic=fallback_nic,
|
||||
+ retries=retries,
|
||||
+ md_type=md_type,
|
||||
+ api_version=IMDS_VER_MIN
|
||||
+ )
|
||||
+
|
||||
def device_name_to_device(self, name):
|
||||
return self.ds_cfg['disk_aliases'].get(name)
|
||||
|
||||
@@ -880,10 +933,11 @@ class DataSourceAzure(sources.DataSource):
|
||||
# primary nic is being attached first helps here. Otherwise each nic
|
||||
# could add several seconds of delay.
|
||||
try:
|
||||
- imds_md = get_metadata_from_imds(
|
||||
+ imds_md = self.get_imds_data_with_api_fallback(
|
||||
ifname,
|
||||
5,
|
||||
- metadata_type.network)
|
||||
+ metadata_type.network
|
||||
+ )
|
||||
except Exception as e:
|
||||
LOG.warning(
|
||||
"Failed to get network metadata using nic %s. Attempt to "
|
||||
@@ -1017,7 +1071,10 @@ class DataSourceAzure(sources.DataSource):
|
||||
def _poll_imds(self):
|
||||
"""Poll IMDS for the new provisioning data until we get a valid
|
||||
response. Then return the returned JSON object."""
|
||||
- url = metadata_type.reprovisiondata.value
|
||||
+ url = "{}?api-version={}".format(
|
||||
+ metadata_type.reprovisiondata.value,
|
||||
+ IMDS_VER_MIN
|
||||
+ )
|
||||
headers = {"Metadata": "true"}
|
||||
nl_sock = None
|
||||
report_ready = bool(not os.path.isfile(REPORTED_READY_MARKER_FILE))
|
||||
@@ -2059,7 +2116,8 @@ def _generate_network_config_from_fallback_config() -> dict:
|
||||
@azure_ds_telemetry_reporter
|
||||
def get_metadata_from_imds(fallback_nic,
|
||||
retries,
|
||||
- md_type=metadata_type.compute):
|
||||
+ md_type=metadata_type.compute,
|
||||
+ api_version=IMDS_VER_MIN):
|
||||
"""Query Azure's instance metadata service, returning a dictionary.
|
||||
|
||||
If network is not up, setup ephemeral dhcp on fallback_nic to talk to the
|
||||
@@ -2069,13 +2127,16 @@ def get_metadata_from_imds(fallback_nic,
|
||||
@param fallback_nic: String. The name of the nic which requires active
|
||||
network in order to query IMDS.
|
||||