From c82ace920a743c6e6797536416018d9680b8fa7e Mon Sep 17 00:00:00 2001 From: Chris Patterson 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 --- 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