xref: /openbmc/bmcweb/redfish-core/lib/environment_metrics.hpp (revision 378f1d6138791d1d936ae3bc2114ddcafe124ed0)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 #pragma once
4 
5 #include "app.hpp"
6 #include "async_resp.hpp"
7 #include "dbus_singleton.hpp"
8 #include "dbus_utility.hpp"
9 #include "error_messages.hpp"
10 #include "http_request.hpp"
11 #include "logging.hpp"
12 #include "query.hpp"
13 #include "registries/privilege_registry.hpp"
14 #include "utils/chassis_utils.hpp"
15 #include "utils/sensor_utils.hpp"
16 
17 #include <boost/beast/http/field.hpp>
18 #include <boost/beast/http/verb.hpp>
19 #include <boost/url/format.hpp>
20 #include <nlohmann/json.hpp>
21 #include <sdbusplus/asio/property.hpp>
22 
23 #include <array>
24 #include <functional>
25 #include <memory>
26 #include <optional>
27 #include <string>
28 #include <string_view>
29 #include <utility>
30 
31 namespace redfish
32 {
33 
afterGetPowerWatts(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::string & path,const boost::system::error_code & ec,const dbus::utility::DBusPropertiesMap & valuesDict)34 inline void afterGetPowerWatts(
35     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
36     const std::string& chassisId, const std::string& path,
37     const boost::system::error_code& ec,
38     const dbus::utility::DBusPropertiesMap& valuesDict)
39 {
40     if (ec)
41     {
42         if (ec != boost::system::errc::io_error)
43         {
44             BMCWEB_LOG_ERROR("DBUS response error for PowerWatts {}", ec);
45             messages::internalError(asyncResp->res);
46         }
47         return;
48     }
49 
50     nlohmann::json item = nlohmann::json::object();
51 
52     /* Don't return an error for a failure to fill in properties from the
53      * single sensor. Just skip adding it.
54      */
55     if (sensor_utils::objectExcerptToJson(
56             path, chassisId,
57             sensor_utils::ChassisSubNode::environmentMetricsNode, "power",
58             valuesDict, item))
59     {
60         asyncResp->res.jsonValue["PowerWatts"] = std::move(item);
61     }
62 }
63 
handleTotalPowerList(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const boost::system::error_code & ec,const std::shared_ptr<sensor_utils::SensorServicePathList> & sensorList)64 inline void handleTotalPowerList(
65     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
66     const std::string& chassisId, const boost::system::error_code& ec,
67     const std::shared_ptr<sensor_utils::SensorServicePathList>& sensorList)
68 {
69     BMCWEB_LOG_DEBUG("handleTotalPowerList: {}", sensorList->size());
70 
71     if (ec)
72     {
73         if (ec != boost::system::errc::io_error)
74         {
75             BMCWEB_LOG_ERROR("D-Bus response error {}", ec);
76             messages::internalError(asyncResp->res);
77         }
78         return;
79     }
80 
81     // TotalPower cannot be supplied by multiple sensors
82     if (sensorList->size() != 1)
83     {
84         if (sensorList->empty())
85         {
86             // None found, not an error
87             return;
88         }
89         BMCWEB_LOG_ERROR("Too many total power sensors found {}. Expected 1.",
90                          sensorList->size());
91         messages::internalError(asyncResp->res);
92         return;
93     }
94 
95     const std::string& serviceName = (*sensorList)[0].first;
96     const std::string& sensorPath = (*sensorList)[0].second;
97     sdbusplus::asio::getAllProperties(
98         *crow::connections::systemBus, serviceName, sensorPath,
99         "xyz.openbmc_project.Sensor.Value",
100         [asyncResp, chassisId,
101          sensorPath](const boost::system::error_code& ec1,
102                      const dbus::utility::DBusPropertiesMap& propertiesList) {
103             afterGetPowerWatts(asyncResp, chassisId, sensorPath, ec1,
104                                propertiesList);
105         });
106 }
107 
getTotalPowerSensor(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const boost::system::error_code & ec,const sensor_utils::SensorServicePathList & sensorsServiceAndPath)108 inline void getTotalPowerSensor(
109     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
110     const std::string& chassisId, const boost::system::error_code& ec,
111     const sensor_utils::SensorServicePathList& sensorsServiceAndPath)
112 {
113     BMCWEB_LOG_DEBUG("getTotalPowerSensor {}", sensorsServiceAndPath.size());
114 
115     if (ec)
116     {
117         if (ec != boost::system::errc::io_error)
118         {
119             BMCWEB_LOG_ERROR("DBUS response error {}", ec);
120             messages::internalError(asyncResp->res);
121         }
122         // None found, not an error
123         return;
124     }
125 
126     if (sensorsServiceAndPath.empty())
127     {
128         // No power sensors implement Sensor.Purpose, not an error
129         return;
130     }
131 
132     // Create vector to hold list of sensors with totalPower purpose
133     std::shared_ptr<sensor_utils::SensorServicePathList> sensorList =
134         std::make_shared<sensor_utils::SensorServicePathList>();
135 
136     sensor_utils::getSensorsByPurpose(
137         asyncResp, sensorsServiceAndPath,
138         sensor_utils::SensorPurpose::totalPower, sensorList,
139         std::bind_front(handleTotalPowerList, asyncResp, chassisId));
140 }
141 
142 /**
143  * @brief Find sensor providing totalPower and fill in response
144  *
145  * Multiple D-Bus calls are needed to find the sensor providing the totalPower
146  * details:
147  *
148  * 1. Retrieve list of power sensors associated with specified chassis which
149  * implement the Sensor.Purpose interface.
150  *
151  * 2. For each of those power sensors retrieve the actual purpose of the sensor
152  * to find the sensor implementing totalPower purpose. Expect no more than
153  * one sensor to implement this purpose.
154  *
155  * 3. If a totalPower sensor is found then retrieve its properties to fill in
156  * PowerWatts in the response.
157  *
158  * @param asyncResp Response data
159  * @param validChassisPath Path to chassis, caller confirms path is valid
160  * @param chassisId Chassis id matching <validChassisPath>
161  */
getPowerWatts(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & validChassisPath,const std::string & chassisId)162 inline void getPowerWatts(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
163                           const std::string& validChassisPath,
164                           const std::string& chassisId)
165 {
166     BMCWEB_LOG_DEBUG("getPowerWatts: {}", validChassisPath);
167 
168     constexpr std::array<std::string_view, 1> interfaces = {
169         "xyz.openbmc_project.Sensor.Purpose"};
170     sensor_utils::getAllSensorObjects(
171         validChassisPath, "/xyz/openbmc_project/sensors/power", interfaces, 1,
172         std::bind_front(getTotalPowerSensor, asyncResp, chassisId));
173 }
174 
handleEnvironmentMetricsHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)175 inline void handleEnvironmentMetricsHead(
176     App& app, const crow::Request& req,
177     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
178     const std::string& chassisId)
179 {
180     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
181     {
182         return;
183     }
184 
185     auto respHandler = [asyncResp, chassisId](
186                            const std::optional<std::string>& validChassisPath) {
187         if (!validChassisPath)
188         {
189             messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
190             return;
191         }
192 
193         asyncResp->res.addHeader(
194             boost::beast::http::field::link,
195             "</redfish/v1/JsonSchemas/EnvironmentMetrics/EnvironmentMetrics.json>; rel=describedby");
196     };
197 
198     redfish::chassis_utils::getValidChassisPath(asyncResp, chassisId,
199                                                 std::move(respHandler));
200 }
201 
doEnvironmentMetricsGet(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId,const std::optional<std::string> & validChassisPath)202 inline void doEnvironmentMetricsGet(
203     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
204     const std::string& chassisId,
205     const std::optional<std::string>& validChassisPath)
206 {
207     if (!validChassisPath)
208     {
209         messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
210         return;
211     }
212 
213     asyncResp->res.addHeader(
214         boost::beast::http::field::link,
215         "</redfish/v1/JsonSchemas/EnvironmentMetrics/EnvironmentMetrics.json>; rel=describedby");
216     asyncResp->res.jsonValue["@odata.type"] =
217         "#EnvironmentMetrics.v1_3_0.EnvironmentMetrics";
218     asyncResp->res.jsonValue["Name"] = "Chassis Environment Metrics";
219     asyncResp->res.jsonValue["Id"] = "EnvironmentMetrics";
220     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
221         "/redfish/v1/Chassis/{}/EnvironmentMetrics", chassisId);
222 
223     getPowerWatts(asyncResp, *validChassisPath, chassisId);
224 }
225 
handleEnvironmentMetricsGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & chassisId)226 inline void handleEnvironmentMetricsGet(
227     App& app, const crow::Request& req,
228     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
229     const std::string& chassisId)
230 {
231     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
232     {
233         return;
234     }
235 
236     redfish::chassis_utils::getValidChassisPath(
237         asyncResp, chassisId,
238         std::bind_front(doEnvironmentMetricsGet, asyncResp, chassisId));
239 }
240 
requestRoutesEnvironmentMetrics(App & app)241 inline void requestRoutesEnvironmentMetrics(App& app)
242 {
243     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/EnvironmentMetrics/")
244         .privileges(redfish::privileges::headEnvironmentMetrics)
245         .methods(boost::beast::http::verb::head)(
246             std::bind_front(handleEnvironmentMetricsHead, std::ref(app)));
247 
248     BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/EnvironmentMetrics/")
249         .privileges(redfish::privileges::getEnvironmentMetrics)
250         .methods(boost::beast::http::verb::get)(
251             std::bind_front(handleEnvironmentMetricsGet, std::ref(app)));
252 }
253 
254 } // namespace redfish
255