From a22473ff08fc4af6eef9016b8e16ad5bbb6b50a7 Mon Sep 17 00:00:00 2001 From: Pavel Kostyuchenko Date: Mon, 17 Jan 2022 04:41:16 +0200 Subject: [PATCH 1/7] Test for the new caching configuration options (#810) --- cache/__init__.py | 3 +- cache/test_cache_control.py | 185 ++++++++++++++++++++++++++++++++++++ 2 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 cache/test_cache_control.py diff --git a/cache/__init__.py b/cache/__init__.py index 2e82e017b..b0ecdd5d8 100644 --- a/cache/__init__.py +++ b/cache/__init__.py @@ -1,3 +1,4 @@ -__all__ = ['test_cache', 'test_cache_methods', 'test_purge', 'test_conditional', 'test_cache_deception_attack'] +__all__ = ['test_cache', 'test_cache_control', 'test_cache_methods', 'test_purge', + 'test_conditional', 'test_cache_deception_attack'] # vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4 diff --git a/cache/test_cache_control.py b/cache/test_cache_control.py new file mode 100644 index 000000000..079b1914e --- /dev/null +++ b/cache/test_cache_control.py @@ -0,0 +1,185 @@ +"""Functional tests for custom processing of cached responses.""" + +from __future__ import print_function +from testers import functional +from helpers import chains + +__author__ = 'Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2021 Tempesta Technologies, Inc.' +__license__ = 'GPL2' + +class TestCacheControl(functional.FunctionalTest): + messages = 2 + # Replicated + cache_mode = 2 + + def chain(self, uri='/', cache_allowed=True): + if self.cache_mode == 0: + cache_allowed = False + # cache_allowed = True when caching is forbidden would lead to umbiguous + # error with empty received "response". + if cache_allowed: + test_chains = chains.cache_repeated(self.messages, uri=uri) + else: + test_chains = chains.proxy_repeated(self.messages, uri=uri) + return test_chains + + # cache_resp_hdr_del option + def common_cache_hdr_del(self, cache_allowed, hdr_val, hdr_del=''): + cache_allowed_options = { False: 'cache_bypass', True: 'cache_fulfill' } + hdr_kept = hdr_del == '' + config = ('cache %d;\n' % self.cache_mode + + '%s * *;\n' % cache_allowed_options[cache_allowed] + + hdr_del) + + chains = self.chain(cache_allowed=cache_allowed) + for chain in chains: + # chains.cache() has neither server_response nor fwd_request + if hdr_kept or chain.server_response: + chain.response.headers['Remove-me-2'] = hdr_val + chain.response.headers['Remove-me'] = hdr_val + chain.response.update() + + if chain.server_response: + chain.server_response.headers['Remove-me'] = hdr_val + chain.server_response.headers['remove-me-2'] = hdr_val + chain.server_response.update() + + self.generic_test_routine(config, chains) + + def test_cache_hdr_del_bypass(self): + self.common_cache_hdr_del(cache_allowed=False, \ + hdr_del='cache_resp_hdr_del remove-me Remove-me-2;\n', hdr_val='2 ') + + def test_cache_hdr_del_fulfill(self): + self.common_cache_hdr_del(cache_allowed=True, \ + hdr_del='cache_resp_hdr_del remove-me Remove-me-2;\n', hdr_val='2 ') + + def test_cache_hdr_del_fulfill2(self): + self.common_cache_hdr_del(cache_allowed=True, \ + hdr_del='cache_resp_hdr_del remove-me Remove-me-2;\n', hdr_val='2') + + # This test does a regular caching without additional processing, + # however, the regular caching might not work correctly for + # empty 'Remove-me' header value due to a bug in message fixups (see #530). + def test_cache_bypass(self): + self.common_cache_hdr_del(cache_allowed=False, hdr_val='') + + def test_cache_fulfill(self): + self.common_cache_hdr_del(cache_allowed=True, hdr_val='') + + def test_cache_fulfill2(self): + self.common_cache_hdr_del(cache_allowed=True, hdr_val='2') + + + ####################################################### + # cache_control_ignore + def common_no_cache(self, cache_allowed=True, force_cache=None, + cache_dir='', req_cache_dir='', cache_config=''): + cache_allowed_options = { False: 'cache_bypass', True: 'cache_fulfill' } + config = ('cache %d;\n' % self.cache_mode + + '%s * *;\n' % (cache_allowed_options[cache_allowed]) + + cache_config) + + if force_cache is not None: + cache_allowed = force_cache + chains = self.chain(cache_allowed=cache_allowed) + for chain in chains: + if req_cache_dir != '': + chain.request.headers['Cache-Control'] = req_cache_dir + chain.request.update() + if chain.fwd_request: + chain.fwd_request.headers['Cache-Control'] = req_cache_dir + chain.fwd_request.update() + + # do we pass the original Cache-Control directives downstream? + if cache_dir != '': + chain.response.headers['Cache-Control'] = cache_dir + chain.response.update() + + if chain.server_response: + if cache_dir != '': + chain.server_response.headers['Cache-Control'] = cache_dir + chain.server_response.update() + + self.generic_test_routine(config, chains) + + def test_req_no_store_fulfill(self): + self.common_no_cache(req_cache_dir='no-store', force_cache=False) + + def test_private_fulfill(self): + self.common_no_cache(cache_dir='private', force_cache=False) + + def test_no_cache_fulfill(self): + self.common_no_cache(cache_dir='no-cache', force_cache=False) + + def test_no_store_fulfill(self): + self.common_no_cache(cache_dir='no-store', force_cache=False) + + def test_no_cache_ignore_fulfill(self): + self.common_no_cache(cache_dir='no-cache', force_cache=True, \ + cache_config='cache_control_ignore no-cache;\n') + + def test_no_cache_ignore_fulfill(self): + self.common_no_cache(cache_dir='no-cache, private, no-store', + force_cache=True, \ + cache_config='cache_control_ignore no-cache private no-store;\n') + + ####################################################### + # Cache-Control: no-cache and private with arguments + def common_no_cache_argument(self, cache_allowed, hdr_val, cache_dir='', + hdr_kept=True, hdr2_kept=True): + cache_allowed_options = { False: 'cache_bypass', True: 'cache_fulfill' } + config = ('cache %d;\n' % self.cache_mode + + '%s * *;\n' % cache_allowed_options[cache_allowed]) + + chains = self.chain(cache_allowed=cache_allowed) + for chain in chains: + # chains.cache() has neither server_response nor fwd_request + if hdr_kept or chain.server_response: + chain.response.headers['Remove-me'] = hdr_val + chain.response.update() + if hdr2_kept or chain.server_response: + chain.response.headers['Remove-me-2'] = '"' + chain.response.update() + if cache_dir != '': + chain.response.headers['Cache-Control'] = cache_dir + chain.response.update() + + if chain.server_response: + chain.server_response.headers['Remove-me'] = hdr_val + chain.server_response.headers['Remove-me-2'] = '"' + if cache_dir != '': + chain.server_response.headers['Cache-Control'] = cache_dir + chain.server_response.update() + + self.generic_test_routine(config, chains) + + def test_no_cache_arg_bypass(self): + self.common_no_cache_argument(cache_allowed=False, \ + cache_dir='no-cache="remove-me"', hdr_kept=False, hdr_val='') + + def test_no_cache_arg_fulfill(self): + self.common_no_cache_argument(cache_allowed=True, \ + cache_dir='no-cache="remove-me"', hdr_kept=False, hdr_val='') + + def test_no_cache_arg_fulfill2(self): + self.common_no_cache_argument(cache_allowed=True, \ + cache_dir='no-cache="remove-me"', hdr_kept=False, hdr_val='"arg"') + + def test_private_arg_bypass(self): + self.common_no_cache_argument(cache_allowed=False, \ + cache_dir='private="remove-me"', hdr_kept=False, hdr_val='') + + def test_private_arg_fulfill(self): + self.common_no_cache_argument(cache_allowed=True, \ + cache_dir='private="remove-me"', hdr_kept=False, hdr_val='') + + def test_private_arg_fulfill2(self): + self.common_no_cache_argument(cache_allowed=True, \ + cache_dir='private="remove-me"', hdr_kept=False, hdr_val='=') + + def test_private_arg_fulfill2(self): + self.common_no_cache_argument(cache_allowed=True, \ + cache_dir='no-cache="remove-me, Remove-me-2"', hdr_val='=', \ + hdr_kept=False, hdr2_kept=False) \ No newline at end of file From 25ab58e1c2918a77e7bbc2ee1afdb66dda524dab Mon Sep 17 00:00:00 2001 From: Pavel Kostyuchenko Date: Mon, 31 Jan 2022 17:03:11 +0200 Subject: [PATCH 2/7] Complete rework of #810 tests Ported to "framework". Extended the test suit for additional Cache-Control directives. Added PURGE-GET tests for header removal. --- cache/test_cache_control.py | 779 ++++++++++++++++++++++++++++-------- cache/test_purge.py | 61 ++- framework/tester.py | 14 +- 3 files changed, 676 insertions(+), 178 deletions(-) diff --git a/cache/test_cache_control.py b/cache/test_cache_control.py index 079b1914e..35496b7f3 100644 --- a/cache/test_cache_control.py +++ b/cache/test_cache_control.py @@ -1,185 +1,612 @@ """Functional tests for custom processing of cached responses.""" from __future__ import print_function -from testers import functional -from helpers import chains +from framework import tester +import copy +import time __author__ = 'Tempesta Technologies, Inc.' -__copyright__ = 'Copyright (C) 2021 Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2022 Tempesta Technologies, Inc.' __license__ = 'GPL2' -class TestCacheControl(functional.FunctionalTest): - messages = 2 - # Replicated - cache_mode = 2 - - def chain(self, uri='/', cache_allowed=True): - if self.cache_mode == 0: - cache_allowed = False - # cache_allowed = True when caching is forbidden would lead to umbiguous - # error with empty received "response". - if cache_allowed: - test_chains = chains.cache_repeated(self.messages, uri=uri) +class TestCacheControl(tester.TempestaTest): + clients = [ + { + 'id' : 'deproxy', + 'type' : 'deproxy', + 'addr' : "${tempesta_ip}", + 'port' : '80' + } + ] + + tempesta_template = { + 'config' : + """ + server ${general_ip}:8000; + cache 2; + %(tempesta_config)s + """ + } + + backends_template = [ + { + 'id' : 'deproxy', + 'type' : 'deproxy', + 'port' : '8000', + 'response' : 'static', + 'response_content' : + 'HTTP/1.1 200 OK\r\n' + 'Server-id: deproxy\r\n' + 'Content-Length: 0\r\n' + '%(response_headers)s\r\n' + }, + ] + + tempesta_config = ''' + cache_fulfill * *; + ''' + + request_headers = {} + response_headers = {} + response_status = '200' + should_cache = False # True means Tempesta Fw should make no forward the + # request upstream + sleep_interval = None + second_request_headers = None + cached_headers = None + cached_status = '200' + + def start_all(self): + self.start_all_servers() + self.start_tempesta() + self.start_all_clients() + self.deproxy_manager.start() + self.assertTrue(self.wait_all_connections()) + + def setUp(self): + self.tempesta = copy.deepcopy(self.tempesta_template) + self.tempesta['config'] = self.tempesta['config'] % \ + {'tempesta_config': self.tempesta_config or ''} + + self.backends = copy.deepcopy(self.backends_template) + headers = '' + for name, val in self.response_headers.iteritems(): + headers += '%s: %s\r\n' % (name, '' if val is None else val) + self.backends[0]['response_content'] = \ + self.backends[0]['response_content'] % {'response_headers': headers} + + if getattr(self, 'cached_headers', None) is None: + self.cached_headers = self.response_headers + if getattr(self, 'second_request_headers', None) is None: + self.second_request_headers = self.request_headers + + super(TestCacheControl, self).setUp() + + def client_send_req(self, client, req): + curr_responses = len(client.responses) + client.make_requests(req) + client.wait_for_response(timeout=1) + self.assertEqual(curr_responses + 1, len(client.responses)) + + return client.responses[-1] + + def check_response_headers(self, response): + for name, val in self.response_headers.iteritems(): + actual_val = response.headers.get(name, None) + if actual_val is None: + self.assertIsNone(actual_val, + "{} header is missing in the response".format(name)) + else: + self.assertIsNotNone(actual_val, + "{} header is present in the response".format(name)) + + def check_cached_response_headers(self, response): + for name, val in self.cached_headers.iteritems(): + actual_val = response.headers.get(name, None) + if actual_val is None: + self.assertIsNone(actual_val, + "{} header is missing in the cached response". \ + format(name)) + else: + self.assertIsNotNone(actual_val, + "{} header is present in the cached response". \ + format(name)) + + def _test(self): + self.start_all() + client = self.get_client('deproxy') + srv = self.get_server('deproxy') + + req_headers = '' + if self.request_headers: + for name, val in self.request_headers.iteritems(): + req_headers += '%s: %s\r\n' % (name, '' if val is None else val) + req = ("GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "%s\r\n" % req_headers) + + response = self.client_send_req(client, req) + self.assertEqual(response.status, self.response_status, + "request failed: {}, expected {}" \ + .format(response.status, self.response_status)) + self.check_response_headers(response) + + if self.sleep_interval: + time.sleep(self.sleep_interval) + + cached_response = self.client_send_req(client, req) + self.assertEqual(response.status, self.cached_status, + "request for cache failed: {}, expected {}" \ + .format(response.status, self.cached_status)) + + self.assertEqual(2, len(client.responses), "response lost") + if self.should_cache: + self.assertEqual(1, len(srv.requests), + "response not cached as expected") else: - test_chains = chains.proxy_repeated(self.messages, uri=uri) - return test_chains - - # cache_resp_hdr_del option - def common_cache_hdr_del(self, cache_allowed, hdr_val, hdr_del=''): - cache_allowed_options = { False: 'cache_bypass', True: 'cache_fulfill' } - hdr_kept = hdr_del == '' - config = ('cache %d;\n' % self.cache_mode + - '%s * *;\n' % cache_allowed_options[cache_allowed] + - hdr_del) - - chains = self.chain(cache_allowed=cache_allowed) - for chain in chains: - # chains.cache() has neither server_response nor fwd_request - if hdr_kept or chain.server_response: - chain.response.headers['Remove-me-2'] = hdr_val - chain.response.headers['Remove-me'] = hdr_val - chain.response.update() - - if chain.server_response: - chain.server_response.headers['Remove-me'] = hdr_val - chain.server_response.headers['remove-me-2'] = hdr_val - chain.server_response.update() - - self.generic_test_routine(config, chains) - - def test_cache_hdr_del_bypass(self): - self.common_cache_hdr_del(cache_allowed=False, \ - hdr_del='cache_resp_hdr_del remove-me Remove-me-2;\n', hdr_val='2 ') - - def test_cache_hdr_del_fulfill(self): - self.common_cache_hdr_del(cache_allowed=True, \ - hdr_del='cache_resp_hdr_del remove-me Remove-me-2;\n', hdr_val='2 ') - - def test_cache_hdr_del_fulfill2(self): - self.common_cache_hdr_del(cache_allowed=True, \ - hdr_del='cache_resp_hdr_del remove-me Remove-me-2;\n', hdr_val='2') - - # This test does a regular caching without additional processing, - # however, the regular caching might not work correctly for - # empty 'Remove-me' header value due to a bug in message fixups (see #530). - def test_cache_bypass(self): - self.common_cache_hdr_del(cache_allowed=False, hdr_val='') - - def test_cache_fulfill(self): - self.common_cache_hdr_del(cache_allowed=True, hdr_val='') - - def test_cache_fulfill2(self): - self.common_cache_hdr_del(cache_allowed=True, hdr_val='2') - - - ####################################################### - # cache_control_ignore - def common_no_cache(self, cache_allowed=True, force_cache=None, - cache_dir='', req_cache_dir='', cache_config=''): - cache_allowed_options = { False: 'cache_bypass', True: 'cache_fulfill' } - config = ('cache %d;\n' % self.cache_mode + - '%s * *;\n' % (cache_allowed_options[cache_allowed]) + - cache_config) - - if force_cache is not None: - cache_allowed = force_cache - chains = self.chain(cache_allowed=cache_allowed) - for chain in chains: - if req_cache_dir != '': - chain.request.headers['Cache-Control'] = req_cache_dir - chain.request.update() - if chain.fwd_request: - chain.fwd_request.headers['Cache-Control'] = req_cache_dir - chain.fwd_request.update() - - # do we pass the original Cache-Control directives downstream? - if cache_dir != '': - chain.response.headers['Cache-Control'] = cache_dir - chain.response.update() - - if chain.server_response: - if cache_dir != '': - chain.server_response.headers['Cache-Control'] = cache_dir - chain.server_response.update() - - self.generic_test_routine(config, chains) - - def test_req_no_store_fulfill(self): - self.common_no_cache(req_cache_dir='no-store', force_cache=False) - - def test_private_fulfill(self): - self.common_no_cache(cache_dir='private', force_cache=False) + self.assertEqual(2, len(srv.requests), + "response is cached while it should not") - def test_no_cache_fulfill(self): - self.common_no_cache(cache_dir='no-cache', force_cache=False) - - def test_no_store_fulfill(self): - self.common_no_cache(cache_dir='no-store', force_cache=False) - - def test_no_cache_ignore_fulfill(self): - self.common_no_cache(cache_dir='no-cache', force_cache=True, \ - cache_config='cache_control_ignore no-cache;\n') - - def test_no_cache_ignore_fulfill(self): - self.common_no_cache(cache_dir='no-cache, private, no-store', - force_cache=True, \ - cache_config='cache_control_ignore no-cache private no-store;\n') - - ####################################################### - # Cache-Control: no-cache and private with arguments - def common_no_cache_argument(self, cache_allowed, hdr_val, cache_dir='', - hdr_kept=True, hdr2_kept=True): - cache_allowed_options = { False: 'cache_bypass', True: 'cache_fulfill' } - config = ('cache %d;\n' % self.cache_mode + - '%s * *;\n' % cache_allowed_options[cache_allowed]) - - chains = self.chain(cache_allowed=cache_allowed) - for chain in chains: - # chains.cache() has neither server_response nor fwd_request - if hdr_kept or chain.server_response: - chain.response.headers['Remove-me'] = hdr_val - chain.response.update() - if hdr2_kept or chain.server_response: - chain.response.headers['Remove-me-2'] = '"' - chain.response.update() - if cache_dir != '': - chain.response.headers['Cache-Control'] = cache_dir - chain.response.update() - - if chain.server_response: - chain.server_response.headers['Remove-me'] = hdr_val - chain.server_response.headers['Remove-me-2'] = '"' - if cache_dir != '': - chain.server_response.headers['Cache-Control'] = cache_dir - chain.server_response.update() - - self.generic_test_routine(config, chains) - - def test_no_cache_arg_bypass(self): - self.common_no_cache_argument(cache_allowed=False, \ - cache_dir='no-cache="remove-me"', hdr_kept=False, hdr_val='') - - def test_no_cache_arg_fulfill(self): - self.common_no_cache_argument(cache_allowed=True, \ - cache_dir='no-cache="remove-me"', hdr_kept=False, hdr_val='') - - def test_no_cache_arg_fulfill2(self): - self.common_no_cache_argument(cache_allowed=True, \ - cache_dir='no-cache="remove-me"', hdr_kept=False, hdr_val='"arg"') - - def test_private_arg_bypass(self): - self.common_no_cache_argument(cache_allowed=False, \ - cache_dir='private="remove-me"', hdr_kept=False, hdr_val='') - - def test_private_arg_fulfill(self): - self.common_no_cache_argument(cache_allowed=True, \ - cache_dir='private="remove-me"', hdr_kept=False, hdr_val='') - - def test_private_arg_fulfill2(self): - self.common_no_cache_argument(cache_allowed=True, \ - cache_dir='private="remove-me"', hdr_kept=False, hdr_val='=') - - def test_private_arg_fulfill2(self): - self.common_no_cache_argument(cache_allowed=True, \ - cache_dir='no-cache="remove-me, Remove-me-2"', hdr_val='=', \ - hdr_kept=False, hdr2_kept=False) \ No newline at end of file + if self.should_cache: + self.check_cached_response_headers(cached_response) + else: + self.check_response_headers(cached_response) + +######################################################### +# cache_resp_hdr_del +class CacheHdrDelBypass(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_bypass * *; + cache_resp_hdr_del set-cookie Remove-me-2; + ''' + response_headers = {'Set-Cookie': 'cookie=2; a=b', 'Remove-me-2': ''} + should_cache = False + +class CacheHdrDelFulfill(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_resp_hdr_del set-cookie Remove-me-2; + ''' + response_headers = {'Set-Cookie': 'cookie=2; a=b', 'Remove-me-2': ''} + cached_headers = {'Set-Cookie': None, 'Remove-me-2': None} + should_cache = True + +class CacheHdrDelFulfill2(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_resp_hdr_del set-cookie Remove-me-2; + ''' + response_headers = {'Set-Cookie': 'cookie=2; a=b', 'Remove-me-2': '2'} + cached_headers = {'Set-Cookie': None, 'Remove-me-2': None} + should_cache = True + +# This test does a regular caching without additional processing, +# however, the regular caching might not work correctly for +# empty 'Remove-me' header value due to a bug in message fixups (see #530). +class TestCacheBypass(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_bypass * *; + ''' + response_headers = {'Remove-me': '', 'Remove-me-2': ''} + should_cache = False + +class TestCacheFulfill(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Remove-me': '', 'Remove-me-2': ''} + should_cache = True + +class TestCacheFulfill2(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Remove-me': '2', 'Remove-me-2': '2'} + should_cache = True + +######################################################### +# cache_control_ignore +######### +# request +class RequestMaxAgeBypass(TestCacheControl, tester.SingleTest): + request_headers = {'Cache-control': 'max-age=1'} + response_headers = {'Cache-control': 'max-age=2'} + sleep_interval = 1.5 + should_cache = False + +class RequestMaxAgeFulfill(TestCacheControl, tester.SingleTest): + request_headers = {'Cache-control': 'max-age=1'} + response_headers = {'Cache-control': 'max-age=2'} + sleep_interval = None + should_cache = True + +class RequestMaxAgeMaxStaleBypass(TestCacheControl, tester.SingleTest): + request_headers = {'Cache-control': 'max-age=3, max-stale=1'} + response_headers = {'Cache-control': 'max-age=1'} + sleep_interval = 2.5 + should_cache = False + +class RequestMaxAgeMaxStaleFulfill(TestCacheControl, tester.SingleTest): + request_headers = {'Cache-control': 'max-age=3, max-stale=1'} + response_headers = {'Cache-control': 'max-age=1'} + sleep_interval = 1.5 + should_cache = True + +class RequestMaxStaleFulfill(TestCacheControl, tester.SingleTest): + request_headers = {'Cache-control': 'max-stale'} + response_headers = {'Cache-control': 'max-age=1'} + sleep_interval = 1.5 + should_cache = True + +# min-fresh +class RequestMinFreshBypass(TestCacheControl, tester.SingleTest): + request_headers = {'Cache-control': 'min-fresh=1'} + response_headers = {'Cache-control': 'max-age=2'} + sleep_interval = 1.5 + should_cache = False + +class RequestMinFreshFulfill(TestCacheControl, tester.SingleTest): + request_headers = {'Cache-control': 'min-fresh=1'} + response_headers = {'Cache-control': 'max-age=2'} + sleep_interval = None + should_cache = True + +class RequestOnlyIfCached(TestCacheControl, tester.SingleTest): + request_headers = {'Cache-control': 'max-age=1'} + response_headers = {'Cache-control': 'max-age=2'} + sleep_interval = None + second_request_headers = {'Cache-control': 'max-age=1, only-if-cached'} + cached_status = '200' + should_cache = True + +class RequestOnlyIfCached504(TestCacheControl, tester.SingleTest): + request_headers = {'Cache-control': 'max-age=1'} + response_headers = {'Cache-control': 'max-age=2'} + sleep_interval = 1.5 + second_request_headers = {'Cache-control': 'max-age=1, only-if-cached'} + cached_status = '504' + should_cache = True + +class RequestNoStore(TestCacheControl, tester.SingleTest): + request_headers = {'Cache-control': 'no-store'} + should_cache = False + +########## +# response +# must-revalidate +class ResponseMustRevalidateBypass(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {} + response_headers = {'Cache-control': 'max-age=1, must-revalidate'} + sleep_interval = 1.5 + should_cache = False + +class ResponseMustRevalidateBypass2(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {'Cache-control': 'max-stale=1'} + response_headers = {'Cache-control': 'max-age=1, must-revalidate'} + sleep_interval = 1.5 + should_cache = False + +class ResponseMustRevalidateFulfill(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {'Cache-control': 'max-stale=1'} + response_headers = {'Cache-control': 'max-age=1, must-revalidate'} + should_cache = True + sleep_interval = None + +class ResponseMustRevalidateFulfill2(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {} + response_headers = {'Cache-control': 'max-age=1, must-revalidate'} + sleep_interval = None + should_cache = True + +class ResponseMustRevalidateIgnore(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore must-revalidate; + ''' + request_headers = {} + response_headers = {'Cache-control': 'max-age=1, must-revalidate'} + sleep_interval = 1.5 + should_cache = True + +# proxy-revalidate +class ResponseMustRevalidateBypass(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {} + response_headers = {'Cache-control': 'max-age=1, proxy-revalidate'} + sleep_interval = 1.5 + should_cache = False + +class ResponseMustRevalidateBypass2(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {'Cache-control': 'max-stale=1'} + response_headers = {'Cache-control': 'max-age=1, proxy-revalidate'} + sleep_interval = 1.5 + should_cache = False + +class ResponseMustRevalidateIgnore(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore proxy-revalidate; + ''' + request_headers = {} + response_headers = {'Cache-control': 'max-age=1, proxy-revalidate'} + sleep_interval = 1.5 + should_cache = True + +# multiple directives +class ResponseMustRevalidateHalfIgnore(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore proxy-revalidate; + ''' + request_headers = {} + response_headers = {'Cache-control': + 'max-age=1, must-revalidate, proxy-revalidate'} + sleep_interval = 1.5 + should_cache = False + +class ResponseMustRevalidateMultiIgnore(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore proxy-revalidate must-revalidate; + ''' + request_headers = {} + response_headers = {'Cache-control': + 'max-age=1, must-revalidate, proxy-revalidate'} + sleep_interval = 1.5 + should_cache = True + +# max-age/s-maxage +class ResponseMaxAgeBypass(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Cache-control': 'max-age=1'} + sleep_interval = 1.5 + should_cache = False + +class ResponseMaxAgeFulfill(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Cache-control': 'max-age=1'} + sleep_interval = None + should_cache = True + +class ResponseMaxAgeIgnore(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore max-age; + ''' + response_headers = {'Cache-control': 'max-age=1'} + sleep_interval = 1.5 + should_cache = True + +class ResponseMaxageBypass(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Cache-control': 's-maxage=1'} + sleep_interval = 1.5 + should_cache = False + +class ResponseSMaxageFulfill(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Cache-control': 's-maxage=1'} + sleep_interval = None + should_cache = True + +class ResponseSMaxageIgnore(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore max-age; + ''' + response_headers = {'Cache-control': 's-maxage=1'} + sleep_interval = 1.5 + should_cache = True + +class ResponseSMaxageMaxAgeBypass(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore max-age; + ''' + response_headers = {'Cache-control': 'max-age=1, s-maxage=1'} + sleep_interval = 1.5 + should_cache = False + +class ResponseSMaxageMaxAgeIgnore(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore max-age s-maxage; + ''' + response_headers = {'Cache-control': 'max-age=1, s-maxage=1'} + sleep_interval = 1.5 + should_cache = True + +# private/no-cache/no-store +class ResponsePrivate(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Cache-control': 'private'} + should_cache = False + +class ResponseNoCache(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Cache-control': 'no-cache'} + should_cache = False + +class ResponseNoStore(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Cache-control': 'no-store'} + should_cache = False + +class ResponseNoCacheIgnore(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore no-cache; + ''' + response_headers = {'Cache-control': 'no-cache'} + should_cache = True + +# multiple cache_control_ignore directives +class ResponseNoCacheIgnoreMulti(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore max-age no-cache; + ''' + response_headers = {'Cache-control': 'no-cache'} + should_cache = True + +class ResponseMultipleNoCacheIgnore(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore no-cache private no-store; + ''' + response_headers = {'Cache-control': 'no-cache, private, no-store'} + should_cache = True + +#public +class ResponsePublicBypass(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {'Authorization': 'asd'} + response_headers = {} + should_cache = False + +class ResponsePublicFullfill(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {'Authorization': 'asd'} + response_headers = {'Cache-control': 'public'} + should_cache = True + +class ResponsePublicIgnore(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore public; + ''' + request_headers = {'Authorization': 'asd'} + response_headers = {'Cache-control': 'public'} + should_cache = False + +# multiple cache_control_ignore directives +class ResponsePublicIgnore2(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore must-revalidate public; + ''' + request_headers = {'Authorization': 'asd'} + response_headers = {'Cache-control': 'public'} + should_cache = False + +######################################################### +# Cache-Control: no-cache and private with arguments +############# +# no-cache +class ArgNoCacheBypass(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_bypass * *; + ''' + response_headers = {'Remove-me': '', 'Remove-me-2': '', + 'Cache-control': 'no-cache="remove-me"'} + should_cache = False + +class ArgNoCacheFulfill(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Remove-me': '', 'Remove-me-2': '', + 'Cache-control': 'no-cache="remove-me"'} + cached_headers = {'Remove-me': None, 'Remove-me-2': '', + 'Cache-control': 'no-cache="remove-me"'} + should_cache = True + +class ArgNoCacheFulfill2(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Remove-me': '"arg"', 'Remove-me-2': '"arg"', + 'Cache-control': 'no-cache="remove-me"'} + cached_headers = {'Remove-me': None, 'Remove-me-2': '"arg"', + 'Cache-control': 'no-cache="remove-me"'} + should_cache = True + +class ArgNoCacheFulfill3(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Cache-Control': + 'public, no-cache="Set-Cookie", must-revalidate, max-age=120', + 'Set-Cookie': 'some=cookie'} + cached_headers = { + 'Cache-Control': + 'public, no-cache="Set-Cookie", must-revalidate, max-age=120', + 'Set-Cookie': None + } + should_cache = True + +############# +# private +class ArgPrivateBypass(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_bypass * *; + ''' + response_headers = {'Set-cookie': 'some=cookie', 'remove-me-2': '', + 'Cache-control': 'private="set-cookie"'} + should_cache = False + +class ArgPrivateFulfill(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Set-cookie': 'some=cookie', 'remove-me-2': '', + 'Cache-control': 'private="set-cookie"'} + cached_headers = {'Set-cookie': None, 'remove-me-2': '', + 'Cache-control': 'private="set-cookie"'} + should_cache = True + +class ArgPrivateFulfill2(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Set-cookie': 'some=cookie', 'remove-me-2': '=', + 'Cache-control': 'private="set-cookie"'} + cached_headers = {'Set-cookie': None, 'remove-me-2': '=', + 'Cache-control': 'private="set-cookie"'} + should_cache = True + +# erase two headers +class ArgBothNoCacheFulfill(TestCacheControl, tester.SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Set-cookie': 'some=cookie', 'remove-me-2': '"', + 'Cache-control': 'no-cache="set-cookie, Remove-me-2"'} + cached_headers = {'Set-cookie': None, 'remove-me-2': None, + 'Cache-control': 'no-cache="set-cookie, Remove-me-2"'} + should_cache = True diff --git a/cache/test_purge.py b/cache/test_purge.py index cd97788a0..0409e854c 100644 --- a/cache/test_purge.py +++ b/cache/test_purge.py @@ -6,7 +6,7 @@ from testers import functional __author__ = 'Tempesta Technologies, Inc.' -__copyright__ = 'Copyright (C) 2017-2021 Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2017-2022 Tempesta Technologies, Inc.' __license__ = 'GPL2' # TODO: add tests for 'cache_purge_acl' @@ -20,6 +20,14 @@ class TestPurge(functional.FunctionalTest): 'cache_purge_acl %s;\n' % tf_cfg.cfg.get('Client', 'ip')) + config_hdr_del = ('cache 2;\n' + 'cache_fulfill * *;\n' + 'cache_methods GET HEAD;\n' + 'cache_purge;\n' + 'cache_purge_acl %s;\n' + 'cache_resp_hdr_del set-cookie;\n' + % tf_cfg.cfg.get('Client', 'ip')) + def chains(self): uri = '/page.html' result = [ @@ -92,6 +100,57 @@ def test_purge_get_update(self): c.update() self.generic_test_routine(self.config, ch) + def test_purge_get_update_hdr_del(self): + # Similar PURGE-GET test, but with Set-Cookie header removed via config + uri = '/page.html' + page = 'Another page text!\n' + ch = [ + chains.proxy(method='GET', uri=uri), + chains.cache(method='GET', uri=uri), + self.purge_get(uri), + chains.cache(method='GET', uri=uri), + ] + + c = ch[2].server_response + c.body = page + c.headers['Content-Length'] = len(page) + c.headers['Set-Cookie'] = 'somecookie=2' + c.update() + + c = ch[3].response + c.body = page + c.headers['Content-Length'] = len(page) + c.update() + + self.generic_test_routine(self.config_hdr_del, ch) + + def test_purge_get_update_cc(self): + # And another PURGE-GET test, with Set-Cookie removed due to + # no-cache="set-cookie" in the response + uri = '/page.html' + page = 'Another page text!\n' + ch = [ + chains.proxy(method='GET', uri=uri), + chains.cache(method='GET', uri=uri), + self.purge_get(uri), + chains.cache(method='GET', uri=uri), + ] + + c = ch[2].server_response + c.body = page + c.headers['Content-Length'] = len(page) + c.headers['Set-Cookie'] = 'somecookie=2' + c.headers['Cache-control'] = 'no-cache="set-cookie"' + c.update() + + c = ch[3].response + c.body = page + c.headers['Content-Length'] = len(page) + c.headers['Cache-control'] = 'no-cache="set-cookie"' + c.update() + + self.generic_test_routine(self.config_hdr_del, ch) + def test_useless_x_tempesta_cache(self): # Send an ordinary GET request with an "X-Tempesta-Cache" header, and # make sure it doesn't affect anything. diff --git a/framework/tester.py b/framework/tester.py index 21f0908de..7da59ab64 100644 --- a/framework/tester.py +++ b/framework/tester.py @@ -15,7 +15,7 @@ import struct __author__ = 'Tempesta Technologies, Inc.' -__copyright__ = 'Copyright (C) 2018-2019 Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2018-2022 Tempesta Technologies, Inc.' __license__ = 'GPL2' backend_defs = {} @@ -328,3 +328,15 @@ def wait_while_busy(self, *items): for item in items: if item.is_running(): item.wait_for_finish() + + # Should replace all duplicated instances of wait_all_connections + def wait_all_connections(self, tmt=1): + for sid in self.__servers: + srv = self.__servers[sid] + if not srv.wait_for_connections(timeout=tmt): + return False + return True + +class SingleTest(object): + def test(self): + self._test() \ No newline at end of file From 82b4501cc8ec2a80fa820ea44ac9125503690b19 Mon Sep 17 00:00:00 2001 From: Pavel Kostyuchenko Date: Wed, 2 Feb 2022 16:28:28 +0200 Subject: [PATCH 3/7] Added lots of comments and renamed test classes Also removed duplicates of wait_all_connections() and fixed incorrect dmesg string in the start_tempesta(). --- cache/test_cache_control.py | 266 +++++++++++++++++++-------------- cache/test_purge.py | 9 ++ framework/tester.py | 8 +- http_rules/test_http_tables.py | 8 - sessions/test_cookies.py | 8 - sessions/test_js_challenge.py | 7 - sessions/test_redir_mark.py | 6 - sessions/test_sessions.py | 8 - 8 files changed, 164 insertions(+), 156 deletions(-) diff --git a/cache/test_cache_control.py b/cache/test_cache_control.py index 35496b7f3..5fdf15cab 100644 --- a/cache/test_cache_control.py +++ b/cache/test_cache_control.py @@ -49,11 +49,13 @@ class TestCacheControl(tester.TempestaTest): request_headers = {} response_headers = {} response_status = '200' - should_cache = False # True means Tempesta Fw should make no forward the - # request upstream - sleep_interval = None - second_request_headers = None - cached_headers = None + should_be_cached = False # True means Tempesta Fw should make no forward the + # request upstream and serve it from cache only. + sleep_interval = None # between the first and second request. + second_request_headers = None # When the two requests differ. + cached_headers = None # Reference headers to compare with actual cached + # response. Empty if cached/second response is same as + # first one. cached_status = '200' def start_all(self): @@ -74,7 +76,7 @@ def setUp(self): headers += '%s: %s\r\n' % (name, '' if val is None else val) self.backends[0]['response_content'] = \ self.backends[0]['response_content'] % {'response_headers': headers} - + # apply default values for optional fields if getattr(self, 'cached_headers', None) is None: self.cached_headers = self.response_headers if getattr(self, 'second_request_headers', None) is None: @@ -134,183 +136,219 @@ def _test(self): if self.sleep_interval: time.sleep(self.sleep_interval) - cached_response = self.client_send_req(client, req) + req_headers2 = '' + if self.request_headers: + for name, val in self.second_request_headers.iteritems(): + req_headers2 += '%s: %s\r\n' % (name, '' if val is None else val) + req2 = ("GET / HTTP/1.1\r\n" + "Host: localhost\r\n" + "%s\r\n" % req_headers2) + cached_response = self.client_send_req(client, req2) self.assertEqual(response.status, self.cached_status, "request for cache failed: {}, expected {}" \ .format(response.status, self.cached_status)) self.assertEqual(2, len(client.responses), "response lost") - if self.should_cache: + if self.should_be_cached: self.assertEqual(1, len(srv.requests), "response not cached as expected") else: self.assertEqual(2, len(srv.requests), - "response is cached while it should not") + "response is cached while it should not be") - if self.should_cache: + if self.should_be_cached: self.check_cached_response_headers(cached_response) else: self.check_response_headers(cached_response) +class SingleTest(object): + def test(self): + self._test() + +# Naming convension for test class name: +# NameSuffix +# where "Name" is the name of the feature being tested e.g. +# "RequestMaxAge" - max-age directive in the request, +# and "Suffix" is either: +# - "Bypass" - cache_bypass is enabled and the response is never cached. +# - "Cached" - response is normally cached; +# - "NotCached" - response is normally not cached (forwarded upstream); +# - "Ignore" - the directive is disabled by cache_control_ignore, default +# behaviour ensues. +# - empty value for default behaviour, +# For example, ResponseMustRevalidateIgnore - testing "must-revalidate" +# in the request, which should be ignored due to cache_control_ignore. + ######################################################### # cache_resp_hdr_del -class CacheHdrDelBypass(TestCacheControl, tester.SingleTest): +class CacheHdrDelBypass(TestCacheControl, SingleTest): tempesta_config = ''' cache_bypass * *; cache_resp_hdr_del set-cookie Remove-me-2; ''' response_headers = {'Set-Cookie': 'cookie=2; a=b', 'Remove-me-2': ''} - should_cache = False + should_be_cached = False -class CacheHdrDelFulfill(TestCacheControl, tester.SingleTest): +class CacheHdrDelCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; cache_resp_hdr_del set-cookie Remove-me-2; ''' response_headers = {'Set-Cookie': 'cookie=2; a=b', 'Remove-me-2': ''} cached_headers = {'Set-Cookie': None, 'Remove-me-2': None} - should_cache = True + should_be_cached = True -class CacheHdrDelFulfill2(TestCacheControl, tester.SingleTest): +class CacheHdrDelCached2(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; cache_resp_hdr_del set-cookie Remove-me-2; ''' response_headers = {'Set-Cookie': 'cookie=2; a=b', 'Remove-me-2': '2'} cached_headers = {'Set-Cookie': None, 'Remove-me-2': None} - should_cache = True + should_be_cached = True # This test does a regular caching without additional processing, # however, the regular caching might not work correctly for # empty 'Remove-me' header value due to a bug in message fixups (see #530). -class TestCacheBypass(TestCacheControl, tester.SingleTest): +class TestCacheBypass(TestCacheControl, SingleTest): tempesta_config = ''' cache_bypass * *; ''' response_headers = {'Remove-me': '', 'Remove-me-2': ''} - should_cache = False + should_be_cached = False -class TestCacheFulfill(TestCacheControl, tester.SingleTest): +class TestCache(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' response_headers = {'Remove-me': '', 'Remove-me-2': ''} - should_cache = True + should_be_cached = True -class TestCacheFulfill2(TestCacheControl, tester.SingleTest): +class TestCache2(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' response_headers = {'Remove-me': '2', 'Remove-me-2': '2'} - should_cache = True + should_be_cached = True ######################################################### # cache_control_ignore ######### # request -class RequestMaxAgeBypass(TestCacheControl, tester.SingleTest): +class RequestMaxAgeNoCached(TestCacheControl, SingleTest): request_headers = {'Cache-control': 'max-age=1'} response_headers = {'Cache-control': 'max-age=2'} sleep_interval = 1.5 - should_cache = False + should_be_cached = False -class RequestMaxAgeFulfill(TestCacheControl, tester.SingleTest): +class RequestMaxAgeCached(TestCacheControl, SingleTest): request_headers = {'Cache-control': 'max-age=1'} response_headers = {'Cache-control': 'max-age=2'} sleep_interval = None - should_cache = True + should_be_cached = True -class RequestMaxAgeMaxStaleBypass(TestCacheControl, tester.SingleTest): +class RequestMaxAgeMaxStaleNotCached(TestCacheControl, SingleTest): request_headers = {'Cache-control': 'max-age=3, max-stale=1'} response_headers = {'Cache-control': 'max-age=1'} sleep_interval = 2.5 - should_cache = False + should_be_cached = False -class RequestMaxAgeMaxStaleFulfill(TestCacheControl, tester.SingleTest): +class RequestMaxAgeMaxStaleCached(TestCacheControl, SingleTest): request_headers = {'Cache-control': 'max-age=3, max-stale=1'} response_headers = {'Cache-control': 'max-age=1'} sleep_interval = 1.5 - should_cache = True + should_be_cached = True -class RequestMaxStaleFulfill(TestCacheControl, tester.SingleTest): +class RequestMaxStaleCached(TestCacheControl, SingleTest): request_headers = {'Cache-control': 'max-stale'} response_headers = {'Cache-control': 'max-age=1'} sleep_interval = 1.5 - should_cache = True + should_be_cached = True # min-fresh -class RequestMinFreshBypass(TestCacheControl, tester.SingleTest): +class RequestMinFreshNotCached(TestCacheControl, SingleTest): request_headers = {'Cache-control': 'min-fresh=1'} response_headers = {'Cache-control': 'max-age=2'} sleep_interval = 1.5 - should_cache = False + should_be_cached = False -class RequestMinFreshFulfill(TestCacheControl, tester.SingleTest): +class RequestMinFreshCached(TestCacheControl, SingleTest): request_headers = {'Cache-control': 'min-fresh=1'} response_headers = {'Cache-control': 'max-age=2'} sleep_interval = None - should_cache = True + should_be_cached = True -class RequestOnlyIfCached(TestCacheControl, tester.SingleTest): +class RequestOnlyIfCachedCached(TestCacheControl, SingleTest): request_headers = {'Cache-control': 'max-age=1'} response_headers = {'Cache-control': 'max-age=2'} sleep_interval = None second_request_headers = {'Cache-control': 'max-age=1, only-if-cached'} cached_status = '200' - should_cache = True + should_be_cached = True -class RequestOnlyIfCached504(TestCacheControl, tester.SingleTest): +class RequestOnlyIfCached504(TestCacheControl, SingleTest): request_headers = {'Cache-control': 'max-age=1'} response_headers = {'Cache-control': 'max-age=2'} sleep_interval = 1.5 second_request_headers = {'Cache-control': 'max-age=1, only-if-cached'} cached_status = '504' - should_cache = True + should_be_cached = True -class RequestNoStore(TestCacheControl, tester.SingleTest): +class RequestNoStoreNotCached(TestCacheControl, SingleTest): request_headers = {'Cache-control': 'no-store'} - should_cache = False + should_be_cached = False ########## # response +# # must-revalidate -class ResponseMustRevalidateBypass(TestCacheControl, tester.SingleTest): +# +# Per RFC 7234 "Cache-Control: max-age=0, must-revalidate" has exactly same +# semantic as "no-cache". See section 4.2.4: +# "A cache MUST NOT generate a stale response if it is prohibited by an +# explicit in-protocol directive (e.g., by a "no-store" or "no-cache" +# cache directive, a "must-revalidate" cache-response-directive, or an +# applicable "s-maxage" or "proxy-revalidate" cache-response-directive; +# see Section 5.2.2)." +# Here we test the cache behaviour for stale responses with +# "max-age=1, must-revalidate", which mandates revalidation after 1 second. +class ResponseMustRevalidateNotCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' request_headers = {} response_headers = {'Cache-control': 'max-age=1, must-revalidate'} sleep_interval = 1.5 - should_cache = False + should_be_cached = False -class ResponseMustRevalidateBypass2(TestCacheControl, tester.SingleTest): +class ResponseMustRevalidateNotCached2(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' request_headers = {'Cache-control': 'max-stale=1'} response_headers = {'Cache-control': 'max-age=1, must-revalidate'} sleep_interval = 1.5 - should_cache = False + should_be_cached = False -class ResponseMustRevalidateFulfill(TestCacheControl, tester.SingleTest): +class ResponseMustRevalidateCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' request_headers = {'Cache-control': 'max-stale=1'} response_headers = {'Cache-control': 'max-age=1, must-revalidate'} - should_cache = True + should_be_cached = True sleep_interval = None -class ResponseMustRevalidateFulfill2(TestCacheControl, tester.SingleTest): +class ResponseMustRevalidateCached2(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' request_headers = {} response_headers = {'Cache-control': 'max-age=1, must-revalidate'} sleep_interval = None - should_cache = True + should_be_cached = True -class ResponseMustRevalidateIgnore(TestCacheControl, tester.SingleTest): +class ResponseMustRevalidateIgnore(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; cache_control_ignore must-revalidate; @@ -318,28 +356,28 @@ class ResponseMustRevalidateIgnore(TestCacheControl, tester.SingleTest): request_headers = {} response_headers = {'Cache-control': 'max-age=1, must-revalidate'} sleep_interval = 1.5 - should_cache = True + should_be_cached = True # proxy-revalidate -class ResponseMustRevalidateBypass(TestCacheControl, tester.SingleTest): +class ResponseProxyRevalidateNotCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' request_headers = {} response_headers = {'Cache-control': 'max-age=1, proxy-revalidate'} sleep_interval = 1.5 - should_cache = False + should_be_cached = False -class ResponseMustRevalidateBypass2(TestCacheControl, tester.SingleTest): +class ResponseProxyRevalidateNotCached2(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' request_headers = {'Cache-control': 'max-stale=1'} response_headers = {'Cache-control': 'max-age=1, proxy-revalidate'} sleep_interval = 1.5 - should_cache = False + should_be_cached = False -class ResponseMustRevalidateIgnore(TestCacheControl, tester.SingleTest): +class ResponseProxyRevalidateIgnore(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; cache_control_ignore proxy-revalidate; @@ -347,10 +385,10 @@ class ResponseMustRevalidateIgnore(TestCacheControl, tester.SingleTest): request_headers = {} response_headers = {'Cache-control': 'max-age=1, proxy-revalidate'} sleep_interval = 1.5 - should_cache = True + should_be_cached = True # multiple directives -class ResponseMustRevalidateHalfIgnore(TestCacheControl, tester.SingleTest): +class ResponseProxyRevalidateHalfIgnore(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; cache_control_ignore proxy-revalidate; @@ -359,9 +397,9 @@ class ResponseMustRevalidateHalfIgnore(TestCacheControl, tester.SingleTest): response_headers = {'Cache-control': 'max-age=1, must-revalidate, proxy-revalidate'} sleep_interval = 1.5 - should_cache = False + should_be_cached = False -class ResponseMustRevalidateMultiIgnore(TestCacheControl, tester.SingleTest): +class ResponseProxyRevalidateMultiIgnore(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; cache_control_ignore proxy-revalidate must-revalidate; @@ -370,173 +408,173 @@ class ResponseMustRevalidateMultiIgnore(TestCacheControl, tester.SingleTest): response_headers = {'Cache-control': 'max-age=1, must-revalidate, proxy-revalidate'} sleep_interval = 1.5 - should_cache = True + should_be_cached = True # max-age/s-maxage -class ResponseMaxAgeBypass(TestCacheControl, tester.SingleTest): +class ResponseMaxAgeNotCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' response_headers = {'Cache-control': 'max-age=1'} sleep_interval = 1.5 - should_cache = False + should_be_cached = False -class ResponseMaxAgeFulfill(TestCacheControl, tester.SingleTest): +class ResponseMaxAgeCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' response_headers = {'Cache-control': 'max-age=1'} sleep_interval = None - should_cache = True + should_be_cached = True -class ResponseMaxAgeIgnore(TestCacheControl, tester.SingleTest): +class ResponseMaxAgeIgnore(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; cache_control_ignore max-age; ''' response_headers = {'Cache-control': 'max-age=1'} sleep_interval = 1.5 - should_cache = True + should_be_cached = True -class ResponseMaxageBypass(TestCacheControl, tester.SingleTest): +class ResponseMaxageNotCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' response_headers = {'Cache-control': 's-maxage=1'} sleep_interval = 1.5 - should_cache = False + should_be_cached = False -class ResponseSMaxageFulfill(TestCacheControl, tester.SingleTest): +class ResponseSMaxageCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' response_headers = {'Cache-control': 's-maxage=1'} sleep_interval = None - should_cache = True + should_be_cached = True -class ResponseSMaxageIgnore(TestCacheControl, tester.SingleTest): +class ResponseSMaxageIgnore(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; cache_control_ignore max-age; ''' response_headers = {'Cache-control': 's-maxage=1'} sleep_interval = 1.5 - should_cache = True + should_be_cached = True -class ResponseSMaxageMaxAgeBypass(TestCacheControl, tester.SingleTest): +class ResponseSMaxageMaxAgeNotCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; cache_control_ignore max-age; ''' response_headers = {'Cache-control': 'max-age=1, s-maxage=1'} sleep_interval = 1.5 - should_cache = False + should_be_cached = False -class ResponseSMaxageMaxAgeIgnore(TestCacheControl, tester.SingleTest): +class ResponseSMaxageMaxAgeIgnore(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; cache_control_ignore max-age s-maxage; ''' response_headers = {'Cache-control': 'max-age=1, s-maxage=1'} sleep_interval = 1.5 - should_cache = True + should_be_cached = True # private/no-cache/no-store -class ResponsePrivate(TestCacheControl, tester.SingleTest): +class ResponsePrivate(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' response_headers = {'Cache-control': 'private'} - should_cache = False + should_be_cached = False -class ResponseNoCache(TestCacheControl, tester.SingleTest): +class ResponseNoCache(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' response_headers = {'Cache-control': 'no-cache'} - should_cache = False + should_be_cached = False -class ResponseNoStore(TestCacheControl, tester.SingleTest): +class ResponseNoStore(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' response_headers = {'Cache-control': 'no-store'} - should_cache = False + should_be_cached = False -class ResponseNoCacheIgnore(TestCacheControl, tester.SingleTest): +class ResponseNoCacheIgnore(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; cache_control_ignore no-cache; ''' response_headers = {'Cache-control': 'no-cache'} - should_cache = True + should_be_cached = True # multiple cache_control_ignore directives -class ResponseNoCacheIgnoreMulti(TestCacheControl, tester.SingleTest): +class ResponseNoCacheIgnoreMulti(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; cache_control_ignore max-age no-cache; ''' response_headers = {'Cache-control': 'no-cache'} - should_cache = True + should_be_cached = True -class ResponseMultipleNoCacheIgnore(TestCacheControl, tester.SingleTest): +class ResponseMultipleNoCacheIgnore(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; cache_control_ignore no-cache private no-store; ''' response_headers = {'Cache-control': 'no-cache, private, no-store'} - should_cache = True + should_be_cached = True #public -class ResponsePublicBypass(TestCacheControl, tester.SingleTest): +class ResponsePublicNotCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' request_headers = {'Authorization': 'asd'} response_headers = {} - should_cache = False + should_be_cached = False -class ResponsePublicFullfill(TestCacheControl, tester.SingleTest): +class ResponsePublicFullfill(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' request_headers = {'Authorization': 'asd'} response_headers = {'Cache-control': 'public'} - should_cache = True + should_be_cached = True -class ResponsePublicIgnore(TestCacheControl, tester.SingleTest): +class ResponsePublicIgnore(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; cache_control_ignore public; ''' request_headers = {'Authorization': 'asd'} response_headers = {'Cache-control': 'public'} - should_cache = False + should_be_cached = False # multiple cache_control_ignore directives -class ResponsePublicIgnore2(TestCacheControl, tester.SingleTest): +class ResponsePublicIgnore2(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; cache_control_ignore must-revalidate public; ''' request_headers = {'Authorization': 'asd'} response_headers = {'Cache-control': 'public'} - should_cache = False + should_be_cached = False ######################################################### # Cache-Control: no-cache and private with arguments ############# # no-cache -class ArgNoCacheBypass(TestCacheControl, tester.SingleTest): +class CCArgNoCacheBypass(TestCacheControl, SingleTest): tempesta_config = ''' cache_bypass * *; ''' response_headers = {'Remove-me': '', 'Remove-me-2': '', 'Cache-control': 'no-cache="remove-me"'} - should_cache = False + should_be_cached = False -class ArgNoCacheFulfill(TestCacheControl, tester.SingleTest): +class CCArgNoCacheCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' @@ -544,9 +582,9 @@ class ArgNoCacheFulfill(TestCacheControl, tester.SingleTest): 'Cache-control': 'no-cache="remove-me"'} cached_headers = {'Remove-me': None, 'Remove-me-2': '', 'Cache-control': 'no-cache="remove-me"'} - should_cache = True + should_be_cached = True -class ArgNoCacheFulfill2(TestCacheControl, tester.SingleTest): +class CCArgNoCacheCached2(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' @@ -554,9 +592,9 @@ class ArgNoCacheFulfill2(TestCacheControl, tester.SingleTest): 'Cache-control': 'no-cache="remove-me"'} cached_headers = {'Remove-me': None, 'Remove-me-2': '"arg"', 'Cache-control': 'no-cache="remove-me"'} - should_cache = True + should_be_cached = True -class ArgNoCacheFulfill3(TestCacheControl, tester.SingleTest): +class CCArgNoCacheCached3(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' @@ -568,19 +606,19 @@ class ArgNoCacheFulfill3(TestCacheControl, tester.SingleTest): 'public, no-cache="Set-Cookie", must-revalidate, max-age=120', 'Set-Cookie': None } - should_cache = True + should_be_cached = True ############# # private -class ArgPrivateBypass(TestCacheControl, tester.SingleTest): +class CCArgPrivateBypass(TestCacheControl, SingleTest): tempesta_config = ''' cache_bypass * *; ''' response_headers = {'Set-cookie': 'some=cookie', 'remove-me-2': '', 'Cache-control': 'private="set-cookie"'} - should_cache = False + should_be_cached = False -class ArgPrivateFulfill(TestCacheControl, tester.SingleTest): +class CCArgPrivateCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' @@ -588,9 +626,9 @@ class ArgPrivateFulfill(TestCacheControl, tester.SingleTest): 'Cache-control': 'private="set-cookie"'} cached_headers = {'Set-cookie': None, 'remove-me-2': '', 'Cache-control': 'private="set-cookie"'} - should_cache = True + should_be_cached = True -class ArgPrivateFulfill2(TestCacheControl, tester.SingleTest): +class CCArgPrivateCached2(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' @@ -598,10 +636,10 @@ class ArgPrivateFulfill2(TestCacheControl, tester.SingleTest): 'Cache-control': 'private="set-cookie"'} cached_headers = {'Set-cookie': None, 'remove-me-2': '=', 'Cache-control': 'private="set-cookie"'} - should_cache = True + should_be_cached = True # erase two headers -class ArgBothNoCacheFulfill(TestCacheControl, tester.SingleTest): +class CCArgBothNoCacheCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' @@ -609,4 +647,4 @@ class ArgBothNoCacheFulfill(TestCacheControl, tester.SingleTest): 'Cache-control': 'no-cache="set-cookie, Remove-me-2"'} cached_headers = {'Set-cookie': None, 'remove-me-2': None, 'Cache-control': 'no-cache="set-cookie, Remove-me-2"'} - should_cache = True + should_be_cached = True diff --git a/cache/test_purge.py b/cache/test_purge.py index 0409e854c..09c68897f 100644 --- a/cache/test_purge.py +++ b/cache/test_purge.py @@ -111,11 +111,15 @@ def test_purge_get_update_hdr_del(self): chains.cache(method='GET', uri=uri), ] + # purge_get c = ch[2].server_response c.body = page c.headers['Content-Length'] = len(page) c.headers['Set-Cookie'] = 'somecookie=2' c.update() + c = ch[2].response + c.headers['Set-Cookie'] = 'somecookie=2' + c.update() c = ch[3].response c.body = page @@ -136,12 +140,17 @@ def test_purge_get_update_cc(self): chains.cache(method='GET', uri=uri), ] + # purge_get c = ch[2].server_response c.body = page c.headers['Content-Length'] = len(page) c.headers['Set-Cookie'] = 'somecookie=2' c.headers['Cache-control'] = 'no-cache="set-cookie"' c.update() + c = ch[2].response + c.headers['Set-Cookie'] = 'somecookie=2' + c.headers['Cache-control'] = 'no-cache="set-cookie"' + c.update() c = ch[3].response c.body = page diff --git a/framework/tester.py b/framework/tester.py index 7da59ab64..48315bf21 100644 --- a/framework/tester.py +++ b/framework/tester.py @@ -267,7 +267,9 @@ def start_all_servers(self): def start_tempesta(self): """ Start Tempesta and wait until the initialization process finish. """ - with dmesg.wait_for_msg('[tempesta fw] modules are started', 1, True): + # "modules are started" string is only logged in debug builds while + # "Tempesta FW is ready" is logged at all levels. + with dmesg.wait_for_msg('[tempesta fw] Tempesta FW is ready', 1, True): self.__tempesta.start() if not self.__tempesta.is_running(): raise Exception("Can not start Tempesta") @@ -336,7 +338,3 @@ def wait_all_connections(self, tmt=1): if not srv.wait_for_connections(timeout=tmt): return False return True - -class SingleTest(object): - def test(self): - self._test() \ No newline at end of file diff --git a/http_rules/test_http_tables.py b/http_rules/test_http_tables.py index 25e8e8489..0272ce901 100644 --- a/http_rules/test_http_tables.py +++ b/http_rules/test_http_tables.py @@ -268,14 +268,6 @@ def setUp(self): self.init_chain(self.requests_opt[i]) tester.TempestaTest.setUp(self) - def wait_all_connections(self, tmt=1): - sids = self.get_servers_id() - for id in sids: - srv = self.get_server(id) - if not srv.wait_for_connections(timeout=tmt): - return False - return True - def start_all(self): self.start_all_servers() self.start_tempesta() diff --git a/sessions/test_cookies.py b/sessions/test_cookies.py index 7dcbb1d79..a5fc98f48 100644 --- a/sessions/test_cookies.py +++ b/sessions/test_cookies.py @@ -78,14 +78,6 @@ class CookiesNotEnabled(tester.TempestaTest): }, ] - def wait_all_connections(self, tmt=1): - sids = self.get_servers_id() - for sid in sids: - srv = self.get_server(sid) - if not srv.wait_for_connections(timeout=tmt): - return False - return True - def client_supports_cookies(self, client_name): for client in self.clients: if client['id'] == client_name: diff --git a/sessions/test_js_challenge.py b/sessions/test_js_challenge.py index 7cd3a7c7f..26415d0a0 100644 --- a/sessions/test_js_challenge.py +++ b/sessions/test_js_challenge.py @@ -14,13 +14,6 @@ __license__ = 'GPL2' class BaseJSChallenge(tester.TempestaTest): - def wait_all_connections(self, tmt=1): - sids = self.get_servers_id() - for sid in sids: - srv = self.get_server(sid) - if not srv.wait_for_connections(timeout=tmt): - return False - return True def client_send_req(self, client, req): curr_responses = len(client.responses) diff --git a/sessions/test_redir_mark.py b/sessions/test_redir_mark.py index 4ac95efa1..46d1e7de1 100644 --- a/sessions/test_redir_mark.py +++ b/sessions/test_redir_mark.py @@ -37,12 +37,6 @@ class BaseRedirectMark(tester.TempestaTest): } ] - def wait_all_connections(self, tmt=1): - srv = self.get_server('server') - if not srv.wait_for_connections(timeout=tmt): - return False - return True - def client_expect_block(self, client, req): curr_responses = len(client.responses) client.make_requests(req) diff --git a/sessions/test_sessions.py b/sessions/test_sessions.py index 09fa0a324..f0065ec63 100644 --- a/sessions/test_sessions.py +++ b/sessions/test_sessions.py @@ -78,14 +78,6 @@ class StickySessions(tester.TempestaTest): } ] - def wait_all_connections(self, tmt=1): - sids = self.get_servers_id() - for sid in sids: - srv = self.get_server(sid) - if not srv.wait_for_connections(timeout=tmt): - return False - return True - def client_send_req(self, client, req): curr_responses = len(client.responses) client.make_requests(req) From 1ee02ae3d47e1db2f5e0bd9c40312bdea82cc1ac Mon Sep 17 00:00:00 2001 From: Pavel Kostyuchenko Date: Wed, 2 Feb 2022 19:25:54 +0200 Subject: [PATCH 4/7] Added test for "Cache-control: no-cache" in request --- cache/test_cache_control.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/cache/test_cache_control.py b/cache/test_cache_control.py index 5fdf15cab..496266621 100644 --- a/cache/test_cache_control.py +++ b/cache/test_cache_control.py @@ -235,6 +235,7 @@ class TestCache2(TestCacheControl, SingleTest): # cache_control_ignore ######### # request +# max-age class RequestMaxAgeNoCached(TestCacheControl, SingleTest): request_headers = {'Cache-control': 'max-age=1'} response_headers = {'Cache-control': 'max-age=2'} @@ -246,7 +247,8 @@ class RequestMaxAgeCached(TestCacheControl, SingleTest): response_headers = {'Cache-control': 'max-age=2'} sleep_interval = None should_be_cached = True - + +# max-age, max-stale class RequestMaxAgeMaxStaleNotCached(TestCacheControl, SingleTest): request_headers = {'Cache-control': 'max-age=3, max-stale=1'} response_headers = {'Cache-control': 'max-age=1'} @@ -278,6 +280,7 @@ class RequestMinFreshCached(TestCacheControl, SingleTest): sleep_interval = None should_be_cached = True +# max-age class RequestOnlyIfCachedCached(TestCacheControl, SingleTest): request_headers = {'Cache-control': 'max-age=1'} response_headers = {'Cache-control': 'max-age=2'} @@ -294,10 +297,15 @@ class RequestOnlyIfCached504(TestCacheControl, SingleTest): cached_status = '504' should_be_cached = True + class RequestNoStoreNotCached(TestCacheControl, SingleTest): request_headers = {'Cache-control': 'no-store'} should_be_cached = False +class RequestNoStoreNotCached(TestCacheControl, SingleTest): + request_headers = {'Cache-control': 'no-cache'} + should_be_cached = False + ########## # response # From 29bd2351ae3090dd9b15e5c60be65487f2a0e26e Mon Sep 17 00:00:00 2001 From: Pavel Kostyuchenko Date: Thu, 3 Feb 2022 00:18:42 +0200 Subject: [PATCH 5/7] Additional tests and comments --- cache/test_cache_control.py | 112 ++++++++++++++++++++++++++++++++---- 1 file changed, 100 insertions(+), 12 deletions(-) diff --git a/cache/test_cache_control.py b/cache/test_cache_control.py index 496266621..69c3a2d03 100644 --- a/cache/test_cache_control.py +++ b/cache/test_cache_control.py @@ -338,6 +338,10 @@ class ResponseMustRevalidateNotCached2(TestCacheControl, SingleTest): sleep_interval = 1.5 should_be_cached = False +# RFC 7234 Sections 3.2, 4.2.4: +# "cached responses that contain the "must-revalidate" and/or +# "s-maxage" response directives are not allowed to be served stale +# by shared caches" class ResponseMustRevalidateCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; @@ -361,9 +365,21 @@ class ResponseMustRevalidateIgnore(TestCacheControl, SingleTest): cache_fulfill * *; cache_control_ignore must-revalidate; ''' + # Although must-revalidate is ignored, max-age=1 remains active. request_headers = {} response_headers = {'Cache-control': 'max-age=1, must-revalidate'} sleep_interval = 1.5 + should_be_cached = False + +class ResponseMustRevalidateIgnore2(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore must-revalidate; + ''' + request_headers = {} + request_headers = {'Cache-control': 'max-stale=1'} + response_headers = {'Cache-control': 'max-age=1, must-revalidate'} + sleep_interval = 1.5 should_be_cached = True # proxy-revalidate @@ -393,31 +409,62 @@ class ResponseProxyRevalidateIgnore(TestCacheControl, SingleTest): request_headers = {} response_headers = {'Cache-control': 'max-age=1, proxy-revalidate'} sleep_interval = 1.5 + should_be_cached = False + +class ResponseProxyRevalidateCached3(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {'Cache-control': 'max-stale=1'} + response_headers = {'Cache-control': 'max-age=1'} + sleep_interval = 1.5 should_be_cached = True -# multiple directives -class ResponseProxyRevalidateHalfIgnore(TestCacheControl, SingleTest): +class ResponseProxyRevalidateIgnore3(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; cache_control_ignore proxy-revalidate; ''' - request_headers = {} + request_headers = {'Cache-control': 'max-stale=1'} + response_headers = {'Cache-control': 'max-age=1, proxy-revalidate'} + sleep_interval = 1.5 + should_be_cached = True + +# Support for "max-stale" + "max-age=1, proxy-revalidate" is questionable and +# already tested above. So we test multiple directives with a more reliable +# logic of "Authorizarion" caching. +class ResponseMustRevalidateNotCached(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {'Authorization': 'asd'} response_headers = {'Cache-control': - 'max-age=1, must-revalidate, proxy-revalidate'} + 'max-age=1, must-revalidate'} sleep_interval = 1.5 should_be_cached = False -class ResponseProxyRevalidateMultiIgnore(TestCacheControl, SingleTest): +class ResponseMustRevalidateHalfIgnore(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; - cache_control_ignore proxy-revalidate must-revalidate; + cache_control_ignore max-age; ''' - request_headers = {} + request_headers = {'Authorization': 'asd'} response_headers = {'Cache-control': - 'max-age=1, must-revalidate, proxy-revalidate'} + 'max-age=1, must-revalidate'} sleep_interval = 1.5 should_be_cached = True +class ResponseMustRevalidateMultiIgnore(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore max-age must-revalidate; + ''' + request_headers = {'Authorization': 'asd'} + response_headers = {'Cache-control': + 'max-age=1, must-revalidate'} + sleep_interval = 1.5 + should_be_cached = False + # max-age/s-maxage class ResponseMaxAgeNotCached(TestCacheControl, SingleTest): tempesta_config = ''' @@ -444,10 +491,20 @@ class ResponseMaxAgeIgnore(TestCacheControl, SingleTest): sleep_interval = 1.5 should_be_cached = True -class ResponseMaxageNotCached(TestCacheControl, SingleTest): +class ResponseSMaxageNotCached(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Cache-control': 's-maxage=1'} + sleep_interval = 1.5 + should_be_cached = False + +# s-maxage forbids serving stale responses +class ResponseSMaxageNotCached2(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' + request_headers = {'Cache-control': 'max-stale=1'} response_headers = {'Cache-control': 's-maxage=1'} sleep_interval = 1.5 should_be_cached = False @@ -460,10 +517,30 @@ class ResponseSMaxageCached(TestCacheControl, SingleTest): sleep_interval = None should_be_cached = True +# Authorization interacts with s-maxage, but not with max-age. +# See RFC 7234 Section 3.2. +class ResponseMaxAgeNotCached2(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {'Authorization': 'asd'} + response_headers = {'Cache-control': 's-maxage=0'} + sleep_interval = None + should_be_cached = False + +class ResponseMaxAgeCached2(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {'Authorization': 'asd'} + response_headers = {'Cache-control': 's-maxage=1'} + sleep_interval = None + should_be_cached = True + class ResponseSMaxageIgnore(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; - cache_control_ignore max-age; + cache_control_ignore s-maxage; ''' response_headers = {'Cache-control': 's-maxage=1'} sleep_interval = 1.5 @@ -534,7 +611,7 @@ class ResponseMultipleNoCacheIgnore(TestCacheControl, SingleTest): response_headers = {'Cache-control': 'no-cache, private, no-store'} should_be_cached = True -#public +# public directive and Authorization header class ResponsePublicNotCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; @@ -543,7 +620,7 @@ class ResponsePublicNotCached(TestCacheControl, SingleTest): response_headers = {} should_be_cached = False -class ResponsePublicFullfill(TestCacheControl, SingleTest): +class ResponsePublicCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' @@ -551,6 +628,17 @@ class ResponsePublicFullfill(TestCacheControl, SingleTest): response_headers = {'Cache-control': 'public'} should_be_cached = True +class ResponsePublicCached2(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {} + response_headers = {} + # Interestingly enough, RFC 7234 does not forbid serving cached response for + # subsequent requests with "Authorization" header. + second_request_headers = {'Authorization': 'asd'} + should_be_cached = True + class ResponsePublicIgnore(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; From 975c37253ec0a767b57e88237fcd42b0e091539e Mon Sep 17 00:00:00 2001 From: Pavel Kostyuchenko Date: Fri, 4 Feb 2022 01:22:24 +0200 Subject: [PATCH 6/7] Minor changes * Fixed wrong check for second response status. * Larger windows for cache lifetimes. --- cache/test_cache_control.py | 42 ++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/cache/test_cache_control.py b/cache/test_cache_control.py index 69c3a2d03..fea38efac 100644 --- a/cache/test_cache_control.py +++ b/cache/test_cache_control.py @@ -90,7 +90,7 @@ def client_send_req(self, client, req): client.wait_for_response(timeout=1) self.assertEqual(curr_responses + 1, len(client.responses)) - return client.responses[-1] + return client.last_response def check_response_headers(self, response): for name, val in self.response_headers.iteritems(): @@ -144,7 +144,7 @@ def _test(self): "Host: localhost\r\n" "%s\r\n" % req_headers2) cached_response = self.client_send_req(client, req2) - self.assertEqual(response.status, self.cached_status, + self.assertEqual(cached_response.status, self.cached_status, "request for cache failed: {}, expected {}" \ .format(response.status, self.cached_status)) @@ -238,29 +238,29 @@ class TestCache2(TestCacheControl, SingleTest): # max-age class RequestMaxAgeNoCached(TestCacheControl, SingleTest): request_headers = {'Cache-control': 'max-age=1'} - response_headers = {'Cache-control': 'max-age=2'} + response_headers = {'Cache-control': 'max-age=3'} sleep_interval = 1.5 should_be_cached = False class RequestMaxAgeCached(TestCacheControl, SingleTest): request_headers = {'Cache-control': 'max-age=1'} - response_headers = {'Cache-control': 'max-age=2'} + response_headers = {'Cache-control': 'max-age=3'} sleep_interval = None should_be_cached = True - + # max-age, max-stale class RequestMaxAgeMaxStaleNotCached(TestCacheControl, SingleTest): - request_headers = {'Cache-control': 'max-age=3, max-stale=1'} + request_headers = {'Cache-control': 'max-age=5, max-stale=1'} response_headers = {'Cache-control': 'max-age=1'} - sleep_interval = 2.5 + sleep_interval = 3 should_be_cached = False class RequestMaxAgeMaxStaleCached(TestCacheControl, SingleTest): - request_headers = {'Cache-control': 'max-age=3, max-stale=1'} + request_headers = {'Cache-control': 'max-age=3, max-stale=2'} response_headers = {'Cache-control': 'max-age=1'} sleep_interval = 1.5 should_be_cached = True - + class RequestMaxStaleCached(TestCacheControl, SingleTest): request_headers = {'Cache-control': 'max-stale'} response_headers = {'Cache-control': 'max-age=1'} @@ -269,8 +269,8 @@ class RequestMaxStaleCached(TestCacheControl, SingleTest): # min-fresh class RequestMinFreshNotCached(TestCacheControl, SingleTest): - request_headers = {'Cache-control': 'min-fresh=1'} - response_headers = {'Cache-control': 'max-age=2'} + request_headers = {'Cache-control': 'min-fresh=2'} + response_headers = {'Cache-control': 'max-age=3'} sleep_interval = 1.5 should_be_cached = False @@ -292,7 +292,7 @@ class RequestOnlyIfCachedCached(TestCacheControl, SingleTest): class RequestOnlyIfCached504(TestCacheControl, SingleTest): request_headers = {'Cache-control': 'max-age=1'} response_headers = {'Cache-control': 'max-age=2'} - sleep_interval = 1.5 + sleep_interval = 2.5 second_request_headers = {'Cache-control': 'max-age=1, only-if-cached'} cached_status = '504' should_be_cached = True @@ -333,7 +333,7 @@ class ResponseMustRevalidateNotCached2(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' - request_headers = {'Cache-control': 'max-stale=1'} + request_headers = {'Cache-control': 'max-stale=2'} response_headers = {'Cache-control': 'max-age=1, must-revalidate'} sleep_interval = 1.5 should_be_cached = False @@ -346,7 +346,7 @@ class ResponseMustRevalidateCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' - request_headers = {'Cache-control': 'max-stale=1'} + request_headers = {'Cache-control': 'max-stale=2'} response_headers = {'Cache-control': 'max-age=1, must-revalidate'} should_be_cached = True sleep_interval = None @@ -377,7 +377,7 @@ class ResponseMustRevalidateIgnore2(TestCacheControl, SingleTest): cache_control_ignore must-revalidate; ''' request_headers = {} - request_headers = {'Cache-control': 'max-stale=1'} + request_headers = {'Cache-control': 'max-stale=2'} response_headers = {'Cache-control': 'max-age=1, must-revalidate'} sleep_interval = 1.5 should_be_cached = True @@ -396,7 +396,7 @@ class ResponseProxyRevalidateNotCached2(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' - request_headers = {'Cache-control': 'max-stale=1'} + request_headers = {'Cache-control': 'max-stale=2'} response_headers = {'Cache-control': 'max-age=1, proxy-revalidate'} sleep_interval = 1.5 should_be_cached = False @@ -411,11 +411,11 @@ class ResponseProxyRevalidateIgnore(TestCacheControl, SingleTest): sleep_interval = 1.5 should_be_cached = False -class ResponseProxyRevalidateCached3(TestCacheControl, SingleTest): +class ResponseCached3(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' - request_headers = {'Cache-control': 'max-stale=1'} + request_headers = {'Cache-control': 'max-stale=2'} response_headers = {'Cache-control': 'max-age=1'} sleep_interval = 1.5 should_be_cached = True @@ -425,7 +425,7 @@ class ResponseProxyRevalidateIgnore3(TestCacheControl, SingleTest): cache_fulfill * *; cache_control_ignore proxy-revalidate; ''' - request_headers = {'Cache-control': 'max-stale=1'} + request_headers = {'Cache-control': 'max-stale=2'} response_headers = {'Cache-control': 'max-age=1, proxy-revalidate'} sleep_interval = 1.5 should_be_cached = True @@ -499,12 +499,12 @@ class ResponseSMaxageNotCached(TestCacheControl, SingleTest): sleep_interval = 1.5 should_be_cached = False -# s-maxage forbids serving stale responses +# s-maxage forbids serving stale responses (implies proxy-revalidate) class ResponseSMaxageNotCached2(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' - request_headers = {'Cache-control': 'max-stale=1'} + request_headers = {'Cache-control': 'max-stale=2'} response_headers = {'Cache-control': 's-maxage=1'} sleep_interval = 1.5 should_be_cached = False From bb90562fbbc92a20ddefac3b9957b80f2b54ebc9 Mon Sep 17 00:00:00 2001 From: Pavel Kostyuchenko Date: Sat, 5 Feb 2022 20:03:20 +0200 Subject: [PATCH 7/7] Some renames --- cache/test_cache_control.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cache/test_cache_control.py b/cache/test_cache_control.py index fea38efac..7e313ef36 100644 --- a/cache/test_cache_control.py +++ b/cache/test_cache_control.py @@ -329,7 +329,7 @@ class ResponseMustRevalidateNotCached(TestCacheControl, SingleTest): sleep_interval = 1.5 should_be_cached = False -class ResponseMustRevalidateNotCached2(TestCacheControl, SingleTest): +class ResponseMustRevalidateStaleNotCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' @@ -342,7 +342,7 @@ class ResponseMustRevalidateNotCached2(TestCacheControl, SingleTest): # "cached responses that contain the "must-revalidate" and/or # "s-maxage" response directives are not allowed to be served stale # by shared caches" -class ResponseMustRevalidateCached(TestCacheControl, SingleTest): +class ResponseMustRevalidateStaleCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' @@ -351,7 +351,7 @@ class ResponseMustRevalidateCached(TestCacheControl, SingleTest): should_be_cached = True sleep_interval = None -class ResponseMustRevalidateCached2(TestCacheControl, SingleTest): +class ResponseMustRevalidateCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' @@ -371,7 +371,7 @@ class ResponseMustRevalidateIgnore(TestCacheControl, SingleTest): sleep_interval = 1.5 should_be_cached = False -class ResponseMustRevalidateIgnore2(TestCacheControl, SingleTest): +class ResponseMustRevalidateStaleIgnore(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; cache_control_ignore must-revalidate; @@ -392,7 +392,7 @@ class ResponseProxyRevalidateNotCached(TestCacheControl, SingleTest): sleep_interval = 1.5 should_be_cached = False -class ResponseProxyRevalidateNotCached2(TestCacheControl, SingleTest): +class ResponseProxyRevalidateStaleNotCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; ''' @@ -411,7 +411,7 @@ class ResponseProxyRevalidateIgnore(TestCacheControl, SingleTest): sleep_interval = 1.5 should_be_cached = False -class ResponseCached3(TestCacheControl, SingleTest): +class ResponseStaleCached(TestCacheControl, SingleTest): tempesta_config = ''' cache_fulfill * *; '''