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