diff options
Diffstat (limited to 'tests/unittests/net/test_dhcp.py')
-rw-r--r-- | tests/unittests/net/test_dhcp.py | 144 |
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 |