test_container_lan_link_3417.py 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. """A macvlan or ipvlan container is on the LAN, not on bridge networking (#3417).
  2. Its interface is created in the host's namespace and moved into the
  3. container's, so like a veth its ``iflink`` names an interface elsewhere: the
  4. diagnostic called it bridge networking and told the user to recreate the
  5. container with host networking, though discovery and the Virtual Printer work.
  6. """
  7. import json
  8. import subprocess
  9. from unittest.mock import patch
  10. import pytest
  11. from backend.app.services import discovery
  12. from backend.app.services.printer_diagnostic import (
  13. _detect_container_network_mode,
  14. _lan_link_kinds,
  15. )
  16. from backend.tests.unit.services.test_container_runtime_3092 import _BRIDGE_NETNS, _HOST_NETNS, MOD, _netns
  17. def _link(name, kind, mode=None):
  18. info = {"info_kind": kind}
  19. if mode:
  20. info["info_data"] = {"mode": mode}
  21. return {"ifname": name, "linkinfo": info}
  22. _MACVLAN = [_link("lo", None), _link("eth0", "macvlan")]
  23. class TestDetect:
  24. def test_macvlan_is_on_the_lan(self):
  25. with _netns(_BRIDGE_NETNS, _MACVLAN):
  26. assert _detect_container_network_mode(discovery.RUNTIME_DOCKER) == "macvlan"
  27. def test_ipvlan_l2_is_on_the_lan(self):
  28. with _netns(_BRIDGE_NETNS, [_link("eth0", "ipvlan", "l2")]):
  29. assert _detect_container_network_mode(discovery.RUNTIME_DOCKER) == "ipvlan"
  30. @pytest.mark.parametrize("mode", ["l3", "l3s"])
  31. def test_ipvlan_l3_routes(self, mode):
  32. with _netns(_BRIDGE_NETNS, [_link("eth0", "ipvlan", mode)]):
  33. assert _detect_container_network_mode(discovery.RUNTIME_PODMAN) == "ipvlan_l3"
  34. def test_a_bridge_and_a_macvlan_network_reach_the_lan_through_the_macvlan(self):
  35. interfaces = {**_BRIDGE_NETNS, "eth1": (3, 46, False)}
  36. with _netns(interfaces, [_link("eth0", "veth"), _link("eth1", "macvlan")]):
  37. assert _detect_container_network_mode(discovery.RUNTIME_DOCKER) == "macvlan"
  38. def test_an_ipvlan_mode_not_known_to_route_is_not_warned(self):
  39. """iproute2 prints "unknown" for a mode it has no name for."""
  40. with _netns(_BRIDGE_NETNS, [_link("eth0", "ipvlan", "unknown")]):
  41. assert _detect_container_network_mode(discovery.RUNTIME_DOCKER) == "ipvlan"
  42. def test_ipvlan_wins_over_its_l3_sibling(self):
  43. interfaces = {**_BRIDGE_NETNS, "eth1": (3, 46, False)}
  44. with _netns(interfaces, [_link("eth0", "ipvlan", "l3"), _link("eth1", "ipvlan", "l2")]):
  45. assert _detect_container_network_mode(discovery.RUNTIME_DOCKER) == "ipvlan"
  46. def test_a_veth_is_still_bridge(self):
  47. with _netns(_BRIDGE_NETNS, [_link("eth0", "veth")]):
  48. assert _detect_container_network_mode(discovery.RUNTIME_DOCKER) == "bridge"
  49. def test_host_networking_answers_first(self):
  50. """A host may carry a macvlan shim to reach its containers."""
  51. with _netns({**_HOST_NETNS, "shim": (3, 2, False)}, [_link("shim", "macvlan")]):
  52. assert _detect_container_network_mode(discovery.RUNTIME_DOCKER) == "host"
  53. def test_no_engine_is_still_unknown(self):
  54. with _netns(_BRIDGE_NETNS, _MACVLAN):
  55. assert _detect_container_network_mode(None) is None
  56. class TestUnreadableLinks:
  57. """Without the link kinds a veth and a macvlan look alike: say so."""
  58. @pytest.mark.parametrize(
  59. "ip",
  60. [
  61. FileNotFoundError("ip"),
  62. subprocess.CompletedProcess([], 1, stdout="", stderr="no netlink"),
  63. subprocess.CompletedProcess([], 0, stdout="not json", stderr=""),
  64. ],
  65. )
  66. def test_the_mode_is_unknown_rather_than_bridge(self, ip):
  67. with _netns(_BRIDGE_NETNS):
  68. kwargs = {"side_effect": ip} if isinstance(ip, Exception) else {"return_value": ip}
  69. with patch(f"{MOD}.subprocess.run", **kwargs):
  70. assert _lan_link_kinds() is None
  71. assert _detect_container_network_mode(discovery.RUNTIME_DOCKER) is None
  72. def test_host_networking_needs_no_link_kinds(self):
  73. with _netns(_HOST_NETNS), patch(f"{MOD}.subprocess.run", side_effect=FileNotFoundError("ip")):
  74. assert _detect_container_network_mode(discovery.RUNTIME_DOCKER) == "host"
  75. class TestSupportPackage:
  76. def test_the_support_package_reads_the_mode_as_the_diagnostic_does(self):
  77. from backend.app.api.routes.support import _detect_docker_network_mode
  78. with (
  79. _netns(_BRIDGE_NETNS, _MACVLAN),
  80. patch("backend.app.services.discovery.detect_container_runtime", return_value=discovery.RUNTIME_DOCKER),
  81. ):
  82. assert _detect_docker_network_mode() == "macvlan"
  83. def test_podman_host_networking_is_no_longer_bridge_there(self):
  84. from backend.app.api.routes.support import _detect_docker_network_mode
  85. with (
  86. _netns(_HOST_NETNS),
  87. patch("backend.app.services.discovery.detect_container_runtime", return_value=discovery.RUNTIME_PODMAN),
  88. ):
  89. assert _detect_docker_network_mode() == "host"