cloud-init/backport-net-dhcp-catch-dhclient-failures-and-raise-NoDHCPLea.patch
2023-08-26 14:46:22 +08:00

119 lines
4.4 KiB
Diff

From c82ace920a743c6e6797536416018d9680b8fa7e Mon Sep 17 00:00:00 2001
From: Chris Patterson <cpatterson@microsoft.com>
Date: Wed, 29 Mar 2023 17:30:13 -0400
Subject: [PATCH] net/dhcp: catch dhclient failures and raise NoDHCPLeaseError
(#2083)
Some variants of dhclient will exit with non-zero codes on lease
failure. For example, on RHEL 8.7:
```
[cpatterson@test-rhel87 ~]$ sudo /usr/sbin/dhclient -1 -v -lf
/tmp/my.lease -pf /tmp/my.pid bridge2nowhere -sf /bin/
true
Internet Systems Consortium DHCP Client 4.3.6
Copyright 2004-2017 Internet Systems Consortium.
All rights reserved.
For info, please visit https://www.isc.org/software/dhcp/
Listening on LPF/bridge2nowhere/42:ef:d5:38:1d:19
Sending on LPF/bridge2nowhere/42:ef:d5:38:1d:19
Sending on Socket/fallback
Created duid "\000\004E<\225X\232\304J\337\243\026T\324\243O\270\177".
DHCPDISCOVER on bridge2nowhere to 255.255.255.255 port 67 interval 4
(xid=0x777bc142)
DHCPDISCOVER on bridge2nowhere to 255.255.255.255 port 67 interval 7
(xid=0x777bc142)
DHCPDISCOVER on bridge2nowhere to 255.255.255.255 port 67 interval 13
(xid=0x777bc142)
DHCPDISCOVER on bridge2nowhere to 255.255.255.255 port 67 interval 6
(xid=0x777bc142)
No DHCPOFFERS received.
Unable to obtain a lease on first try. Exiting.
[cpatterson@test-rhel87 ~]$ echo $?
2
```
This results in an unhandled subp.ProcessExecutionError exception.
Catch these failures and re-raise as NoDHCPLeaseError.
Signed-off-by: Chris Patterson <cpatterson@microsoft.com>
---
cloudinit/net/dhcp.py | 11 ++++++++++-
cloudinit/net/tests/test_dhcp.py | 23 +++++++++++++++++++++--
2 files changed, 31 insertions(+), 3 deletions(-)
diff --git a/cloudinit/net/dhcp.py b/cloudinit/net/dhcp.py
index 3f4b041..e5f36e1 100644
--- a/cloudinit/net/dhcp.py
+++ b/cloudinit/net/dhcp.py
@@ -239,7 +239,16 @@ def dhcp_discovery(dhclient_cmd_path, interface, cleandir, dhcp_log_func=None):
subp.subp(['ip', 'link', 'set', 'dev', interface, 'up'], capture=True)
cmd = [sandbox_dhclient_cmd, '-1', '-v', '-lf', lease_file,
'-pf', pid_file, interface, '-sf', '/bin/true']
- out, err = subp.subp(cmd, capture=True)
+ try:
+ out, err = subp.subp(cmd, capture=True)
+ except subp.ProcessExecutionError as error:
+ LOG.debug(
+ "dhclient exited with code: %s stderr: %r stdout: %r",
+ error.exit_code,
+ error.stderr,
+ error.stdout,
+ )
+ raise NoDHCPLeaseError from error
# Wait for pid file and lease file to appear, and for the process
# named by the pid file to daemonize (have pid 1 as its parent). If we
diff --git a/cloudinit/net/tests/test_dhcp.py b/cloudinit/net/tests/test_dhcp.py
index 28b4ecf..de4b461 100644
--- a/cloudinit/net/tests/test_dhcp.py
+++ b/cloudinit/net/tests/test_dhcp.py
@@ -3,14 +3,15 @@
import httpretty
import os
import signal
+import pytest
from textwrap import dedent
import cloudinit.net as net
from cloudinit.net.dhcp import (
- InvalidDHCPLeaseFileError, maybe_perform_dhcp_discovery,
+ InvalidDHCPLeaseFileError, NoDHCPLeaseError, maybe_perform_dhcp_discovery,
parse_dhcp_lease_file, dhcp_discovery, networkd_load_leases,
parse_static_routes)
-from cloudinit.util import ensure_file, write_file
+from cloudinit.util import ensure_file, subp, write_file
from cloudinit.tests.helpers import (
CiTestCase, HttprettyTestCase, mock, populate_dir, wrap_and_call)
@@ -268,6 +269,24 @@ class TestDHCPDiscoveryClean(CiTestCase):
'Skip dhcp_discovery: Unable to find fallback nic.',
self.logs.getvalue())
+ @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")
+ def test_dhclient_exits_with_error(self, m_subp, m_remove, m_fallback):
+ """Log and do nothing when nic is absent and no fallback is found."""
+ m_subp.side_effect = [
+ ("", ""),
+ subp.ProcessExecutionError(exit_code=-5),
+ ]
+
+ with pytest.raises(NoDHCPLeaseError):
+ maybe_perform_dhcp_discovery()
+
+ self.assertIn(
+ "dhclient exited with code: -5",
+ self.logs.getvalue(),
+ )
+
def test_provided_nic_does_not_exist(self):
"""When the provided nic doesn't exist, log a message and no-op."""
self.assertEqual([], maybe_perform_dhcp_discovery('idontexist'))
--
2.33.0