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_utility.hpp" 19 #include "error_messages.hpp" 20 #include "event_log.hpp" 21 #include "event_matches_filter.hpp" 22 #include "event_service_store.hpp" 23 #include "filesystem_log_watcher.hpp" 24 #include "metric_report.hpp" 25 #include "ossl_random.hpp" 26 #include "persistent_data.hpp" 27 #include "subscription.hpp" 28 #include "utility.hpp" 29 #include "utils/dbus_event_log_entry.hpp" 30 #include "utils/json_utils.hpp" 31 #include "utils/time_utils.hpp" 32 33 #include <boost/asio/io_context.hpp> 34 #include <boost/circular_buffer.hpp> 35 #include <boost/container/flat_map.hpp> 36 #include <boost/url/format.hpp> 37 #include <boost/url/url_view_base.hpp> 38 39 #include <algorithm> 40 #include <cstdlib> 41 #include <ctime> 42 #include <format> 43 #include <fstream> 44 #include <memory> 45 #include <string> 46 #include <string_view> 47 #include <utility> 48 #include <variant> 49 50 namespace redfish 51 { 52 53 static constexpr const char* eventFormatType = "Event"; 54 static constexpr const char* metricReportFormatType = "MetricReport"; 55 56 static constexpr const char* eventServiceFile = 57 "/var/lib/bmcweb/eventservice_config.json"; 58 59 static constexpr const char* redfishEventLogFile = "/var/log/redfish"; 60 61 class EventServiceManager 62 { 63 private: 64 bool serviceEnabled = false; 65 uint32_t retryAttempts = 0; 66 uint32_t retryTimeoutInterval = 0; 67 68 std::streampos redfishLogFilePosition{0}; 69 size_t noOfEventLogSubscribers{0}; 70 size_t noOfMetricReportSubscribers{0}; 71 std::optional<DbusTelemetryMonitor> matchTelemetryMonitor; 72 boost::container::flat_map<std::string, std::shared_ptr<Subscription>> 73 subscriptionsMap; 74 75 uint64_t eventId{1}; 76 77 struct Event 78 { 79 std::string id; 80 nlohmann::json message; 81 }; 82 83 constexpr static size_t maxMessages = 200; 84 boost::circular_buffer<Event> messages{maxMessages}; 85 86 boost::asio::io_context& ioc; 87 88 public: 89 EventServiceManager(const EventServiceManager&) = delete; 90 EventServiceManager& operator=(const EventServiceManager&) = delete; 91 EventServiceManager(EventServiceManager&&) = delete; 92 EventServiceManager& operator=(EventServiceManager&&) = delete; 93 ~EventServiceManager() = default; 94 95 explicit EventServiceManager(boost::asio::io_context& iocIn) : ioc(iocIn) 96 { 97 // Load config from persist store. 98 initConfig(); 99 } 100 101 static EventServiceManager& 102 getInstance(boost::asio::io_context* ioc = nullptr) 103 { 104 static EventServiceManager handler(*ioc); 105 return handler; 106 } 107 108 void initConfig() 109 { 110 loadOldBehavior(); 111 112 persistent_data::EventServiceConfig eventServiceConfig = 113 persistent_data::EventServiceStore::getInstance() 114 .getEventServiceConfig(); 115 116 serviceEnabled = eventServiceConfig.enabled; 117 retryAttempts = eventServiceConfig.retryAttempts; 118 retryTimeoutInterval = eventServiceConfig.retryTimeoutInterval; 119 120 for (const auto& it : persistent_data::EventServiceStore::getInstance() 121 .subscriptionsConfigMap) 122 { 123 std::shared_ptr<persistent_data::UserSubscription> newSub = 124 it.second; 125 126 boost::system::result<boost::urls::url> url = 127 boost::urls::parse_absolute_uri(newSub->destinationUrl); 128 129 if (!url) 130 { 131 BMCWEB_LOG_ERROR( 132 "Failed to validate and split destination url"); 133 continue; 134 } 135 std::shared_ptr<Subscription> subValue = 136 std::make_shared<Subscription>(newSub, *url, ioc); 137 std::string id = subValue->userSub->id; 138 subValue->deleter = [id]() { 139 EventServiceManager::getInstance().deleteSubscription(id); 140 }; 141 142 subscriptionsMap.emplace(id, subValue); 143 144 updateNoOfSubscribersCount(); 145 146 if constexpr (!BMCWEB_REDFISH_DBUS_LOG) 147 { 148 cacheRedfishLogFile(); 149 } 150 151 // Update retry configuration. 152 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 153 } 154 } 155 156 static void loadOldBehavior() 157 { 158 std::ifstream eventConfigFile(eventServiceFile); 159 if (!eventConfigFile.good()) 160 { 161 BMCWEB_LOG_DEBUG("Old eventService config not exist"); 162 return; 163 } 164 auto jsonData = nlohmann::json::parse(eventConfigFile, nullptr, false); 165 if (jsonData.is_discarded()) 166 { 167 BMCWEB_LOG_ERROR("Old eventService config parse error."); 168 return; 169 } 170 171 const nlohmann::json::object_t* obj = 172 jsonData.get_ptr<const nlohmann::json::object_t*>(); 173 for (const auto& item : *obj) 174 { 175 if (item.first == "Configuration") 176 { 177 persistent_data::EventServiceStore::getInstance() 178 .getEventServiceConfig() 179 .fromJson(item.second); 180 } 181 else if (item.first == "Subscriptions") 182 { 183 for (const auto& elem : item.second) 184 { 185 std::optional<persistent_data::UserSubscription> 186 newSubscription = 187 persistent_data::UserSubscription::fromJson(elem, 188 true); 189 if (!newSubscription) 190 { 191 BMCWEB_LOG_ERROR("Problem reading subscription " 192 "from old persistent store"); 193 continue; 194 } 195 persistent_data::UserSubscription& newSub = 196 *newSubscription; 197 198 std::uniform_int_distribution<uint32_t> dist(0); 199 bmcweb::OpenSSLGenerator gen; 200 201 std::string id; 202 203 int retry = 3; 204 while (retry != 0) 205 { 206 id = std::to_string(dist(gen)); 207 if (gen.error()) 208 { 209 retry = 0; 210 break; 211 } 212 newSub.id = id; 213 auto inserted = 214 persistent_data::EventServiceStore::getInstance() 215 .subscriptionsConfigMap.insert(std::pair( 216 id, std::make_shared< 217 persistent_data::UserSubscription>( 218 newSub))); 219 if (inserted.second) 220 { 221 break; 222 } 223 --retry; 224 } 225 226 if (retry <= 0) 227 { 228 BMCWEB_LOG_ERROR( 229 "Failed to generate random number from old " 230 "persistent store"); 231 continue; 232 } 233 } 234 } 235 236 persistent_data::getConfig().writeData(); 237 std::error_code ec; 238 std::filesystem::remove(eventServiceFile, ec); 239 if (ec) 240 { 241 BMCWEB_LOG_DEBUG( 242 "Failed to remove old event service file. Ignoring"); 243 } 244 else 245 { 246 BMCWEB_LOG_DEBUG("Remove old eventservice config"); 247 } 248 } 249 } 250 251 void updateSubscriptionData() const 252 { 253 persistent_data::EventServiceStore::getInstance() 254 .eventServiceConfig.enabled = serviceEnabled; 255 persistent_data::EventServiceStore::getInstance() 256 .eventServiceConfig.retryAttempts = retryAttempts; 257 persistent_data::EventServiceStore::getInstance() 258 .eventServiceConfig.retryTimeoutInterval = retryTimeoutInterval; 259 260 persistent_data::getConfig().writeData(); 261 } 262 263 void setEventServiceConfig(const persistent_data::EventServiceConfig& cfg) 264 { 265 bool updateConfig = false; 266 bool updateRetryCfg = false; 267 268 if (serviceEnabled) 269 { 270 if (noOfMetricReportSubscribers > 0U) 271 { 272 if (!matchTelemetryMonitor) 273 { 274 matchTelemetryMonitor.emplace(); 275 } 276 } 277 else 278 { 279 matchTelemetryMonitor.reset(); 280 } 281 } 282 else 283 { 284 matchTelemetryMonitor.reset(); 285 } 286 287 if (serviceEnabled != cfg.enabled) 288 { 289 serviceEnabled = cfg.enabled; 290 updateConfig = true; 291 } 292 293 if (retryAttempts != cfg.retryAttempts) 294 { 295 retryAttempts = cfg.retryAttempts; 296 updateConfig = true; 297 updateRetryCfg = true; 298 } 299 300 if (retryTimeoutInterval != cfg.retryTimeoutInterval) 301 { 302 retryTimeoutInterval = cfg.retryTimeoutInterval; 303 updateConfig = true; 304 updateRetryCfg = true; 305 } 306 307 if (updateConfig) 308 { 309 updateSubscriptionData(); 310 } 311 312 if (updateRetryCfg) 313 { 314 // Update the changed retry config to all subscriptions 315 for (const auto& it : 316 EventServiceManager::getInstance().subscriptionsMap) 317 { 318 Subscription& entry = *it.second; 319 entry.updateRetryConfig(retryAttempts, retryTimeoutInterval); 320 } 321 } 322 } 323 324 void updateNoOfSubscribersCount() 325 { 326 size_t eventLogSubCount = 0; 327 size_t metricReportSubCount = 0; 328 for (const auto& it : subscriptionsMap) 329 { 330 std::shared_ptr<Subscription> entry = it.second; 331 if (entry->userSub->eventFormatType == eventFormatType) 332 { 333 eventLogSubCount++; 334 } 335 else if (entry->userSub->eventFormatType == metricReportFormatType) 336 { 337 metricReportSubCount++; 338 } 339 } 340 341 noOfEventLogSubscribers = eventLogSubCount; 342 if (noOfMetricReportSubscribers != metricReportSubCount) 343 { 344 noOfMetricReportSubscribers = metricReportSubCount; 345 if (noOfMetricReportSubscribers != 0U) 346 { 347 if (!matchTelemetryMonitor) 348 { 349 matchTelemetryMonitor.emplace(); 350 } 351 } 352 else 353 { 354 matchTelemetryMonitor.reset(); 355 } 356 } 357 } 358 359 std::shared_ptr<Subscription> getSubscription(const std::string& id) 360 { 361 auto obj = subscriptionsMap.find(id); 362 if (obj == subscriptionsMap.end()) 363 { 364 BMCWEB_LOG_ERROR("No subscription exist with ID:{}", id); 365 return nullptr; 366 } 367 std::shared_ptr<Subscription> subValue = obj->second; 368 return subValue; 369 } 370 371 std::string 372 addSubscriptionInternal(const std::shared_ptr<Subscription>& subValue) 373 { 374 std::uniform_int_distribution<uint32_t> dist(0); 375 bmcweb::OpenSSLGenerator gen; 376 377 std::string id; 378 379 int retry = 3; 380 while (retry != 0) 381 { 382 id = std::to_string(dist(gen)); 383 if (gen.error()) 384 { 385 retry = 0; 386 break; 387 } 388 auto inserted = subscriptionsMap.insert(std::pair(id, subValue)); 389 if (inserted.second) 390 { 391 break; 392 } 393 --retry; 394 } 395 396 if (retry <= 0) 397 { 398 BMCWEB_LOG_ERROR("Failed to generate random number"); 399 return ""; 400 } 401 402 // Set Subscription ID for back trace 403 subValue->userSub->id = id; 404 405 persistent_data::EventServiceStore::getInstance() 406 .subscriptionsConfigMap.emplace(id, subValue->userSub); 407 408 updateNoOfSubscribersCount(); 409 410 if constexpr (!BMCWEB_REDFISH_DBUS_LOG) 411 { 412 if (redfishLogFilePosition != 0) 413 { 414 cacheRedfishLogFile(); 415 } 416 } 417 418 // Update retry configuration. 419 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 420 421 return id; 422 } 423 424 std::string 425 addSSESubscription(const std::shared_ptr<Subscription>& subValue, 426 std::string_view lastEventId) 427 { 428 std::string id = addSubscriptionInternal(subValue); 429 430 if (!lastEventId.empty()) 431 { 432 BMCWEB_LOG_INFO("Attempting to find message for last id {}", 433 lastEventId); 434 boost::circular_buffer<Event>::iterator lastEvent = 435 std::find_if(messages.begin(), messages.end(), 436 [&lastEventId](const Event& event) { 437 return event.id == lastEventId; 438 }); 439 // Can't find a matching ID 440 if (lastEvent == messages.end()) 441 { 442 nlohmann::json msg = messages::eventBufferExceeded(); 443 // If the buffer overloaded, send all messages. 444 subValue->sendEventToSubscriber(msg); 445 lastEvent = messages.begin(); 446 } 447 else 448 { 449 // Skip the last event the user already has 450 lastEvent++; 451 } 452 453 for (boost::circular_buffer<Event>::const_iterator event = 454 lastEvent; 455 lastEvent != messages.end(); lastEvent++) 456 { 457 subValue->sendEventToSubscriber(event->message); 458 } 459 } 460 return id; 461 } 462 463 std::string 464 addPushSubscription(const std::shared_ptr<Subscription>& subValue) 465 { 466 std::string id = addSubscriptionInternal(subValue); 467 subValue->deleter = [id]() { 468 EventServiceManager::getInstance().deleteSubscription(id); 469 }; 470 updateSubscriptionData(); 471 return id; 472 } 473 474 bool isSubscriptionExist(const std::string& id) 475 { 476 auto obj = subscriptionsMap.find(id); 477 return obj != subscriptionsMap.end(); 478 } 479 480 bool deleteSubscription(const std::string& id) 481 { 482 auto obj = subscriptionsMap.find(id); 483 if (obj == subscriptionsMap.end()) 484 { 485 BMCWEB_LOG_WARNING("Could not find subscription with id {}", id); 486 return false; 487 } 488 subscriptionsMap.erase(obj); 489 auto& event = persistent_data::EventServiceStore::getInstance(); 490 auto persistentObj = event.subscriptionsConfigMap.find(id); 491 if (persistentObj == event.subscriptionsConfigMap.end()) 492 { 493 BMCWEB_LOG_ERROR("Subscription wasn't in persistent data"); 494 return true; 495 } 496 persistent_data::EventServiceStore::getInstance() 497 .subscriptionsConfigMap.erase(persistentObj); 498 updateNoOfSubscribersCount(); 499 updateSubscriptionData(); 500 501 return true; 502 } 503 504 void deleteSseSubscription(const crow::sse_socket::Connection& thisConn) 505 { 506 for (auto it = subscriptionsMap.begin(); it != subscriptionsMap.end();) 507 { 508 std::shared_ptr<Subscription> entry = it->second; 509 bool entryIsThisConn = entry->matchSseId(thisConn); 510 if (entryIsThisConn) 511 { 512 persistent_data::EventServiceStore::getInstance() 513 .subscriptionsConfigMap.erase(entry->userSub->id); 514 it = subscriptionsMap.erase(it); 515 return; 516 } 517 it++; 518 } 519 } 520 521 size_t getNumberOfSubscriptions() const 522 { 523 return subscriptionsMap.size(); 524 } 525 526 size_t getNumberOfSSESubscriptions() const 527 { 528 auto size = std::ranges::count_if( 529 subscriptionsMap, 530 [](const std::pair<std::string, std::shared_ptr<Subscription>>& 531 entry) { 532 return (entry.second->userSub->subscriptionType == 533 subscriptionTypeSSE); 534 }); 535 return static_cast<size_t>(size); 536 } 537 538 std::vector<std::string> getAllIDs() 539 { 540 std::vector<std::string> idList; 541 for (const auto& it : subscriptionsMap) 542 { 543 idList.emplace_back(it.first); 544 } 545 return idList; 546 } 547 548 bool sendTestEventLog() 549 { 550 for (const auto& it : subscriptionsMap) 551 { 552 std::shared_ptr<Subscription> entry = it.second; 553 if (!entry->sendTestEventLog()) 554 { 555 return false; 556 } 557 } 558 return true; 559 } 560 561 static void 562 sendEventsToSubs(const std::vector<EventLogObjectsType>& eventRecords) 563 { 564 for (const auto& it : 565 EventServiceManager::getInstance().subscriptionsMap) 566 { 567 Subscription& entry = *it.second; 568 entry.filterAndSendEventLogs(eventRecords); 569 } 570 } 571 572 static void sendTelemetryReportToSubs( 573 const std::string& reportId, const telemetry::TimestampReadings& var) 574 { 575 for (const auto& it : 576 EventServiceManager::getInstance().subscriptionsMap) 577 { 578 Subscription& entry = *it.second; 579 entry.filterAndSendReports(reportId, var); 580 } 581 } 582 583 void sendEvent(nlohmann::json::object_t eventMessage, 584 std::string_view origin, std::string_view resourceType) 585 { 586 eventMessage["EventId"] = eventId; 587 588 eventMessage["EventTimestamp"] = 589 redfish::time_utils::getDateTimeOffsetNow().first; 590 eventMessage["OriginOfCondition"] = origin; 591 592 // MemberId is 0 : since we are sending one event record. 593 eventMessage["MemberId"] = "0"; 594 595 messages.push_back(Event(std::to_string(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(std::move(strMsg)); 620 eventId++; // increment the eventId 621 } 622 } 623 624 void resetRedfishFilePosition() 625 { 626 // Control would be here when Redfish file is created. 627 // Reset File Position as new file is created 628 redfishLogFilePosition = 0; 629 } 630 631 void cacheRedfishLogFile() 632 { 633 // Open the redfish file and read till the last record. 634 635 std::ifstream logStream(redfishEventLogFile); 636 if (!logStream.good()) 637 { 638 BMCWEB_LOG_ERROR(" Redfish log file open failed "); 639 return; 640 } 641 std::string logEntry; 642 while (std::getline(logStream, logEntry)) 643 { 644 redfishLogFilePosition = logStream.tellg(); 645 } 646 } 647 648 void readEventLogsFromFile() 649 { 650 std::ifstream logStream(redfishEventLogFile); 651 if (!logStream.good()) 652 { 653 BMCWEB_LOG_ERROR(" Redfish log file open failed"); 654 return; 655 } 656 657 std::vector<EventLogObjectsType> eventRecords; 658 659 std::string logEntry; 660 661 BMCWEB_LOG_DEBUG("Redfish log file: seek to {}", 662 static_cast<int>(redfishLogFilePosition)); 663 664 // Get the read pointer to the next log to be read. 665 logStream.seekg(redfishLogFilePosition); 666 667 while (std::getline(logStream, logEntry)) 668 { 669 BMCWEB_LOG_DEBUG("Redfish log file: found new event log entry"); 670 // Update Pointer position 671 redfishLogFilePosition = logStream.tellg(); 672 673 std::string idStr; 674 if (!event_log::getUniqueEntryID(logEntry, idStr)) 675 { 676 BMCWEB_LOG_DEBUG( 677 "Redfish log file: could not get unique entry id for {}", 678 logEntry); 679 continue; 680 } 681 682 if (!serviceEnabled || noOfEventLogSubscribers == 0) 683 { 684 // If Service is not enabled, no need to compute 685 // the remaining items below. 686 // But, Loop must continue to keep track of Timestamp 687 BMCWEB_LOG_DEBUG( 688 "Redfish log file: no subscribers / event service not enabled"); 689 continue; 690 } 691 692 std::string timestamp; 693 std::string messageID; 694 std::vector<std::string> messageArgs; 695 if (event_log::getEventLogParams(logEntry, timestamp, messageID, 696 messageArgs) != 0) 697 { 698 BMCWEB_LOG_DEBUG("Read eventLog entry params failed for {}", 699 logEntry); 700 continue; 701 } 702 703 eventRecords.emplace_back(idStr, timestamp, messageID, messageArgs); 704 } 705 706 if (!serviceEnabled || noOfEventLogSubscribers == 0) 707 { 708 BMCWEB_LOG_DEBUG("EventService disabled or no Subscriptions."); 709 return; 710 } 711 712 if (eventRecords.empty()) 713 { 714 // No Records to send 715 BMCWEB_LOG_DEBUG("No log entries available to be transferred."); 716 return; 717 } 718 EventServiceManager::sendEventsToSubs(eventRecords); 719 } 720 }; 721 722 } // namespace redfish 723