@@ -210,7 +210,7 @@ def _patch_run(self):
210210 # ------------------------------------------------------------------ success
211211
212212 @patch (f"{ MODULE } .clean_tc_vmi_chaos" )
213- @patch (f"{ MODULE } .apply_tc_vmi_chaos" )
213+ @patch (f"{ MODULE } .apply_tc_vmi_chaos" , return_value = ([], []) )
214214 @patch (f"{ MODULE } .get_vmi_tap_interface" , return_value = "tap0" )
215215 @patch (f"{ MODULE } .find_virt_launcher_netns_pid" , return_value = "101" )
216216 @patch (f"{ MODULE } .deploy_network_chaos_ng_pod" )
@@ -237,7 +237,7 @@ def test_run_success(
237237 self .mock_kubernetes .delete_pod .assert_called_once ()
238238
239239 @patch (f"{ MODULE } .clean_tc_vmi_chaos" )
240- @patch (f"{ MODULE } .apply_tc_vmi_chaos" )
240+ @patch (f"{ MODULE } .apply_tc_vmi_chaos" , return_value = ([], []) )
241241 @patch (f"{ MODULE } .get_vmi_tap_interface" , return_value = "tap0" )
242242 @patch (f"{ MODULE } .find_virt_launcher_netns_pid" , return_value = "101" )
243243 @patch (f"{ MODULE } .deploy_network_chaos_ng_pod" )
@@ -352,7 +352,7 @@ def test_run_strips_container_id_prefix(self, mock_log, mock_deploy, mock_find):
352352 self .mock_kubernetes .get_pod_pids .return_value = ["100" ]
353353
354354 with patch (f"{ MODULE } .get_vmi_tap_interface" , return_value = "tap0" ), \
355- patch (f"{ MODULE } .apply_tc_vmi_chaos" ), \
355+ patch (f"{ MODULE } .apply_tc_vmi_chaos" , return_value = ([], []) ), \
356356 patch (f"{ MODULE } .clean_tc_vmi_chaos" ), \
357357 patch (f"{ MODULE } .time.sleep" ):
358358 self .module .run ("virt-density-udn-3/virt-server-3" )
@@ -395,7 +395,7 @@ def test_run_error_queue_captures_exception(self, mock_log, mock_deploy):
395395 self .assertIn ("not found" , error_queue .get ())
396396
397397 @patch (f"{ MODULE } .clean_tc_vmi_chaos" )
398- @patch (f"{ MODULE } .apply_tc_vmi_chaos" )
398+ @patch (f"{ MODULE } .apply_tc_vmi_chaos" , return_value = ([], []) )
399399 @patch (f"{ MODULE } .get_vmi_tap_interface" , return_value = "tap0" )
400400 @patch (f"{ MODULE } .find_virt_launcher_netns_pid" , return_value = "101" )
401401 @patch (f"{ MODULE } .deploy_network_chaos_ng_pod" )
@@ -412,7 +412,7 @@ def test_run_no_error_queue_raises_directly(
412412 # ------------------------------------------------------------------ apply / clean called correctly
413413
414414 @patch (f"{ MODULE } .clean_tc_vmi_chaos" )
415- @patch (f"{ MODULE } .apply_tc_vmi_chaos" )
415+ @patch (f"{ MODULE } .apply_tc_vmi_chaos" , return_value = ([], []) )
416416 @patch (f"{ MODULE } .get_vmi_tap_interface" , return_value = "tap0" )
417417 @patch (f"{ MODULE } .find_virt_launcher_netns_pid" , return_value = "101" )
418418 @patch (f"{ MODULE } .deploy_network_chaos_ng_pod" )
@@ -432,7 +432,7 @@ def test_run_apply_and_clean_called_with_tap_and_pid(
432432 self .assertEqual (clean_args [4 ], "tap0" ) # iface
433433
434434 @patch (f"{ MODULE } .clean_tc_vmi_chaos" )
435- @patch (f"{ MODULE } .apply_tc_vmi_chaos" )
435+ @patch (f"{ MODULE } .apply_tc_vmi_chaos" , return_value = ([], []) )
436436 @patch (f"{ MODULE } .get_vmi_tap_interface" , return_value = "tap0" )
437437 @patch (f"{ MODULE } .find_virt_launcher_netns_pid" , return_value = "101" )
438438 @patch (f"{ MODULE } .deploy_network_chaos_ng_pod" )
@@ -479,21 +479,31 @@ def setUp(self):
479479 # ------------------------------------------------------------------ _rollback directly
480480
481481 def test_rollback_calls_clean_then_delete_when_tc_applied (self ):
482+ input_rules = ["nsenter ... iptables -I INPUT 1 -i tap0 -p tcp -j DROP" ]
483+ output_rules = ["nsenter ... iptables -I OUTPUT 1 -p tcp -j DROP" ]
482484 with patch (f"{ MODULE } .clean_tc_vmi_chaos" ) as mock_clean :
483- self .module ._rollback ("ns" , "chaos-pod" , "101" , "tap0" )
485+ self .module ._rollback ("ns" , "chaos-pod" , "101" , "tap0" , input_rules , output_rules )
484486
485487 mock_clean .assert_called_once_with (
486- self .mock_kubernetes , "chaos-pod" , "ns" , "101" , "tap0"
488+ self .mock_kubernetes , "chaos-pod" , "ns" , "101" , "tap0" , input_rules , output_rules
487489 )
488490 self .mock_kubernetes .delete_pod .assert_called_once_with ("chaos-pod" , "ns" )
489491
490- def test_rollback_skips_clean_when_tc_not_applied (self ):
492+ def test_rollback_skips_clean_when_no_rules (self ):
491493 with patch (f"{ MODULE } .clean_tc_vmi_chaos" ) as mock_clean :
492494 self .module ._rollback ("ns" , "chaos-pod" )
493495
494496 mock_clean .assert_not_called ()
495497 self .mock_kubernetes .delete_pod .assert_called_once_with ("chaos-pod" , "ns" )
496498
499+ def test_rollback_skips_clean_when_rules_none_but_pid_set (self ):
500+ """Chaos pod deployed, netns_pid found, but rules never applied: skip clean."""
501+ with patch (f"{ MODULE } .clean_tc_vmi_chaos" ) as mock_clean :
502+ self .module ._rollback ("ns" , "chaos-pod" , "101" , "tap0" , None , None )
503+
504+ mock_clean .assert_not_called ()
505+ self .mock_kubernetes .delete_pod .assert_called_once_with ("chaos-pod" , "ns" )
506+
497507 # ------------------------------------------------------------------ rollback from run: error before tc
498508
499509 @patch (f"{ MODULE } .clean_tc_vmi_chaos" )
@@ -528,7 +538,7 @@ def test_run_rollback_does_not_clean_when_no_netns_pid(
528538 # ------------------------------------------------------------------ rollback from run: error after tc
529539
530540 @patch (f"{ MODULE } .clean_tc_vmi_chaos" )
531- @patch (f"{ MODULE } .apply_tc_vmi_chaos" )
541+ @patch (f"{ MODULE } .apply_tc_vmi_chaos" , return_value = ([ "in_rule" ], [ "out_rule" ]) )
532542 @patch (f"{ MODULE } .get_vmi_tap_interface" , return_value = "tap0" )
533543 @patch (f"{ MODULE } .find_virt_launcher_netns_pid" , return_value = "101" )
534544 @patch (f"{ MODULE } .deploy_network_chaos_ng_pod" )
@@ -547,24 +557,103 @@ def test_run_rollback_cleans_tc_and_deletes_pod_on_error_after_tc(
547557 self .mock_kubernetes .delete_pod .assert_called_once ()
548558
549559 @patch (f"{ MODULE } .clean_tc_vmi_chaos" )
550- @patch (f"{ MODULE } .apply_tc_vmi_chaos" )
560+ @patch (f"{ MODULE } .apply_tc_vmi_chaos" , return_value = ([ "in_rule" ], [ "out_rule" ]) )
551561 @patch (f"{ MODULE } .get_vmi_tap_interface" , return_value = "tap0" )
552562 @patch (f"{ MODULE } .find_virt_launcher_netns_pid" , return_value = "101" )
553563 @patch (f"{ MODULE } .deploy_network_chaos_ng_pod" )
554564 @patch (f"{ MODULE } .time.sleep" )
555565 @patch (f"{ MODULE } .log_info" )
556- def test_run_rollback_passes_correct_pid_and_iface_to_clean (
566+ def test_run_rollback_passes_correct_pid_iface_and_rules_to_clean (
557567 self , mock_log , mock_sleep , mock_deploy , mock_find , mock_tap , mock_apply , mock_clean
558568 ):
559- """Clean must receive the resolved netns_pid and iface, not defaults ."""
569+ """Clean must receive the resolved netns_pid, iface, and the rules returned by apply ."""
560570 mock_sleep .side_effect = RuntimeError ("interrupted" )
561571
562572 with self .assertRaises (RuntimeError ):
563573 self .module .run ("virt-density-udn-3/virt-server-3" )
564574
565575 clean_args = mock_clean .call_args [0 ]
566- self .assertEqual (clean_args [3 ], "101" ) # netns_pid
567- self .assertEqual (clean_args [4 ], "tap0" ) # iface
576+ self .assertEqual (clean_args [3 ], "101" ) # netns_pid
577+ self .assertEqual (clean_args [4 ], "tap0" ) # iface
578+ self .assertEqual (clean_args [5 ], ["in_rule" ]) # input_rules from apply
579+ self .assertEqual (clean_args [6 ], ["out_rule" ]) # output_rules from apply
580+
581+
582+ class TestVmiNetworkFilterPortsProtocols (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 .get_vmi .return_value = {"status" : {"nodeName" : "worker-1" }}
590+ self .mock_kubernetes .list_pods .return_value = ["virt-launcher-virt-server-3-abc12" ]
591+ compute = _make_container ("compute" , ready = True , container_id = "containerd://deadbeef" )
592+ mock_pod_info = MagicMock ()
593+ mock_pod_info .containers = [compute ]
594+ self .mock_kubernetes .get_pod_info .return_value = mock_pod_info
595+ self .mock_kubernetes .get_pod_pids .return_value = ["100" , "101" , "102" ]
596+
597+ def _run_and_capture_apply (self , ** config_overrides ):
598+ config = _make_config (** config_overrides )
599+ module = VmiNetworkFilterModule (config , self .mock_kubecli )
600+ with patch (f"{ MODULE } .deploy_network_chaos_ng_pod" ), \
601+ patch (f"{ MODULE } .find_virt_launcher_netns_pid" , return_value = "101" ), \
602+ patch (f"{ MODULE } .get_vmi_tap_interface" , return_value = "tap0" ), \
603+ patch (f"{ MODULE } .apply_tc_vmi_chaos" , return_value = ([], [])) as mock_apply , \
604+ patch (f"{ MODULE } .clean_tc_vmi_chaos" ), \
605+ patch (f"{ MODULE } .time.sleep" ), \
606+ patch (f"{ MODULE } .log_info" ):
607+ module .run ("virt-density-udn-3/virt-server-3" )
608+ return mock_apply
609+
610+ def test_apply_receives_specific_ports (self ):
611+ mock_apply = self ._run_and_capture_apply (ports = [53 , 80 , 443 ])
612+ config_arg = mock_apply .call_args [0 ][5 ]
613+ self .assertEqual (config_arg .ports , [53 , 80 , 443 ])
614+
615+ def test_apply_receives_empty_ports_for_all_traffic (self ):
616+ mock_apply = self ._run_and_capture_apply (ports = [])
617+ config_arg = mock_apply .call_args [0 ][5 ]
618+ self .assertEqual (config_arg .ports , [])
619+
620+ def test_apply_receives_tcp_only_protocol (self ):
621+ mock_apply = self ._run_and_capture_apply (protocols = ["tcp" ])
622+ config_arg = mock_apply .call_args [0 ][5 ]
623+ self .assertEqual (config_arg .protocols , ["tcp" ])
624+
625+ def test_apply_receives_udp_only_protocol (self ):
626+ mock_apply = self ._run_and_capture_apply (protocols = ["udp" ])
627+ config_arg = mock_apply .call_args [0 ][5 ]
628+ self .assertEqual (config_arg .protocols , ["udp" ])
629+
630+ def test_apply_receives_both_protocols (self ):
631+ mock_apply = self ._run_and_capture_apply (protocols = ["tcp" , "udp" ])
632+ config_arg = mock_apply .call_args [0 ][5 ]
633+ self .assertIn ("tcp" , config_arg .protocols )
634+ self .assertIn ("udp" , config_arg .protocols )
635+
636+ def test_apply_receives_dns_ports_with_both_protocols (self ):
637+ """DNS blackout: port 53 on tcp and udp."""
638+ mock_apply = self ._run_and_capture_apply (ports = [53 ], protocols = ["tcp" , "udp" ])
639+ config_arg = mock_apply .call_args [0 ][5 ]
640+ self .assertEqual (config_arg .ports , [53 ])
641+ self .assertIn ("tcp" , config_arg .protocols )
642+ self .assertIn ("udp" , config_arg .protocols )
643+
644+ def test_apply_receives_management_ports (self ):
645+ """Management plane loss: SSH + HTTPS + k8s API."""
646+ mock_apply = self ._run_and_capture_apply (ports = [22 , 443 , 6443 ], protocols = ["tcp" ])
647+ config_arg = mock_apply .call_args [0 ][5 ]
648+ self .assertEqual (config_arg .ports , [22 , 443 , 6443 ])
649+ self .assertEqual (config_arg .protocols , ["tcp" ])
650+
651+ def test_apply_config_has_resolved_namespace_not_regex (self ):
652+ """The config passed to apply must use the real namespace from the target string."""
653+ mock_apply = self ._run_and_capture_apply (namespace = "virt-density-udn-.*" )
654+ config_arg = mock_apply .call_args [0 ][5 ]
655+ self .assertEqual (config_arg .namespace , "virt-density-udn-3" )
656+ self .assertNotEqual (config_arg .namespace , "virt-density-udn-.*" )
568657
569658
570659if __name__ == "__main__" :
0 commit comments