xref: /openbmc/bmcweb/features/redfish/lib/manager_logservices_journal.hpp (revision 60e995cd6ab15aac32e146860c2a99c069ea111f)
1b0983db2SEd Tanous #pragma once
2b0983db2SEd Tanous 
3b0983db2SEd Tanous #include "app.hpp"
4b0983db2SEd Tanous #include "error_messages.hpp"
5b0983db2SEd Tanous #include "generated/enums/log_entry.hpp"
6b0983db2SEd Tanous #include "query.hpp"
7b0983db2SEd Tanous #include "registries/base_message_registry.hpp"
8b0983db2SEd Tanous #include "registries/privilege_registry.hpp"
9*60e995cdSEd Tanous #include "utils/journal_utils.hpp"
10b0983db2SEd Tanous #include "utils/time_utils.hpp"
11b0983db2SEd Tanous 
12b0983db2SEd Tanous #include <systemd/sd-journal.h>
13b0983db2SEd Tanous 
14b0983db2SEd Tanous #include <boost/beast/http/verb.hpp>
15b0983db2SEd Tanous 
16b0983db2SEd Tanous #include <array>
17b0983db2SEd Tanous #include <memory>
18b0983db2SEd Tanous #include <string>
19b0983db2SEd Tanous #include <string_view>
20b0983db2SEd Tanous 
21b0983db2SEd Tanous namespace redfish
22b0983db2SEd Tanous {
23b0983db2SEd Tanous 
2484177a2fSEd Tanous inline void handleManagersLogServiceJournalGet(
2584177a2fSEd Tanous     App& app, const crow::Request& req,
26b0983db2SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
2784177a2fSEd Tanous     const std::string& managerId)
2884177a2fSEd Tanous {
29b0983db2SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
30b0983db2SEd Tanous     {
31b0983db2SEd Tanous         return;
32b0983db2SEd Tanous     }
33b0983db2SEd Tanous 
34b0983db2SEd Tanous     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
35b0983db2SEd Tanous     {
36b0983db2SEd Tanous         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
37b0983db2SEd Tanous         return;
38b0983db2SEd Tanous     }
39b0983db2SEd Tanous 
4084177a2fSEd Tanous     asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
41b0983db2SEd Tanous     asyncResp->res.jsonValue["@odata.id"] =
42b0983db2SEd Tanous         boost::urls::format("/redfish/v1/Managers/{}/LogServices/Journal",
43b0983db2SEd Tanous                             BMCWEB_REDFISH_MANAGER_URI_NAME);
44b0983db2SEd Tanous     asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
45b0983db2SEd Tanous     asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
46b0983db2SEd Tanous     asyncResp->res.jsonValue["Id"] = "Journal";
47b0983db2SEd Tanous     asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
48b0983db2SEd Tanous 
49b0983db2SEd Tanous     std::pair<std::string, std::string> redfishDateTimeOffset =
50b0983db2SEd Tanous         redfish::time_utils::getDateTimeOffsetNow();
51b0983db2SEd Tanous     asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
52b0983db2SEd Tanous     asyncResp->res.jsonValue["DateTimeLocalOffset"] =
53b0983db2SEd Tanous         redfishDateTimeOffset.second;
54b0983db2SEd Tanous 
55b0983db2SEd Tanous     asyncResp->res.jsonValue["Entries"]["@odata.id"] = boost::urls::format(
56b0983db2SEd Tanous         "/redfish/v1/Managers/{}/LogServices/Journal/Entries",
57b0983db2SEd Tanous         BMCWEB_REDFISH_MANAGER_URI_NAME);
58b0983db2SEd Tanous }
59b0983db2SEd Tanous 
60055713e4SEd Tanous struct JournalReadState
61055713e4SEd Tanous {
62055713e4SEd Tanous     std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal;
63055713e4SEd Tanous };
64055713e4SEd Tanous 
65bd79bce8SPatrick Williams inline void readJournalEntries(
66bd79bce8SPatrick Williams     uint64_t topEntryCount, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
67055713e4SEd Tanous     JournalReadState&& readState)
68055713e4SEd Tanous {
69055713e4SEd Tanous     nlohmann::json& logEntry = asyncResp->res.jsonValue["Members"];
70055713e4SEd Tanous     nlohmann::json::array_t* logEntryArray =
71055713e4SEd Tanous         logEntry.get_ptr<nlohmann::json::array_t*>();
72055713e4SEd Tanous     if (logEntryArray == nullptr)
73055713e4SEd Tanous     {
74055713e4SEd Tanous         messages::internalError(asyncResp->res);
75055713e4SEd Tanous         return;
76055713e4SEd Tanous     }
77055713e4SEd Tanous 
78055713e4SEd Tanous     // The Journal APIs unfortunately do blocking calls to the filesystem, and
79055713e4SEd Tanous     // can be somewhat expensive.  Short of creating our own io_uring based
80055713e4SEd Tanous     // implementation of sd-journal, which would be difficult, the best thing we
81055713e4SEd Tanous     // can do is to only parse a certain number of entries at a time.  The
82055713e4SEd Tanous     // current chunk size is selected arbitrarily to ensure that we're not
83055713e4SEd Tanous     // trying to process thousands of entries at the same time.
84055713e4SEd Tanous     // The implementation will process the number of entries, then return
85055713e4SEd Tanous     // control to the io_context to let other operations continue.
86055713e4SEd Tanous     size_t segmentCountRemaining = 10;
87055713e4SEd Tanous 
88055713e4SEd Tanous     // Reset the unique ID on the first entry
89055713e4SEd Tanous     for (uint64_t entryCount = logEntryArray->size();
90055713e4SEd Tanous          entryCount < topEntryCount; entryCount++)
91055713e4SEd Tanous     {
92055713e4SEd Tanous         if (segmentCountRemaining == 0)
93055713e4SEd Tanous         {
94055713e4SEd Tanous             boost::asio::post(crow::connections::systemBus->get_io_context(),
95055713e4SEd Tanous                               [asyncResp, topEntryCount,
96055713e4SEd Tanous                                readState = std::move(readState)]() mutable {
97055713e4SEd Tanous                                   readJournalEntries(topEntryCount, asyncResp,
98055713e4SEd Tanous                                                      std::move(readState));
99055713e4SEd Tanous                               });
100055713e4SEd Tanous             return;
101055713e4SEd Tanous         }
102055713e4SEd Tanous 
103055713e4SEd Tanous         nlohmann::json::object_t bmcJournalLogEntry;
1048274eb11SEd Tanous         if (!fillBMCJournalLogEntryJson(readState.journal.get(),
105055713e4SEd Tanous                                         bmcJournalLogEntry))
106055713e4SEd Tanous         {
107055713e4SEd Tanous             messages::internalError(asyncResp->res);
108055713e4SEd Tanous             return;
109055713e4SEd Tanous         }
110055713e4SEd Tanous         logEntryArray->emplace_back(std::move(bmcJournalLogEntry));
111055713e4SEd Tanous 
1128274eb11SEd Tanous         int ret = sd_journal_next(readState.journal.get());
113055713e4SEd Tanous         if (ret < 0)
114055713e4SEd Tanous         {
115055713e4SEd Tanous             messages::internalError(asyncResp->res);
116055713e4SEd Tanous             return;
117055713e4SEd Tanous         }
118055713e4SEd Tanous         if (ret == 0)
119055713e4SEd Tanous         {
120055713e4SEd Tanous             break;
121055713e4SEd Tanous         }
122055713e4SEd Tanous         segmentCountRemaining--;
123055713e4SEd Tanous     }
124055713e4SEd Tanous }
125055713e4SEd Tanous 
12684177a2fSEd Tanous inline void handleManagersJournalLogEntryCollectionGet(
12784177a2fSEd Tanous     App& app, const crow::Request& req,
128b0983db2SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
12984177a2fSEd Tanous     const std::string& managerId)
13084177a2fSEd Tanous {
131b0983db2SEd Tanous     query_param::QueryCapabilities capabilities = {
132b0983db2SEd Tanous         .canDelegateTop = true,
133b0983db2SEd Tanous         .canDelegateSkip = true,
134b0983db2SEd Tanous     };
135b0983db2SEd Tanous     query_param::Query delegatedQuery;
13684177a2fSEd Tanous     if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
13784177a2fSEd Tanous                                                   delegatedQuery, capabilities))
138b0983db2SEd Tanous     {
139b0983db2SEd Tanous         return;
140b0983db2SEd Tanous     }
141b0983db2SEd Tanous 
142b0983db2SEd Tanous     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
143b0983db2SEd Tanous     {
144b0983db2SEd Tanous         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
145b0983db2SEd Tanous         return;
146b0983db2SEd Tanous     }
147b0983db2SEd Tanous 
148b0983db2SEd Tanous     size_t skip = delegatedQuery.skip.value_or(0);
149b0983db2SEd Tanous     size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
150b0983db2SEd Tanous 
151b0983db2SEd Tanous     // Collections don't include the static data added by SubRoute
152b0983db2SEd Tanous     // because it has a duplicate entry for members
153b0983db2SEd Tanous     asyncResp->res.jsonValue["@odata.type"] =
154b0983db2SEd Tanous         "#LogEntryCollection.LogEntryCollection";
155b0983db2SEd Tanous     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
156b0983db2SEd Tanous         "/redfish/v1/Managers/{}/LogServices/Journal/Entries",
157b0983db2SEd Tanous         BMCWEB_REDFISH_MANAGER_URI_NAME);
158b0983db2SEd Tanous     asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
159b0983db2SEd Tanous     asyncResp->res.jsonValue["Description"] =
160b0983db2SEd Tanous         "Collection of BMC Journal Entries";
161055713e4SEd Tanous     asyncResp->res.jsonValue["Members"] = nlohmann::json::array_t();
162b0983db2SEd Tanous 
163b0983db2SEd Tanous     // Go through the journal and use the timestamp to create a
164b0983db2SEd Tanous     // unique ID for each entry
165b0983db2SEd Tanous     sd_journal* journalTmp = nullptr;
166b0983db2SEd Tanous     int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
167b0983db2SEd Tanous     if (ret < 0)
168b0983db2SEd Tanous     {
1697da633f0SEd Tanous         BMCWEB_LOG_ERROR("failed to open journal: {}", ret);
170b0983db2SEd Tanous         messages::internalError(asyncResp->res);
171b0983db2SEd Tanous         return;
172b0983db2SEd Tanous     }
173055713e4SEd Tanous 
174b0983db2SEd Tanous     std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
175b0983db2SEd Tanous         journalTmp, sd_journal_close);
176b0983db2SEd Tanous     journalTmp = nullptr;
177b0983db2SEd Tanous 
178055713e4SEd Tanous     // Seek to the end
179055713e4SEd Tanous     if (sd_journal_seek_tail(journal.get()) < 0)
180b0983db2SEd Tanous     {
181b0983db2SEd Tanous         messages::internalError(asyncResp->res);
182b0983db2SEd Tanous         return;
183b0983db2SEd Tanous     }
184055713e4SEd Tanous 
185055713e4SEd Tanous     // Get the last entry
186055713e4SEd Tanous     if (sd_journal_previous(journal.get()) < 0)
187055713e4SEd Tanous     {
188055713e4SEd Tanous         messages::internalError(asyncResp->res);
189055713e4SEd Tanous         return;
190b0983db2SEd Tanous     }
191055713e4SEd Tanous 
192055713e4SEd Tanous     // Get the last sequence number
193055713e4SEd Tanous     uint64_t endSeqNum = 0;
194058f54edSPatrick Williams #if LIBSYSTEMD_VERSION >= 254
195055713e4SEd Tanous     {
196055713e4SEd Tanous         if (sd_journal_get_seqnum(journal.get(), &endSeqNum, nullptr) < 0)
197055713e4SEd Tanous         {
198055713e4SEd Tanous             messages::internalError(asyncResp->res);
199055713e4SEd Tanous             return;
200055713e4SEd Tanous         }
201055713e4SEd Tanous     }
202055713e4SEd Tanous #endif
203055713e4SEd Tanous 
204055713e4SEd Tanous     // Seek to the beginning
205055713e4SEd Tanous     if (sd_journal_seek_head(journal.get()) < 0)
206055713e4SEd Tanous     {
207055713e4SEd Tanous         messages::internalError(asyncResp->res);
208055713e4SEd Tanous         return;
209055713e4SEd Tanous     }
210055713e4SEd Tanous 
211055713e4SEd Tanous     // Get the first entry
212055713e4SEd Tanous     if (sd_journal_next(journal.get()) < 0)
213055713e4SEd Tanous     {
214055713e4SEd Tanous         messages::internalError(asyncResp->res);
215055713e4SEd Tanous         return;
216055713e4SEd Tanous     }
217055713e4SEd Tanous 
218055713e4SEd Tanous     // Get the first sequence number
219055713e4SEd Tanous     uint64_t startSeqNum = 0;
220058f54edSPatrick Williams #if LIBSYSTEMD_VERSION >= 254
221055713e4SEd Tanous     {
222055713e4SEd Tanous         if (sd_journal_get_seqnum(journal.get(), &startSeqNum, nullptr) < 0)
223055713e4SEd Tanous         {
224055713e4SEd Tanous             messages::internalError(asyncResp->res);
225055713e4SEd Tanous             return;
226055713e4SEd Tanous         }
227055713e4SEd Tanous     }
228055713e4SEd Tanous #endif
229055713e4SEd Tanous 
230055713e4SEd Tanous     BMCWEB_LOG_DEBUG("journal Sequence IDs start:{} end:{}", startSeqNum,
231055713e4SEd Tanous                      endSeqNum);
232055713e4SEd Tanous 
233055713e4SEd Tanous     // Add 1 to account for the last entry
234055713e4SEd Tanous     uint64_t totalEntries = endSeqNum - startSeqNum + 1;
235055713e4SEd Tanous     asyncResp->res.jsonValue["Members@odata.count"] = totalEntries;
236055713e4SEd Tanous     if (skip + top < totalEntries)
237b0983db2SEd Tanous     {
23884177a2fSEd Tanous         asyncResp->res.jsonValue["Members@odata.nextLink"] =
23984177a2fSEd Tanous             boost::urls::format(
240b0983db2SEd Tanous                 "/redfish/v1/Managers/{}/LogServices/Journal/Entries?$skip={}",
241b0983db2SEd Tanous                 BMCWEB_REDFISH_MANAGER_URI_NAME, std::to_string(skip + top));
242b0983db2SEd Tanous     }
243055713e4SEd Tanous     uint64_t index = 0;
244055713e4SEd Tanous     if (skip > 0)
245055713e4SEd Tanous     {
246055713e4SEd Tanous         if (sd_journal_next_skip(journal.get(), skip) < 0)
247055713e4SEd Tanous         {
248055713e4SEd Tanous             messages::internalError(asyncResp->res);
249055713e4SEd Tanous             return;
250055713e4SEd Tanous         }
251055713e4SEd Tanous     }
252055713e4SEd Tanous     BMCWEB_LOG_DEBUG("Index was {}", index);
2538274eb11SEd Tanous     readJournalEntries(top, asyncResp, {std::move(journal)});
254b0983db2SEd Tanous }
255b0983db2SEd Tanous 
25684177a2fSEd Tanous inline void handleManagersJournalEntriesLogEntryGet(
25784177a2fSEd Tanous     App& app, const crow::Request& req,
258b0983db2SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
25984177a2fSEd Tanous     const std::string& managerId, const std::string& entryID)
26084177a2fSEd Tanous {
261b0983db2SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
262b0983db2SEd Tanous     {
263b0983db2SEd Tanous         return;
264b0983db2SEd Tanous     }
265b0983db2SEd Tanous 
266b0983db2SEd Tanous     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
267b0983db2SEd Tanous     {
268b0983db2SEd Tanous         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
269b0983db2SEd Tanous         return;
270b0983db2SEd Tanous     }
271b0983db2SEd Tanous 
272b0983db2SEd Tanous     sd_journal* journalTmp = nullptr;
273b0983db2SEd Tanous     int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
274b0983db2SEd Tanous     if (ret < 0)
275b0983db2SEd Tanous     {
2767da633f0SEd Tanous         BMCWEB_LOG_ERROR("failed to open journal: {}", ret);
277b0983db2SEd Tanous         messages::internalError(asyncResp->res);
278b0983db2SEd Tanous         return;
279b0983db2SEd Tanous     }
280b0983db2SEd Tanous     std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
281b0983db2SEd Tanous         journalTmp, sd_journal_close);
282b0983db2SEd Tanous     journalTmp = nullptr;
2838274eb11SEd Tanous 
2848274eb11SEd Tanous     std::string cursor;
2858274eb11SEd Tanous     if (!crow::utility::base64Decode(entryID, cursor))
2868274eb11SEd Tanous     {
2878274eb11SEd Tanous         messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
2888274eb11SEd Tanous         return;
2898274eb11SEd Tanous     }
2908274eb11SEd Tanous 
2918274eb11SEd Tanous     // Go to the cursor in the log
2928274eb11SEd Tanous     ret = sd_journal_seek_cursor(journal.get(), cursor.c_str());
293b0983db2SEd Tanous     if (ret < 0)
294b0983db2SEd Tanous     {
2958274eb11SEd Tanous         messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
2968274eb11SEd Tanous         return;
2978274eb11SEd Tanous     }
2988274eb11SEd Tanous 
2998274eb11SEd Tanous     if (sd_journal_next(journal.get()) < 0)
3008274eb11SEd Tanous     {
301b0983db2SEd Tanous         messages::internalError(asyncResp->res);
302b0983db2SEd Tanous         return;
303b0983db2SEd Tanous     }
304055713e4SEd Tanous 
3058274eb11SEd Tanous     ret = sd_journal_test_cursor(journal.get(), cursor.c_str());
3068274eb11SEd Tanous     if (ret == 0)
3078274eb11SEd Tanous     {
3088274eb11SEd Tanous         messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
3098274eb11SEd Tanous         return;
3108274eb11SEd Tanous     }
3118274eb11SEd Tanous     if (ret < 0)
312b0983db2SEd Tanous     {
313b0983db2SEd Tanous         messages::internalError(asyncResp->res);
314b0983db2SEd Tanous         return;
315b0983db2SEd Tanous     }
316055713e4SEd Tanous 
317b0983db2SEd Tanous     nlohmann::json::object_t bmcJournalLogEntry;
3188274eb11SEd Tanous     if (!fillBMCJournalLogEntryJson(journal.get(), bmcJournalLogEntry))
319b0983db2SEd Tanous     {
320b0983db2SEd Tanous         messages::internalError(asyncResp->res);
321b0983db2SEd Tanous         return;
322b0983db2SEd Tanous     }
323b0983db2SEd Tanous     asyncResp->res.jsonValue.update(bmcJournalLogEntry);
324055713e4SEd Tanous }
32584177a2fSEd Tanous 
32684177a2fSEd Tanous inline void requestRoutesBMCJournalLogService(App& app)
32784177a2fSEd Tanous {
32884177a2fSEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/")
32984177a2fSEd Tanous         .privileges(redfish::privileges::getLogService)
33084177a2fSEd Tanous         .methods(boost::beast::http::verb::get)(
33184177a2fSEd Tanous             std::bind_front(handleManagersLogServiceJournalGet, std::ref(app)));
33284177a2fSEd Tanous 
33384177a2fSEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/")
33484177a2fSEd Tanous         .privileges(redfish::privileges::getLogEntryCollection)
33584177a2fSEd Tanous         .methods(boost::beast::http::verb::get)(std::bind_front(
33684177a2fSEd Tanous             handleManagersJournalLogEntryCollectionGet, std::ref(app)));
33784177a2fSEd Tanous 
33884177a2fSEd Tanous     BMCWEB_ROUTE(
33984177a2fSEd Tanous         app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/<str>/")
34084177a2fSEd Tanous         .privileges(redfish::privileges::getLogEntry)
34184177a2fSEd Tanous         .methods(boost::beast::http::verb::get)(std::bind_front(
34284177a2fSEd Tanous             handleManagersJournalEntriesLogEntryGet, std::ref(app)));
343b0983db2SEd Tanous }
344b0983db2SEd Tanous } // namespace redfish
345