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