#pragma once #include "sensors.hpp" #include "utils/telemetry_utils.hpp" #include "utils/time_utils.hpp" #include #include #include #include #include #include #include namespace redfish { namespace telemetry { constexpr const char* metricReportDefinitionUri = "/redfish/v1/TelemetryService/MetricReportDefinitions"; using ReadingParameters = std::vector>; inline void fillReportDefinition(const std::shared_ptr& 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) .string(); asyncResp->res.jsonValue["Id"] = id; asyncResp->res.jsonValue["Name"] = id; asyncResp->res.jsonValue["MetricReport"]["@odata.id"] = crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", "MetricReports", id) .string(); asyncResp->res.jsonValue["Status"]["State"] = "Enabled"; asyncResp->res.jsonValue["ReportUpdates"] = "Overwrite"; const bool* emitsReadingsUpdate = nullptr; const bool* logToMetricReportsCollection = nullptr; const ReadingParameters* readingParams = nullptr; const std::string* reportingType = nullptr; const uint64_t* interval = nullptr; for (const auto& [key, var] : ret) { if (key == "EmitsReadingsUpdate") { emitsReadingsUpdate = std::get_if(&var); } else if (key == "LogToMetricReportsCollection") { logToMetricReportsCollection = std::get_if(&var); } else if (key == "ReadingParameters") { readingParams = std::get_if(&var); } else if (key == "ReportingType") { reportingType = std::get_if(&var); } else if (key == "Interval") { interval = std::get_if(&var); } } if (emitsReadingsUpdate == nullptr || logToMetricReportsCollection == nullptr || readingParams == nullptr || reportingType == nullptr || interval == nullptr) { BMCWEB_LOG_ERROR << "Property type mismatch or property is missing"; messages::internalError(asyncResp->res); return; } std::vector redfishReportActions; redfishReportActions.reserve(2); if (*emitsReadingsUpdate) { redfishReportActions.emplace_back("RedfishEvent"); } if (*logToMetricReportsCollection) { redfishReportActions.emplace_back("LogToMetricReportsCollection"); } nlohmann::json metrics = nlohmann::json::array(); for (const auto& [sensorPath, operationType, id, metadata] : *readingParams) { metrics.push_back({ {"MetricId", id}, {"MetricProperties", {metadata}}, }); } asyncResp->res.jsonValue["Metrics"] = metrics; asyncResp->res.jsonValue["MetricReportDefinitionType"] = *reportingType; asyncResp->res.jsonValue["ReportActions"] = redfishReportActions; asyncResp->res.jsonValue["Schedule"]["RecurrenceInterval"] = time_utils::toDurationString(std::chrono::milliseconds(*interval)); } struct AddReportArgs { std::string name; std::string reportingType; bool emitsReadingsUpdate = false; bool logToMetricReportsCollection = false; uint64_t interval = 0; std::vector>> metrics; }; inline bool toDbusReportActions(crow::Response& res, std::vector& 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 metrics; std::vector reportActions; std::optional 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 durationNum = time_utils::fromDurationString(durationStr); if (!durationNum) { messages::propertyValueIncorrect(res, "RecurrenceInterval", durationStr); return false; } args.interval = static_cast(durationNum->count()); } args.metrics.reserve(metrics.size()); for (auto& m : metrics) { std::string id; std::vector 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& asyncResp, const std::vector>>& metrics, boost::container::flat_set>& matched) { for (const auto& metric : metrics) { const std::vector& uris = metric.second; std::optional 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& asyncResp) : asyncResp(asyncResp), 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 aResp = asyncResp; crow::connections::systemBus->async_method_call( [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); } AddReport(const AddReport&) = delete; AddReport(AddReport&&) = delete; AddReport& operator=(const AddReport&) = delete; AddReport& operator=(AddReport&&) = delete; void insert(const boost::container::flat_map& el) { uriToDbus.insert(el.begin(), el.end()); } private: const std::shared_ptr asyncResp; AddReportArgs args; boost::container::flat_map uriToDbus{}; }; } // namespace telemetry inline void requestRoutesMetricReportDefinitionCollection(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") .privileges(redfish::privileges::getMetricReportDefinitionCollection) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) { return; } asyncResp->res.jsonValue["@odata.type"] = "#MetricReportDefinitionCollection." "MetricReportDefinitionCollection"; asyncResp->res.jsonValue["@odata.id"] = telemetry::metricReportDefinitionUri; asyncResp->res.jsonValue["Name"] = "Metric Definition Collection"; const std::vector interfaces{ telemetry::reportInterface}; collection_util::getCollectionMembers( asyncResp, telemetry::metricReportDefinitionUri, interfaces, "/xyz/openbmc_project/Telemetry/Reports/TelemetryService"); }); BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions/") .privileges(redfish::privileges::postMetricReportDefinitionCollection) .methods( boost::beast::http::verb:: post)([&app]( const crow::Request& req, const std::shared_ptr& asyncResp) { if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) { return; } telemetry::AddReportArgs args; if (!telemetry::getUserParameters(asyncResp->res, req, args)) { return; } boost::container::flat_set> chassisSensors; if (!telemetry::getChassisSensorNodeFromMetrics( asyncResp, args.metrics, chassisSensors)) { return; } auto addReportReq = std::make_shared( std::move(args), asyncResp); for (const auto& [chassis, sensorType] : chassisSensors) { retrieveUriToDbusMap( chassis, sensorType, [asyncResp, addReportReq](const boost::beast::http::status status, const boost::container::flat_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(status); return; } addReportReq->insert(uriToDbus); }); } }); } inline void requestRoutesMetricReportDefinition(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions//") .privileges(redfish::privileges::getMetricReportDefinition) .methods(boost::beast::http::verb::get)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& id) { if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) { return; } crow::connections::systemBus->async_method_call( [asyncResp, id](const boost::system::error_code ec, const std::vector>& 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); }, telemetry::service, telemetry::getDbusReportPath(id), "org.freedesktop.DBus.Properties", "GetAll", telemetry::reportInterface); }); BMCWEB_ROUTE(app, "/redfish/v1/TelemetryService/MetricReportDefinitions//") .privileges(redfish::privileges::deleteMetricReportDefinitionCollection) .methods(boost::beast::http::verb::delete_)( [&app](const crow::Request& req, const std::shared_ptr& asyncResp, const std::string& id) { if (!redfish::setUpRedfishRoute(app, req, asyncResp->res)) { return; } const std::string reportPath = telemetry::getDbusReportPath(id); crow::connections::systemBus->async_method_call( [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"); }); } } // namespace redfish