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