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