From 51589bfb07e39cdb86de90a1e8399ab01ceac384 Mon Sep 17 00:00:00 2001 From: bneradt Date: Tue, 21 Jul 2026 18:04:26 -0500 Subject: [PATCH] Add traffic_ctl cache clear command Cache resets currently require restarting Traffic Server or manually changing the cache generation. This complicates repeatable workload comparisons and their automation. This adds a traffic_ctl cache clear command backed by a restricted JSON-RPC method that advances the global HTTP cache generation. This also documents the logical purge semantics and exercises the command in the cache-generation AuTest. Fixes: #9399 --- .../command-line/traffic_ctl.en.rst | 18 +++++ .../jsonrpc/jsonrpc-api.en.rst | 57 +++++++++++++++ include/mgmt/rpc/handlers/cache/Cache.h | 31 +++++++++ src/mgmt/rpc/CMakeLists.txt | 1 + src/mgmt/rpc/handlers/cache/Cache.cc | 69 +++++++++++++++++++ src/traffic_ctl/CtrlCommands.cc | 20 ++++++ src/traffic_ctl/CtrlCommands.h | 11 +++ src/traffic_ctl/jsonrpc/CtrlRPCRequests.h | 8 +++ src/traffic_ctl/traffic_ctl.cc | 6 ++ src/traffic_server/RpcAdminPubHandlers.cc | 5 ++ .../cache/cache-generation-clear.test.py | 12 ++-- .../{hit_default77.gold => hit_default0.gold} | 2 +- ...miss_default77.gold => miss_default0.gold} | 2 +- 13 files changed, 234 insertions(+), 8 deletions(-) create mode 100644 include/mgmt/rpc/handlers/cache/Cache.h create mode 100644 src/mgmt/rpc/handlers/cache/Cache.cc rename tests/gold_tests/cache/gold/{hit_default77.gold => hit_default0.gold} (83%) rename tests/gold_tests/cache/gold/{miss_default77.gold => miss_default0.gold} (82%) diff --git a/doc/appendices/command-line/traffic_ctl.en.rst b/doc/appendices/command-line/traffic_ctl.en.rst index 5fff291cf19..6d50dce4cac 100644 --- a/doc/appendices/command-line/traffic_ctl.en.rst +++ b/doc/appendices/command-line/traffic_ctl.en.rst @@ -43,6 +43,8 @@ of subcommands that control different aspects of Traffic Server: :program:`traffic_ctl config` Manipulate and display configuration records +:program:`traffic_ctl cache` + Invalidate cached objects :program:`traffic_ctl metric` Manipulate performance and status metrics :program:`traffic_ctl server` @@ -1137,6 +1139,22 @@ traffic_ctl server $ traffic_ctl server debug disable ■ TS Runtime debug set to »OFF(0)« +.. _traffic-control-command-cache: + +traffic_ctl cache +----------------- + +.. program:: traffic_ctl cache +.. option:: clear + + :ref:`admin_cache_clear` + + Invalidate cached HTTP objects in the global cache-generation namespace without restarting |TS|. This advances + :ts:cv:`proxy.config.http.cache.generation`, causing subsequent cache lookups to use a new key namespace. Remap rules that + override this configuration have independent namespaces and are not affected. The invalidated data remains on cache storage until + it is overwritten through normal cache operation. The configuration change applies to the running process and is not persisted to + :file:`records.yaml`. + .. _traffic-control-command-storage: traffic_ctl storage diff --git a/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst b/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst index d0084b7eaaf..8a06ecf6b0f 100644 --- a/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst +++ b/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst @@ -444,6 +444,8 @@ Example with stats meta: JSONRPC API =========== +* `admin_cache_clear`_ + * `admin_lookup_records`_ @@ -471,6 +473,61 @@ JSONRPC API * `get_connection_tracker_info`_ + +Cache +===== + +.. _admin_cache_clear: + +admin_cache_clear +----------------- + +|method| + +Description +~~~~~~~~~~~ + +Invalidate cached HTTP objects in the global cache-generation namespace without restarting |TS|. The method advances +:ts:cv:`proxy.config.http.cache.generation`, causing subsequent cache lookups to use a new key namespace. Remap rules that override +this configuration have independent namespaces and are not affected. The invalidated data remains on cache storage until it is +overwritten through normal cache operation. The configuration change applies to the running process and is not persisted to +:file:`records.yaml`. + +Parameters +~~~~~~~~~~ + +* ``params``: Omitted + +Result +~~~~~~ + +The response will contain the default `success_response` or an error. :ref:`jsonrpc-node-errors`. + +Examples +~~~~~~~~ + +Request: + +.. code-block:: json + :linenos: + + { + "id": "9db0608c-b696-46f4-b865-1b5dc8a80d38", + "jsonrpc": "2.0", + "method": "admin_cache_clear" + } + +Response: + +.. code-block:: json + :linenos: + + { + "jsonrpc": "2.0", + "result": "success", + "id": "9db0608c-b696-46f4-b865-1b5dc8a80d38" + } + .. _jsonapi-management-records: diff --git a/include/mgmt/rpc/handlers/cache/Cache.h b/include/mgmt/rpc/handlers/cache/Cache.h new file mode 100644 index 00000000000..2a7dcefae77 --- /dev/null +++ b/include/mgmt/rpc/handlers/cache/Cache.h @@ -0,0 +1,31 @@ +/** @file + + Cache JSON-RPC handlers. + + @section license License + + 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. +*/ + +#pragma once + +#include "mgmt/rpc/jsonrpc/JsonRPCManager.h" + +namespace rpc::handlers::cache +{ +swoc::Rv clear_cache(std::string_view const &id, YAML::Node const ¶ms); +} // namespace rpc::handlers::cache diff --git a/src/mgmt/rpc/CMakeLists.txt b/src/mgmt/rpc/CMakeLists.txt index 8bd738c48c0..cab1ed7f613 100644 --- a/src/mgmt/rpc/CMakeLists.txt +++ b/src/mgmt/rpc/CMakeLists.txt @@ -39,6 +39,7 @@ target_link_libraries(jsonrpc_server PUBLIC ts::jsonrpc_protocol) add_library( rpcpublichandlers STATIC + handlers/cache/Cache.cc handlers/common/ErrorUtils.cc handlers/common/RecordsUtils.cc handlers/config/Configuration.cc diff --git a/src/mgmt/rpc/handlers/cache/Cache.cc b/src/mgmt/rpc/handlers/cache/Cache.cc new file mode 100644 index 00000000000..01f6ba7fdf7 --- /dev/null +++ b/src/mgmt/rpc/handlers/cache/Cache.cc @@ -0,0 +1,69 @@ +/** @file + + Cache JSON-RPC handlers. + + @section license License + + 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. +*/ + +#include "mgmt/rpc/handlers/cache/Cache.h" +#include "mgmt/rpc/handlers/common/ErrorUtils.h" + +#include "records/RecCore.h" + +#include +#include + +namespace +{ +constexpr char CACHE_GENERATION_RECORD[] = "proxy.config.http.cache.generation"; +std::mutex generation_mutex; +} // namespace + +namespace rpc::handlers::cache +{ +swoc::Rv +clear_cache(std::string_view const & /* id ATS_UNUSED */, YAML::Node const & /* params ATS_UNUSED */) +{ + namespace err = rpc::handlers::errors; + + swoc::Rv resp; + std::lock_guard lock{generation_mutex}; + auto generation = RecGetRecordInt(CACHE_GENERATION_RECORD); + + if (!generation) { + resp.errata() + .assign(std::error_code{err::Codes::CONFIGURATION}) + .note("Could not read cache generation record '{}'.", CACHE_GENERATION_RECORD); + return resp; + } + + RecInt next_generation = 0; + if (*generation >= 0 && *generation < std::numeric_limits::max()) { + next_generation = *generation + 1; + } + + if (RecSetRecordInt(CACHE_GENERATION_RECORD, next_generation, REC_SOURCE_DEFAULT) != REC_ERR_OKAY) { + resp.errata() + .assign(std::error_code{err::Codes::CONFIGURATION}) + .note("Could not advance cache generation record '{}'.", CACHE_GENERATION_RECORD); + } + + return resp; +} +} // namespace rpc::handlers::cache diff --git a/src/traffic_ctl/CtrlCommands.cc b/src/traffic_ctl/CtrlCommands.cc index 1f64bd45db9..8cdd5e870b0 100644 --- a/src/traffic_ctl/CtrlCommands.cc +++ b/src/traffic_ctl/CtrlCommands.cc @@ -736,6 +736,26 @@ ConfigCommand::config_show_file_registry() _printer->write_output(invoke_rpc(ConfigShowFileRegistryRequest{})); } //------------------------------------------------------------------------------------------------------------------------------------ +CacheCommand::CacheCommand(ts::Arguments *args) : CtrlCommand(args) +{ + auto printOpts = parse_print_opts(args); + + if (get_parsed_arguments()->get(CLEAR_STR)) { + _printer = std::make_unique(printOpts); + _invoked_func = [&]() { clear(); }; + } +} + +void +CacheCommand::clear() +{ + CacheClearRequest request; + + auto response = invoke_rpc(request); + + _printer->write_output(response); +} +//------------------------------------------------------------------------------------------------------------------------------------ MetricCommand::MetricCommand(ts::Arguments *args) : RecordCommand(args) { BasePrinter::Options printOpts{parse_print_opts(args)}; diff --git a/src/traffic_ctl/CtrlCommands.h b/src/traffic_ctl/CtrlCommands.h index eb6913109c5..0958fb70697 100644 --- a/src/traffic_ctl/CtrlCommands.h +++ b/src/traffic_ctl/CtrlCommands.h @@ -160,6 +160,17 @@ class ConfigCommand : public RecordCommand ConfigCommand(ts::Arguments *args); }; // ----------------------------------------------------------------------------------------------------------------------------------- +class CacheCommand : public CtrlCommand +{ +public: + CacheCommand(ts::Arguments *args); + +private: + static inline const std::string CLEAR_STR{"clear"}; + + void clear(); +}; +// ----------------------------------------------------------------------------------------------------------------------------------- class MetricCommand : public RecordCommand { static inline const std::string MONITOR_STR{"monitor"}; diff --git a/src/traffic_ctl/jsonrpc/CtrlRPCRequests.h b/src/traffic_ctl/jsonrpc/CtrlRPCRequests.h index f3e51b77ce6..6583340529a 100644 --- a/src/traffic_ctl/jsonrpc/CtrlRPCRequests.h +++ b/src/traffic_ctl/jsonrpc/CtrlRPCRequests.h @@ -142,6 +142,14 @@ struct ConfigSetRecordResponse { std::vector data; }; //------------------------------------------------------------------------------------------------------------------------------------ +struct CacheClearRequest : shared::rpc::ClientRequest { + std::string + get_method() const override + { + return "admin_cache_clear"; + } +}; +//------------------------------------------------------------------------------------------------------------------------------------ struct HostStatusLookUpResponse { struct HostStatusInfo { std::string hostName; diff --git a/src/traffic_ctl/traffic_ctl.cc b/src/traffic_ctl/traffic_ctl.cc index 9697aaa05da..f39759e2f2f 100644 --- a/src/traffic_ctl/traffic_ctl.cc +++ b/src/traffic_ctl/traffic_ctl.cc @@ -94,6 +94,7 @@ main([[maybe_unused]] int argc, const char **argv) .add_option("--watch", "-w", "Execute a program periodically. Watch interval(in seconds) can be passed.", "", 1, "-1", "watch"); auto &config_command = parser.add_command("config", "Manipulate configuration records").require_commands(); + auto &cache_command = parser.add_command("cache", "Manage the document cache").require_commands(); auto &metric_command = parser.add_command("metric", "Manipulate performance metrics").require_commands(); auto &server_command = parser.add_command("server", "Stop, restart and examine the server").require_commands(); auto &storage_command = parser.add_command("storage", "Manipulate cache storage").require_commands(); @@ -201,6 +202,10 @@ main([[maybe_unused]] int argc, const char **argv) config_command.add_command("registry", "Show configuration file registry", Command_Execute) .add_example_usage("traffic_ctl config registry"); + // cache commands + cache_command.add_command("clear", "Advance the global cache generation", "", 0, Command_Execute) + .add_example_usage("traffic_ctl cache clear"); + // ssl-multicert subcommand auto &ssl_multicert_command = config_command.add_command("ssl-multicert", "Manage ssl_multicert configuration").require_commands(); @@ -330,6 +335,7 @@ main([[maybe_unused]] int argc, const char **argv) } static const std::map(ts::Arguments *)>> factories = { + {"cache", [](ts::Arguments *a) { return std::make_unique(a); } }, {"metric", [](ts::Arguments *a) { return std::make_unique(a); } }, {"server", [](ts::Arguments *a) { return std::make_unique(a); } }, {"storage", [](ts::Arguments *a) { return std::make_unique(a); } }, diff --git a/src/traffic_server/RpcAdminPubHandlers.cc b/src/traffic_server/RpcAdminPubHandlers.cc index 1e053db966b..58a0794121f 100644 --- a/src/traffic_server/RpcAdminPubHandlers.cc +++ b/src/traffic_server/RpcAdminPubHandlers.cc @@ -21,6 +21,7 @@ #include "mgmt/rpc/jsonrpc/JsonRPC.h" // Admin API Implementation headers. +#include "mgmt/rpc/handlers/cache/Cache.h" #include "mgmt/rpc/handlers/config/Configuration.h" #include "mgmt/rpc/handlers/hostdb/HostDB.h" #include "mgmt/rpc/handlers/records/Records.h" @@ -34,6 +35,10 @@ namespace rpc::admin void register_admin_jsonrpc_handlers() { + // Cache + using namespace rpc::handlers::cache; + rpc::add_method_handler("admin_cache_clear", &clear_cache, &core_ats_rpc_service_provider_handle, {{rpc::RESTRICTED_API}}); + // Config using namespace rpc::handlers::config; rpc::add_method_handler("admin_config_set_records", &set_config_records, &core_ats_rpc_service_provider_handle, diff --git a/tests/gold_tests/cache/cache-generation-clear.test.py b/tests/gold_tests/cache/cache-generation-clear.test.py index 94e7d4d5f5e..8095f2c5194 100644 --- a/tests/gold_tests/cache/cache-generation-clear.test.py +++ b/tests/gold_tests/cache/cache-generation-clear.test.py @@ -64,9 +64,9 @@ tr.Processes.Default.ReturnCode = 0 tr.Processes.Default.Streams.All = "gold/hit_default-1.gold" -# Call traffic_ctrl to set new generation +# Clear the cache through traffic_ctl. The command advances the initial generation from -1 to 0. tr = Test.AddTestRun() -tr.Processes.Default.Command = f'traffic_ctl --debug config set proxy.config.http.cache.generation 77' +tr.Processes.Default.Command = 'traffic_ctl cache clear' tr.Processes.Default.ForceUseShell = False tr.Processes.Default.ReturnCode = 0 tr.Processes.Default.Env = ts.Env # set the environment for traffic_control to run in @@ -80,16 +80,16 @@ ts.Variables.port, objectid), ts=ts) tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.All = "gold/miss_default77.gold" +tr.Processes.Default.Streams.All = "gold/miss_default0.gold" -# new generation should should now hit. +# The new generation should now hit. tr = Test.AddTestRun() tr.MakeCurlCommand( '"http://127.0.0.1:{0}/default/cache/10/{1}" -H "x-debug: x-cache,x-cache-key,via,x-cache-generation" --verbose'.format( ts.Variables.port, objectid), ts=ts) tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.All = "gold/hit_default77.gold" +tr.Processes.Default.Streams.All = "gold/hit_default0.gold" # should still hit. tr = Test.AddTestRun() @@ -98,4 +98,4 @@ ts.Variables.port, objectid), ts=ts) tr.Processes.Default.ReturnCode = 0 -tr.Processes.Default.Streams.All = "gold/hit_default77.gold" +tr.Processes.Default.Streams.All = "gold/hit_default0.gold" diff --git a/tests/gold_tests/cache/gold/hit_default77.gold b/tests/gold_tests/cache/gold/hit_default0.gold similarity index 83% rename from tests/gold_tests/cache/gold/hit_default77.gold rename to tests/gold_tests/cache/gold/hit_default0.gold index 79f4dc85c53..73d272ad25a 100644 --- a/tests/gold_tests/cache/gold/hit_default77.gold +++ b/tests/gold_tests/cache/gold/hit_default0.gold @@ -4,5 +4,5 @@ < Server: ATS/{} < X-Cache-Key: http://127.0.0.1/cache/10/{} < X-Cache: hit-fresh -< X-Cache-Generation: 77 +< X-Cache-Generation: 0 {} diff --git a/tests/gold_tests/cache/gold/miss_default77.gold b/tests/gold_tests/cache/gold/miss_default0.gold similarity index 82% rename from tests/gold_tests/cache/gold/miss_default77.gold rename to tests/gold_tests/cache/gold/miss_default0.gold index 083ff08d092..0972ecee4d3 100644 --- a/tests/gold_tests/cache/gold/miss_default77.gold +++ b/tests/gold_tests/cache/gold/miss_default0.gold @@ -4,5 +4,5 @@ < Server: ATS/{} < X-Cache-Key: http://127.0.0.1/cache/10/{} < X-Cache: miss -< X-Cache-Generation: 77 +< X-Cache-Generation: 0 {}