xref: /openbmc/bmcweb/features/redfish/lib/manager_logservices_journal.hpp (revision 40e9b92ec19acffb46f83a6e55b18974da5d708e)
1*40e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
2*40e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3b0983db2SEd Tanous #pragma once
4b0983db2SEd Tanous 
5b0983db2SEd Tanous #include "app.hpp"
6b0983db2SEd Tanous #include "error_messages.hpp"
7b0983db2SEd Tanous #include "generated/enums/log_entry.hpp"
8b0983db2SEd Tanous #include "query.hpp"
9b0983db2SEd Tanous #include "registries/base_message_registry.hpp"
10b0983db2SEd Tanous #include "registries/privilege_registry.hpp"
1160e995cdSEd Tanous #include "utils/journal_utils.hpp"
12b0983db2SEd Tanous #include "utils/time_utils.hpp"
13b0983db2SEd Tanous 
14b0983db2SEd Tanous #include <systemd/sd-journal.h>
15b0983db2SEd Tanous 
16b0983db2SEd Tanous #include <boost/beast/http/verb.hpp>
17b0983db2SEd Tanous 
18b0983db2SEd Tanous #include <array>
19b0983db2SEd Tanous #include <memory>
20b0983db2SEd Tanous #include <string>
21b0983db2SEd Tanous #include <string_view>
22b0983db2SEd Tanous 
23b0983db2SEd Tanous namespace redfish
24b0983db2SEd Tanous {
25b0983db2SEd Tanous 
2684177a2fSEd Tanous inline void handleManagersLogServiceJournalGet(
2784177a2fSEd Tanous     App& app, const crow::Request& req,
28b0983db2SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2984177a2fSEd Tanous     const std::string& managerId)
3084177a2fSEd Tanous {
31b0983db2SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
32b0983db2SEd Tanous     {
33b0983db2SEd Tanous         return;
34b0983db2SEd Tanous     }
35b0983db2SEd Tanous 
36b0983db2SEd Tanous     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
37b0983db2SEd Tanous     {
38b0983db2SEd Tanous         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
39b0983db2SEd Tanous         return;
40b0983db2SEd Tanous     }
41b0983db2SEd Tanous 
4284177a2fSEd Tanous     asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
43b0983db2SEd Tanous     asyncResp->res.jsonValue["@odata.id"] =
44b0983db2SEd Tanous         boost::urls::format("/redfish/v1/Managers/{}/LogServices/Journal",
45b0983db2SEd Tanous                             BMCWEB_REDFISH_MANAGER_URI_NAME);
46b0983db2SEd Tanous     asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
47b0983db2SEd Tanous     asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
48b0983db2SEd Tanous     asyncResp->res.jsonValue["Id"] = "Journal";
49b0983db2SEd Tanous     asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
50b0983db2SEd Tanous 
51b0983db2SEd Tanous     std::pair<std::string, std::string> redfishDateTimeOffset =
52b0983db2SEd Tanous         redfish::time_utils::getDateTimeOffsetNow();
53b0983db2SEd Tanous     asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
54b0983db2SEd Tanous     asyncResp->res.jsonValue["DateTimeLocalOffset"] =
55b0983db2SEd Tanous         redfishDateTimeOffset.second;
56b0983db2SEd Tanous 
57b0983db2SEd Tanous     asyncResp->res.jsonValue["Entries"]["@odata.id"] = boost::urls::format(
58b0983db2SEd Tanous         "/redfish/v1/Managers/{}/LogServices/Journal/Entries",
59b0983db2SEd Tanous         BMCWEB_REDFISH_MANAGER_URI_NAME);
60b0983db2SEd Tanous }
61b0983db2SEd Tanous 
62055713e4SEd Tanous struct JournalReadState
63055713e4SEd Tanous {
64055713e4SEd Tanous     std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal;
65055713e4SEd Tanous };
66055713e4SEd Tanous 
67bd79bce8SPatrick Williams inline void readJournalEntries(
68bd79bce8SPatrick Williams     uint64_t topEntryCount, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
69055713e4SEd Tanous     JournalReadState&& readState)
70055713e4SEd Tanous {
71055713e4SEd Tanous     nlohmann::json& logEntry = asyncResp->res.jsonValue["Members"];
72055713e4SEd Tanous     nlohmann::json::array_t* logEntryArray =
73055713e4SEd Tanous         logEntry.get_ptr<nlohmann::json::array_t*>();
74055713e4SEd Tanous     if (logEntryArray == nullptr)
75055713e4SEd Tanous     {
76055713e4SEd Tanous         messages::internalError(asyncResp->res);
77055713e4SEd Tanous         return;
78055713e4SEd Tanous     }
79055713e4SEd Tanous 
80055713e4SEd Tanous     // The Journal APIs unfortunately do blocking calls to the filesystem, and
81055713e4SEd Tanous     // can be somewhat expensive.  Short of creating our own io_uring based
82055713e4SEd Tanous     // implementation of sd-journal, which would be difficult, the best thing we
83055713e4SEd Tanous     // can do is to only parse a certain number of entries at a time.  The
84055713e4SEd Tanous     // current chunk size is selected arbitrarily to ensure that we're not
85055713e4SEd Tanous     // trying to process thousands of entries at the same time.
86055713e4SEd Tanous     // The implementation will process the number of entries, then return
87055713e4SEd Tanous     // control to the io_context to let other operations continue.
88055713e4SEd Tanous     size_t segmentCountRemaining = 10;
89055713e4SEd Tanous 
90055713e4SEd Tanous     // Reset the unique ID on the first entry
91055713e4SEd Tanous     for (uint64_t entryCount = logEntryArray->size();
92055713e4SEd Tanous          entryCount < topEntryCount; entryCount++)
93055713e4SEd Tanous     {
94055713e4SEd Tanous         if (segmentCountRemaining == 0)
95055713e4SEd Tanous         {
96055713e4SEd Tanous             boost::asio::post(crow::connections::systemBus->get_io_context(),
97055713e4SEd Tanous                               [asyncResp, topEntryCount,
98055713e4SEd Tanous                                readState = std::move(readState)]() mutable {
99055713e4SEd Tanous                                   readJournalEntries(topEntryCount, asyncResp,
100055713e4SEd Tanous                                                      std::move(readState));
101055713e4SEd Tanous                               });
102055713e4SEd Tanous             return;
103055713e4SEd Tanous         }
104055713e4SEd Tanous 
105055713e4SEd Tanous         nlohmann::json::object_t bmcJournalLogEntry;
1068274eb11SEd Tanous         if (!fillBMCJournalLogEntryJson(readState.journal.get(),
107055713e4SEd Tanous                                         bmcJournalLogEntry))
108055713e4SEd Tanous         {
109055713e4SEd Tanous             messages::internalError(asyncResp->res);
110055713e4SEd Tanous             return;
111055713e4SEd Tanous         }
112055713e4SEd Tanous         logEntryArray->emplace_back(std::move(bmcJournalLogEntry));
113055713e4SEd Tanous 
1148274eb11SEd Tanous         int ret = sd_journal_next(readState.journal.get());
115055713e4SEd Tanous         if (ret < 0)
116055713e4SEd Tanous         {
117055713e4SEd Tanous             messages::internalError(asyncResp->res);
118055713e4SEd Tanous             return;
119055713e4SEd Tanous         }
120055713e4SEd Tanous         if (ret == 0)
121055713e4SEd Tanous         {
122055713e4SEd Tanous             break;
123055713e4SEd Tanous         }
124055713e4SEd Tanous         segmentCountRemaining--;
125055713e4SEd Tanous     }
126055713e4SEd Tanous }
127055713e4SEd Tanous 
12884177a2fSEd Tanous inline void handleManagersJournalLogEntryCollectionGet(
12984177a2fSEd Tanous     App& app, const crow::Request& req,
130b0983db2SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
13184177a2fSEd Tanous     const std::string& managerId)
13284177a2fSEd Tanous {
133b0983db2SEd Tanous     query_param::QueryCapabilities capabilities = {
134b0983db2SEd Tanous         .canDelegateTop = true,
135b0983db2SEd Tanous         .canDelegateSkip = true,
136b0983db2SEd Tanous     };
137b0983db2SEd Tanous     query_param::Query delegatedQuery;
13884177a2fSEd Tanous     if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
13984177a2fSEd Tanous                                                   delegatedQuery, capabilities))
140b0983db2SEd Tanous     {
141b0983db2SEd Tanous         return;
142b0983db2SEd Tanous     }
143b0983db2SEd Tanous 
144b0983db2SEd Tanous     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
145b0983db2SEd Tanous     {
146b0983db2SEd Tanous         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
147b0983db2SEd Tanous         return;
148b0983db2SEd Tanous     }
149b0983db2SEd Tanous 
150b0983db2SEd Tanous     size_t skip = delegatedQuery.skip.value_or(0);
151b0983db2SEd Tanous     size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
152b0983db2SEd Tanous 
153b0983db2SEd Tanous     // Collections don't include the static data added by SubRoute
154b0983db2SEd Tanous     // because it has a duplicate entry for members
155b0983db2SEd Tanous     asyncResp->res.jsonValue["@odata.type"] =
156b0983db2SEd Tanous         "#LogEntryCollection.LogEntryCollection";
157b0983db2SEd Tanous     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
158b0983db2SEd Tanous         "/redfish/v1/Managers/{}/LogServices/Journal/Entries",
159b0983db2SEd Tanous         BMCWEB_REDFISH_MANAGER_URI_NAME);
160b0983db2SEd Tanous     asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
161b0983db2SEd Tanous     asyncResp->res.jsonValue["Description"] =
162b0983db2SEd Tanous         "Collection of BMC Journal Entries";
163055713e4SEd Tanous     asyncResp->res.jsonValue["Members"] = nlohmann::json::array_t();
164b0983db2SEd Tanous 
165b0983db2SEd Tanous     // Go through the journal and use the timestamp to create a
166b0983db2SEd Tanous     // unique ID for each entry
167b0983db2SEd Tanous     sd_journal* journalTmp = nullptr;
168b0983db2SEd Tanous     int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
169b0983db2SEd Tanous     if (ret < 0)
170b0983db2SEd Tanous     {
1717da633f0SEd Tanous         BMCWEB_LOG_ERROR("failed to open journal: {}", ret);
172b0983db2SEd Tanous         messages::internalError(asyncResp->res);
173b0983db2SEd Tanous         return;
174b0983db2SEd Tanous     }
175055713e4SEd Tanous 
176b0983db2SEd Tanous     std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
177b0983db2SEd Tanous         journalTmp, sd_journal_close);
178b0983db2SEd Tanous     journalTmp = nullptr;
179b0983db2SEd Tanous 
180055713e4SEd Tanous     // Seek to the end
181055713e4SEd Tanous     if (sd_journal_seek_tail(journal.get()) < 0)
182b0983db2SEd Tanous     {
183b0983db2SEd Tanous         messages::internalError(asyncResp->res);
184b0983db2SEd Tanous         return;
185b0983db2SEd Tanous     }
186055713e4SEd Tanous 
187055713e4SEd Tanous     // Get the last entry
188055713e4SEd Tanous     if (sd_journal_previous(journal.get()) < 0)
189055713e4SEd Tanous     {
190055713e4SEd Tanous         messages::internalError(asyncResp->res);
191055713e4SEd Tanous         return;
192b0983db2SEd Tanous     }
193055713e4SEd Tanous 
194055713e4SEd Tanous     // Get the last sequence number
195055713e4SEd Tanous     uint64_t endSeqNum = 0;
196058f54edSPatrick Williams #if LIBSYSTEMD_VERSION >= 254
197055713e4SEd Tanous     {
198055713e4SEd Tanous         if (sd_journal_get_seqnum(journal.get(), &endSeqNum, nullptr) < 0)
199055713e4SEd Tanous         {
200055713e4SEd Tanous             messages::internalError(asyncResp->res);
201055713e4SEd Tanous             return;
202055713e4SEd Tanous         }
203055713e4SEd Tanous     }
204055713e4SEd Tanous #endif
205055713e4SEd Tanous 
206055713e4SEd Tanous     // Seek to the beginning
207055713e4SEd Tanous     if (sd_journal_seek_head(journal.get()) < 0)
208055713e4SEd Tanous     {
209055713e4SEd Tanous         messages::internalError(asyncResp->res);
210055713e4SEd Tanous         return;
211055713e4SEd Tanous     }
212055713e4SEd Tanous 
213055713e4SEd Tanous     // Get the first entry
214055713e4SEd Tanous     if (sd_journal_next(journal.get()) < 0)
215055713e4SEd Tanous     {
216055713e4SEd Tanous         messages::internalError(asyncResp->res);
217055713e4SEd Tanous         return;
218055713e4SEd Tanous     }
219055713e4SEd Tanous 
220055713e4SEd Tanous     // Get the first sequence number
221055713e4SEd Tanous     uint64_t startSeqNum = 0;
222058f54edSPatrick Williams #if LIBSYSTEMD_VERSION >= 254
223055713e4SEd Tanous     {
224055713e4SEd Tanous         if (sd_journal_get_seqnum(journal.get(), &startSeqNum, nullptr) < 0)
225055713e4SEd Tanous         {
226055713e4SEd Tanous             messages::internalError(asyncResp->res);
227055713e4SEd Tanous             return;
228055713e4SEd Tanous         }
229055713e4SEd Tanous     }
230055713e4SEd Tanous #endif
231055713e4SEd Tanous 
232055713e4SEd Tanous     BMCWEB_LOG_DEBUG("journal Sequence IDs start:{} end:{}", startSeqNum,
233055713e4SEd Tanous                      endSeqNum);
234055713e4SEd Tanous 
235055713e4SEd Tanous     // Add 1 to account for the last entry
236055713e4SEd Tanous     uint64_t totalEntries = endSeqNum - startSeqNum + 1;
237055713e4SEd Tanous     asyncResp->res.jsonValue["Members@odata.count"] = totalEntries;
238055713e4SEd Tanous     if (skip + top < totalEntries)
239b0983db2SEd Tanous     {
24084177a2fSEd Tanous         asyncResp->res.jsonValue["Members@odata.nextLink"] =
24184177a2fSEd Tanous             boost::urls::format(
242b0983db2SEd Tanous                 "/redfish/v1/Managers/{}/LogServices/Journal/Entries?$skip={}",
243b0983db2SEd Tanous                 BMCWEB_REDFISH_MANAGER_URI_NAME, std::to_string(skip + top));
244b0983db2SEd Tanous     }
245055713e4SEd Tanous     uint64_t index = 0;
246055713e4SEd Tanous     if (skip > 0)
247055713e4SEd Tanous     {
248055713e4SEd Tanous         if (sd_journal_next_skip(journal.get(), skip) < 0)
249055713e4SEd Tanous         {
250055713e4SEd Tanous             messages::internalError(asyncResp->res);
251055713e4SEd Tanous             return;
252055713e4SEd Tanous         }
253055713e4SEd Tanous     }
254055713e4SEd Tanous     BMCWEB_LOG_DEBUG("Index was {}", index);
2558274eb11SEd Tanous     readJournalEntries(top, asyncResp, {std::move(journal)});
256b0983db2SEd Tanous }
257b0983db2SEd Tanous 
25884177a2fSEd Tanous inline void handleManagersJournalEntriesLogEntryGet(
25984177a2fSEd Tanous     App& app, const crow::Request& req,
260b0983db2SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
26184177a2fSEd Tanous     const std::string& managerId, const std::string& entryID)
26284177a2fSEd Tanous {
263b0983db2SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
264b0983db2SEd Tanous     {
265b0983db2SEd Tanous         return;
266b0983db2SEd Tanous     }
267b0983db2SEd Tanous 
268b0983db2SEd Tanous     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
269b0983db2SEd Tanous     {
270b0983db2SEd Tanous         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
271b0983db2SEd Tanous         return;
272b0983db2SEd Tanous     }
273b0983db2SEd Tanous 
274b0983db2SEd Tanous     sd_journal* journalTmp = nullptr;
275b0983db2SEd Tanous     int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
276b0983db2SEd Tanous     if (ret < 0)
277b0983db2SEd Tanous     {
2787da633f0SEd Tanous         BMCWEB_LOG_ERROR("failed to open journal: {}", ret);
279b0983db2SEd Tanous         messages::internalError(asyncResp->res);
280b0983db2SEd Tanous         return;
281b0983db2SEd Tanous     }
282b0983db2SEd Tanous     std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
283b0983db2SEd Tanous         journalTmp, sd_journal_close);
284b0983db2SEd Tanous     journalTmp = nullptr;
2858274eb11SEd Tanous 
2868274eb11SEd Tanous     std::string cursor;
2878274eb11SEd Tanous     if (!crow::utility::base64Decode(entryID, cursor))
2888274eb11SEd Tanous     {
2898274eb11SEd Tanous         messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
2908274eb11SEd Tanous         return;
2918274eb11SEd Tanous     }
2928274eb11SEd Tanous 
2938274eb11SEd Tanous     // Go to the cursor in the log
2948274eb11SEd Tanous     ret = sd_journal_seek_cursor(journal.get(), cursor.c_str());
295b0983db2SEd Tanous     if (ret < 0)
296b0983db2SEd Tanous     {
2978274eb11SEd Tanous         messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
2988274eb11SEd Tanous         return;
2998274eb11SEd Tanous     }
3008274eb11SEd Tanous 
3018274eb11SEd Tanous     if (sd_journal_next(journal.get()) < 0)
3028274eb11SEd Tanous     {
303b0983db2SEd Tanous         messages::internalError(asyncResp->res);
304b0983db2SEd Tanous         return;
305b0983db2SEd Tanous     }
306055713e4SEd Tanous 
3078274eb11SEd Tanous     ret = sd_journal_test_cursor(journal.get(), cursor.c_str());
3088274eb11SEd Tanous     if (ret == 0)
3098274eb11SEd Tanous     {
3108274eb11SEd Tanous         messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
3118274eb11SEd Tanous         return;
3128274eb11SEd Tanous     }
3138274eb11SEd Tanous     if (ret < 0)
314b0983db2SEd Tanous     {
315b0983db2SEd Tanous         messages::internalError(asyncResp->res);
316b0983db2SEd Tanous         return;
317b0983db2SEd Tanous     }
318055713e4SEd Tanous 
319b0983db2SEd Tanous     nlohmann::json::object_t bmcJournalLogEntry;
3208274eb11SEd Tanous     if (!fillBMCJournalLogEntryJson(journal.get(), bmcJournalLogEntry))
321b0983db2SEd Tanous     {
322b0983db2SEd Tanous         messages::internalError(asyncResp->res);
323b0983db2SEd Tanous         return;
324b0983db2SEd Tanous     }
325b0983db2SEd Tanous     asyncResp->res.jsonValue.update(bmcJournalLogEntry);
326055713e4SEd Tanous }
32784177a2fSEd Tanous 
32884177a2fSEd Tanous inline void requestRoutesBMCJournalLogService(App& app)
32984177a2fSEd Tanous {
33084177a2fSEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/")
33184177a2fSEd Tanous         .privileges(redfish::privileges::getLogService)
33284177a2fSEd Tanous         .methods(boost::beast::http::verb::get)(
33384177a2fSEd Tanous             std::bind_front(handleManagersLogServiceJournalGet, std::ref(app)));
33484177a2fSEd Tanous 
33584177a2fSEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/")
33684177a2fSEd Tanous         .privileges(redfish::privileges::getLogEntryCollection)
33784177a2fSEd Tanous         .methods(boost::beast::http::verb::get)(std::bind_front(
33884177a2fSEd Tanous             handleManagersJournalLogEntryCollectionGet, std::ref(app)));
33984177a2fSEd Tanous 
34084177a2fSEd Tanous     BMCWEB_ROUTE(
34184177a2fSEd Tanous         app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/<str>/")
34284177a2fSEd Tanous         .privileges(redfish::privileges::getLogEntry)
34384177a2fSEd Tanous         .methods(boost::beast::http::verb::get)(std::bind_front(
34484177a2fSEd Tanous             handleManagersJournalEntriesLogEntryGet, std::ref(app)));
345b0983db2SEd Tanous }
346b0983db2SEd Tanous } // namespace redfish
347