| #pragma once |
| |
| #include "app.hpp" |
| #include "dbus_utility.hpp" |
| #include "query.hpp" |
| #include "registries/privilege_registry.hpp" |
| #include "sensors.hpp" |
| #include "utils/collection.hpp" |
| #include "utils/dbus_utils.hpp" |
| #include "utils/telemetry_utils.hpp" |
| #include "utils/time_utils.hpp" |
| |
| #include <boost/container/flat_map.hpp> |
| #include <sdbusplus/asio/property.hpp> |
| #include <sdbusplus/unpack_properties.hpp> |
| |
| #include <array> |
| #include <map> |
| #include <string_view> |
| #include <tuple> |
| #include <variant> |
| |
| #include "managed_store.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 |