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