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 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 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::find_if(messages.begin(), messages.end(), 473 [&lastEventId](const Event& event) { 474 return event.id == lastEventId; 475 }); 476 // Can't find a matching ID 477 if (lastEvent == messages.end()) 478 { 479 nlohmann::json msg = messages::eventBufferExceeded(); 480 // If the buffer overloaded, send all messages. 481 subValue->sendEventToSubscriber(msg); 482 lastEvent = messages.begin(); 483 } 484 else 485 { 486 // Skip the last event the user already has 487 lastEvent++; 488 } 489 490 for (boost::circular_buffer<Event>::const_iterator event = 491 lastEvent; 492 lastEvent != messages.end(); lastEvent++) 493 { 494 subValue->sendEventToSubscriber(event->message); 495 } 496 } 497 return id; 498 } 499 addPushSubscription(const std::shared_ptr<Subscription> & subValue)500 std::string addPushSubscription( 501 const std::shared_ptr<Subscription>& subValue) 502 { 503 std::string id = addSubscriptionInternal(subValue); 504 subValue->deleter = [id]() { 505 EventServiceManager::getInstance().deleteSubscription(id); 506 }; 507 updateSubscriptionData(); 508 return id; 509 } 510 isSubscriptionExist(const std::string & id)511 bool isSubscriptionExist(const std::string& id) 512 { 513 auto obj = subscriptionsMap.find(id); 514 return obj != subscriptionsMap.end(); 515 } 516 deleteSubscription(const std::string & id)517 bool deleteSubscription(const std::string& id) 518 { 519 auto obj = subscriptionsMap.find(id); 520 if (obj == subscriptionsMap.end()) 521 { 522 BMCWEB_LOG_WARNING("Could not find subscription with id {}", id); 523 return false; 524 } 525 subscriptionsMap.erase(obj); 526 auto& event = persistent_data::EventServiceStore::getInstance(); 527 auto persistentObj = event.subscriptionsConfigMap.find(id); 528 if (persistentObj == event.subscriptionsConfigMap.end()) 529 { 530 BMCWEB_LOG_ERROR("Subscription wasn't in persistent data"); 531 return true; 532 } 533 persistent_data::EventServiceStore::getInstance() 534 .subscriptionsConfigMap.erase(persistentObj); 535 updateNoOfSubscribersCount(); 536 updateSubscriptionData(); 537 538 return true; 539 } 540 deleteSseSubscription(const crow::sse_socket::Connection & thisConn)541 void deleteSseSubscription(const crow::sse_socket::Connection& thisConn) 542 { 543 for (auto it = subscriptionsMap.begin(); it != subscriptionsMap.end();) 544 { 545 std::shared_ptr<Subscription> entry = it->second; 546 bool entryIsThisConn = entry->matchSseId(thisConn); 547 if (entryIsThisConn) 548 { 549 persistent_data::EventServiceStore::getInstance() 550 .subscriptionsConfigMap.erase(entry->userSub->id); 551 it = subscriptionsMap.erase(it); 552 return; 553 } 554 it++; 555 } 556 } 557 getNumberOfSubscriptions() const558 size_t getNumberOfSubscriptions() const 559 { 560 return subscriptionsMap.size(); 561 } 562 getNumberOfSSESubscriptions() const563 size_t getNumberOfSSESubscriptions() const 564 { 565 auto size = std::ranges::count_if( 566 subscriptionsMap, 567 [](const std::pair<std::string, std::shared_ptr<Subscription>>& 568 entry) { 569 return (entry.second->userSub->subscriptionType == 570 subscriptionTypeSSE); 571 }); 572 return static_cast<size_t>(size); 573 } 574 getAllIDs()575 std::vector<std::string> getAllIDs() 576 { 577 std::vector<std::string> idList; 578 for (const auto& it : subscriptionsMap) 579 { 580 idList.emplace_back(it.first); 581 } 582 return idList; 583 } 584 sendTestEventLog(TestEvent & testEvent)585 bool sendTestEventLog(TestEvent& testEvent) 586 { 587 for (const auto& it : subscriptionsMap) 588 { 589 std::shared_ptr<Subscription> entry = it.second; 590 if (!entry->sendTestEventLog(testEvent)) 591 { 592 return false; 593 } 594 } 595 return true; 596 } 597 sendEventsToSubs(const std::vector<EventLogObjectsType> & eventRecords)598 static void sendEventsToSubs( 599 const std::vector<EventLogObjectsType>& eventRecords) 600 { 601 for (const auto& it : 602 EventServiceManager::getInstance().subscriptionsMap) 603 { 604 Subscription& entry = *it.second; 605 entry.filterAndSendEventLogs(eventRecords); 606 } 607 } 608 sendTelemetryReportToSubs(const std::string & reportId,const telemetry::TimestampReadings & var)609 static void sendTelemetryReportToSubs( 610 const std::string& reportId, const telemetry::TimestampReadings& var) 611 { 612 for (const auto& it : 613 EventServiceManager::getInstance().subscriptionsMap) 614 { 615 Subscription& entry = *it.second; 616 entry.filterAndSendReports(reportId, var); 617 } 618 } 619 sendEvent(nlohmann::json::object_t eventMessage,std::string_view origin,std::string_view resourceType)620 void sendEvent(nlohmann::json::object_t eventMessage, 621 std::string_view origin, std::string_view resourceType) 622 { 623 eventMessage["EventId"] = eventId; 624 625 eventMessage["EventTimestamp"] = 626 redfish::time_utils::getDateTimeOffsetNow().first; 627 eventMessage["OriginOfCondition"] = origin; 628 629 // MemberId is 0 : since we are sending one event record. 630 eventMessage["MemberId"] = "0"; 631 632 messages.push_back(Event(std::to_string(eventId), eventMessage)); 633 634 for (auto& it : subscriptionsMap) 635 { 636 std::shared_ptr<Subscription>& entry = it.second; 637 if (!eventMatchesFilter(*entry->userSub, eventMessage, 638 resourceType)) 639 { 640 BMCWEB_LOG_DEBUG("Filter didn't match"); 641 continue; 642 } 643 644 nlohmann::json::array_t eventRecord; 645 eventRecord.emplace_back(eventMessage); 646 647 nlohmann::json msgJson; 648 649 msgJson["@odata.type"] = "#Event.v1_4_0.Event"; 650 msgJson["Name"] = "Event Log"; 651 msgJson["Id"] = eventId; 652 msgJson["Events"] = std::move(eventRecord); 653 654 std::string strMsg = msgJson.dump( 655 2, ' ', true, nlohmann::json::error_handler_t::replace); 656 entry->sendEventToSubscriber(std::move(strMsg)); 657 } 658 eventId++; // increment the eventId 659 } 660 }; 661 662 } // namespace redfish 663