diff --git a/salt/_modules/metalk8s_checks.py b/salt/_modules/metalk8s_checks.py index 279eb8b07f..5cb8e7b47b 100644 --- a/salt/_modules/metalk8s_checks.py +++ b/salt/_modules/metalk8s_checks.py @@ -408,7 +408,7 @@ def route_exists(destination, raises=True): error = None route_exists = False - all_routes = __salt__["network.routes"](family="inet") + all_routes = __salt__["metalk8s_network.routes"]() for route in all_routes: # Check if our destination network is fully included in this route. diff --git a/salt/_modules/metalk8s_network.py b/salt/_modules/metalk8s_network.py index ae77c78600..2d05ff36e8 100644 --- a/salt/_modules/metalk8s_network.py +++ b/salt/_modules/metalk8s_network.py @@ -167,3 +167,62 @@ def get_listening_processes(): ) return all_listen_connections + + +def routes(): + """ + Return currently configured IPv4 routes from routing table + + CLI Example: + + .. code-block:: bash + + salt '*' metalk8s_network.routes + """ + ret = [] + cmd = "ip -4 route show table main" + out = __salt__["cmd.run"](cmd) + for line in out.splitlines(): + comps = line.split() + + if comps[0] in ("unreachable", "blackhole"): + continue + + if comps[0] == "default": + ip_interface = "" + if comps[3] == "dev": + ip_interface = comps[4] + + ret.append( + { + "addr_family": "inet", + "destination": "0.0.0.0", + "gateway": comps[2], + "netmask": "0.0.0.0", + "flags": "UG", + "interface": ip_interface, + } + ) + else: + try: + address_mask = __salt__["network.convert_cidr"](comps[0]) + except ValueError: + log.warning("Unsupported route type or format: %s", line) + continue + + ip_interface = "" + if comps[1] == "dev": + ip_interface = comps[2] + + ret.append( + { + "addr_family": "inet", + "destination": address_mask["network"], + "gateway": "0.0.0.0", + "netmask": address_mask["netmask"], + "flags": "U", + "interface": ip_interface, + } + ) + + return ret diff --git a/salt/tests/unit/modules/files/test_metalk8s_network.yaml b/salt/tests/unit/modules/files/test_metalk8s_network.yaml index eb0d597eda..162265a0b6 100644 --- a/salt/tests/unit/modules/files/test_metalk8s_network.yaml +++ b/salt/tests/unit/modules/files/test_metalk8s_network.yaml @@ -37,3 +37,49 @@ get_listening_processes: 127.0.0.1: pid: 123 name: likely-something + +routes: + # 0. Default route + - ip_route_output: |- + default via 10.200.0.1 dev eth0 + result: + - &default_route + addr_family: inet + destination: 0.0.0.0 + flags: UG + gateway: 10.200.0.1 + interface: eth0 + netmask: 0.0.0.0 + # 1. A simple route + - ip_route_output: |- + 10.200.0.0/16 dev eth0 proto kernel scope link src 10.200.2.41 + result: + - &simple_route + addr_family: inet + destination: 10.200.0.0 + flags: U + gateway: 0.0.0.0 + interface: eth0 + netmask: 255.255.0.0 + # 2. A blackhole route + - ip_route_output: |- + blackhole 10.233.162.0/26 proto bird + result: [] + # 3. Multiple routes + - ip_route_output: |- + default via 10.200.0.1 dev eth0 + 10.200.0.0/16 dev eth0 proto kernel scope link src 10.200.2.41 + blackhole 10.233.162.0/26 proto bird + result: + - *default_route + - *simple_route + # 4. No routes + - ip_route_output: '' + result: [] + # 5. Unsupported type or format + - ip_route_output: |- + banana route via foo + this is not a valid input + 10.200.0.0/16 dev eth0 proto kernel scope link src 10.200.2.41 + result: + - *simple_route diff --git a/salt/tests/unit/modules/test_metalk8s_checks.py b/salt/tests/unit/modules/test_metalk8s_checks.py index caf485e7e0..9889cb6a0d 100644 --- a/salt/tests/unit/modules/test_metalk8s_checks.py +++ b/salt/tests/unit/modules/test_metalk8s_checks.py @@ -193,7 +193,7 @@ def test_route_exists( network_routes_mock = MagicMock(return_value=routes_ret) patch_dict = { - "network.routes": network_routes_mock, + "metalk8s_network.routes": network_routes_mock, } with patch.dict(metalk8s_checks.__salt__, patch_dict): diff --git a/salt/tests/unit/modules/test_metalk8s_network.py b/salt/tests/unit/modules/test_metalk8s_network.py index cd06a3e65c..50bd4481b1 100644 --- a/salt/tests/unit/modules/test_metalk8s_network.py +++ b/salt/tests/unit/modules/test_metalk8s_network.py @@ -1,3 +1,4 @@ +import ipaddress import os.path from unittest import TestCase from unittest.mock import MagicMock, patch @@ -266,3 +267,26 @@ def test_get_listening_processes( "psutil.Process", process_mock ): self.assertEqual(metalk8s_network.get_listening_processes(), result) + + @utils.parameterized_from_cases(YAML_TESTS_CASES["routes"]) + def test_routes(self, ip_route_output, result): + """ + Tests the return of `routes` function + """ + + def _mock_convert_cidr(cidr): + ret = {"network": None, "netmask": None, "broadcast": None} + network_info = ipaddress.ip_network(cidr, strict=False) + ret["network"] = str(network_info.network_address) + ret["netmask"] = str(network_info.netmask) + ret["broadcast"] = str(network_info.broadcast_address) + return ret + + mock_convert_cidr = MagicMock(side_effect=_mock_convert_cidr) + mock_ip_cmd = MagicMock(return_value=ip_route_output) + with patch.dict( + metalk8s_network.__salt__, + {"cmd.run": mock_ip_cmd, "network.convert_cidr": mock_convert_cidr}, + ): + self.assertEqual(metalk8s_network.routes(), result) + mock_ip_cmd.assert_called_once_with("ip -4 route show table main")