This is an automated email from the ASF dual-hosted git repository.
cmcfarlen pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
The following commit(s) were added to refs/heads/10.1.x by this push:
new c8668c6354 Add proxy.config.http.negative_revalidating_list (#12212)
c8668c6354 is described below
commit c8668c635429188dd3eae04f3ee78a12412927d3
Author: Masaori Koshiba <[email protected]>
AuthorDate: Wed Apr 30 20:05:28 2025 +0900
Add proxy.config.http.negative_revalidating_list (#12212)
* Add proxy.config.http.negative_revalidating_list
* Address comments
(cherry picked from commit db850f1c5dc0f874af19134bacf5ceaa7697fec4)
---
doc/admin-guide/files/records.yaml.en.rst | 6 +
include/proxy/http/HttpConfig.h | 3 +
src/proxy/http/HttpConfig.cc | 90 +++++++++++--
src/proxy/http/HttpTransact.cc | 9 +-
src/records/RecordsConfig.cc | 2 +
.../gold_tests/cache/negative-revalidating.test.py | 77 +++++++----
.../replay/negative-revalidating-list.replay.yaml | 150 +++++++++++++++++++++
7 files changed, 294 insertions(+), 43 deletions(-)
diff --git a/doc/admin-guide/files/records.yaml.en.rst
b/doc/admin-guide/files/records.yaml.en.rst
index 3a544064e3..7596741621 100644
--- a/doc/admin-guide/files/records.yaml.en.rst
+++ b/doc/admin-guide/files/records.yaml.en.rst
@@ -1928,6 +1928,12 @@ Negative Response Caching
This configuration defaults to 1,800 seconds (30 minutes).
+.. ts:cv:: CONFIG proxy.config.http.negative_revalidating_list STRING 500 502
503 504
+ :reloadable:
+
+ The HTTP status codes for which the negative revalidating feature applies.
Note that this is a
+ `STRING` configuration containing a space separated list of the desired
HTTP status codes.
+
Proxy User Variables
====================
diff --git a/include/proxy/http/HttpConfig.h b/include/proxy/http/HttpConfig.h
index 9228635f38..63257470ac 100644
--- a/include/proxy/http/HttpConfig.h
+++ b/include/proxy/http/HttpConfig.h
@@ -802,6 +802,9 @@ public:
// bitset to hold the status codes that will BE cached with negative caching
enabled
HttpStatusBitset negative_caching_list;
+ // bitset to hold the status codes that will used by nagative revalidating
enabled
+ HttpStatusBitset negative_revalidating_list;
+
// All the overridable configurations goes into this class member, but they
// are not copied over until needed ("lazy").
OverridableHttpConfigParams oride;
diff --git a/src/proxy/http/HttpConfig.cc b/src/proxy/http/HttpConfig.cc
index 8989a32e7e..0d6e6e87b9 100644
--- a/src/proxy/http/HttpConfig.cc
+++ b/src/proxy/http/HttpConfig.cc
@@ -635,33 +635,51 @@ register_stat_callbacks()
});
}
+/**
+ Parse list of HTTP status code and return HttpStatusBitset
+ - e.g. "204 305 403 404 414 500 501 502 503 504"
+ */
+static HttpStatusBitset
+parse_http_status_code_list(swoc::TextView status_list)
+{
+ HttpStatusBitset set;
+
+ auto is_sep{[](char c) { return isspace(c) || ',' == c || ';' == c; }};
+
+ while (!status_list.ltrim_if(is_sep).empty()) {
+ swoc::TextView span;
+ swoc::TextView token{status_list.take_prefix_if(is_sep)};
+ auto n = swoc::svtoi(token, &span);
+ if (span.size() != token.size()) {
+ Error("Invalid status code '%.*s': not a number",
static_cast<int>(token.size()), token.data());
+ } else if (n <= 0 || n >= HTTP_STATUS_NUMBER) {
+ Error("Invalid status code '%.*s': out of range",
static_cast<int>(token.size()), token.data());
+ } else {
+ set[n] = true;
+ }
+ }
+
+ return set;
+}
+
static bool
set_negative_caching_list(const char *name, RecDataT dtype, RecData data,
HttpConfigParams *c, bool update)
{
bool ret = false;
HttpStatusBitset set;
+
// values from proxy.config.http.negative_caching_list
if (0 == strcasecmp("proxy.config.http.negative_caching_list", name) &&
RECD_STRING == dtype && data.rec_string) {
// parse the list of status codes
- swoc::TextView status_list(data.rec_string, strlen(data.rec_string));
- auto is_sep{[](char c) { return isspace(c) || ',' == c || ';' ==
c; }};
- while (!status_list.ltrim_if(is_sep).empty()) {
- swoc::TextView span, token{status_list.take_prefix_if(is_sep)};
- auto n = swoc::svtoi(token, &span);
- if (span.size() != token.size()) {
- Error("Invalid status code '%.*s' for negative caching: not a number",
static_cast<int>(token.size()), token.data());
- } else if (n <= 0 || n >= HTTP_STATUS_NUMBER) {
- Error("Invalid status code '%.*s' for negative caching: out of range",
static_cast<int>(token.size()), token.data());
- } else {
- set[n] = true;
- }
- }
+ set = parse_http_status_code_list({data.rec_string,
strlen(data.rec_string)});
}
+
// set the return value
if (set != c->negative_caching_list) {
c->negative_caching_list = set;
ret = ret || update;
}
+
return ret;
}
@@ -685,6 +703,47 @@ load_negative_caching_var(RecRecord const *r, void *cookie)
set_negative_caching_list(r->name, r->data_type, r->data, c, false);
}
+static bool
+set_negative_revalidating_list(const char *name, RecDataT dtype, RecData data,
HttpConfigParams *c, bool update)
+{
+ bool ret = false;
+ HttpStatusBitset set;
+
+ // values from proxy.config.http.negative_revalidating_list
+ if (0 == strcasecmp("proxy.config.http.negative_revalidating_list", name) &&
RECD_STRING == dtype && data.rec_string) {
+ // parse the list of status codes
+ set = parse_http_status_code_list({data.rec_string,
strlen(data.rec_string)});
+ }
+
+ // set the return value
+ if (set != c->negative_revalidating_list) {
+ c->negative_revalidating_list = set;
+ ret = ret || update;
+ }
+
+ return ret;
+}
+
+// Method of getting the status code bitset
+static int
+negative_revalidating_list_cb(const char *name, RecDataT dtype, RecData data,
void *cookie)
+{
+ HttpConfigParams *c = static_cast<HttpConfigParams *>(cookie);
+ // Signal an update if valid value arrived.
+ if (set_negative_revalidating_list(name, dtype, data, c, true)) {
+ http_config_cb(name, dtype, data, cookie);
+ }
+ return REC_ERR_OKAY;
+}
+
+// Method of loading the negative caching config bitset
+void
+load_negative_revalidating_var(RecRecord const *r, void *cookie)
+{
+ HttpConfigParams *c = static_cast<HttpConfigParams *>(cookie);
+ set_negative_revalidating_list(r->name, r->data_type, r->data, c, false);
+}
+
/** Template for creating conversions and initialization for @c std::chrono
based configuration variables.
*
* @tparam V The exact type of the configuration variable.
@@ -1020,6 +1079,8 @@ HttpConfig::startup()
HttpEstablishStaticConfigLongLong(c.oride.negative_revalidating_lifetime,
"proxy.config.http.negative_revalidating_lifetime");
RecRegisterConfigUpdateCb("proxy.config.http.negative_caching_list",
&negative_caching_list_cb, &c);
RecLookupRecord("proxy.config.http.negative_caching_list",
&load_negative_caching_var, &c, true);
+ RecRegisterConfigUpdateCb("proxy.config.http.negative_revalidating_list",
&negative_revalidating_list_cb, &c);
+ RecLookupRecord("proxy.config.http.negative_revalidating_list",
&load_negative_revalidating_var, &c, true);
// Buffer size and watermark
HttpEstablishStaticConfigLongLong(c.oride.default_buffer_size_index,
"proxy.config.http.default_buffer_size");
@@ -1339,7 +1400,8 @@ HttpConfig::reconfigure()
params->oride.ssl_client_sni_policy =
ats_strdup(m_master.oride.ssl_client_sni_policy);
params->oride.ssl_client_alpn_protocols =
ats_strdup(m_master.oride.ssl_client_alpn_protocols);
- params->negative_caching_list = m_master.negative_caching_list;
+ params->negative_caching_list = m_master.negative_caching_list;
+ params->negative_revalidating_list = m_master.negative_revalidating_list;
params->oride.host_res_data = m_master.oride.host_res_data;
params->oride.host_res_data.conf_value =
ats_strdup(m_master.oride.host_res_data.conf_value);
diff --git a/src/proxy/http/HttpTransact.cc b/src/proxy/http/HttpTransact.cc
index ff72cf5ac5..2a0bab4cb9 100644
--- a/src/proxy/http/HttpTransact.cc
+++ b/src/proxy/http/HttpTransact.cc
@@ -4319,15 +4319,12 @@
HttpTransact::handle_cache_operation_on_forward_server_response(State *s)
SET_VIA_STRING(VIA_SERVER_RESULT, VIA_SERVER_SERVED);
SET_VIA_STRING(VIA_PROXY_RESULT, VIA_PROXY_SERVED);
- /* if we receive a 500, 502, 503 or 504 while revalidating
+ /* By default, if we receive a 500, 502, 503 or 504 while revalidating
a document, treat the response as a 304 and in effect revalidate the
document for
negative_revalidating_lifetime. (negative revalidating)
*/
-
- if ((server_response_code == HTTP_STATUS_INTERNAL_SERVER_ERROR ||
server_response_code == HTTP_STATUS_GATEWAY_TIMEOUT ||
- server_response_code == HTTP_STATUS_BAD_GATEWAY ||
server_response_code == HTTP_STATUS_SERVICE_UNAVAILABLE) &&
- s->cache_info.action == CACHE_DO_UPDATE &&
s->txn_conf->negative_revalidating_enabled &&
- is_stale_cache_response_returnable(s)) {
+ if (s->txn_conf->negative_revalidating_enabled &&
s->http_config_param->negative_revalidating_list[server_response_code] &&
+ s->cache_info.action == CACHE_DO_UPDATE &&
is_stale_cache_response_returnable(s)) {
HTTPStatus cached_response_code =
s->cache_info.object_read->response_get()->status_get();
if (!(cached_response_code == HTTP_STATUS_INTERNAL_SERVER_ERROR ||
cached_response_code == HTTP_STATUS_GATEWAY_TIMEOUT ||
cached_response_code == HTTP_STATUS_BAD_GATEWAY ||
cached_response_code == HTTP_STATUS_SERVICE_UNAVAILABLE)) {
diff --git a/src/records/RecordsConfig.cc b/src/records/RecordsConfig.cc
index 73deb6e06c..c612e2d438 100644
--- a/src/records/RecordsConfig.cc
+++ b/src/records/RecordsConfig.cc
@@ -488,6 +488,8 @@ static const RecordElement RecordsConfig[] =
,
{RECT_CONFIG, "proxy.config.http.negative_caching_list", RECD_STRING, "204
305 403 404 414 500 501 502 503 504", RECU_DYNAMIC, RR_NULL, RECC_NULL,
nullptr, RECA_NULL}
,
+ {RECT_CONFIG, "proxy.config.http.negative_revalidating_list", RECD_STRING,
"500 502 503 504", RECU_DYNAMIC, RR_NULL, RECC_NULL, nullptr, RECA_NULL}
+ ,
// #########################
// # proxy users variables #
diff --git a/tests/gold_tests/cache/negative-revalidating.test.py
b/tests/gold_tests/cache/negative-revalidating.test.py
index 453814d9ef..e80c577827 100644
--- a/tests/gold_tests/cache/negative-revalidating.test.py
+++ b/tests/gold_tests/cache/negative-revalidating.test.py
@@ -21,47 +21,78 @@ Test.Summary = '''
Test the negative revalidating feature.
'''
+
+class NegativeRevalidatingTest:
+ _test_num: int = 0
+
+ def __init__(self, name: str, records_config: dict, replay_file: str):
+ self._tr = Test.AddTestRun(name)
+ self._replay_file = replay_file
+
+ self.__setupOriginServer()
+ self.__setupTS(records_config)
+ self.__setupClient()
+
+ NegativeRevalidatingTest._test_num += 1
+
+ def __setupClient(self):
+ self._tr.AddVerifierClientProcess(
+ f"client-{NegativeRevalidatingTest._test_num}", self._replay_file,
http_ports=[self._ts.Variables.port])
+
+ def __setupOriginServer(self):
+ self._server =
self._tr.AddVerifierServerProcess(f"server-{NegativeRevalidatingTest._test_num}",
self._replay_file)
+
+ def __setupTS(self, records_config):
+ self._ts =
Test.MakeATSProcess(f"ts-{NegativeRevalidatingTest._test_num}")
+ self._ts.Disk.records_config.update(records_config)
+ self._ts.Disk.remap_config.AddLine('map /
http://127.0.0.1:{0}'.format(self._server.Variables.http_port))
+
+ def run(self):
+ self._tr.Processes.Default.StartBefore(self._ts)
+ self._tr.StillRunningAfter = self._ts
+
+
#
# Verify disabled negative_revalidating behavior.
#
-ts = Test.MakeATSProcess("ts-negative-revalidating-disabled")
-ts.Disk.records_config.update(
- {
+NegativeRevalidatingTest(
+ "Verify negative revalidating disabled", {
'proxy.config.diags.debug.enabled': 1,
'proxy.config.diags.debug.tags': 'http|cache',
'proxy.config.http.insert_age_in_response': 0,
+ 'proxy.config.http.insert_response_via_str': 2,
'proxy.config.http.negative_revalidating_enabled': 0,
'proxy.config.http.cache.max_stale_age': 6
- })
-tr = Test.AddTestRun("Verify disabled negative revalidating behavior.")
-replay_file = "replay/negative-revalidating-disabled.replay.yaml"
-server = tr.AddVerifierServerProcess("server1", replay_file)
-server_port = server.Variables.http_port
-tr.AddVerifierClientProcess("client1", replay_file,
http_ports=[ts.Variables.port])
-ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server_port))
-tr.Processes.Default.StartBefore(ts)
-tr.StillRunningAfter = ts
+ }, "replay/negative-revalidating-disabled.replay.yaml").run()
#
# Verify enabled negative_revalidating behavior.
#
-ts = Test.MakeATSProcess("ts-negative-revalidating-enabled")
-ts.Disk.records_config.update(
+NegativeRevalidatingTest(
+ "Verify negative revalidating enabled",
{
'proxy.config.diags.debug.enabled': 1,
'proxy.config.diags.debug.tags': 'http|cache',
'proxy.config.http.insert_age_in_response': 0,
+ 'proxy.config.http.insert_response_via_str': 2,
# Negative revalidating is on by default. Verify this by leaving out
the
# following line and expect negative_revalidating to be enabled.
# 'proxy.config.http.negative_revalidating_enabled': 1,
'proxy.config.http.cache.max_stale_age': 6
- })
-tr = Test.AddTestRun("Verify negative revalidating behavior.")
-replay_file = "replay/negative-revalidating-enabled.replay.yaml"
-server = tr.AddVerifierServerProcess("server2", replay_file)
-server_port = server.Variables.http_port
-tr.AddVerifierClientProcess("client2", replay_file,
http_ports=[ts.Variables.port])
-ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server_port))
-tr.Processes.Default.StartBefore(ts)
-tr.StillRunningAfter = ts
+ },
+ "replay/negative-revalidating-enabled.replay.yaml").run()
+
+#
+# Verify negative_revalidating list behavior.
+#
+NegativeRevalidatingTest(
+ "Verify negative_revalidating_list behavior", {
+ 'proxy.config.diags.debug.enabled': 1,
+ 'proxy.config.diags.debug.tags': 'http|cache',
+ 'proxy.config.http.insert_age_in_response': 0,
+ 'proxy.config.http.insert_response_via_str': 2,
+ 'proxy.config.http.cache.max_stale_age': 6,
+ 'proxy.config.http.negative_revalidating_enabled': 1,
+ 'proxy.config.http.negative_revalidating_list': "403 404"
+ }, "replay/negative-revalidating-list.replay.yaml").run()
diff --git
a/tests/gold_tests/cache/replay/negative-revalidating-list.replay.yaml
b/tests/gold_tests/cache/replay/negative-revalidating-list.replay.yaml
new file mode 100644
index 0000000000..8b757b94d2
--- /dev/null
+++ b/tests/gold_tests/cache/replay/negative-revalidating-list.replay.yaml
@@ -0,0 +1,150 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+#
+# Verify negative_revalidating disabled behavior. This replay file assumes:
+# * ATS is configured with negative_revalidating disabled.
+# * max_stale_age is set to 6 seconds.
+#
+
+meta:
+ version: "1.0"
+
+sessions:
+- transactions:
+
+ #
+ # Test 1: Negative revalidating for a cached content-length response.
+ #
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /path/reques_item
+ headers:
+ fields:
+ - [ Host, example.com ]
+ - [ uuid, 31 ]
+
+ # Populate the cache with a 200 response.
+ server-response:
+ status: 200
+ reason: "OK"
+ headers:
+ fields:
+ - [ Content-Length, 32 ]
+ - [ Cache-Control, max-age=2 ]
+
+ proxy-response:
+ status: 200
+
+ # Verify we serve the 200 OK out of the cache if it is not stale.
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /path/reques_item
+ headers:
+ fields:
+ - [ Host, example.com ]
+ - [ uuid, 32 ]
+
+ # Give cache IO enough time to finish.
+ delay: 100ms
+
+ # This should not reach the origin server.
+ server-response:
+ status: 503
+ reason: "Service Unavailable"
+ headers:
+ fields:
+ - [ Content-Length, 32 ]
+
+ # Again, we should serve this out of the cache.
+ proxy-response:
+ status: 200
+
+ # Verify that with negative_revalidating enabled, we serve the 200 OK out of
+ # the cache even though it is stale (but younger than max_stale_age) for
configured status code.
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /path/reques_item
+ headers:
+ fields:
+ - [ Host, example.com ]
+ - [ uuid, 33 ]
+
+ # Make sure the item is stale per it's 2 second max-age.
+ delay: 4s
+
+ server-response:
+ status: 403
+ reason: "Forbidden"
+ headers:
+ fields:
+ - [ Content-Length, 32 ]
+
+ # With negative_revalidating enabled, the cached response should be served
+ # even though it is stale.
+ proxy-response:
+ status: 200
+
+ # Verify that with negative_revalidating enabled, we serve the response from
origin if it's not configured status code.
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /path/reques_item
+ headers:
+ fields:
+ - [ Host, example.com ]
+ - [ uuid, 34 ]
+
+ server-response:
+ status: 503
+ reason: "Service Unavailable"
+ headers:
+ fields:
+ - [ Content-Length, 32 ]
+
+ # With negative_revalidating enabled, but status code is not configured
one, return it directly
+ proxy-response:
+ status: 503
+
+ # Verify that max_stale_age is respected.
+ - client-request:
+ method: "GET"
+ version: "1.1"
+ url: /path/reques_item
+ headers:
+ fields:
+ - [ Host, example.com ]
+ - [ uuid, 35 ]
+
+ # After this delay, the item is 8 seconds old. This makes it:
+ # 6 seconds beyond the server's max-age of 2 seconds and
+ # 2 seconds beyond ATS's max_stale_age of 6 seconds.
+ delay: 4s
+
+ server-response:
+ status: 503
+ reason: "Service Unavailable"
+ headers:
+ fields:
+ - [ Content-Length, 32 ]
+
+ # negative_revalidating is enabled, but now the cached item is older than
+ # max_stale_age.
+ proxy-response:
+ status: 503