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