xref: /openbmc/bmcweb/redfish-core/lib/aggregation_service.hpp (revision 71526116e1eb38a983df0ca12de63348d37e0e9b)
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 "error_messages.hpp"
8 #include "http_request.hpp"
9 #include "http_response.hpp"
10 #include "logging.hpp"
11 #include "ossl_random.hpp"
12 #include "query.hpp"
13 #include "redfish_aggregator.hpp"
14 #include "registries/privilege_registry.hpp"
15 #include "utility.hpp"
16 #include "utils/json_utils.hpp"
17 
18 #include <boost/beast/http/field.hpp>
19 #include <boost/beast/http/verb.hpp>
20 #include <boost/system/result.hpp>
21 #include <boost/url/format.hpp>
22 #include <boost/url/parse.hpp>
23 #include <boost/url/url.hpp>
24 #include <nlohmann/json.hpp>
25 
26 #include <cstddef>
27 #include <functional>
28 #include <memory>
29 #include <unordered_map>
30 #include <utility>
31 
32 namespace redfish
33 {
34 
handleAggregationServiceHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)35 inline void handleAggregationServiceHead(
36     App& app, const crow::Request& req,
37     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
38 {
39     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
40     {
41         return;
42     }
43     asyncResp->res.addHeader(
44         boost::beast::http::field::link,
45         "</redfish/v1/JsonSchemas/AggregationService/AggregationService.json>; rel=describedby");
46 }
47 
handleAggregationServiceGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)48 inline void handleAggregationServiceGet(
49     App& app, const crow::Request& req,
50     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
51 {
52     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
53     {
54         return;
55     }
56     asyncResp->res.addHeader(
57         boost::beast::http::field::link,
58         "</redfish/v1/JsonSchemas/AggregationService/AggregationService.json>; rel=describedby");
59     nlohmann::json& json = asyncResp->res.jsonValue;
60     json["@odata.id"] = "/redfish/v1/AggregationService";
61     json["@odata.type"] = "#AggregationService.v1_0_1.AggregationService";
62     json["Id"] = "AggregationService";
63     json["Name"] = "Aggregation Service";
64     json["Description"] = "Aggregation Service";
65     json["ServiceEnabled"] = true;
66     json["AggregationSources"]["@odata.id"] =
67         "/redfish/v1/AggregationService/AggregationSources";
68 }
69 
requestRoutesAggregationService(App & app)70 inline void requestRoutesAggregationService(App& app)
71 {
72     BMCWEB_ROUTE(app, "/redfish/v1/AggregationService/")
73         .privileges(redfish::privileges::headAggregationService)
74         .methods(boost::beast::http::verb::head)(
75             std::bind_front(handleAggregationServiceHead, std::ref(app)));
76     BMCWEB_ROUTE(app, "/redfish/v1/AggregationService/")
77         .privileges(redfish::privileges::getAggregationService)
78         .methods(boost::beast::http::verb::get)(
79             std::bind_front(handleAggregationServiceGet, std::ref(app)));
80 }
81 
populateAggregationSourceCollection(const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,const std::unordered_map<std::string,boost::urls::url> & satelliteInfo)82 inline void populateAggregationSourceCollection(
83     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
84     const boost::system::error_code& ec,
85     const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
86 {
87     // Something went wrong while querying dbus
88     if (ec)
89     {
90         messages::internalError(asyncResp->res);
91         return;
92     }
93     nlohmann::json::array_t members;
94     for (const auto& sat : satelliteInfo)
95     {
96         nlohmann::json::object_t member;
97         member["@odata.id"] = boost::urls::format(
98             "/redfish/v1/AggregationService/AggregationSources/{}", sat.first);
99         members.emplace_back(std::move(member));
100     }
101     asyncResp->res.jsonValue["Members@odata.count"] = members.size();
102     asyncResp->res.jsonValue["Members"] = std::move(members);
103 }
104 
handleAggregationSourceCollectionGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)105 inline void handleAggregationSourceCollectionGet(
106     App& app, const crow::Request& req,
107     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
108 {
109     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
110     {
111         return;
112     }
113     asyncResp->res.addHeader(
114         boost::beast::http::field::link,
115         "</redfish/v1/JsonSchemas/AggregationSourceCollection/AggregationSourceCollection.json>; rel=describedby");
116     nlohmann::json& json = asyncResp->res.jsonValue;
117     json["@odata.id"] = "/redfish/v1/AggregationService/AggregationSources";
118     json["@odata.type"] =
119         "#AggregationSourceCollection.AggregationSourceCollection";
120     json["Name"] = "Aggregation Source Collection";
121 
122     // Query D-Bus for satellite configs and add them to the Members array
123     RedfishAggregator::getInstance().getSatelliteConfigs(
124         std::bind_front(populateAggregationSourceCollection, asyncResp));
125 }
126 
handleAggregationSourceCollectionHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)127 inline void handleAggregationSourceCollectionHead(
128     App& app, const crow::Request& req,
129     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
130 {
131     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
132     {
133         return;
134     }
135     asyncResp->res.addHeader(
136         boost::beast::http::field::link,
137         "</redfish/v1/JsonSchemas/AggregationService/AggregationSourceCollection.json>; rel=describedby");
138 }
139 
requestRoutesAggregationSourceCollection(App & app)140 inline void requestRoutesAggregationSourceCollection(App& app)
141 {
142     BMCWEB_ROUTE(app, "/redfish/v1/AggregationService/AggregationSources/")
143         .privileges(redfish::privileges::getAggregationSourceCollection)
144         .methods(boost::beast::http::verb::get)(std::bind_front(
145             handleAggregationSourceCollectionGet, std::ref(app)));
146 
147     BMCWEB_ROUTE(app, "/redfish/v1/AggregationService/AggregationSources/")
148         .privileges(redfish::privileges::getAggregationSourceCollection)
149         .methods(boost::beast::http::verb::head)(std::bind_front(
150             handleAggregationSourceCollectionHead, std::ref(app)));
151 }
152 
populateAggregationSource(const std::string & aggregationSourceId,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const boost::system::error_code & ec,const std::unordered_map<std::string,boost::urls::url> & satelliteInfo)153 inline void populateAggregationSource(
154     const std::string& aggregationSourceId,
155     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
156     const boost::system::error_code& ec,
157     const std::unordered_map<std::string, boost::urls::url>& satelliteInfo)
158 {
159     asyncResp->res.addHeader(
160         boost::beast::http::field::link,
161         "</redfish/v1/JsonSchemas/AggregationSource/AggregationSource.json>; rel=describedby");
162 
163     // Something went wrong while querying dbus
164     if (ec)
165     {
166         messages::internalError(asyncResp->res);
167         return;
168     }
169 
170     const auto& sat = satelliteInfo.find(aggregationSourceId);
171     if (sat == satelliteInfo.end())
172     {
173         messages::resourceNotFound(asyncResp->res, "AggregationSource",
174                                    aggregationSourceId);
175         return;
176     }
177 
178     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
179         "/redfish/v1/AggregationService/AggregationSources/{}",
180         aggregationSourceId);
181     asyncResp->res.jsonValue["@odata.type"] =
182         "#AggregationSource.v1_3_1.AggregationSource";
183     asyncResp->res.jsonValue["Id"] = aggregationSourceId;
184 
185     // TODO: We may want to change this whenever we support aggregating multiple
186     // satellite BMCs.  Otherwise all AggregationSource resources will have the
187     // same "Name".
188     // TODO: We should use the "Name" from the satellite config whenever we add
189     // support for including it in the data returned in satelliteInfo.
190     asyncResp->res.jsonValue["Name"] = "Aggregation source";
191     std::string hostName(sat->second.encoded_origin());
192     asyncResp->res.jsonValue["HostName"] = std::move(hostName);
193 
194     // The Redfish spec requires Password to be null in responses
195     asyncResp->res.jsonValue["Password"] = nullptr;
196 }
197 
handleAggregationSourceGet(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & aggregationSourceId)198 inline void handleAggregationSourceGet(
199     App& app, const crow::Request& req,
200     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
201     const std::string& aggregationSourceId)
202 {
203     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
204     {
205         return;
206     }
207 
208     // Query D-Bus for satellite config corresponding to the specified
209     // AggregationSource
210     RedfishAggregator::getInstance().getSatelliteConfigs(std::bind_front(
211         populateAggregationSource, aggregationSourceId, asyncResp));
212 }
213 
handleAggregationSourceHead(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & aggregationSourceId)214 inline void handleAggregationSourceHead(
215     App& app, const crow::Request& req,
216     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
217     const std::string& aggregationSourceId)
218 {
219     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
220     {
221         return;
222     }
223     asyncResp->res.addHeader(
224         boost::beast::http::field::link,
225         "</redfish/v1/JsonSchemas/AggregationService/AggregationSource.json>; rel=describedby");
226 
227     // Needed to prevent unused variable error
228     BMCWEB_LOG_DEBUG("Added link header to response from {}",
229                      aggregationSourceId);
230 }
231 
handleAggregationSourceCollectionPost(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp)232 inline void handleAggregationSourceCollectionPost(
233     App& app, const crow::Request& req,
234     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
235 {
236     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
237     {
238         return;
239     }
240     std::string hostname;
241     if (!json_util::readJsonPatch(req, asyncResp->res, "HostName", hostname))
242     {
243         return;
244     }
245     boost::system::result<boost::urls::url> url =
246         boost::urls::parse_absolute_uri(hostname);
247     if (!url)
248     {
249         messages::propertyValueIncorrect(asyncResp->res, hostname, "HostName");
250         return;
251     }
252     url->normalize();
253     if (url->scheme() != "http" && url->scheme() != "https")
254     {
255         messages::propertyValueIncorrect(asyncResp->res, hostname, "HostName");
256         return;
257     }
258     crow::utility::setPortDefaults(*url);
259     std::string prefix = bmcweb::getRandomIdOfLength(8);
260     RedfishAggregator::getInstance().currentAggregationSources.emplace(
261         prefix, *url);
262     BMCWEB_LOG_DEBUG("Emplaced {} with url {}", prefix, url->buffer());
263     asyncResp->res.addHeader(
264         boost::beast::http::field::location,
265         boost::urls::format("/redfish/v1/AggregationSources/{}", prefix)
266             .buffer());
267     messages::created(asyncResp->res);
268 }
269 
handleAggregationSourceDelete(App & app,const crow::Request & req,const std::shared_ptr<bmcweb::AsyncResp> & asyncResp,const std::string & aggregationSourceId)270 inline void handleAggregationSourceDelete(
271     App& app, const crow::Request& req,
272     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
273     const std::string& aggregationSourceId)
274 {
275     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
276     {
277         return;
278     }
279     asyncResp->res.addHeader(
280         boost::beast::http::field::link,
281         "</redfish/v1/JsonSchemas/AggregationService/AggregationSource.json>; rel=describedby");
282 
283     size_t deleted =
284         RedfishAggregator::getInstance().currentAggregationSources.erase(
285             aggregationSourceId);
286     if (deleted == 0)
287     {
288         messages::resourceNotFound(asyncResp->res, "AggregationSource",
289                                    aggregationSourceId);
290         return;
291     }
292 
293     messages::success(asyncResp->res);
294 }
295 
requestRoutesAggregationSource(App & app)296 inline void requestRoutesAggregationSource(App& app)
297 {
298     BMCWEB_ROUTE(app,
299                  "/redfish/v1/AggregationService/AggregationSources/<str>/")
300         .privileges(redfish::privileges::getAggregationSource)
301         .methods(boost::beast::http::verb::get)(
302             std::bind_front(handleAggregationSourceGet, std::ref(app)));
303 
304     BMCWEB_ROUTE(app,
305                  "/redfish/v1/AggregationService/AggregationSources/<str>/")
306         .privileges(redfish::privileges::deleteAggregationSource)
307         .methods(boost::beast::http::verb::delete_)(
308             std::bind_front(handleAggregationSourceDelete, std::ref(app)));
309 
310     BMCWEB_ROUTE(app,
311                  "/redfish/v1/AggregationService/AggregationSources/<str>/")
312         .privileges(redfish::privileges::headAggregationSource)
313         .methods(boost::beast::http::verb::head)(
314             std::bind_front(handleAggregationSourceHead, std::ref(app)));
315 
316     BMCWEB_ROUTE(app, "/redfish/v1/AggregationService/AggregationSources/")
317         .privileges(redfish::privileges::postAggregationSourceCollection)
318         .methods(boost::beast::http::verb::post)(std::bind_front(
319             handleAggregationSourceCollectionPost, std::ref(app)));
320 }
321 
322 } // namespace redfish
323