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