Add GET method for Triggers
Added GET method for retrieving details of individual Trigger searched
by given Trigger name, details are extracted from Telemetry service
Tested:
- Added single Trigger and requested result from bmcweb via
/redfish/v1/TelemetryService/Triggers/<triggername>
- Added multiple Triggers numeric and discrete with various parameters
(empty, non-empty), and requested results from bmcweb via
/redfish/v1/TelemetryService/Triggers/<triggername>
- Verified uris /redfish/v1/TelemetryService/Triggers/<triggername> by
using Redfish-Service-Validator (all passed)
Signed-off-by: Lukasz Kazmierczak <lukasz.kazmierczak@intel.com>
Change-Id: I1c966b2f792324cc6f6a8784ad18a683e5ce7bd9
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index e56996d..2397df6 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -208,6 +208,7 @@
requestRoutesMetricReportCollection(app);
requestRoutesMetricReport(app);
requestRoutesTriggerCollection(app);
+ requestRoutesTrigger(app);
}
};
diff --git a/redfish-core/include/utils/time_utils.hpp b/redfish-core/include/utils/time_utils.hpp
index ee21fd7..d4c65de 100644
--- a/redfish-core/include/utils/time_utils.hpp
+++ b/redfish-core/include/utils/time_utils.hpp
@@ -208,5 +208,25 @@
return fmt;
}
+inline std::optional<std::string>
+ toDurationStringFromUint(const uint64_t timeMs)
+{
+ static const uint64_t maxTimeMs =
+ static_cast<uint64_t>(std::chrono::milliseconds::max().count());
+
+ if (maxTimeMs < timeMs)
+ {
+ return std::nullopt;
+ }
+
+ std::string duration = toDurationString(std::chrono::milliseconds(timeMs));
+ if (duration.empty())
+ {
+ return std::nullopt;
+ }
+
+ return std::make_optional(duration);
+}
+
} // namespace time_utils
} // namespace redfish
diff --git a/redfish-core/lib/trigger.hpp b/redfish-core/lib/trigger.hpp
index bbe0887..c3d1448 100644
--- a/redfish-core/lib/trigger.hpp
+++ b/redfish-core/lib/trigger.hpp
@@ -6,6 +6,10 @@
#include <app.hpp>
#include <registries/privilege_registry.hpp>
+#include <tuple>
+#include <variant>
+#include <vector>
+
namespace redfish
{
namespace telemetry
@@ -14,6 +18,265 @@
"xyz.openbmc_project.Telemetry.Trigger";
constexpr const char* triggerUri = "/redfish/v1/TelemetryService/Triggers";
+using NumericThresholdParams =
+ std::tuple<std::string, uint64_t, std::string, double>;
+
+using DiscreteThresholdParams =
+ std::tuple<std::string, std::string, uint64_t, std::string>;
+
+using TriggerThresholdParamsExt =
+ std::variant<std::monostate, std::vector<NumericThresholdParams>,
+ std::vector<DiscreteThresholdParams>>;
+
+using TriggerSensorsParams =
+ std::vector<std::pair<sdbusplus::message::object_path, std::string>>;
+
+using TriggerGetParamsVariant =
+ std::variant<std::monostate, bool, std::string, TriggerThresholdParamsExt,
+ TriggerSensorsParams, std::vector<std::string>>;
+
+inline std::optional<std::string>
+ getRedfishFromDbusAction(const std::string& dbusAction)
+{
+ std::optional<std::string> redfishAction = std::nullopt;
+ if (dbusAction == "UpdateReport")
+ {
+ redfishAction = "RedfishMetricReport";
+ }
+ if (dbusAction == "RedfishEvent")
+ {
+ redfishAction = "RedfishEvent";
+ }
+ if (dbusAction == "LogToLogService")
+ {
+ redfishAction = "LogToLogService";
+ }
+ return redfishAction;
+}
+
+inline std::optional<std::vector<std::string>>
+ getTriggerActions(const std::vector<std::string>& dbusActions)
+{
+ std::vector<std::string> triggerActions;
+ for (const std::string& dbusAction : dbusActions)
+ {
+ std::optional<std::string> redfishAction =
+ getRedfishFromDbusAction(dbusAction);
+
+ if (!redfishAction)
+ {
+ return std::nullopt;
+ }
+
+ triggerActions.push_back(*redfishAction);
+ }
+
+ return std::make_optional(triggerActions);
+}
+
+inline std::optional<nlohmann::json>
+ getDiscreteTriggers(const TriggerThresholdParamsExt& thresholdParams)
+{
+ const std::vector<DiscreteThresholdParams>* discreteParams =
+ std::get_if<std::vector<DiscreteThresholdParams>>(&thresholdParams);
+
+ if (!discreteParams)
+ {
+ return std::nullopt;
+ }
+
+ nlohmann::json triggers = nlohmann::json::array();
+ for (const auto& [name, severity, dwellTime, value] : *discreteParams)
+ {
+ std::optional<std::string> duration =
+ time_utils::toDurationStringFromUint(dwellTime);
+
+ if (!duration)
+ {
+ return std::nullopt;
+ }
+
+ triggers.push_back({
+ {"Name", name},
+ {"Severity", severity},
+ {"DwellTime", *duration},
+ {"Value", value},
+ });
+ }
+
+ return std::make_optional(triggers);
+}
+
+inline std::optional<nlohmann::json>
+ getNumericThresholds(const TriggerThresholdParamsExt& thresholdParams)
+{
+ const std::vector<NumericThresholdParams>* numericParams =
+ std::get_if<std::vector<NumericThresholdParams>>(&thresholdParams);
+
+ if (!numericParams)
+ {
+ return std::nullopt;
+ }
+
+ nlohmann::json thresholds;
+ for (const auto& [type, dwellTime, activation, reading] : *numericParams)
+ {
+ std::optional<std::string> duration =
+ time_utils::toDurationStringFromUint(dwellTime);
+
+ if (!duration)
+ {
+ return std::nullopt;
+ }
+
+ thresholds[type] = {{"Reading", reading},
+ {"Activation", activation},
+ {"DwellTime", *duration}};
+ }
+
+ return std::make_optional(thresholds);
+}
+
+nlohmann::json
+ getMetricReportDefinitions(const std::vector<std::string>& reportNames)
+{
+ nlohmann::json reports = nlohmann::json::array();
+ for (const std::string& name : reportNames)
+ {
+ reports.push_back({
+ {"@odata.id", metricReportDefinitionUri + std::string("/") + name},
+ });
+ }
+
+ return reports;
+}
+
+inline std::vector<std::string>
+ getMetricProperties(const TriggerSensorsParams& sensors)
+{
+ std::vector<std::string> metricProperties;
+ metricProperties.reserve(sensors.size());
+ for (const auto& [_, metadata] : sensors)
+ {
+ metricProperties.emplace_back(metadata);
+ }
+
+ return metricProperties;
+}
+
+inline bool fillTrigger(
+ nlohmann::json& json, const std::string& id,
+ const std::vector<std::pair<std::string, TriggerGetParamsVariant>>&
+ properties)
+{
+ const std::string* name = nullptr;
+ const bool* discrete = nullptr;
+ const TriggerSensorsParams* sensors = nullptr;
+ const std::vector<std::string>* reports = nullptr;
+ const std::vector<std::string>* actions = nullptr;
+ const TriggerThresholdParamsExt* thresholds = nullptr;
+
+ for (const auto& [key, var] : properties)
+ {
+ if (key == "Name")
+ {
+ name = std::get_if<std::string>(&var);
+ }
+ else if (key == "Discrete")
+ {
+ discrete = std::get_if<bool>(&var);
+ }
+ else if (key == "Sensors")
+ {
+ sensors = std::get_if<TriggerSensorsParams>(&var);
+ }
+ else if (key == "ReportNames")
+ {
+ reports = std::get_if<std::vector<std::string>>(&var);
+ }
+ else if (key == "TriggerActions")
+ {
+ actions = std::get_if<std::vector<std::string>>(&var);
+ }
+ else if (key == "Thresholds")
+ {
+ thresholds = std::get_if<TriggerThresholdParamsExt>(&var);
+ }
+ }
+
+ if (!name || !discrete || !sensors || !reports || !actions || !thresholds)
+ {
+ BMCWEB_LOG_ERROR
+ << "Property type mismatch or property is missing in Trigger: "
+ << id;
+ return false;
+ }
+
+ json["@odata.type"] = "#Triggers.v1_2_0.Triggers";
+ json["@odata.id"] = triggerUri + std::string("/") + id;
+ json["Id"] = id;
+ json["Name"] = *name;
+
+ if (*discrete)
+ {
+ std::optional<nlohmann::json> discreteTriggers =
+ getDiscreteTriggers(*thresholds);
+
+ if (!discreteTriggers)
+ {
+ BMCWEB_LOG_ERROR << "Property Thresholds is invalid for discrete "
+ "triggers in Trigger: "
+ << id;
+ return false;
+ }
+
+ json["DiscreteTriggers"] = *discreteTriggers;
+ json["DiscreteTriggerCondition"] =
+ discreteTriggers->empty() ? "Changed" : "Specified";
+ json["MetricType"] = "Discrete";
+ }
+ else
+ {
+ std::optional<nlohmann::json> numericThresholds =
+ getNumericThresholds(*thresholds);
+
+ if (!numericThresholds)
+ {
+ BMCWEB_LOG_ERROR << "Property Thresholds is invalid for numeric "
+ "thresholds in Trigger: "
+ << id;
+ return false;
+ }
+
+ json["NumericThresholds"] = *numericThresholds;
+ json["MetricType"] = "Numeric";
+ }
+
+ std::optional<std::vector<std::string>> triggerActions =
+ getTriggerActions(*actions);
+
+ if (!triggerActions)
+ {
+ BMCWEB_LOG_ERROR << "Property TriggerActions is invalid in Trigger: "
+ << id;
+ return false;
+ }
+
+ json["TriggerActions"] = *triggerActions;
+ json["MetricProperties"] = getMetricProperties(*sensors);
+ json["Links"]["MetricReportDefinitions"] =
+ getMetricReportDefinitions(*reports);
+
+ return true;
+}
+
+inline std::string getDbusTriggerPath(const std::string& id)
+{
+ sdbusplus::message::object_path path(
+ "/xyz/openbmc_project/Telemetry/Triggers/TelemetryService");
+ return {path / id};
+}
+
} // namespace telemetry
inline void requestRoutesTriggerCollection(App& app)
@@ -36,4 +299,44 @@
});
}
+inline void requestRoutesTrigger(App& app)
+{
+ BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/Triggers/<str>/")
+ .privileges(redfish::privileges::getTriggers)
+ .methods(boost::beast::http::verb::get)(
+ [](const crow::Request&,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const std::string& id) {
+ crow::connections::systemBus->async_method_call(
+ [asyncResp,
+ id](const boost::system::error_code ec,
+ const std::vector<std::pair<
+ std::string, telemetry::TriggerGetParamsVariant>>&
+ ret) {
+ if (ec.value() == EBADR ||
+ ec == boost::system::errc::host_unreachable)
+ {
+ messages::resourceNotFound(asyncResp->res,
+ "Triggers", id);
+ return;
+ }
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
+ messages::internalError(asyncResp->res);
+ return;
+ }
+
+ if (!telemetry::fillTrigger(asyncResp->res.jsonValue,
+ id, ret))
+ {
+ messages::internalError(asyncResp->res);
+ }
+ },
+ telemetry::service, telemetry::getDbusTriggerPath(id),
+ "org.freedesktop.DBus.Properties", "GetAll",
+ telemetry::triggerInterface);
+ });
+}
+
} // namespace redfish
diff --git a/redfish-core/ut/time_utils_test.cpp b/redfish-core/ut/time_utils_test.cpp
index d03c316..0a88e65 100644
--- a/redfish-core/ut/time_utils_test.cpp
+++ b/redfish-core/ut/time_utils_test.cpp
@@ -50,3 +50,27 @@
using std::chrono::milliseconds;
EXPECT_EQ(toDurationString(milliseconds(-250)), "");
}
+
+TEST(ToDurationStringFromUintTest, PositiveTests)
+{
+ using redfish::time_utils::toDurationStringFromUint;
+ uint64_t maxAcceptedTimeMs =
+ static_cast<uint64_t>(std::chrono::milliseconds::max().count());
+
+ EXPECT_THAT(toDurationStringFromUint(maxAcceptedTimeMs),
+ ::testing::Ne(std::nullopt));
+ EXPECT_EQ(toDurationStringFromUint(0), "PT");
+ EXPECT_EQ(toDurationStringFromUint(250), "PT0.250S");
+ EXPECT_EQ(toDurationStringFromUint(5000), "PT5.000S");
+}
+
+TEST(ToDurationStringFromUintTest, NegativeTests)
+{
+ using redfish::time_utils::toDurationStringFromUint;
+ uint64_t minNotAcceptedTimeMs =
+ static_cast<uint64_t>(std::chrono::milliseconds::max().count()) + 1;
+
+ EXPECT_EQ(toDurationStringFromUint(minNotAcceptedTimeMs), std::nullopt);
+ EXPECT_EQ(toDurationStringFromUint(static_cast<uint64_t>(-1)),
+ std::nullopt);
+}