blob: e68b7273fc58ee185fa93758adf910034e914074 [file] [log] [blame]
#ifndef THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_METRIC_REPORT_DEFINITION_H_
#define THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_METRIC_REPORT_DEFINITION_H_
#include <array>
#include <chrono> // NOLINT
#include <cstddef>
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
#include "boost/container/flat_map.hpp" // NOLINT
#include "app.hpp"
#include "http_request.hpp"
#include "http_response.hpp"
#include "logging.hpp"
#include "utility.hpp"
#include "async_resp.hpp"
#include "dbus_utility.hpp"
#include "error_messages.hpp"
#include "query.hpp"
#include "registries/privilege_registry.hpp"
#include "collection.hpp"
#include "dbus_utils.hpp"
#include "json_utils.hpp"
#include "telemetry_utils.hpp"
#include "time_utils.hpp"
#include "sensors.hpp"
#include <nlohmann/json.hpp>
#include "managed_store.hpp"
#include "managed_store_types.hpp"
#include "sdbusplus/message/native_types.hpp"
#include "sdbusplus/unpack_properties.hpp"
#ifdef UNIT_TEST_BUILD
#include "test/g3/mock_managed_store.hpp" // NOLINT
#endif
namespace redfish {
namespace telemetry {
using ReadingParameters =
std::vector<std::tuple<sdbusplus::message::object_path, std::string,
std::string, std::string>>;
inline void fillReportDefinition(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, const std::string& id,
const dbus::utility::DBusPropertiesMap& ret) {
asyncResp->res.jsonValue["@odata.type"] =
"#MetricReportDefinition.v1_3_0.MetricReportDefinition";
asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
"redfish", "v1", "TelemetryService", "MetricReportDefinitions", id);
asyncResp->res.jsonValue["Id"] = id;
asyncResp->res.jsonValue["Name"] = id;
asyncResp->res.jsonValue["MetricReport"]["@odata.id"] =
crow::utility::urlFromPieces("redfish", "v1", "TelemetryService",
"MetricReports", id);
asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite";
const bool* emitsReadingsUpdate = nullptr;
const bool* logToMetricReportsCollection = nullptr;
const ReadingParameters* readingParameters = nullptr;
const std::string* reportingType = nullptr;
const uint64_t* interval = nullptr;
const bool success = sdbusplus::unpackPropertiesNoThrow(
dbus_utils::UnpackErrorPrinter(), ret, "EmitsReadingsUpdate",
emitsReadingsUpdate, "LogToMetricReportsCollection",
logToMetricReportsCollection, "ReadingParameters", readingParameters,
"ReportingType", reportingType, "Interval", interval);
if (!success) {
messages::internalError(asyncResp->res);
return;
}
std::vector<std::string> redfishReportActions;
redfishReportActions.reserve(2);
if (emitsReadingsUpdate != nullptr && *emitsReadingsUpdate) {
redfishReportActions.emplace_back("RedfishEvent");
}
if (logToMetricReportsCollection != nullptr &&
*logToMetricReportsCollection) {
redfishReportActions.emplace_back("LogToMetricReportsCollection");
}
nlohmann::json metrics = nlohmann::json::array();
if (readingParameters != nullptr) {
for (const auto& [sensorPath, operationType, metricId, metadata] :
*readingParameters) {
nlohmann::json::object_t metric;
metric["MetricId"] = metricId;
metric["MetricProperties"] = nlohmann::json::array_t({metadata});
metrics.push_back(std::move(metric));
}
}
if (reportingType != nullptr) {
asyncResp->res.jsonValue["MetricReportDefinitionType"] = *reportingType;
}
if (interval != nullptr) {
asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] =
time_utils::toDurationString(std::chrono::milliseconds(*interval));
}
asyncResp->res.jsonValue["Metrics"] = metrics;
asyncResp->res.jsonValue["ReportActions"] = redfishReportActions;
}
struct AddReportArgs {
std::string name;
std::string reportingType;
bool emitsReadingsUpdate = false;
bool logToMetricReportsCollection = false;
uint64_t interval = 0;
std::vector<std::pair<std::string, std::vector<std::string>>> metrics;
};
inline bool toDbusReportActions(crow::Response& res,
std::vector<std::string>& actions,
AddReportArgs& args) {
size_t index = 0;
for (auto& action : actions) {
if (action == "RedfishEvent") {
args.emitsReadingsUpdate = true;
} else if (action == "LogToMetricReportsCollection") {
args.logToMetricReportsCollection = true;
} else {
messages::propertyValueNotInList(
res, action, "ReportActions/" + std::to_string(index));
return false;
}
index++;
}
return true;
}
inline bool getUserParameters(crow::Response& res, const crow::Request& req,
AddReportArgs& args) {
std::vector<nlohmann::json> metrics;
std::vector<std::string> reportActions;
std::optional<nlohmann::json> schedule;
if (!json_util::readJsonPatch(req, res, "Id", args.name, "Metrics", metrics,
"MetricReportDefinitionType",
args.reportingType, "ReportActions",
reportActions, "Schedule", schedule)) {
return false;
}
constexpr const char* allowedCharactersInName =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
if (args.name.empty() || args.name.find_first_not_of(
allowedCharactersInName) != std::string::npos) {
BMCWEB_LOG_ERROR << "Failed to match " << args.name
<< " with allowed character " << allowedCharactersInName;
messages::propertyValueIncorrect(res, "Id", args.name);
return false;
}
if (args.reportingType != "Periodic" && args.reportingType != "OnRequest") {
messages::propertyValueNotInList(res, args.reportingType,
"MetricReportDefinitionType");
return false;
}
if (!toDbusReportActions(res, reportActions, args)) {
return false;
}
if (args.reportingType == "Periodic") {
if (!schedule) {
messages::createFailedMissingReqProperties(res, "Schedule");
return false;
}
std::string durationStr;
if (!json_util::readJson(*schedule, res, "RecurrenceInterval",
durationStr)) {
return false;
}
std::optional<std::chrono::milliseconds> durationNum =
time_utils::fromDurationString(durationStr);
if (!durationNum) {
messages::propertyValueIncorrect(res, "RecurrenceInterval", durationStr);
return false;
}
args.interval = static_cast<uint64_t>(durationNum->count());
}
args.metrics.reserve(metrics.size());
for (auto& m : metrics) {
std::string id;
std::vector<std::string> uris;
if (!json_util::readJson(m, res, "MetricId", id, "MetricProperties",
uris)) {
return false;
}
args.metrics.emplace_back(std::move(id), std::move(uris));
}
return true;
}
inline bool getChassisSensorNodeFromMetrics(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::vector<std::pair<std::string, std::vector<std::string>>>&
metrics,
boost::container::flat_set<std::pair<std::string, std::string>>& matched) {
for (const auto& metric : metrics) {
const std::vector<std::string>& uris = metric.second;
std::optional<IncorrectMetricUri> error =
getChassisSensorNode(uris, matched);
if (error) {
messages::propertyValueIncorrect(
asyncResp->res, error->uri,
"MetricProperties/" + std::to_string(error->index));
return false;
}
}
return true;
}
class AddReport {
public:
AddReport(AddReportArgs argsIn,
const std::shared_ptr<bmcweb::AsyncResp>& asyncRespIn)
: asyncResp(asyncRespIn), args{std::move(argsIn)} {}
~AddReport() {
if (asyncResp->res.result() != boost::beast::http::status::ok) {
return;
}
telemetry::ReadingParameters readingParams;
readingParams.reserve(args.metrics.size());
for (const auto& [id, uris] : args.metrics) {
for (size_t i = 0; i < uris.size(); i++) {
const std::string& uri = uris[i];
auto el = uriToDbus.find(uri);
if (el == uriToDbus.end()) {
BMCWEB_LOG_ERROR << "Failed to find DBus sensor corresponding to URI "
<< uri;
messages::propertyValueNotInList(
asyncResp->res, uri, "MetricProperties/" + std::to_string(i));
return;
}
const std::string& dbusPath = el->second;
readingParams.emplace_back(dbusPath, "SINGLE", id, uri);
}
}
const std::shared_ptr<bmcweb::AsyncResp> aResp = asyncResp;
#ifndef UNIT_TEST_BUILD
// Hard to mock
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
aResp->strand_,
[aResp, name = args.name, uriToDbus = std::move(uriToDbus)](
const boost::system::error_code& ec, const std::string&) {
if (ec == boost::system::errc::file_exists) {
messages::resourceAlreadyExists(
aResp->res, "MetricReportDefinition", "Id", name);
return;
}
if (ec == boost::system::errc::too_many_files_open) {
messages::createLimitReachedForResource(aResp->res);
return;
}
if (ec == boost::system::errc::argument_list_too_long) {
nlohmann::json metricProperties = nlohmann::json::array();
for (const auto& [uri, _] : uriToDbus) {
metricProperties.emplace_back(uri);
}
messages::propertyValueIncorrect(
aResp->res, metricProperties.dump(), "MetricProperties");
return;
}
if (ec) {
messages::internalError(aResp->res);
BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
return;
}
messages::created(aResp->res);
},
telemetry::service, "/xyz/openbmc_project/Telemetry/Reports",
"xyz.openbmc_project.Telemetry.ReportManager", "AddReport",
"TelemetryService/" + args.name, args.reportingType,
args.emitsReadingsUpdate, args.logToMetricReportsCollection,
args.interval, readingParams);
#endif
}
AddReport(const AddReport&) = delete;
AddReport(AddReport&&) = delete;
AddReport& operator=(const AddReport&) = delete;
AddReport& operator=(AddReport&&) = delete;
void insert(const std::map<std::string, std::string>& el) {
uriToDbus.insert(el.begin(), el.end());
}
private:
const std::shared_ptr<bmcweb::AsyncResp> asyncResp;
AddReportArgs args;
boost::container::flat_map<std::string, std::string> uriToDbus;
};
} // namespace telemetry
inline void handleGetMetricReportDefinitionCollection(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
asyncResp->res.jsonValue["@odata.type"] =
"#MetricReportDefinitionCollection."
"MetricReportDefinitionCollection";
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/TelemetryService/MetricReportDefinitions";
asyncResp->res.jsonValue["Name"] = "Metric Definition Collection";
constexpr std::array<std::string_view, 1> interfaces{
telemetry::reportInterface};
collection_util::getCollectionMembers(
asyncResp,
boost::urls::url("/redfish/v1/TelemetryService/MetricReportDefinitions"),
interfaces, "/xyz/openbmc_project/Telemetry/Reports/TelemetryService");
}
inline void handlePostMetricReportDefinitionCollection(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
telemetry::AddReportArgs args;
if (!telemetry::getUserParameters(asyncResp->res, req, args)) {
return;
}
boost::container::flat_set<std::pair<std::string, std::string>>
chassisSensors;
if (!telemetry::getChassisSensorNodeFromMetrics(asyncResp, args.metrics,
chassisSensors)) {
return;
}
auto addReportReq =
std::make_shared<telemetry::AddReport>(std::move(args), asyncResp);
// TODO: (b/376349303) Potential memory leak
// NOLINTNEXTLINE(clang-analyzer-cplusplus.NewDeleteLeaks)
for (const auto& [chassis, sensorType] : chassisSensors) {
retrieveUriToDbusMap(
chassis, sensorType,
[asyncResp, addReportReq](
const boost::beast::http::status status,
const std::map<std::string, std::string>& uriToDbus) {
if (status != boost::beast::http::status::ok) {
BMCWEB_LOG_ERROR
<< "Failed to retrieve URI to dbus sensors map with err "
<< static_cast<unsigned>(status);
return;
}
addReportReq->insert(uriToDbus);
});
}
}
inline void requestRoutesMetricReportDefinitionCollection(App& app) {
BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/")
.privileges(redfish::privileges::getMetricReportDefinitionCollection)
.methods(boost::beast::http::verb::get)(std::bind_front(
handleGetMetricReportDefinitionCollection, std::ref(app)));
BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/")
.privileges(redfish::privileges::postMetricReportDefinitionCollection)
.methods(boost::beast::http::verb::post)(std::bind_front(
handlePostMetricReportDefinitionCollection, std::ref(app)));
}
inline void handleGetMetricReportDefinition(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& id) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
managedStore::ManagedObjectStoreContext context(asyncResp);
managedStore::GetManagedObjectStore()->getAllProperties(
telemetry::service, telemetry::getDbusReportPath(id),
telemetry::reportInterface, context,
[asyncResp, id](
const boost::system::error_code& ec,
const std::vector<
std::pair<std::string, dbus::utility::DbusVariantType>>& ret) {
if (ec.value() == EBADR ||
ec == boost::system::errc::host_unreachable) {
messages::resourceNotFound(asyncResp->res, "MetricReportDefinition",
id);
return;
}
if (ec) {
BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
messages::internalError(asyncResp->res);
return;
}
telemetry::fillReportDefinition(asyncResp, id, ret);
});
}
inline void handleDeleteMetricReportDefinition(
App& app, const crow::Request& req,
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& id) {
if (!redfish::setUpRedfishRoute(app, req, asyncResp)) {
return;
}
const std::string reportPath = telemetry::getDbusReportPath(id);
managedStore::GetManagedObjectStore()->PostDbusCallToIoContextThreadSafe(
asyncResp->strand_,
[asyncResp, id](const boost::system::error_code& ec) {
/*
* boost::system::errc and std::errc are missing value
* for EBADR error that is defined in Linux.
*/
if (ec.value() == EBADR) {
messages::resourceNotFound(asyncResp->res, "MetricReportDefinition",
id);
return;
}
if (ec) {
BMCWEB_LOG_ERROR << "respHandler DBus error " << ec;
messages::internalError(asyncResp->res);
return;
}
asyncResp->res.result(boost::beast::http::status::no_content);
},
telemetry::service, reportPath, "xyz.openbmc_project.Object.Delete",
"Delete");
}
inline void requestRoutesMetricReportDefinition(App& app) {
BMCWEB_ROUTE(app,
"/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/")
.privileges(redfish::privileges::getMetricReportDefinition)
.methods(boost::beast::http::verb::get)(
std::bind_front(handleGetMetricReportDefinition, std::ref(app)));
BMCWEB_ROUTE(app,
"/redfish/v1/TelemetryService/MetricReportDefinitions/<str>/")
.privileges(redfish::privileges::deleteMetricReportDefinitionCollection)
.methods(boost::beast::http::verb::delete_)(
std::bind_front(handleDeleteMetricReportDefinition, std::ref(app)));
}
} // namespace redfish
#endif // THIRD_PARTY_GBMCWEB_REDFISH_CORE_LIB_METRIC_REPORT_DEFINITION_H_