diff --git a/include/mgmt/rpc/jsonrpc/json/YAMLCodec.h b/include/mgmt/rpc/jsonrpc/json/YAMLCodec.h index cc29dfe2dd9..b27bd8b320f 100644 --- a/include/mgmt/rpc/jsonrpc/json/YAMLCodec.h +++ b/include/mgmt/rpc/jsonrpc/json/YAMLCodec.h @@ -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(); + json << YAML::Key << "severity" << YAML::Value << severity; json << YAML::Key << "message" << YAML::Value << std::string{err.text().data(), err.text().size()}; json << YAML::EndMap; } diff --git a/include/shared/rpc/yaml_codecs.h b/include/shared/rpc/yaml_codecs.h index 1e61742a515..42c5d233dbb 100644 --- a/include/shared/rpc/yaml_codecs.h +++ b/include/shared/rpc/yaml_codecs.h @@ -64,7 +64,7 @@ template <> struct convert { error.message = helper::try_extract(node, "message"); if (auto data = node["data"]) { for (auto &&err : data) { - error.data.emplace_back(helper::try_extract(err, "code"), helper::try_extract(err, "message")); + error.data.emplace_back(helper::try_extract(err, "severity"), helper::try_extract(err, "message")); } } return true; diff --git a/src/mgmt/rpc/jsonrpc/unit_tests/test_basic_protocol.cc b/src/mgmt/rpc/jsonrpc/unit_tests/test_basic_protocol.cc index f5d8e525c17..eb18e6a19a6 100644 --- a/src/mgmt/rpc/jsonrpc/unit_tests/test_basic_protocol.cc +++ b/src/mgmt/rpc/jsonrpc/unit_tests/test_basic_protocol.cc @@ -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(); if (yesOrNo == "yes") { - resp.errata().assign(ERR1).note(err); + resp.errata().note(ERRATA_WARN, err); } else { resp.result()["ran"] = "ok"; } @@ -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); } } @@ -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); } } diff --git a/src/traffic_ctl/CtrlPrinters.cc b/src/traffic_ctl/CtrlPrinters.cc index 55e51de20e6..09bffc83106 100644 --- a/src/traffic_ctl/CtrlPrinters.cc +++ b/src/traffic_ctl/CtrlPrinters.cc @@ -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! diff --git a/src/traffic_ctl/TrafficCtlStatus.h b/src/traffic_ctl/TrafficCtlStatus.h index b7d758fd25c..61dd0d74e91 100644 --- a/src/traffic_ctl/TrafficCtlStatus.h +++ b/src/traffic_ctl/TrafficCtlStatus.h @@ -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 &); diff --git a/src/traffic_ctl/traffic_ctl.cc b/src/traffic_ctl/traffic_ctl.cc index 1f6bc416867..f7dc1e44d96 100644 --- a/src/traffic_ctl/traffic_ctl.cc +++ b/src/traffic_ctl/traffic_ctl.cc @@ -24,6 +24,9 @@ #include #include +#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" @@ -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(); + swoc::Errata::severity_type most_severe = static_cast(ERRATA_DIAG); + + for (auto const &[code, msg] : err.data) { + swoc::Errata::severity_type sev(code); + + if (sev > most_severe) { + most_severe = sev; + } + } + + if (most_severe < App_Exit_Level_Error) { + return CTRL_EX_OK; + } + + return CTRL_EX_ERROR; +} + namespace { void @@ -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(); @@ -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();