cloud-init/ci-fix-cloudstack-Improve-domain-name-DHCP-lease-lookup.patch
2026-04-13 03:12:54 -04:00

157 lines
6.0 KiB
Diff

From d45780e65158d7f9b0a00933f8998934f5ceb4cf Mon Sep 17 00:00:00 2001
From: CodeBleu <400979+CodeBleu@users.noreply.github.com>
Date: Tue, 20 Jan 2026 13:52:54 -0500
Subject: [PATCH 2/3] fix(cloudstack): Improve domain-name DHCP lease lookup
(Cloudstack) (#6554)
RH-Author: Ani Sinha <anisinha@redhat.com>
RH-MergeRequest: 178: fix(cloudstack): Improve domain-name DHCP lease lookup
RH-Jira: RHEL-159032
RH-Acked-by: Emanuele Giuseppe Esposito <eesposit@redhat.com>
RH-Acked-by: xiachen <xiachen@redhat.com>
RH-Commit: [2/3] 5f3c7dac734ddcc068f086866963d05c42e0c24f
* Adding case-insensitive options for systemd-networkd leases ("DOMAINNAME", "Domain", "domain-name").
* Falling back gracefully from systemd leases to ISC dhclient leases.
* Including dhcpcd ephemeral leases as an additional fallback.
* Returning an empty string when no domain name found instead of None for non-fatal missing cases.
(cherry picked from commit d2bf883931723cfdc9f529eba5395d83059d09c6)
Signed-off-by: Ani Sinha <anisinha@redhat.com>
---
cloudinit/sources/DataSourceCloudStack.py | 50 +++++++++++-----------
tests/unittests/sources/test_cloudstack.py | 27 ++++++++++++
2 files changed, 53 insertions(+), 24 deletions(-)
diff --git a/cloudinit/sources/DataSourceCloudStack.py b/cloudinit/sources/DataSourceCloudStack.py
index 6d19077c..98189c23 100644
--- a/cloudinit/sources/DataSourceCloudStack.py
+++ b/cloudinit/sources/DataSourceCloudStack.py
@@ -23,6 +23,7 @@ from cloudinit import sources, subp
from cloudinit import url_helper as uhelp
from cloudinit import util
from cloudinit.net import dhcp
+from cloudinit.net.dhcp import NoDHCPLeaseError
from cloudinit.sources.helpers import ec2
LOG = logging.getLogger(__name__)
@@ -97,46 +98,47 @@ class DataSourceCloudStack(sources.DataSource):
self.cfg = {}
def _get_domainname(self):
+ """Try obtaining a "domain-name" DHCP lease parameter:
+ - From systemd-networkd lease (case-insensitive)
+ - From ISC dhclient
+ - From dhcpcd (ephemeral)
+ - Return empty string if not found (non-fatal)
"""
- Try obtaining a "domain-name" DHCP lease parameter:
- - From systemd-networkd lease
- - From dhclient lease
- """
+
LOG.debug("Try obtaining domain name from networkd leases")
- domainname = dhcp.networkd_get_option_from_leases("DOMAINNAME")
- if domainname:
- return domainname
+ for key in ["DOMAINNAME", "Domain", "domain-name"]:
+ domainname = dhcp.networkd_get_option_from_leases(key)
+ if domainname:
+ return domainname.strip()
+
LOG.debug(
- "Could not obtain FQDN from networkd leases. "
- "Falling back to ISC dhclient"
+ "Could not obtain FQDN from networkd leases. Falling back to "
+ "ISC dhclient"
)
-
- # some distros might use isc-dhclient for network setup via their
- # network manager. If this happens, the lease is more recent than the
- # ephemeral lease, so use it first.
with suppress(dhcp.NoDHCPLeaseMissingDhclientError):
domain_name = dhcp.IscDhclient().get_key_from_latest_lease(
self.distro, "domain-name"
)
if domain_name:
- return domain_name
+ return domain_name.strip()
LOG.debug(
- "Could not obtain FQDN from ISC dhclient leases. "
- "Falling back to %s",
+ "Could not obtain FQDN from ISC dhclient leases. Falling back to "
+ "%s",
self.distro.dhcp_client.client_name,
)
-
- # If no distro leases were found, check the ephemeral lease that
- # cloud-init set up.
- with suppress(FileNotFoundError):
+ try:
latest_lease = self.distro.dhcp_client.get_newest_lease(
self.distro.fallback_interface
)
- domain_name = latest_lease.get("domain-name") or None
- return domain_name
- LOG.debug("No dhcp leases found")
- return None
+ domain_name = latest_lease.get("domain-name")
+ if domain_name:
+ return domain_name.strip()
+ except (NoDHCPLeaseError, FileNotFoundError, AttributeError):
+ pass
+
+ LOG.debug("No domain name found in any DHCP lease; returning empty")
+ return ""
def get_hostname(
self,
diff --git a/tests/unittests/sources/test_cloudstack.py b/tests/unittests/sources/test_cloudstack.py
index a64d80f3..0f987066 100644
--- a/tests/unittests/sources/test_cloudstack.py
+++ b/tests/unittests/sources/test_cloudstack.py
@@ -1,5 +1,6 @@
# This file is part of cloud-init. See LICENSE file for license information.
from textwrap import dedent
+from unittest.mock import patch
import pytest
@@ -327,6 +328,32 @@ class TestCloudStackHostname(CiTestCase):
result = ds.get_hostname(fqdn=True)
self.assertTupleEqual(expected, result)
+ @pytest.mark.parametrize(
+ "lease_key,expected_domain",
+ [
+ ("DOMAINNAME", "example.com"),
+ ("Domain", "example.com"),
+ ("domain-name", "example.com"),
+ ],
+ )
+ def test__get_domainname_supports_all_casing_variants(
+ self, lease_key, expected_domain
+ ):
+ """Ensure _get_domainname works with DOMAINNAME, Domain and
+ domain-name."""
+ # Mock the helper to return the domain only when the exact key is asked
+ with patch(
+ "cloudinit.net.dhcp.networkd_get_option_from_leases"
+ ) as m_get:
+ m_get.side_effect = lambda key, extra_keys=None: (
+ "example.com " if key == lease_key else None
+ )
+
+ ds = DataSourceCloudStack(
+ {}, distro=MockDistro(), paths=helpers.Paths({})
+ )
+ assert ds._get_domainname() == expected_domain
+
@pytest.mark.usefixtures("dhclient_exists")
class TestCloudStackPasswordFetching(CiTestCase):
--
2.47.3