diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py index ec1489391f0..58947946c05 100644 --- a/cloudinit/distros/__init__.py +++ b/cloudinit/distros/__init__.py @@ -18,6 +18,7 @@ from io import StringIO from typing import Any, Mapping, MutableMapping, Optional, Type +import cloudinit.net.netops.iproute2 as iproute2 from cloudinit import importer from cloudinit import log as logging from cloudinit import ( @@ -104,6 +105,7 @@ class Distro(persistence.CloudInitPickleMixin, metaclass=abc.ABCMeta): # This is used by self.shutdown_command(), and can be overridden in # subclasses shutdown_options_map = {"halt": "-H", "poweroff": "-P", "reboot": "-r"} + net_ops = iproute2.Iproute2 _ci_pkl_version = 1 prefer_fqdn = False @@ -118,6 +120,7 @@ def __init__(self, name, cfg, paths): self.name = name self.networking: Networking = self.networking_cls() self.dhcp_client_priority = [dhcp.IscDhclient, dhcp.Dhcpcd] + self.net_ops = iproute2.Iproute2 def _unpickle(self, ci_pkl_version: int) -> None: """Perform deserialization fixes for Distro.""" @@ -998,6 +1001,26 @@ def do_as(self, command: list, user: str, cwd: str = "", **kwargs): **kwargs, ) + @staticmethod + def build_dhclient_cmd( + path: str, + lease_file: str, + pid_file: str, + interface: str, + config_file: str, + ) -> list: + return [ + path, + "-1", + "-v", + "-lf", + lease_file, + "-pf", + pid_file, + "-sf", + "/bin/true", + ] + (["-cf", config_file, interface] if config_file else [interface]) + def _apply_hostname_transformations_to_url(url: str, transformations: list): """ diff --git a/cloudinit/distros/bsd.py b/cloudinit/distros/bsd.py index c4e1e15c600..2df287b671a 100644 --- a/cloudinit/distros/bsd.py +++ b/cloudinit/distros/bsd.py @@ -1,6 +1,7 @@ import platform from typing import List, Optional +import cloudinit.net.netops.bsd_netops as bsd_netops from cloudinit import distros, helpers from cloudinit import log as logging from cloudinit import net, subp, util @@ -27,6 +28,7 @@ class BSD(distros.Distro): # There is no update/upgrade on OpenBSD pkg_cmd_update_prefix: Optional[List[str]] = None pkg_cmd_upgrade_prefix: Optional[List[str]] = None + net_ops = bsd_netops.BsdNetOps # type: ignore def __init__(self, name, cfg, paths): super().__init__(name, cfg, paths) @@ -36,6 +38,7 @@ def __init__(self, name, cfg, paths): self._runner = helpers.Runners(paths) cfg["ssh_svcname"] = "sshd" self.osfamily = platform.system().lower() + self.net_ops = bsd_netops def _read_system_hostname(self): sys_hostname = self._read_hostname(self.hostname_conf_fn) diff --git a/cloudinit/distros/freebsd.py b/cloudinit/distros/freebsd.py index 77a94c6186a..4ed1356e93c 100644 --- a/cloudinit/distros/freebsd.py +++ b/cloudinit/distros/freebsd.py @@ -194,3 +194,15 @@ def update_package_sources(self): ["update"], freq=PER_INSTANCE, ) + + @staticmethod + def build_dhclient_cmd( + path: str, + lease_file: str, + pid_file: str, + interface: str, + config_file: str, + ) -> list: + return [path, "-l", lease_file, "-p", pid_file] + ( + ["-c", config_file, interface] if config_file else [interface] + ) diff --git a/cloudinit/net/dhcp.py b/cloudinit/net/dhcp.py index 6c8c2f54ab7..75a47efaa58 100644 --- a/cloudinit/net/dhcp.py +++ b/cloudinit/net/dhcp.py @@ -225,6 +225,7 @@ def dhcp_discovery( # scripts in /etc/dhcp/dhclient*hooks.d. pid_file = "/run/dhclient.pid" lease_file = "/run/dhclient.lease" + config_file = None # this function waits for these files to exist, clean previous runs # to avoid false positive in wait_for_files @@ -236,23 +237,11 @@ def dhcp_discovery( # Generally dhclient relies on dhclient-script PREINIT action to bring # the link up before attempting discovery. Since we are using # -sf /bin/true, we need to do that "link up" ourselves first. - subp.subp(["ip", "link", "set", "dev", interface, "up"], capture=True) + distro.net_ops.link_up(interface) # For INFINIBAND port the dhlient must be sent with # dhcp-client-identifier. So here we are checking if the interface is # INFINIBAND or not. If yes, we are generating the the client-id to be # used with the dhclient - cmd = [ - self.dhclient_path, - "-1", - "-v", - "-lf", - lease_file, - "-pf", - pid_file, - interface, - "-sf", - "/bin/true", - ] if is_ib_interface(interface): dhcp_client_identifier = ( "20:%s" % get_interface_mac(interface)[36:] @@ -263,13 +252,19 @@ def dhcp_discovery( % (interface, dhcp_client_identifier) ) tmp_dir = temp_utils.get_tmp_ancestor(needs_exe=True) - file_name = os.path.join(tmp_dir, interface + "-dhclient.conf") - util.write_file(file_name, interface_dhclient_content) - cmd.append("-cf") - cmd.append(file_name) + config_file = os.path.join(tmp_dir, interface + "-dhclient.conf") + util.write_file(config_file, interface_dhclient_content) try: - out, err = subp.subp(cmd, capture=True) + out, err = subp.subp( + distro.build_dhclient_cmd( + self.dhclient_path, + lease_file, + pid_file, + interface, + config_file, + ) + ) except subp.ProcessExecutionError as error: LOG.debug( "dhclient exited with code: %s stderr: %r stdout: %r", diff --git a/cloudinit/net/ephemeral.py b/cloudinit/net/ephemeral.py index 8339f169a9e..3b5f9b96a2e 100644 --- a/cloudinit/net/ephemeral.py +++ b/cloudinit/net/ephemeral.py @@ -4,7 +4,8 @@ """ import contextlib import logging -from typing import Any, Dict, List, Optional +from functools import partial +from typing import Any, Callable, Dict, List, Optional import cloudinit.net as net from cloudinit import subp @@ -30,6 +31,7 @@ class EphemeralIPv4Network: def __init__( self, + distro, interface, ip, prefix_or_mask, @@ -71,7 +73,8 @@ def __init__( self.router = router self.static_routes = static_routes # List of commands to run to cleanup state. - self.cleanup_cmds: List[str] = [] + self.cleanup_cmds: List[Callable] = [] + self.distro = distro def __enter__(self): """Perform ephemeral network setup if interface is not connected.""" @@ -114,7 +117,7 @@ def __enter__(self): def __exit__(self, excp_type, excp_value, excp_traceback): """Teardown anything we set up.""" for cmd in self.cleanup_cmds: - subp.subp(cmd, capture=True) + cmd() def _bringup_device(self): """Perform the ip comands to fully setup the device.""" @@ -126,22 +129,7 @@ def _bringup_device(self): self.broadcast, ) try: - subp.subp( - [ - "ip", - "-family", - "inet", - "addr", - "add", - cidr, - "broadcast", - self.broadcast, - "dev", - self.interface, - ], - capture=True, - update_env={"LANG": "C"}, - ) + self.distro.net_ops.add_addr(self.interface, cidr, self.broadcast) except subp.ProcessExecutionError as e: if "File exists" not in str(e.stderr): raise @@ -152,52 +140,22 @@ def _bringup_device(self): ) else: # Address creation success, bring up device and queue cleanup - subp.subp( - [ - "ip", - "-family", - "inet", - "link", - "set", - "dev", - self.interface, - "up", - ], - capture=True, - ) + self.distro.net_ops.link_up(self.interface, family="inet") self.cleanup_cmds.append( - [ - "ip", - "-family", - "inet", - "link", - "set", - "dev", + partial( + self.distro.net_ops.link_down, self.interface, - "down", - ] + family="inet", + ) ) self.cleanup_cmds.append( - [ - "ip", - "-family", - "inet", - "addr", - "del", - cidr, - "dev", - self.interface, - ] + partial(self.distro.net_ops.del_addr, self.interface, cidr) ) def _bringup_static_routes(self): # static_routes = [("169.254.169.254/32", "130.56.248.255"), # ("0.0.0.0/0", "130.56.240.1")] for net_address, gateway in self.static_routes: - via_arg = [] - if gateway != "0.0.0.0": - via_arg = ["via", gateway] - # Use "append" rather than "add" since the DHCP server may provide # rfc3442 classless static routes with multiple routes to the same # subnet via different routers or local interface addresses. @@ -205,23 +163,23 @@ def _bringup_static_routes(self): # In this scenario, `ip r add` fails. # # RHBZ: #2003231 - subp.subp( - ["ip", "-4", "route", "append", net_address] - + via_arg - + ["dev", self.interface], - capture=True, + self.distro.net_ops.append_route( + self.interface, net_address, gateway ) self.cleanup_cmds.insert( 0, - ["ip", "-4", "route", "del", net_address] - + via_arg - + ["dev", self.interface], + partial( + self.distro.net_ops.del_route, + self.interface, + net_address, + gateway=gateway, + ), ) def _bringup_router(self): """Perform the ip commands to fully setup the router if needed.""" # Check if a default route exists and exit if it does - out, _ = subp.subp(["ip", "route", "show", "0.0.0.0/0"], capture=True) + out = self.distro.net_ops.get_default_route() if "default" in out: LOG.debug( "Skip ephemeral route setup. %s already has default route: %s", @@ -229,50 +187,24 @@ def _bringup_router(self): out.strip(), ) return - subp.subp( - [ - "ip", - "-4", - "route", - "add", - self.router, - "dev", - self.interface, - "src", - self.ip, - ], - capture=True, + self.distro.net_ops.add_route( + self.interface, self.router, source_address=self.ip ) self.cleanup_cmds.insert( 0, - [ - "ip", - "-4", - "route", - "del", - self.router, - "dev", + partial( + self.distro.net_ops.del_route, self.interface, - "src", - self.ip, - ], - ) - subp.subp( - [ - "ip", - "-4", - "route", - "add", - "default", - "via", self.router, - "dev", - self.interface, - ], - capture=True, + source_address=self.ip, + ), + ) + self.distro.net_ops.add_route( + self.interface, "default", gateway=self.router ) self.cleanup_cmds.insert( - 0, ["ip", "-4", "route", "del", "default", "dev", self.interface] + 0, + partial(self.distro.net_ops.del_route, self.interface, "default"), ) @@ -283,7 +215,7 @@ class EphemeralIPv6Network: sufficient for link-local communication. """ - def __init__(self, interface): + def __init__(self, distro, interface): """Setup context manager and validate call signature. @param interface: Name of the network interface to bring up. @@ -294,6 +226,7 @@ def __init__(self, interface): raise ValueError("Cannot init network on {0}".format(interface)) self.interface = interface + self.distro = distro def __enter__(self): """linux kernel does autoconfiguration even when autoconf=0 @@ -301,10 +234,7 @@ def __enter__(self): https://www.kernel.org/doc/html/latest/networking/ipv6.html """ if net.read_sys_net(self.interface, "operstate") != "up": - subp.subp( - ["ip", "link", "set", "dev", self.interface, "up"], - capture=False, - ) + self.distro.net_ops.link_up(self.interface) def __exit__(self, *_args): """No need to set the link to down state""" @@ -395,7 +325,7 @@ def obtain_lease(self): ) if self.connectivity_url_data: kwargs["connectivity_url_data"] = self.connectivity_url_data - ephipv4 = EphemeralIPv4Network(**kwargs) + ephipv4 = EphemeralIPv4Network(self.distro, **kwargs) ephipv4.__enter__() self._ephipv4 = ephipv4 return self.lease @@ -445,7 +375,9 @@ def __enter__(self): EphemeralDHCPv4(self.distro, self.interface) ) if self.ipv6: - self.stack.enter_context(EphemeralIPv6Network(self.interface)) + self.stack.enter_context( + EphemeralIPv6Network(self.distro, self.interface) + ) # v6 link local might be usable # caller may want to log network state except NoDHCPLeaseError as e: diff --git a/cloudinit/net/netops/__init__.py b/cloudinit/net/netops/__init__.py new file mode 100644 index 00000000000..0a862918ef7 --- /dev/null +++ b/cloudinit/net/netops/__init__.py @@ -0,0 +1,47 @@ +from typing import Optional + + +class NetOps: + @staticmethod + def link_up(interface: str): + pass + + @staticmethod + def link_down(interface: str): + pass + + @staticmethod + def add_route( + interface: str, + route: str, + *, + gateway: Optional[str] = None, + source_address: Optional[str] = None + ): + pass + + @staticmethod + def append_route(interface: str, address: str, gateway: str): + pass + + @staticmethod + def del_route( + interface: str, + address: str, + *, + gateway: Optional[str] = None, + source_address: Optional[str] = None + ): + pass + + @staticmethod + def get_default_route() -> str: + pass + + @staticmethod + def add_addr(interface: str, address: str, broadcast: str): + pass + + @staticmethod + def del_addr(interface: str, address: str): + pass diff --git a/cloudinit/net/netops/bsd_netops.py b/cloudinit/net/netops/bsd_netops.py new file mode 100644 index 00000000000..e87ee1e8444 --- /dev/null +++ b/cloudinit/net/netops/bsd_netops.py @@ -0,0 +1,49 @@ +from typing import Optional + +import cloudinit.net.netops as netops + + +class BsdNetOps(netops.NetOps): + @staticmethod + def link_up(interface: str): + pass + + @staticmethod + def link_down(interface: str): + pass + + @staticmethod + def add_route( + interface: str, + route: str, + *, + gateway: Optional[str] = None, + source_address: Optional[str] = None + ): + pass + + @staticmethod + def append_route(interface: str, address: str, gateway: str): + pass + + @staticmethod + def del_route( + interface: str, + address: str, + *, + gateway: Optional[str] = None, + source_address: Optional[str] = None + ): + pass + + @staticmethod + def get_default_route() -> str: + pass + + @staticmethod + def add_addr(interface: str, address: str, broadcast: str): + pass + + @staticmethod + def del_addr(interface: str, address: str): + pass diff --git a/cloudinit/net/netops/iproute2.py b/cloudinit/net/netops/iproute2.py new file mode 100644 index 00000000000..08d79b187e1 --- /dev/null +++ b/cloudinit/net/netops/iproute2.py @@ -0,0 +1,93 @@ +from typing import Optional + +import cloudinit.net.netops as netops +from cloudinit import subp + + +class Iproute2(netops.NetOps): + @staticmethod + def link_up(interface: str, family: Optional[str] = None): + subp.subp( + ["ip"] + + (["-family", family] if family else []) + + ["link", "set", "dev", interface, "up"] + ) + + @staticmethod + def link_down(interface: str, family: Optional[str] = None): + subp.subp( + ["ip"] + + (["-family", family] if family else []) + + ["link", "set", "dev", interface, "down"] + ) + + @staticmethod + def add_route( + interface: str, + route: str, + *, + gateway: Optional[str] = None, + source_address: Optional[str] = None, + ): + subp.subp( + ["ip", "-4", "route", "add", route] + + (["via", gateway] if gateway and gateway != "0.0.0.0" else []) + + [ + "dev", + interface, + ] + + (["src", source_address] if source_address else []), + ) + + @staticmethod + def append_route(interface: str, address: str, gateway: str): + subp.subp( + ["ip", "-4", "route", "append", address] + + (["via", gateway] if gateway and gateway != "0.0.0.0" else []) + + ["dev", interface] + ) + + @staticmethod + def del_route( + interface: str, + address: str, + *, + gateway: Optional[str] = None, + source_address: Optional[str] = None, + ): + subp.subp( + ["ip", "-4", "route", "del", address] + + (["via", gateway] if gateway and gateway != "0.0.0.0" else []) + + ["dev", interface] + + (["src", source_address] if source_address else []) + ) + + @staticmethod + def get_default_route() -> str: + return subp.subp( + ["ip", "route", "show", "0.0.0.0/0"], + ).stdout + + @staticmethod + def add_addr(interface: str, address: str, broadcast: str): + subp.subp( + [ + "ip", + "-family", + "inet", + "addr", + "add", + address, + "broadcast", + broadcast, + "dev", + interface, + ], + update_env={"LANG": "C"}, + ) + + @staticmethod + def del_addr(interface: str, address: str): + subp.subp( + ["ip", "-family", "inet", "addr", "del", address, "dev", interface] + ) diff --git a/cloudinit/sources/DataSourceScaleway.py b/cloudinit/sources/DataSourceScaleway.py index e7fb9a3bbb9..a037b68c01f 100644 --- a/cloudinit/sources/DataSourceScaleway.py +++ b/cloudinit/sources/DataSourceScaleway.py @@ -311,6 +311,7 @@ def _get_data(self): if not self.has_ipv4: try: with EphemeralIPv6Network( + self.distro, self._fallback_interface, ): util.log_time( diff --git a/tests/unittests/net/test_dhcp.py b/tests/unittests/net/test_dhcp.py index 55d4c6e9f92..588f9bccdec 100644 --- a/tests/unittests/net/test_dhcp.py +++ b/tests/unittests/net/test_dhcp.py @@ -187,9 +187,8 @@ def test_obtain_lease_parses_static_routes(self, m_maybe, m_ipv4): } ] m_maybe.return_value = lease - eph = EphemeralDHCPv4( - MockDistro(), - ) + distro = MockDistro() + eph = EphemeralDHCPv4(distro) eph.obtain_lease() expected_kwargs = { "interface": "wlp3s0", @@ -199,7 +198,7 @@ def test_obtain_lease_parses_static_routes(self, m_maybe, m_ipv4): "static_routes": [("0.0.0.0/0", "130.56.240.1")], "router": "192.168.2.1", } - m_ipv4.assert_called_with(**expected_kwargs) + m_ipv4.assert_called_with(distro, **expected_kwargs) @mock.patch("cloudinit.net.ephemeral.EphemeralIPv4Network") @mock.patch("cloudinit.net.ephemeral.maybe_perform_dhcp_discovery") @@ -220,9 +219,8 @@ def test_obtain_centos_lease_parses_static_routes(self, m_maybe, m_ipv4): } ] m_maybe.return_value = lease - eph = EphemeralDHCPv4( - MockDistro(), - ) + distro = MockDistro() + eph = EphemeralDHCPv4(distro) eph.obtain_lease() expected_kwargs = { "interface": "wlp3s0", @@ -232,7 +230,7 @@ def test_obtain_centos_lease_parses_static_routes(self, m_maybe, m_ipv4): "static_routes": [("0.0.0.0/0", "130.56.240.1")], "router": "192.168.2.1", } - m_ipv4.assert_called_with(**expected_kwargs) + m_ipv4.assert_called_with(distro, **expected_kwargs) class TestDHCPParseStaticRoutes(CiTestCase): @@ -480,7 +478,7 @@ def test_dhcp_discovery_warns_invalid_pid( ) with self.assertRaises(InvalidDHCPLeaseFileError): with mock.patch("cloudinit.util.load_file", return_value=""): - IscDhclient().dhcp_discovery("eth9") + IscDhclient().dhcp_discovery("eth9", distro=MockDistro()) self.assertIn( "dhclient(pid=, parentpid=unknown) failed " "to daemonize after 10.0 seconds", @@ -503,7 +501,9 @@ def test_dhcp_discovery_waits_on_lease_and_pid( # 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([], IscDhclient().dhcp_discovery("eth9")) + self.assertEqual( + [], IscDhclient().dhcp_discovery("eth9", distro=MockDistro()) + ) self.assertEqual( mock.call([PID_F, LEASE_F], maxwait=5, naplen=0.01), m_wait.call_args_list[0], @@ -561,13 +561,13 @@ def test_dhcp_discovery( "routers": "192.168.2.1", } ], - IscDhclient().dhcp_discovery("eth9"), + IscDhclient().dhcp_discovery("eth9", distro=MockDistro()), ) # Interface was brought up before dhclient called m_subp.assert_has_calls( [ mock.call( - ["ip", "link", "set", "dev", "eth9", "up"], capture=True + ["ip", "link", "set", "dev", "eth9", "up"], ), mock.call( [ @@ -578,11 +578,10 @@ def test_dhcp_discovery( LEASE_F, "-pf", PID_F, - "eth9", "-sf", "/bin/true", + "eth9", ], - capture=True, ), ] ) @@ -642,13 +641,13 @@ def test_dhcp_discovery_ib( "routers": "192.168.2.1", } ], - IscDhclient().dhcp_discovery("ib0"), + IscDhclient().dhcp_discovery("ib0", distro=MockDistro()), ) # Interface was brought up before dhclient called m_subp.assert_has_calls( [ mock.call( - ["ip", "link", "set", "dev", "ib0", "up"], capture=True + ["ip", "link", "set", "dev", "ib0", "up"], ), mock.call( [ @@ -659,13 +658,12 @@ def test_dhcp_discovery_ib( LEASE_F, "-pf", PID_F, - "ib0", "-sf", "/bin/true", "-cf", "/tmp/ib0-dhclient.conf", + "ib0", ], - capture=True, ), ] ) @@ -715,7 +713,9 @@ def dhcp_log_func(out, err): self.assertEqual(out, dhclient_out) self.assertEqual(err, dhclient_err) - IscDhclient().dhcp_discovery("eth9", dhcp_log_func=dhcp_log_func) + IscDhclient().dhcp_discovery( + "eth9", dhcp_log_func=dhcp_log_func, distro=MockDistro() + ) class TestSystemdParseLeases(CiTestCase): diff --git a/tests/unittests/net/test_ephemeral.py b/tests/unittests/net/test_ephemeral.py index ddd9912cab6..99fbcb0bf6b 100644 --- a/tests/unittests/net/test_ephemeral.py +++ b/tests/unittests/net/test_ephemeral.py @@ -43,7 +43,7 @@ def test_stack_order( mock.call(m_ephemeral_ip_v6_network.return_value) ) assert [ - mock.call(interface) + mock.call(distro, interface) ] == m_ephemeral_ip_v6_network.call_args_list else: assert [] == m_ephemeral_ip_v6_network.call_args_list diff --git a/tests/unittests/net/test_init.py b/tests/unittests/net/test_init.py index 7cf5db277d6..91070575680 100644 --- a/tests/unittests/net/test_init.py +++ b/tests/unittests/net/test_init.py @@ -18,6 +18,7 @@ from cloudinit.subp import ProcessExecutionError from cloudinit.util import ensure_file, write_file from tests.unittests.helpers import CiTestCase, ResponsesTestCase +from tests.unittests.util import MockDistro class TestSysDevPath(CiTestCase): @@ -769,7 +770,7 @@ def test_ephemeral_ipv4_network_errors_on_missing_params(self, m_subp): params = copy.deepcopy(required_params) params[key] = None with self.assertRaises(ValueError) as context_manager: - EphemeralIPv4Network(**params) + EphemeralIPv4Network(MockDistro(), **params) error = context_manager.exception self.assertIn("Cannot init network on", str(error)) self.assertEqual(0, m_subp.call_count) @@ -785,7 +786,7 @@ def test_ephemeral_ipv4_network_errors_invalid_mask_prefix(self, m_subp): for error_val in invalid_masks: params["prefix_or_mask"] = error_val with self.assertRaises(ValueError) as context_manager: - with EphemeralIPv4Network(**params): + with EphemeralIPv4Network(MockDistro(), **params): pass error = context_manager.exception self.assertIn( @@ -809,12 +810,10 @@ def test_ephemeral_ipv4_network_performs_teardown(self, m_subp): "dev", "eth0", ], - capture=True, update_env={"LANG": "C"}, ), mock.call( ["ip", "-family", "inet", "link", "set", "dev", "eth0", "up"], - capture=True, ), ] expected_teardown_calls = [ @@ -829,7 +828,6 @@ def test_ephemeral_ipv4_network_performs_teardown(self, m_subp): "eth0", "down", ], - capture=True, ), mock.call( [ @@ -842,7 +840,6 @@ def test_ephemeral_ipv4_network_performs_teardown(self, m_subp): "dev", "eth0", ], - capture=True, ), ] params = { @@ -851,7 +848,7 @@ def test_ephemeral_ipv4_network_performs_teardown(self, m_subp): "prefix_or_mask": "255.255.255.0", "broadcast": "192.168.2.255", } - with EphemeralIPv4Network(**params): + with EphemeralIPv4Network(MockDistro(), **params): self.assertEqual(expected_setup_calls, m_subp.call_args_list) m_subp.assert_has_calls(expected_teardown_calls) @@ -864,13 +861,14 @@ def test_teardown_on_enter_exception(self, m_subp): """ def side_effect(args, **kwargs): - if args[3] == "append" and args[4] == "3.3.3.3/32": + if "append" in args and "3.3.3.3/32" in args: raise subp.ProcessExecutionError("oh no!") m_subp.side_effect = side_effect with pytest.raises(subp.ProcessExecutionError): with EphemeralIPv4Network( + MockDistro(), interface="eth0", ip="1.1.1.1", prefix_or_mask="255.255.255.0", @@ -895,7 +893,6 @@ def side_effect(args, **kwargs): "dev", "eth0", ], - capture=True, ), mock.call( [ @@ -908,7 +905,6 @@ def side_effect(args, **kwargs): "eth0", "down", ], - capture=True, ), mock.call( [ @@ -921,7 +917,6 @@ def side_effect(args, **kwargs): "dev", "eth0", ], - capture=True, ), ] for teardown in expected_teardown_calls: @@ -941,7 +936,7 @@ def test_ephemeral_ipv4_no_network_if_url_connectivity( "connectivity_url_data": {"url": "http://example.org/index.html"}, } - with EphemeralIPv4Network(**params): + with EphemeralIPv4Network(MockDistro(), **params): self.assertEqual( [mock.call(url="http://example.org/index.html", timeout=5)], m_readurl.call_args_list, @@ -977,11 +972,10 @@ def test_ephemeral_ipv4_network_noop_when_configured(self, m_subp): "dev", "eth0", ], - capture=True, update_env={"LANG": "C"}, ) ] - with EphemeralIPv4Network(**params): + with EphemeralIPv4Network(MockDistro(), **params): pass self.assertEqual(expected_calls, m_subp.call_args_list) self.assertIn( @@ -999,7 +993,7 @@ def test_ephemeral_ipv4_network_with_prefix(self, m_subp): } for prefix_val in ["24", 16]: # prefix can be int or string params["prefix_or_mask"] = prefix_val - with EphemeralIPv4Network(**params): + with EphemeralIPv4Network(MockDistro(), **params): pass m_subp.assert_has_calls( [ @@ -1016,7 +1010,6 @@ def test_ephemeral_ipv4_network_with_prefix(self, m_subp): "dev", "eth0", ], - capture=True, update_env={"LANG": "C"}, ) ] @@ -1036,7 +1029,6 @@ def test_ephemeral_ipv4_network_with_prefix(self, m_subp): "dev", "eth0", ], - capture=True, update_env={"LANG": "C"}, ) ] @@ -1051,7 +1043,8 @@ def test_ephemeral_ipv4_network_with_new_default_route(self, m_subp): "broadcast": "192.168.2.255", "router": "192.168.2.1", } - m_subp.return_value = "", "" # Empty response from ip route gw check + # Empty response from ip route gw check + m_subp.return_value = subp.SubpResult("", "") expected_setup_calls = [ mock.call( [ @@ -1066,14 +1059,12 @@ def test_ephemeral_ipv4_network_with_new_default_route(self, m_subp): "dev", "eth0", ], - capture=True, update_env={"LANG": "C"}, ), mock.call( ["ip", "-family", "inet", "link", "set", "dev", "eth0", "up"], - capture=True, ), - mock.call(["ip", "route", "show", "0.0.0.0/0"], capture=True), + mock.call(["ip", "route", "show", "0.0.0.0/0"]), mock.call( [ "ip", @@ -1086,7 +1077,6 @@ def test_ephemeral_ipv4_network_with_new_default_route(self, m_subp): "src", "192.168.2.2", ], - capture=True, ), mock.call( [ @@ -1100,13 +1090,11 @@ def test_ephemeral_ipv4_network_with_new_default_route(self, m_subp): "dev", "eth0", ], - capture=True, ), ] expected_teardown_calls = [ mock.call( ["ip", "-4", "route", "del", "default", "dev", "eth0"], - capture=True, ), mock.call( [ @@ -1120,11 +1108,10 @@ def test_ephemeral_ipv4_network_with_new_default_route(self, m_subp): "src", "192.168.2.2", ], - capture=True, ), ] - with EphemeralIPv4Network(**params): + with EphemeralIPv4Network(MockDistro(), **params): self.assertEqual(expected_setup_calls, m_subp.call_args_list) m_subp.assert_has_calls(expected_teardown_calls) @@ -1155,12 +1142,10 @@ def test_ephemeral_ipv4_network_with_rfc3442_static_routes(self, m_subp): "dev", "eth0", ], - capture=True, update_env={"LANG": "C"}, ), mock.call( ["ip", "-family", "inet", "link", "set", "dev", "eth0", "up"], - capture=True, ), mock.call( [ @@ -1172,7 +1157,6 @@ def test_ephemeral_ipv4_network_with_rfc3442_static_routes(self, m_subp): "dev", "eth0", ], - capture=True, ), mock.call( [ @@ -1186,7 +1170,6 @@ def test_ephemeral_ipv4_network_with_rfc3442_static_routes(self, m_subp): "dev", "eth0", ], - capture=True, ), mock.call( [ @@ -1200,7 +1183,6 @@ def test_ephemeral_ipv4_network_with_rfc3442_static_routes(self, m_subp): "dev", "eth0", ], - capture=True, ), ] expected_teardown_calls = [ @@ -1216,7 +1198,6 @@ def test_ephemeral_ipv4_network_with_rfc3442_static_routes(self, m_subp): "dev", "eth0", ], - capture=True, ), mock.call( [ @@ -1230,11 +1211,9 @@ def test_ephemeral_ipv4_network_with_rfc3442_static_routes(self, m_subp): "dev", "eth0", ], - capture=True, ), mock.call( ["ip", "-4", "route", "del", "192.168.2.1/32", "dev", "eth0"], - capture=True, ), mock.call( [ @@ -1247,7 +1226,6 @@ def test_ephemeral_ipv4_network_with_rfc3442_static_routes(self, m_subp): "eth0", "down", ], - capture=True, ), mock.call( [ @@ -1260,10 +1238,9 @@ def test_ephemeral_ipv4_network_with_rfc3442_static_routes(self, m_subp): "dev", "eth0", ], - capture=True, ), ] - with EphemeralIPv4Network(**params): + with EphemeralIPv4Network(MockDistro(), **params): self.assertEqual(expected_setup_calls, m_subp.call_args_list) m_subp.assert_has_calls(expected_setup_calls + expected_teardown_calls) @@ -1276,10 +1253,9 @@ def test_ephemeral_ipv6_network_performs_setup(self, m_subp, _): expected_setup_calls = [ mock.call( ["ip", "link", "set", "dev", "eth0", "up"], - capture=False, ), ] - with EphemeralIPv6Network(interface="eth0"): + with EphemeralIPv6Network(MockDistro(), interface="eth0"): assert expected_setup_calls == m_subp.call_args_list diff --git a/tests/unittests/sources/test_azure.py b/tests/unittests/sources/test_azure.py index b3f7b1bcb3d..c76bfd5efce 100644 --- a/tests/unittests/sources/test_azure.py +++ b/tests/unittests/sources/test_azure.py @@ -3072,6 +3072,7 @@ def test_poll_imds_returns_ovf_env( self.assertTrue(len(dsa._poll_imds()) > 0) self.assertEqual(m_dhcp.call_count, 1) m_net.assert_any_call( + dsa.distro, broadcast="192.168.2.255", interface="eth9", ip="192.168.2.9", @@ -3106,6 +3107,7 @@ def test__reprovision_calls__poll_imds( self.assertEqual(cfg["system_info"]["default_user"]["name"], username) self.assertEqual(m_dhcp.call_count, 1) m_net.assert_any_call( + dsa.distro, broadcast="192.168.2.255", interface="eth9", ip="192.168.2.9", diff --git a/tests/unittests/sources/test_ec2.py b/tests/unittests/sources/test_ec2.py index a72576683ff..ea8621a6523 100644 --- a/tests/unittests/sources/test_ec2.py +++ b/tests/unittests/sources/test_ec2.py @@ -881,6 +881,7 @@ def test_ec2_local_performs_dhcp_on_non_bsd( self.assertTrue(ret) m_dhcp.assert_called_once_with(ds.distro, "eth9", None) m_net4.assert_called_once_with( + ds.distro, broadcast="192.168.2.255", interface="eth9", ip="192.168.2.9", diff --git a/tests/unittests/sources/test_upcloud.py b/tests/unittests/sources/test_upcloud.py index 86c40845369..e945ae5d889 100644 --- a/tests/unittests/sources/test_upcloud.py +++ b/tests/unittests/sources/test_upcloud.py @@ -245,6 +245,7 @@ def test_network_configured_metadata( m_dhcp.assert_called_with(ds.distro, "eth1", None) m_net.assert_called_once_with( + ds.distro, broadcast="10.6.3.255", interface="eth1", ip="10.6.3.27",