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