xref: /openbmc/bmcweb/features/redfish/include/event_service_manager.hpp (revision b26ff34d5c35b623b0eb7de24f952d4c5d2459bf)
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_utility.hpp"
19 #include "error_messages.hpp"
20 #include "event_log.hpp"
21 #include "event_matches_filter.hpp"
22 #include "event_service_store.hpp"
23 #include "filesystem_log_watcher.hpp"
24 #include "metric_report.hpp"
25 #include "ossl_random.hpp"
26 #include "persistent_data.hpp"
27 #include "subscription.hpp"
28 #include "utils/time_utils.hpp"
29 
30 #include <sys/inotify.h>
31 
32 #include <boost/asio/io_context.hpp>
33 #include <boost/circular_buffer.hpp>
34 #include <boost/container/flat_map.hpp>
35 #include <boost/url/format.hpp>
36 #include <boost/url/url_view_base.hpp>
37 #include <sdbusplus/bus/match.hpp>
38 
39 #include <algorithm>
40 #include <cstdlib>
41 #include <ctime>
42 #include <format>
43 #include <fstream>
44 #include <memory>
45 #include <string>
46 #include <string_view>
47 #include <utility>
48 
49 namespace redfish
50 {
51 
52 static constexpr const char* eventFormatType = "Event";
53 static constexpr const char* metricReportFormatType = "MetricReport";
54 
55 static constexpr const char* eventServiceFile =
56     "/var/lib/bmcweb/eventservice_config.json";
57 
58 static constexpr const char* redfishEventLogFile = "/var/log/redfish";
59 
60 class EventServiceManager
61 {
62   private:
63     bool serviceEnabled = false;
64     uint32_t retryAttempts = 0;
65     uint32_t retryTimeoutInterval = 0;
66 
67     std::streampos redfishLogFilePosition{0};
68     size_t noOfEventLogSubscribers{0};
69     size_t noOfMetricReportSubscribers{0};
70     std::shared_ptr<sdbusplus::bus::match_t> matchTelemetryMonitor;
71     boost::container::flat_map<std::string, std::shared_ptr<Subscription>>
72         subscriptionsMap;
73 
74     uint64_t eventId{1};
75 
76     struct Event
77     {
78         std::string id;
79         nlohmann::json message;
80     };
81 
82     constexpr static size_t maxMessages = 200;
83     boost::circular_buffer<Event> messages{maxMessages};
84 
85     boost::asio::io_context& ioc;
86 
87   public:
88     EventServiceManager(const EventServiceManager&) = delete;
89     EventServiceManager& operator=(const EventServiceManager&) = delete;
90     EventServiceManager(EventServiceManager&&) = delete;
91     EventServiceManager& operator=(EventServiceManager&&) = delete;
92     ~EventServiceManager() = default;
93 
94     explicit EventServiceManager(boost::asio::io_context& iocIn) : ioc(iocIn)
95     {
96         // Load config from persist store.
97         initConfig();
98     }
99 
100     static EventServiceManager&
101         getInstance(boost::asio::io_context* ioc = nullptr)
102     {
103         static EventServiceManager handler(*ioc);
104         return handler;
105     }
106 
107     void initConfig()
108     {
109         loadOldBehavior();
110 
111         persistent_data::EventServiceConfig eventServiceConfig =
112             persistent_data::EventServiceStore::getInstance()
113                 .getEventServiceConfig();
114 
115         serviceEnabled = eventServiceConfig.enabled;
116         retryAttempts = eventServiceConfig.retryAttempts;
117         retryTimeoutInterval = eventServiceConfig.retryTimeoutInterval;
118 
119         for (const auto& it : persistent_data::EventServiceStore::getInstance()
120                                   .subscriptionsConfigMap)
121         {
122             std::shared_ptr<persistent_data::UserSubscription> newSub =
123                 it.second;
124 
125             boost::system::result<boost::urls::url> url =
126                 boost::urls::parse_absolute_uri(newSub->destinationUrl);
127 
128             if (!url)
129             {
130                 BMCWEB_LOG_ERROR(
131                     "Failed to validate and split destination url");
132                 continue;
133             }
134             std::shared_ptr<Subscription> subValue =
135                 std::make_shared<Subscription>(newSub, *url, ioc);
136             std::string id = subValue->userSub->id;
137             subValue->deleter = [id]() {
138                 EventServiceManager::getInstance().deleteSubscription(id);
139             };
140 
141             subscriptionsMap.emplace(id, subValue);
142 
143             updateNoOfSubscribersCount();
144 
145             if constexpr (!BMCWEB_REDFISH_DBUS_LOG)
146             {
147                 cacheRedfishLogFile();
148             }
149 
150             // Update retry configuration.
151             subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval);
152         }
153     }
154 
155     static void loadOldBehavior()
156     {
157         std::ifstream eventConfigFile(eventServiceFile);
158         if (!eventConfigFile.good())
159         {
160             BMCWEB_LOG_DEBUG("Old eventService config not exist");
161             return;
162         }
163         auto jsonData = nlohmann::json::parse(eventConfigFile, nullptr, false);
164         if (jsonData.is_discarded())
165         {
166             BMCWEB_LOG_ERROR("Old eventService config parse error.");
167             return;
168         }
169 
170         const nlohmann::json::object_t* obj =
171             jsonData.get_ptr<const nlohmann::json::object_t*>();
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 != cfg.enabled)
268         {
269             serviceEnabled = cfg.enabled;
270             if (serviceEnabled && noOfMetricReportSubscribers != 0U)
271             {
272                 registerMetricReportSignal();
273             }
274             else
275             {
276                 unregisterMetricReportSignal();
277             }
278             updateConfig = true;
279         }
280 
281         if (retryAttempts != cfg.retryAttempts)
282         {
283             retryAttempts = cfg.retryAttempts;
284             updateConfig = true;
285             updateRetryCfg = true;
286         }
287 
288         if (retryTimeoutInterval != cfg.retryTimeoutInterval)
289         {
290             retryTimeoutInterval = cfg.retryTimeoutInterval;
291             updateConfig = true;
292             updateRetryCfg = true;
293         }
294 
295         if (updateConfig)
296         {
297             updateSubscriptionData();
298         }
299 
300         if (updateRetryCfg)
301         {
302             // Update the changed retry config to all subscriptions
303             for (const auto& it :
304                  EventServiceManager::getInstance().subscriptionsMap)
305             {
306                 Subscription& entry = *it.second;
307                 entry.updateRetryConfig(retryAttempts, retryTimeoutInterval);
308             }
309         }
310     }
311 
312     void updateNoOfSubscribersCount()
313     {
314         size_t eventLogSubCount = 0;
315         size_t metricReportSubCount = 0;
316         for (const auto& it : subscriptionsMap)
317         {
318             std::shared_ptr<Subscription> entry = it.second;
319             if (entry->userSub->eventFormatType == eventFormatType)
320             {
321                 eventLogSubCount++;
322             }
323             else if (entry->userSub->eventFormatType == metricReportFormatType)
324             {
325                 metricReportSubCount++;
326             }
327         }
328 
329         noOfEventLogSubscribers = eventLogSubCount;
330         if (noOfMetricReportSubscribers != metricReportSubCount)
331         {
332             noOfMetricReportSubscribers = metricReportSubCount;
333             if (noOfMetricReportSubscribers != 0U)
334             {
335                 registerMetricReportSignal();
336             }
337             else
338             {
339                 unregisterMetricReportSignal();
340             }
341         }
342     }
343 
344     std::shared_ptr<Subscription> getSubscription(const std::string& id)
345     {
346         auto obj = subscriptionsMap.find(id);
347         if (obj == subscriptionsMap.end())
348         {
349             BMCWEB_LOG_ERROR("No subscription exist with ID:{}", id);
350             return nullptr;
351         }
352         std::shared_ptr<Subscription> subValue = obj->second;
353         return subValue;
354     }
355 
356     std::string
357         addSubscriptionInternal(const std::shared_ptr<Subscription>& subValue)
358     {
359         std::uniform_int_distribution<uint32_t> dist(0);
360         bmcweb::OpenSSLGenerator gen;
361 
362         std::string id;
363 
364         int retry = 3;
365         while (retry != 0)
366         {
367             id = std::to_string(dist(gen));
368             if (gen.error())
369             {
370                 retry = 0;
371                 break;
372             }
373             auto inserted = subscriptionsMap.insert(std::pair(id, subValue));
374             if (inserted.second)
375             {
376                 break;
377             }
378             --retry;
379         }
380 
381         if (retry <= 0)
382         {
383             BMCWEB_LOG_ERROR("Failed to generate random number");
384             return "";
385         }
386 
387         // Set Subscription ID for back trace
388         subValue->userSub->id = id;
389 
390         persistent_data::EventServiceStore::getInstance()
391             .subscriptionsConfigMap.emplace(id, subValue->userSub);
392 
393         updateNoOfSubscribersCount();
394 
395         if constexpr (!BMCWEB_REDFISH_DBUS_LOG)
396         {
397             if (redfishLogFilePosition != 0)
398             {
399                 cacheRedfishLogFile();
400             }
401         }
402         // Update retry configuration.
403         subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval);
404 
405         return id;
406     }
407 
408     std::string
409         addSSESubscription(const std::shared_ptr<Subscription>& subValue,
410                            std::string_view lastEventId)
411     {
412         std::string id = addSubscriptionInternal(subValue);
413 
414         if (!lastEventId.empty())
415         {
416             BMCWEB_LOG_INFO("Attempting to find message for last id {}",
417                             lastEventId);
418             boost::circular_buffer<Event>::iterator lastEvent =
419                 std::find_if(messages.begin(), messages.end(),
420                              [&lastEventId](const Event& event) {
421                                  return event.id == lastEventId;
422                              });
423             // Can't find a matching ID
424             if (lastEvent == messages.end())
425             {
426                 nlohmann::json msg = messages::eventBufferExceeded();
427                 // If the buffer overloaded, send all messages.
428                 subValue->sendEventToSubscriber(msg);
429                 lastEvent = messages.begin();
430             }
431             else
432             {
433                 // Skip the last event the user already has
434                 lastEvent++;
435             }
436 
437             for (boost::circular_buffer<Event>::const_iterator event =
438                      lastEvent;
439                  lastEvent != messages.end(); lastEvent++)
440             {
441                 subValue->sendEventToSubscriber(event->message);
442             }
443         }
444         return id;
445     }
446 
447     std::string
448         addPushSubscription(const std::shared_ptr<Subscription>& subValue)
449     {
450         std::string id = addSubscriptionInternal(subValue);
451         subValue->deleter = [id]() {
452             EventServiceManager::getInstance().deleteSubscription(id);
453         };
454         updateSubscriptionData();
455         return id;
456     }
457 
458     bool isSubscriptionExist(const std::string& id)
459     {
460         auto obj = subscriptionsMap.find(id);
461         return obj != subscriptionsMap.end();
462     }
463 
464     bool deleteSubscription(const std::string& id)
465     {
466         auto obj = subscriptionsMap.find(id);
467         if (obj == subscriptionsMap.end())
468         {
469             BMCWEB_LOG_WARNING("Could not find subscription with id {}", id);
470             return false;
471         }
472         subscriptionsMap.erase(obj);
473         auto& event = persistent_data::EventServiceStore::getInstance();
474         auto persistentObj = event.subscriptionsConfigMap.find(id);
475         if (persistentObj == event.subscriptionsConfigMap.end())
476         {
477             BMCWEB_LOG_ERROR("Subscription wasn't in persistent data");
478             return true;
479         }
480         persistent_data::EventServiceStore::getInstance()
481             .subscriptionsConfigMap.erase(persistentObj);
482         updateNoOfSubscribersCount();
483         updateSubscriptionData();
484 
485         return true;
486     }
487 
488     void deleteSseSubscription(const crow::sse_socket::Connection& thisConn)
489     {
490         for (auto it = subscriptionsMap.begin(); it != subscriptionsMap.end();)
491         {
492             std::shared_ptr<Subscription> entry = it->second;
493             bool entryIsThisConn = entry->matchSseId(thisConn);
494             if (entryIsThisConn)
495             {
496                 persistent_data::EventServiceStore::getInstance()
497                     .subscriptionsConfigMap.erase(entry->userSub->id);
498                 it = subscriptionsMap.erase(it);
499                 return;
500             }
501             it++;
502         }
503     }
504 
505     size_t getNumberOfSubscriptions() const
506     {
507         return subscriptionsMap.size();
508     }
509 
510     size_t getNumberOfSSESubscriptions() const
511     {
512         auto size = std::ranges::count_if(
513             subscriptionsMap,
514             [](const std::pair<std::string, std::shared_ptr<Subscription>>&
515                    entry) {
516                 return (entry.second->userSub->subscriptionType ==
517                         subscriptionTypeSSE);
518             });
519         return static_cast<size_t>(size);
520     }
521 
522     std::vector<std::string> getAllIDs()
523     {
524         std::vector<std::string> idList;
525         for (const auto& it : subscriptionsMap)
526         {
527             idList.emplace_back(it.first);
528         }
529         return idList;
530     }
531 
532     bool sendTestEventLog()
533     {
534         for (const auto& it : subscriptionsMap)
535         {
536             std::shared_ptr<Subscription> entry = it.second;
537             if (!entry->sendTestEventLog())
538             {
539                 return false;
540             }
541         }
542         return true;
543     }
544 
545     static void sendTelemetryReportToSubs(
546         const std::string& reportId, const telemetry::TimestampReadings& var)
547     {
548         for (const auto& it :
549              EventServiceManager::getInstance().subscriptionsMap)
550         {
551             Subscription& entry = *it.second;
552             entry.filterAndSendReports(reportId, var);
553         }
554     }
555 
556     void sendEvent(nlohmann::json::object_t eventMessage,
557                    std::string_view origin, std::string_view resourceType)
558     {
559         eventMessage["EventId"] = eventId;
560 
561         eventMessage["EventTimestamp"] =
562             redfish::time_utils::getDateTimeOffsetNow().first;
563         eventMessage["OriginOfCondition"] = origin;
564 
565         // MemberId is 0 : since we are sending one event record.
566         eventMessage["MemberId"] = "0";
567 
568         messages.push_back(Event(std::to_string(eventId), eventMessage));
569 
570         for (auto& it : subscriptionsMap)
571         {
572             std::shared_ptr<Subscription>& entry = it.second;
573             if (!eventMatchesFilter(*entry->userSub, eventMessage,
574                                     resourceType))
575             {
576                 BMCWEB_LOG_DEBUG("Filter didn't match");
577                 continue;
578             }
579 
580             nlohmann::json::array_t eventRecord;
581             eventRecord.emplace_back(eventMessage);
582 
583             nlohmann::json msgJson;
584 
585             msgJson["@odata.type"] = "#Event.v1_4_0.Event";
586             msgJson["Name"] = "Event Log";
587             msgJson["Id"] = eventId;
588             msgJson["Events"] = std::move(eventRecord);
589 
590             std::string strMsg = msgJson.dump(
591                 2, ' ', true, nlohmann::json::error_handler_t::replace);
592             entry->sendEventToSubscriber(std::move(strMsg));
593             eventId++; // increment the eventId
594         }
595     }
596 
597     void resetRedfishFilePosition()
598     {
599         // Control would be here when Redfish file is created.
600         // Reset File Position as new file is created
601         redfishLogFilePosition = 0;
602     }
603 
604     void cacheRedfishLogFile()
605     {
606         // Open the redfish file and read till the last record.
607 
608         std::ifstream logStream(redfishEventLogFile);
609         if (!logStream.good())
610         {
611             BMCWEB_LOG_ERROR(" Redfish log file open failed ");
612             return;
613         }
614         std::string logEntry;
615         while (std::getline(logStream, logEntry))
616         {
617             redfishLogFilePosition = logStream.tellg();
618         }
619     }
620 
621     void readEventLogsFromFile()
622     {
623         std::ifstream logStream(redfishEventLogFile);
624         if (!logStream.good())
625         {
626             BMCWEB_LOG_ERROR(" Redfish log file open failed");
627             return;
628         }
629 
630         std::vector<EventLogObjectsType> eventRecords;
631 
632         std::string logEntry;
633 
634         BMCWEB_LOG_DEBUG("Redfish log file: seek to {}",
635                          static_cast<int>(redfishLogFilePosition));
636 
637         // Get the read pointer to the next log to be read.
638         logStream.seekg(redfishLogFilePosition);
639 
640         while (std::getline(logStream, logEntry))
641         {
642             BMCWEB_LOG_DEBUG("Redfish log file: found new event log entry");
643             // Update Pointer position
644             redfishLogFilePosition = logStream.tellg();
645 
646             std::string idStr;
647             if (!event_log::getUniqueEntryID(logEntry, idStr))
648             {
649                 BMCWEB_LOG_DEBUG(
650                     "Redfish log file: could not get unique entry id for {}",
651                     logEntry);
652                 continue;
653             }
654 
655             if (!serviceEnabled || noOfEventLogSubscribers == 0)
656             {
657                 // If Service is not enabled, no need to compute
658                 // the remaining items below.
659                 // But, Loop must continue to keep track of Timestamp
660                 BMCWEB_LOG_DEBUG(
661                     "Redfish log file: no subscribers / event service not enabled");
662                 continue;
663             }
664 
665             std::string timestamp;
666             std::string messageID;
667             std::vector<std::string> messageArgs;
668             if (event_log::getEventLogParams(logEntry, timestamp, messageID,
669                                              messageArgs) != 0)
670             {
671                 BMCWEB_LOG_DEBUG("Read eventLog entry params failed for {}",
672                                  logEntry);
673                 continue;
674             }
675 
676             eventRecords.emplace_back(idStr, timestamp, messageID, messageArgs);
677         }
678 
679         if (!serviceEnabled || noOfEventLogSubscribers == 0)
680         {
681             BMCWEB_LOG_DEBUG("EventService disabled or no Subscriptions.");
682             return;
683         }
684 
685         if (eventRecords.empty())
686         {
687             // No Records to send
688             BMCWEB_LOG_DEBUG("No log entries available to be transferred.");
689             return;
690         }
691 
692         for (const auto& it : subscriptionsMap)
693         {
694             std::shared_ptr<Subscription> entry = it.second;
695             if (entry->userSub->eventFormatType == "Event")
696             {
697                 entry->filterAndSendEventLogs(eventRecords);
698             }
699         }
700     }
701 
702     void unregisterMetricReportSignal()
703     {
704         if (matchTelemetryMonitor)
705         {
706             BMCWEB_LOG_DEBUG("Metrics report signal - Unregister");
707             matchTelemetryMonitor.reset();
708             matchTelemetryMonitor = nullptr;
709         }
710     }
711 
712     void registerMetricReportSignal()
713     {
714         if (!serviceEnabled || matchTelemetryMonitor)
715         {
716             BMCWEB_LOG_DEBUG("Not registering metric report signal.");
717             return;
718         }
719 
720         BMCWEB_LOG_DEBUG("Metrics report signal - Register");
721         std::string matchStr = "type='signal',member='PropertiesChanged',"
722                                "interface='org.freedesktop.DBus.Properties',"
723                                "arg0=xyz.openbmc_project.Telemetry.Report";
724 
725         matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match_t>(
726             *crow::connections::systemBus, matchStr, getReadingsForReport);
727     }
728 };
729 
730 } // namespace redfish
731