xref: /openbmc/bmcweb/features/redfish/lib/aggregation_service.hpp (revision 66620686e54d54a1ea4ba537a5f745f839343ac8)
140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
36c068982SEd Tanous #pragma once
46c068982SEd Tanous 
56c068982SEd Tanous #include "app.hpp"
6d7857201SEd Tanous #include "async_resp.hpp"
76c068982SEd Tanous #include "error_messages.hpp"
86c068982SEd Tanous #include "http_request.hpp"
96c068982SEd Tanous #include "http_response.hpp"
10d7857201SEd Tanous #include "logging.hpp"
11*66620686SEd Tanous #include "ossl_random.hpp"
126c068982SEd Tanous #include "query.hpp"
138b2521a5SCarson Labrado #include "redfish_aggregator.hpp"
146c068982SEd Tanous #include "registries/privilege_registry.hpp"
15*66620686SEd Tanous #include "utility.hpp"
16*66620686SEd Tanous #include "utils/json_utils.hpp"
176c068982SEd Tanous 
18d7857201SEd Tanous #include <boost/beast/http/field.hpp>
19d7857201SEd Tanous #include <boost/beast/http/verb.hpp>
20*66620686SEd Tanous #include <boost/system/result.hpp>
21ef4c65b7SEd Tanous #include <boost/url/format.hpp>
22*66620686SEd Tanous #include <boost/url/parse.hpp>
23d7857201SEd Tanous #include <boost/url/url.hpp>
246c068982SEd Tanous #include <nlohmann/json.hpp>
256c068982SEd Tanous 
26*66620686SEd Tanous #include <cstddef>
276c068982SEd Tanous #include <functional>
286c068982SEd Tanous #include <memory>
29d7857201SEd Tanous #include <unordered_map>
30d7857201SEd Tanous #include <utility>
316c068982SEd Tanous 
326c068982SEd Tanous namespace redfish
336c068982SEd Tanous {
346c068982SEd Tanous 
356c068982SEd Tanous inline void handleAggregationServiceHead(
366c068982SEd Tanous     App& app, const crow::Request& req,
376c068982SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
386c068982SEd Tanous {
396c068982SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
406c068982SEd Tanous     {
416c068982SEd Tanous         return;
426c068982SEd Tanous     }
436c068982SEd Tanous     asyncResp->res.addHeader(
446c068982SEd Tanous         boost::beast::http::field::link,
456c068982SEd Tanous         "</redfish/v1/JsonSchemas/AggregationService/AggregationService.json>; rel=describedby");
466c068982SEd Tanous }
476c068982SEd Tanous 
486c068982SEd Tanous inline void handleAggregationServiceGet(
496c068982SEd Tanous     App& app, const crow::Request& req,
506c068982SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
516c068982SEd Tanous {
526c068982SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
536c068982SEd Tanous     {
546c068982SEd Tanous         return;
556c068982SEd Tanous     }
566c068982SEd Tanous     asyncResp->res.addHeader(
576c068982SEd Tanous         boost::beast::http::field::link,
586c068982SEd Tanous         "</redfish/v1/JsonSchemas/AggregationService/AggregationService.json>; rel=describedby");
596c068982SEd Tanous     nlohmann::json& json = asyncResp->res.jsonValue;
606c068982SEd Tanous     json["@odata.id"] = "/redfish/v1/AggregationService";
616c068982SEd Tanous     json["@odata.type"] = "#AggregationService.v1_0_1.AggregationService";
626c068982SEd Tanous     json["Id"] = "AggregationService";
636c068982SEd Tanous     json["Name"] = "Aggregation Service";
646c068982SEd Tanous     json["Description"] = "Aggregation Service";
656c068982SEd Tanous     json["ServiceEnabled"] = true;
665315c1b1SCarson Labrado     json["AggregationSources"]["@odata.id"] =
675315c1b1SCarson Labrado         "/redfish/v1/AggregationService/AggregationSources";
686c068982SEd Tanous }
696c068982SEd Tanous 
708b2521a5SCarson Labrado inline void requestRoutesAggregationService(App& app)
716c068982SEd Tanous {
726c068982SEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/AggregationService/")
736c068982SEd Tanous         .privileges(redfish::privileges::headAggregationService)
746c068982SEd Tanous         .methods(boost::beast::http::verb::head)(
756c068982SEd Tanous             std::bind_front(handleAggregationServiceHead, std::ref(app)));
766c068982SEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/AggregationService/")
776c068982SEd Tanous         .privileges(redfish::privileges::getAggregationService)
786c068982SEd Tanous         .methods(boost::beast::http::verb::get)(
796c068982SEd Tanous             std::bind_front(handleAggregationServiceGet, std::ref(app)));
806c068982SEd Tanous }
816c068982SEd Tanous 
828b2521a5SCarson Labrado inline void populateAggregationSourceCollection(
838b2521a5SCarson Labrado     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
8481c4e330SEd Tanous     const boost::system::error_code& ec,
858b2521a5SCarson Labrado     const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
868b2521a5SCarson Labrado {
878b2521a5SCarson Labrado     // Something went wrong while querying dbus
888b2521a5SCarson Labrado     if (ec)
898b2521a5SCarson Labrado     {
908b2521a5SCarson Labrado         messages::internalError(asyncResp->res);
918b2521a5SCarson Labrado         return;
928b2521a5SCarson Labrado     }
9382b286fbSEd Tanous     nlohmann::json::array_t members;
948b2521a5SCarson Labrado     for (const auto& sat : satelliteInfo)
958b2521a5SCarson Labrado     {
968b2521a5SCarson Labrado         nlohmann::json::object_t member;
97ef4c65b7SEd Tanous         member["@odata.id"] = boost::urls::format(
98ef4c65b7SEd Tanous             "/redfish/v1/AggregationService/AggregationSources/{}", sat.first);
99ad539545SPatrick Williams         members.emplace_back(std::move(member));
1008b2521a5SCarson Labrado     }
1018b2521a5SCarson Labrado     asyncResp->res.jsonValue["Members@odata.count"] = members.size();
1028b2521a5SCarson Labrado     asyncResp->res.jsonValue["Members"] = std::move(members);
1038b2521a5SCarson Labrado }
1048b2521a5SCarson Labrado 
1058b2521a5SCarson Labrado inline void handleAggregationSourceCollectionGet(
1065315c1b1SCarson Labrado     App& app, const crow::Request& req,
1075315c1b1SCarson Labrado     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1085315c1b1SCarson Labrado {
1095315c1b1SCarson Labrado     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1105315c1b1SCarson Labrado     {
1115315c1b1SCarson Labrado         return;
1125315c1b1SCarson Labrado     }
1135315c1b1SCarson Labrado     asyncResp->res.addHeader(
1145315c1b1SCarson Labrado         boost::beast::http::field::link,
1155315c1b1SCarson Labrado         "</redfish/v1/JsonSchemas/AggregationSourceCollection/AggregationSourceCollection.json>; rel=describedby");
1165315c1b1SCarson Labrado     nlohmann::json& json = asyncResp->res.jsonValue;
1175315c1b1SCarson Labrado     json["@odata.id"] = "/redfish/v1/AggregationService/AggregationSources";
1185315c1b1SCarson Labrado     json["@odata.type"] =
1195315c1b1SCarson Labrado         "#AggregationSourceCollection.AggregationSourceCollection";
1205315c1b1SCarson Labrado     json["Name"] = "Aggregation Source Collection";
1215315c1b1SCarson Labrado 
1228b2521a5SCarson Labrado     // Query D-Bus for satellite configs and add them to the Members array
123*66620686SEd Tanous     RedfishAggregator::getInstance().getSatelliteConfigs(
1248b2521a5SCarson Labrado         std::bind_front(populateAggregationSourceCollection, asyncResp));
1255315c1b1SCarson Labrado }
1265315c1b1SCarson Labrado 
1278b2521a5SCarson Labrado inline void handleAggregationSourceCollectionHead(
1288b2521a5SCarson Labrado     App& app, const crow::Request& req,
1298b2521a5SCarson Labrado     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
1308b2521a5SCarson Labrado {
1318b2521a5SCarson Labrado     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
1328b2521a5SCarson Labrado     {
1338b2521a5SCarson Labrado         return;
1348b2521a5SCarson Labrado     }
1358b2521a5SCarson Labrado     asyncResp->res.addHeader(
1368b2521a5SCarson Labrado         boost::beast::http::field::link,
1378b2521a5SCarson Labrado         "</redfish/v1/JsonSchemas/AggregationService/AggregationSourceCollection.json>; rel=describedby");
1388b2521a5SCarson Labrado }
1398b2521a5SCarson Labrado 
1408b2521a5SCarson Labrado inline void requestRoutesAggregationSourceCollection(App& app)
1415315c1b1SCarson Labrado {
1425315c1b1SCarson Labrado     BMCWEB_ROUTE(app, "/redfish/v1/AggregationService/AggregationSources/")
1438b2521a5SCarson Labrado         .privileges(redfish::privileges::getAggregationSourceCollection)
1448b2521a5SCarson Labrado         .methods(boost::beast::http::verb::get)(std::bind_front(
1458b2521a5SCarson Labrado             handleAggregationSourceCollectionGet, std::ref(app)));
1468b2521a5SCarson Labrado 
1478b2521a5SCarson Labrado     BMCWEB_ROUTE(app, "/redfish/v1/AggregationService/AggregationSources/")
1488b2521a5SCarson Labrado         .privileges(redfish::privileges::getAggregationSourceCollection)
1498b2521a5SCarson Labrado         .methods(boost::beast::http::verb::head)(std::bind_front(
1508b2521a5SCarson Labrado             handleAggregationSourceCollectionHead, std::ref(app)));
1518b2521a5SCarson Labrado }
1528b2521a5SCarson Labrado 
1538b2521a5SCarson Labrado inline void populateAggregationSource(
1548b2521a5SCarson Labrado     const std::string& aggregationSourceId,
1558b2521a5SCarson Labrado     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
15681c4e330SEd Tanous     const boost::system::error_code& ec,
1578b2521a5SCarson Labrado     const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
1588b2521a5SCarson Labrado {
1598b2521a5SCarson Labrado     asyncResp->res.addHeader(
1608b2521a5SCarson Labrado         boost::beast::http::field::link,
1618b2521a5SCarson Labrado         "</redfish/v1/JsonSchemas/AggregationSource/AggregationSource.json>; rel=describedby");
1628b2521a5SCarson Labrado 
1638b2521a5SCarson Labrado     // Something went wrong while querying dbus
1648b2521a5SCarson Labrado     if (ec)
1658b2521a5SCarson Labrado     {
1668b2521a5SCarson Labrado         messages::internalError(asyncResp->res);
1678b2521a5SCarson Labrado         return;
1688b2521a5SCarson Labrado     }
1698b2521a5SCarson Labrado 
1708b2521a5SCarson Labrado     const auto& sat = satelliteInfo.find(aggregationSourceId);
1718b2521a5SCarson Labrado     if (sat == satelliteInfo.end())
1728b2521a5SCarson Labrado     {
1738b2521a5SCarson Labrado         messages::resourceNotFound(asyncResp->res, "AggregationSource",
1748b2521a5SCarson Labrado                                    aggregationSourceId);
1758b2521a5SCarson Labrado         return;
1768b2521a5SCarson Labrado     }
1778b2521a5SCarson Labrado 
178ef4c65b7SEd Tanous     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
179ef4c65b7SEd Tanous         "/redfish/v1/AggregationService/AggregationSources/{}",
180ef4c65b7SEd Tanous         aggregationSourceId);
1818b2521a5SCarson Labrado     asyncResp->res.jsonValue["@odata.type"] =
1828b2521a5SCarson Labrado         "#AggregationSource.v1_3_1.AggregationSource";
1838b2521a5SCarson Labrado     asyncResp->res.jsonValue["Id"] = aggregationSourceId;
1848b2521a5SCarson Labrado 
1858b2521a5SCarson Labrado     // TODO: We may want to change this whenever we support aggregating multiple
1868b2521a5SCarson Labrado     // satellite BMCs.  Otherwise all AggregationSource resources will have the
1878b2521a5SCarson Labrado     // same "Name".
1888b2521a5SCarson Labrado     // TODO: We should use the "Name" from the satellite config whenever we add
1898b2521a5SCarson Labrado     // support for including it in the data returned in satelliteInfo.
1908b2521a5SCarson Labrado     asyncResp->res.jsonValue["Name"] = "Aggregation source";
1918b2521a5SCarson Labrado     std::string hostName(sat->second.encoded_origin());
1928b2521a5SCarson Labrado     asyncResp->res.jsonValue["HostName"] = std::move(hostName);
1938b2521a5SCarson Labrado 
1948b2521a5SCarson Labrado     // The Redfish spec requires Password to be null in responses
1958b2521a5SCarson Labrado     asyncResp->res.jsonValue["Password"] = nullptr;
1968b2521a5SCarson Labrado }
1978b2521a5SCarson Labrado 
1988b2521a5SCarson Labrado inline void handleAggregationSourceGet(
1998b2521a5SCarson Labrado     App& app, const crow::Request& req,
2008b2521a5SCarson Labrado     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2018b2521a5SCarson Labrado     const std::string& aggregationSourceId)
2028b2521a5SCarson Labrado {
2038b2521a5SCarson Labrado     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2048b2521a5SCarson Labrado     {
2058b2521a5SCarson Labrado         return;
2068b2521a5SCarson Labrado     }
2078b2521a5SCarson Labrado 
2088b2521a5SCarson Labrado     // Query D-Bus for satellite config corresponding to the specified
2098b2521a5SCarson Labrado     // AggregationSource
210*66620686SEd Tanous     RedfishAggregator::getInstance().getSatelliteConfigs(std::bind_front(
2118b2521a5SCarson Labrado         populateAggregationSource, aggregationSourceId, asyncResp));
2128b2521a5SCarson Labrado }
2138b2521a5SCarson Labrado 
2148b2521a5SCarson Labrado inline void handleAggregationSourceHead(
2158b2521a5SCarson Labrado     App& app, const crow::Request& req,
2168b2521a5SCarson Labrado     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2178b2521a5SCarson Labrado     const std::string& aggregationSourceId)
2188b2521a5SCarson Labrado {
2198b2521a5SCarson Labrado     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
2208b2521a5SCarson Labrado     {
2218b2521a5SCarson Labrado         return;
2228b2521a5SCarson Labrado     }
2238b2521a5SCarson Labrado     asyncResp->res.addHeader(
2248b2521a5SCarson Labrado         boost::beast::http::field::link,
2258b2521a5SCarson Labrado         "</redfish/v1/JsonSchemas/AggregationService/AggregationSource.json>; rel=describedby");
2268b2521a5SCarson Labrado 
2278b2521a5SCarson Labrado     // Needed to prevent unused variable error
22862598e31SEd Tanous     BMCWEB_LOG_DEBUG("Added link header to response from {}",
22962598e31SEd Tanous                      aggregationSourceId);
2308b2521a5SCarson Labrado }
2318b2521a5SCarson Labrado 
232*66620686SEd Tanous inline void handleAggregationSourceCollectionPost(
233*66620686SEd Tanous     App& app, const crow::Request& req,
234*66620686SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
235*66620686SEd Tanous {
236*66620686SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
237*66620686SEd Tanous     {
238*66620686SEd Tanous         return;
239*66620686SEd Tanous     }
240*66620686SEd Tanous     std::string hostname;
241*66620686SEd Tanous     if (!json_util::readJsonPatch(req, asyncResp->res, "HostName", hostname))
242*66620686SEd Tanous     {
243*66620686SEd Tanous         return;
244*66620686SEd Tanous     }
245*66620686SEd Tanous 
246*66620686SEd Tanous     boost::system::result<boost::urls::url> url =
247*66620686SEd Tanous         boost::urls::parse_absolute_uri(hostname);
248*66620686SEd Tanous     if (!url)
249*66620686SEd Tanous     {
250*66620686SEd Tanous         messages::propertyValueIncorrect(asyncResp->res, hostname, "HostName");
251*66620686SEd Tanous         return;
252*66620686SEd Tanous     }
253*66620686SEd Tanous     url->normalize();
254*66620686SEd Tanous     if (url->scheme() != "http" && url->scheme() != "https")
255*66620686SEd Tanous     {
256*66620686SEd Tanous         messages::propertyValueIncorrect(asyncResp->res, hostname, "HostName");
257*66620686SEd Tanous         return;
258*66620686SEd Tanous     }
259*66620686SEd Tanous     crow::utility::setPortDefaults(*url);
260*66620686SEd Tanous 
261*66620686SEd Tanous     std::string prefix = bmcweb::getRandomIdOfLength(8);
262*66620686SEd Tanous     RedfishAggregator::getInstance().currentAggregationSources.emplace(
263*66620686SEd Tanous         prefix, *url);
264*66620686SEd Tanous 
265*66620686SEd Tanous     asyncResp->res.addHeader(
266*66620686SEd Tanous         boost::beast::http::field::location,
267*66620686SEd Tanous         boost::urls::format("/redfish/v1/AggregationSources/{}", prefix)
268*66620686SEd Tanous             .buffer());
269*66620686SEd Tanous 
270*66620686SEd Tanous     messages::created(asyncResp->res);
271*66620686SEd Tanous }
272*66620686SEd Tanous 
273*66620686SEd Tanous inline void handleAggregationSourceDelete(
274*66620686SEd Tanous     App& app, const crow::Request& req,
275*66620686SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
276*66620686SEd Tanous     const std::string& aggregationSourceId)
277*66620686SEd Tanous {
278*66620686SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
279*66620686SEd Tanous     {
280*66620686SEd Tanous         return;
281*66620686SEd Tanous     }
282*66620686SEd Tanous     asyncResp->res.addHeader(
283*66620686SEd Tanous         boost::beast::http::field::link,
284*66620686SEd Tanous         "</redfish/v1/JsonSchemas/AggregationService/AggregationSource.json>; rel=describedby");
285*66620686SEd Tanous 
286*66620686SEd Tanous     size_t deleted =
287*66620686SEd Tanous         RedfishAggregator::getInstance().currentAggregationSources.erase(
288*66620686SEd Tanous             aggregationSourceId);
289*66620686SEd Tanous     if (deleted == 0)
290*66620686SEd Tanous     {
291*66620686SEd Tanous         messages::resourceNotFound(asyncResp->res, "AggregationSource",
292*66620686SEd Tanous                                    aggregationSourceId);
293*66620686SEd Tanous         return;
294*66620686SEd Tanous     }
295*66620686SEd Tanous 
296*66620686SEd Tanous     messages::success(asyncResp->res);
297*66620686SEd Tanous }
298*66620686SEd Tanous 
2998b2521a5SCarson Labrado inline void requestRoutesAggregationSource(App& app)
3008b2521a5SCarson Labrado {
3018b2521a5SCarson Labrado     BMCWEB_ROUTE(app,
3028b2521a5SCarson Labrado                  "/redfish/v1/AggregationService/AggregationSources/<str>/")
3038b2521a5SCarson Labrado         .privileges(redfish::privileges::getAggregationSource)
30441024f39SEd Tanous         .methods(boost::beast::http::verb::head)(
30541024f39SEd Tanous             std::bind_front(handleAggregationSourceHead, std::ref(app)));
30641024f39SEd Tanous 
30741024f39SEd Tanous     BMCWEB_ROUTE(app,
30841024f39SEd Tanous                  "/redfish/v1/AggregationService/AggregationSources/<str>/")
30941024f39SEd Tanous         .privileges(redfish::privileges::getAggregationSource)
3105315c1b1SCarson Labrado         .methods(boost::beast::http::verb::get)(
3118b2521a5SCarson Labrado             std::bind_front(handleAggregationSourceGet, std::ref(app)));
312*66620686SEd Tanous 
313*66620686SEd Tanous     BMCWEB_ROUTE(app,
314*66620686SEd Tanous                  "/redfish/v1/AggregationService/AggregationSources/<str>/")
315*66620686SEd Tanous         .privileges(redfish::privileges::deleteAggregationSource)
316*66620686SEd Tanous         .methods(boost::beast::http::verb::delete_)(
317*66620686SEd Tanous             std::bind_front(handleAggregationSourceDelete, std::ref(app)));
318*66620686SEd Tanous 
319*66620686SEd Tanous     BMCWEB_ROUTE(app,
320*66620686SEd Tanous                  "/redfish/v1/AggregationService/AggregationSources/<str>/")
321*66620686SEd Tanous         .privileges(redfish::privileges::headAggregationSource)
322*66620686SEd Tanous         .methods(boost::beast::http::verb::head)(
323*66620686SEd Tanous             std::bind_front(handleAggregationSourceHead, std::ref(app)));
324*66620686SEd Tanous 
325*66620686SEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/AggregationService/AggregationSources/")
326*66620686SEd Tanous         .privileges(redfish::privileges::postAggregationSourceCollection)
327*66620686SEd Tanous         .methods(boost::beast::http::verb::post)(std::bind_front(
328*66620686SEd Tanous             handleAggregationSourceCollectionPost, std::ref(app)));
3295315c1b1SCarson Labrado }
3305315c1b1SCarson Labrado 
3316c068982SEd Tanous } // namespace redfish
332