xref: /openbmc/bmcweb/features/redfish/lib/systems_logservices_dbus_eventlog.hpp (revision 664c95605d55d04c03a7fb40de1d0b22ce1be946)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 // SPDX-FileCopyrightText: Copyright 2018 Intel Corporation
4 #pragma once
5 
6 #include "bmcweb_config.h"
7 
8 #include "app.hpp"
9 #include "async_resp.hpp"
10 #include "dbus_utility.hpp"
11 #include "error_messages.hpp"
12 #include "http_body.hpp"
13 #include "http_request.hpp"
14 #include "http_response.hpp"
15 #include "http_utility.hpp"
16 #include "logging.hpp"
17 #include "query.hpp"
18 #include "registries/privilege_registry.hpp"
19 #include "utils/dbus_event_log_entry.hpp"
20 #include "utils/dbus_utils.hpp"
21 #include "utils/json_utils.hpp"
22 #include "utils/log_services_utils.hpp"
23 #include "utils/time_utils.hpp"
24 
25 #include <asm-generic/errno.h>
26 #include <systemd/sd-bus.h>
27 #include <unistd.h>
28 
29 #include <boost/beast/http/field.hpp>
30 #include <boost/beast/http/status.hpp>
31 #include <boost/beast/http/verb.hpp>
32 #include <boost/system/linux_error.hpp>
33 #include <boost/url/format.hpp>
34 #include <boost/url/url.hpp>
35 #include <sdbusplus/message.hpp>
36 #include <sdbusplus/message/native_types.hpp>
37 #include <sdbusplus/unpack_properties.hpp>
38 
39 #include <algorithm>
40 #include <format>
41 #include <functional>
42 #include <memory>
43 #include <optional>
44 #include <string>
45 #include <string_view>
46 #include <utility>
47 #include <vector>
48 
49 namespace redfish
50 {
51 
52 inline std::optional<bool> getProviderNotifyAction(const std::string& notify)
53 {
54     std::optional<bool> notifyAction;
55     if (notify == "xyz.openbmc_project.Logging.Entry.Notify.Notify")
56     {
57         notifyAction = true;
58     }
59     else if (notify == "xyz.openbmc_project.Logging.Entry.Notify.Inhibit")
60     {
61         notifyAction = false;
62     }
63 
64     return notifyAction;
65 }
66 
67 inline std::string translateSeverityDbusToRedfish(const std::string& s)
68 {
69     if ((s == "xyz.openbmc_project.Logging.Entry.Level.Alert") ||
70         (s == "xyz.openbmc_project.Logging.Entry.Level.Critical") ||
71         (s == "xyz.openbmc_project.Logging.Entry.Level.Emergency") ||
72         (s == "xyz.openbmc_project.Logging.Entry.Level.Error"))
73     {
74         return "Critical";
75     }
76     if ((s == "xyz.openbmc_project.Logging.Entry.Level.Debug") ||
77         (s == "xyz.openbmc_project.Logging.Entry.Level.Informational") ||
78         (s == "xyz.openbmc_project.Logging.Entry.Level.Notice"))
79     {
80         return "OK";
81     }
82     if (s == "xyz.openbmc_project.Logging.Entry.Level.Warning")
83     {
84         return "Warning";
85     }
86     return "";
87 }
88 
89 inline void fillEventLogLogEntryFromDbusLogEntry(
90     const DbusEventLogEntry& entry, nlohmann::json& objectToFillOut)
91 {
92     objectToFillOut["@odata.type"] = "#LogEntry.v1_9_0.LogEntry";
93     objectToFillOut["@odata.id"] = boost::urls::format(
94         "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}",
95         BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(entry.Id));
96     objectToFillOut["Name"] = "System Event Log Entry";
97     objectToFillOut["Id"] = std::to_string(entry.Id);
98     objectToFillOut["Message"] = entry.Message;
99     objectToFillOut["Resolved"] = entry.Resolved;
100     std::optional<bool> notifyAction =
101         getProviderNotifyAction(entry.ServiceProviderNotify);
102     if (notifyAction)
103     {
104         objectToFillOut["ServiceProviderNotified"] = *notifyAction;
105     }
106     if ((entry.Resolution != nullptr) && !entry.Resolution->empty())
107     {
108         objectToFillOut["Resolution"] = *entry.Resolution;
109     }
110     objectToFillOut["EntryType"] = "Event";
111     objectToFillOut["Severity"] =
112         translateSeverityDbusToRedfish(entry.Severity);
113     objectToFillOut["Created"] =
114         redfish::time_utils::getDateTimeUintMs(entry.Timestamp);
115     objectToFillOut["Modified"] =
116         redfish::time_utils::getDateTimeUintMs(entry.UpdateTimestamp);
117     if (entry.Path != nullptr)
118     {
119         objectToFillOut["AdditionalDataURI"] = boost::urls::format(
120             "/redfish/v1/Systems/{}/LogServices/EventLog/Entries/{}/attachment",
121             BMCWEB_REDFISH_SYSTEM_URI_NAME, std::to_string(entry.Id));
122     }
123 }
124 
125 inline void afterLogEntriesGetManagedObjects(
126     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
127     const boost::system::error_code& ec,
128     const dbus::utility::ManagedObjectType& resp)
129 {
130     if (ec)
131     {
132         // TODO Handle for specific error code
133         BMCWEB_LOG_ERROR("getLogEntriesIfaceData resp_handler got error {}",
134                          ec);
135         messages::internalError(asyncResp->res);
136         return;
137     }
138     nlohmann::json::array_t entriesArray;
139     for (const auto& objectPath : resp)
140     {
141         dbus::utility::DBusPropertiesMap propsFlattened;
142         auto isEntry =
143             std::ranges::find_if(objectPath.second, [](const auto& object) {
144                 return object.first == "xyz.openbmc_project.Logging.Entry";
145             });
146         if (isEntry == objectPath.second.end())
147         {
148             continue;
149         }
150 
151         for (const auto& interfaceMap : objectPath.second)
152         {
153             for (const auto& propertyMap : interfaceMap.second)
154             {
155                 propsFlattened.emplace_back(propertyMap.first,
156                                             propertyMap.second);
157             }
158         }
159         std::optional<DbusEventLogEntry> optEntry =
160             fillDbusEventLogEntryFromPropertyMap(propsFlattened);
161 
162         if (!optEntry.has_value())
163         {
164             messages::internalError(asyncResp->res);
165             return;
166         }
167         fillEventLogLogEntryFromDbusLogEntry(*optEntry,
168                                              entriesArray.emplace_back());
169     }
170 
171     redfish::json_util::sortJsonArrayByKey(entriesArray, "Id");
172     asyncResp->res.jsonValue["Members@odata.count"] = entriesArray.size();
173     asyncResp->res.jsonValue["Members"] = std::move(entriesArray);
174 }
175 
176 inline void dBusEventLogEntryCollection(
177     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
178 {
179     // Collections don't include the static data added by SubRoute
180     // because it has a duplicate entry for members
181     asyncResp->res.jsonValue["@odata.type"] =
182         "#LogEntryCollection.LogEntryCollection";
183     asyncResp->res.jsonValue["@odata.id"] =
184         std::format("/redfish/v1/Systems/{}/LogServices/EventLog/Entries",
185                     BMCWEB_REDFISH_SYSTEM_URI_NAME);
186     asyncResp->res.jsonValue["Name"] = "System Event Log Entries";
187     asyncResp->res.jsonValue["Description"] =
188         "Collection of System Event Log Entries";
189 
190     // DBus implementation of EventLog/Entries
191     // Make call to Logging Service to find all log entry objects
192     sdbusplus::message::object_path path("/xyz/openbmc_project/logging");
193     dbus::utility::getManagedObjects(
194         "xyz.openbmc_project.Logging", path,
195         [asyncResp](const boost::system::error_code& ec,
196                     const dbus::utility::ManagedObjectType& resp) {
197             afterLogEntriesGetManagedObjects(asyncResp, ec, resp);
198         });
199 }
200 
201 inline void afterDBusEventLogEntryGet(
202     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
203     const std::string& entryID, const boost::system::error_code& ec,
204     const dbus::utility::DBusPropertiesMap& resp)
205 {
206     if (ec.value() == EBADR)
207     {
208         messages::resourceNotFound(asyncResp->res, "EventLogEntry", entryID);
209         return;
210     }
211     if (ec)
212     {
213         BMCWEB_LOG_ERROR("EventLogEntry (DBus) resp_handler got error {}", ec);
214         messages::internalError(asyncResp->res);
215         return;
216     }
217 
218     std::optional<DbusEventLogEntry> optEntry =
219         fillDbusEventLogEntryFromPropertyMap(resp);
220 
221     if (!optEntry.has_value())
222     {
223         messages::internalError(asyncResp->res);
224         return;
225     }
226 
227     fillEventLogLogEntryFromDbusLogEntry(*optEntry, asyncResp->res.jsonValue);
228 }
229 
230 inline void dBusEventLogEntryGet(
231     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, std::string entryID)
232 {
233     dbus::utility::escapePathForDbus(entryID);
234 
235     // DBus implementation of EventLog/Entries
236     // Make call to Logging Service to find all log entry objects
237     dbus::utility::getAllProperties(
238         "xyz.openbmc_project.Logging",
239         "/xyz/openbmc_project/logging/entry/" + entryID, "",
240         std::bind_front(afterDBusEventLogEntryGet, asyncResp, entryID));
241 }
242 
243 inline void dBusEventLogEntryPatch(
244     const crow::Request& req,
245     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
246     const std::string& entryId)
247 {
248     std::optional<bool> resolved;
249 
250     if (!json_util::readJsonPatch(req, asyncResp->res, "Resolved", resolved))
251     {
252         return;
253     }
254     BMCWEB_LOG_DEBUG("Set Resolved");
255 
256     setDbusProperty(asyncResp, "Resolved", "xyz.openbmc_project.Logging",
257                     "/xyz/openbmc_project/logging/entry/" + entryId,
258                     "xyz.openbmc_project.Logging.Entry", "Resolved",
259                     resolved.value_or(false));
260 }
261 
262 inline void dBusEventLogEntryDelete(
263     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, std::string entryID)
264 {
265     BMCWEB_LOG_DEBUG("Do delete single event entries.");
266 
267     dbus::utility::escapePathForDbus(entryID);
268 
269     // Process response from Logging service.
270     auto respHandler = [asyncResp,
271                         entryID](const boost::system::error_code& ec) {
272         BMCWEB_LOG_DEBUG("EventLogEntry (DBus) doDelete callback: Done");
273         if (ec)
274         {
275             if (ec.value() == EBADR)
276             {
277                 messages::resourceNotFound(asyncResp->res, "LogEntry", entryID);
278                 return;
279             }
280             // TODO Handle for specific error code
281             BMCWEB_LOG_ERROR(
282                 "EventLogEntry (DBus) doDelete respHandler got error {}", ec);
283             asyncResp->res.result(
284                 boost::beast::http::status::internal_server_error);
285             return;
286         }
287 
288         messages::success(asyncResp->res);
289     };
290 
291     // Make call to Logging service to request Delete Log
292     dbus::utility::async_method_call(
293         asyncResp, respHandler, "xyz.openbmc_project.Logging",
294         "/xyz/openbmc_project/logging/entry/" + entryID,
295         "xyz.openbmc_project.Object.Delete", "Delete");
296 }
297 
298 inline void dBusLogServiceActionsClear(
299     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp)
300 {
301     BMCWEB_LOG_DEBUG("Do delete all entries.");
302 
303     // Process response from Logging service.
304     auto respHandler = [asyncResp](const boost::system::error_code& ec) {
305         BMCWEB_LOG_DEBUG("doClearLog resp_handler callback: Done");
306         if (ec)
307         {
308             // TODO Handle for specific error code
309             BMCWEB_LOG_ERROR("doClearLog resp_handler got error {}", ec);
310             asyncResp->res.result(
311                 boost::beast::http::status::internal_server_error);
312             return;
313         }
314 
315         messages::success(asyncResp->res);
316     };
317 
318     // Make call to Logging service to request Clear Log
319     dbus::utility::async_method_call(
320         asyncResp, respHandler, "xyz.openbmc_project.Logging",
321         "/xyz/openbmc_project/logging",
322         "xyz.openbmc_project.Collection.DeleteAll", "DeleteAll");
323 }
324 
325 inline void downloadEventLogEntry(
326     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
327     const std::string& systemName, const std::string& entryID,
328     const std::string& dumpType)
329 {
330     if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
331     {
332         // Option currently returns no systems.  TBD
333         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
334                                    systemName);
335         return;
336     }
337     if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
338     {
339         messages::resourceNotFound(asyncResp->res, "ComputerSystem",
340                                    systemName);
341         return;
342     }
343 
344     std::string entryPath =
345         sdbusplus::message::object_path("/xyz/openbmc_project/logging/entry") /
346         entryID;
347 
348     auto downloadEventLogEntryHandler =
349         [asyncResp, entryID,
350          dumpType](const boost::system::error_code& ec,
351                    const sdbusplus::message::unix_fd& unixfd) {
352             log_services_utils::downloadEntryCallback(asyncResp, entryID,
353                                                       dumpType, ec, unixfd);
354         };
355 
356     dbus::utility::async_method_call(
357         asyncResp, std::move(downloadEventLogEntryHandler),
358         "xyz.openbmc_project.Logging", entryPath,
359         "xyz.openbmc_project.Logging.Entry", "GetEntry");
360 }
361 
362 inline void handleDBusEventLogEntryDownloadGet(
363     crow::App& app, const std::string& dumpType, const crow::Request& req,
364     const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
365     const std::string& systemName, const std::string& entryID)
366 {
367     if (!redfish::setUpRedfishRoute(app, req, asyncResp))
368     {
369         return;
370     }
371     if (!http_helpers::isContentTypeAllowed(
372             req.getHeaderValue("Accept"),
373             http_helpers::ContentType::OctetStream, true))
374     {
375         asyncResp->res.result(boost::beast::http::status::bad_request);
376         return;
377     }
378     downloadEventLogEntry(asyncResp, systemName, entryID, dumpType);
379 }
380 
381 inline void requestRoutesDBusEventLogEntryCollection(App& app)
382 {
383     BMCWEB_ROUTE(app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/")
384         .privileges(redfish::privileges::getLogEntryCollection)
385         .methods(boost::beast::http::verb::get)(
386             [&app](const crow::Request& req,
387                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
388                    const std::string& systemName) {
389                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
390                 {
391                     return;
392                 }
393                 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
394                 {
395                     // Option currently returns no systems.  TBD
396                     messages::resourceNotFound(asyncResp->res, "ComputerSystem",
397                                                systemName);
398                     return;
399                 }
400                 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
401                 {
402                     messages::resourceNotFound(asyncResp->res, "ComputerSystem",
403                                                systemName);
404                     return;
405                 }
406                 dBusEventLogEntryCollection(asyncResp);
407             });
408 }
409 
410 inline void requestRoutesDBusEventLogEntry(App& app)
411 {
412     BMCWEB_ROUTE(
413         app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
414         .privileges(redfish::privileges::getLogEntry)
415         .methods(boost::beast::http::verb::get)(
416             [&app](const crow::Request& req,
417                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
418                    const std::string& systemName, const std::string& entryId) {
419                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
420                 {
421                     return;
422                 }
423                 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
424                 {
425                     // Option currently returns no systems.  TBD
426                     messages::resourceNotFound(asyncResp->res, "ComputerSystem",
427                                                systemName);
428                     return;
429                 }
430                 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
431                 {
432                     messages::resourceNotFound(asyncResp->res, "ComputerSystem",
433                                                systemName);
434                     return;
435                 }
436 
437                 dBusEventLogEntryGet(asyncResp, entryId);
438             });
439 
440     BMCWEB_ROUTE(
441         app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
442         .privileges(redfish::privileges::patchLogEntry)
443         .methods(boost::beast::http::verb::patch)(
444             [&app](const crow::Request& req,
445                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
446                    const std::string& systemName, const std::string& entryId) {
447                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
448                 {
449                     return;
450                 }
451                 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
452                 {
453                     // Option currently returns no systems.  TBD
454                     messages::resourceNotFound(asyncResp->res, "ComputerSystem",
455                                                systemName);
456                     return;
457                 }
458                 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
459                 {
460                     messages::resourceNotFound(asyncResp->res, "ComputerSystem",
461                                                systemName);
462                     return;
463                 }
464 
465                 dBusEventLogEntryPatch(req, asyncResp, entryId);
466             });
467 
468     BMCWEB_ROUTE(
469         app, "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/")
470         .privileges(
471             redfish::privileges::
472                 deleteLogEntrySubOverComputerSystemLogServiceCollectionLogServiceLogEntryCollection)
473         .methods(boost::beast::http::verb::delete_)(
474             [&app](const crow::Request& req,
475                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
476                    const std::string& systemName, const std::string& param) {
477                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
478                 {
479                     return;
480                 }
481                 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
482                 {
483                     // Option currently returns no systems.  TBD
484                     messages::resourceNotFound(asyncResp->res, "ComputerSystem",
485                                                systemName);
486                     return;
487                 }
488                 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
489                 {
490                     messages::resourceNotFound(asyncResp->res, "ComputerSystem",
491                                                systemName);
492                     return;
493                 }
494                 dBusEventLogEntryDelete(asyncResp, param);
495             });
496 }
497 
498 /**
499  * DBusLogServiceActionsClear class supports POST method for ClearLog action.
500  */
501 inline void requestRoutesDBusLogServiceActionsClear(App& app)
502 {
503     /**
504      * Function handles POST method request.
505      * The Clear Log actions does not require any parameter.The action deletes
506      * all entries found in the Entries collection for this Log Service.
507      */
508 
509     BMCWEB_ROUTE(
510         app,
511         "/redfish/v1/Systems/<str>/LogServices/EventLog/Actions/LogService.ClearLog/")
512         .privileges(redfish::privileges::
513                         postLogServiceSubOverComputerSystemLogServiceCollection)
514         .methods(boost::beast::http::verb::post)(
515             [&app](const crow::Request& req,
516                    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
517                    const std::string& systemName) {
518                 if (!redfish::setUpRedfishRoute(app, req, asyncResp))
519                 {
520                     return;
521                 }
522                 if constexpr (BMCWEB_EXPERIMENTAL_REDFISH_MULTI_COMPUTER_SYSTEM)
523                 {
524                     // Option currently returns no systems.  TBD
525                     messages::resourceNotFound(asyncResp->res, "ComputerSystem",
526                                                systemName);
527                     return;
528                 }
529                 if (systemName != BMCWEB_REDFISH_SYSTEM_URI_NAME)
530                 {
531                     messages::resourceNotFound(asyncResp->res, "ComputerSystem",
532                                                systemName);
533                     return;
534                 }
535                 dBusLogServiceActionsClear(asyncResp);
536             });
537 }
538 
539 inline void requestRoutesDBusEventLogEntryDownload(App& app)
540 {
541     BMCWEB_ROUTE(
542         app,
543         "/redfish/v1/Systems/<str>/LogServices/EventLog/Entries/<str>/attachment/")
544         .privileges(redfish::privileges::getLogEntry)
545         .methods(boost::beast::http::verb::get)(std::bind_front(
546             handleDBusEventLogEntryDownloadGet, std::ref(app), "System"));
547 }
548 } // namespace redfish
549