xref: /openbmc/bmcweb/redfish-core/include/event_service_manager.hpp (revision 82b286fb77833c70bd670f3b428e49f6d9416d06)
1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors
3 // SPDX-FileCopyrightText: Copyright 2020 Intel Corporation
4 #pragma once
5 #include "bmcweb_config.h"
6 
7 #include "dbus_log_watcher.hpp"
8 #include "error_messages.hpp"
9 #include "event_logs_object_type.hpp"
10 #include "event_matches_filter.hpp"
11 #include "event_service_store.hpp"
12 #include "filesystem_log_watcher.hpp"
13 #include "io_context_singleton.hpp"
14 #include "logging.hpp"
15 #include "metric_report.hpp"
16 #include "ossl_random.hpp"
17 #include "persistent_data.hpp"
18 #include "server_sent_event.hpp"
19 #include "subscription.hpp"
20 #include "utils/time_utils.hpp"
21 
22 #include <boost/circular_buffer.hpp>
23 #include <boost/circular_buffer/base.hpp>
24 #include <boost/container/flat_map.hpp>
25 #include <boost/system/result.hpp>
26 #include <boost/url/parse.hpp>
27 #include <boost/url/url_view_base.hpp>
28 
29 #include <algorithm>
30 #include <cstdint>
31 #include <cstdlib>
32 #include <ctime>
33 #include <filesystem>
34 #include <format>
35 #include <fstream>
36 #include <memory>
37 #include <optional>
38 #include <random>
39 #include <string>
40 #include <string_view>
41 #include <system_error>
42 #include <utility>
43 #include <vector>
44 
45 namespace redfish
46 {
47 
48 static constexpr const char* eventFormatType = "Event";
49 static constexpr const char* metricReportFormatType = "MetricReport";
50 
51 class EventServiceManager
52 {
53   private:
54     bool serviceEnabled = false;
55     uint32_t retryAttempts = 0;
56     uint32_t retryTimeoutInterval = 0;
57 
58     size_t noOfEventLogSubscribers{0};
59     size_t noOfMetricReportSubscribers{0};
60     std::optional<DbusEventLogMonitor> dbusEventLogMonitor;
61     std::optional<DbusTelemetryMonitor> matchTelemetryMonitor;
62     std::optional<FilesystemLogWatcher> filesystemLogMonitor;
63     boost::container::flat_map<std::string, std::shared_ptr<Subscription>>
64         subscriptionsMap;
65 
66     uint64_t eventId{1};
67 
68     struct Event
69     {
70         uint64_t id;
71         nlohmann::json::object_t message;
72     };
73 
74     constexpr static size_t maxMessages = 200;
75     boost::circular_buffer<Event> messages{maxMessages};
76 
77   public:
78     EventServiceManager(const EventServiceManager&) = delete;
79     EventServiceManager& operator=(const EventServiceManager&) = delete;
80     EventServiceManager(EventServiceManager&&) = delete;
81     EventServiceManager& operator=(EventServiceManager&&) = delete;
82     ~EventServiceManager() = default;
83 
EventServiceManager()84     explicit EventServiceManager()
85     {
86         // Load config from persist store.
87         initConfig();
88     }
89 
getInstance()90     static EventServiceManager& getInstance()
91     {
92         static EventServiceManager handler;
93         return handler;
94     }
95 
initConfig()96     void initConfig()
97     {
98         persistent_data::EventServiceConfig eventServiceConfig =
99             persistent_data::EventServiceStore::getInstance()
100                 .getEventServiceConfig();
101 
102         serviceEnabled = eventServiceConfig.enabled;
103         retryAttempts = eventServiceConfig.retryAttempts;
104         retryTimeoutInterval = eventServiceConfig.retryTimeoutInterval;
105 
106         for (const auto& it : persistent_data::EventServiceStore::getInstance()
107                                   .subscriptionsConfigMap)
108         {
109             std::shared_ptr<persistent_data::UserSubscription> newSub =
110                 it.second;
111 
112             boost::system::result<boost::urls::url> url =
113                 boost::urls::parse_absolute_uri(newSub->destinationUrl);
114 
115             if (!url)
116             {
117                 BMCWEB_LOG_ERROR(
118                     "Failed to validate and split destination url");
119                 continue;
120             }
121             std::shared_ptr<Subscription> subValue =
122                 std::make_shared<Subscription>(newSub, *url, getIoContext());
123             std::string id = subValue->userSub->id;
124             subValue->deleter = [id]() {
125                 EventServiceManager::getInstance().deleteSubscription(id);
126             };
127 
128             subscriptionsMap.emplace(id, subValue);
129 
130             updateNoOfSubscribersCount();
131 
132             // Update retry configuration.
133             subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval);
134 
135             // schedule a heartbeat if sendHeartbeat was set to true
136             if (subValue->userSub->sendHeartbeat)
137             {
138                 subValue->scheduleNextHeartbeatEvent();
139             }
140         }
141     }
142 
updateSubscriptionData() const143     void updateSubscriptionData() const
144     {
145         persistent_data::EventServiceStore::getInstance()
146             .eventServiceConfig.enabled = serviceEnabled;
147         persistent_data::EventServiceStore::getInstance()
148             .eventServiceConfig.retryAttempts = retryAttempts;
149         persistent_data::EventServiceStore::getInstance()
150             .eventServiceConfig.retryTimeoutInterval = retryTimeoutInterval;
151 
152         persistent_data::getConfig().writeData();
153     }
154 
setEventServiceConfig(const persistent_data::EventServiceConfig & cfg)155     void setEventServiceConfig(const persistent_data::EventServiceConfig& cfg)
156     {
157         bool updateConfig = false;
158         bool updateRetryCfg = false;
159 
160         if (serviceEnabled)
161         {
162             if (noOfEventLogSubscribers > 0U)
163             {
164                 if constexpr (BMCWEB_REDFISH_DBUS_LOG)
165                 {
166                     if (!dbusEventLogMonitor)
167                     {
168                         if constexpr (
169                             BMCWEB_EXPERIMENTAL_REDFISH_DBUS_LOG_SUBSCRIPTION)
170                         {
171                             dbusEventLogMonitor.emplace();
172                         }
173                     }
174                 }
175                 else
176                 {
177                     if (!filesystemLogMonitor)
178                     {
179                         filesystemLogMonitor.emplace(getIoContext());
180                     }
181                 }
182             }
183             else
184             {
185                 dbusEventLogMonitor.reset();
186                 filesystemLogMonitor.reset();
187             }
188 
189             if (noOfMetricReportSubscribers > 0U)
190             {
191                 if (!matchTelemetryMonitor)
192                 {
193                     matchTelemetryMonitor.emplace();
194                 }
195             }
196             else
197             {
198                 matchTelemetryMonitor.reset();
199             }
200         }
201         else
202         {
203             matchTelemetryMonitor.reset();
204             dbusEventLogMonitor.reset();
205             filesystemLogMonitor.reset();
206         }
207 
208         if (serviceEnabled != cfg.enabled)
209         {
210             serviceEnabled = cfg.enabled;
211             updateConfig = true;
212         }
213 
214         if (retryAttempts != cfg.retryAttempts)
215         {
216             retryAttempts = cfg.retryAttempts;
217             updateConfig = true;
218             updateRetryCfg = true;
219         }
220 
221         if (retryTimeoutInterval != cfg.retryTimeoutInterval)
222         {
223             retryTimeoutInterval = cfg.retryTimeoutInterval;
224             updateConfig = true;
225             updateRetryCfg = true;
226         }
227 
228         if (updateConfig)
229         {
230             updateSubscriptionData();
231         }
232 
233         if (updateRetryCfg)
234         {
235             // Update the changed retry config to all subscriptions
236             for (const auto& it :
237                  EventServiceManager::getInstance().subscriptionsMap)
238             {
239                 Subscription& entry = *it.second;
240                 entry.updateRetryConfig(retryAttempts, retryTimeoutInterval);
241             }
242         }
243     }
244 
updateNoOfSubscribersCount()245     void updateNoOfSubscribersCount()
246     {
247         size_t eventLogSubCount = 0;
248         size_t metricReportSubCount = 0;
249         for (const auto& it : subscriptionsMap)
250         {
251             std::shared_ptr<Subscription> entry = it.second;
252             if (entry->userSub->eventFormatType == eventFormatType)
253             {
254                 eventLogSubCount++;
255             }
256             else if (entry->userSub->eventFormatType == metricReportFormatType)
257             {
258                 metricReportSubCount++;
259             }
260         }
261         noOfEventLogSubscribers = eventLogSubCount;
262         if (eventLogSubCount > 0U)
263         {
264             if constexpr (BMCWEB_REDFISH_DBUS_LOG)
265             {
266                 if (!dbusEventLogMonitor &&
267                     BMCWEB_EXPERIMENTAL_REDFISH_DBUS_LOG_SUBSCRIPTION)
268                 {
269                     dbusEventLogMonitor.emplace();
270                 }
271             }
272             else
273             {
274                 if (!filesystemLogMonitor)
275                 {
276                     filesystemLogMonitor.emplace(getIoContext());
277                 }
278             }
279         }
280         else
281         {
282             dbusEventLogMonitor.reset();
283             filesystemLogMonitor.reset();
284         }
285 
286         noOfMetricReportSubscribers = metricReportSubCount;
287         if (metricReportSubCount > 0U)
288         {
289             if (!matchTelemetryMonitor)
290             {
291                 matchTelemetryMonitor.emplace();
292             }
293         }
294         else
295         {
296             matchTelemetryMonitor.reset();
297         }
298     }
299 
getSubscription(const std::string & id)300     std::shared_ptr<Subscription> getSubscription(const std::string& id)
301     {
302         auto obj = subscriptionsMap.find(id);
303         if (obj == subscriptionsMap.end())
304         {
305             BMCWEB_LOG_ERROR("No subscription exist with ID:{}", id);
306             return nullptr;
307         }
308         std::shared_ptr<Subscription> subValue = obj->second;
309         return subValue;
310     }
311 
addSubscriptionInternal(const std::shared_ptr<Subscription> & subValue)312     std::string addSubscriptionInternal(
313         const std::shared_ptr<Subscription>& subValue)
314     {
315         std::uniform_int_distribution<uint32_t> dist(0);
316         bmcweb::OpenSSLGenerator gen;
317 
318         std::string id;
319 
320         int retry = 3;
321         while (retry != 0)
322         {
323             id = std::to_string(dist(gen));
324             if (gen.error())
325             {
326                 retry = 0;
327                 break;
328             }
329             auto inserted = subscriptionsMap.insert(std::pair(id, subValue));
330             if (inserted.second)
331             {
332                 break;
333             }
334             --retry;
335         }
336 
337         if (retry <= 0)
338         {
339             BMCWEB_LOG_ERROR("Failed to generate random number");
340             return "";
341         }
342 
343         // Set Subscription ID for back trace
344         subValue->userSub->id = id;
345 
346         persistent_data::EventServiceStore::getInstance()
347             .subscriptionsConfigMap.emplace(id, subValue->userSub);
348 
349         updateNoOfSubscribersCount();
350 
351         // Update retry configuration.
352         subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval);
353 
354         return id;
355     }
356 
addSSESubscription(const std::shared_ptr<Subscription> & subValue,std::string_view lastEventId)357     std::string addSSESubscription(
358         const std::shared_ptr<Subscription>& subValue,
359         std::string_view lastEventId)
360     {
361         std::string id = addSubscriptionInternal(subValue);
362 
363         if (!lastEventId.empty())
364         {
365             BMCWEB_LOG_INFO("Attempting to find message for last id {}",
366                             lastEventId);
367             boost::circular_buffer<Event>::iterator lastEvent =
368                 std::ranges::find_if(
369                     messages, [&lastEventId](const Event& event) {
370                         return std::to_string(event.id) == lastEventId;
371                     });
372             // Can't find a matching ID
373             if (lastEvent == messages.end())
374             {
375                 nlohmann::json msg = messages::eventBufferExceeded();
376 
377                 std::string strMsg = msg.dump(
378                     2, ' ', true, nlohmann::json::error_handler_t::replace);
379                 eventId++;
380                 subValue->sendEventToSubscriber(eventId, std::move(strMsg));
381             }
382             else
383             {
384                 // Skip the last event the user already has
385                 lastEvent++;
386 
387                 for (boost::circular_buffer<Event>::const_iterator event =
388                          lastEvent;
389                      event != messages.end(); event++)
390                 {
391                     std::string strMsg =
392                         nlohmann::json(event->message)
393                             .dump(2, ' ', true,
394                                   nlohmann::json::error_handler_t::replace);
395 
396                     subValue->sendEventToSubscriber(event->id,
397                                                     std::move(strMsg));
398                 }
399             }
400         }
401         return id;
402     }
403 
addPushSubscription(const std::shared_ptr<Subscription> & subValue)404     std::string addPushSubscription(
405         const std::shared_ptr<Subscription>& subValue)
406     {
407         std::string id = addSubscriptionInternal(subValue);
408         subValue->deleter = [id]() {
409             EventServiceManager::getInstance().deleteSubscription(id);
410         };
411         updateSubscriptionData();
412         return id;
413     }
414 
isSubscriptionExist(const std::string & id)415     bool isSubscriptionExist(const std::string& id)
416     {
417         auto obj = subscriptionsMap.find(id);
418         return obj != subscriptionsMap.end();
419     }
420 
deleteSubscription(const std::string & id)421     bool deleteSubscription(const std::string& id)
422     {
423         auto obj = subscriptionsMap.find(id);
424         if (obj == subscriptionsMap.end())
425         {
426             BMCWEB_LOG_WARNING("Could not find subscription with id {}", id);
427             return false;
428         }
429         subscriptionsMap.erase(obj);
430         auto& event = persistent_data::EventServiceStore::getInstance();
431         auto persistentObj = event.subscriptionsConfigMap.find(id);
432         if (persistentObj == event.subscriptionsConfigMap.end())
433         {
434             BMCWEB_LOG_ERROR("Subscription {} wasn't in persistent data", id);
435             return true;
436         }
437         persistent_data::EventServiceStore::getInstance()
438             .subscriptionsConfigMap.erase(persistentObj);
439         updateNoOfSubscribersCount();
440         updateSubscriptionData();
441 
442         return true;
443     }
444 
deleteSseSubscription(const crow::sse_socket::Connection & thisConn)445     void deleteSseSubscription(const crow::sse_socket::Connection& thisConn)
446     {
447         for (auto it = subscriptionsMap.begin(); it != subscriptionsMap.end();)
448         {
449             std::shared_ptr<Subscription> entry = it->second;
450             bool entryIsThisConn = entry->matchSseId(thisConn);
451             if (entryIsThisConn)
452             {
453                 persistent_data::EventServiceStore::getInstance()
454                     .subscriptionsConfigMap.erase(entry->userSub->id);
455                 it = subscriptionsMap.erase(it);
456                 return;
457             }
458             it++;
459         }
460     }
461 
getNumberOfSubscriptions() const462     size_t getNumberOfSubscriptions() const
463     {
464         return subscriptionsMap.size();
465     }
466 
getNumberOfSSESubscriptions() const467     size_t getNumberOfSSESubscriptions() const
468     {
469         auto size = std::ranges::count_if(
470             subscriptionsMap,
471             [](const std::pair<std::string, std::shared_ptr<Subscription>>&
472                    entry) {
473                 return (entry.second->userSub->subscriptionType ==
474                         subscriptionTypeSSE);
475             });
476         return static_cast<size_t>(size);
477     }
478 
getAllIDs()479     std::vector<std::string> getAllIDs()
480     {
481         std::vector<std::string> idList;
482         for (const auto& it : subscriptionsMap)
483         {
484             idList.emplace_back(it.first);
485         }
486         return idList;
487     }
488 
sendTestEventLog(TestEvent & testEvent)489     bool sendTestEventLog(TestEvent& testEvent)
490     {
491         eventId++;
492         nlohmann::json::array_t logEntryArray;
493         nlohmann::json& logEntryJson = logEntryArray.emplace_back();
494 
495         if (testEvent.eventGroupId)
496         {
497             logEntryJson["EventGroupId"] = *testEvent.eventGroupId;
498         }
499 
500         if (testEvent.eventTimestamp)
501         {
502             logEntryJson["EventTimestamp"] = *testEvent.eventTimestamp;
503         }
504 
505         if (testEvent.originOfCondition)
506         {
507             logEntryJson["OriginOfCondition"]["@odata.id"] =
508                 *testEvent.originOfCondition;
509         }
510         if (testEvent.severity)
511         {
512             logEntryJson["Severity"] = *testEvent.severity;
513         }
514 
515         if (testEvent.message)
516         {
517             logEntryJson["Message"] = *testEvent.message;
518         }
519 
520         if (testEvent.resolution)
521         {
522             logEntryJson["Resolution"] = *testEvent.resolution;
523         }
524 
525         if (testEvent.messageId)
526         {
527             logEntryJson["MessageId"] = *testEvent.messageId;
528         }
529 
530         if (testEvent.messageArgs)
531         {
532             logEntryJson["MessageArgs"] = *testEvent.messageArgs;
533         }
534         // MemberId is 0 : since we are sending one event record.
535         logEntryJson["MemberId"] = "0";
536 
537         nlohmann::json::object_t msg;
538         msg["@odata.type"] = "#Event.v1_4_0.Event";
539         msg["Id"] = std::to_string(eventId);
540         msg["Name"] = "Event Log";
541         msg["Events"] = logEntryArray;
542 
543         std::string strMsg = nlohmann::json(msg).dump(
544             2, ' ', true, nlohmann::json::error_handler_t::replace);
545 
546         messages.push_back(Event(eventId, msg));
547         for (const auto& it : subscriptionsMap)
548         {
549             std::shared_ptr<Subscription> entry = it.second;
550             if (!entry->sendEventToSubscriber(eventId, std::string(strMsg)))
551             {
552                 return false;
553             }
554         }
555         return true;
556     }
557 
sendEventsToSubs(const std::vector<EventLogObjectsType> & eventRecords)558     static void sendEventsToSubs(
559         const std::vector<EventLogObjectsType>& eventRecords)
560     {
561         EventServiceManager& mgr = EventServiceManager::getInstance();
562         mgr.eventId++;
563         for (const auto& it : mgr.subscriptionsMap)
564         {
565             Subscription& entry = *it.second;
566             entry.filterAndSendEventLogs(mgr.eventId, eventRecords);
567         }
568     }
569 
sendTelemetryReportToSubs(const std::string & reportId,const telemetry::TimestampReadings & var)570     static void sendTelemetryReportToSubs(
571         const std::string& reportId, const telemetry::TimestampReadings& var)
572     {
573         EventServiceManager& mgr = EventServiceManager::getInstance();
574         mgr.eventId++;
575 
576         for (const auto& it : mgr.subscriptionsMap)
577         {
578             Subscription& entry = *it.second;
579             entry.filterAndSendReports(mgr.eventId, reportId, var);
580         }
581     }
582 
sendEvent(nlohmann::json::object_t eventMessage,std::string_view origin,std::string_view resourceType)583     void sendEvent(nlohmann::json::object_t eventMessage,
584                    std::string_view origin, std::string_view resourceType)
585     {
586         eventId++;
587         eventMessage["EventId"] = eventId;
588 
589         eventMessage["EventTimestamp"] =
590             redfish::time_utils::getDateTimeOffsetNow().first;
591 
592         if (!origin.empty())
593         {
594             eventMessage["OriginOfCondition"] = origin;
595         }
596 
597         // MemberId is 0 : since we are sending one event record.
598         eventMessage["MemberId"] = "0";
599 
600         messages.push_back(Event(eventId, eventMessage));
601 
602         for (auto& it : subscriptionsMap)
603         {
604             std::shared_ptr<Subscription>& entry = it.second;
605             if (!eventMatchesFilter(*entry->userSub, eventMessage,
606                                     resourceType))
607             {
608                 BMCWEB_LOG_DEBUG("Filter didn't match");
609                 continue;
610             }
611 
612             nlohmann::json::array_t eventRecord;
613             eventRecord.emplace_back(eventMessage);
614 
615             nlohmann::json msgJson;
616 
617             msgJson["@odata.type"] = "#Event.v1_4_0.Event";
618             msgJson["Name"] = "Event Log";
619             msgJson["Id"] = eventId;
620             msgJson["Events"] = std::move(eventRecord);
621 
622             std::string strMsg = msgJson.dump(
623                 2, ' ', true, nlohmann::json::error_handler_t::replace);
624             entry->sendEventToSubscriber(eventId, std::move(strMsg));
625         }
626     }
627 };
628 
629 } // namespace redfish
630