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