summaryrefslogtreecommitdiff
path: root/tests/unittests/net/test_dhcp.py
diff options
context:
space:
mode:
Diffstat (limited to 'tests/unittests/net/test_dhcp.py')
-rw-r--r--tests/unittests/net/test_dhcp.py144
1 files changed, 99 insertions, 45 deletions
diff --git a/tests/unittests/net/test_dhcp.py b/tests/unittests/net/test_dhcp.py
index a55d49cb..ed01e60b 100644
--- a/tests/unittests/net/test_dhcp.py
+++ b/tests/unittests/net/test_dhcp.py
@@ -9,14 +9,12 @@ import responses
from cloudinit.net.dhcp import (
InvalidDHCPLeaseFileError,
+ IscDhclient,
NoDHCPLeaseError,
NoDHCPLeaseInterfaceError,
NoDHCPLeaseMissingDhclientError,
- dhcp_discovery,
maybe_perform_dhcp_discovery,
networkd_load_leases,
- parse_dhcp_lease_file,
- parse_static_routes,
)
from cloudinit.net.ephemeral import EphemeralDHCPv4
from cloudinit.util import ensure_file, subp, write_file
@@ -26,6 +24,7 @@ from tests.unittests.helpers import (
mock,
populate_dir,
)
+from tests.unittests.util import MockDistro
PID_F = "/run/dhclient.pid"
LEASE_F = "/run/dhclient.lease"
@@ -38,21 +37,25 @@ class TestParseDHCPLeasesFile(CiTestCase):
empty_file = self.tmp_path("leases")
ensure_file(empty_file)
with self.assertRaises(InvalidDHCPLeaseFileError) as context_manager:
- parse_dhcp_lease_file(empty_file)
+ IscDhclient.parse_dhcp_lease_file(empty_file)
error = context_manager.exception
self.assertIn("Cannot parse empty dhcp lease file", str(error))
def test_parse_malformed_lease_file_content_errors(self):
- """parse_dhcp_lease_file errors when file content isn't dhcp leases."""
+ """IscDhclient.parse_dhcp_lease_file errors when file content isn't
+ dhcp leases.
+ """
non_lease_file = self.tmp_path("leases")
write_file(non_lease_file, "hi mom.")
with self.assertRaises(InvalidDHCPLeaseFileError) as context_manager:
- parse_dhcp_lease_file(non_lease_file)
+ IscDhclient.parse_dhcp_lease_file(non_lease_file)
error = context_manager.exception
self.assertIn("Cannot parse dhcp lease file", str(error))
def test_parse_multiple_leases(self):
- """parse_dhcp_lease_file returns a list of all leases within."""
+ """IscDhclient.parse_dhcp_lease_file returns a list of all leases
+ within.
+ """
lease_file = self.tmp_path("leases")
content = dedent(
"""
@@ -93,12 +96,16 @@ class TestParseDHCPLeasesFile(CiTestCase):
},
]
write_file(lease_file, content)
- self.assertCountEqual(expected, parse_dhcp_lease_file(lease_file))
+ self.assertCountEqual(
+ expected, IscDhclient.parse_dhcp_lease_file(lease_file)
+ )
class TestDHCPRFC3442(CiTestCase):
def test_parse_lease_finds_rfc3442_classless_static_routes(self):
- """parse_dhcp_lease_file returns rfc3442-classless-static-routes."""
+ """IscDhclient.parse_dhcp_lease_file returns
+ rfc3442-classless-static-routes.
+ """
lease_file = self.tmp_path("leases")
content = dedent(
"""
@@ -125,11 +132,13 @@ class TestDHCPRFC3442(CiTestCase):
}
]
write_file(lease_file, content)
- self.assertCountEqual(expected, parse_dhcp_lease_file(lease_file))
+ self.assertCountEqual(
+ expected, IscDhclient.parse_dhcp_lease_file(lease_file)
+ )
def test_parse_lease_finds_classless_static_routes(self):
"""
- parse_dhcp_lease_file returns classless-static-routes
+ IscDhclient.parse_dhcp_lease_file returns classless-static-routes
for Centos lease format.
"""
lease_file = self.tmp_path("leases")
@@ -158,7 +167,9 @@ class TestDHCPRFC3442(CiTestCase):
}
]
write_file(lease_file, content)
- self.assertCountEqual(expected, parse_dhcp_lease_file(lease_file))
+ self.assertCountEqual(
+ expected, IscDhclient.parse_dhcp_lease_file(lease_file)
+ )
@mock.patch("cloudinit.net.ephemeral.EphemeralIPv4Network")
@mock.patch("cloudinit.net.ephemeral.maybe_perform_dhcp_discovery")
@@ -176,7 +187,9 @@ class TestDHCPRFC3442(CiTestCase):
}
]
m_maybe.return_value = lease
- eph = EphemeralDHCPv4()
+ eph = EphemeralDHCPv4(
+ MockDistro(),
+ )
eph.obtain_lease()
expected_kwargs = {
"interface": "wlp3s0",
@@ -207,7 +220,9 @@ class TestDHCPRFC3442(CiTestCase):
}
]
m_maybe.return_value = lease
- eph = EphemeralDHCPv4()
+ eph = EphemeralDHCPv4(
+ MockDistro(),
+ )
eph.obtain_lease()
expected_kwargs = {
"interface": "wlp3s0",
@@ -223,41 +238,43 @@ class TestDHCPRFC3442(CiTestCase):
class TestDHCPParseStaticRoutes(CiTestCase):
with_logs = True
- def parse_static_routes_empty_string(self):
- self.assertEqual([], parse_static_routes(""))
+ def test_parse_static_routes_empty_string(self):
+ self.assertEqual([], IscDhclient.parse_static_routes(""))
def test_parse_static_routes_invalid_input_returns_empty_list(self):
rfc3442 = "32,169,254,169,254,130,56,248"
- self.assertEqual([], parse_static_routes(rfc3442))
+ self.assertEqual([], IscDhclient.parse_static_routes(rfc3442))
def test_parse_static_routes_bogus_width_returns_empty_list(self):
rfc3442 = "33,169,254,169,254,130,56,248"
- self.assertEqual([], parse_static_routes(rfc3442))
+ self.assertEqual([], IscDhclient.parse_static_routes(rfc3442))
def test_parse_static_routes_single_ip(self):
rfc3442 = "32,169,254,169,254,130,56,248,255"
self.assertEqual(
[("169.254.169.254/32", "130.56.248.255")],
- parse_static_routes(rfc3442),
+ IscDhclient.parse_static_routes(rfc3442),
)
def test_parse_static_routes_single_ip_handles_trailing_semicolon(self):
rfc3442 = "32,169,254,169,254,130,56,248,255;"
self.assertEqual(
[("169.254.169.254/32", "130.56.248.255")],
- parse_static_routes(rfc3442),
+ IscDhclient.parse_static_routes(rfc3442),
)
def test_parse_static_routes_default_route(self):
rfc3442 = "0,130,56,240,1"
self.assertEqual(
- [("0.0.0.0/0", "130.56.240.1")], parse_static_routes(rfc3442)
+ [("0.0.0.0/0", "130.56.240.1")],
+ IscDhclient.parse_static_routes(rfc3442),
)
def test_unspecified_gateway(self):
rfc3442 = "32,169,254,169,254,0,0,0,0"
self.assertEqual(
- [("169.254.169.254/32", "0.0.0.0")], parse_static_routes(rfc3442)
+ [("169.254.169.254/32", "0.0.0.0")],
+ IscDhclient.parse_static_routes(rfc3442),
)
def test_parse_static_routes_class_c_b_a(self):
@@ -273,7 +290,7 @@ class TestDHCPParseStaticRoutes(CiTestCase):
("10.0.0.0/8", "10.0.0.4"),
]
),
- sorted(parse_static_routes(rfc3442)),
+ sorted(IscDhclient.parse_static_routes(rfc3442)),
)
def test_parse_static_routes_logs_error_truncated(self):
@@ -285,7 +302,7 @@ class TestDHCPParseStaticRoutes(CiTestCase):
"netlen": "33,0",
}
for rfc3442 in bad_rfc3442.values():
- self.assertEqual([], parse_static_routes(rfc3442))
+ self.assertEqual([], IscDhclient.parse_static_routes(rfc3442))
logs = self.logs.getvalue()
self.assertEqual(len(bad_rfc3442.keys()), len(logs.splitlines()))
@@ -302,7 +319,7 @@ class TestDHCPParseStaticRoutes(CiTestCase):
("172.16.0.0/16", "172.16.0.4"),
]
),
- sorted(parse_static_routes(rfc3442)),
+ sorted(IscDhclient.parse_static_routes(rfc3442)),
)
logs = self.logs.getvalue()
@@ -317,7 +334,7 @@ class TestDHCPParseStaticRoutes(CiTestCase):
("0.0.0.0/0", "192.168.128.1"),
]
),
- sorted(parse_static_routes(redhat_format)),
+ sorted(IscDhclient.parse_static_routes(redhat_format)),
)
def test_redhat_format_with_a_space_too_much_after_comma(self):
@@ -329,7 +346,7 @@ class TestDHCPParseStaticRoutes(CiTestCase):
("0.0.0.0/0", "192.168.128.1"),
]
),
- sorted(parse_static_routes(redhat_format)),
+ sorted(IscDhclient.parse_static_routes(redhat_format)),
)
@@ -343,7 +360,7 @@ class TestDHCPDiscoveryClean(CiTestCase):
m_fallback_nic.return_value = None # No fallback nic found
with pytest.raises(NoDHCPLeaseInterfaceError):
- maybe_perform_dhcp_discovery()
+ maybe_perform_dhcp_discovery(MockDistro())
self.assertIn(
"Skip dhcp_discovery: Unable to find fallback nic.",
@@ -364,10 +381,34 @@ class TestDHCPDiscoveryClean(CiTestCase):
]
with pytest.raises(NoDHCPLeaseError):
- maybe_perform_dhcp_discovery()
+ maybe_perform_dhcp_discovery(MockDistro())
self.assertIn(
- "dhclient exited with code: -5",
+ "DHCP client selected: dhclient",
+ 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")
+ @mock.patch("cloudinit.net.dhcp.subp.which")
+ def test_dhcp_client_failover(self, m_which, 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),
+ ]
+
+ m_which.side_effect = [False, True]
+ with pytest.raises(NoDHCPLeaseError):
+ maybe_perform_dhcp_discovery(MockDistro())
+
+ self.assertIn(
+ "DHCP client not found: dhclient",
+ self.logs.getvalue(),
+ )
+ self.assertIn(
+ "DHCP client not found: dhcpcd",
self.logs.getvalue(),
)
@@ -375,7 +416,7 @@ class TestDHCPDiscoveryClean(CiTestCase):
def test_provided_nic_does_not_exist(self, m_fallback_nic):
"""When the provided nic doesn't exist, log a message and no-op."""
with pytest.raises(NoDHCPLeaseInterfaceError):
- maybe_perform_dhcp_discovery("idontexist")
+ maybe_perform_dhcp_discovery(MockDistro(), "idontexist")
self.assertIn(
"Skip dhcp_discovery: nic idontexist not found in get_devicelist.",
@@ -390,7 +431,7 @@ class TestDHCPDiscoveryClean(CiTestCase):
m_which.return_value = None # dhclient isn't found
with pytest.raises(NoDHCPLeaseMissingDhclientError):
- maybe_perform_dhcp_discovery()
+ maybe_perform_dhcp_discovery(MockDistro())
self.assertIn(
"Skip dhclient configuration: No dhclient command found.",
@@ -434,11 +475,11 @@ class TestDHCPDiscoveryClean(CiTestCase):
"routers": "192.168.2.1",
}
],
- parse_dhcp_lease_file("lease"),
+ IscDhclient.parse_dhcp_lease_file("lease"),
)
with self.assertRaises(InvalidDHCPLeaseFileError):
with mock.patch("cloudinit.util.load_file", return_value=""):
- dhcp_discovery(DHCLIENT, "eth9")
+ IscDhclient().dhcp_discovery("eth9")
self.assertIn(
"dhclient(pid=, parentpid=unknown) failed "
"to daemonize after 10.0 seconds",
@@ -460,7 +501,9 @@ class TestDHCPDiscoveryClean(CiTestCase):
# Don't create pid or leases file
m_wait.return_value = [PID_F] # Return the missing pidfile wait for
m_getppid.return_value = 1 # Indicate that dhclient has daemonized
- self.assertEqual([], dhcp_discovery("/sbin/dhclient", "eth9"))
+ self.assertEqual(
+ [], IscDhclient().dhcp_discovery("/sbin/dhclient", "eth9")
+ )
self.assertEqual(
mock.call([PID_F, LEASE_F], maxwait=5, naplen=0.01),
m_wait.call_args_list[0],
@@ -476,10 +519,12 @@ class TestDHCPDiscoveryClean(CiTestCase):
@mock.patch("cloudinit.net.dhcp.util.get_proc_ppid")
@mock.patch("cloudinit.net.dhcp.os.kill")
@mock.patch("cloudinit.net.dhcp.subp.subp")
+ @mock.patch("cloudinit.net.dhcp.subp.which", return_value="/sbin/dhclient")
@mock.patch("cloudinit.util.wait_for_files", return_value=False)
def test_dhcp_discovery(
self,
m_wait,
+ m_which,
m_subp,
m_kill,
m_getppid,
@@ -516,7 +561,7 @@ class TestDHCPDiscoveryClean(CiTestCase):
"routers": "192.168.2.1",
}
],
- dhcp_discovery("/sbin/dhclient", "eth9"),
+ IscDhclient().dhcp_discovery("eth9"),
)
# Interface was brought up before dhclient called
m_subp.assert_has_calls(
@@ -554,12 +599,14 @@ class TestDHCPDiscoveryClean(CiTestCase):
@mock.patch("cloudinit.net.dhcp.os.remove")
@mock.patch("cloudinit.net.dhcp.util.get_proc_ppid", return_value=1)
@mock.patch("cloudinit.net.dhcp.os.kill")
+ @mock.patch("cloudinit.net.dhcp.subp.which", return_value="/sbin/dhclient")
@mock.patch("cloudinit.net.dhcp.subp.subp", return_value=("", ""))
@mock.patch("cloudinit.util.wait_for_files", return_value=False)
def test_dhcp_discovery_ib(
self,
m_wait,
m_subp,
+ m_which,
m_kill,
m_getppid,
m_remove,
@@ -595,7 +642,7 @@ class TestDHCPDiscoveryClean(CiTestCase):
"routers": "192.168.2.1",
}
],
- dhcp_discovery("/sbin/dhclient", "ib0"),
+ IscDhclient().dhcp_discovery("ib0"),
)
# Interface was brought up before dhclient called
m_subp.assert_has_calls(
@@ -667,7 +714,7 @@ class TestDHCPDiscoveryClean(CiTestCase):
self.assertEqual(out, dhclient_out)
self.assertEqual(err, dhclient_err)
- dhcp_discovery(DHCLIENT, "eth9", dhcp_log_func=dhcp_log_func)
+ IscDhclient().dhcp_discovery("eth9", dhcp_log_func=dhcp_log_func)
class TestSystemdParseLeases(CiTestCase):
@@ -796,6 +843,7 @@ class TestEphemeralDhcpNoNetworkSetup(ResponsesTestCase):
self.responses.add(responses.GET, url)
with EphemeralDHCPv4(
+ MockDistro(),
connectivity_url_data={"url": url},
) as lease:
self.assertIsNone(lease)
@@ -819,6 +867,7 @@ class TestEphemeralDhcpNoNetworkSetup(ResponsesTestCase):
self.responses.add(responses.GET, url, body=b"", status=404)
with EphemeralDHCPv4(
+ MockDistro(),
connectivity_url_data={"url": url},
) as lease:
self.assertEqual(fake_lease, lease)
@@ -840,7 +889,9 @@ class TestEphemeralDhcpLeaseErrors:
m_dhcp.side_effect = [error_class()]
with pytest.raises(error_class):
- EphemeralDHCPv4().obtain_lease()
+ EphemeralDHCPv4(
+ MockDistro(),
+ ).obtain_lease()
assert len(m_dhcp.mock_calls) == 1
@@ -848,7 +899,9 @@ class TestEphemeralDhcpLeaseErrors:
def test_obtain_lease_umbrella_error(self, m_dhcp, error_class):
m_dhcp.side_effect = [error_class()]
with pytest.raises(NoDHCPLeaseError):
- EphemeralDHCPv4().obtain_lease()
+ EphemeralDHCPv4(
+ MockDistro(),
+ ).obtain_lease()
assert len(m_dhcp.mock_calls) == 1
@@ -857,7 +910,9 @@ class TestEphemeralDhcpLeaseErrors:
m_dhcp.side_effect = [error_class()]
with pytest.raises(error_class):
- with EphemeralDHCPv4():
+ with EphemeralDHCPv4(
+ MockDistro(),
+ ):
pass
assert len(m_dhcp.mock_calls) == 1
@@ -866,10 +921,9 @@ class TestEphemeralDhcpLeaseErrors:
def test_ctx_mgr_umbrella_error(self, m_dhcp, error_class):
m_dhcp.side_effect = [error_class()]
with pytest.raises(NoDHCPLeaseError):
- with EphemeralDHCPv4():
+ with EphemeralDHCPv4(
+ MockDistro(),
+ ):
pass
assert len(m_dhcp.mock_calls) == 1
-
-
-# vi: ts=4 expandtab