From b369c09460b46902878da10a106e3b3400fd776e Mon Sep 17 00:00:00 2001 From: "Wludzik, Jozef" Date: Mon, 8 Jun 2020 17:15:54 +0200 Subject: [PATCH 09/10] 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 %% original patch: 0005-Add-support-for-MetricDefinition-scheme.patch Change-Id: Ibcb7a858c9118c8af5ff1167a055b044f0d8db77 --- 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 2587b37..705f490 100644 --- a/redfish-core/include/redfish.hpp +++ b/redfish-core/include/redfish.hpp @@ -25,6 +25,7 @@ #include "../lib/managers.hpp" #include "../lib/memory.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" @@ -211,6 +212,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..1417efa --- /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(App& 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&, + const std::vector&) 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, + 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(App& 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&, + 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, + 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 768cce9..bcb0d3e 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 567cb0c..2f7f70b 100644 --- a/redfish-core/lib/sensors.hpp +++ b/redfish-core/lib/sensors.hpp @@ -54,20 +54,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 b849781..efbef6e 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