Files
krkn/tests/test_node_network_chaos.py
Tullio Sebastiani cebc60f5a8 Network chaos NG porting - pod network chaos node network chaos (#991)
* fix ibm

Signed-off-by: Paige Patton <prubenda@redhat.com>

* type hint fix

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

* pod network chaos plugin structure + utils method refactoring

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

* Pod network chaos plugin

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

* Node network chaos plugin

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

* default config files

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

* config.yaml

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

* all field optional

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

* minor fixes

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

* minor nit on config

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

* utils unit tests

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

* PodNetworkChaos unit tests

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

* NodeNetworkChaos unit test

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

* PodNetworkChaos functional test

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

* NodeNetworkChaso functional test

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

* added funtests to the gh action

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

* unit test fix

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

* changed test order + resource rename

* functional tests fix

smallchange

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

fix requirements

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

* changed pod test target

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

* added kind port mapping and removed portforwarding

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

fix

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

test fixes

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

test fixes

Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>

---------

Signed-off-by: Paige Patton <prubenda@redhat.com>
Signed-off-by: Tullio Sebastiani <tsebasti@redhat.com>
Co-authored-by: Paige Patton <prubenda@redhat.com>
2026-02-18 16:20:16 +01:00

492 lines
17 KiB
Python

#!/usr/bin/env python3
"""
Test suite for NodeNetworkChaosModule class
Usage:
python -m coverage run -a -m unittest tests/test_node_network_chaos.py -v
Assisted By: Claude Code
"""
import unittest
import queue
from unittest.mock import MagicMock, patch, call
from krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos import (
NodeNetworkChaosModule,
)
from krkn.scenario_plugins.network_chaos_ng.models import (
NetworkChaosConfig,
NetworkChaosScenarioType,
)
class TestNodeNetworkChaosModule(unittest.TestCase):
def setUp(self):
"""
Set up test fixtures for NodeNetworkChaosModule
"""
self.mock_kubecli = MagicMock()
self.mock_kubernetes = MagicMock()
self.mock_kubecli.get_lib_kubernetes.return_value = self.mock_kubernetes
self.config = NetworkChaosConfig(
id="test-node-network-chaos",
image="test-image",
wait_duration=1,
test_duration=30,
label_selector="",
service_account="",
taints=[],
namespace="default",
instance_count=1,
target="worker-1",
execution="parallel",
interfaces=["eth0"],
ingress=True,
egress=True,
latency="100ms",
loss="10",
bandwidth="100mbit",
force=False,
)
self.module = NodeNetworkChaosModule(self.config, self.mock_kubecli)
def test_initialization(self):
"""
Test NodeNetworkChaosModule initialization
"""
self.assertEqual(self.module.config, self.config)
self.assertEqual(self.module.kubecli, self.mock_kubecli)
self.assertEqual(self.module.base_network_config, self.config)
def test_get_config(self):
"""
Test get_config returns correct scenario type and config
"""
scenario_type, config = self.module.get_config()
self.assertEqual(scenario_type, NetworkChaosScenarioType.Node)
self.assertEqual(config, self.config)
def test_get_targets_with_target_name(self):
"""
Test get_targets with specific node target name
"""
self.config.label_selector = ""
self.config.target = "worker-1"
self.mock_kubernetes.list_nodes.return_value = ["worker-1", "worker-2"]
targets = self.module.get_targets()
self.assertEqual(targets, ["worker-1"])
def test_get_targets_with_label_selector(self):
"""
Test get_targets with label selector
"""
self.config.label_selector = "node-role.kubernetes.io/worker="
self.mock_kubernetes.list_nodes.return_value = ["worker-1", "worker-2"]
targets = self.module.get_targets()
self.assertEqual(targets, ["worker-1", "worker-2"])
self.mock_kubernetes.list_nodes.assert_called_once_with(
"node-role.kubernetes.io/worker="
)
def test_get_targets_node_not_found(self):
"""
Test get_targets raises exception when node doesn't exist
"""
self.config.label_selector = ""
self.config.target = "non-existent-node"
self.mock_kubernetes.list_nodes.return_value = ["worker-1", "worker-2"]
with self.assertRaises(Exception) as context:
self.module.get_targets()
self.assertIn("not found", str(context.exception))
def test_get_targets_no_target_or_selector(self):
"""
Test get_targets raises exception when neither target nor selector specified
"""
self.config.label_selector = ""
self.config.target = ""
with self.assertRaises(Exception) as context:
self.module.get_targets()
self.assertIn("neither", str(context.exception))
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.node_qdisc_is_simple"
)
@patch("krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.time.sleep")
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.common_delete_limit_rules"
)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.common_set_limit_rules"
)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.setup_network_chaos_ng_scenario"
)
@patch("krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.log_info")
def test_run_success(
self,
mock_log_info,
mock_setup,
mock_set_rules,
mock_delete_rules,
mock_sleep,
mock_qdisc_is_simple,
):
"""
Test successful run of node network chaos
"""
# Mock setup returns container_ids and interfaces
mock_setup.return_value = (["container-123"], ["eth0"])
# Mock qdisc check - simple qdisc
mock_qdisc_is_simple.return_value = True
self.module.run("worker-1")
# Verify setup was called with node name
mock_setup.assert_called_once()
setup_args = mock_setup.call_args[0]
# Node name should be passed as target and is_node=True (8th arg, index 7)
self.assertEqual(setup_args[7], True) # is_node flag
# Verify qdisc was checked
mock_qdisc_is_simple.assert_called_once()
# Verify tc rules were set (with pids=None for node scenario)
mock_set_rules.assert_called_once()
set_call_args = mock_set_rules.call_args
# pids should be None (last argument)
self.assertIsNone(set_call_args[0][-1])
# Verify sleep for test duration
mock_sleep.assert_called_once_with(30)
# Verify tc rules were deleted
mock_delete_rules.assert_called_once()
delete_call_args = mock_delete_rules.call_args
# pids should be None (7th argument, index 6)
self.assertIsNone(delete_call_args[0][6])
# Verify cleanup pod was deleted
self.assertEqual(self.mock_kubernetes.delete_pod.call_count, 1)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.node_qdisc_is_simple"
)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.setup_network_chaos_ng_scenario"
)
@patch("krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.log_error")
@patch("krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.log_info")
def test_run_no_interfaces_detected(
self, mock_log_info, mock_log_error, mock_setup, mock_qdisc_is_simple
):
"""
Test run handles case when no network interfaces detected
"""
# Mock setup returns empty interfaces
mock_setup.return_value = (["container-123"], [])
# Set config to auto-detect interfaces
self.config.interfaces = []
self.module.run("worker-1")
# Verify error was logged
mock_log_error.assert_called()
self.assertIn("no network interface", str(mock_log_error.call_args))
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.node_qdisc_is_simple"
)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.setup_network_chaos_ng_scenario"
)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.log_warning"
)
@patch("krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.log_info")
def test_run_complex_qdisc_without_force(
self, mock_log_info, mock_log_warning, mock_setup, mock_qdisc_is_simple
):
"""
Test run skips chaos when complex qdisc exists and force=False
"""
# Mock setup
mock_setup.return_value = (["container-123"], ["eth0"])
# Mock qdisc check - complex qdisc
mock_qdisc_is_simple.return_value = False
# force is False
self.config.force = False
self.module.run("worker-1")
# Verify warning was logged
mock_log_warning.assert_called()
self.assertIn("already has tc rules", str(mock_log_warning.call_args))
# Verify cleanup pod was still deleted
self.assertEqual(self.mock_kubernetes.delete_pod.call_count, 1)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.node_qdisc_is_simple"
)
@patch("krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.time.sleep")
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.common_delete_limit_rules"
)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.common_set_limit_rules"
)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.setup_network_chaos_ng_scenario"
)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.log_warning"
)
@patch("krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.log_info")
def test_run_complex_qdisc_with_force(
self,
mock_log_info,
mock_log_warning,
mock_setup,
mock_set_rules,
mock_delete_rules,
mock_sleep,
mock_qdisc_is_simple,
):
"""
Test run proceeds with chaos when complex qdisc exists and force=True
"""
# Mock setup
mock_setup.return_value = (["container-123"], ["eth0"])
# Mock qdisc check - complex qdisc
mock_qdisc_is_simple.return_value = False
# force is True
self.config.force = True
self.module.run("worker-1")
# Verify warning was logged about forcing
mock_log_warning.assert_called()
self.assertIn("forcing", str(mock_log_warning.call_args))
# Verify sleep for safety warning (10 seconds)
sleep_calls = [call[0][0] for call in mock_sleep.call_args_list]
self.assertIn(10, sleep_calls)
# Verify tc rules were set
mock_set_rules.assert_called_once()
# Verify tc rules were deleted
mock_delete_rules.assert_called_once()
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.node_qdisc_is_simple"
)
@patch("krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.time.sleep")
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.common_delete_limit_rules"
)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.common_set_limit_rules"
)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.setup_network_chaos_ng_scenario"
)
@patch("krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.log_info")
def test_run_uses_configured_interfaces(
self,
mock_log_info,
mock_setup,
mock_set_rules,
mock_delete_rules,
mock_sleep,
mock_qdisc_is_simple,
):
"""
Test run uses configured interfaces instead of detected ones
"""
# Mock setup returns different interfaces
mock_setup.return_value = (["container-123"], ["eth0", "eth1"])
# Mock qdisc check - simple qdisc
mock_qdisc_is_simple.return_value = True
# Set specific interfaces in config
self.config.interfaces = ["eth2"]
self.module.run("worker-1")
# Verify set_rules was called with configured interfaces
call_args = mock_set_rules.call_args
# interfaces is the 3rd positional argument (index 2)
self.assertEqual(call_args[0][2], ["eth2"])
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.node_qdisc_is_simple"
)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.setup_network_chaos_ng_scenario"
)
@patch("krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.log_info")
def test_run_with_error_queue(
self, mock_log_info, mock_setup, mock_qdisc_is_simple
):
"""
Test run with error_queue for parallel execution
"""
# Mock setup to raise exception
mock_setup.side_effect = Exception("Test error")
error_queue = queue.Queue()
self.module.run("worker-1", error_queue)
# Verify error was put in queue instead of raising
self.assertFalse(error_queue.empty())
error = error_queue.get()
self.assertEqual(error, "Test error")
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.node_qdisc_is_simple"
)
@patch("krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.time.sleep")
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.common_delete_limit_rules"
)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.common_set_limit_rules"
)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.setup_network_chaos_ng_scenario"
)
@patch("krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.log_info")
def test_run_ingress_egress_flags(
self,
mock_log_info,
mock_setup,
mock_set_rules,
mock_delete_rules,
mock_sleep,
mock_qdisc_is_simple,
):
"""
Test run passes ingress and egress flags correctly
"""
# Mock setup
mock_setup.return_value = (["container-123"], ["eth0"])
# Mock qdisc check
mock_qdisc_is_simple.return_value = True
# Set specific ingress/egress config
self.config.ingress = False
self.config.egress = True
self.module.run("worker-1")
# Verify set_rules was called with correct egress/ingress flags
set_call_args = mock_set_rules.call_args
# egress is 1st arg (index 0), ingress is 2nd arg (index 1)
self.assertEqual(set_call_args[0][0], True) # egress
self.assertEqual(set_call_args[0][1], False) # ingress
# Verify delete_rules was called with correct flags
delete_call_args = mock_delete_rules.call_args
self.assertEqual(delete_call_args[0][0], True) # egress
self.assertEqual(delete_call_args[0][1], False) # ingress
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.node_qdisc_is_simple"
)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.setup_network_chaos_ng_scenario"
)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.log_warning"
)
@patch("krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.log_info")
def test_run_mixed_simple_and_complex_qdisc(
self, mock_log_info, mock_log_warning, mock_setup, mock_qdisc_is_simple
):
"""
Test run with multiple interfaces where some have complex qdisc
"""
# Mock setup with multiple interfaces
mock_setup.return_value = (["container-123"], ["eth0", "eth1"])
# Set config to use detected interfaces
self.config.interfaces = []
self.config.force = False
# Mock qdisc check - eth0 simple, eth1 complex
mock_qdisc_is_simple.side_effect = [True, False]
self.module.run("worker-1")
# Verify warning about complex qdisc on eth1
mock_log_warning.assert_called()
warning_message = str(mock_log_warning.call_args)
self.assertIn("already has tc rules", warning_message)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.node_qdisc_is_simple"
)
@patch("krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.time.sleep")
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.common_delete_limit_rules"
)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.common_set_limit_rules"
)
@patch(
"krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.setup_network_chaos_ng_scenario"
)
@patch("krkn.scenario_plugins.network_chaos_ng.modules.node_network_chaos.log_info")
def test_run_checks_qdisc_for_all_interfaces(
self,
mock_log_info,
mock_setup,
mock_set_rules,
mock_delete_rules,
mock_sleep,
mock_qdisc_is_simple,
):
"""
Test run checks qdisc for all interfaces
"""
# Mock setup with multiple interfaces
mock_setup.return_value = (["container-123"], ["eth0", "eth1", "eth2"])
# Set config to use detected interfaces
self.config.interfaces = []
# All interfaces simple
mock_qdisc_is_simple.return_value = True
self.module.run("worker-1")
# Verify qdisc was checked for all 3 interfaces
self.assertEqual(mock_qdisc_is_simple.call_count, 3)
if __name__ == "__main__":
unittest.main()