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