Skip to content

Commit d2bf883

Browse files
authored
fix(cloudstack): Improve domain-name DHCP lease lookup (Cloudstack) (#6554)
* 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.
1 parent f065903 commit d2bf883

File tree

2 files changed

+52
-24
lines changed

2 files changed

+52
-24
lines changed

cloudinit/sources/DataSourceCloudStack.py

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -87,46 +87,47 @@ def __init__(self, sys_cfg, distro, paths):
8787
self.vr_addr = None
8888

8989
def _get_domainname(self):
90+
"""Try obtaining a "domain-name" DHCP lease parameter:
91+
- From systemd-networkd lease (case-insensitive)
92+
- From ISC dhclient
93+
- From dhcpcd (ephemeral)
94+
- Return empty string if not found (non-fatal)
9095
"""
91-
Try obtaining a "domain-name" DHCP lease parameter:
92-
- From systemd-networkd lease
93-
- From dhclient lease
94-
"""
96+
9597
LOG.debug("Try obtaining domain name from networkd leases")
96-
domainname = dhcp.networkd_get_option_from_leases("DOMAINNAME")
97-
if domainname:
98-
return domainname
98+
for key in ["DOMAINNAME", "Domain", "domain-name"]:
99+
domainname = dhcp.networkd_get_option_from_leases(key)
100+
if domainname:
101+
return domainname.strip()
102+
99103
LOG.debug(
100-
"Could not obtain FQDN from networkd leases. "
101-
"Falling back to ISC dhclient"
104+
"Could not obtain FQDN from networkd leases. Falling back to "
105+
"ISC dhclient"
102106
)
103-
104-
# some distros might use isc-dhclient for network setup via their
105-
# network manager. If this happens, the lease is more recent than the
106-
# ephemeral lease, so use it first.
107107
with suppress(dhcp.NoDHCPLeaseMissingDhclientError):
108108
domain_name = dhcp.IscDhclient().get_key_from_latest_lease(
109109
self.distro, "domain-name"
110110
)
111111
if domain_name:
112-
return domain_name
112+
return domain_name.strip()
113113

114114
LOG.debug(
115-
"Could not obtain FQDN from ISC dhclient leases. "
116-
"Falling back to %s",
115+
"Could not obtain FQDN from ISC dhclient leases. Falling back to "
116+
"%s",
117117
self.distro.dhcp_client.client_name,
118118
)
119-
120-
# If no distro leases were found, check the ephemeral lease that
121-
# cloud-init set up.
122-
with suppress(FileNotFoundError):
119+
try:
123120
latest_lease = self.distro.dhcp_client.get_newest_lease(
124121
self.distro.fallback_interface
125122
)
126-
domain_name = latest_lease.get("domain-name") or None
127-
return domain_name
128-
LOG.debug("No dhcp leases found")
129-
return None
123+
domain_name = latest_lease.get("domain-name")
124+
if domain_name:
125+
return domain_name.strip()
126+
except (NoDHCPLeaseError, FileNotFoundError, AttributeError):
127+
pass
128+
129+
LOG.debug("No domain name found in any DHCP lease; returning empty")
130+
return ""
130131

131132
def get_hostname(
132133
self,

tests/unittests/sources/test_cloudstack.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# pylint: disable=attribute-defined-outside-init
33
from socket import gaierror
44
from textwrap import dedent
5+
from unittest.mock import patch
56

67
import pytest
78

@@ -220,6 +221,32 @@ def test_get_hostname_fqdn_fallback(self, cloudstack_ds, mocker):
220221
result = cloudstack_ds.get_hostname(fqdn=True)
221222
assert expected == result
222223

224+
@pytest.mark.parametrize(
225+
"lease_key,expected_domain",
226+
[
227+
("DOMAINNAME", "example.com"),
228+
("Domain", "example.com"),
229+
("domain-name", "example.com"),
230+
],
231+
)
232+
def test__get_domainname_supports_all_casing_variants(
233+
self, lease_key, expected_domain
234+
):
235+
"""Ensure _get_domainname works with DOMAINNAME, Domain and
236+
domain-name."""
237+
# Mock the helper to return the domain only when the exact key is asked
238+
with patch(
239+
"cloudinit.net.dhcp.networkd_get_option_from_leases"
240+
) as m_get:
241+
m_get.side_effect = lambda key, extra_keys=None: (
242+
"example.com " if key == lease_key else None
243+
)
244+
245+
ds = DataSourceCloudStack(
246+
{}, distro=MockDistro(), paths=helpers.Paths({})
247+
)
248+
assert ds._get_domainname() == expected_domain
249+
223250

224251
class TestGetDataServer:
225252
@pytest.mark.parametrize(

0 commit comments

Comments
 (0)