xref: /openbmc/bmcweb/features/redfish/lib/manager_logservices_journal.hpp (revision 08fad5d9dc59323a8916ff97a035221621047d8c)
140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3b0983db2SEd Tanous #pragma once
4b0983db2SEd Tanous 
5d7857201SEd Tanous #include "bmcweb_config.h"
6d7857201SEd Tanous 
7b0983db2SEd Tanous #include "app.hpp"
8d7857201SEd Tanous #include "async_resp.hpp"
9d7857201SEd Tanous #include "dbus_singleton.hpp"
10b0983db2SEd Tanous #include "error_messages.hpp"
11d7857201SEd Tanous #include "http_request.hpp"
12d7857201SEd Tanous #include "logging.hpp"
13b0983db2SEd Tanous #include "query.hpp"
14b0983db2SEd Tanous #include "registries/privilege_registry.hpp"
15d7857201SEd Tanous #include "utility.hpp"
16*08fad5d9SCorey Ethington #include "utils/etag_utils.hpp"
1760e995cdSEd Tanous #include "utils/journal_utils.hpp"
18d7857201SEd Tanous #include "utils/query_param.hpp"
19b0983db2SEd Tanous #include "utils/time_utils.hpp"
20b0983db2SEd Tanous 
21b0983db2SEd Tanous #include <systemd/sd-journal.h>
22b0983db2SEd Tanous 
23d7857201SEd Tanous #include <boost/asio/post.hpp>
24b0983db2SEd Tanous #include <boost/beast/http/verb.hpp>
25d7857201SEd Tanous #include <boost/url/format.hpp>
26b0983db2SEd Tanous 
27d7857201SEd Tanous #include <cstddef>
28d7857201SEd Tanous #include <cstdint>
29d7857201SEd Tanous #include <functional>
30b0983db2SEd Tanous #include <memory>
31b0983db2SEd Tanous #include <string>
32b0983db2SEd Tanous #include <string_view>
33d7857201SEd Tanous #include <utility>
34b0983db2SEd Tanous 
35b0983db2SEd Tanous namespace redfish
36b0983db2SEd Tanous {
37b0983db2SEd Tanous 
3884177a2fSEd Tanous inline void handleManagersLogServiceJournalGet(
3984177a2fSEd Tanous     App& app, const crow::Request& req,
40b0983db2SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
4184177a2fSEd Tanous     const std::string& managerId)
4284177a2fSEd Tanous {
43b0983db2SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
44b0983db2SEd Tanous     {
45b0983db2SEd Tanous         return;
46b0983db2SEd Tanous     }
47b0983db2SEd Tanous 
48b0983db2SEd Tanous     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
49b0983db2SEd Tanous     {
50b0983db2SEd Tanous         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
51b0983db2SEd Tanous         return;
52b0983db2SEd Tanous     }
53b0983db2SEd Tanous 
5484177a2fSEd Tanous     asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
55b0983db2SEd Tanous     asyncResp->res.jsonValue["@odata.id"] =
56b0983db2SEd Tanous         boost::urls::format("/redfish/v1/Managers/{}/LogServices/Journal",
57b0983db2SEd Tanous                             BMCWEB_REDFISH_MANAGER_URI_NAME);
58b0983db2SEd Tanous     asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
59b0983db2SEd Tanous     asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
60b0983db2SEd Tanous     asyncResp->res.jsonValue["Id"] = "Journal";
61b0983db2SEd Tanous     asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
62b0983db2SEd Tanous 
63b0983db2SEd Tanous     std::pair<std::string, std::string> redfishDateTimeOffset =
64b0983db2SEd Tanous         redfish::time_utils::getDateTimeOffsetNow();
65b0983db2SEd Tanous     asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
66b0983db2SEd Tanous     asyncResp->res.jsonValue["DateTimeLocalOffset"] =
67b0983db2SEd Tanous         redfishDateTimeOffset.second;
68b0983db2SEd Tanous 
69b0983db2SEd Tanous     asyncResp->res.jsonValue["Entries"]["@odata.id"] = boost::urls::format(
70b0983db2SEd Tanous         "/redfish/v1/Managers/{}/LogServices/Journal/Entries",
71b0983db2SEd Tanous         BMCWEB_REDFISH_MANAGER_URI_NAME);
72*08fad5d9SCorey Ethington 
73*08fad5d9SCorey Ethington     etag_utils::setEtagOmitDateTimeHandler(asyncResp);
74b0983db2SEd Tanous }
75b0983db2SEd Tanous 
76055713e4SEd Tanous struct JournalReadState
77055713e4SEd Tanous {
78055713e4SEd Tanous     std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal;
79055713e4SEd Tanous };
80055713e4SEd Tanous 
81bd79bce8SPatrick Williams inline void readJournalEntries(
82bd79bce8SPatrick Williams     uint64_t topEntryCount, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
83055713e4SEd Tanous     JournalReadState&& readState)
84055713e4SEd Tanous {
85055713e4SEd Tanous     nlohmann::json& logEntry = asyncResp->res.jsonValue["Members"];
86055713e4SEd Tanous     nlohmann::json::array_t* logEntryArray =
87055713e4SEd Tanous         logEntry.get_ptr<nlohmann::json::array_t*>();
88055713e4SEd Tanous     if (logEntryArray == nullptr)
89055713e4SEd Tanous     {
90055713e4SEd Tanous         messages::internalError(asyncResp->res);
91055713e4SEd Tanous         return;
92055713e4SEd Tanous     }
93055713e4SEd Tanous 
94055713e4SEd Tanous     // The Journal APIs unfortunately do blocking calls to the filesystem, and
95055713e4SEd Tanous     // can be somewhat expensive.  Short of creating our own io_uring based
96055713e4SEd Tanous     // implementation of sd-journal, which would be difficult, the best thing we
97055713e4SEd Tanous     // can do is to only parse a certain number of entries at a time.  The
98055713e4SEd Tanous     // current chunk size is selected arbitrarily to ensure that we're not
99055713e4SEd Tanous     // trying to process thousands of entries at the same time.
100055713e4SEd Tanous     // The implementation will process the number of entries, then return
101055713e4SEd Tanous     // control to the io_context to let other operations continue.
102055713e4SEd Tanous     size_t segmentCountRemaining = 10;
103055713e4SEd Tanous 
104055713e4SEd Tanous     // Reset the unique ID on the first entry
105055713e4SEd Tanous     for (uint64_t entryCount = logEntryArray->size();
106055713e4SEd Tanous          entryCount < topEntryCount; entryCount++)
107055713e4SEd Tanous     {
108055713e4SEd Tanous         if (segmentCountRemaining == 0)
109055713e4SEd Tanous         {
110055713e4SEd Tanous             boost::asio::post(crow::connections::systemBus->get_io_context(),
111055713e4SEd Tanous                               [asyncResp, topEntryCount,
112055713e4SEd Tanous                                readState = std::move(readState)]() mutable {
113055713e4SEd Tanous                                   readJournalEntries(topEntryCount, asyncResp,
114055713e4SEd Tanous                                                      std::move(readState));
115055713e4SEd Tanous                               });
116055713e4SEd Tanous             return;
117055713e4SEd Tanous         }
118055713e4SEd Tanous 
119055713e4SEd Tanous         nlohmann::json::object_t bmcJournalLogEntry;
1208274eb11SEd Tanous         if (!fillBMCJournalLogEntryJson(readState.journal.get(),
121055713e4SEd Tanous                                         bmcJournalLogEntry))
122055713e4SEd Tanous         {
123055713e4SEd Tanous             messages::internalError(asyncResp->res);
124055713e4SEd Tanous             return;
125055713e4SEd Tanous         }
126055713e4SEd Tanous         logEntryArray->emplace_back(std::move(bmcJournalLogEntry));
127055713e4SEd Tanous 
1288274eb11SEd Tanous         int ret = sd_journal_next(readState.journal.get());
129055713e4SEd Tanous         if (ret < 0)
130055713e4SEd Tanous         {
131055713e4SEd Tanous             messages::internalError(asyncResp->res);
132055713e4SEd Tanous             return;
133055713e4SEd Tanous         }
134055713e4SEd Tanous         if (ret == 0)
135055713e4SEd Tanous         {
136055713e4SEd Tanous             break;
137055713e4SEd Tanous         }
138055713e4SEd Tanous         segmentCountRemaining--;
139055713e4SEd Tanous     }
140055713e4SEd Tanous }
141055713e4SEd Tanous 
14284177a2fSEd Tanous inline void handleManagersJournalLogEntryCollectionGet(
14384177a2fSEd Tanous     App& app, const crow::Request& req,
144b0983db2SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
14584177a2fSEd Tanous     const std::string& managerId)
14684177a2fSEd Tanous {
147b0983db2SEd Tanous     query_param::QueryCapabilities capabilities = {
148b0983db2SEd Tanous         .canDelegateTop = true,
149b0983db2SEd Tanous         .canDelegateSkip = true,
150b0983db2SEd Tanous     };
151b0983db2SEd Tanous     query_param::Query delegatedQuery;
15284177a2fSEd Tanous     if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
15384177a2fSEd Tanous                                                   delegatedQuery, capabilities))
154b0983db2SEd Tanous     {
155b0983db2SEd Tanous         return;
156b0983db2SEd Tanous     }
157b0983db2SEd Tanous 
158b0983db2SEd Tanous     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
159b0983db2SEd Tanous     {
160b0983db2SEd Tanous         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
161b0983db2SEd Tanous         return;
162b0983db2SEd Tanous     }
163b0983db2SEd Tanous 
164b0983db2SEd Tanous     size_t skip = delegatedQuery.skip.value_or(0);
165b0983db2SEd Tanous     size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
166b0983db2SEd Tanous 
167b0983db2SEd Tanous     // Collections don't include the static data added by SubRoute
168b0983db2SEd Tanous     // because it has a duplicate entry for members
169b0983db2SEd Tanous     asyncResp->res.jsonValue["@odata.type"] =
170b0983db2SEd Tanous         "#LogEntryCollection.LogEntryCollection";
171b0983db2SEd Tanous     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
172b0983db2SEd Tanous         "/redfish/v1/Managers/{}/LogServices/Journal/Entries",
173b0983db2SEd Tanous         BMCWEB_REDFISH_MANAGER_URI_NAME);
174b0983db2SEd Tanous     asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
175b0983db2SEd Tanous     asyncResp->res.jsonValue["Description"] =
176b0983db2SEd Tanous         "Collection of BMC Journal Entries";
177055713e4SEd Tanous     asyncResp->res.jsonValue["Members"] = nlohmann::json::array_t();
178b0983db2SEd Tanous 
179b0983db2SEd Tanous     // Go through the journal and use the timestamp to create a
180b0983db2SEd Tanous     // unique ID for each entry
181b0983db2SEd Tanous     sd_journal* journalTmp = nullptr;
182b0983db2SEd Tanous     int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
183b0983db2SEd Tanous     if (ret < 0)
184b0983db2SEd Tanous     {
1857da633f0SEd Tanous         BMCWEB_LOG_ERROR("failed to open journal: {}", ret);
186b0983db2SEd Tanous         messages::internalError(asyncResp->res);
187b0983db2SEd Tanous         return;
188b0983db2SEd Tanous     }
189055713e4SEd Tanous 
190b0983db2SEd Tanous     std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
191b0983db2SEd Tanous         journalTmp, sd_journal_close);
192b0983db2SEd Tanous     journalTmp = nullptr;
193b0983db2SEd Tanous 
194055713e4SEd Tanous     // Seek to the end
195055713e4SEd Tanous     if (sd_journal_seek_tail(journal.get()) < 0)
196b0983db2SEd Tanous     {
197b0983db2SEd Tanous         messages::internalError(asyncResp->res);
198b0983db2SEd Tanous         return;
199b0983db2SEd Tanous     }
200055713e4SEd Tanous 
201055713e4SEd Tanous     // Get the last entry
202055713e4SEd Tanous     if (sd_journal_previous(journal.get()) < 0)
203055713e4SEd Tanous     {
204055713e4SEd Tanous         messages::internalError(asyncResp->res);
205055713e4SEd Tanous         return;
206b0983db2SEd Tanous     }
207055713e4SEd Tanous 
208055713e4SEd Tanous     // Get the last sequence number
209055713e4SEd Tanous     uint64_t endSeqNum = 0;
210058f54edSPatrick Williams #if LIBSYSTEMD_VERSION >= 254
211055713e4SEd Tanous     {
212055713e4SEd Tanous         if (sd_journal_get_seqnum(journal.get(), &endSeqNum, nullptr) < 0)
213055713e4SEd Tanous         {
214055713e4SEd Tanous             messages::internalError(asyncResp->res);
215055713e4SEd Tanous             return;
216055713e4SEd Tanous         }
217055713e4SEd Tanous     }
218055713e4SEd Tanous #endif
219055713e4SEd Tanous 
220055713e4SEd Tanous     // Seek to the beginning
221055713e4SEd Tanous     if (sd_journal_seek_head(journal.get()) < 0)
222055713e4SEd Tanous     {
223055713e4SEd Tanous         messages::internalError(asyncResp->res);
224055713e4SEd Tanous         return;
225055713e4SEd Tanous     }
226055713e4SEd Tanous 
227055713e4SEd Tanous     // Get the first entry
228055713e4SEd Tanous     if (sd_journal_next(journal.get()) < 0)
229055713e4SEd Tanous     {
230055713e4SEd Tanous         messages::internalError(asyncResp->res);
231055713e4SEd Tanous         return;
232055713e4SEd Tanous     }
233055713e4SEd Tanous 
234055713e4SEd Tanous     // Get the first sequence number
235055713e4SEd Tanous     uint64_t startSeqNum = 0;
236058f54edSPatrick Williams #if LIBSYSTEMD_VERSION >= 254
237055713e4SEd Tanous     {
238055713e4SEd Tanous         if (sd_journal_get_seqnum(journal.get(), &startSeqNum, nullptr) < 0)
239055713e4SEd Tanous         {
240055713e4SEd Tanous             messages::internalError(asyncResp->res);
241055713e4SEd Tanous             return;
242055713e4SEd Tanous         }
243055713e4SEd Tanous     }
244055713e4SEd Tanous #endif
245055713e4SEd Tanous 
246055713e4SEd Tanous     BMCWEB_LOG_DEBUG("journal Sequence IDs start:{} end:{}", startSeqNum,
247055713e4SEd Tanous                      endSeqNum);
248055713e4SEd Tanous 
249055713e4SEd Tanous     // Add 1 to account for the last entry
250055713e4SEd Tanous     uint64_t totalEntries = endSeqNum - startSeqNum + 1;
251055713e4SEd Tanous     asyncResp->res.jsonValue["Members@odata.count"] = totalEntries;
252055713e4SEd Tanous     if (skip + top < totalEntries)
253b0983db2SEd Tanous     {
25484177a2fSEd Tanous         asyncResp->res.jsonValue["Members@odata.nextLink"] =
25584177a2fSEd Tanous             boost::urls::format(
256b0983db2SEd Tanous                 "/redfish/v1/Managers/{}/LogServices/Journal/Entries?$skip={}",
257b0983db2SEd Tanous                 BMCWEB_REDFISH_MANAGER_URI_NAME, std::to_string(skip + top));
258b0983db2SEd Tanous     }
259055713e4SEd Tanous     uint64_t index = 0;
260055713e4SEd Tanous     if (skip > 0)
261055713e4SEd Tanous     {
262055713e4SEd Tanous         if (sd_journal_next_skip(journal.get(), skip) < 0)
263055713e4SEd Tanous         {
264055713e4SEd Tanous             messages::internalError(asyncResp->res);
265055713e4SEd Tanous             return;
266055713e4SEd Tanous         }
267055713e4SEd Tanous     }
268055713e4SEd Tanous     BMCWEB_LOG_DEBUG("Index was {}", index);
2698274eb11SEd Tanous     readJournalEntries(top, asyncResp, {std::move(journal)});
270b0983db2SEd Tanous }
271b0983db2SEd Tanous 
27284177a2fSEd Tanous inline void handleManagersJournalEntriesLogEntryGet(
27384177a2fSEd Tanous     App& app, const crow::Request& req,
274b0983db2SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
27584177a2fSEd Tanous     const std::string& managerId, const std::string& entryID)
27684177a2fSEd Tanous {
277b0983db2SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
278b0983db2SEd Tanous     {
279b0983db2SEd Tanous         return;
280b0983db2SEd Tanous     }
281b0983db2SEd Tanous 
282b0983db2SEd Tanous     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
283b0983db2SEd Tanous     {
284b0983db2SEd Tanous         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
285b0983db2SEd Tanous         return;
286b0983db2SEd Tanous     }
287b0983db2SEd Tanous 
288b0983db2SEd Tanous     sd_journal* journalTmp = nullptr;
289b0983db2SEd Tanous     int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
290b0983db2SEd Tanous     if (ret < 0)
291b0983db2SEd Tanous     {
2927da633f0SEd Tanous         BMCWEB_LOG_ERROR("failed to open journal: {}", ret);
293b0983db2SEd Tanous         messages::internalError(asyncResp->res);
294b0983db2SEd Tanous         return;
295b0983db2SEd Tanous     }
296b0983db2SEd Tanous     std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
297b0983db2SEd Tanous         journalTmp, sd_journal_close);
298b0983db2SEd Tanous     journalTmp = nullptr;
2998274eb11SEd Tanous 
3008274eb11SEd Tanous     std::string cursor;
3018274eb11SEd Tanous     if (!crow::utility::base64Decode(entryID, cursor))
3028274eb11SEd Tanous     {
3038274eb11SEd Tanous         messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
3048274eb11SEd Tanous         return;
3058274eb11SEd Tanous     }
3068274eb11SEd Tanous 
3078274eb11SEd Tanous     // Go to the cursor in the log
3088274eb11SEd Tanous     ret = sd_journal_seek_cursor(journal.get(), cursor.c_str());
309b0983db2SEd Tanous     if (ret < 0)
310b0983db2SEd Tanous     {
3118274eb11SEd Tanous         messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
3128274eb11SEd Tanous         return;
3138274eb11SEd Tanous     }
3148274eb11SEd Tanous 
3158274eb11SEd Tanous     if (sd_journal_next(journal.get()) < 0)
3168274eb11SEd Tanous     {
317b0983db2SEd Tanous         messages::internalError(asyncResp->res);
318b0983db2SEd Tanous         return;
319b0983db2SEd Tanous     }
320055713e4SEd Tanous 
3218274eb11SEd Tanous     ret = sd_journal_test_cursor(journal.get(), cursor.c_str());
3228274eb11SEd Tanous     if (ret == 0)
3238274eb11SEd Tanous     {
3248274eb11SEd Tanous         messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
3258274eb11SEd Tanous         return;
3268274eb11SEd Tanous     }
3278274eb11SEd Tanous     if (ret < 0)
328b0983db2SEd Tanous     {
329b0983db2SEd Tanous         messages::internalError(asyncResp->res);
330b0983db2SEd Tanous         return;
331b0983db2SEd Tanous     }
332055713e4SEd Tanous 
333b0983db2SEd Tanous     nlohmann::json::object_t bmcJournalLogEntry;
3348274eb11SEd Tanous     if (!fillBMCJournalLogEntryJson(journal.get(), bmcJournalLogEntry))
335b0983db2SEd Tanous     {
336b0983db2SEd Tanous         messages::internalError(asyncResp->res);
337b0983db2SEd Tanous         return;
338b0983db2SEd Tanous     }
339b0983db2SEd Tanous     asyncResp->res.jsonValue.update(bmcJournalLogEntry);
340055713e4SEd Tanous }
34184177a2fSEd Tanous 
34284177a2fSEd Tanous inline void requestRoutesBMCJournalLogService(App& app)
34384177a2fSEd Tanous {
34484177a2fSEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/")
34584177a2fSEd Tanous         .privileges(redfish::privileges::getLogService)
34684177a2fSEd Tanous         .methods(boost::beast::http::verb::get)(
34784177a2fSEd Tanous             std::bind_front(handleManagersLogServiceJournalGet, std::ref(app)));
34884177a2fSEd Tanous 
34984177a2fSEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/")
35084177a2fSEd Tanous         .privileges(redfish::privileges::getLogEntryCollection)
35184177a2fSEd Tanous         .methods(boost::beast::http::verb::get)(std::bind_front(
35284177a2fSEd Tanous             handleManagersJournalLogEntryCollectionGet, std::ref(app)));
35384177a2fSEd Tanous 
35484177a2fSEd Tanous     BMCWEB_ROUTE(
35584177a2fSEd Tanous         app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/<str>/")
35684177a2fSEd Tanous         .privileges(redfish::privileges::getLogEntry)
35784177a2fSEd Tanous         .methods(boost::beast::http::verb::get)(std::bind_front(
35884177a2fSEd Tanous             handleManagersJournalEntriesLogEntryGet, std::ref(app)));
359b0983db2SEd Tanous }
360b0983db2SEd Tanous } // namespace redfish
361