Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion include/mgmt/rpc/jsonrpc/json/YAMLCodec.h
Original file line number Diff line number Diff line change
Expand Up @@ -193,8 +193,9 @@ class yamlcpp_json_encoder
json << YAML::Key << "data";
json << YAML::BeginSeq;
for (auto const &err : errata) {
int severity = err.severity(ERRATA_DIAG);
json << YAML::BeginMap;
json << YAML::Key << "code" << YAML::Value << errata.code().value();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a bug in my code. it's printing errata.code all the time. It should be the particular error not the one set in the jsonrpc response.

json << YAML::Key << "severity" << YAML::Value << severity;
json << YAML::Key << "message" << YAML::Value << std::string{err.text().data(), err.text().size()};
json << YAML::EndMap;
}
Expand Down
2 changes: 1 addition & 1 deletion include/shared/rpc/yaml_codecs.h
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ template <> struct convert<shared::rpc::JSONRPCError> {
error.message = helper::try_extract<std::string>(node, "message");
if (auto data = node["data"]) {
for (auto &&err : data) {
error.data.emplace_back(helper::try_extract<int32_t>(err, "code"), helper::try_extract<std::string>(err, "message"));
error.data.emplace_back(helper::try_extract<int32_t>(err, "severity"), helper::try_extract<std::string>(err, "message"));
}
}
return true;
Expand Down
6 changes: 3 additions & 3 deletions src/mgmt/rpc/jsonrpc/unit_tests/test_basic_protocol.cc
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ test_callback_ok_or_error(std::string_view const & /* id ATS_UNUSED */, YAML::No
if (YAML::Node n = params["return_error"]) {
auto yesOrNo = n.as<std::string>();
if (yesOrNo == "yes") {
resp.errata().assign(ERR1).note(err);
resp.errata().note(ERRATA_WARN, err);
} else {
resp.result()["ran"] = "ok";
}
Expand Down Expand Up @@ -140,7 +140,7 @@ TEST_CASE("Register/call method - respond with errors (data field)", "[method][e
R"({"jsonrpc": "2.0", "method": "test_callback_ok_or_error", "params": {"return_error": "yes"}, "id": "14"})");
REQUIRE(json);
const std::string_view expected =
R"({"jsonrpc": "2.0", "error": {"code": 9, "message": "Error during execution", "data": [{"code": 9999, "message": "Just an error message to add more meaning to the failure"}]}, "id": "14"})";
R"({"jsonrpc": "2.0", "error": {"code": 9, "message": "Error during execution", "data": [{"code": 4, "message": "Just an error message to add more meaning to the failure"}]}, "id": "14"})";
REQUIRE(*json == expected);
}
}
Expand Down Expand Up @@ -184,7 +184,7 @@ TEST_CASE("Basic test, batch calls", "[methods][notifications]")

REQUIRE(resp1);
const std::string_view expected =
R"([{"jsonrpc": "2.0", "result": {"ran": "ok"}, "id": "13"}, {"jsonrpc": "2.0", "error": {"code": 9, "message": "Error during execution", "data": [{"code": 9999, "message": "Just an error message to add more meaning to the failure"}]}, "id": "14"}])";
R"([{"jsonrpc": "2.0", "result": {"ran": "ok"}, "id": "13"}, {"jsonrpc": "2.0", "error": {"code": 9, "message": "Error during execution", "data": [{"code": 4, "message": "Just an error message to add more meaning to the failure"}]}, "id": "14"}])";
REQUIRE(*resp1 == expected);
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/traffic_ctl/CtrlPrinters.cc
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ BasePrinter::write_output(shared::rpc::JSONRPCResponse const &response)
}

if (response.is_error()) {
App_Exit_Status_Code = CTRL_EX_ERROR; // Set the exit code to error, so we can return it later.
App_Exit_Status_Code = appExitCodeFromResponse(response);

// If an error is present, then as per the specs we can ignore the jsonrpc.result field,
// so we print the error and we are done here!
Expand Down
8 changes: 7 additions & 1 deletion src/traffic_ctl/TrafficCtlStatus.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,15 @@ limitations under the License.
*/
#pragma once

#include "shared/rpc/RPCRequests.h"
#include "swoc/Errata.h"

constexpr int CTRL_EX_OK = 0;
// EXIT_FAILURE can also be used.
constexpr int CTRL_EX_ERROR = 2;
constexpr int CTRL_EX_UNIMPLEMENTED = 3;

extern int App_Exit_Status_Code; //!< Global variable to store the exit status code of the application.
extern int App_Exit_Status_Code; //!< Global variable to store the exit status code of the application.
extern swoc::Errata::severity_type App_Exit_Level_Error; //!< Minimum severity to treat as error for exit status.

int appExitCodeFromResponse(const shared::rpc::JSONRPCResponse &);
57 changes: 55 additions & 2 deletions src/traffic_ctl/traffic_ctl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
#include <iostream>
#include <csignal>

#include "shared/rpc/RPCRequests.h"
#include "swoc/Errata.h"
#include "swoc/string_view_util.h"
#include "tscore/Layout.h"
#include "tscore/runroot.h"
#include "tscore/ArgParser.h"
Expand All @@ -33,9 +36,41 @@
#include "CtrlCommands.h"
#include "FileConfigCommand.h"
#include "TrafficCtlStatus.h"
#include "tsutil/ts_errata.h"

// Define the global variable
int App_Exit_Status_Code = CTRL_EX_OK; // Initialize it to a default value
int App_Exit_Status_Code = CTRL_EX_OK; // Initialize it to a default value
swoc::Errata::severity_type App_Exit_Level_Error = ERRATA_ERROR;

/// Determine the exit code from a JSONRPC error response by examining
/// the severity of each errata entry. Returns @c CTRL_EX_OK when the
/// most severe entry is below @c App_Exit_Level_Error, otherwise
/// returns @c CTRL_EX_ERROR.
int
appExitCodeFromResponse(const shared::rpc::JSONRPCResponse &response)
{
if (!response.is_error()) {
return CTRL_EX_OK;
}

auto err = response.error.as<shared::rpc::JSONRPCError>();
swoc::Errata::severity_type most_severe = static_cast<swoc::Errata::severity_type>(ERRATA_DIAG);

for (auto const &[code, msg] : err.data) {
swoc::Errata::severity_type sev(code);
Comment on lines +56 to +60
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

appExitCodeFromResponse can return CTRL_EX_OK for real JSON-RPC errors when err.data is empty (e.g., protocol/decoder errors or execution errors without errata details). Because most_severe is initialized to ERRATA_DIAG and only updated from err.data, an error response with no data entries will incorrectly exit 0 by default. Consider treating err.data.empty() as an error (return CTRL_EX_ERROR), or incorporate the top-level JSONRPCError::code into the decision so non-errata errors still produce a non-zero exit status.

Copilot uses AI. Check for mistakes.

if (sev > most_severe) {
most_severe = sev;
}
}

if (most_severe < App_Exit_Level_Error) {
return CTRL_EX_OK;
}

return CTRL_EX_ERROR;
}

namespace
{
void
Expand Down Expand Up @@ -88,7 +123,10 @@ main([[maybe_unused]] int argc, const char **argv)
.add_option("--run-root", "", "using TS_RUNROOT as sandbox", "TS_RUNROOT", 1)
.add_option("--format", "-f", "Use a specific output format {json|rpc}", "", 1, "", "format")
.add_option("--read-timeout-ms", "", "Read timeout for RPC (in milliseconds)", "", 1, "10000", "read-timeout")
.add_option("--read-attempts", "", "Read attempts for RPC", "", 1, "100", "read-attempts");
.add_option("--read-attempts", "", "Read attempts for RPC", "", 1, "100", "read-attempts")
.add_option("--error-level", "-e",
"Minimum severity to treat as error for exit status {diag|debug|status|note|warn|error|fatal|alert|emergency}", "",
1, "error", "error-level");

auto &config_command = parser.add_command("config", "Manipulate configuration records").require_commands();
auto &metric_command = parser.add_command("metric", "Manipulate performance metrics").require_commands();
Expand Down Expand Up @@ -259,6 +297,21 @@ main([[maybe_unused]] int argc, const char **argv)
signal_register_handler(SIGINT, handle_signal);

auto args = parser.parse(argv);

// Set the error level threshold from the CLI option.
auto error_level_str = args.get("error-level").value();
bool found = false;
for (size_t i = 0; i < Severity_Names.size(); ++i) {
if (strcasecmp(Severity_Names[i], error_level_str) == 0) {
App_Exit_Level_Error = swoc::Errata::severity_type(i);
found = true;
break;
}
}
if (!found) {
throw std::runtime_error(std::string("Unknown error level: ") + std::string(error_level_str));
}

argparser_runroot_handler(args.get("run-root").value(), argv[0]);
Layout::create();

Expand Down