From b1da8901b5985d6a77b63ca9eb0570b46528f0bd Mon Sep 17 00:00:00 2001 From: "Wludzik, Jozef" Date: Mon, 8 Jun 2020 17:15:54 +0200 Subject: [PATCH 5/5] Add support for MetricDefinition scheme Added MetricDefinition node to redfish core. Now user is able to get all possible metrics that are present in system and are supported by TelemetryService. Tested: - Succesfully passed RedfishServiceValidator.py - Validated a presence of MetricDefinition members Signed-off-by: Wludzik, Jozef Signed-off-by: Krzysztof Grobelny Change-Id: I3086e1302e1ba2e5442d1367939fd5507a0cbc00 --- redfish-core/include/redfish.hpp | 3 + redfish-core/include/utils/telemetry_utils.hpp | 2 + redfish-core/lib/metric_definition.hpp | 300 +++++++++++++++++++++++++ redfish-core/lib/metric_report.hpp | 65 +++++- redfish-core/lib/sensors.hpp | 43 +++- redfish-core/lib/telemetry_service.hpp | 2 + 6 files changed, 402 insertions(+), 13 deletions(-) create mode 100644 redfish-core/lib/metric_definition.hpp diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp index 3d4c117..2a12bf9 100644 --- a/redfish-core/include/redfish.hpp +++ b/redfish-core/include/redfish.hpp @@ -25,6 +25,7 @@ #include "../lib/log_services.hpp" #include "../lib/managers.hpp" #include "../lib/message_registries.hpp" +#include "../lib/metric_definition.hpp" #include "../lib/metric_report.hpp" #include "../lib/metric_report_definition.hpp" #include "../lib/network_protocol.hpp" @@ -206,6 +207,8 @@ class RedfishService nodes.emplace_back(std::make_unique(app)); nodes.emplace_back(std::make_unique(app)); + nodes.emplace_back(std::make_unique(app)); + nodes.emplace_back(std::make_unique(app)); nodes.emplace_back( std::make_unique(app)); nodes.emplace_back(std::make_unique(app)); diff --git a/redfish-core/include/utils/telemetry_utils.hpp b/redfish-core/include/utils/telemetry_utils.hpp index 6c4e810..bb747c4 100644 --- a/redfish-core/include/utils/telemetry_utils.hpp +++ b/redfish-core/include/utils/telemetry_utils.hpp @@ -22,6 +22,8 @@ namespace redfish namespace telemetry { +static constexpr const char* metricDefinitionUri = + "/redfish/v1/TelemetryService/MetricDefinitions/"; static constexpr const char* metricReportDefinitionUri = "/redfish/v1/TelemetryService/MetricReportDefinitions/"; static constexpr const char* metricReportUri = diff --git a/redfish-core/lib/metric_definition.hpp b/redfish-core/lib/metric_definition.hpp new file mode 100644 index 0000000..837a068 --- /dev/null +++ b/redfish-core/lib/metric_definition.hpp @@ -0,0 +1,300 @@ +/* +// Copyright (c) 2018-2020 Intel Corporation +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +*/ + +#pragma once + +#include "node.hpp" +#include "sensors.hpp" +#include "utils/telemetry_utils.hpp" + +namespace redfish +{ + +namespace chassis +{ +template +static inline void getChassisNames(F&& callback) +{ + const std::array interfaces = { + "xyz.openbmc_project.Inventory.Item.Board", + "xyz.openbmc_project.Inventory.Item.Chassis"}; + + dbus::utility::getSubTreePaths( + [callback{std::move(callback)}](const boost::system::error_code ec, + std::vector& chassisList) { + if (ec) + { + return; + } + + std::vector chassisNames; + chassisNames.reserve(chassisList.size()); + for (auto& chassisPath : chassisList) + { + auto pos = chassisPath.rfind("/"); + if (pos == std::string::npos) + { + continue; + } + chassisNames.push_back(chassisPath.substr(pos + 1)); + } + + callback(chassisNames); + }, + "/xyz/openbmc_project/inventory", 0, interfaces); +} +} // namespace chassis + +class MetricDefinitionCollection : public Node +{ + public: + MetricDefinitionCollection(CrowApp& app) : + Node(app, "/redfish/v1/TelemetryService/MetricDefinitions") + { + entityPrivileges = { + {boost::beast::http::verb::get, {{"Login"}}}, + {boost::beast::http::verb::head, {{"Login"}}}, + {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, + {boost::beast::http::verb::put, {{"ConfigureManager"}}}, + {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, + {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; + } + + private: + void doGet(crow::Response& res, const crow::Request& req, + const std::vector& params) override + { + res.jsonValue["@odata.type"] = "#MetricDefinitionCollection." + "MetricDefinitionCollection"; + res.jsonValue["@odata.id"] = + "/redfish/v1/TelemetryService/MetricDefinitions"; + res.jsonValue["Name"] = "Metric Definition Collection"; + res.jsonValue["Members"] = nlohmann::json::array(); + res.jsonValue["Members@odata.count"] = sensors::dbus::types.size(); + + auto asyncResp = std::make_shared(res); + auto collectionReduce = std::make_shared(asyncResp); + chassis::getChassisNames( + [asyncResp, + collectionReduce](const std::vector& chassisNames) { + for (auto& chassisName : chassisNames) + { + for (auto& [sensorNode, _] : sensors::dbus::types) + { + BMCWEB_LOG_INFO << "Chassis: " << chassisName + << " sensor: " << sensorNode; + retrieveUriToDbusMap( + chassisName, sensorNode.data(), + [asyncResp, collectionReduce]( + const boost::beast::http::status status, + const boost::container::flat_map< + std::string, std::string>& uriToDbus) { + *collectionReduce += uriToDbus; + }); + } + } + }); + } + + class CollectionGather + { + public: + CollectionGather(const std::shared_ptr& asyncResp) : + asyncResp{asyncResp} + { + dbusTypes.reserve(sensors::dbus::paths.size()); + } + + ~CollectionGather() + { + json_util::dbusPathsToMembersArray( + asyncResp->res, + std::vector(dbusTypes.begin(), dbusTypes.end()), + telemetry::metricDefinitionUri); + } + + CollectionGather& operator+=( + const boost::container::flat_map& rhs) + { + for (auto& [_, dbusSensor] : rhs) + { + auto pos = dbusSensor.rfind("/"); + if (pos == std::string::npos) + { + BMCWEB_LOG_ERROR << "Received invalid DBus Sensor Path = " + << dbusSensor; + continue; + } + + this->dbusTypes.insert(dbusSensor.substr(0, pos)); + } + return *this; + } + + private: + const std::shared_ptr asyncResp; + boost::container::flat_set dbusTypes; + }; +}; + +class MetricDefinition : public Node +{ + public: + MetricDefinition(CrowApp& app) : + Node(app, std::string(telemetry::metricDefinitionUri) + "/", + std::string()) + { + entityPrivileges = { + {boost::beast::http::verb::get, {{"Login"}}}, + {boost::beast::http::verb::head, {{"Login"}}}, + {boost::beast::http::verb::patch, {{"ConfigureManager"}}}, + {boost::beast::http::verb::put, {{"ConfigureManager"}}}, + {boost::beast::http::verb::delete_, {{"ConfigureManager"}}}, + {boost::beast::http::verb::post, {{"ConfigureManager"}}}}; + } + + private: + void doGet(crow::Response& res, const crow::Request& req, + const std::vector& params) override + { + auto asyncResp = std::make_shared(res); + if (params.size() != 1) + { + messages::internalError(asyncResp->res); + return; + } + + const std::string& id = params[0]; + + size_t sensorIndex = 0; + for (auto& name : sensors::dbus::names) + { + if (name == id) + { + break; + } + sensorIndex++; + } + if (sensorIndex >= sensors::dbus::max) + { + messages::resourceNotFound(asyncResp->res, schemaType, id); + return; + } + + auto definitionGather = + std::make_shared(asyncResp, id); + chassis::getChassisNames( + [asyncResp, definitionGather, + sensorIndex](const std::vector& chassisNames) { + for (auto& chassisName : chassisNames) + { + for (auto& [sensorNode, dbusPaths] : sensors::dbus::types) + { + auto found = + std::find(dbusPaths.begin(), dbusPaths.end(), + sensors::dbus::paths[sensorIndex]); + if (found == dbusPaths.end()) + { + continue; + } + + retrieveUriToDbusMap( + chassisName, sensorNode.data(), + [asyncResp, definitionGather]( + const boost::beast::http::status status, + const boost::container::flat_map< + std::string, std::string>& uriToDbus) { + *definitionGather += uriToDbus; + }); + } + } + }); + } + + class DefinitionGather + { + public: + DefinitionGather(const std::shared_ptr& asyncResp, + const std::string& id) : + id(id), + asyncResp{asyncResp} + {} + ~DefinitionGather() + { + if (redfishSensors.empty()) + { + messages::resourceNotFound(asyncResp->res, schemaType, id); + return; + } + + asyncResp->res.jsonValue["MetricProperties"] = + nlohmann::json::array(); + auto& members = asyncResp->res.jsonValue["MetricProperties"]; + for (auto& redfishSensor : redfishSensors) + { + members.push_back(redfishSensor); + } + + asyncResp->res.jsonValue["Id"] = id; + asyncResp->res.jsonValue["Name"] = id; + asyncResp->res.jsonValue["@odata.id"] = + telemetry::metricDefinitionUri + id; + asyncResp->res.jsonValue["@odata.type"] = schemaType; + asyncResp->res.jsonValue["MetricDataType"] = "Decimal"; + asyncResp->res.jsonValue["MetricType"] = "Numeric"; + asyncResp->res.jsonValue["Implementation"] = "PhysicalSensor"; + asyncResp->res.jsonValue["IsLinear"] = true; + asyncResp->res.jsonValue["TimestampAccuracy"] = "PT0.1S"; + auto unit = sensorUnits.find(id); + if (unit != sensorUnits.end()) + { + asyncResp->res.jsonValue["Units"] = unit->second; + } + } + + DefinitionGather& operator+=( + const boost::container::flat_map& rhs) + { + for (auto& [redfishSensor, dbusSensor] : rhs) + { + if (dbusSensor.find(id) != std::string::npos) + { + this->redfishSensors.push_back(redfishSensor); + } + } + return *this; + } + + const std::string id; + + private: + const std::shared_ptr asyncResp; + std::vector redfishSensors; + const boost::container::flat_map sensorUnits = + {{sensors::dbus::names[sensors::dbus::voltage], "V"}, + {sensors::dbus::names[sensors::dbus::power], "W"}, + {sensors::dbus::names[sensors::dbus::current], "A"}, + {sensors::dbus::names[sensors::dbus::fan_tach], "RPM"}, + {sensors::dbus::names[sensors::dbus::temperature], "Cel"}, + {sensors::dbus::names[sensors::dbus::utilization], "%"}, + {sensors::dbus::names[sensors::dbus::fan_pwm], "Duty cycle"}}; + }; + + static constexpr const char* schemaType = + "#MetricDefinition.v1_0_3.MetricDefinition"; +}; + +} // namespace redfish diff --git a/redfish-core/lib/metric_report.hpp b/redfish-core/lib/metric_report.hpp index 877e7f1..be72b18 100644 --- a/redfish-core/lib/metric_report.hpp +++ b/redfish-core/lib/metric_report.hpp @@ -91,6 +91,9 @@ class MetricReport : public Node using Readings = std::vector>; using MetricValues = std::vector>; + using ReadingParameters = + std::vector, + std::string, std::string, std::string>>; static MetricValues toMetricValues(const Readings& readings) { @@ -109,6 +112,49 @@ class MetricReport : public Node return metricValues; } + static void addMetricDefinition(nlohmann::json& metrics, + const ReadingParameters& params) + { + for (auto& metric : metrics) + { + if (!metric.contains("MetricId")) + { + continue; + } + + auto& id = metric["MetricId"].get_ref(); + auto param = + std::find_if(params.begin(), params.end(), [id](const auto& x) { + return id == std::get<2>(x); + }); + if (param == params.end()) + { + continue; + } + + auto& dbusPaths = + std::get>(*param); + if (dbusPaths.size() > 1) + { + continue; + } + + auto dbusPath = dbusPaths.begin(); + for (size_t i = 0; i < sensors::dbus::paths.size(); i++) + { + if (dbusPath->str.find(sensors::dbus::paths[i]) == + std::string::npos) + { + continue; + } + metric["MetricDefinition"]["@odata.id"] = + telemetry::metricDefinitionUri + + std::string(sensors::dbus::names[i]); + break; + } + } + } + static void getReportProperties(const std::shared_ptr asyncResp, const std::string& reportPath, const std::string& id) @@ -124,7 +170,8 @@ class MetricReport : public Node [asyncResp]( const boost::system::error_code ec, const boost::container::flat_map< - std::string, std::variant>& ret) { + std::string, + std::variant>& ret) { if (ec) { messages::internalError(asyncResp->res); @@ -138,6 +185,22 @@ class MetricReport : public Node json_util::assignIfPresent( ret, "Readings", asyncResp->res.jsonValue["MetricValues"], toMetricValues); + + auto found = ret.find("ReadingParameters"); + if (found != ret.end()) + { + auto params = + std::get_if(&found->second); + if (params) + { + auto& jsonValue = asyncResp->res.jsonValue; + if (jsonValue.contains("MetricValues")) + { + addMetricDefinition(jsonValue["MetricValues"], + *params); + } + } + } }, "xyz.openbmc_project.MonitoringService", reportPath, "xyz.openbmc_project.MonitoringService.Report"); diff --git a/redfish-core/lib/sensors.hpp b/redfish-core/lib/sensors.hpp index f12bbe0..1fa1009 100644 --- a/redfish-core/lib/sensors.hpp +++ b/redfish-core/lib/sensors.hpp @@ -53,20 +53,39 @@ static constexpr std::string_view thermal = "Thermal"; namespace dbus { + +enum Index +{ + voltage = 0, + power, + current, + fan_tach, + temperature, + fan_pwm, + utilization, + max +}; + +static constexpr std::array names = { + "voltage", "power", "current", "fan_tach", + "temperature", "fan_pwm", "utilization"}; + +static constexpr std::array paths = { + "/xyz/openbmc_project/sensors/voltage", + "/xyz/openbmc_project/sensors/power", + "/xyz/openbmc_project/sensors/current", + "/xyz/openbmc_project/sensors/fan_tach", + "/xyz/openbmc_project/sensors/temperature", + "/xyz/openbmc_project/sensors/fan_pwm", + "/xyz/openbmc_project/sensors/utilization"}; + static const boost::container::flat_map> - types = {{node::power, - {"/xyz/openbmc_project/sensors/voltage", - "/xyz/openbmc_project/sensors/power"}}, - {node::sensors, - {"/xyz/openbmc_project/sensors/power", - "/xyz/openbmc_project/sensors/current", - "/xyz/openbmc_project/sensors/utilization"}}, - {node::thermal, - {"/xyz/openbmc_project/sensors/fan_tach", - "/xyz/openbmc_project/sensors/temperature", - "/xyz/openbmc_project/sensors/fan_pwm"}}}; -} + types = { + {node::power, {paths[voltage], paths[power]}}, + {node::sensors, {paths[power], paths[current], paths[utilization]}}, + {node::thermal, {paths[fan_tach], paths[temperature], paths[fan_pwm]}}}; +} // namespace dbus } // namespace sensors /** diff --git a/redfish-core/lib/telemetry_service.hpp b/redfish-core/lib/telemetry_service.hpp index a410700..79e4154 100644 --- a/redfish-core/lib/telemetry_service.hpp +++ b/redfish-core/lib/telemetry_service.hpp @@ -52,6 +52,8 @@ class TelemetryService : public Node res.jsonValue["LogService"]["@odata.id"] = "/redfish/v1/Managers/bmc/LogServices/Journal"; + res.jsonValue["MetricDefinitions"]["@odata.id"] = + "/redfish/v1/TelemetryService/MetricDefinitions"; res.jsonValue["MetricReportDefinitions"]["@odata.id"] = "/redfish/v1/TelemetryService/MetricReportDefinitions"; res.jsonValue["MetricReports"]["@odata.id"] = -- 2.16.6