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