Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions cloudinit/net/dhcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,24 @@
NETWORKD_LEASES_DIR = "/run/systemd/netif/leases"


class InvalidDHCPLeaseFileError(Exception):
class NoDHCPLeaseError(Exception):
"""Raised when unable to get a DHCP lease."""


class InvalidDHCPLeaseFileError(NoDHCPLeaseError):
"""Raised when parsing an empty or invalid dhcp.leases file.

Current uses are DataSourceAzure and DataSourceEc2 during ephemeral
boot to scrape metadata.
"""


class NoDHCPLeaseError(Exception):
"""Raised when unable to get a DHCP lease."""
class NoDHCPLeaseInterfaceError(NoDHCPLeaseError):
"""Raised when unable to find a viable interface for DHCP."""


class NoDHCPLeaseMissingDhclientError(NoDHCPLeaseError):
"""Raised when unable to find dhclient."""


class EphemeralDHCPv4(object):
Expand Down Expand Up @@ -89,12 +97,7 @@ def obtain_lease(self):
"""
if self.lease:
return self.lease
try:
leases = maybe_perform_dhcp_discovery(
self.iface, self.dhcp_log_func
)
except InvalidDHCPLeaseFileError as e:
raise NoDHCPLeaseError() from e
leases = maybe_perform_dhcp_discovery(self.iface, self.dhcp_log_func)
if not leases:
raise NoDHCPLeaseError()
self.lease = leases[-1]
Expand Down Expand Up @@ -165,16 +168,16 @@ def maybe_perform_dhcp_discovery(nic=None, dhcp_log_func=None):
nic = find_fallback_nic()
if nic is None:
LOG.debug("Skip dhcp_discovery: Unable to find fallback nic.")
return []
raise NoDHCPLeaseInterfaceError()
elif nic not in get_devicelist():
LOG.debug(
"Skip dhcp_discovery: nic %s not found in get_devicelist.", nic
)
return []
raise NoDHCPLeaseInterfaceError()
dhclient_path = subp.which("dhclient")
if not dhclient_path:
LOG.debug("Skip dhclient configuration: No dhclient command found.")
return []
raise NoDHCPLeaseMissingDhclientError()
with temp_utils.tempdir(
rmtree_ignore_errors=True, prefix="cloud-init-dhcp-", needs_exe=True
) as tdir:
Expand Down
67 changes: 63 additions & 4 deletions tests/unittests/net/test_dhcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,14 @@
from textwrap import dedent

import httpretty
import pytest

import cloudinit.net as net
from cloudinit.net.dhcp import (
InvalidDHCPLeaseFileError,
NoDHCPLeaseError,
NoDHCPLeaseInterfaceError,
NoDHCPLeaseMissingDhclientError,
dhcp_discovery,
maybe_perform_dhcp_discovery,
networkd_load_leases,
Expand Down Expand Up @@ -334,15 +338,21 @@ class TestDHCPDiscoveryClean(CiTestCase):
def test_no_fallback_nic_found(self, m_fallback_nic):
"""Log and do nothing when nic is absent and no fallback is found."""
m_fallback_nic.return_value = None # No fallback nic found
self.assertEqual([], maybe_perform_dhcp_discovery())

with pytest.raises(NoDHCPLeaseInterfaceError):
maybe_perform_dhcp_discovery()

self.assertIn(
"Skip dhcp_discovery: Unable to find fallback nic.",
self.logs.getvalue(),
)

def test_provided_nic_does_not_exist(self):
@mock.patch("cloudinit.net.dhcp.find_fallback_nic", return_value=None)
def test_provided_nic_does_not_exist(self, m_fallback_nic):
"""When the provided nic doesn't exist, log a message and no-op."""
self.assertEqual([], maybe_perform_dhcp_discovery("idontexist"))
with pytest.raises(NoDHCPLeaseInterfaceError):
maybe_perform_dhcp_discovery("idontexist")

self.assertIn(
"Skip dhcp_discovery: nic idontexist not found in get_devicelist.",
self.logs.getvalue(),
Expand All @@ -354,7 +364,10 @@ def test_absent_dhclient_command(self, m_fallback, m_which):
"""When dhclient doesn't exist in the OS, log the issue and no-op."""
m_fallback.return_value = "eth9"
m_which.return_value = None # dhclient isn't found
self.assertEqual([], maybe_perform_dhcp_discovery())

with pytest.raises(NoDHCPLeaseMissingDhclientError):
maybe_perform_dhcp_discovery()

self.assertIn(
"Skip dhclient configuration: No dhclient command found.",
self.logs.getvalue(),
Expand Down Expand Up @@ -794,4 +807,50 @@ def test_ephemeral_dhcp_setup_network_if_url_connectivity(
m_dhcp.called_once_with()


@pytest.mark.parametrize(
"error_class",
[
NoDHCPLeaseInterfaceError,
NoDHCPLeaseInterfaceError,
NoDHCPLeaseMissingDhclientError,
],
)
class TestEphemeralDhcpLeaseErrors:
@mock.patch("cloudinit.net.dhcp.maybe_perform_dhcp_discovery")
def test_obtain_lease_raises_error(self, m_dhcp, error_class):
m_dhcp.side_effect = [error_class()]

with pytest.raises(error_class):
net.dhcp.EphemeralDHCPv4().obtain_lease()

assert len(m_dhcp.mock_calls) == 1

@mock.patch("cloudinit.net.dhcp.maybe_perform_dhcp_discovery")
def test_obtain_lease_umbrella_error(self, m_dhcp, error_class):
m_dhcp.side_effect = [error_class()]
with pytest.raises(NoDHCPLeaseError):
net.dhcp.EphemeralDHCPv4().obtain_lease()

assert len(m_dhcp.mock_calls) == 1

@mock.patch("cloudinit.net.dhcp.maybe_perform_dhcp_discovery")
def test_ctx_mgr_raises_error(self, m_dhcp, error_class):
m_dhcp.side_effect = [error_class()]

with pytest.raises(error_class):
with net.dhcp.EphemeralDHCPv4():
pass

assert len(m_dhcp.mock_calls) == 1

@mock.patch("cloudinit.net.dhcp.maybe_perform_dhcp_discovery")
def test_ctx_mgr_umbrella_error(self, m_dhcp, error_class):
m_dhcp.side_effect = [error_class()]
with pytest.raises(NoDHCPLeaseError):
with net.dhcp.EphemeralDHCPv4():
pass

assert len(m_dhcp.mock_calls) == 1


# vi: ts=4 expandtab