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