1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright OpenBMC Authors 3 // SPDX-FileCopyrightText: Copyright 2020 Intel Corporation 4 #pragma once 5 #include "dbus_log_watcher.hpp" 6 #include "dbus_singleton.hpp" 7 #include "dbus_utility.hpp" 8 #include "error_messages.hpp" 9 #include "event_log.hpp" 10 #include "event_matches_filter.hpp" 11 #include "event_service_store.hpp" 12 #include "filesystem_log_watcher.hpp" 13 #include "metric_report.hpp" 14 #include "ossl_random.hpp" 15 #include "persistent_data.hpp" 16 #include "subscription.hpp" 17 #include "utility.hpp" 18 #include "utils/dbus_event_log_entry.hpp" 19 #include "utils/json_utils.hpp" 20 #include "utils/time_utils.hpp" 21 22 #include <boost/asio/io_context.hpp> 23 #include <boost/asio/steady_timer.hpp> 24 #include <boost/circular_buffer.hpp> 25 #include <boost/container/flat_map.hpp> 26 #include <boost/url/format.hpp> 27 #include <boost/url/url_view_base.hpp> 28 29 #include <algorithm> 30 #include <cstdlib> 31 #include <ctime> 32 #include <format> 33 #include <fstream> 34 #include <memory> 35 #include <string> 36 #include <string_view> 37 #include <utility> 38 #include <variant> 39 40 namespace redfish 41 { 42 43 static constexpr const char* eventFormatType = "Event"; 44 static constexpr const char* metricReportFormatType = "MetricReport"; 45 46 static constexpr const char* eventServiceFile = 47 "/var/lib/bmcweb/eventservice_config.json"; 48 49 class EventServiceManager 50 { 51 private: 52 bool serviceEnabled = false; 53 uint32_t retryAttempts = 0; 54 uint32_t retryTimeoutInterval = 0; 55 56 size_t noOfEventLogSubscribers{0}; 57 size_t noOfMetricReportSubscribers{0}; 58 std::optional<DbusEventLogMonitor> dbusEventLogMonitor; 59 std::optional<DbusTelemetryMonitor> matchTelemetryMonitor; 60 std::optional<FilesystemLogWatcher> filesystemLogMonitor; 61 boost::container::flat_map<std::string, std::shared_ptr<Subscription>> 62 subscriptionsMap; 63 64 uint64_t eventId{1}; 65 66 struct Event 67 { 68 std::string id; 69 nlohmann::json message; 70 }; 71 72 constexpr static size_t maxMessages = 200; 73 boost::circular_buffer<Event> messages{maxMessages}; 74 75 boost::asio::io_context& ioc; 76 77 public: 78 EventServiceManager(const EventServiceManager&) = delete; 79 EventServiceManager& operator=(const EventServiceManager&) = delete; 80 EventServiceManager(EventServiceManager&&) = delete; 81 EventServiceManager& operator=(EventServiceManager&&) = delete; 82 ~EventServiceManager() = default; 83 EventServiceManager(boost::asio::io_context & iocIn)84 explicit EventServiceManager(boost::asio::io_context& iocIn) : ioc(iocIn) 85 { 86 // Load config from persist store. 87 initConfig(); 88 } 89 90 static EventServiceManager& getInstance(boost::asio::io_context * ioc=nullptr)91 getInstance(boost::asio::io_context* ioc = nullptr) 92 { 93 static EventServiceManager handler(*ioc); 94 return handler; 95 } 96 initConfig()97 void initConfig() 98 { 99 loadOldBehavior(); 100 101 persistent_data::EventServiceConfig eventServiceConfig = 102 persistent_data::EventServiceStore::getInstance() 103 .getEventServiceConfig(); 104 105 serviceEnabled = eventServiceConfig.enabled; 106 retryAttempts = eventServiceConfig.retryAttempts; 107 retryTimeoutInterval = eventServiceConfig.retryTimeoutInterval; 108 109 for (const auto& it : persistent_data::EventServiceStore::getInstance() 110 .subscriptionsConfigMap) 111 { 112 std::shared_ptr<persistent_data::UserSubscription> newSub = 113 it.second; 114 115 boost::system::result<boost::urls::url> url = 116 boost::urls::parse_absolute_uri(newSub->destinationUrl); 117 118 if (!url) 119 { 120 BMCWEB_LOG_ERROR( 121 "Failed to validate and split destination url"); 122 continue; 123 } 124 std::shared_ptr<Subscription> subValue = 125 std::make_shared<Subscription>(newSub, *url, ioc); 126 std::string id = subValue->userSub->id; 127 subValue->deleter = [id]() { 128 EventServiceManager::getInstance().deleteSubscription(id); 129 }; 130 131 subscriptionsMap.emplace(id, subValue); 132 133 updateNoOfSubscribersCount(); 134 135 // Update retry configuration. 136 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 137 138 // schedule a heartbeat if sendHeartbeat was set to true 139 if (subValue->userSub->sendHeartbeat) 140 { 141 subValue->scheduleNextHeartbeatEvent(); 142 } 143 } 144 } 145 loadOldBehavior()146 static void loadOldBehavior() 147 { 148 std::ifstream eventConfigFile(eventServiceFile); 149 if (!eventConfigFile.good()) 150 { 151 BMCWEB_LOG_DEBUG("Old eventService config not exist"); 152 return; 153 } 154 auto jsonData = nlohmann::json::parse(eventConfigFile, nullptr, false); 155 if (jsonData.is_discarded()) 156 { 157 BMCWEB_LOG_ERROR("Old eventService config parse error."); 158 return; 159 } 160 161 const nlohmann::json::object_t* obj = 162 jsonData.get_ptr<const nlohmann::json::object_t*>(); 163 if (obj == nullptr) 164 { 165 return; 166 } 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(TestEvent & testEvent)583 bool sendTestEventLog(TestEvent& testEvent) 584 { 585 for (const auto& it : subscriptionsMap) 586 { 587 std::shared_ptr<Subscription> entry = it.second; 588 if (!entry->sendTestEventLog(testEvent)) 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 } 656 eventId++; // increment the eventId 657 } 658 }; 659 660 } // namespace redfish 661