From fc208727469dba044f1f531d7c84a0f434a1caf7 Mon Sep 17 00:00:00 2001 From: EvgeniiMekhanik Date: Sat, 14 Jun 2025 06:35:08 +0300 Subject: [PATCH 01/10] Add tests to check new 'client_mem' option - Add tests to check new configuration option - Add tests to check that request is blocked and connection is closed if `client_mem` is exceeded - Add tests to check that connection is closed if `client_mem` is exceeded by ping. --- tests/client_mem/__init__.py | 5 + tests/client_mem/test_client_mem.py | 168 ++++++++++++++++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 tests/client_mem/__init__.py create mode 100644 tests/client_mem/test_client_mem.py diff --git a/tests/client_mem/__init__.py b/tests/client_mem/__init__.py new file mode 100644 index 000000000..fc5d1fee2 --- /dev/null +++ b/tests/client_mem/__init__.py @@ -0,0 +1,5 @@ +__all__ = [ + "test_client_mem" +] + +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 \ No newline at end of file diff --git a/tests/client_mem/test_client_mem.py b/tests/client_mem/test_client_mem.py new file mode 100644 index 000000000..8e52149b3 --- /dev/null +++ b/tests/client_mem/test_client_mem.py @@ -0,0 +1,168 @@ +"""Tests for client mem configuration.""" + +__author__ = "Tempesta Technologies, Inc." +__copyright__ = "Copyright (C) 2025 Tempesta Technologies, Inc." +__license__ = "GPL2" + +from helpers import error +from test_suite import marks, tester + +DEPROXY_CLIENT = { + "id": "deproxy", + "type": "deproxy", + "addr": "${tempesta_ip}", + "port": "80", +} + +DEPROXY_CLIENT_SSL = { + "id": "deproxy", + "type": "deproxy", + "addr": "${tempesta_ip}", + "port": "443", + "ssl": True, +} + +DEPROXY_CLIENT_H2 = { + "id": "deproxy", + "type": "deproxy_h2", + "addr": "${tempesta_ip}", + "port": "443", + "ssl": True, +} + +DEPROXY_SERVER = { + "id": "deproxy", + "type": "deproxy", + "port": "8000", + "response": "static", + "response_content": "HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nContent-Length: 0\r\n\r\n", +} + + +class TestConfig(tester.TempestaTest): + """ + This class contains tests for 'client_mem' directives. + """ + + tempesta = { + "config": """ +listen 80; +""" + } + + def __update_tempesta_config(self, client_mem_config: str): + new_config = self.get_tempesta().config.defconfig + self.get_tempesta().config.defconfig = new_config + client_mem_config + + @marks.Parameterize.expand( + [ + marks.Param(name="not_present", client_mem_config="client_mem;\n"), + marks.Param(name="to_many_args", client_mem_config="client_mem 1 3 5;\n"), + marks.Param(name="no_attrs", client_mem_config="client_mem 1 b=3;\n"), + marks.Param(name="value_1", client_mem_config="client_mem 11aa;\n"), + marks.Param(name="soft_is_greater_then_hard", client_mem_config="client_mem 10 1;") + ] + ) + def test_invalid(self, name, client_mem_config): + tempesta = self.get_tempesta() + self.__update_tempesta_config(client_mem_config) + self.oops_ignore = ["ERROR"] + with self.assertRaises(error.ProcessBadExitStatusException): + tempesta.start() + + +@marks.parameterize_class( + [ + {"name": "Http", "clients": [DEPROXY_CLIENT]}, + {"name": "Https", "clients": [DEPROXY_CLIENT_SSL]}, + {"name": "H2", "clients": [DEPROXY_CLIENT_H2]}, + ] +) +class TestBlockByMemExceeded(tester.TempestaTest): + tempesta = { + "config": """ +listen 80; +listen 443 proto=h2,https; + +server ${server_ip}:8000; + +client_mem 5000 10000; +block_action attack reply; +block_action error reply; + +tls_certificate ${tempesta_workdir}/tempesta.crt; +tls_certificate_key ${tempesta_workdir}/tempesta.key; +tls_match_any_server_name; +""", + } + + backends = [DEPROXY_SERVER] + + def test_request(self): + self.start_all_services() + + client = self.get_client("deproxy") + request = client.create_request( + method="POST", uri="/", headers=[("Content-Length", "10000")], body="a" * 10000 + ) + + client.send_request(request, "403") + + def test_response(self): + self.start_all_services() + + srv: StaticDeproxyServer = self.get_server("deproxy") + srv.set_response( + "HTTP/1.1 200 OK\r\n" + + "Content-Length: 10000\r\n" + + "Content-Type: text/html\r\n" + + "\r\n" + + "a" * 10000 + ) + + client = self.get_client("deproxy") + request = client.create_request( + method="GET", + uri="/", + headers=[], + ) + + client.send_request(request, "403") + + +class TestBlockByMemExceededByPing(tester.TempestaTest): + tempesta = { + "config": """ +listen 443 proto=h2; + +server ${server_ip}:8000; + +client_mem 5000 10000; +block_action attack reply; +block_action error reply; + +tls_certificate ${tempesta_workdir}/tempesta.crt; +tls_certificate_key ${tempesta_workdir}/tempesta.key; +tls_match_any_server_name; +""", + } + + clients = [DEPROXY_CLIENT_H2] + + backends = [DEPROXY_SERVER] + + def _ping(self, client): + client.h2_connection.ping(opaque_data=b"\x00\x01\x02\x03\x04\x05\x06\x07") + client.send_bytes(client.h2_connection.data_to_send()) + client.h2_connection.clear_outbound_data_buffer() + + def test(self): + self.start_all_services() + + ping_count = 10000 + + client = self.get_client("deproxy") + for _ in range(0, ping_count): + self._ping(client) + + self.assertTrue(client.wait_for_connection_close()) From 6f4417f9a8904869ab85648c38a9a3bce8626633 Mon Sep 17 00:00:00 2001 From: EvgeniiMekhanik Date: Tue, 8 Jul 2025 15:17:08 +0300 Subject: [PATCH 02/10] Enable tests to check flood prevention Also remove some `tests.test_stress.*` tests from tests_disabled_tcpseg/remote files because all test_stress already disabled. --- tests/client_mem/test_client_mem.py | 20 +++++++++++++++----- tests/stress/test_stress.py | 2 ++ tests/tests_disabled.json | 4 ---- tests/tests_disabled_remote.json | 4 ---- tests/tests_disabled_tcpseg.json | 20 -------------------- 5 files changed, 17 insertions(+), 33 deletions(-) diff --git a/tests/client_mem/test_client_mem.py b/tests/client_mem/test_client_mem.py index 8e52149b3..1847aad80 100644 --- a/tests/client_mem/test_client_mem.py +++ b/tests/client_mem/test_client_mem.py @@ -60,7 +60,7 @@ def __update_tempesta_config(self, client_mem_config: str): marks.Param(name="to_many_args", client_mem_config="client_mem 1 3 5;\n"), marks.Param(name="no_attrs", client_mem_config="client_mem 1 b=3;\n"), marks.Param(name="value_1", client_mem_config="client_mem 11aa;\n"), - marks.Param(name="soft_is_greater_then_hard", client_mem_config="client_mem 10 1;") + marks.Param(name="soft_is_greater_then_hard", client_mem_config="client_mem 10 1;\n"), ] ) def test_invalid(self, name, client_mem_config): @@ -73,9 +73,9 @@ def test_invalid(self, name, client_mem_config): @marks.parameterize_class( [ - {"name": "Http", "clients": [DEPROXY_CLIENT]}, - {"name": "Https", "clients": [DEPROXY_CLIENT_SSL]}, - {"name": "H2", "clients": [DEPROXY_CLIENT_H2]}, + {"name": "Http", "clients": [DEPROXY_CLIENT], "expect_response": True}, + {"name": "Https", "clients": [DEPROXY_CLIENT_SSL], "expect_response": True}, + {"name": "H2", "clients": [DEPROXY_CLIENT_H2], "expect_response": False}, ] ) class TestBlockByMemExceeded(tester.TempestaTest): @@ -97,6 +97,7 @@ class TestBlockByMemExceeded(tester.TempestaTest): } backends = [DEPROXY_SERVER] + expect_response = None def test_request(self): self.start_all_services() @@ -106,7 +107,16 @@ def test_request(self): method="POST", uri="/", headers=[("Content-Length", "10000")], body="a" * 10000 ) - client.send_request(request, "403") + client.make_request(request) + if self.expect_response: + self.assertTrue(client.wait_for_response()) + self.assertTrue(client.last_response.status, "403") + """ + For http2 connection Tempesta FW adjust memory on + frame level, so connection will be closed with + TCP RST without any response + """ + self.assertTrue(client.wait_for_connection_close()) def test_response(self): self.start_all_services() diff --git a/tests/stress/test_stress.py b/tests/stress/test_stress.py index ae61290cb..1baad6915 100644 --- a/tests/stress/test_stress.py +++ b/tests/stress/test_stress.py @@ -829,6 +829,8 @@ class TestContinuationFlood(tester.TempestaTest): server ${server_ip}:8000; + client_mem 10000 20000; + tls_certificate ${tempesta_workdir}/tempesta.crt; tls_certificate_key ${tempesta_workdir}/tempesta.key; tls_match_any_server_name; diff --git a/tests/tests_disabled.json b/tests/tests_disabled.json index 64ab2cdf2..cd7ebfb65 100644 --- a/tests/tests_disabled.json +++ b/tests/tests_disabled.json @@ -189,10 +189,6 @@ "name": "tests.clickhouse.test_clickhouse_logs.TestClickHouseLogsDelay", "reason": "The response time varies each time, and it is not possible to predict it exactly." }, - { - "name": "tests.stress.test_stress.TestContinuationFlood", - "reason": "Disabled by test issue #817" - }, { "name": "tests.stress.test_stress.TestStressNoCacheMTU80", "reason": "Disabled by test issue #817" diff --git a/tests/tests_disabled_remote.json b/tests/tests_disabled_remote.json index 2430ed5b1..9340509f2 100644 --- a/tests/tests_disabled_remote.json +++ b/tests/tests_disabled_remote.json @@ -104,10 +104,6 @@ { "name": "tests.frang.test_concurrent_connections.TestConcurrentConnectionsNonTempesta", "reason": "Is not intended to run on remote setup. Local only." - }, - { - "name": "tests.stress.test_stress.TestContinuationFlood", - "reason": "Disabled by test issue #817" }, { "name": "tests.stress.test_stress.H2LoadStressMTU80", diff --git a/tests/tests_disabled_tcpseg.json b/tests/tests_disabled_tcpseg.json index 3cb925dda..8f3ffd4bb 100644 --- a/tests/tests_disabled_tcpseg.json +++ b/tests/tests_disabled_tcpseg.json @@ -161,10 +161,6 @@ "name": "tests.http2_general.test_h2_hpack.TestHpackBomb", "reason": "These tests should not be run with TCP segmentation. We have separate tests for `http_max_header_list_size`." }, - { - "name": "tests.stress.test_ddos", - "reason": "These tests should not be run with TCP segmentation. These tests does not use deproxy." - }, { "name": "tests.frang.test_config", "reason": "These tests should not be run with TCP segmentation." @@ -256,22 +252,6 @@ { "name": "tests.frang.test_concurrent_connections.TestConcurrentConnectionsNonTempesta", "reason": "Is not intended to run with segmentation." - }, - { - "name": "tests.stress.test_stress.TestContinuationFlood", - "reason": "Disabled by test issue #817" - }, - { - "name": "tests.stress.test_stress.H2LoadStressMTU80", - "reason": "Disabled by test issue #817" - }, - { - "name": "tests.stress.test_stress.TlsWrkStressMTU80", - "reason": "Disabled by test issue #817" - }, - { - "name": "tests.stress.test_stress.WrkStressMTU80", - "reason": "Disabled by test issue #817" } ] } From 28ca86999a39d1a1ad1f7859e328748b52115d2f Mon Sep 17 00:00:00 2001 From: EvgeniiMekhanik Date: Mon, 14 Jul 2025 21:23:09 +0300 Subject: [PATCH 03/10] Fix flucky test If we cehck warning in dmesg we always shout use dmesg.unlimited_rate_on_tempesta_node as a decorator. --- tests/client_mem/test_client_mem.py | 9 +++++++-- tests/tls/test_tls_limits.py | 1 + 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/client_mem/test_client_mem.py b/tests/client_mem/test_client_mem.py index 1847aad80..297c40c21 100644 --- a/tests/client_mem/test_client_mem.py +++ b/tests/client_mem/test_client_mem.py @@ -4,6 +4,7 @@ __copyright__ = "Copyright (C) 2025 Tempesta Technologies, Inc." __license__ = "GPL2" +import run_config from helpers import error from test_suite import marks, tester @@ -137,7 +138,11 @@ def test_response(self): headers=[], ) - client.send_request(request, "403") + client.make_request(request) + if not run_config.TCP_SEGMENTATION: + self.assertTrue(client.wait_for_response()) + self.assertTrue(client.last_response.status, "403") + self.assertTrue(client.wait_for_connection_close()) class TestBlockByMemExceededByPing(tester.TempestaTest): @@ -147,7 +152,7 @@ class TestBlockByMemExceededByPing(tester.TempestaTest): server ${server_ip}:8000; -client_mem 5000 10000; +client_mem 500 1000; block_action attack reply; block_action error reply; diff --git a/tests/tls/test_tls_limits.py b/tests/tls/test_tls_limits.py index fc2b5fe96..3134563ad 100644 --- a/tests/tls/test_tls_limits.py +++ b/tests/tls/test_tls_limits.py @@ -66,6 +66,7 @@ class TLSMatchHostSni(tester.TempestaTest): TLS_WARN = "Warning: frang: vhost by SNI doesn't match vhost by authority" + @dmesg.unlimited_rate_on_tempesta_node async def test_host_sni_mismatch(self): """With the `http_strict_host_checking` limit, the host header and SNI name must be identical. Otherwise request will be filtered. After client From 35135a00ba3fb5b2cea93651c0e5fddc8b6bcdea Mon Sep 17 00:00:00 2001 From: EvgeniiMekhanik Date: Thu, 20 Nov 2025 11:09:11 +0200 Subject: [PATCH 04/10] Do not use sniffer to check `server_forward_retries` Now Tempesta FW do not use `tcp_push` to send data, so we can't use sniffer and check count of `PUSH` packets, more over it is not reliably, because this method rely on the call of `tcp_push`. Check count of received requests on server instead. --- tests/reconf/test_reconf_base.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/reconf/test_reconf_base.py b/tests/reconf/test_reconf_base.py index 075fc052f..3d80ac26e 100644 --- a/tests/reconf/test_reconf_base.py +++ b/tests/reconf/test_reconf_base.py @@ -698,9 +698,6 @@ class TestServerOptionsReconf(tester.TempestaTest): async def asyncSetUp(self): await super().asyncSetUp() self.dmesg = dmesg.DmesgFinder(disable_ratelimit=True) - self.sniffer = analyzer.Sniffer( - node=remote.tempesta, host="Tempesta", timeout=self.sniffer_timeout, ports=[8000] - ) def _set_tempesta_config_with_server_retry_nonidempotent(self): self.get_tempesta().config.set_defconfig( @@ -946,7 +943,9 @@ async def test_reconf_server_forward_retries(self, name, old_srv_forward_retries client = self.get_client("deproxy") tempesta = self.get_tempesta() - self.get_server("deproxy").drop_conn_when_request_received = True + server = self.get_server("deproxy") + + server.drop_conn_when_request_received = True tempesta.config.set_defconfig( f""" @@ -978,10 +977,8 @@ async def test_reconf_server_forward_retries(self, name, old_srv_forward_retries """ ) - await self.sniffer.start() tempesta.reload() await client.send_request(client.create_request(method="GET", headers=[]), "504") - self.sniffer.stop() self.assertTrue( await self.dmesg.find( @@ -990,10 +987,9 @@ async def test_reconf_server_forward_retries(self, name, old_srv_forward_retries DMESG_WARNING, ) - forward_tries = len([p for p in self.sniffer.packets if p[TCP].flags & PSH]) self.assertEqual( server_forward_retries + 1, - forward_tries, + len(server.requests), "Tempesta made forward attempts not equal to `server_forward_retries` after reload.", ) From b41540b9b030f5a2448a1876f12f3d95f4e38c87 Mon Sep 17 00:00:00 2001 From: EvgeniiMekhanik Date: Sun, 8 Feb 2026 07:52:43 +0200 Subject: [PATCH 05/10] Fix tests after http adjusting memory in Tempesta Also add test to check `client_lru_size` option. --- tests/client_mem/__init__.py | 6 +- tests/client_mem/test_client_mem.py | 121 ++++++++++++++---- .../multiple_clients/test_multiple_clients.py | 11 ++ 3 files changed, 109 insertions(+), 29 deletions(-) diff --git a/tests/client_mem/__init__.py b/tests/client_mem/__init__.py index fc5d1fee2..4bb358bfd 100644 --- a/tests/client_mem/__init__.py +++ b/tests/client_mem/__init__.py @@ -1,5 +1,3 @@ -__all__ = [ - "test_client_mem" -] +__all__ = ["test_client_mem"] -# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 \ No newline at end of file +# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/tests/client_mem/test_client_mem.py b/tests/client_mem/test_client_mem.py index 297c40c21..a2f9272fe 100644 --- a/tests/client_mem/test_client_mem.py +++ b/tests/client_mem/test_client_mem.py @@ -5,8 +5,8 @@ __license__ = "GPL2" import run_config -from helpers import error -from test_suite import marks, tester +from framework.helpers import error +from framework.test_suite import marks, tester DEPROXY_CLIENT = { "id": "deproxy", @@ -40,7 +40,13 @@ } -class TestConfig(tester.TempestaTest): +class TestClientMemBase(tester.TempestaTest): + def update_tempesta_config(self, client_mem_config: str): + new_config = self.get_tempesta().config.defconfig + self.get_tempesta().config.defconfig = new_config + client_mem_config + + +class TestClientMemConfig(TestClientMemBase): """ This class contains tests for 'client_mem' directives. """ @@ -51,10 +57,6 @@ class TestConfig(tester.TempestaTest): """ } - def __update_tempesta_config(self, client_mem_config: str): - new_config = self.get_tempesta().config.defconfig - self.get_tempesta().config.defconfig = new_config + client_mem_config - @marks.Parameterize.expand( [ marks.Param(name="not_present", client_mem_config="client_mem;\n"), @@ -64,9 +66,9 @@ def __update_tempesta_config(self, client_mem_config: str): marks.Param(name="soft_is_greater_then_hard", client_mem_config="client_mem 10 1;\n"), ] ) - def test_invalid(self, name, client_mem_config): + async def test_invalid(self, name, client_mem_config): tempesta = self.get_tempesta() - self.__update_tempesta_config(client_mem_config) + self.update_tempesta_config(client_mem_config) self.oops_ignore = ["ERROR"] with self.assertRaises(error.ProcessBadExitStatusException): tempesta.start() @@ -74,12 +76,27 @@ def test_invalid(self, name, client_mem_config): @marks.parameterize_class( [ - {"name": "Http", "clients": [DEPROXY_CLIENT], "expect_response": True}, - {"name": "Https", "clients": [DEPROXY_CLIENT_SSL], "expect_response": True}, - {"name": "H2", "clients": [DEPROXY_CLIENT_H2], "expect_response": False}, + { + "name": "Http", + "clients": [DEPROXY_CLIENT], + "expect_response": True, + "client_mem": "client_mem 10000 20000;\n", + }, + { + "name": "Https", + "clients": [DEPROXY_CLIENT_SSL], + "expect_response": True, + "client_mem": "client_mem 10000 20000;\n", + }, + { + "name": "H2", + "clients": [DEPROXY_CLIENT_H2], + "expect_response": False, + "client_mem": "client_mem 20000 40000;\n", + }, ] ) -class TestBlockByMemExceeded(tester.TempestaTest): +class TestBlockByMemExceeded(TestClientMemBase): tempesta = { "config": """ listen 80; @@ -87,7 +104,6 @@ class TestBlockByMemExceeded(tester.TempestaTest): server ${server_ip}:8000; -client_mem 5000 10000; block_action attack reply; block_action error reply; @@ -100,8 +116,9 @@ class TestBlockByMemExceeded(tester.TempestaTest): backends = [DEPROXY_SERVER] expect_response = None - def test_request(self): - self.start_all_services() + async def test_request(self): + self.update_tempesta_config(self.client_mem) + await self.start_all_services() client = self.get_client("deproxy") request = client.create_request( @@ -110,17 +127,18 @@ def test_request(self): client.make_request(request) if self.expect_response: - self.assertTrue(client.wait_for_response()) + await client.wait_for_response(strict=True) self.assertTrue(client.last_response.status, "403") """ For http2 connection Tempesta FW adjust memory on frame level, so connection will be closed with TCP RST without any response """ - self.assertTrue(client.wait_for_connection_close()) + await client.wait_for_connection_close(strict=True) - def test_response(self): - self.start_all_services() + async def test_response(self): + self.update_tempesta_config(self.client_mem) + await self.start_all_services() srv: StaticDeproxyServer = self.get_server("deproxy") srv.set_response( @@ -140,9 +158,9 @@ def test_response(self): client.make_request(request) if not run_config.TCP_SEGMENTATION: - self.assertTrue(client.wait_for_response()) + await client.wait_for_response(strict=True) self.assertTrue(client.last_response.status, "403") - self.assertTrue(client.wait_for_connection_close()) + await client.wait_for_connection_close(strict=True) class TestBlockByMemExceededByPing(tester.TempestaTest): @@ -171,8 +189,8 @@ def _ping(self, client): client.send_bytes(client.h2_connection.data_to_send()) client.h2_connection.clear_outbound_data_buffer() - def test(self): - self.start_all_services() + async def test(self): + await self.start_all_services() ping_count = 10000 @@ -180,4 +198,57 @@ def test(self): for _ in range(0, ping_count): self._ping(client) - self.assertTrue(client.wait_for_connection_close()) + await client.wait_for_connection_close(strict=True) + + +class TestSeveralClientsWithSmallLrusize(tester.TempestaTest): + tempesta = { + "config": """ +listen 443 proto=h2,https; + +server ${server_ip}:8000; + +client_lru_size 1; + +tls_certificate ${tempesta_workdir}/tempesta.crt; +tls_certificate_key ${tempesta_workdir}/tempesta.key; +tls_match_any_server_name; +""", + } + + clients = [ + { + "id": f"deproxy-interface-{id_}", + "type": "deproxy", + "addr": "${tempesta_ip}", + "port": "443", + "interface": True, + "ssl": True, + } + for id_ in range(3) + ] + + backends = [DEPROXY_SERVER] + + @staticmethod + def make_resp(body): + return "HTTP/1.1 200 OK\r\n" "Content-Length: " + str(len(body)) + "\r\n\r\n" + body + + async def test_all_clients_active(self): + await self.start_all_services() + server = self.get_server("deproxy") + + server.set_response(self.make_resp("x" * 10000)) + + for id_ in range(3): + client = self.get_client(f"deproxy-interface-{id_}") + client.start() + + for id_ in range(3): + client = self.get_client(f"deproxy-interface-{id_}") + for i in range(10): + request = client.create_request(method="GET", uri="/", headers=[]) + client.make_request(request) + await server.wait_for_requests(id_ * 10 + i, strict=True) + await client.wait_for_response() + self.assertTrue(len(client.responses), 10) diff --git a/tests/multiple_clients/test_multiple_clients.py b/tests/multiple_clients/test_multiple_clients.py index c88156fa7..cf731cff8 100755 --- a/tests/multiple_clients/test_multiple_clients.py +++ b/tests/multiple_clients/test_multiple_clients.py @@ -99,3 +99,14 @@ async def test_tcp_fin_timeout(self): self.assertTrue( client.is_rst_received, "Client don't receive TCP RST when Tempesta FW closes." ) + + async def test_client_memory(self): + config = self.get_tempesta().config.defconfig + self.get_tempesta().config.defconfig = config + "client_mem 500000 1000000;\n" + await self.start_all_services(client=False) + request = self.get_client("deproxy-0").create_request(method="GET", headers=[]) + + for client in self.get_clients(): + client.start() + await client.send_request(request, "200") + client.stop() From 73e0b5932f6279320646ff602fa9fcc90ae320d8 Mon Sep 17 00:00:00 2001 From: EvgeniiMekhanik Date: Mon, 13 Apr 2026 12:27:40 +0300 Subject: [PATCH 06/10] Implement new fault injection tests. Implement new fault injection tests to cover new allocation code. --- .../test_fault_injection_base.py | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/tests/fault_injection/test_fault_injection_base.py b/tests/fault_injection/test_fault_injection_base.py index cd0da4557..728b0e2f4 100644 --- a/tests/fault_injection/test_fault_injection_base.py +++ b/tests/fault_injection/test_fault_injection_base.py @@ -883,6 +883,70 @@ async def test(self, name, func_name, extra_config, space, retval): module_name_preload=None, retval=0, ), + marks.Param( + name="tfw_alloc_percpu_gfp", + func_name="tfw__alloc_percpu_gfp", + config=f""" +listen 80; +listen 443 proto=h2,https; +client_lru_size 5; +client_mem 500000 1000000; +tls_certificate {TEMPESTA_WORKDIR}/tempesta.crt; +tls_certificate_key {TEMPESTA_WORKDIR}/tempesta.key; +server {SERVER_IP}:8000 conns_n=10; +""", + module_path="lib", + module_name_preload="tempesta_lib", + retval=0, + ), + marks.Param( + name="tfw_percpu_ref_init", + func_name="tfw_percpu_ref_init", + config=f""" +listen 80; +listen 443 proto=h2,https; +client_lru_size 5; +client_mem 500000 1000000; +tls_certificate {TEMPESTA_WORKDIR}/tempesta.crt; +tls_certificate_key {TEMPESTA_WORKDIR}/tempesta.key; +server {SERVER_IP}:8000 conns_n=10; +""", + module_path="lib", + module_name_preload="tempesta_lib", + retval=-12, + ), + marks.Param( + name="tfw_get_free_pages", + func_name="tfw__get_free_pages", + config=f""" +listen 80; +listen 443 proto=h2,https; +client_lru_size 5; +client_mem 500000 1000000; +tls_certificate {TEMPESTA_WORKDIR}/tempesta.crt; +tls_certificate_key {TEMPESTA_WORKDIR}/tempesta.key; +server {SERVER_IP}:8000 conns_n=10; + +health_check h_monitor1 {{ + request "GET / HTTP/1.1\r\n\r\n"; + request_url "/"; + resp_code 200; + resp_crc32 auto; + timeout 1; +}} + +srv_group test {{ + sched hash; + server {SERVER_IP}:8001 conns_n=10; + + health h_monitor1; +}} + +""", + module_path="lib", + module_name_preload="tempesta_lib", + retval=0, + ), ] ) async def test_init_modules( From b7d038fa9a0811d743d15db1fc2181c41839c1d8 Mon Sep 17 00:00:00 2001 From: EvgeniiMekhanik Date: Wed, 15 Apr 2026 14:07:38 +0300 Subject: [PATCH 07/10] Implement tests to check reconfiguring client mem option --- tests/client_mem/test_client_mem.py | 175 +++++++++++++++++++++++----- 1 file changed, 145 insertions(+), 30 deletions(-) diff --git a/tests/client_mem/test_client_mem.py b/tests/client_mem/test_client_mem.py index a2f9272fe..4fc0f602e 100644 --- a/tests/client_mem/test_client_mem.py +++ b/tests/client_mem/test_client_mem.py @@ -4,6 +4,7 @@ __copyright__ = "Copyright (C) 2025 Tempesta Technologies, Inc." __license__ = "GPL2" +import asyncio import run_config from framework.helpers import error from framework.test_suite import marks, tester @@ -74,6 +75,39 @@ async def test_invalid(self, name, client_mem_config): tempesta.start() +class TestBlockByMemExceededBase(TestClientMemBase): + tempesta = { + "config": """ +listen 80; +listen 443 proto=h2,https; + +server ${server_ip}:8000; + +block_action attack reply; +block_action error reply; + +tls_certificate ${tempesta_workdir}/tempesta.crt; +tls_certificate_key ${tempesta_workdir}/tempesta.key; +tls_match_any_server_name; +""", + } + + backends = [DEPROXY_SERVER] + expect_response = None + + async def send_request_and_check_response_and_conn_close(self, client, request): + client.make_request(request) + if self.expect_response: + await client.wait_for_response(strict=True) + self.assertTrue(client.last_response.status, "403") + """ + For http2 connection Tempesta FW adjust memory on + frame level, so connection will be closed with + TCP RST without any response + """ + await client.wait_for_connection_close(strict=True) + + @marks.parameterize_class( [ { @@ -96,26 +130,7 @@ async def test_invalid(self, name, client_mem_config): }, ] ) -class TestBlockByMemExceeded(TestClientMemBase): - tempesta = { - "config": """ -listen 80; -listen 443 proto=h2,https; - -server ${server_ip}:8000; - -block_action attack reply; -block_action error reply; - -tls_certificate ${tempesta_workdir}/tempesta.crt; -tls_certificate_key ${tempesta_workdir}/tempesta.key; -tls_match_any_server_name; -""", - } - - backends = [DEPROXY_SERVER] - expect_response = None - +class TestBlockByMemExceeded(TestBlockByMemExceededBase): async def test_request(self): self.update_tempesta_config(self.client_mem) await self.start_all_services() @@ -125,16 +140,7 @@ async def test_request(self): method="POST", uri="/", headers=[("Content-Length", "10000")], body="a" * 10000 ) - client.make_request(request) - if self.expect_response: - await client.wait_for_response(strict=True) - self.assertTrue(client.last_response.status, "403") - """ - For http2 connection Tempesta FW adjust memory on - frame level, so connection will be closed with - TCP RST without any response - """ - await client.wait_for_connection_close(strict=True) + await self.send_request_and_check_response_and_conn_close(client, request) async def test_response(self): self.update_tempesta_config(self.client_mem) @@ -163,6 +169,115 @@ async def test_response(self): await client.wait_for_connection_close(strict=True) +class TestReconfigClientMemStress(tester.TempestaTest): + tempesta = { + "config": """ +listen 80; +listen 443 proto=h2,https; + +server ${server_ip}:8000; + +block_action attack reply; +block_action error reply; + +tls_certificate ${tempesta_workdir}/tempesta.crt; +tls_certificate_key ${tempesta_workdir}/tempesta.key; +tls_match_any_server_name; +""", + } + + backends = [DEPROXY_SERVER] + + clients = [ + { + "id": "gflood", + "type": "external", + "binary": "gflood", + "ssl": True, + "cmd_args": "-address ${tempesta_ip}:443 -host tempesta-tech.com -threads 4 -connections 100 -streams 100", + }, + ] + + stop = False + + async def _do_reload(self): + base_config = self.get_tempesta().config.defconfig + i = 0 + while not self.stop: + if i % 2 == 0: + config = base_config + "client_mem 10000 20000;\n" + else: + config = base_config + i = i + 1 + self.get_tempesta().config.defconfig = config + self.get_tempesta().reload() + await asyncio.sleep(1) + + async def test_under_load(self): + await self.start_all_services(client=False) + client = self.get_client("gflood") + task = asyncio.create_task(self._do_reload()) + client.start() + await self.wait_while_busy(client) + client.stop() + self.stop = True + await task + + +@marks.parameterize_class( + [ + { + "name": "Http", + "clients": [DEPROXY_CLIENT], + "expect_response": True, + "client_mem": "client_mem 10000 20000;\n", + }, + { + "name": "Https", + "clients": [DEPROXY_CLIENT_SSL], + "expect_response": True, + "client_mem": "client_mem 10000 20000;\n", + }, + { + "name": "H2", + "clients": [DEPROXY_CLIENT_H2], + "expect_response": False, + "client_mem": "client_mem 20000 40000;\n", + }, + ] +) +class TestReconfigClientMem(TestBlockByMemExceededBase): + tempesta = { + "config": """ +listen 80; +listen 443 proto=h2,https; + +server ${server_ip}:8000; + +block_action attack reply; +block_action error reply; + +tls_certificate ${tempesta_workdir}/tempesta.crt; +tls_certificate_key ${tempesta_workdir}/tempesta.key; +tls_match_any_server_name; +""", + } + + backends = [DEPROXY_SERVER] + + async def test(self): + await self.start_all_services() + client = self.get_client("deproxy") + request = client.create_request( + method="POST", uri="/", headers=[("Content-Length", "10000")], body="a" * 10000 + ) + + await client.send_request(request, "200") + self.update_tempesta_config("client_mem 10000 20000;\n") + self.get_tempesta().reload() + await self.send_request_and_check_response_and_conn_close(client, request) + + class TestBlockByMemExceededByPing(tester.TempestaTest): tempesta = { "config": """ From c5315486959148b88e384d6ef153632a76d0ceec Mon Sep 17 00:00:00 2001 From: EvgeniiMekhanik Date: Thu, 16 Apr 2026 09:59:43 +0300 Subject: [PATCH 08/10] Fixes according last changes in Tempesta FW - Do not check error response if client was blocked, because of memory limit exceeded during processing request. --- tests/client_mem/test_client_mem.py | 25 ++++++------------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/tests/client_mem/test_client_mem.py b/tests/client_mem/test_client_mem.py index 4fc0f602e..40eb3d80a 100644 --- a/tests/client_mem/test_client_mem.py +++ b/tests/client_mem/test_client_mem.py @@ -93,13 +93,9 @@ class TestBlockByMemExceededBase(TestClientMemBase): } backends = [DEPROXY_SERVER] - expect_response = None - async def send_request_and_check_response_and_conn_close(self, client, request): + async def send_request_and_check_conn_close(self, client, request): client.make_request(request) - if self.expect_response: - await client.wait_for_response(strict=True) - self.assertTrue(client.last_response.status, "403") """ For http2 connection Tempesta FW adjust memory on frame level, so connection will be closed with @@ -113,19 +109,16 @@ async def send_request_and_check_response_and_conn_close(self, client, request): { "name": "Http", "clients": [DEPROXY_CLIENT], - "expect_response": True, - "client_mem": "client_mem 10000 20000;\n", + "client_mem": "client_mem 5000 10000;\n", }, { "name": "Https", "clients": [DEPROXY_CLIENT_SSL], - "expect_response": True, - "client_mem": "client_mem 10000 20000;\n", + "client_mem": "client_mem 5000 10000;\n", }, { "name": "H2", "clients": [DEPROXY_CLIENT_H2], - "expect_response": False, "client_mem": "client_mem 20000 40000;\n", }, ] @@ -137,10 +130,10 @@ async def test_request(self): client = self.get_client("deproxy") request = client.create_request( - method="POST", uri="/", headers=[("Content-Length", "10000")], body="a" * 10000 + method="POST", uri="/", headers=[("Content-Length", "30000")], body="a" * 30000 ) - await self.send_request_and_check_response_and_conn_close(client, request) + await self.send_request_and_check_conn_close(client, request) async def test_response(self): self.update_tempesta_config(self.client_mem) @@ -229,20 +222,14 @@ async def test_under_load(self): { "name": "Http", "clients": [DEPROXY_CLIENT], - "expect_response": True, - "client_mem": "client_mem 10000 20000;\n", }, { "name": "Https", "clients": [DEPROXY_CLIENT_SSL], - "expect_response": True, - "client_mem": "client_mem 10000 20000;\n", }, { "name": "H2", "clients": [DEPROXY_CLIENT_H2], - "expect_response": False, - "client_mem": "client_mem 20000 40000;\n", }, ] ) @@ -275,7 +262,7 @@ async def test(self): await client.send_request(request, "200") self.update_tempesta_config("client_mem 10000 20000;\n") self.get_tempesta().reload() - await self.send_request_and_check_response_and_conn_close(client, request) + await self.send_request_and_check_conn_close(client, request) class TestBlockByMemExceededByPing(tester.TempestaTest): From d8cc0241db83d85982a98c6dbbe3796467ad8225 Mon Sep 17 00:00:00 2001 From: EvgeniiMekhanik Date: Thu, 30 Apr 2026 15:11:00 +0300 Subject: [PATCH 09/10] Fix according review - Add dockstrings for all new implemented tests - Move `send_ping` function to DeproxyClientH2 - Use self.get_clients to iteration over all clients - Remove unnecessary test - Block some tests for TCP segmentation. - Implement `create_task` in base TempestaTest class to be sure that all tasks will be finished successfully --- framework/deproxy/deproxy_client.py | 5 ++ framework/test_suite/tester.py | 29 +++++- tests/client_mem/test_client_mem.py | 89 +++++++++++-------- tests/http2_general/test_h2_frame.py | 9 +- .../multiple_clients/test_multiple_clients.py | 6 ++ tests/reconf/test_reconf_base.py | 25 ++---- tests/tests_disabled_tcpseg.json | 12 +++ 7 files changed, 112 insertions(+), 63 deletions(-) diff --git a/framework/deproxy/deproxy_client.py b/framework/deproxy/deproxy_client.py index fd625486e..c2a0115d8 100644 --- a/framework/deproxy/deproxy_client.py +++ b/framework/deproxy/deproxy_client.py @@ -608,6 +608,11 @@ def make_request( self.stream_id += 2 self._valid_req_num += 1 + def ping(self): + self.h2_connection.ping(opaque_data=b"\x00\x01\x02\x03\x04\x05\x06\x07") + self.send_bytes(self.h2_connection.data_to_send()) + self.h2_connection.clear_outbound_data_buffer() + @staticmethod def create_request( method, diff --git a/framework/test_suite/tester.py b/framework/test_suite/tester.py index 55acde763..8ea929511 100644 --- a/framework/test_suite/tester.py +++ b/framework/test_suite/tester.py @@ -150,7 +150,8 @@ class TempestaTest(WaitUntilAsserts, unittest.IsolatedAsyncioTestCase): Tempesta tests should have: 1) backends: [...] 2) clients: [...] - 3) several test functions. + 3) tasks: [...] + 4) several test functions. function name should start with 'test' Verbose documentation is placed in README.md @@ -160,6 +161,8 @@ class TempestaTest(WaitUntilAsserts, unittest.IsolatedAsyncioTestCase): clients: list[dict] = [] + tasks: list[tuple(asyncio.Task, asyncio.event)] = [] + tempesta = {"type": "tempesta", "config": "", "tfw_config": tfw.TfwLogger()} def __init_subclass__(cls, base=False, **kwargs): @@ -375,6 +378,29 @@ def start_all_clients(self): if not client.is_running(): raise Exception("Can not start client %s" % cid) + def create_task(self, func): + stop_event = asyncio.Event() + + async def wrapper(): + try: + while not stop_event.is_set(): + await func() + except asyncio.CancelledError: + pass + except Exception as e: + raise RuntimeError("Task failed") from e + + task = asyncio.create_task(wrapper()) + self.tasks.append((task, stop_event)) + + return task, stop_event + + async def __cleanup_tasks(self): + for task, stop_event in self.tasks: + stop_event.set() + await task + self.tasks.clear() + async def asyncSetUp(self): # `unittest.TestLoader.discover` returns initialized objects, we can't # raise `SkipTest` inside of `TempestaTest.__init__` because we are unable @@ -412,6 +438,7 @@ async def asyncSetUp(self): self.addAsyncCleanup(self.cleanup_interfaces) self.addAsyncCleanup(self.cleanup_deproxy) self.addAsyncCleanup(self.cleanup_services) + self.addAsyncCleanup(self.__cleanup_tasks) test_logger.info(f"setUp completed '{self.id()}'") async def cleanup_services(self): diff --git a/tests/client_mem/test_client_mem.py b/tests/client_mem/test_client_mem.py index 40eb3d80a..16e1f291a 100644 --- a/tests/client_mem/test_client_mem.py +++ b/tests/client_mem/test_client_mem.py @@ -68,6 +68,10 @@ class TestClientMemConfig(TestClientMemBase): ] ) async def test_invalid(self, name, client_mem_config): + """ + This test checks that Tempesta FW doesn't start with wrong `client_mem` + option. + """ tempesta = self.get_tempesta() self.update_tempesta_config(client_mem_config) self.oops_ignore = ["ERROR"] @@ -125,6 +129,10 @@ async def send_request_and_check_conn_close(self, client, request): ) class TestBlockByMemExceeded(TestBlockByMemExceededBase): async def test_request(self): + """ + This test checks that Tempesta FW drop client connection + if request exceeded `client_mem` limit. + """ self.update_tempesta_config(self.client_mem) await self.start_all_services() @@ -136,6 +144,12 @@ async def test_request(self): await self.send_request_and_check_conn_close(client, request) async def test_response(self): + """ + This test checks that Tempesta FW drop client connection + if response exceeded `client_mem` limit. Check that + client received 403 error response before connection + will be closed. + """ self.update_tempesta_config(self.client_mem) await self.start_all_services() @@ -155,10 +169,7 @@ async def test_response(self): headers=[], ) - client.make_request(request) - if not run_config.TCP_SEGMENTATION: - await client.wait_for_response(strict=True) - self.assertTrue(client.last_response.status, "403") + await client.send_request(request, "403") await client.wait_for_connection_close(strict=True) @@ -191,30 +202,30 @@ class TestReconfigClientMemStress(tester.TempestaTest): }, ] - stop = False + async def __do_reload_impl(self, base_config, i): + config = base_config + "client_mem 10000 20000;\n" if i % 2 == 0 else base_config + self.get_tempesta().config.defconfig = config + self.get_tempesta().reload() + await asyncio.sleep(1) - async def _do_reload(self): + async def __do_reload(self): base_config = self.get_tempesta().config.defconfig - i = 0 - while not self.stop: - if i % 2 == 0: - config = base_config + "client_mem 10000 20000;\n" - else: - config = base_config - i = i + 1 - self.get_tempesta().config.defconfig = config - self.get_tempesta().reload() - await asyncio.sleep(1) + await self.__do_reload_impl(base_config, 0) + await self.__do_reload_impl(base_config, 1) async def test_under_load(self): + """ + This test checks that there is no crashes if we reload + Tempesta FW with `client_mem` options under heavy load. + (Tempesta FW deletes special data structure used for + client memory accounting in very sofisticated way). + """ await self.start_all_services(client=False) client = self.get_client("gflood") - task = asyncio.create_task(self._do_reload()) + self.create_task(self.__do_reload) client.start() await self.wait_while_busy(client) client.stop() - self.stop = True - await task @marks.parameterize_class( @@ -253,6 +264,14 @@ class TestReconfigClientMem(TestBlockByMemExceededBase): backends = [DEPROXY_SERVER] async def test(self): + """ + This test check that `client_mem` option is reconfigurable. + First of all start Tempesta FW without `client_mem` option, + send request with long body and check that we successfully + receive response. Reload Tempesta FW with strong `client_mem` + limit and check that we close client connection, because + `client_mem` limit is exceeded. + """ await self.start_all_services() client = self.get_client("deproxy") request = client.create_request( @@ -286,19 +305,18 @@ class TestBlockByMemExceededByPing(tester.TempestaTest): backends = [DEPROXY_SERVER] - def _ping(self, client): - client.h2_connection.ping(opaque_data=b"\x00\x01\x02\x03\x04\x05\x06\x07") - client.send_bytes(client.h2_connection.data_to_send()) - client.h2_connection.clear_outbound_data_buffer() - async def test(self): + """ + This test check that Tempesta FW drops client connection under ping + flood, if client_mem is exceeded hard limit. + """ await self.start_all_services() ping_count = 10000 client = self.get_client("deproxy") for _ in range(0, ping_count): - self._ping(client) + client.ping() await client.wait_for_connection_close(strict=True) @@ -334,23 +352,24 @@ class TestSeveralClientsWithSmallLrusize(tester.TempestaTest): @staticmethod def make_resp(body): - return "HTTP/1.1 200 OK\r\n" "Content-Length: " + str(len(body)) + "\r\n\r\n" + body + return f"HTTP/1.1 200 OK\r\nContent-Length: {len(body)}\r\n\r\n{body}" async def test_all_clients_active(self): + """ + This test checks Tempesta FW behaviour, when count of clients + exceeded LRU size. In this case Tempesta FW remove old clients + and delete structure, which is used for memory accounting. + """ await self.start_all_services() server = self.get_server("deproxy") server.set_response(self.make_resp("x" * 10000)) - for id_ in range(3): - client = self.get_client(f"deproxy-interface-{id_}") + i = 0 + for client in self.get_clients(): client.start() - - for id_ in range(3): - client = self.get_client(f"deproxy-interface-{id_}") - for i in range(10): - request = client.create_request(method="GET", uri="/", headers=[]) - client.make_request(request) - await server.wait_for_requests(id_ * 10 + i, strict=True) + request = client.create_request(method="GET", uri="/", headers=[]) + client.make_requests([request] * 10) + await server.wait_for_requests((i + 1) * 10, strict=True) await client.wait_for_response() self.assertTrue(len(client.responses), 10) diff --git a/tests/http2_general/test_h2_frame.py b/tests/http2_general/test_h2_frame.py index 81dc21f43..a2607ef13 100644 --- a/tests/http2_general/test_h2_frame.py +++ b/tests/http2_general/test_h2_frame.py @@ -803,11 +803,6 @@ class TestPostponedFrames(H2Base): This class checks that Tempesta FW doesn't violate this rule. """ - def _ping(self, client): - client.h2_connection.ping(opaque_data=b"\x00\x01\x02\x03\x04\x05\x06\x07") - client.send_bytes(client.h2_connection.data_to_send()) - client.h2_connection.clear_outbound_data_buffer() - @marks.Parameterize.expand( [ marks.Param(name="headers", header="a" * 30000, token="value"), @@ -841,7 +836,7 @@ async def test(self, name, header, token): stream_id = client.stream_id client.make_request(self.get_request) for _ in range(0, ping_count): - self._ping(client) + client.ping() self.assertTrue(await client.wait_for_headers_frame(stream_id)) self.assertTrue(await client.wait_for_ping_frames(ping_count)) @@ -850,7 +845,7 @@ async def test(self, name, header, token): self.assertTrue(await client.wait_for_ack_settings()) for _ in range(0, ping_count): - self._ping(client) + client.ping() self.assertTrue(await client.wait_for_headers_frame(stream_id)) self.assertTrue(await client.wait_for_ping_frames(2 * ping_count)) diff --git a/tests/multiple_clients/test_multiple_clients.py b/tests/multiple_clients/test_multiple_clients.py index cf731cff8..c137dac4e 100755 --- a/tests/multiple_clients/test_multiple_clients.py +++ b/tests/multiple_clients/test_multiple_clients.py @@ -101,6 +101,12 @@ async def test_tcp_fin_timeout(self): ) async def test_client_memory(self): + """ + This test checks Tempesta FW behaviour, when count of clients + exceeded LRU size. In this case Tempesta FW remove old clients + and delete structure, which is used for memory accounting in very + sofisticated way. + """ config = self.get_tempesta().config.defconfig self.get_tempesta().config.defconfig = config + "client_mem 500000 1000000;\n" await self.start_all_services(client=False) diff --git a/tests/reconf/test_reconf_base.py b/tests/reconf/test_reconf_base.py index 3d80ac26e..de5b7b076 100644 --- a/tests/reconf/test_reconf_base.py +++ b/tests/reconf/test_reconf_base.py @@ -294,24 +294,14 @@ class TestListenStartFail(tester.TempestaTest): }, ] - stop = False - async def __heavy_load(self): curl = self.get_client("curl") - while not self.stop: - curl.start() - await self.wait_while_busy(curl) - curl.stop() - - async def __finish_heavy_load(self): - self.stop = True - for task in self.tasks: - await task + curl.start() + await self.wait_while_busy(curl) + curl.stop() async def asyncSetUp(self): await super().asyncSetUp() - self.tasks = [] - self.addAsyncCleanup(self.__finish_heavy_load) async def test_start_failed_under_heavy_load(self): """ @@ -327,7 +317,7 @@ async def test_start_failed_under_heavy_load(self): server.start() self.deproxy_manager.start() - self.tasks.append(asyncio.create_task(self.__heavy_load())) + self.create_task(self.__heavy_load) self.get_tempesta().config.set_defconfig( f""" @@ -2026,11 +2016,6 @@ async def test(self, name, config): with self.assertRaises(error.ProcessBadExitStatusException): tempesta.reload() - def _ping(self, client): - client.h2_connection.ping(opaque_data=b"\x00\x01\x02\x03\x04\x05\x06\x07") - client.send_bytes(client.h2_connection.data_to_send()) - client.h2_connection.clear_outbound_data_buffer() - async def test_decrease(self): tempesta = self.get_tempesta() old_config = tempesta.config.defconfig @@ -2044,7 +2029,7 @@ async def test_decrease(self): self.assertTrue(await client.wait_for_ack_settings()) for _ in range(0, 10000): - self._ping(client) + client.ping() tempesta.config.set_defconfig(old_config) tempesta.reload() diff --git a/tests/tests_disabled_tcpseg.json b/tests/tests_disabled_tcpseg.json index 8f3ffd4bb..2680d6a70 100644 --- a/tests/tests_disabled_tcpseg.json +++ b/tests/tests_disabled_tcpseg.json @@ -193,6 +193,18 @@ "name": "tests.server_connections", "reason": "These tests should not be run with TCP segmentation. These tests check a connection level." }, + { + "name": "tests.client_mem.test_client_mem.TestBlockByMemExceededHttp.test_response", + "reason": "These tests should not be run with TCP segmentation. In case of segmentation we drop request, not response." + }, + { + "name": "tests.client_mem.test_client_mem.TestBlockByMemExceededHttps.test_response", + "reason": "These tests should not be run with TCP segmentation. In case of segmentation we drop request, not response." + }, + { + "name": "tests.client_mem.test_client_mem.TestBlockByMemExceededH2.test_response", + "reason": "These tests should not be run with TCP segmentation. In case of segmentation we drop request, not response." + }, { "name": "tests.tls.test_tls_integrity.ProxyH2", "reason": "Disabled by issue #1714" From e909e1bdc2e723807c75b7aec59b0cde271d741f Mon Sep 17 00:00:00 2001 From: EvgeniiMekhanik Date: Thu, 30 Apr 2026 18:45:24 +0300 Subject: [PATCH 10/10] Fix according review - Use threading to create tasks. --- framework/deproxy/deproxy_client.py | 4 ++-- framework/test_suite/tester.py | 33 +++++++++++----------------- tests/client_mem/test_client_mem.py | 17 ++++++++------ tests/http2_general/test_h2_frame.py | 4 ++-- tests/reconf/test_reconf_base.py | 18 +++++++++------ 5 files changed, 38 insertions(+), 38 deletions(-) diff --git a/framework/deproxy/deproxy_client.py b/framework/deproxy/deproxy_client.py index c2a0115d8..b74420ec4 100644 --- a/framework/deproxy/deproxy_client.py +++ b/framework/deproxy/deproxy_client.py @@ -608,8 +608,8 @@ def make_request( self.stream_id += 2 self._valid_req_num += 1 - def ping(self): - self.h2_connection.ping(opaque_data=b"\x00\x01\x02\x03\x04\x05\x06\x07") + def send_ping(self, data: bytes = b"\x00\x01\x02\x03\x04\x05\x06\x07") -> None: + self.h2_connection.ping(opaque_data=data) self.send_bytes(self.h2_connection.data_to_send()) self.h2_connection.clear_outbound_data_buffer() diff --git a/framework/test_suite/tester.py b/framework/test_suite/tester.py index 8ea929511..bae1bbe7f 100644 --- a/framework/test_suite/tester.py +++ b/framework/test_suite/tester.py @@ -7,6 +7,7 @@ import re import signal import subprocess +import threading import typing import unittest from unittest.util import strclass @@ -150,8 +151,7 @@ class TempestaTest(WaitUntilAsserts, unittest.IsolatedAsyncioTestCase): Tempesta tests should have: 1) backends: [...] 2) clients: [...] - 3) tasks: [...] - 4) several test functions. + 3) several test functions. function name should start with 'test' Verbose documentation is placed in README.md @@ -161,8 +161,6 @@ class TempestaTest(WaitUntilAsserts, unittest.IsolatedAsyncioTestCase): clients: list[dict] = [] - tasks: list[tuple(asyncio.Task, asyncio.event)] = [] - tempesta = {"type": "tempesta", "config": "", "tfw_config": tfw.TfwLogger()} def __init_subclass__(cls, base=False, **kwargs): @@ -379,27 +377,21 @@ def start_all_clients(self): raise Exception("Can not start client %s" % cid) def create_task(self, func): - stop_event = asyncio.Event() - - async def wrapper(): - try: - while not stop_event.is_set(): - await func() - except asyncio.CancelledError: - pass - except Exception as e: - raise RuntimeError("Task failed") from e + stop_event = threading.Event() - task = asyncio.create_task(wrapper()) - self.tasks.append((task, stop_event)) + task = threading.Thread(target=func, args=(stop_event,)) + self.__tasks.append((task, stop_event)) - return task, stop_event + return task async def __cleanup_tasks(self): - for task, stop_event in self.tasks: + for task, stop_event in self.__tasks: stop_event.set() - await task - self.tasks.clear() + + await asyncio.gather( + *[asyncio.to_thread(task.join) for task, _ in self.__tasks if task.ident is not None] + ) + self.__tasks.clear() async def asyncSetUp(self): # `unittest.TestLoader.discover` returns initialized objects, we can't @@ -412,6 +404,7 @@ async def asyncSetUp(self): test_logger.info(f"setUp '{self.id()}'") if not await remote.wait_available(): raise Exception("Tempesta node is unavailable") + self.__tasks = [] self.__exceptions = dict() self.__servers = {} self.__clients = {} diff --git a/tests/client_mem/test_client_mem.py b/tests/client_mem/test_client_mem.py index 16e1f291a..22bf996e5 100644 --- a/tests/client_mem/test_client_mem.py +++ b/tests/client_mem/test_client_mem.py @@ -5,6 +5,7 @@ __license__ = "GPL2" import asyncio +import time import run_config from framework.helpers import error from framework.test_suite import marks, tester @@ -202,16 +203,17 @@ class TestReconfigClientMemStress(tester.TempestaTest): }, ] - async def __do_reload_impl(self, base_config, i): + def __do_reload_impl(self, base_config, i): config = base_config + "client_mem 10000 20000;\n" if i % 2 == 0 else base_config self.get_tempesta().config.defconfig = config self.get_tempesta().reload() - await asyncio.sleep(1) + time.sleep(0.1) - async def __do_reload(self): + def __do_reload(self, stop_event): base_config = self.get_tempesta().config.defconfig - await self.__do_reload_impl(base_config, 0) - await self.__do_reload_impl(base_config, 1) + while not stop_event.is_set(): + self.__do_reload_impl(base_config, 0) + self.__do_reload_impl(base_config, 1) async def test_under_load(self): """ @@ -222,7 +224,8 @@ async def test_under_load(self): """ await self.start_all_services(client=False) client = self.get_client("gflood") - self.create_task(self.__do_reload) + task = self.create_task(self.__do_reload) + task.start() client.start() await self.wait_while_busy(client) client.stop() @@ -316,7 +319,7 @@ async def test(self): client = self.get_client("deproxy") for _ in range(0, ping_count): - client.ping() + client.send_ping() await client.wait_for_connection_close(strict=True) diff --git a/tests/http2_general/test_h2_frame.py b/tests/http2_general/test_h2_frame.py index a2607ef13..81f51229a 100644 --- a/tests/http2_general/test_h2_frame.py +++ b/tests/http2_general/test_h2_frame.py @@ -836,7 +836,7 @@ async def test(self, name, header, token): stream_id = client.stream_id client.make_request(self.get_request) for _ in range(0, ping_count): - client.ping() + client.send_ping() self.assertTrue(await client.wait_for_headers_frame(stream_id)) self.assertTrue(await client.wait_for_ping_frames(ping_count)) @@ -845,7 +845,7 @@ async def test(self, name, header, token): self.assertTrue(await client.wait_for_ack_settings()) for _ in range(0, ping_count): - client.ping() + client.send_ping() self.assertTrue(await client.wait_for_headers_frame(stream_id)) self.assertTrue(await client.wait_for_ping_frames(2 * ping_count)) diff --git a/tests/reconf/test_reconf_base.py b/tests/reconf/test_reconf_base.py index de5b7b076..b4374f776 100644 --- a/tests/reconf/test_reconf_base.py +++ b/tests/reconf/test_reconf_base.py @@ -3,7 +3,8 @@ __license__ = "GPL2" import asyncio -import threading +import time + from framework.helpers import analyzer, dmesg, error, port_checks, remote from framework.helpers.analyzer import PSH, TCP @@ -294,11 +295,13 @@ class TestListenStartFail(tester.TempestaTest): }, ] - async def __heavy_load(self): + def __heavy_load(self, stop_event): curl = self.get_client("curl") - curl.start() - await self.wait_while_busy(curl) - curl.stop() + + while not stop_event.is_set(): + curl.start() + time.sleep(0.1) + curl.stop() async def asyncSetUp(self): await super().asyncSetUp() @@ -317,7 +320,8 @@ async def test_start_failed_under_heavy_load(self): server.start() self.deproxy_manager.start() - self.create_task(self.__heavy_load) + task = self.create_task(self.__heavy_load) + task.start() self.get_tempesta().config.set_defconfig( f""" @@ -2029,7 +2033,7 @@ async def test_decrease(self): self.assertTrue(await client.wait_for_ack_settings()) for _ in range(0, 10000): - client.ping() + client.send_ping() tempesta.config.set_defconfig(old_config) tempesta.reload()