@@ -579,6 +579,138 @@ def test_run_rollback_passes_correct_pid_iface_and_rules_to_clean(
579579 self .assertEqual (clean_args [6 ], ["out_rule" ]) # output_rules from apply
580580
581581
582+ class TestVmiNetworkFilterNetworkMode (unittest .TestCase ):
583+
584+ def setUp (self ):
585+ self .mock_kubecli = MagicMock ()
586+ self .mock_kubernetes = MagicMock ()
587+ self .mock_kubecli .get_lib_kubernetes .return_value = self .mock_kubernetes
588+
589+ self .mock_kubernetes .list_pods .return_value = ["virt-launcher-virt-server-3-abc12" ]
590+ compute = _make_container ("compute" , ready = True , container_id = "containerd://deadbeef" )
591+ mock_pod_info = MagicMock ()
592+ mock_pod_info .containers = [compute ]
593+ self .mock_kubernetes .get_pod_info .return_value = mock_pod_info
594+ self .mock_kubernetes .get_pod_pids .return_value = ["100" , "101" , "102" ]
595+
596+ def _vmi_with_mode (self , mode : str ) -> dict :
597+ binding = {mode : {}}
598+ return {
599+ "status" : {"nodeName" : "worker-1" },
600+ "spec" : {"domain" : {"devices" : {"interfaces" : [{"name" : "default" , ** binding }]}}},
601+ }
602+
603+ def _run_success (self , vmi : dict , ** config_overrides ):
604+ config = _make_config (** config_overrides )
605+ module = VmiNetworkFilterModule (config , self .mock_kubecli )
606+ self .mock_kubernetes .get_vmi .return_value = vmi
607+ with patch (f"{ MODULE } .deploy_network_chaos_ng_pod" ), \
608+ patch (f"{ MODULE } .find_virt_launcher_netns_pid" , return_value = "101" ), \
609+ patch (f"{ MODULE } .get_vmi_tap_interface" , return_value = "tap0" ), \
610+ patch (f"{ MODULE } .get_vmi_masquerade_interface" , return_value = "eth0" ), \
611+ patch (f"{ MODULE } .apply_tc_vmi_chaos" , return_value = ([], [])) as mock_apply , \
612+ patch (f"{ MODULE } .clean_tc_vmi_chaos" ), \
613+ patch (f"{ MODULE } .time.sleep" ), \
614+ patch (f"{ MODULE } .log_info" ):
615+ module .run ("virt-density-udn-3/virt-server-3" )
616+ return mock_apply
617+
618+ # ------------------------------------------------------------------ mode detection
619+
620+ def test_bridge_mode_uses_tap_interface (self ):
621+ with patch (f"{ MODULE } .get_vmi_tap_interface" , return_value = "tap0" ) as mock_tap , \
622+ patch (f"{ MODULE } .get_vmi_masquerade_interface" ) as mock_masq , \
623+ patch (f"{ MODULE } .deploy_network_chaos_ng_pod" ), \
624+ patch (f"{ MODULE } .find_virt_launcher_netns_pid" , return_value = "101" ), \
625+ patch (f"{ MODULE } .apply_tc_vmi_chaos" , return_value = ([], [])), \
626+ patch (f"{ MODULE } .clean_tc_vmi_chaos" ), \
627+ patch (f"{ MODULE } .time.sleep" ), \
628+ patch (f"{ MODULE } .log_info" ):
629+ config = _make_config ()
630+ module = VmiNetworkFilterModule (config , self .mock_kubecli )
631+ self .mock_kubernetes .get_vmi .return_value = self ._vmi_with_mode ("bridge" )
632+ module .run ("virt-density-udn-3/virt-server-3" )
633+
634+ mock_tap .assert_called_once ()
635+ mock_masq .assert_not_called ()
636+
637+ def test_masquerade_mode_uses_default_interface (self ):
638+ with patch (f"{ MODULE } .get_vmi_tap_interface" ) as mock_tap , \
639+ patch (f"{ MODULE } .get_vmi_masquerade_interface" , return_value = "eth0" ) as mock_masq , \
640+ patch (f"{ MODULE } .deploy_network_chaos_ng_pod" ), \
641+ patch (f"{ MODULE } .find_virt_launcher_netns_pid" , return_value = "101" ), \
642+ patch (f"{ MODULE } .apply_tc_vmi_chaos" , return_value = ([], [])), \
643+ patch (f"{ MODULE } .clean_tc_vmi_chaos" ), \
644+ patch (f"{ MODULE } .time.sleep" ), \
645+ patch (f"{ MODULE } .log_info" ):
646+ config = _make_config ()
647+ module = VmiNetworkFilterModule (config , self .mock_kubecli )
648+ self .mock_kubernetes .get_vmi .return_value = self ._vmi_with_mode ("masquerade" )
649+ module .run ("virt-density-udn-3/virt-server-3" )
650+
651+ mock_masq .assert_called_once ()
652+ mock_tap .assert_not_called ()
653+
654+ def test_masquerade_mode_apply_called_with_eth0 (self ):
655+ mock_apply = self ._run_success (self ._vmi_with_mode ("masquerade" ))
656+ iface_arg = mock_apply .call_args [0 ][4 ]
657+ self .assertEqual (iface_arg , "eth0" )
658+
659+ def test_bridge_mode_apply_called_with_tap0 (self ):
660+ mock_apply = self ._run_success (self ._vmi_with_mode ("bridge" ))
661+ iface_arg = mock_apply .call_args [0 ][4 ]
662+ self .assertEqual (iface_arg , "tap0" )
663+
664+ def test_sriov_mode_raises (self ):
665+ config = _make_config ()
666+ module = VmiNetworkFilterModule (config , self .mock_kubecli )
667+ self .mock_kubernetes .get_vmi .return_value = self ._vmi_with_mode ("sriov" )
668+ with patch (f"{ MODULE } .deploy_network_chaos_ng_pod" ), \
669+ patch (f"{ MODULE } .log_info" ):
670+ with self .assertRaises (Exception ) as ctx :
671+ module .run ("virt-density-udn-3/virt-server-3" )
672+ self .assertIn ("sriov" , str (ctx .exception ))
673+ self .assertIn ("not supported" , str (ctx .exception ))
674+
675+ def test_macvtap_mode_raises (self ):
676+ config = _make_config ()
677+ module = VmiNetworkFilterModule (config , self .mock_kubecli )
678+ self .mock_kubernetes .get_vmi .return_value = self ._vmi_with_mode ("macvtap" )
679+ with patch (f"{ MODULE } .deploy_network_chaos_ng_pod" ), \
680+ patch (f"{ MODULE } .log_info" ):
681+ with self .assertRaises (Exception ) as ctx :
682+ module .run ("virt-density-udn-3/virt-server-3" )
683+ self .assertIn ("macvtap" , str (ctx .exception ))
684+ self .assertIn ("not supported" , str (ctx .exception ))
685+
686+ def test_explicit_interface_skips_mode_detection (self ):
687+ """If interfaces is set in config, neither tap nor masquerade detection is called."""
688+ with patch (f"{ MODULE } .get_vmi_tap_interface" ) as mock_tap , \
689+ patch (f"{ MODULE } .get_vmi_masquerade_interface" ) as mock_masq , \
690+ patch (f"{ MODULE } .deploy_network_chaos_ng_pod" ), \
691+ patch (f"{ MODULE } .find_virt_launcher_netns_pid" , return_value = "101" ), \
692+ patch (f"{ MODULE } .apply_tc_vmi_chaos" , return_value = ([], [])) as mock_apply , \
693+ patch (f"{ MODULE } .clean_tc_vmi_chaos" ), \
694+ patch (f"{ MODULE } .time.sleep" ), \
695+ patch (f"{ MODULE } .log_info" ):
696+ config = _make_config (interfaces = ["bond0" ])
697+ module = VmiNetworkFilterModule (config , self .mock_kubecli )
698+ self .mock_kubernetes .get_vmi .return_value = self ._vmi_with_mode ("masquerade" )
699+ module .run ("virt-density-udn-3/virt-server-3" )
700+
701+ mock_tap .assert_not_called ()
702+ mock_masq .assert_not_called ()
703+ iface_arg = mock_apply .call_args [0 ][4 ]
704+ self .assertEqual (iface_arg , "bond0" )
705+
706+ def test_no_mode_in_spec_defaults_to_bridge (self ):
707+ """VMI with no binding in spec should be treated as bridge mode."""
708+ vmi = {"status" : {"nodeName" : "worker-1" }, "spec" : {}}
709+ mock_apply = self ._run_success (vmi )
710+ iface_arg = mock_apply .call_args [0 ][4 ]
711+ self .assertEqual (iface_arg , "tap0" )
712+
713+
582714class TestVmiNetworkFilterPortsProtocols (unittest .TestCase ):
583715
584716 def setUp (self ):
0 commit comments