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