From afe5a67f3223d33bb3ecf0d05262eababc8877cf Mon Sep 17 00:00:00 2001 From: Chris Patterson Date: Tue, 2 Apr 2024 12:11:39 -0400 Subject: [PATCH] net/dhcp: raise InvalidDHCPLeaseFileError on error parsing dhcpcd lease (#5128) Seeing a fairly large number of lease parsing failures on Azure similar to: ``` Traceback (most recent call last): File "/usr/lib/python3/dist-packages/cloudinit/sources/DataSourceAzure.py", line 851, in _get_data crawled_data = util.log_time( ^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/cloudinit/util.py", line 2828, in log_time ret = func(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/cloudinit/sources/helpers/azure.py", line 45, in impl return func(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/cloudinit/sources/DataSourceAzure.py", line 660, in crawl_metadata self._wait_for_pps_savable_reuse() File "/usr/lib/python3/dist-packages/cloudinit/sources/helpers/azure.py", line 45, in impl return func(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/cloudinit/sources/DataSourceAzure.py", line 1236, in _wait_for_pps_savable_reuse self._wait_for_hot_attached_primary_nic(nl_sock) File "/usr/lib/python3/dist-packages/cloudinit/sources/helpers/azure.py", line 45, in impl return func(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/cloudinit/sources/DataSourceAzure.py", line 1142, in _wait_for_hot_attached_primary_nic primary_nic_found = self._setup_ephemeral_networking( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/cloudinit/sources/helpers/azure.py", line 45, in impl return func(*args, **kwargs) ^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/cloudinit/sources/DataSourceAzure.py", line 440, in _setup_ephemeral_networking lease = self._ephemeral_dhcp_ctx.obtain_lease() ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/cloudinit/net/ephemeral.py", line 293, in obtain_lease self.lease = maybe_perform_dhcp_discovery( ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/cloudinit/net/dhcp.py", line 103, in maybe_perform_dhcp_discovery return distro.dhcp_client.dhcp_discovery(interface, dhcp_log_func, distro) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/cloudinit/net/dhcp.py", line 656, in dhcp_discovery lease = self.get_newest_lease(interface) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/cloudinit/net/dhcp.py", line 829, in get_newest_lease return self.parse_dhcpcd_lease( ^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3/dist-packages/cloudinit/net/dhcp.py", line 787, in parse_dhcpcd_lease lease = dict( ^^^^^ ValueError: dictionary update sequence element #0 has length 1; 2 is required ``` Catch this error in parse_dhcpcd_lease() and raise InvalidDHCPLeaseFileError after logging an error. Signed-off-by: Chris Patterson --- cloudinit/net/dhcp.py | 19 +++++++++++++------ tests/unittests/net/test_dhcp.py | 11 +++++++++++ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/cloudinit/net/dhcp.py b/cloudinit/net/dhcp.py index 68a3834a2bd..23745cb4ef0 100644 --- a/cloudinit/net/dhcp.py +++ b/cloudinit/net/dhcp.py @@ -783,14 +783,21 @@ def parse_dhcpcd_lease(lease_dump: str, interface: str) -> Dict: subnet_cidr='20' subnet_mask='255.255.240.0' """ + LOG.debug( + "Parsing dhcpcd lease for interface %s: %r", interface, lease_dump + ) # create a dict from dhcpcd dump output - remove single quotes - lease = dict( - [ - a.split("=") - for a in lease_dump.strip().replace("'", "").split("\n") - ] - ) + try: + lease = dict( + [ + a.split("=") + for a in lease_dump.strip().replace("'", "").split("\n") + ] + ) + except ValueError as error: + LOG.error("Error parsing dhcpcd lease: %r", error) + raise InvalidDHCPLeaseFileError from error # this is expected by cloud-init's code lease["interface"] = interface diff --git a/tests/unittests/net/test_dhcp.py b/tests/unittests/net/test_dhcp.py index c38ee676d97..ac1754a5ce2 100644 --- a/tests/unittests/net/test_dhcp.py +++ b/tests/unittests/net/test_dhcp.py @@ -1199,6 +1199,17 @@ def test_parse_lease_dump(self): assert "255.255.240.0" == parsed_lease["subnet-mask"] assert "192.168.0.1" == parsed_lease["routers"] + def test_parse_lease_dump_fails(self): + lease = dedent( + """ + fail + """ + ) + + with pytest.raises(InvalidDHCPLeaseFileError): + with mock.patch("cloudinit.net.dhcp.util.load_binary_file"): + Dhcpcd.parse_dhcpcd_lease(lease, "eth0") + @pytest.mark.parametrize( "lease_file, option_245", (