xref: /openbmc/bmcweb/redfish-core/include/event_service_manager.hpp (revision 4a19a7b5e62ad2569913d944c84c19f1005c922f)
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         uint64_t id;
74         nlohmann::json::object_t 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::ranges::find_if(
473                     messages, [&lastEventId](const Event& event) {
474                         return std::to_string(event.id) == lastEventId;
475                     });
476             // Can't find a matching ID
477             if (lastEvent == messages.end())
478             {
479                 nlohmann::json msg = messages::eventBufferExceeded();
480 
481                 std::string strMsg = msg.dump(
482                     2, ' ', true, nlohmann::json::error_handler_t::replace);
483                 eventId++;
484                 subValue->sendEventToSubscriber(eventId, std::move(strMsg));
485             }
486             else
487             {
488                 // Skip the last event the user already has
489                 lastEvent++;
490 
491                 for (boost::circular_buffer<Event>::const_iterator event =
492                          lastEvent;
493                      event != messages.end(); event++)
494                 {
495                     std::string strMsg =
496                         nlohmann::json(event->message)
497                             .dump(2, ' ', true,
498                                   nlohmann::json::error_handler_t::replace);
499 
500                     subValue->sendEventToSubscriber(event->id,
501                                                     std::move(strMsg));
502                 }
503             }
504         }
505         return id;
506     }
507 
addPushSubscription(const std::shared_ptr<Subscription> & subValue)508     std::string addPushSubscription(
509         const std::shared_ptr<Subscription>& subValue)
510     {
511         std::string id = addSubscriptionInternal(subValue);
512         subValue->deleter = [id]() {
513             EventServiceManager::getInstance().deleteSubscription(id);
514         };
515         updateSubscriptionData();
516         return id;
517     }
518 
isSubscriptionExist(const std::string & id)519     bool isSubscriptionExist(const std::string& id)
520     {
521         auto obj = subscriptionsMap.find(id);
522         return obj != subscriptionsMap.end();
523     }
524 
deleteSubscription(const std::string & id)525     bool deleteSubscription(const std::string& id)
526     {
527         auto obj = subscriptionsMap.find(id);
528         if (obj == subscriptionsMap.end())
529         {
530             BMCWEB_LOG_WARNING("Could not find subscription with id {}", id);
531             return false;
532         }
533         subscriptionsMap.erase(obj);
534         auto& event = persistent_data::EventServiceStore::getInstance();
535         auto persistentObj = event.subscriptionsConfigMap.find(id);
536         if (persistentObj == event.subscriptionsConfigMap.end())
537         {
538             BMCWEB_LOG_ERROR("Subscription wasn't in persistent data");
539             return true;
540         }
541         persistent_data::EventServiceStore::getInstance()
542             .subscriptionsConfigMap.erase(persistentObj);
543         updateNoOfSubscribersCount();
544         updateSubscriptionData();
545 
546         return true;
547     }
548 
deleteSseSubscription(const crow::sse_socket::Connection & thisConn)549     void deleteSseSubscription(const crow::sse_socket::Connection& thisConn)
550     {
551         for (auto it = subscriptionsMap.begin(); it != subscriptionsMap.end();)
552         {
553             std::shared_ptr<Subscription> entry = it->second;
554             bool entryIsThisConn = entry->matchSseId(thisConn);
555             if (entryIsThisConn)
556             {
557                 persistent_data::EventServiceStore::getInstance()
558                     .subscriptionsConfigMap.erase(entry->userSub->id);
559                 it = subscriptionsMap.erase(it);
560                 return;
561             }
562             it++;
563         }
564     }
565 
getNumberOfSubscriptions() const566     size_t getNumberOfSubscriptions() const
567     {
568         return subscriptionsMap.size();
569     }
570 
getNumberOfSSESubscriptions() const571     size_t getNumberOfSSESubscriptions() const
572     {
573         auto size = std::ranges::count_if(
574             subscriptionsMap,
575             [](const std::pair<std::string, std::shared_ptr<Subscription>>&
576                    entry) {
577                 return (entry.second->userSub->subscriptionType ==
578                         subscriptionTypeSSE);
579             });
580         return static_cast<size_t>(size);
581     }
582 
getAllIDs()583     std::vector<std::string> getAllIDs()
584     {
585         std::vector<std::string> idList;
586         for (const auto& it : subscriptionsMap)
587         {
588             idList.emplace_back(it.first);
589         }
590         return idList;
591     }
592 
sendTestEventLog(TestEvent & testEvent)593     bool sendTestEventLog(TestEvent& testEvent)
594     {
595         eventId++;
596         nlohmann::json::array_t logEntryArray;
597         nlohmann::json& logEntryJson = logEntryArray.emplace_back();
598 
599         if (testEvent.eventGroupId)
600         {
601             logEntryJson["EventGroupId"] = *testEvent.eventGroupId;
602         }
603 
604         if (testEvent.eventTimestamp)
605         {
606             logEntryJson["EventTimestamp"] = *testEvent.eventTimestamp;
607         }
608 
609         if (testEvent.originOfCondition)
610         {
611             logEntryJson["OriginOfCondition"]["@odata.id"] =
612                 *testEvent.originOfCondition;
613         }
614         if (testEvent.severity)
615         {
616             logEntryJson["Severity"] = *testEvent.severity;
617         }
618 
619         if (testEvent.message)
620         {
621             logEntryJson["Message"] = *testEvent.message;
622         }
623 
624         if (testEvent.resolution)
625         {
626             logEntryJson["Resolution"] = *testEvent.resolution;
627         }
628 
629         if (testEvent.messageId)
630         {
631             logEntryJson["MessageId"] = *testEvent.messageId;
632         }
633 
634         if (testEvent.messageArgs)
635         {
636             logEntryJson["MessageArgs"] = *testEvent.messageArgs;
637         }
638         // MemberId is 0 : since we are sending one event record.
639         logEntryJson["MemberId"] = "0";
640 
641         nlohmann::json msg;
642         msg["@odata.type"] = "#Event.v1_4_0.Event";
643         msg["Id"] = std::to_string(eventId);
644         msg["Name"] = "Event Log";
645         msg["Events"] = logEntryArray;
646 
647         std::string strMsg =
648             msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace);
649 
650         messages.push_back(Event(eventId, msg));
651         for (const auto& it : subscriptionsMap)
652         {
653             std::shared_ptr<Subscription> entry = it.second;
654             if (!entry->sendEventToSubscriber(eventId, std::string(strMsg)))
655             {
656                 return false;
657             }
658         }
659         return true;
660     }
661 
sendEventsToSubs(const std::vector<EventLogObjectsType> & eventRecords)662     static void sendEventsToSubs(
663         const std::vector<EventLogObjectsType>& eventRecords)
664     {
665         EventServiceManager& mgr = EventServiceManager::getInstance();
666         mgr.eventId++;
667         for (const auto& it : mgr.subscriptionsMap)
668         {
669             Subscription& entry = *it.second;
670             entry.filterAndSendEventLogs(mgr.eventId, eventRecords);
671         }
672     }
673 
sendTelemetryReportToSubs(const std::string & reportId,const telemetry::TimestampReadings & var)674     static void sendTelemetryReportToSubs(
675         const std::string& reportId, const telemetry::TimestampReadings& var)
676     {
677         EventServiceManager& mgr = EventServiceManager::getInstance();
678         mgr.eventId++;
679 
680         for (const auto& it : mgr.subscriptionsMap)
681         {
682             Subscription& entry = *it.second;
683             entry.filterAndSendReports(mgr.eventId, reportId, var);
684         }
685     }
686 
sendEvent(nlohmann::json::object_t eventMessage,std::string_view origin,std::string_view resourceType)687     void sendEvent(nlohmann::json::object_t eventMessage,
688                    std::string_view origin, std::string_view resourceType)
689     {
690         eventId++;
691         eventMessage["EventId"] = eventId;
692 
693         eventMessage["EventTimestamp"] =
694             redfish::time_utils::getDateTimeOffsetNow().first;
695 
696         if (!origin.empty())
697         {
698             eventMessage["OriginOfCondition"] = origin;
699         }
700 
701         // MemberId is 0 : since we are sending one event record.
702         eventMessage["MemberId"] = "0";
703 
704         messages.push_back(Event(eventId, eventMessage));
705 
706         for (auto& it : subscriptionsMap)
707         {
708             std::shared_ptr<Subscription>& entry = it.second;
709             if (!eventMatchesFilter(*entry->userSub, eventMessage,
710                                     resourceType))
711             {
712                 BMCWEB_LOG_DEBUG("Filter didn't match");
713                 continue;
714             }
715 
716             nlohmann::json::array_t eventRecord;
717             eventRecord.emplace_back(eventMessage);
718 
719             nlohmann::json msgJson;
720 
721             msgJson["@odata.type"] = "#Event.v1_4_0.Event";
722             msgJson["Name"] = "Event Log";
723             msgJson["Id"] = eventId;
724             msgJson["Events"] = std::move(eventRecord);
725 
726             std::string strMsg = msgJson.dump(
727                 2, ' ', true, nlohmann::json::error_handler_t::replace);
728             entry->sendEventToSubscriber(eventId, std::move(strMsg));
729         }
730     }
731 };
732 
733 } // namespace redfish
734