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