xref: /openbmc/bmcweb/features/redfish/lib/manager_logservices_journal.hpp (revision d78572018fc2022091ff8b8eb5a7fef2172ba3d6)
140e9b92eSEd Tanous // SPDX-License-Identifier: Apache-2.0
240e9b92eSEd Tanous // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3b0983db2SEd Tanous #pragma once
4b0983db2SEd Tanous 
5*d7857201SEd Tanous #include "bmcweb_config.h"
6*d7857201SEd Tanous 
7b0983db2SEd Tanous #include "app.hpp"
8*d7857201SEd Tanous #include "async_resp.hpp"
9*d7857201SEd Tanous #include "dbus_singleton.hpp"
10b0983db2SEd Tanous #include "error_messages.hpp"
11*d7857201SEd Tanous #include "http_request.hpp"
12*d7857201SEd Tanous #include "logging.hpp"
13b0983db2SEd Tanous #include "query.hpp"
14b0983db2SEd Tanous #include "registries/privilege_registry.hpp"
15*d7857201SEd Tanous #include "utility.hpp"
1660e995cdSEd Tanous #include "utils/journal_utils.hpp"
17*d7857201SEd Tanous #include "utils/query_param.hpp"
18b0983db2SEd Tanous #include "utils/time_utils.hpp"
19b0983db2SEd Tanous 
20b0983db2SEd Tanous #include <systemd/sd-journal.h>
21b0983db2SEd Tanous 
22*d7857201SEd Tanous #include <boost/asio/post.hpp>
23b0983db2SEd Tanous #include <boost/beast/http/verb.hpp>
24*d7857201SEd Tanous #include <boost/url/format.hpp>
25b0983db2SEd Tanous 
26*d7857201SEd Tanous #include <cstddef>
27*d7857201SEd Tanous #include <cstdint>
28*d7857201SEd Tanous #include <functional>
29b0983db2SEd Tanous #include <memory>
30b0983db2SEd Tanous #include <string>
31b0983db2SEd Tanous #include <string_view>
32*d7857201SEd Tanous #include <utility>
33b0983db2SEd Tanous 
34b0983db2SEd Tanous namespace redfish
35b0983db2SEd Tanous {
36b0983db2SEd Tanous 
3784177a2fSEd Tanous inline void handleManagersLogServiceJournalGet(
3884177a2fSEd Tanous     App& app, const crow::Request& req,
39b0983db2SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
4084177a2fSEd Tanous     const std::string& managerId)
4184177a2fSEd Tanous {
42b0983db2SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
43b0983db2SEd Tanous     {
44b0983db2SEd Tanous         return;
45b0983db2SEd Tanous     }
46b0983db2SEd Tanous 
47b0983db2SEd Tanous     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
48b0983db2SEd Tanous     {
49b0983db2SEd Tanous         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
50b0983db2SEd Tanous         return;
51b0983db2SEd Tanous     }
52b0983db2SEd Tanous 
5384177a2fSEd Tanous     asyncResp->res.jsonValue["@odata.type"] = "#LogService.v1_2_0.LogService";
54b0983db2SEd Tanous     asyncResp->res.jsonValue["@odata.id"] =
55b0983db2SEd Tanous         boost::urls::format("/redfish/v1/Managers/{}/LogServices/Journal",
56b0983db2SEd Tanous                             BMCWEB_REDFISH_MANAGER_URI_NAME);
57b0983db2SEd Tanous     asyncResp->res.jsonValue["Name"] = "Open BMC Journal Log Service";
58b0983db2SEd Tanous     asyncResp->res.jsonValue["Description"] = "BMC Journal Log Service";
59b0983db2SEd Tanous     asyncResp->res.jsonValue["Id"] = "Journal";
60b0983db2SEd Tanous     asyncResp->res.jsonValue["OverWritePolicy"] = "WrapsWhenFull";
61b0983db2SEd Tanous 
62b0983db2SEd Tanous     std::pair<std::string, std::string> redfishDateTimeOffset =
63b0983db2SEd Tanous         redfish::time_utils::getDateTimeOffsetNow();
64b0983db2SEd Tanous     asyncResp->res.jsonValue["DateTime"] = redfishDateTimeOffset.first;
65b0983db2SEd Tanous     asyncResp->res.jsonValue["DateTimeLocalOffset"] =
66b0983db2SEd Tanous         redfishDateTimeOffset.second;
67b0983db2SEd Tanous 
68b0983db2SEd Tanous     asyncResp->res.jsonValue["Entries"]["@odata.id"] = boost::urls::format(
69b0983db2SEd Tanous         "/redfish/v1/Managers/{}/LogServices/Journal/Entries",
70b0983db2SEd Tanous         BMCWEB_REDFISH_MANAGER_URI_NAME);
71b0983db2SEd Tanous }
72b0983db2SEd Tanous 
73055713e4SEd Tanous struct JournalReadState
74055713e4SEd Tanous {
75055713e4SEd Tanous     std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal;
76055713e4SEd Tanous };
77055713e4SEd Tanous 
78bd79bce8SPatrick Williams inline void readJournalEntries(
79bd79bce8SPatrick Williams     uint64_t topEntryCount, const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
80055713e4SEd Tanous     JournalReadState&& readState)
81055713e4SEd Tanous {
82055713e4SEd Tanous     nlohmann::json& logEntry = asyncResp->res.jsonValue["Members"];
83055713e4SEd Tanous     nlohmann::json::array_t* logEntryArray =
84055713e4SEd Tanous         logEntry.get_ptr<nlohmann::json::array_t*>();
85055713e4SEd Tanous     if (logEntryArray == nullptr)
86055713e4SEd Tanous     {
87055713e4SEd Tanous         messages::internalError(asyncResp->res);
88055713e4SEd Tanous         return;
89055713e4SEd Tanous     }
90055713e4SEd Tanous 
91055713e4SEd Tanous     // The Journal APIs unfortunately do blocking calls to the filesystem, and
92055713e4SEd Tanous     // can be somewhat expensive.  Short of creating our own io_uring based
93055713e4SEd Tanous     // implementation of sd-journal, which would be difficult, the best thing we
94055713e4SEd Tanous     // can do is to only parse a certain number of entries at a time.  The
95055713e4SEd Tanous     // current chunk size is selected arbitrarily to ensure that we're not
96055713e4SEd Tanous     // trying to process thousands of entries at the same time.
97055713e4SEd Tanous     // The implementation will process the number of entries, then return
98055713e4SEd Tanous     // control to the io_context to let other operations continue.
99055713e4SEd Tanous     size_t segmentCountRemaining = 10;
100055713e4SEd Tanous 
101055713e4SEd Tanous     // Reset the unique ID on the first entry
102055713e4SEd Tanous     for (uint64_t entryCount = logEntryArray->size();
103055713e4SEd Tanous          entryCount < topEntryCount; entryCount++)
104055713e4SEd Tanous     {
105055713e4SEd Tanous         if (segmentCountRemaining == 0)
106055713e4SEd Tanous         {
107055713e4SEd Tanous             boost::asio::post(crow::connections::systemBus->get_io_context(),
108055713e4SEd Tanous                               [asyncResp, topEntryCount,
109055713e4SEd Tanous                                readState = std::move(readState)]() mutable {
110055713e4SEd Tanous                                   readJournalEntries(topEntryCount, asyncResp,
111055713e4SEd Tanous                                                      std::move(readState));
112055713e4SEd Tanous                               });
113055713e4SEd Tanous             return;
114055713e4SEd Tanous         }
115055713e4SEd Tanous 
116055713e4SEd Tanous         nlohmann::json::object_t bmcJournalLogEntry;
1178274eb11SEd Tanous         if (!fillBMCJournalLogEntryJson(readState.journal.get(),
118055713e4SEd Tanous                                         bmcJournalLogEntry))
119055713e4SEd Tanous         {
120055713e4SEd Tanous             messages::internalError(asyncResp->res);
121055713e4SEd Tanous             return;
122055713e4SEd Tanous         }
123055713e4SEd Tanous         logEntryArray->emplace_back(std::move(bmcJournalLogEntry));
124055713e4SEd Tanous 
1258274eb11SEd Tanous         int ret = sd_journal_next(readState.journal.get());
126055713e4SEd Tanous         if (ret < 0)
127055713e4SEd Tanous         {
128055713e4SEd Tanous             messages::internalError(asyncResp->res);
129055713e4SEd Tanous             return;
130055713e4SEd Tanous         }
131055713e4SEd Tanous         if (ret == 0)
132055713e4SEd Tanous         {
133055713e4SEd Tanous             break;
134055713e4SEd Tanous         }
135055713e4SEd Tanous         segmentCountRemaining--;
136055713e4SEd Tanous     }
137055713e4SEd Tanous }
138055713e4SEd Tanous 
13984177a2fSEd Tanous inline void handleManagersJournalLogEntryCollectionGet(
14084177a2fSEd Tanous     App& app, const crow::Request& req,
141b0983db2SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
14284177a2fSEd Tanous     const std::string& managerId)
14384177a2fSEd Tanous {
144b0983db2SEd Tanous     query_param::QueryCapabilities capabilities = {
145b0983db2SEd Tanous         .canDelegateTop = true,
146b0983db2SEd Tanous         .canDelegateSkip = true,
147b0983db2SEd Tanous     };
148b0983db2SEd Tanous     query_param::Query delegatedQuery;
14984177a2fSEd Tanous     if (!redfish::setUpRedfishRouteWithDelegation(app, req, asyncResp,
15084177a2fSEd Tanous                                                   delegatedQuery, capabilities))
151b0983db2SEd Tanous     {
152b0983db2SEd Tanous         return;
153b0983db2SEd Tanous     }
154b0983db2SEd Tanous 
155b0983db2SEd Tanous     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
156b0983db2SEd Tanous     {
157b0983db2SEd Tanous         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
158b0983db2SEd Tanous         return;
159b0983db2SEd Tanous     }
160b0983db2SEd Tanous 
161b0983db2SEd Tanous     size_t skip = delegatedQuery.skip.value_or(0);
162b0983db2SEd Tanous     size_t top = delegatedQuery.top.value_or(query_param::Query::maxTop);
163b0983db2SEd Tanous 
164b0983db2SEd Tanous     // Collections don't include the static data added by SubRoute
165b0983db2SEd Tanous     // because it has a duplicate entry for members
166b0983db2SEd Tanous     asyncResp->res.jsonValue["@odata.type"] =
167b0983db2SEd Tanous         "#LogEntryCollection.LogEntryCollection";
168b0983db2SEd Tanous     asyncResp->res.jsonValue["@odata.id"] = boost::urls::format(
169b0983db2SEd Tanous         "/redfish/v1/Managers/{}/LogServices/Journal/Entries",
170b0983db2SEd Tanous         BMCWEB_REDFISH_MANAGER_URI_NAME);
171b0983db2SEd Tanous     asyncResp->res.jsonValue["Name"] = "Open BMC Journal Entries";
172b0983db2SEd Tanous     asyncResp->res.jsonValue["Description"] =
173b0983db2SEd Tanous         "Collection of BMC Journal Entries";
174055713e4SEd Tanous     asyncResp->res.jsonValue["Members"] = nlohmann::json::array_t();
175b0983db2SEd Tanous 
176b0983db2SEd Tanous     // Go through the journal and use the timestamp to create a
177b0983db2SEd Tanous     // unique ID for each entry
178b0983db2SEd Tanous     sd_journal* journalTmp = nullptr;
179b0983db2SEd Tanous     int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
180b0983db2SEd Tanous     if (ret < 0)
181b0983db2SEd Tanous     {
1827da633f0SEd Tanous         BMCWEB_LOG_ERROR("failed to open journal: {}", ret);
183b0983db2SEd Tanous         messages::internalError(asyncResp->res);
184b0983db2SEd Tanous         return;
185b0983db2SEd Tanous     }
186055713e4SEd Tanous 
187b0983db2SEd Tanous     std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
188b0983db2SEd Tanous         journalTmp, sd_journal_close);
189b0983db2SEd Tanous     journalTmp = nullptr;
190b0983db2SEd Tanous 
191055713e4SEd Tanous     // Seek to the end
192055713e4SEd Tanous     if (sd_journal_seek_tail(journal.get()) < 0)
193b0983db2SEd Tanous     {
194b0983db2SEd Tanous         messages::internalError(asyncResp->res);
195b0983db2SEd Tanous         return;
196b0983db2SEd Tanous     }
197055713e4SEd Tanous 
198055713e4SEd Tanous     // Get the last entry
199055713e4SEd Tanous     if (sd_journal_previous(journal.get()) < 0)
200055713e4SEd Tanous     {
201055713e4SEd Tanous         messages::internalError(asyncResp->res);
202055713e4SEd Tanous         return;
203b0983db2SEd Tanous     }
204055713e4SEd Tanous 
205055713e4SEd Tanous     // Get the last sequence number
206055713e4SEd Tanous     uint64_t endSeqNum = 0;
207058f54edSPatrick Williams #if LIBSYSTEMD_VERSION >= 254
208055713e4SEd Tanous     {
209055713e4SEd Tanous         if (sd_journal_get_seqnum(journal.get(), &endSeqNum, nullptr) < 0)
210055713e4SEd Tanous         {
211055713e4SEd Tanous             messages::internalError(asyncResp->res);
212055713e4SEd Tanous             return;
213055713e4SEd Tanous         }
214055713e4SEd Tanous     }
215055713e4SEd Tanous #endif
216055713e4SEd Tanous 
217055713e4SEd Tanous     // Seek to the beginning
218055713e4SEd Tanous     if (sd_journal_seek_head(journal.get()) < 0)
219055713e4SEd Tanous     {
220055713e4SEd Tanous         messages::internalError(asyncResp->res);
221055713e4SEd Tanous         return;
222055713e4SEd Tanous     }
223055713e4SEd Tanous 
224055713e4SEd Tanous     // Get the first entry
225055713e4SEd Tanous     if (sd_journal_next(journal.get()) < 0)
226055713e4SEd Tanous     {
227055713e4SEd Tanous         messages::internalError(asyncResp->res);
228055713e4SEd Tanous         return;
229055713e4SEd Tanous     }
230055713e4SEd Tanous 
231055713e4SEd Tanous     // Get the first sequence number
232055713e4SEd Tanous     uint64_t startSeqNum = 0;
233058f54edSPatrick Williams #if LIBSYSTEMD_VERSION >= 254
234055713e4SEd Tanous     {
235055713e4SEd Tanous         if (sd_journal_get_seqnum(journal.get(), &startSeqNum, nullptr) < 0)
236055713e4SEd Tanous         {
237055713e4SEd Tanous             messages::internalError(asyncResp->res);
238055713e4SEd Tanous             return;
239055713e4SEd Tanous         }
240055713e4SEd Tanous     }
241055713e4SEd Tanous #endif
242055713e4SEd Tanous 
243055713e4SEd Tanous     BMCWEB_LOG_DEBUG("journal Sequence IDs start:{} end:{}", startSeqNum,
244055713e4SEd Tanous                      endSeqNum);
245055713e4SEd Tanous 
246055713e4SEd Tanous     // Add 1 to account for the last entry
247055713e4SEd Tanous     uint64_t totalEntries = endSeqNum - startSeqNum + 1;
248055713e4SEd Tanous     asyncResp->res.jsonValue["Members@odata.count"] = totalEntries;
249055713e4SEd Tanous     if (skip + top < totalEntries)
250b0983db2SEd Tanous     {
25184177a2fSEd Tanous         asyncResp->res.jsonValue["Members@odata.nextLink"] =
25284177a2fSEd Tanous             boost::urls::format(
253b0983db2SEd Tanous                 "/redfish/v1/Managers/{}/LogServices/Journal/Entries?$skip={}",
254b0983db2SEd Tanous                 BMCWEB_REDFISH_MANAGER_URI_NAME, std::to_string(skip + top));
255b0983db2SEd Tanous     }
256055713e4SEd Tanous     uint64_t index = 0;
257055713e4SEd Tanous     if (skip > 0)
258055713e4SEd Tanous     {
259055713e4SEd Tanous         if (sd_journal_next_skip(journal.get(), skip) < 0)
260055713e4SEd Tanous         {
261055713e4SEd Tanous             messages::internalError(asyncResp->res);
262055713e4SEd Tanous             return;
263055713e4SEd Tanous         }
264055713e4SEd Tanous     }
265055713e4SEd Tanous     BMCWEB_LOG_DEBUG("Index was {}", index);
2668274eb11SEd Tanous     readJournalEntries(top, asyncResp, {std::move(journal)});
267b0983db2SEd Tanous }
268b0983db2SEd Tanous 
26984177a2fSEd Tanous inline void handleManagersJournalEntriesLogEntryGet(
27084177a2fSEd Tanous     App& app, const crow::Request& req,
271b0983db2SEd Tanous     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
27284177a2fSEd Tanous     const std::string& managerId, const std::string& entryID)
27384177a2fSEd Tanous {
274b0983db2SEd Tanous     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
275b0983db2SEd Tanous     {
276b0983db2SEd Tanous         return;
277b0983db2SEd Tanous     }
278b0983db2SEd Tanous 
279b0983db2SEd Tanous     if (managerId != BMCWEB_REDFISH_MANAGER_URI_NAME)
280b0983db2SEd Tanous     {
281b0983db2SEd Tanous         messages::resourceNotFound(asyncResp->res, "Manager", managerId);
282b0983db2SEd Tanous         return;
283b0983db2SEd Tanous     }
284b0983db2SEd Tanous 
285b0983db2SEd Tanous     sd_journal* journalTmp = nullptr;
286b0983db2SEd Tanous     int ret = sd_journal_open(&journalTmp, SD_JOURNAL_LOCAL_ONLY);
287b0983db2SEd Tanous     if (ret < 0)
288b0983db2SEd Tanous     {
2897da633f0SEd Tanous         BMCWEB_LOG_ERROR("failed to open journal: {}", ret);
290b0983db2SEd Tanous         messages::internalError(asyncResp->res);
291b0983db2SEd Tanous         return;
292b0983db2SEd Tanous     }
293b0983db2SEd Tanous     std::unique_ptr<sd_journal, decltype(&sd_journal_close)> journal(
294b0983db2SEd Tanous         journalTmp, sd_journal_close);
295b0983db2SEd Tanous     journalTmp = nullptr;
2968274eb11SEd Tanous 
2978274eb11SEd Tanous     std::string cursor;
2988274eb11SEd Tanous     if (!crow::utility::base64Decode(entryID, cursor))
2998274eb11SEd Tanous     {
3008274eb11SEd Tanous         messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
3018274eb11SEd Tanous         return;
3028274eb11SEd Tanous     }
3038274eb11SEd Tanous 
3048274eb11SEd Tanous     // Go to the cursor in the log
3058274eb11SEd Tanous     ret = sd_journal_seek_cursor(journal.get(), cursor.c_str());
306b0983db2SEd Tanous     if (ret < 0)
307b0983db2SEd Tanous     {
3088274eb11SEd Tanous         messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
3098274eb11SEd Tanous         return;
3108274eb11SEd Tanous     }
3118274eb11SEd Tanous 
3128274eb11SEd Tanous     if (sd_journal_next(journal.get()) < 0)
3138274eb11SEd Tanous     {
314b0983db2SEd Tanous         messages::internalError(asyncResp->res);
315b0983db2SEd Tanous         return;
316b0983db2SEd Tanous     }
317055713e4SEd Tanous 
3188274eb11SEd Tanous     ret = sd_journal_test_cursor(journal.get(), cursor.c_str());
3198274eb11SEd Tanous     if (ret == 0)
3208274eb11SEd Tanous     {
3218274eb11SEd Tanous         messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
3228274eb11SEd Tanous         return;
3238274eb11SEd Tanous     }
3248274eb11SEd Tanous     if (ret < 0)
325b0983db2SEd Tanous     {
326b0983db2SEd Tanous         messages::internalError(asyncResp->res);
327b0983db2SEd Tanous         return;
328b0983db2SEd Tanous     }
329055713e4SEd Tanous 
330b0983db2SEd Tanous     nlohmann::json::object_t bmcJournalLogEntry;
3318274eb11SEd Tanous     if (!fillBMCJournalLogEntryJson(journal.get(), bmcJournalLogEntry))
332b0983db2SEd Tanous     {
333b0983db2SEd Tanous         messages::internalError(asyncResp->res);
334b0983db2SEd Tanous         return;
335b0983db2SEd Tanous     }
336b0983db2SEd Tanous     asyncResp->res.jsonValue.update(bmcJournalLogEntry);
337055713e4SEd Tanous }
33884177a2fSEd Tanous 
33984177a2fSEd Tanous inline void requestRoutesBMCJournalLogService(App& app)
34084177a2fSEd Tanous {
34184177a2fSEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/")
34284177a2fSEd Tanous         .privileges(redfish::privileges::getLogService)
34384177a2fSEd Tanous         .methods(boost::beast::http::verb::get)(
34484177a2fSEd Tanous             std::bind_front(handleManagersLogServiceJournalGet, std::ref(app)));
34584177a2fSEd Tanous 
34684177a2fSEd Tanous     BMCWEB_ROUTE(app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/")
34784177a2fSEd Tanous         .privileges(redfish::privileges::getLogEntryCollection)
34884177a2fSEd Tanous         .methods(boost::beast::http::verb::get)(std::bind_front(
34984177a2fSEd Tanous             handleManagersJournalLogEntryCollectionGet, std::ref(app)));
35084177a2fSEd Tanous 
35184177a2fSEd Tanous     BMCWEB_ROUTE(
35284177a2fSEd Tanous         app, "/redfish/v1/Managers/<str>/LogServices/Journal/Entries/<str>/")
35384177a2fSEd Tanous         .privileges(redfish::privileges::getLogEntry)
35484177a2fSEd Tanous         .methods(boost::beast::http::verb::get)(std::bind_front(
35584177a2fSEd Tanous             handleManagersJournalEntriesLogEntryGet, std::ref(app)));
356b0983db2SEd Tanous }
357b0983db2SEd Tanous } // namespace redfish
358