This is an automated email from the ASF dual-hosted git repository.

bneradt pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 2d9d666bc6 Add traffic_ctl cache clear command (#13418)
2d9d666bc6 is described below

commit 2d9d666bc698934ea1fda903ec7e727a9213cacc
Author: Brian Neradt <[email protected]>
AuthorDate: Tue Aug 4 14:59:35 2026 -0500

    Add traffic_ctl cache clear command (#13418)
    
    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
---
 doc/appendices/command-line/traffic_ctl.en.rst     | 18 ++++++
 doc/developer-guide/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 ++--
 .../gold/{hit_default77.gold => hit_default0.gold} |  2 +-
 .../{miss_default77.gold => miss_default0.gold}    |  2 +-
 13 files changed, 234 insertions(+), 8 deletions(-)

diff --git a/doc/appendices/command-line/traffic_ctl.en.rst 
b/doc/appendices/command-line/traffic_ctl.en.rst
index 5fff291cf1..6d50dce4ca 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 d0084b7eaa..8a06ecf6b0 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 0000000000..2a7dcefae7
--- /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<YAML::Node> clear_cache(std::string_view const &id, YAML::Node const 
&params);
+} // namespace rpc::handlers::cache
diff --git a/src/mgmt/rpc/CMakeLists.txt b/src/mgmt/rpc/CMakeLists.txt
index 8bd738c48c..cab1ed7f61 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 0000000000..01f6ba7fdf
--- /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 <limits>
+#include <mutex>
+
+namespace
+{
+constexpr char CACHE_GENERATION_RECORD[] = 
"proxy.config.http.cache.generation";
+std::mutex     generation_mutex;
+} // namespace
+
+namespace rpc::handlers::cache
+{
+swoc::Rv<YAML::Node>
+clear_cache(std::string_view const & /* id ATS_UNUSED */, YAML::Node const & 
/* params ATS_UNUSED */)
+{
+  namespace err = rpc::handlers::errors;
+
+  swoc::Rv<YAML::Node> 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<RecInt>::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 8c5d51b4a4..514c5d1709 100644
--- a/src/traffic_ctl/CtrlCommands.cc
+++ b/src/traffic_ctl/CtrlCommands.cc
@@ -737,6 +737,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<GenericPrinter>(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 eb6913109c..0958fb7069 100644
--- a/src/traffic_ctl/CtrlCommands.h
+++ b/src/traffic_ctl/CtrlCommands.h
@@ -160,6 +160,17 @@ public:
   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 f3e51b77ce..6583340529 100644
--- a/src/traffic_ctl/jsonrpc/CtrlRPCRequests.h
+++ b/src/traffic_ctl/jsonrpc/CtrlRPCRequests.h
@@ -142,6 +142,14 @@ struct ConfigSetRecordResponse {
   std::vector<UpdatedRec> 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 9697aaa05d..f39759e2f2 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<std::string, 
std::function<std::unique_ptr<CtrlCommand>(ts::Arguments *)>> factories = {
+      {"cache",   [](ts::Arguments *a) { return 
std::make_unique<CacheCommand>(a); }    },
       {"metric",  [](ts::Arguments *a) { return 
std::make_unique<MetricCommand>(a); }   },
       {"server",  [](ts::Arguments *a) { return 
std::make_unique<ServerCommand>(a); }   },
       {"storage", [](ts::Arguments *a) { return 
std::make_unique<StorageCommand>(a); }  },
diff --git a/src/traffic_server/RpcAdminPubHandlers.cc 
b/src/traffic_server/RpcAdminPubHandlers.cc
index 1e053db966..58a0794121 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 94e7d4d5f5..8095f2c519 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.MakeCurlCommand(
 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 @@ tr.MakeCurlCommand(
         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 @@ tr.MakeCurlCommand(
         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 79f4dc85c5..73d272ad25 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 083ff08d09..0972ecee4d 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
 {}

Reply via email to