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