cloud-init: 23.1.2 -> 23.2
Keep support for udhcpc (waiting for upstream PR: https://github.com/canonical/cloud-init/pull/4190). Vultr patch has been merged upstream (https://github.com/canonical/cloud-init/pull/2151).
This commit is contained in:
parent
619d9c7bb7
commit
67f5018fe6
4 changed files with 337 additions and 244 deletions
|
@ -12,10 +12,10 @@ index b82852e1..c998b21e 100644
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
diff --git a/cloudinit/distros/nixos.py b/cloudinit/distros/nixos.py
|
diff --git a/cloudinit/distros/nixos.py b/cloudinit/distros/nixos.py
|
||||||
new file mode 100644
|
new file mode 100644
|
||||||
index 00000000..d53d2a61
|
index 00000000..d53d2a62
|
||||||
--- /dev/null
|
--- /dev/null
|
||||||
+++ b/cloudinit/distros/nixos.py
|
+++ b/cloudinit/distros/nixos.py
|
||||||
@@ -0,0 +1,103 @@
|
@@ -0,0 +1,109 @@
|
||||||
+# vi: ts=4 expandtab
|
+# vi: ts=4 expandtab
|
||||||
+#
|
+#
|
||||||
+# Copyright (C) 2012 Canonical Ltd.
|
+# Copyright (C) 2012 Canonical Ltd.
|
||||||
|
@ -47,6 +47,7 @@ index 00000000..d53d2a61
|
||||||
+from cloudinit import atomic_helper
|
+from cloudinit import atomic_helper
|
||||||
+
|
+
|
||||||
+from cloudinit.distros.parsers.hostname import HostnameConf
|
+from cloudinit.distros.parsers.hostname import HostnameConf
|
||||||
|
+from cloudinit.net import dhcp
|
||||||
+
|
+
|
||||||
+LOG = logging.getLogger(__name__)
|
+LOG = logging.getLogger(__name__)
|
||||||
+
|
+
|
||||||
|
@ -61,6 +62,11 @@ index 00000000..d53d2a61
|
||||||
+ self.usr_lib_exec = os.path.join(os.path.dirname(__file__),
|
+ self.usr_lib_exec = os.path.join(os.path.dirname(__file__),
|
||||||
+ "../../../../../libexec")
|
+ "../../../../../libexec")
|
||||||
+ self.osfamily = 'nixos'
|
+ self.osfamily = 'nixos'
|
||||||
|
+ self.dhcp_client_priority = [
|
||||||
|
+ dhcp.Udhcpc,
|
||||||
|
+ dhcp.IscDhclient,
|
||||||
|
+ dhcp.Dhcpcd,
|
||||||
|
+ ]
|
||||||
+
|
+
|
||||||
+ def _select_hostname(self, hostname, fqdn):
|
+ def _select_hostname(self, hostname, fqdn):
|
||||||
+ # Prefer the short hostname over the long
|
+ # Prefer the short hostname over the long
|
||||||
|
@ -112,10 +118,10 @@ index 00000000..d53d2a61
|
||||||
+ raise NotImplementedError()
|
+ raise NotImplementedError()
|
||||||
+
|
+
|
||||||
+ def package_command(self, command, args=None, pkgs=None):
|
+ def package_command(self, command, args=None, pkgs=None):
|
||||||
+ raise NotImplementedError()
|
+ pass
|
||||||
+
|
+
|
||||||
+ def set_timezone(self, tz):
|
+ def set_timezone(self, tz):
|
||||||
+ raise NotImplementedError()
|
+ pass
|
||||||
+
|
+
|
||||||
+ def update_package_sources(self):
|
+ def update_package_sources(self):
|
||||||
+ raise NotImplementedError()
|
+ pass
|
||||||
|
|
|
@ -1,15 +1,56 @@
|
||||||
|
From 53260ce3bd70a0852d3e0d5569474214cea0ec0c Mon Sep 17 00:00:00 2001
|
||||||
|
From: =?UTF-8?q?Jean-Fran=C3=A7ois=20Roche?= <jfroche@pyxel.be>
|
||||||
|
Date: Mon, 19 Jun 2023 15:56:46 +0200
|
||||||
|
Subject: [PATCH] net/dhcp: add udhcpc support
|
||||||
|
|
||||||
|
The currently used dhcp client, dhclient, is coming from the unmaintained package, isc-dhcp-client (refer https://www.isc.org/dhcp/) which ended support in 2022.
|
||||||
|
|
||||||
|
This change introduce support for the dhcp client, udhcpc, from the busybox project. Busybox advantages are that it is available across many distributions and comes with lightweight executables.
|
||||||
|
---
|
||||||
|
cloudinit/distros/__init__.py | 8 +-
|
||||||
|
cloudinit/net/dhcp.py | 129 ++++++++++++++++++++++-
|
||||||
|
tests/unittests/net/test_dhcp.py | 175 ++++++++++++++++++++++++++++++-
|
||||||
|
tools/.github-cla-signers | 1 +
|
||||||
|
4 files changed, 309 insertions(+), 4 deletions(-)
|
||||||
|
|
||||||
|
diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py
|
||||||
|
index ec148939..0fab8945 100644
|
||||||
|
--- a/cloudinit/distros/__init__.py
|
||||||
|
+++ b/cloudinit/distros/__init__.py
|
||||||
|
@@ -110,14 +110,18 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta):
|
||||||
|
resolve_conf_fn = "/etc/resolv.conf"
|
||||||
|
|
||||||
|
osfamily: str
|
||||||
|
- dhcp_client_priority = [dhcp.IscDhclient, dhcp.Dhcpcd]
|
||||||
|
+ dhcp_client_priority = [dhcp.IscDhclient, dhcp.Dhcpcd, dhcp.Udhcpc]
|
||||||
|
|
||||||
|
def __init__(self, name, cfg, paths):
|
||||||
|
self._paths = paths
|
||||||
|
self._cfg = cfg
|
||||||
|
self.name = name
|
||||||
|
self.networking: Networking = self.networking_cls()
|
||||||
|
- self.dhcp_client_priority = [dhcp.IscDhclient, dhcp.Dhcpcd]
|
||||||
|
+ self.dhcp_client_priority = [
|
||||||
|
+ dhcp.IscDhclient,
|
||||||
|
+ dhcp.Dhcpcd,
|
||||||
|
+ dhcp.Udhcpc,
|
||||||
|
+ ]
|
||||||
|
|
||||||
|
def _unpickle(self, ci_pkl_version: int) -> None:
|
||||||
|
"""Perform deserialization fixes for Distro."""
|
||||||
diff --git a/cloudinit/net/dhcp.py b/cloudinit/net/dhcp.py
|
diff --git a/cloudinit/net/dhcp.py b/cloudinit/net/dhcp.py
|
||||||
index a9a1c980..2d83089b 100644
|
index 6c8c2f54..f5586cea 100644
|
||||||
--- a/cloudinit/net/dhcp.py
|
--- a/cloudinit/net/dhcp.py
|
||||||
+++ b/cloudinit/net/dhcp.py
|
+++ b/cloudinit/net/dhcp.py
|
||||||
@@ -14,12 +14,48 @@ from io import StringIO
|
@@ -21,6 +21,7 @@ from cloudinit import subp, temp_utils, util
|
||||||
|
from cloudinit.net import (
|
||||||
import configobj
|
find_fallback_nic,
|
||||||
|
get_devicelist,
|
||||||
-from cloudinit import subp, util
|
+ get_ib_interface_hwaddr,
|
||||||
+from cloudinit import subp, util, temp_utils
|
get_interface_mac,
|
||||||
from cloudinit.net import find_fallback_nic, get_devicelist
|
is_ib_interface,
|
||||||
|
)
|
||||||
|
@@ -28,6 +29,37 @@ from cloudinit.net import (
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
NETWORKD_LEASES_DIR = "/run/systemd/netif/leases"
|
NETWORKD_LEASES_DIR = "/run/systemd/netif/leases"
|
||||||
|
@ -17,9 +58,7 @@ index a9a1c980..2d83089b 100644
|
||||||
+log() {
|
+log() {
|
||||||
+ echo "udhcpc[$PPID]" "$interface: $2"
|
+ echo "udhcpc[$PPID]" "$interface: $2"
|
||||||
+}
|
+}
|
||||||
+
|
|
||||||
+[ -z "$1" ] && echo "Error: should be called from udhcpc" && exit 1
|
+[ -z "$1" ] && echo "Error: should be called from udhcpc" && exit 1
|
||||||
+
|
|
||||||
+case $1 in
|
+case $1 in
|
||||||
+ bound|renew)
|
+ bound|renew)
|
||||||
+ cat <<JSON > "$LEASE_FILE"
|
+ cat <<JSON > "$LEASE_FILE"
|
||||||
|
@ -32,17 +71,14 @@ index a9a1c980..2d83089b 100644
|
||||||
+}
|
+}
|
||||||
+JSON
|
+JSON
|
||||||
+ ;;
|
+ ;;
|
||||||
+
|
|
||||||
+ deconfig)
|
+ deconfig)
|
||||||
+ log err "Not supported"
|
+ log err "Not supported"
|
||||||
+ exit 1
|
+ exit 1
|
||||||
+ ;;
|
+ ;;
|
||||||
+
|
|
||||||
+ leasefail | nak)
|
+ leasefail | nak)
|
||||||
+ log err "configuration failed: $1: $message"
|
+ log err "configuration failed: $1: $message"
|
||||||
+ exit 1
|
+ exit 1
|
||||||
+ ;;
|
+ ;;
|
||||||
+
|
|
||||||
+ *)
|
+ *)
|
||||||
+ echo "$0: Unknown udhcpc command: $1" >&2
|
+ echo "$0: Unknown udhcpc command: $1" >&2
|
||||||
+ exit 1
|
+ exit 1
|
||||||
|
@ -52,134 +88,199 @@ index a9a1c980..2d83089b 100644
|
||||||
|
|
||||||
|
|
||||||
class NoDHCPLeaseError(Exception):
|
class NoDHCPLeaseError(Exception):
|
||||||
@@ -43,12 +79,14 @@ class NoDHCPLeaseMissingDhclientError(NoDHCPLeaseError):
|
@@ -50,6 +82,10 @@ class NoDHCPLeaseMissingDhclientError(NoDHCPLeaseError):
|
||||||
|
"""Raised when unable to find dhclient."""
|
||||||
|
|
||||||
|
|
||||||
def maybe_perform_dhcp_discovery(nic=None, dhcp_log_func=None, tmp_dir=None):
|
+class NoDHCPLeaseMissingUdhcpcError(NoDHCPLeaseError):
|
||||||
- """Perform dhcp discovery if nic valid and dhclient command exists.
|
+ """Raised when unable to find udhcpc client."""
|
||||||
+ """Perform dhcp discovery if nic valid and dhclient or udhcpc command
|
|
||||||
+ exists.
|
|
||||||
|
|
||||||
If the nic is invalid or undiscoverable or dhclient command is not found,
|
|
||||||
skip dhcp_discovery and return an empty dict.
|
|
||||||
|
|
||||||
- @param nic: Name of the network interface we want to run dhclient on.
|
|
||||||
+ @param nic: Name of the network interface we want to run the dhcp client
|
|
||||||
+ on.
|
|
||||||
@param dhcp_log_func: A callable accepting the dhclient output and error
|
|
||||||
streams.
|
|
||||||
@param tmp_dir: Tmp dir with exec permissions.
|
|
||||||
@@ -66,11 +104,16 @@ def maybe_perform_dhcp_discovery(nic=None, dhcp_log_func=None, tmp_dir=None):
|
|
||||||
"Skip dhcp_discovery: nic %s not found in get_devicelist.", nic
|
|
||||||
)
|
|
||||||
raise NoDHCPLeaseInterfaceError()
|
|
||||||
+ udhcpc_path = subp.which("udhcpc")
|
|
||||||
+ if udhcpc_path:
|
|
||||||
+ return dhcp_udhcpc_discovery(udhcpc_path, nic, dhcp_log_func)
|
|
||||||
dhclient_path = subp.which("dhclient")
|
|
||||||
- if not dhclient_path:
|
|
||||||
- LOG.debug("Skip dhclient configuration: No dhclient command found.")
|
|
||||||
- raise NoDHCPLeaseMissingDhclientError()
|
|
||||||
- return dhcp_discovery(dhclient_path, nic, dhcp_log_func)
|
|
||||||
+ if dhclient_path:
|
|
||||||
+ return dhcp_discovery(dhclient_path, nic, dhcp_log_func)
|
|
||||||
+ LOG.debug(
|
|
||||||
+ "Skip dhclient configuration: No dhclient or udhcpc command found."
|
|
||||||
+ )
|
|
||||||
+ raise NoDHCPLeaseMissingDhclientError()
|
|
||||||
|
|
||||||
|
|
||||||
def parse_dhcp_lease_file(lease_file):
|
|
||||||
@@ -107,6 +150,61 @@ def parse_dhcp_lease_file(lease_file):
|
|
||||||
return dhcp_leases
|
|
||||||
|
|
||||||
|
|
||||||
+def dhcp_udhcpc_discovery(udhcpc_cmd_path, interface, dhcp_log_func=None):
|
|
||||||
+ """Run udhcpc on the interface without scripts or filesystem artifacts.
|
|
||||||
+
|
+
|
||||||
+ @param udhcpc_cmd_path: Full path to the udhcpc used.
|
|
||||||
+ @param interface: Name of the network interface on which to dhclient.
|
|
||||||
+ @param dhcp_log_func: A callable accepting the dhclient output and error
|
|
||||||
+ streams.
|
|
||||||
+
|
+
|
||||||
+ @return: A list of dicts of representing the dhcp leases parsed from the
|
def select_dhcp_client(distro):
|
||||||
+ dhclient.lease file or empty list.
|
"""distros set priority list, select based on this order which to use
|
||||||
+ """
|
|
||||||
+ LOG.debug("Performing a dhcp discovery on %s", interface)
|
@@ -60,7 +96,10 @@ def select_dhcp_client(distro):
|
||||||
|
dhcp_client = client()
|
||||||
|
LOG.debug("DHCP client selected: %s", client.client_name)
|
||||||
|
return dhcp_client
|
||||||
|
- except NoDHCPLeaseMissingDhclientError:
|
||||||
|
+ except (
|
||||||
|
+ NoDHCPLeaseMissingDhclientError,
|
||||||
|
+ NoDHCPLeaseMissingUdhcpcError,
|
||||||
|
+ ):
|
||||||
|
LOG.warning("DHCP client not found: %s", client.client_name)
|
||||||
|
raise NoDHCPLeaseMissingDhclientError()
|
||||||
|
|
||||||
|
@@ -497,3 +536,91 @@ class Dhcpcd:
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
raise NoDHCPLeaseMissingDhclientError("Dhcpcd not yet implemented")
|
||||||
+
|
+
|
||||||
+ tmp_dir = temp_utils.get_tmp_ancestor(needs_exe=True)
|
|
||||||
+ lease_file = os.path.join(tmp_dir, interface + ".lease.json")
|
|
||||||
+ with contextlib.suppress(FileNotFoundError):
|
|
||||||
+ os.remove(lease_file)
|
|
||||||
+
|
+
|
||||||
+ # udhcpc needs the interface up to send initial discovery packets.
|
+class Udhcpc(DhcpClient):
|
||||||
+ # Generally dhclient relies on dhclient-script PREINIT action to bring the
|
+ client_name = "udhcpc"
|
||||||
+ # link up before attempting discovery. Since we are using -sf /bin/true,
|
+
|
||||||
+ # we need to do that "link up" ourselves first.
|
+ def __init__(self):
|
||||||
+ subp.subp(["ip", "link", "set", "dev", interface, "up"], capture=True)
|
+ self.udhcpc_path = subp.which("udhcpc")
|
||||||
+ udhcpc_script = os.path.join(tmp_dir, "udhcpc_script")
|
+ if not self.udhcpc_path:
|
||||||
+ util.write_file(udhcpc_script, UDHCPC_SCRIPT, 0o755)
|
+ LOG.debug("Skip udhcpc configuration: No udhcpc command found.")
|
||||||
+ cmd = [
|
+ raise NoDHCPLeaseMissingUdhcpcError()
|
||||||
+ udhcpc_cmd_path,
|
+
|
||||||
+ "-O",
|
+ def dhcp_discovery(
|
||||||
+ "staticroutes",
|
+ self,
|
||||||
+ "-i",
|
|
||||||
+ interface,
|
+ interface,
|
||||||
+ "-s",
|
+ dhcp_log_func=None,
|
||||||
+ udhcpc_script,
|
+ distro=None,
|
||||||
+ "-n", # Exit if lease is not obtained
|
+ ):
|
||||||
+ "-q", # Exit after obtaining lease
|
+ """Run udhcpc on the interface without scripts or filesystem artifacts.
|
||||||
+ "-f", # Run in foreground
|
|
||||||
+ "-v",
|
|
||||||
+ ]
|
|
||||||
+
|
+
|
||||||
+ out, err = subp.subp(
|
+ @param interface: Name of the network interface on which to run udhcpc.
|
||||||
+ cmd, update_env={"LEASE_FILE": lease_file}, capture=True
|
+ @param dhcp_log_func: A callable accepting the udhcpc output and
|
||||||
+ )
|
+ error streams.
|
||||||
+
|
+
|
||||||
+ if dhcp_log_func is not None:
|
+ @return: A list of dicts of representing the dhcp leases parsed from
|
||||||
+ dhcp_log_func(out, err)
|
+ the udhcpc lease file.
|
||||||
+ lease_json = util.load_json(util.load_file(lease_file))
|
+ """
|
||||||
+ static_routes = lease_json["static_routes"].split()
|
+ LOG.debug("Performing a dhcp discovery on %s", interface)
|
||||||
+ if static_routes:
|
+
|
||||||
+ # format: dest1/mask gw1 ... destn/mask gwn
|
+ tmp_dir = temp_utils.get_tmp_ancestor(needs_exe=True)
|
||||||
+ lease_json["static_routes"] = [
|
+ lease_file = os.path.join(tmp_dir, interface + ".lease.json")
|
||||||
+ i for i in zip(static_routes[::2], static_routes[1::2])
|
+ with contextlib.suppress(FileNotFoundError):
|
||||||
|
+ os.remove(lease_file)
|
||||||
|
+
|
||||||
|
+ # udhcpc needs the interface up to send initial discovery packets
|
||||||
|
+ subp.subp(["ip", "link", "set", "dev", interface, "up"], capture=True)
|
||||||
|
+
|
||||||
|
+ udhcpc_script = os.path.join(tmp_dir, "udhcpc_script")
|
||||||
|
+ util.write_file(udhcpc_script, UDHCPC_SCRIPT, 0o755)
|
||||||
|
+
|
||||||
|
+ cmd = [
|
||||||
|
+ self.udhcpc_path,
|
||||||
|
+ "-O",
|
||||||
|
+ "staticroutes",
|
||||||
|
+ "-i",
|
||||||
|
+ interface,
|
||||||
|
+ "-s",
|
||||||
|
+ udhcpc_script,
|
||||||
|
+ "-n", # Exit if lease is not obtained
|
||||||
|
+ "-q", # Exit after obtaining lease
|
||||||
|
+ "-f", # Run in foreground
|
||||||
|
+ "-v",
|
||||||
+ ]
|
+ ]
|
||||||
+ return [lease_json]
|
|
||||||
+
|
+
|
||||||
|
+ # For INFINIBAND port the dhcpc must be running with
|
||||||
|
+ # client id option. So here we are checking if the interface is
|
||||||
|
+ # INFINIBAND or not. If yes, we are generating the the client-id to be
|
||||||
|
+ # used with the udhcpc
|
||||||
|
+ if is_ib_interface(interface):
|
||||||
|
+ dhcp_client_identifier = get_ib_interface_hwaddr(
|
||||||
|
+ interface, ethernet_format=True
|
||||||
|
+ )
|
||||||
|
+ cmd.extend(
|
||||||
|
+ ["-x", "0x3d:%s" % dhcp_client_identifier.replace(":", "")]
|
||||||
|
+ )
|
||||||
|
+ try:
|
||||||
|
+ out, err = subp.subp(
|
||||||
|
+ cmd, update_env={"LEASE_FILE": lease_file}, capture=True
|
||||||
|
+ )
|
||||||
|
+ except subp.ProcessExecutionError as error:
|
||||||
|
+ LOG.debug(
|
||||||
|
+ "udhcpc exited with code: %s stderr: %r stdout: %r",
|
||||||
|
+ error.exit_code,
|
||||||
|
+ error.stderr,
|
||||||
|
+ error.stdout,
|
||||||
|
+ )
|
||||||
|
+ raise NoDHCPLeaseError from error
|
||||||
+
|
+
|
||||||
def dhcp_discovery(dhclient_cmd_path, interface, dhcp_log_func=None):
|
+ if dhcp_log_func is not None:
|
||||||
"""Run dhclient on the interface without scripts or filesystem artifacts.
|
+ dhcp_log_func(out, err)
|
||||||
|
+
|
||||||
|
+ lease_json = util.load_json(util.load_file(lease_file))
|
||||||
|
+ static_routes = lease_json["static_routes"].split()
|
||||||
|
+ if static_routes:
|
||||||
|
+ # format: dest1/mask gw1 ... destn/mask gwn
|
||||||
|
+ lease_json["static_routes"] = [
|
||||||
|
+ i for i in zip(static_routes[::2], static_routes[1::2])
|
||||||
|
+ ]
|
||||||
|
+ return [lease_json]
|
||||||
diff --git a/tests/unittests/net/test_dhcp.py b/tests/unittests/net/test_dhcp.py
|
diff --git a/tests/unittests/net/test_dhcp.py b/tests/unittests/net/test_dhcp.py
|
||||||
index 40340553..8913cf65 100644
|
index 55d4c6e9..9123cd15 100644
|
||||||
--- a/tests/unittests/net/test_dhcp.py
|
--- a/tests/unittests/net/test_dhcp.py
|
||||||
+++ b/tests/unittests/net/test_dhcp.py
|
+++ b/tests/unittests/net/test_dhcp.py
|
||||||
@@ -12,6 +12,7 @@ from cloudinit.net.dhcp import (
|
@@ -13,6 +13,8 @@ from cloudinit.net.dhcp import (
|
||||||
NoDHCPLeaseError,
|
NoDHCPLeaseError,
|
||||||
NoDHCPLeaseInterfaceError,
|
NoDHCPLeaseInterfaceError,
|
||||||
NoDHCPLeaseMissingDhclientError,
|
NoDHCPLeaseMissingDhclientError,
|
||||||
+ dhcp_udhcpc_discovery,
|
+ NoDHCPLeaseMissingUdhcpcError,
|
||||||
dhcp_discovery,
|
+ Udhcpc,
|
||||||
maybe_perform_dhcp_discovery,
|
maybe_perform_dhcp_discovery,
|
||||||
networkd_load_leases,
|
networkd_load_leases,
|
||||||
@@ -334,6 +335,43 @@ class TestDHCPParseStaticRoutes(CiTestCase):
|
)
|
||||||
|
@@ -388,11 +390,13 @@ class TestDHCPDiscoveryClean(CiTestCase):
|
||||||
|
self.logs.getvalue(),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
+ @mock.patch("cloudinit.temp_utils.get_tmp_ancestor", return_value="/tmp")
|
||||||
|
@mock.patch("cloudinit.net.dhcp.find_fallback_nic", return_value="eth9")
|
||||||
|
@mock.patch("cloudinit.net.dhcp.os.remove")
|
||||||
|
@mock.patch("cloudinit.net.dhcp.subp.subp")
|
||||||
|
@mock.patch("cloudinit.net.dhcp.subp.which")
|
||||||
|
- def test_dhcp_client_failover(self, m_which, m_subp, m_remove, m_fallback):
|
||||||
|
+ def test_dhcp_client_failover(self, m_which, m_subp, m_remove, m_fallback,
|
||||||
|
+ m_get_tmp_ancestor):
|
||||||
|
"""Log and do nothing when nic is absent and no fallback is found."""
|
||||||
|
m_subp.side_effect = [
|
||||||
|
("", ""),
|
||||||
|
@@ -928,3 +932,172 @@ class TestEphemeralDhcpLeaseErrors:
|
||||||
|
pass
|
||||||
|
|
||||||
|
assert len(m_dhcp.mock_calls) == 1
|
||||||
|
+
|
||||||
|
+
|
||||||
+class TestUDHCPCDiscoveryClean(CiTestCase):
|
+class TestUDHCPCDiscoveryClean(CiTestCase):
|
||||||
|
+ with_logs = True
|
||||||
+ maxDiff = None
|
+ maxDiff = None
|
||||||
+
|
+
|
||||||
|
+ @mock.patch("cloudinit.temp_utils.get_tmp_ancestor", return_value="/tmp")
|
||||||
|
+ @mock.patch("cloudinit.net.dhcp.subp.which")
|
||||||
|
+ @mock.patch("cloudinit.net.dhcp.find_fallback_nic")
|
||||||
|
+ def test_absent_udhcpc_command(self, m_fallback, m_which,
|
||||||
|
+ m_get_tmp_ancestor):
|
||||||
|
+ """When dhclient doesn't exist in the OS, log the issue and no-op."""
|
||||||
|
+ m_fallback.return_value = "eth9"
|
||||||
|
+ m_which.return_value = None # udhcpc isn't found
|
||||||
|
+
|
||||||
|
+ distro = MockDistro()
|
||||||
|
+ distro.dhcp_client_priority = [Udhcpc]
|
||||||
|
+
|
||||||
|
+ with pytest.raises(NoDHCPLeaseMissingDhclientError):
|
||||||
|
+ maybe_perform_dhcp_discovery(distro)
|
||||||
|
+
|
||||||
|
+ self.assertIn(
|
||||||
|
+ "Skip udhcpc configuration: No udhcpc command found.",
|
||||||
|
+ self.logs.getvalue(),
|
||||||
|
+ )
|
||||||
|
+
|
||||||
|
+ @mock.patch("cloudinit.temp_utils.get_tmp_ancestor", return_value="/tmp")
|
||||||
|
+ @mock.patch("cloudinit.net.dhcp.is_ib_interface", return_value=False)
|
||||||
|
+ @mock.patch("cloudinit.net.dhcp.subp.which", return_value="/sbin/udhcpc")
|
||||||
+ @mock.patch("cloudinit.net.dhcp.os.remove")
|
+ @mock.patch("cloudinit.net.dhcp.os.remove")
|
||||||
+ @mock.patch("cloudinit.net.dhcp.subp.subp")
|
+ @mock.patch("cloudinit.net.dhcp.subp.subp")
|
||||||
+ @mock.patch("cloudinit.util.load_json")
|
+ @mock.patch("cloudinit.util.load_json")
|
||||||
+ @mock.patch("cloudinit.util.load_file")
|
+ @mock.patch("cloudinit.util.load_file")
|
||||||
+ @mock.patch("cloudinit.util.write_file")
|
+ @mock.patch("cloudinit.util.write_file")
|
||||||
+ def test_udhcpc_discovery(
|
+ def test_udhcpc_discovery(
|
||||||
+ self, m_write_file, m_load_file, m_loadjson, m_subp, m_remove
|
+ self,
|
||||||
|
+ m_write_file,
|
||||||
|
+ m_load_file,
|
||||||
|
+ m_loadjson,
|
||||||
|
+ m_subp,
|
||||||
|
+ m_remove,
|
||||||
|
+ m_which,
|
||||||
|
+ mocked_is_ib_interface,
|
||||||
|
+ m_get_tmp_ancestor,
|
||||||
+ ):
|
+ ):
|
||||||
+ """dhcp_discovery waits for the presence of pidfile and dhcp.leases."""
|
+ """dhcp_discovery runs udcpc and parse the dhcp leases."""
|
||||||
+ m_subp.return_value = ("", "")
|
+ m_subp.return_value = ("", "")
|
||||||
+ m_loadjson.return_value = {
|
+ m_loadjson.return_value = {
|
||||||
+ "interface": "eth9",
|
+ "interface": "eth9",
|
||||||
|
@ -201,22 +302,120 @@ index 40340553..8913cf65 100644
|
||||||
+ "subnet-mask": "255.255.255.0",
|
+ "subnet-mask": "255.255.255.0",
|
||||||
+ }
|
+ }
|
||||||
+ ],
|
+ ],
|
||||||
+ dhcp_udhcpc_discovery("/sbin/udhcpc", "eth9"),
|
+ Udhcpc().dhcp_discovery("eth9", distro=MockDistro()),
|
||||||
|
+ )
|
||||||
|
+ # Interface was brought up before dhclient called
|
||||||
|
+ m_subp.assert_has_calls(
|
||||||
|
+ [
|
||||||
|
+ mock.call(
|
||||||
|
+ ["ip", "link", "set", "dev", "eth9", "up"],
|
||||||
|
+ capture=True,
|
||||||
|
+ ),
|
||||||
|
+ mock.call(
|
||||||
|
+ [
|
||||||
|
+ "/sbin/udhcpc",
|
||||||
|
+ "-O",
|
||||||
|
+ "staticroutes",
|
||||||
|
+ "-i",
|
||||||
|
+ "eth9",
|
||||||
|
+ "-s",
|
||||||
|
+ "/tmp/udhcpc_script",
|
||||||
|
+ "-n",
|
||||||
|
+ "-q",
|
||||||
|
+ "-f",
|
||||||
|
+ "-v",
|
||||||
|
+ ],
|
||||||
|
+ update_env={"LEASE_FILE": "/tmp/eth9.lease.json"},
|
||||||
|
+ capture=True,
|
||||||
|
+ ),
|
||||||
|
+ ]
|
||||||
+ )
|
+ )
|
||||||
+
|
+
|
||||||
+
|
+ @mock.patch("cloudinit.temp_utils.get_tmp_ancestor", return_value="/tmp")
|
||||||
class TestDHCPDiscoveryClean(CiTestCase):
|
+ @mock.patch("cloudinit.net.dhcp.is_ib_interface", return_value=True)
|
||||||
with_logs = True
|
+ @mock.patch("cloudinit.net.dhcp.get_ib_interface_hwaddr")
|
||||||
|
+ @mock.patch("cloudinit.net.dhcp.subp.which", return_value="/sbin/udhcpc")
|
||||||
@@ -372,7 +410,7 @@ class TestDHCPDiscoveryClean(CiTestCase):
|
+ @mock.patch("cloudinit.net.dhcp.os.remove")
|
||||||
maybe_perform_dhcp_discovery()
|
+ @mock.patch("cloudinit.net.dhcp.subp.subp")
|
||||||
|
+ @mock.patch("cloudinit.util.load_json")
|
||||||
self.assertIn(
|
+ @mock.patch("cloudinit.util.load_file")
|
||||||
- "Skip dhclient configuration: No dhclient command found.",
|
+ @mock.patch("cloudinit.util.write_file")
|
||||||
+ "Skip dhclient configuration: No dhclient or udhcpc command found.",
|
+ def test_udhcpc_discovery_ib(
|
||||||
self.logs.getvalue(),
|
+ self,
|
||||||
)
|
+ m_write_file,
|
||||||
|
+ m_load_file,
|
||||||
|
+ m_loadjson,
|
||||||
|
+ m_subp,
|
||||||
|
+ m_remove,
|
||||||
|
+ m_which,
|
||||||
|
+ m_get_ib_interface_hwaddr,
|
||||||
|
+ m_is_ib_interface,
|
||||||
|
+ m_get_tmp_ancestor,
|
||||||
|
+ ):
|
||||||
|
+ """dhcp_discovery runs udcpc and parse the dhcp leases."""
|
||||||
|
+ m_subp.return_value = ("", "")
|
||||||
|
+ m_loadjson.return_value = {
|
||||||
|
+ "interface": "ib0",
|
||||||
|
+ "fixed-address": "192.168.2.74",
|
||||||
|
+ "subnet-mask": "255.255.255.0",
|
||||||
|
+ "routers": "192.168.2.1",
|
||||||
|
+ "static_routes": "10.240.0.1/32 0.0.0.0 0.0.0.0/0 10.240.0.1",
|
||||||
|
+ }
|
||||||
|
+ m_get_ib_interface_hwaddr.return_value = "00:21:28:00:01:cf:4b:01"
|
||||||
|
+ self.assertEqual(
|
||||||
|
+ [
|
||||||
|
+ {
|
||||||
|
+ "fixed-address": "192.168.2.74",
|
||||||
|
+ "interface": "ib0",
|
||||||
|
+ "routers": "192.168.2.1",
|
||||||
|
+ "static_routes": [
|
||||||
|
+ ("10.240.0.1/32", "0.0.0.0"),
|
||||||
|
+ ("0.0.0.0/0", "10.240.0.1"),
|
||||||
|
+ ],
|
||||||
|
+ "subnet-mask": "255.255.255.0",
|
||||||
|
+ }
|
||||||
|
+ ],
|
||||||
|
+ Udhcpc().dhcp_discovery("ib0", distro=MockDistro()),
|
||||||
|
+ )
|
||||||
|
+ # Interface was brought up before dhclient called
|
||||||
|
+ m_subp.assert_has_calls(
|
||||||
|
+ [
|
||||||
|
+ mock.call(
|
||||||
|
+ ["ip", "link", "set", "dev", "ib0", "up"], capture=True
|
||||||
|
+ ),
|
||||||
|
+ mock.call(
|
||||||
|
+ [
|
||||||
|
+ "/sbin/udhcpc",
|
||||||
|
+ "-O",
|
||||||
|
+ "staticroutes",
|
||||||
|
+ "-i",
|
||||||
|
+ "ib0",
|
||||||
|
+ "-s",
|
||||||
|
+ "/tmp/udhcpc_script",
|
||||||
|
+ "-n",
|
||||||
|
+ "-q",
|
||||||
|
+ "-f",
|
||||||
|
+ "-v",
|
||||||
|
+ "-x",
|
||||||
|
+ "0x3d:0021280001cf4b01",
|
||||||
|
+ ],
|
||||||
|
+ update_env={"LEASE_FILE": "/tmp/ib0.lease.json"},
|
||||||
|
+ capture=True,
|
||||||
|
+ ),
|
||||||
|
+ ]
|
||||||
|
+ )
|
||||||
|
diff --git a/tools/.github-cla-signers b/tools/.github-cla-signers
|
||||||
|
index b4a9326e..4d82a055 100644
|
||||||
|
--- a/tools/.github-cla-signers
|
||||||
|
+++ b/tools/.github-cla-signers
|
||||||
|
@@ -65,6 +65,7 @@ jacobsalmela
|
||||||
|
jamesottinger
|
||||||
|
Jehops
|
||||||
|
jf
|
||||||
|
+jfroche
|
||||||
|
Jille
|
||||||
|
JohnKepplers
|
||||||
|
johnsonshi
|
||||||
--
|
--
|
||||||
2.38.4
|
2.40.1
|
||||||
|
|
||||||
|
|
|
@ -1,110 +0,0 @@
|
||||||
From 6df2a198013ebed9aeff119ee0d15cb2d616474c Mon Sep 17 00:00:00 2001
|
|
||||||
From: zimbatm <zimbatm@zimbatm.com>
|
|
||||||
Date: Sun, 30 Apr 2023 12:13:54 +0200
|
|
||||||
Subject: [PATCH] vultr: remove check_route check
|
|
||||||
|
|
||||||
The heuristic is assuming that the URL will contain an IP, and that the
|
|
||||||
route explicitly lists that IP (eg: 0.0.0.0/0 should match but doesn't).
|
|
||||||
In order for the heuristic to be 100% reliable, it would have to
|
|
||||||
replicate exactly what the system is doing both in terms of DNS and
|
|
||||||
route resolution.
|
|
||||||
|
|
||||||
Because the HTTP request below is already exercising the python nd
|
|
||||||
system resolution, it is simpler to just remove this check and lean on
|
|
||||||
the HTTP request to provide the answer if the network is up or not.
|
|
||||||
---
|
|
||||||
cloudinit/sources/helpers/vultr.py | 22 ----------------------
|
|
||||||
tests/unittests/sources/test_vultr.py | 12 ------------
|
|
||||||
2 files changed, 34 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/cloudinit/sources/helpers/vultr.py b/cloudinit/sources/helpers/vultr.py
|
|
||||||
index 71676bb1..aac2a610 100644
|
|
||||||
--- a/cloudinit/sources/helpers/vultr.py
|
|
||||||
+++ b/cloudinit/sources/helpers/vultr.py
|
|
||||||
@@ -32,10 +32,6 @@ def get_metadata(
|
|
||||||
iface=iface,
|
|
||||||
connectivity_url_data={"url": url},
|
|
||||||
):
|
|
||||||
- # Check for the metadata route, skip if not there
|
|
||||||
- if not check_route(url):
|
|
||||||
- continue
|
|
||||||
-
|
|
||||||
# Fetch the metadata
|
|
||||||
v1 = read_metadata(url, timeout, retries, sec_between, agent)
|
|
||||||
|
|
||||||
@@ -75,24 +71,6 @@ def get_interface_list():
|
|
||||||
return ifaces
|
|
||||||
|
|
||||||
|
|
||||||
-# Check for /32 route that our dhcp servers inject
|
|
||||||
-# in order to determine if this a customer-run dhcp server
|
|
||||||
-def check_route(url):
|
|
||||||
- # Get routes, confirm entry exists
|
|
||||||
- routes = netinfo.route_info()
|
|
||||||
-
|
|
||||||
- # If no tools exist and empty dict is returned
|
|
||||||
- if "ipv4" not in routes:
|
|
||||||
- return False
|
|
||||||
-
|
|
||||||
- # Parse each route into a more searchable format
|
|
||||||
- for route in routes["ipv4"]:
|
|
||||||
- if route.get("destination", None) in url:
|
|
||||||
- return True
|
|
||||||
-
|
|
||||||
- return False
|
|
||||||
-
|
|
||||||
-
|
|
||||||
# Read the system information from SMBIOS
|
|
||||||
def get_sysinfo():
|
|
||||||
return {
|
|
||||||
diff --git a/tests/unittests/sources/test_vultr.py b/tests/unittests/sources/test_vultr.py
|
|
||||||
index ba21ae24..7fa02b1c 100644
|
|
||||||
--- a/tests/unittests/sources/test_vultr.py
|
|
||||||
+++ b/tests/unittests/sources/test_vultr.py
|
|
||||||
@@ -274,14 +274,6 @@ INTERFACE_MAP = {
|
|
||||||
FINAL_INTERFACE_USED = ""
|
|
||||||
|
|
||||||
|
|
||||||
-# Static override, pylint doesnt like this in
|
|
||||||
-# classes without self
|
|
||||||
-def check_route(url):
|
|
||||||
- if FINAL_INTERFACE_USED == "eth0":
|
|
||||||
- return True
|
|
||||||
- return False
|
|
||||||
-
|
|
||||||
-
|
|
||||||
class TestDataSourceVultr(CiTestCase):
|
|
||||||
def setUp(self):
|
|
||||||
global VULTR_V1_3
|
|
||||||
@@ -431,7 +423,6 @@ class TestDataSourceVultr(CiTestCase):
|
|
||||||
@mock.patch(
|
|
||||||
"cloudinit.net.ephemeral.EphemeralDHCPv4.__exit__", override_exit
|
|
||||||
)
|
|
||||||
- @mock.patch("cloudinit.sources.helpers.vultr.check_route")
|
|
||||||
@mock.patch("cloudinit.sources.helpers.vultr.is_vultr")
|
|
||||||
@mock.patch("cloudinit.sources.helpers.vultr.read_metadata")
|
|
||||||
@mock.patch("cloudinit.sources.helpers.vultr.get_interface_list")
|
|
||||||
@@ -440,12 +431,10 @@ class TestDataSourceVultr(CiTestCase):
|
|
||||||
mock_interface_list,
|
|
||||||
mock_read_metadata,
|
|
||||||
mock_isvultr,
|
|
||||||
- mock_check_route,
|
|
||||||
):
|
|
||||||
mock_read_metadata.return_value = {}
|
|
||||||
mock_isvultr.return_value = True
|
|
||||||
mock_interface_list.return_value = FILTERED_INTERFACES
|
|
||||||
- mock_check_route.return_value = True
|
|
||||||
|
|
||||||
distro = mock.MagicMock()
|
|
||||||
distro.get_tmp_exec_path = self.tmp_dir
|
|
||||||
@@ -461,7 +450,6 @@ class TestDataSourceVultr(CiTestCase):
|
|
||||||
self.assertEqual(FINAL_INTERFACE_USED, INTERFACES[3])
|
|
||||||
|
|
||||||
# Test route checking sucessful DHCPs
|
|
||||||
- @mock.patch("cloudinit.sources.helpers.vultr.check_route", check_route)
|
|
||||||
@mock.patch(
|
|
||||||
"cloudinit.net.ephemeral.EphemeralDHCPv4.__init__",
|
|
||||||
ephemeral_init_always,
|
|
||||||
--
|
|
||||||
2.40.0
|
|
||||||
|
|
|
@ -16,22 +16,20 @@
|
||||||
|
|
||||||
python3.pkgs.buildPythonApplication rec {
|
python3.pkgs.buildPythonApplication rec {
|
||||||
pname = "cloud-init";
|
pname = "cloud-init";
|
||||||
version = "23.1.2";
|
version = "23.2";
|
||||||
namePrefix = "";
|
namePrefix = "";
|
||||||
|
|
||||||
src = fetchFromGitHub {
|
src = fetchFromGitHub {
|
||||||
owner = "canonical";
|
owner = "canonical";
|
||||||
repo = "cloud-init";
|
repo = "cloud-init";
|
||||||
rev = "refs/tags/${version}";
|
rev = "refs/tags/${version}";
|
||||||
hash = "sha256-tn4flcrf04hVWhqkmK4qDenXcnV93pP+C+8J63b6FXQ=";
|
hash = "sha256-/bhezXS5GunlgID7e/QaTC4UsQ2hDvS5HnG8Wphk64k=";
|
||||||
};
|
};
|
||||||
|
|
||||||
patches = [
|
patches = [
|
||||||
./0001-add-nixos-support.patch
|
./0001-add-nixos-support.patch
|
||||||
# upstream: https://github.com/canonical/cloud-init/pull/2125
|
# upstream: https://github.com/canonical/cloud-init/pull/4190
|
||||||
./0002-Add-Udhcpc-support.patch
|
./0002-Add-Udhcpc-support.patch
|
||||||
# upstream: https://github.com/canonical/cloud-init/pull/2151
|
|
||||||
./0003-vultr-remove-check_route-check.patch
|
|
||||||
];
|
];
|
||||||
|
|
||||||
prePatch = ''
|
prePatch = ''
|
||||||
|
|
Loading…
Reference in a new issue