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