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..7e313ef36 --- /dev/null +++ b/cache/test_cache_control.py @@ -0,0 +1,746 @@ +"""Functional tests for custom processing of cached responses.""" + +from __future__ import print_function +from framework import tester +import copy +import time + +__author__ = 'Tempesta Technologies, Inc.' +__copyright__ = 'Copyright (C) 2022 Tempesta Technologies, Inc.' +__license__ = 'GPL2' + +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_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): + 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} + # 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: + 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.last_response + + 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) + + 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(cached_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_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 be") + + 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, 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_be_cached = False + +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_be_cached = True + +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_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, SingleTest): + tempesta_config = ''' + cache_bypass * *; + ''' + response_headers = {'Remove-me': '', 'Remove-me-2': ''} + should_be_cached = False + +class TestCache(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Remove-me': '', 'Remove-me-2': ''} + should_be_cached = True + +class TestCache2(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Remove-me': '2', 'Remove-me-2': '2'} + should_be_cached = True + +######################################################### +# cache_control_ignore +######### +# request +# max-age +class RequestMaxAgeNoCached(TestCacheControl, SingleTest): + request_headers = {'Cache-control': 'max-age=1'} + 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=3'} + sleep_interval = None + should_be_cached = True + +# max-age, max-stale +class RequestMaxAgeMaxStaleNotCached(TestCacheControl, SingleTest): + request_headers = {'Cache-control': 'max-age=5, max-stale=1'} + response_headers = {'Cache-control': 'max-age=1'} + sleep_interval = 3 + should_be_cached = False + +class RequestMaxAgeMaxStaleCached(TestCacheControl, SingleTest): + 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'} + sleep_interval = 1.5 + should_be_cached = True + +# min-fresh +class RequestMinFreshNotCached(TestCacheControl, SingleTest): + request_headers = {'Cache-control': 'min-fresh=2'} + response_headers = {'Cache-control': 'max-age=3'} + sleep_interval = 1.5 + should_be_cached = False + +class RequestMinFreshCached(TestCacheControl, SingleTest): + request_headers = {'Cache-control': 'min-fresh=1'} + response_headers = {'Cache-control': 'max-age=2'} + 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'} + sleep_interval = None + second_request_headers = {'Cache-control': 'max-age=1, only-if-cached'} + cached_status = '200' + should_be_cached = True + +class RequestOnlyIfCached504(TestCacheControl, SingleTest): + request_headers = {'Cache-control': 'max-age=1'} + response_headers = {'Cache-control': 'max-age=2'} + sleep_interval = 2.5 + second_request_headers = {'Cache-control': 'max-age=1, only-if-cached'} + 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 +# +# must-revalidate +# +# 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_be_cached = False + +class ResponseMustRevalidateStaleNotCached(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {'Cache-control': 'max-stale=2'} + response_headers = {'Cache-control': 'max-age=1, must-revalidate'} + 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 ResponseMustRevalidateStaleCached(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {'Cache-control': 'max-stale=2'} + response_headers = {'Cache-control': 'max-age=1, must-revalidate'} + should_be_cached = True + sleep_interval = None + +class ResponseMustRevalidateCached(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {} + response_headers = {'Cache-control': 'max-age=1, must-revalidate'} + sleep_interval = None + should_be_cached = True + +class ResponseMustRevalidateIgnore(TestCacheControl, SingleTest): + tempesta_config = ''' + 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 ResponseMustRevalidateStaleIgnore(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore must-revalidate; + ''' + request_headers = {} + request_headers = {'Cache-control': 'max-stale=2'} + response_headers = {'Cache-control': 'max-age=1, must-revalidate'} + sleep_interval = 1.5 + should_be_cached = True + +# proxy-revalidate +class ResponseProxyRevalidateNotCached(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {} + response_headers = {'Cache-control': 'max-age=1, proxy-revalidate'} + sleep_interval = 1.5 + should_be_cached = False + +class ResponseProxyRevalidateStaleNotCached(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {'Cache-control': 'max-stale=2'} + response_headers = {'Cache-control': 'max-age=1, proxy-revalidate'} + sleep_interval = 1.5 + should_be_cached = False + +class ResponseProxyRevalidateIgnore(TestCacheControl, 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_be_cached = False + +class ResponseStaleCached(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {'Cache-control': 'max-stale=2'} + response_headers = {'Cache-control': 'max-age=1'} + sleep_interval = 1.5 + should_be_cached = True + +class ResponseProxyRevalidateIgnore3(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore proxy-revalidate; + ''' + request_headers = {'Cache-control': 'max-stale=2'} + 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'} + sleep_interval = 1.5 + should_be_cached = False + +class ResponseMustRevalidateHalfIgnore(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore max-age; + ''' + request_headers = {'Authorization': 'asd'} + response_headers = {'Cache-control': + '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 = ''' + cache_fulfill * *; + ''' + response_headers = {'Cache-control': 'max-age=1'} + sleep_interval = 1.5 + should_be_cached = False + +class ResponseMaxAgeCached(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Cache-control': 'max-age=1'} + sleep_interval = None + should_be_cached = True + +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_be_cached = True + +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 (implies proxy-revalidate) +class ResponseSMaxageNotCached2(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {'Cache-control': 'max-stale=2'} + response_headers = {'Cache-control': 's-maxage=1'} + sleep_interval = 1.5 + should_be_cached = False + +class ResponseSMaxageCached(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Cache-control': 's-maxage=1'} + 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 s-maxage; + ''' + response_headers = {'Cache-control': 's-maxage=1'} + sleep_interval = 1.5 + should_be_cached = True + +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_be_cached = False + +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_be_cached = True + +# private/no-cache/no-store +class ResponsePrivate(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Cache-control': 'private'} + should_be_cached = False + +class ResponseNoCache(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Cache-control': 'no-cache'} + should_be_cached = False + +class ResponseNoStore(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + response_headers = {'Cache-control': 'no-store'} + should_be_cached = False + +class ResponseNoCacheIgnore(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore no-cache; + ''' + response_headers = {'Cache-control': 'no-cache'} + should_be_cached = True + +# multiple cache_control_ignore directives +class ResponseNoCacheIgnoreMulti(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore max-age no-cache; + ''' + response_headers = {'Cache-control': 'no-cache'} + should_be_cached = True + +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_be_cached = True + +# public directive and Authorization header +class ResponsePublicNotCached(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {'Authorization': 'asd'} + response_headers = {} + should_be_cached = False + +class ResponsePublicCached(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + ''' + request_headers = {'Authorization': 'asd'} + 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 * *; + cache_control_ignore public; + ''' + request_headers = {'Authorization': 'asd'} + response_headers = {'Cache-control': 'public'} + should_be_cached = False + +# multiple cache_control_ignore directives +class ResponsePublicIgnore2(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_fulfill * *; + cache_control_ignore must-revalidate public; + ''' + request_headers = {'Authorization': 'asd'} + response_headers = {'Cache-control': 'public'} + should_be_cached = False + +######################################################### +# Cache-Control: no-cache and private with arguments +############# +# no-cache +class CCArgNoCacheBypass(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_bypass * *; + ''' + response_headers = {'Remove-me': '', 'Remove-me-2': '', + 'Cache-control': 'no-cache="remove-me"'} + should_be_cached = False + +class CCArgNoCacheCached(TestCacheControl, 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_be_cached = True + +class CCArgNoCacheCached2(TestCacheControl, 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_be_cached = True + +class CCArgNoCacheCached3(TestCacheControl, 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_be_cached = True + +############# +# private +class CCArgPrivateBypass(TestCacheControl, SingleTest): + tempesta_config = ''' + cache_bypass * *; + ''' + response_headers = {'Set-cookie': 'some=cookie', 'remove-me-2': '', + 'Cache-control': 'private="set-cookie"'} + should_be_cached = False + +class CCArgPrivateCached(TestCacheControl, 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_be_cached = True + +class CCArgPrivateCached2(TestCacheControl, 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_be_cached = True + +# erase two headers +class CCArgBothNoCacheCached(TestCacheControl, 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_be_cached = True diff --git a/cache/test_purge.py b/cache/test_purge.py index cd97788a0..09c68897f 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,66 @@ 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), + ] + + # 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 + 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), + ] + + # 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 + 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..48315bf21 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 = {} @@ -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") @@ -328,3 +330,11 @@ 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 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)