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