mirror of
https://github.com/krkn-chaos/krkn.git
synced 2026-02-14 18:10:00 +00:00
adding test server (#982)
Some checks failed
Functional & Unit Tests / Functional & Unit Tests (push) Failing after 4m2s
Functional & Unit Tests / Generate Coverage Badge (push) Has been skipped
Some checks failed
Functional & Unit Tests / Functional & Unit Tests (push) Failing after 4m2s
Functional & Unit Tests / Generate Coverage Badge (push) Has been skipped
Signed-off-by: Paige Patton <prubenda@redhat.com>
This commit is contained in:
6
.github/workflows/tests.yml
vendored
6
.github/workflows/tests.yml
vendored
@@ -79,8 +79,6 @@ jobs:
|
||||
if: |
|
||||
github.event_name == 'pull_request'
|
||||
run: |
|
||||
yq -i '.kraken.port="8081"' CI/config/common_test_config.yaml
|
||||
yq -i '.kraken.signal_address="0.0.0.0"' CI/config/common_test_config.yaml
|
||||
yq -i '.kraken.performance_monitoring="localhost:9090"' CI/config/common_test_config.yaml
|
||||
yq -i '.elastic.elastic_port=9200' CI/config/common_test_config.yaml
|
||||
yq -i '.elastic.elastic_url="https://localhost"' CI/config/common_test_config.yaml
|
||||
@@ -100,6 +98,7 @@ jobs:
|
||||
echo "test_memory_hog" >> ./CI/tests/functional_tests
|
||||
echo "test_io_hog" >> ./CI/tests/functional_tests
|
||||
echo "test_pod_network_filter" >> ./CI/tests/functional_tests
|
||||
echo "test_pod_server" >> ./CI/tests/functional_tests
|
||||
|
||||
# Push on main only steps + all other functional to collect coverage
|
||||
# for the badge
|
||||
@@ -113,8 +112,6 @@ jobs:
|
||||
- name: Setup Post Merge Request Functional Tests
|
||||
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
||||
run: |
|
||||
yq -i '.kraken.port="8081"' CI/config/common_test_config.yaml
|
||||
yq -i '.kraken.signal_address="0.0.0.0"' CI/config/common_test_config.yaml
|
||||
yq -i '.kraken.performance_monitoring="localhost:9090"' CI/config/common_test_config.yaml
|
||||
yq -i '.elastic.enable_elastic=False' CI/config/common_test_config.yaml
|
||||
yq -i '.elastic.password="${{env.ELASTIC_PASSWORD}}"' CI/config/common_test_config.yaml
|
||||
@@ -137,6 +134,7 @@ jobs:
|
||||
echo "test_memory_hog" >> ./CI/tests/functional_tests
|
||||
echo "test_io_hog" >> ./CI/tests/functional_tests
|
||||
echo "test_pod_network_filter" >> ./CI/tests/functional_tests
|
||||
echo "test_pod_server" >> ./CI/tests/functional_tests
|
||||
|
||||
# Final common steps
|
||||
- name: Run Functional tests
|
||||
|
||||
@@ -2,6 +2,10 @@ kraken:
|
||||
distribution: kubernetes # Distribution can be kubernetes or openshift.
|
||||
kubeconfig_path: ~/.kube/config # Path to kubeconfig.
|
||||
exit_on_failure: False # Exit when a post action scenario fails.
|
||||
publish_kraken_status: True # Can be accessed at http://0.0.0.0:8081
|
||||
signal_state: RUN # Will wait for the RUN signal when set to PAUSE before running the scenarios, refer docs/signal.md for more details
|
||||
signal_address: 0.0.0.0 # Signal listening address
|
||||
port: 8081 # Signal port
|
||||
auto_rollback: True # Enable auto rollback for scenarios.
|
||||
rollback_versions_directory: /tmp/kraken-rollback # Directory to store rollback version files.
|
||||
chaos_scenarios: # List of policies/chaos scenarios to load.
|
||||
|
||||
35
CI/tests/test_pod_server.sh
Executable file
35
CI/tests/test_pod_server.sh
Executable file
@@ -0,0 +1,35 @@
|
||||
set -xeEo pipefail
|
||||
|
||||
source CI/tests/common.sh
|
||||
|
||||
trap error ERR
|
||||
trap finish EXIT
|
||||
|
||||
function functional_test_pod_server {
|
||||
export scenario_type="pod_disruption_scenarios"
|
||||
export scenario_file="scenarios/kind/pod_etcd.yml"
|
||||
export post_config=""
|
||||
|
||||
envsubst < CI/config/common_test_config.yaml > CI/config/pod_config.yaml
|
||||
yq -i '.[0].config.kill=1' scenarios/kind/pod_etcd.yml
|
||||
|
||||
yq -i '.tunings.daemon_mode=True' CI/config/pod_config.yaml
|
||||
cat CI/config/pod_config.yaml
|
||||
python3 -m coverage run -a run_kraken.py -c CI/config/pod_config.yaml &
|
||||
sleep 15
|
||||
curl -X POST http:/0.0.0.0:8081/STOP
|
||||
|
||||
wait
|
||||
|
||||
yq -i '.kraken.signal_state="PAUSE"' CI/config/pod_config.yaml
|
||||
yq -i '.tunings.daemon_mode=False' CI/config/pod_config.yaml
|
||||
cat CI/config/pod_config.yaml
|
||||
python3 -m coverage run -a run_kraken.py -c CI/config/pod_config.yaml &
|
||||
sleep 5
|
||||
curl -X POST http:/0.0.0.0:8081/RUN
|
||||
wait
|
||||
|
||||
echo "Pod disruption with server scenario test: Success"
|
||||
}
|
||||
|
||||
functional_test_pod_server
|
||||
385
tests/test_server.py
Normal file
385
tests/test_server.py
Normal file
@@ -0,0 +1,385 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
"""
|
||||
Test suite for SimpleHTTPRequestHandler class
|
||||
|
||||
Usage:
|
||||
python -m coverage run -a -m unittest tests/test_server.py -v
|
||||
|
||||
Assisted By: Claude Code
|
||||
"""
|
||||
|
||||
import unittest
|
||||
from unittest.mock import Mock, patch, MagicMock
|
||||
from io import BytesIO
|
||||
|
||||
import server
|
||||
from server import SimpleHTTPRequestHandler
|
||||
|
||||
|
||||
class TestSimpleHTTPRequestHandler(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Set up test fixtures for SimpleHTTPRequestHandler
|
||||
"""
|
||||
# Reset the global server_status before each test
|
||||
server.server_status = ""
|
||||
# Reset the requests_served counter
|
||||
SimpleHTTPRequestHandler.requests_served = 0
|
||||
|
||||
# Create a mock request
|
||||
self.mock_request = MagicMock()
|
||||
self.mock_client_address = ('127.0.0.1', 12345)
|
||||
self.mock_server = MagicMock()
|
||||
|
||||
def _create_handler(self, method='GET', path='/'):
|
||||
"""
|
||||
Helper method to create a handler instance with mocked request
|
||||
"""
|
||||
# Create a mock request with proper attributes
|
||||
mock_request = MagicMock()
|
||||
mock_request.makefile.return_value = BytesIO(
|
||||
f"{method} {path} HTTP/1.1\r\n\r\n".encode('utf-8')
|
||||
)
|
||||
|
||||
# Create handler
|
||||
handler = SimpleHTTPRequestHandler(
|
||||
mock_request,
|
||||
self.mock_client_address,
|
||||
self.mock_server
|
||||
)
|
||||
|
||||
# Mock the wfile (write file) for response
|
||||
handler.wfile = BytesIO()
|
||||
|
||||
return handler
|
||||
|
||||
def test_do_GET_root_path_calls_do_status(self):
|
||||
"""
|
||||
Test do_GET with root path calls do_status
|
||||
"""
|
||||
handler = self._create_handler('GET', '/')
|
||||
|
||||
with patch.object(handler, 'do_status') as mock_do_status:
|
||||
handler.do_GET()
|
||||
mock_do_status.assert_called_once()
|
||||
|
||||
def test_do_GET_non_root_path_does_nothing(self):
|
||||
"""
|
||||
Test do_GET with non-root path does not call do_status
|
||||
"""
|
||||
handler = self._create_handler('GET', '/other')
|
||||
|
||||
with patch.object(handler, 'do_status') as mock_do_status:
|
||||
handler.do_GET()
|
||||
mock_do_status.assert_not_called()
|
||||
|
||||
def test_do_status_sends_200_response(self):
|
||||
"""
|
||||
Test do_status sends 200 status code
|
||||
"""
|
||||
server.server_status = "TEST_STATUS"
|
||||
handler = self._create_handler()
|
||||
|
||||
with patch.object(handler, 'send_response') as mock_send_response:
|
||||
with patch.object(handler, 'end_headers'):
|
||||
handler.do_status()
|
||||
mock_send_response.assert_called_once_with(200)
|
||||
|
||||
def test_do_status_writes_server_status(self):
|
||||
"""
|
||||
Test do_status writes server_status to response
|
||||
"""
|
||||
server.server_status = "RUNNING"
|
||||
handler = self._create_handler()
|
||||
|
||||
with patch.object(handler, 'send_response'):
|
||||
with patch.object(handler, 'end_headers'):
|
||||
handler.do_status()
|
||||
|
||||
# Check that the status was written to wfile
|
||||
response_content = handler.wfile.getvalue().decode('utf-8')
|
||||
self.assertEqual(response_content, "RUNNING")
|
||||
|
||||
def test_do_status_increments_requests_served(self):
|
||||
"""
|
||||
Test do_status increments requests_served counter
|
||||
"""
|
||||
# Note: Creating a handler increments the counter by 1
|
||||
# Then do_status increments it again
|
||||
SimpleHTTPRequestHandler.requests_served = 0
|
||||
handler = self._create_handler()
|
||||
initial_count = SimpleHTTPRequestHandler.requests_served
|
||||
|
||||
with patch.object(handler, 'send_response'):
|
||||
with patch.object(handler, 'end_headers'):
|
||||
handler.do_status()
|
||||
|
||||
self.assertEqual(
|
||||
SimpleHTTPRequestHandler.requests_served,
|
||||
initial_count + 1
|
||||
)
|
||||
|
||||
def test_do_status_multiple_requests_increment_counter(self):
|
||||
"""
|
||||
Test multiple do_status calls increment counter correctly
|
||||
"""
|
||||
SimpleHTTPRequestHandler.requests_served = 0
|
||||
|
||||
for i in range(5):
|
||||
handler = self._create_handler()
|
||||
with patch.object(handler, 'send_response'):
|
||||
with patch.object(handler, 'end_headers'):
|
||||
handler.do_status()
|
||||
|
||||
# Each iteration: handler creation increments by 1, do_status increments by 1
|
||||
# Total: 5 * 2 = 10
|
||||
self.assertEqual(SimpleHTTPRequestHandler.requests_served, 10)
|
||||
|
||||
def test_do_POST_STOP_path_calls_set_stop(self):
|
||||
"""
|
||||
Test do_POST with /STOP path calls set_stop
|
||||
"""
|
||||
handler = self._create_handler('POST', '/STOP')
|
||||
|
||||
with patch.object(handler, 'set_stop') as mock_set_stop:
|
||||
handler.do_POST()
|
||||
mock_set_stop.assert_called_once()
|
||||
|
||||
def test_do_POST_RUN_path_calls_set_run(self):
|
||||
"""
|
||||
Test do_POST with /RUN path calls set_run
|
||||
"""
|
||||
handler = self._create_handler('POST', '/RUN')
|
||||
|
||||
with patch.object(handler, 'set_run') as mock_set_run:
|
||||
handler.do_POST()
|
||||
mock_set_run.assert_called_once()
|
||||
|
||||
def test_do_POST_PAUSE_path_calls_set_pause(self):
|
||||
"""
|
||||
Test do_POST with /PAUSE path calls set_pause
|
||||
"""
|
||||
handler = self._create_handler('POST', '/PAUSE')
|
||||
|
||||
with patch.object(handler, 'set_pause') as mock_set_pause:
|
||||
handler.do_POST()
|
||||
mock_set_pause.assert_called_once()
|
||||
|
||||
def test_do_POST_unknown_path_does_nothing(self):
|
||||
"""
|
||||
Test do_POST with unknown path does not call any setter
|
||||
"""
|
||||
handler = self._create_handler('POST', '/UNKNOWN')
|
||||
|
||||
with patch.object(handler, 'set_stop') as mock_set_stop:
|
||||
with patch.object(handler, 'set_run') as mock_set_run:
|
||||
with patch.object(handler, 'set_pause') as mock_set_pause:
|
||||
handler.do_POST()
|
||||
mock_set_stop.assert_not_called()
|
||||
mock_set_run.assert_not_called()
|
||||
mock_set_pause.assert_not_called()
|
||||
|
||||
def test_set_run_sets_status_to_RUN(self):
|
||||
"""
|
||||
Test set_run sets global server_status to 'RUN'
|
||||
"""
|
||||
handler = self._create_handler()
|
||||
|
||||
with patch.object(handler, 'send_response'):
|
||||
with patch.object(handler, 'end_headers'):
|
||||
handler.set_run()
|
||||
|
||||
self.assertEqual(server.server_status, 'RUN')
|
||||
|
||||
def test_set_run_sends_200_response(self):
|
||||
"""
|
||||
Test set_run sends 200 status code
|
||||
"""
|
||||
handler = self._create_handler()
|
||||
|
||||
with patch.object(handler, 'send_response') as mock_send_response:
|
||||
with patch.object(handler, 'end_headers'):
|
||||
handler.set_run()
|
||||
mock_send_response.assert_called_once_with(200)
|
||||
|
||||
def test_set_stop_sets_status_to_STOP(self):
|
||||
"""
|
||||
Test set_stop sets global server_status to 'STOP'
|
||||
"""
|
||||
handler = self._create_handler()
|
||||
|
||||
with patch.object(handler, 'send_response'):
|
||||
with patch.object(handler, 'end_headers'):
|
||||
handler.set_stop()
|
||||
|
||||
self.assertEqual(server.server_status, 'STOP')
|
||||
|
||||
def test_set_stop_sends_200_response(self):
|
||||
"""
|
||||
Test set_stop sends 200 status code
|
||||
"""
|
||||
handler = self._create_handler()
|
||||
|
||||
with patch.object(handler, 'send_response') as mock_send_response:
|
||||
with patch.object(handler, 'end_headers'):
|
||||
handler.set_stop()
|
||||
mock_send_response.assert_called_once_with(200)
|
||||
|
||||
def test_set_pause_sets_status_to_PAUSE(self):
|
||||
"""
|
||||
Test set_pause sets global server_status to 'PAUSE'
|
||||
"""
|
||||
handler = self._create_handler()
|
||||
|
||||
with patch.object(handler, 'send_response'):
|
||||
with patch.object(handler, 'end_headers'):
|
||||
handler.set_pause()
|
||||
|
||||
self.assertEqual(server.server_status, 'PAUSE')
|
||||
|
||||
def test_set_pause_sends_200_response(self):
|
||||
"""
|
||||
Test set_pause sends 200 status code
|
||||
"""
|
||||
handler = self._create_handler()
|
||||
|
||||
with patch.object(handler, 'send_response') as mock_send_response:
|
||||
with patch.object(handler, 'end_headers'):
|
||||
handler.set_pause()
|
||||
mock_send_response.assert_called_once_with(200)
|
||||
|
||||
def test_requests_served_is_class_variable(self):
|
||||
"""
|
||||
Test requests_served is shared across all instances
|
||||
"""
|
||||
SimpleHTTPRequestHandler.requests_served = 0
|
||||
|
||||
handler1 = self._create_handler() # Increments to 1
|
||||
handler2 = self._create_handler() # Increments to 2
|
||||
|
||||
with patch.object(handler1, 'send_response'):
|
||||
with patch.object(handler1, 'end_headers'):
|
||||
handler1.do_status() # Increments to 3
|
||||
|
||||
with patch.object(handler2, 'send_response'):
|
||||
with patch.object(handler2, 'end_headers'):
|
||||
handler2.do_status() # Increments to 4
|
||||
|
||||
# Both handlers should see the same counter
|
||||
# 2 handler creations + 2 do_status calls = 4
|
||||
self.assertEqual(handler1.requests_served, 4)
|
||||
self.assertEqual(handler2.requests_served, 4)
|
||||
self.assertEqual(SimpleHTTPRequestHandler.requests_served, 4)
|
||||
|
||||
|
||||
class TestServerModuleFunctions(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
"""
|
||||
Set up test fixtures for server module functions
|
||||
"""
|
||||
server.server_status = ""
|
||||
|
||||
def test_publish_kraken_status_sets_server_status(self):
|
||||
"""
|
||||
Test publish_kraken_status sets global server_status
|
||||
"""
|
||||
server.publish_kraken_status("NEW_STATUS")
|
||||
self.assertEqual(server.server_status, "NEW_STATUS")
|
||||
|
||||
def test_publish_kraken_status_overwrites_existing_status(self):
|
||||
"""
|
||||
Test publish_kraken_status overwrites existing status
|
||||
"""
|
||||
server.server_status = "OLD_STATUS"
|
||||
server.publish_kraken_status("NEW_STATUS")
|
||||
self.assertEqual(server.server_status, "NEW_STATUS")
|
||||
|
||||
@patch('server.HTTPServer')
|
||||
@patch('server._thread')
|
||||
def test_start_server_creates_http_server(self, mock_thread, mock_http_server):
|
||||
"""
|
||||
Test start_server creates HTTPServer with correct address
|
||||
"""
|
||||
address = ("localhost", 8080)
|
||||
mock_server_instance = MagicMock()
|
||||
mock_http_server.return_value = mock_server_instance
|
||||
|
||||
server.start_server(address, "RUNNING")
|
||||
|
||||
mock_http_server.assert_called_once_with(
|
||||
address,
|
||||
SimpleHTTPRequestHandler
|
||||
)
|
||||
|
||||
@patch('server.HTTPServer')
|
||||
@patch('server._thread')
|
||||
def test_start_server_starts_thread(self, mock_thread, mock_http_server):
|
||||
"""
|
||||
Test start_server starts a new thread for serve_forever
|
||||
"""
|
||||
address = ("localhost", 8080)
|
||||
mock_server_instance = MagicMock()
|
||||
mock_http_server.return_value = mock_server_instance
|
||||
|
||||
server.start_server(address, "RUNNING")
|
||||
|
||||
mock_thread.start_new_thread.assert_called_once()
|
||||
# Check that serve_forever was passed to the thread
|
||||
args = mock_thread.start_new_thread.call_args[0]
|
||||
self.assertEqual(args[0], mock_server_instance.serve_forever)
|
||||
|
||||
@patch('server.HTTPServer')
|
||||
@patch('server._thread')
|
||||
def test_start_server_publishes_status(self, mock_thread, mock_http_server):
|
||||
"""
|
||||
Test start_server publishes the provided status
|
||||
"""
|
||||
address = ("localhost", 8080)
|
||||
mock_server_instance = MagicMock()
|
||||
mock_http_server.return_value = mock_server_instance
|
||||
|
||||
server.start_server(address, "INITIAL_STATUS")
|
||||
|
||||
self.assertEqual(server.server_status, "INITIAL_STATUS")
|
||||
|
||||
@patch('server.HTTPConnection')
|
||||
def test_get_status_makes_http_request(self, mock_http_connection):
|
||||
"""
|
||||
Test get_status makes HTTP GET request to root path
|
||||
"""
|
||||
address = ("localhost", 8080)
|
||||
mock_connection = MagicMock()
|
||||
mock_response = MagicMock()
|
||||
mock_response.read.return_value = b"TEST_STATUS"
|
||||
mock_connection.getresponse.return_value = mock_response
|
||||
mock_http_connection.return_value = mock_connection
|
||||
|
||||
result = server.get_status(address)
|
||||
|
||||
mock_http_connection.assert_called_once_with("localhost", 8080)
|
||||
mock_connection.request.assert_called_once_with("GET", "/")
|
||||
self.assertEqual(result, "TEST_STATUS")
|
||||
|
||||
@patch('server.HTTPConnection')
|
||||
def test_get_status_returns_decoded_response(self, mock_http_connection):
|
||||
"""
|
||||
Test get_status returns decoded response string
|
||||
"""
|
||||
address = ("localhost", 8080)
|
||||
mock_connection = MagicMock()
|
||||
mock_response = MagicMock()
|
||||
mock_response.read.return_value = b"RUNNING"
|
||||
mock_connection.getresponse.return_value = mock_response
|
||||
mock_http_connection.return_value = mock_connection
|
||||
|
||||
result = server.get_status(address)
|
||||
|
||||
self.assertEqual(result, "RUNNING")
|
||||
self.assertIsInstance(result, str)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user