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_utility.hpp" 18 #include "error_messages.hpp" 19 #include "event_matches_filter.hpp" 20 #include "event_service_store.hpp" 21 #include "filter_expr_executor.hpp" 22 #include "generated/enums/event.hpp" 23 #include "generated/enums/log_entry.hpp" 24 #include "http_client.hpp" 25 #include "metric_report.hpp" 26 #include "ossl_random.hpp" 27 #include "persistent_data.hpp" 28 #include "registries.hpp" 29 #include "registries_selector.hpp" 30 #include "str_utility.hpp" 31 #include "utility.hpp" 32 #include "utils/json_utils.hpp" 33 #include "utils/time_utils.hpp" 34 35 #include <sys/inotify.h> 36 37 #include <boost/asio/io_context.hpp> 38 #include <boost/circular_buffer.hpp> 39 #include <boost/container/flat_map.hpp> 40 #include <boost/url/format.hpp> 41 #include <boost/url/url_view_base.hpp> 42 #include <sdbusplus/bus/match.hpp> 43 44 #include <algorithm> 45 #include <cstdlib> 46 #include <ctime> 47 #include <format> 48 #include <fstream> 49 #include <memory> 50 #include <ranges> 51 #include <span> 52 #include <string> 53 #include <string_view> 54 55 namespace redfish 56 { 57 58 static constexpr const char* eventFormatType = "Event"; 59 static constexpr const char* metricReportFormatType = "MetricReport"; 60 61 static constexpr const char* subscriptionTypeSSE = "SSE"; 62 static constexpr const char* eventServiceFile = 63 "/var/lib/bmcweb/eventservice_config.json"; 64 65 static constexpr const uint8_t maxNoOfSubscriptions = 20; 66 static constexpr const uint8_t maxNoOfSSESubscriptions = 10; 67 68 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 69 static std::optional<boost::asio::posix::stream_descriptor> inotifyConn; 70 static constexpr const char* redfishEventLogDir = "/var/log"; 71 static constexpr const char* redfishEventLogFile = "/var/log/redfish"; 72 static constexpr const size_t iEventSize = sizeof(inotify_event); 73 74 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 75 static int inotifyFd = -1; 76 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 77 static int dirWatchDesc = -1; 78 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 79 static int fileWatchDesc = -1; 80 struct EventLogObjectsType 81 { 82 std::string id; 83 std::string timestamp; 84 std::string messageId; 85 std::vector<std::string> messageArgs; 86 }; 87 88 namespace registries 89 { 90 static const Message* 91 getMsgFromRegistry(const std::string& messageKey, 92 const std::span<const MessageEntry>& registry) 93 { 94 std::span<const MessageEntry>::iterator messageIt = std::ranges::find_if( 95 registry, [&messageKey](const MessageEntry& messageEntry) { 96 return messageKey == messageEntry.first; 97 }); 98 if (messageIt != registry.end()) 99 { 100 return &messageIt->second; 101 } 102 103 return nullptr; 104 } 105 106 static const Message* formatMessage(std::string_view messageID) 107 { 108 // Redfish MessageIds are in the form 109 // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find 110 // the right Message 111 std::vector<std::string> fields; 112 fields.reserve(4); 113 114 bmcweb::split(fields, messageID, '.'); 115 if (fields.size() != 4) 116 { 117 return nullptr; 118 } 119 const std::string& registryName = fields[0]; 120 const std::string& messageKey = fields[3]; 121 122 // Find the right registry and check it for the MessageKey 123 return getMsgFromRegistry(messageKey, getRegistryFromPrefix(registryName)); 124 } 125 } // namespace registries 126 127 namespace event_log 128 { 129 inline bool getUniqueEntryID(const std::string& logEntry, std::string& entryID) 130 { 131 static time_t prevTs = 0; 132 static int index = 0; 133 134 // Get the entry timestamp 135 std::time_t curTs = 0; 136 std::tm timeStruct = {}; 137 std::istringstream entryStream(logEntry); 138 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 139 { 140 curTs = std::mktime(&timeStruct); 141 if (curTs == -1) 142 { 143 return false; 144 } 145 } 146 // If the timestamp isn't unique, increment the index 147 index = (curTs == prevTs) ? index + 1 : 0; 148 149 // Save the timestamp 150 prevTs = curTs; 151 152 entryID = std::to_string(curTs); 153 if (index > 0) 154 { 155 entryID += "_" + std::to_string(index); 156 } 157 return true; 158 } 159 160 inline int getEventLogParams(const std::string& logEntry, 161 std::string& timestamp, std::string& messageID, 162 std::vector<std::string>& messageArgs) 163 { 164 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>" 165 // First get the Timestamp 166 size_t space = logEntry.find_first_of(' '); 167 if (space == std::string::npos) 168 { 169 BMCWEB_LOG_ERROR("EventLog Params: could not find first space: {}", 170 logEntry); 171 return -EINVAL; 172 } 173 timestamp = logEntry.substr(0, space); 174 // Then get the log contents 175 size_t entryStart = logEntry.find_first_not_of(' ', space); 176 if (entryStart == std::string::npos) 177 { 178 BMCWEB_LOG_ERROR("EventLog Params: could not find log contents: {}", 179 logEntry); 180 return -EINVAL; 181 } 182 std::string_view entry(logEntry); 183 entry.remove_prefix(entryStart); 184 // Use split to separate the entry into its fields 185 std::vector<std::string> logEntryFields; 186 bmcweb::split(logEntryFields, entry, ','); 187 // We need at least a MessageId to be valid 188 if (logEntryFields.empty()) 189 { 190 BMCWEB_LOG_ERROR("EventLog Params: could not find entry fields: {}", 191 logEntry); 192 return -EINVAL; 193 } 194 messageID = logEntryFields[0]; 195 196 // Get the MessageArgs from the log if there are any 197 if (logEntryFields.size() > 1) 198 { 199 const std::string& messageArgsStart = logEntryFields[1]; 200 // If the first string is empty, assume there are no MessageArgs 201 if (!messageArgsStart.empty()) 202 { 203 messageArgs.assign(logEntryFields.begin() + 1, 204 logEntryFields.end()); 205 } 206 } 207 208 return 0; 209 } 210 211 inline int formatEventLogEntry( 212 const std::string& logEntryID, const std::string& messageID, 213 const std::span<std::string_view> messageArgs, std::string timestamp, 214 const std::string& customText, nlohmann::json::object_t& logEntryJson) 215 { 216 // Get the Message from the MessageRegistry 217 const registries::Message* message = registries::formatMessage(messageID); 218 219 if (message == nullptr) 220 { 221 return -1; 222 } 223 224 std::string msg = 225 redfish::registries::fillMessageArgs(messageArgs, message->message); 226 if (msg.empty()) 227 { 228 return -1; 229 } 230 231 // Get the Created time from the timestamp. The log timestamp is in 232 // RFC3339 format which matches the Redfish format except for the 233 // fractional seconds between the '.' and the '+', so just remove them. 234 std::size_t dot = timestamp.find_first_of('.'); 235 std::size_t plus = timestamp.find_first_of('+', dot); 236 if (dot != std::string::npos && plus != std::string::npos) 237 { 238 timestamp.erase(dot, plus - dot); 239 } 240 241 // Fill in the log entry with the gathered data 242 logEntryJson["EventId"] = logEntryID; 243 244 logEntryJson["Severity"] = message->messageSeverity; 245 logEntryJson["Message"] = std::move(msg); 246 logEntryJson["MessageId"] = messageID; 247 logEntryJson["MessageArgs"] = messageArgs; 248 logEntryJson["EventTimestamp"] = std::move(timestamp); 249 logEntryJson["Context"] = customText; 250 return 0; 251 } 252 253 } // namespace event_log 254 255 class Subscription : public std::enable_shared_from_this<Subscription> 256 { 257 public: 258 Subscription(const Subscription&) = delete; 259 Subscription& operator=(const Subscription&) = delete; 260 Subscription(Subscription&&) = delete; 261 Subscription& operator=(Subscription&&) = delete; 262 263 Subscription(const persistent_data::UserSubscription& userSubIn, 264 const boost::urls::url_view_base& url, 265 boost::asio::io_context& ioc) : 266 userSub(userSubIn), policy(std::make_shared<crow::ConnectionPolicy>()) 267 { 268 userSub.destinationUrl = url; 269 client.emplace(ioc, policy); 270 // Subscription constructor 271 policy->invalidResp = retryRespHandler; 272 } 273 274 explicit Subscription(crow::sse_socket::Connection& connIn) : 275 sseConn(&connIn) 276 {} 277 278 ~Subscription() = default; 279 280 // callback for subscription sendData 281 void resHandler(const std::shared_ptr<Subscription>& /*unused*/, 282 const crow::Response& res) 283 { 284 BMCWEB_LOG_DEBUG("Response handled with return code: {}", 285 res.resultInt()); 286 287 if (!client) 288 { 289 BMCWEB_LOG_ERROR( 290 "Http client wasn't filled but http client callback was called."); 291 return; 292 } 293 294 if (userSub.retryPolicy != "TerminateAfterRetries") 295 { 296 return; 297 } 298 if (client->isTerminated()) 299 { 300 if (deleter) 301 { 302 BMCWEB_LOG_INFO( 303 "Subscription {} is deleted after MaxRetryAttempts", 304 userSub.id); 305 deleter(); 306 } 307 } 308 } 309 310 bool sendEventToSubscriber(std::string&& msg) 311 { 312 persistent_data::EventServiceConfig eventServiceConfig = 313 persistent_data::EventServiceStore::getInstance() 314 .getEventServiceConfig(); 315 if (!eventServiceConfig.enabled) 316 { 317 return false; 318 } 319 320 if (client) 321 { 322 client->sendDataWithCallback( 323 std::move(msg), userSub.destinationUrl, 324 static_cast<ensuressl::VerifyCertificate>( 325 userSub.verifyCertificate), 326 userSub.httpHeaders, boost::beast::http::verb::post, 327 std::bind_front(&Subscription::resHandler, this, 328 shared_from_this())); 329 return true; 330 } 331 332 if (sseConn != nullptr) 333 { 334 eventSeqNum++; 335 sseConn->sendSseEvent(std::to_string(eventSeqNum), msg); 336 } 337 return true; 338 } 339 340 bool sendTestEventLog() 341 { 342 nlohmann::json::array_t logEntryArray; 343 nlohmann::json& logEntryJson = logEntryArray.emplace_back(); 344 345 logEntryJson["EventId"] = "TestID"; 346 logEntryJson["Severity"] = log_entry::EventSeverity::OK; 347 logEntryJson["Message"] = "Generated test event"; 348 logEntryJson["MessageId"] = "OpenBMC.0.2.TestEventLog"; 349 // MemberId is 0 : since we are sending one event record. 350 logEntryJson["MemberId"] = "0"; 351 logEntryJson["MessageArgs"] = nlohmann::json::array(); 352 logEntryJson["EventTimestamp"] = 353 redfish::time_utils::getDateTimeOffsetNow().first; 354 logEntryJson["Context"] = userSub.customText; 355 356 nlohmann::json msg; 357 msg["@odata.type"] = "#Event.v1_4_0.Event"; 358 msg["Id"] = std::to_string(eventSeqNum); 359 msg["Name"] = "Event Log"; 360 msg["Events"] = logEntryArray; 361 362 std::string strMsg = 363 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 364 return sendEventToSubscriber(std::move(strMsg)); 365 } 366 367 void filterAndSendEventLogs( 368 const std::vector<EventLogObjectsType>& eventRecords) 369 { 370 nlohmann::json::array_t logEntryArray; 371 for (const EventLogObjectsType& logEntry : eventRecords) 372 { 373 std::vector<std::string_view> messageArgsView( 374 logEntry.messageArgs.begin(), logEntry.messageArgs.end()); 375 376 nlohmann::json::object_t bmcLogEntry; 377 if (event_log::formatEventLogEntry( 378 logEntry.id, logEntry.messageId, messageArgsView, 379 logEntry.timestamp, userSub.customText, bmcLogEntry) != 0) 380 { 381 BMCWEB_LOG_DEBUG("Read eventLog entry failed"); 382 continue; 383 } 384 385 if (!eventMatchesFilter(userSub, bmcLogEntry, "")) 386 { 387 BMCWEB_LOG_DEBUG("Event {} did not match the filter", 388 nlohmann::json(bmcLogEntry).dump()); 389 continue; 390 } 391 392 if (filter) 393 { 394 if (!memberMatches(bmcLogEntry, *filter)) 395 { 396 BMCWEB_LOG_DEBUG("Filter didn't match"); 397 continue; 398 } 399 } 400 401 logEntryArray.emplace_back(std::move(bmcLogEntry)); 402 } 403 404 if (logEntryArray.empty()) 405 { 406 BMCWEB_LOG_DEBUG("No log entries available to be transferred."); 407 return; 408 } 409 410 nlohmann::json msg; 411 msg["@odata.type"] = "#Event.v1_4_0.Event"; 412 msg["Id"] = std::to_string(eventSeqNum); 413 msg["Name"] = "Event Log"; 414 msg["Events"] = std::move(logEntryArray); 415 std::string strMsg = 416 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 417 sendEventToSubscriber(std::move(strMsg)); 418 eventSeqNum++; 419 } 420 421 void filterAndSendReports(const std::string& reportId, 422 const telemetry::TimestampReadings& var) 423 { 424 boost::urls::url mrdUri = boost::urls::format( 425 "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", 426 reportId); 427 428 // Empty list means no filter. Send everything. 429 if (!userSub.metricReportDefinitions.empty()) 430 { 431 if (std::ranges::find(userSub.metricReportDefinitions, 432 mrdUri.buffer()) == 433 userSub.metricReportDefinitions.end()) 434 { 435 return; 436 } 437 } 438 439 nlohmann::json msg; 440 if (!telemetry::fillReport(msg, reportId, var)) 441 { 442 BMCWEB_LOG_ERROR("Failed to fill the MetricReport for DBus " 443 "Report with id {}", 444 reportId); 445 return; 446 } 447 448 // Context is set by user during Event subscription and it must be 449 // set for MetricReport response. 450 if (!userSub.customText.empty()) 451 { 452 msg["Context"] = userSub.customText; 453 } 454 455 std::string strMsg = 456 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 457 sendEventToSubscriber(std::move(strMsg)); 458 } 459 460 void updateRetryConfig(uint32_t retryAttempts, 461 uint32_t retryTimeoutInterval) 462 { 463 if (policy == nullptr) 464 { 465 BMCWEB_LOG_DEBUG("Retry policy was nullptr, ignoring set"); 466 return; 467 } 468 policy->maxRetryAttempts = retryAttempts; 469 policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval); 470 } 471 472 uint64_t getEventSeqNum() const 473 { 474 return eventSeqNum; 475 } 476 477 void setSubscriptionId(const std::string& idIn) 478 { 479 BMCWEB_LOG_DEBUG("Subscription ID: {}", idIn); 480 userSub.id = idIn; 481 } 482 483 std::string getSubscriptionId() const 484 { 485 return userSub.id; 486 } 487 488 bool matchSseId(const crow::sse_socket::Connection& thisConn) 489 { 490 return &thisConn == sseConn; 491 } 492 493 // Check used to indicate what response codes are valid as part of our retry 494 // policy. 2XX is considered acceptable 495 static boost::system::error_code retryRespHandler(unsigned int respCode) 496 { 497 BMCWEB_LOG_DEBUG( 498 "Checking response code validity for SubscriptionEvent"); 499 if ((respCode < 200) || (respCode >= 300)) 500 { 501 return boost::system::errc::make_error_code( 502 boost::system::errc::result_out_of_range); 503 } 504 505 // Return 0 if the response code is valid 506 return boost::system::errc::make_error_code( 507 boost::system::errc::success); 508 } 509 510 persistent_data::UserSubscription userSub; 511 std::function<void()> deleter; 512 513 private: 514 uint64_t eventSeqNum = 1; 515 boost::urls::url host; 516 std::shared_ptr<crow::ConnectionPolicy> policy; 517 crow::sse_socket::Connection* sseConn = nullptr; 518 519 std::optional<crow::HttpClient> client; 520 521 public: 522 std::optional<filter_ast::LogicalAnd> filter; 523 }; 524 525 class EventServiceManager 526 { 527 private: 528 bool serviceEnabled = false; 529 uint32_t retryAttempts = 0; 530 uint32_t retryTimeoutInterval = 0; 531 532 std::streampos redfishLogFilePosition{0}; 533 size_t noOfEventLogSubscribers{0}; 534 size_t noOfMetricReportSubscribers{0}; 535 std::shared_ptr<sdbusplus::bus::match_t> matchTelemetryMonitor; 536 boost::container::flat_map<std::string, std::shared_ptr<Subscription>> 537 subscriptionsMap; 538 539 uint64_t eventId{1}; 540 541 struct Event 542 { 543 std::string id; 544 nlohmann::json message; 545 }; 546 547 constexpr static size_t maxMessages = 200; 548 boost::circular_buffer<Event> messages{maxMessages}; 549 550 boost::asio::io_context& ioc; 551 552 public: 553 EventServiceManager(const EventServiceManager&) = delete; 554 EventServiceManager& operator=(const EventServiceManager&) = delete; 555 EventServiceManager(EventServiceManager&&) = delete; 556 EventServiceManager& operator=(EventServiceManager&&) = delete; 557 ~EventServiceManager() = default; 558 559 explicit EventServiceManager(boost::asio::io_context& iocIn) : ioc(iocIn) 560 { 561 // Load config from persist store. 562 initConfig(); 563 } 564 565 static EventServiceManager& 566 getInstance(boost::asio::io_context* ioc = nullptr) 567 { 568 static EventServiceManager handler(*ioc); 569 return handler; 570 } 571 572 void initConfig() 573 { 574 loadOldBehavior(); 575 576 persistent_data::EventServiceConfig eventServiceConfig = 577 persistent_data::EventServiceStore::getInstance() 578 .getEventServiceConfig(); 579 580 serviceEnabled = eventServiceConfig.enabled; 581 retryAttempts = eventServiceConfig.retryAttempts; 582 retryTimeoutInterval = eventServiceConfig.retryTimeoutInterval; 583 584 for (const auto& it : persistent_data::EventServiceStore::getInstance() 585 .subscriptionsConfigMap) 586 { 587 const persistent_data::UserSubscription& newSub = it.second; 588 589 boost::system::result<boost::urls::url> url = 590 boost::urls::parse_absolute_uri(newSub.destinationUrl); 591 592 if (!url) 593 { 594 BMCWEB_LOG_ERROR( 595 "Failed to validate and split destination url"); 596 continue; 597 } 598 std::shared_ptr<Subscription> subValue = 599 std::make_shared<Subscription>(newSub, *url, ioc); 600 std::string id = subValue->userSub.id; 601 subValue->deleter = [id]() { 602 EventServiceManager::getInstance().deleteSubscription(id); 603 }; 604 605 subscriptionsMap.emplace(id, subValue); 606 607 updateNoOfSubscribersCount(); 608 609 if constexpr (!BMCWEB_REDFISH_DBUS_LOG) 610 { 611 cacheRedfishLogFile(); 612 } 613 614 // Update retry configuration. 615 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 616 } 617 } 618 619 static void loadOldBehavior() 620 { 621 std::ifstream eventConfigFile(eventServiceFile); 622 if (!eventConfigFile.good()) 623 { 624 BMCWEB_LOG_DEBUG("Old eventService config not exist"); 625 return; 626 } 627 auto jsonData = nlohmann::json::parse(eventConfigFile, nullptr, false); 628 if (jsonData.is_discarded()) 629 { 630 BMCWEB_LOG_ERROR("Old eventService config parse error."); 631 return; 632 } 633 634 const nlohmann::json::object_t* obj = 635 jsonData.get_ptr<const nlohmann::json::object_t*>(); 636 for (const auto& item : *obj) 637 { 638 if (item.first == "Configuration") 639 { 640 persistent_data::EventServiceStore::getInstance() 641 .getEventServiceConfig() 642 .fromJson(item.second); 643 } 644 else if (item.first == "Subscriptions") 645 { 646 for (const auto& elem : item.second) 647 { 648 std::optional<persistent_data::UserSubscription> 649 newSubscription = 650 persistent_data::UserSubscription::fromJson(elem, 651 true); 652 if (!newSubscription) 653 { 654 BMCWEB_LOG_ERROR("Problem reading subscription " 655 "from old persistent store"); 656 continue; 657 } 658 persistent_data::UserSubscription& newSub = 659 *newSubscription; 660 661 std::uniform_int_distribution<uint32_t> dist(0); 662 bmcweb::OpenSSLGenerator gen; 663 664 std::string id; 665 666 int retry = 3; 667 while (retry != 0) 668 { 669 id = std::to_string(dist(gen)); 670 if (gen.error()) 671 { 672 retry = 0; 673 break; 674 } 675 newSub.id = id; 676 auto inserted = 677 persistent_data::EventServiceStore::getInstance() 678 .subscriptionsConfigMap.insert( 679 std::pair(id, newSub)); 680 if (inserted.second) 681 { 682 break; 683 } 684 --retry; 685 } 686 687 if (retry <= 0) 688 { 689 BMCWEB_LOG_ERROR( 690 "Failed to generate random number from old " 691 "persistent store"); 692 continue; 693 } 694 } 695 } 696 697 persistent_data::getConfig().writeData(); 698 std::error_code ec; 699 std::filesystem::remove(eventServiceFile, ec); 700 if (ec) 701 { 702 BMCWEB_LOG_DEBUG( 703 "Failed to remove old event service file. Ignoring"); 704 } 705 else 706 { 707 BMCWEB_LOG_DEBUG("Remove old eventservice config"); 708 } 709 } 710 } 711 712 void updateSubscriptionData() const 713 { 714 persistent_data::EventServiceStore::getInstance() 715 .eventServiceConfig.enabled = serviceEnabled; 716 persistent_data::EventServiceStore::getInstance() 717 .eventServiceConfig.retryAttempts = retryAttempts; 718 persistent_data::EventServiceStore::getInstance() 719 .eventServiceConfig.retryTimeoutInterval = retryTimeoutInterval; 720 721 persistent_data::getConfig().writeData(); 722 } 723 724 void setEventServiceConfig(const persistent_data::EventServiceConfig& cfg) 725 { 726 bool updateConfig = false; 727 bool updateRetryCfg = false; 728 729 if (serviceEnabled != cfg.enabled) 730 { 731 serviceEnabled = cfg.enabled; 732 if (serviceEnabled && noOfMetricReportSubscribers != 0U) 733 { 734 registerMetricReportSignal(); 735 } 736 else 737 { 738 unregisterMetricReportSignal(); 739 } 740 updateConfig = true; 741 } 742 743 if (retryAttempts != cfg.retryAttempts) 744 { 745 retryAttempts = cfg.retryAttempts; 746 updateConfig = true; 747 updateRetryCfg = true; 748 } 749 750 if (retryTimeoutInterval != cfg.retryTimeoutInterval) 751 { 752 retryTimeoutInterval = cfg.retryTimeoutInterval; 753 updateConfig = true; 754 updateRetryCfg = true; 755 } 756 757 if (updateConfig) 758 { 759 updateSubscriptionData(); 760 } 761 762 if (updateRetryCfg) 763 { 764 // Update the changed retry config to all subscriptions 765 for (const auto& it : 766 EventServiceManager::getInstance().subscriptionsMap) 767 { 768 Subscription& entry = *it.second; 769 entry.updateRetryConfig(retryAttempts, retryTimeoutInterval); 770 } 771 } 772 } 773 774 void updateNoOfSubscribersCount() 775 { 776 size_t eventLogSubCount = 0; 777 size_t metricReportSubCount = 0; 778 for (const auto& it : subscriptionsMap) 779 { 780 std::shared_ptr<Subscription> entry = it.second; 781 if (entry->userSub.eventFormatType == eventFormatType) 782 { 783 eventLogSubCount++; 784 } 785 else if (entry->userSub.eventFormatType == metricReportFormatType) 786 { 787 metricReportSubCount++; 788 } 789 } 790 791 noOfEventLogSubscribers = eventLogSubCount; 792 if (noOfMetricReportSubscribers != metricReportSubCount) 793 { 794 noOfMetricReportSubscribers = metricReportSubCount; 795 if (noOfMetricReportSubscribers != 0U) 796 { 797 registerMetricReportSignal(); 798 } 799 else 800 { 801 unregisterMetricReportSignal(); 802 } 803 } 804 } 805 806 std::shared_ptr<Subscription> getSubscription(const std::string& id) 807 { 808 auto obj = subscriptionsMap.find(id); 809 if (obj == subscriptionsMap.end()) 810 { 811 BMCWEB_LOG_ERROR("No subscription exist with ID:{}", id); 812 return nullptr; 813 } 814 std::shared_ptr<Subscription> subValue = obj->second; 815 return subValue; 816 } 817 818 std::string 819 addSubscriptionInternal(const std::shared_ptr<Subscription>& subValue) 820 { 821 std::uniform_int_distribution<uint32_t> dist(0); 822 bmcweb::OpenSSLGenerator gen; 823 824 std::string id; 825 826 int retry = 3; 827 while (retry != 0) 828 { 829 id = std::to_string(dist(gen)); 830 if (gen.error()) 831 { 832 retry = 0; 833 break; 834 } 835 auto inserted = subscriptionsMap.insert(std::pair(id, subValue)); 836 if (inserted.second) 837 { 838 break; 839 } 840 --retry; 841 } 842 843 if (retry <= 0) 844 { 845 BMCWEB_LOG_ERROR("Failed to generate random number"); 846 return ""; 847 } 848 849 // Set Subscription ID for back trace 850 subValue->setSubscriptionId(id); 851 852 persistent_data::UserSubscription newSub(subValue->userSub); 853 854 persistent_data::EventServiceStore::getInstance() 855 .subscriptionsConfigMap.emplace(newSub.id, newSub); 856 857 updateNoOfSubscribersCount(); 858 859 if constexpr (!BMCWEB_REDFISH_DBUS_LOG) 860 { 861 if (redfishLogFilePosition != 0) 862 { 863 cacheRedfishLogFile(); 864 } 865 } 866 // Update retry configuration. 867 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 868 869 return id; 870 } 871 872 std::string 873 addSSESubscription(const std::shared_ptr<Subscription>& subValue, 874 std::string_view lastEventId) 875 { 876 std::string id = addSubscriptionInternal(subValue); 877 878 if (!lastEventId.empty()) 879 { 880 BMCWEB_LOG_INFO("Attempting to find message for last id {}", 881 lastEventId); 882 boost::circular_buffer<Event>::iterator lastEvent = 883 std::find_if(messages.begin(), messages.end(), 884 [&lastEventId](const Event& event) { 885 return event.id == lastEventId; 886 }); 887 // Can't find a matching ID 888 if (lastEvent == messages.end()) 889 { 890 nlohmann::json msg = messages::eventBufferExceeded(); 891 // If the buffer overloaded, send all messages. 892 subValue->sendEventToSubscriber(msg); 893 lastEvent = messages.begin(); 894 } 895 else 896 { 897 // Skip the last event the user already has 898 lastEvent++; 899 } 900 901 for (boost::circular_buffer<Event>::const_iterator event = 902 lastEvent; 903 lastEvent != messages.end(); lastEvent++) 904 { 905 subValue->sendEventToSubscriber(event->message); 906 } 907 } 908 return id; 909 } 910 911 std::string 912 addPushSubscription(const std::shared_ptr<Subscription>& subValue) 913 { 914 std::string id = addSubscriptionInternal(subValue); 915 subValue->deleter = [id]() { 916 EventServiceManager::getInstance().deleteSubscription(id); 917 }; 918 updateSubscriptionData(); 919 return id; 920 } 921 922 bool isSubscriptionExist(const std::string& id) 923 { 924 auto obj = subscriptionsMap.find(id); 925 return obj != subscriptionsMap.end(); 926 } 927 928 bool deleteSubscription(const std::string& id) 929 { 930 auto obj = subscriptionsMap.find(id); 931 if (obj == subscriptionsMap.end()) 932 { 933 BMCWEB_LOG_WARNING("Could not find subscription with id {}", id); 934 return false; 935 } 936 subscriptionsMap.erase(obj); 937 auto& event = persistent_data::EventServiceStore::getInstance(); 938 auto persistentObj = event.subscriptionsConfigMap.find(id); 939 if (persistentObj == event.subscriptionsConfigMap.end()) 940 { 941 BMCWEB_LOG_ERROR("Subscription wasn't in persistent data"); 942 return true; 943 } 944 persistent_data::EventServiceStore::getInstance() 945 .subscriptionsConfigMap.erase(persistentObj); 946 updateNoOfSubscribersCount(); 947 updateSubscriptionData(); 948 949 return true; 950 } 951 952 void deleteSseSubscription(const crow::sse_socket::Connection& thisConn) 953 { 954 for (auto it = subscriptionsMap.begin(); it != subscriptionsMap.end();) 955 { 956 std::shared_ptr<Subscription> entry = it->second; 957 bool entryIsThisConn = entry->matchSseId(thisConn); 958 if (entryIsThisConn) 959 { 960 persistent_data::EventServiceStore::getInstance() 961 .subscriptionsConfigMap.erase( 962 it->second->getSubscriptionId()); 963 it = subscriptionsMap.erase(it); 964 return; 965 } 966 it++; 967 } 968 } 969 970 size_t getNumberOfSubscriptions() const 971 { 972 return subscriptionsMap.size(); 973 } 974 975 size_t getNumberOfSSESubscriptions() const 976 { 977 auto size = std::ranges::count_if( 978 subscriptionsMap, 979 [](const std::pair<std::string, std::shared_ptr<Subscription>>& 980 entry) { 981 return (entry.second->userSub.subscriptionType == 982 subscriptionTypeSSE); 983 }); 984 return static_cast<size_t>(size); 985 } 986 987 std::vector<std::string> getAllIDs() 988 { 989 std::vector<std::string> idList; 990 for (const auto& it : subscriptionsMap) 991 { 992 idList.emplace_back(it.first); 993 } 994 return idList; 995 } 996 997 bool sendTestEventLog() 998 { 999 for (const auto& it : subscriptionsMap) 1000 { 1001 std::shared_ptr<Subscription> entry = it.second; 1002 if (!entry->sendTestEventLog()) 1003 { 1004 return false; 1005 } 1006 } 1007 return true; 1008 } 1009 1010 void sendEvent(nlohmann::json::object_t eventMessage, 1011 std::string_view origin, std::string_view resourceType) 1012 { 1013 eventMessage["EventId"] = eventId; 1014 1015 eventMessage["EventTimestamp"] = 1016 redfish::time_utils::getDateTimeOffsetNow().first; 1017 eventMessage["OriginOfCondition"] = origin; 1018 1019 // MemberId is 0 : since we are sending one event record. 1020 eventMessage["MemberId"] = "0"; 1021 1022 messages.push_back(Event(std::to_string(eventId), eventMessage)); 1023 1024 for (auto& it : subscriptionsMap) 1025 { 1026 std::shared_ptr<Subscription>& entry = it.second; 1027 if (!eventMatchesFilter(entry->userSub, eventMessage, resourceType)) 1028 { 1029 BMCWEB_LOG_DEBUG("Filter didn't match"); 1030 continue; 1031 } 1032 1033 nlohmann::json::array_t eventRecord; 1034 eventRecord.emplace_back(eventMessage); 1035 1036 nlohmann::json msgJson; 1037 1038 msgJson["@odata.type"] = "#Event.v1_4_0.Event"; 1039 msgJson["Name"] = "Event Log"; 1040 msgJson["Id"] = eventId; 1041 msgJson["Events"] = std::move(eventRecord); 1042 1043 std::string strMsg = msgJson.dump( 1044 2, ' ', true, nlohmann::json::error_handler_t::replace); 1045 entry->sendEventToSubscriber(std::move(strMsg)); 1046 eventId++; // increment the eventId 1047 } 1048 } 1049 1050 void resetRedfishFilePosition() 1051 { 1052 // Control would be here when Redfish file is created. 1053 // Reset File Position as new file is created 1054 redfishLogFilePosition = 0; 1055 } 1056 1057 void cacheRedfishLogFile() 1058 { 1059 // Open the redfish file and read till the last record. 1060 1061 std::ifstream logStream(redfishEventLogFile); 1062 if (!logStream.good()) 1063 { 1064 BMCWEB_LOG_ERROR(" Redfish log file open failed "); 1065 return; 1066 } 1067 std::string logEntry; 1068 while (std::getline(logStream, logEntry)) 1069 { 1070 redfishLogFilePosition = logStream.tellg(); 1071 } 1072 } 1073 1074 void readEventLogsFromFile() 1075 { 1076 std::ifstream logStream(redfishEventLogFile); 1077 if (!logStream.good()) 1078 { 1079 BMCWEB_LOG_ERROR(" Redfish log file open failed"); 1080 return; 1081 } 1082 1083 std::vector<EventLogObjectsType> eventRecords; 1084 1085 std::string logEntry; 1086 1087 BMCWEB_LOG_DEBUG("Redfish log file: seek to {}", 1088 static_cast<int>(redfishLogFilePosition)); 1089 1090 // Get the read pointer to the next log to be read. 1091 logStream.seekg(redfishLogFilePosition); 1092 1093 while (std::getline(logStream, logEntry)) 1094 { 1095 BMCWEB_LOG_DEBUG("Redfish log file: found new event log entry"); 1096 // Update Pointer position 1097 redfishLogFilePosition = logStream.tellg(); 1098 1099 std::string idStr; 1100 if (!event_log::getUniqueEntryID(logEntry, idStr)) 1101 { 1102 BMCWEB_LOG_DEBUG( 1103 "Redfish log file: could not get unique entry id for {}", 1104 logEntry); 1105 continue; 1106 } 1107 1108 if (!serviceEnabled || noOfEventLogSubscribers == 0) 1109 { 1110 // If Service is not enabled, no need to compute 1111 // the remaining items below. 1112 // But, Loop must continue to keep track of Timestamp 1113 BMCWEB_LOG_DEBUG( 1114 "Redfish log file: no subscribers / event service not enabled"); 1115 continue; 1116 } 1117 1118 std::string timestamp; 1119 std::string messageID; 1120 std::vector<std::string> messageArgs; 1121 if (event_log::getEventLogParams(logEntry, timestamp, messageID, 1122 messageArgs) != 0) 1123 { 1124 BMCWEB_LOG_DEBUG("Read eventLog entry params failed for {}", 1125 logEntry); 1126 continue; 1127 } 1128 1129 eventRecords.emplace_back(idStr, timestamp, messageID, messageArgs); 1130 } 1131 1132 if (!serviceEnabled || noOfEventLogSubscribers == 0) 1133 { 1134 BMCWEB_LOG_DEBUG("EventService disabled or no Subscriptions."); 1135 return; 1136 } 1137 1138 if (eventRecords.empty()) 1139 { 1140 // No Records to send 1141 BMCWEB_LOG_DEBUG("No log entries available to be transferred."); 1142 return; 1143 } 1144 1145 for (const auto& it : subscriptionsMap) 1146 { 1147 std::shared_ptr<Subscription> entry = it.second; 1148 if (entry->userSub.eventFormatType == "Event") 1149 { 1150 entry->filterAndSendEventLogs(eventRecords); 1151 } 1152 } 1153 } 1154 1155 static void watchRedfishEventLogFile() 1156 { 1157 if (!inotifyConn) 1158 { 1159 BMCWEB_LOG_ERROR("inotify Connection is not present"); 1160 return; 1161 } 1162 1163 static std::array<char, 1024> readBuffer; 1164 1165 inotifyConn->async_read_some( 1166 boost::asio::buffer(readBuffer), 1167 [&](const boost::system::error_code& ec, 1168 const std::size_t& bytesTransferred) { 1169 if (ec == boost::asio::error::operation_aborted) 1170 { 1171 BMCWEB_LOG_DEBUG("Inotify was canceled (shutdown?)"); 1172 return; 1173 } 1174 if (ec) 1175 { 1176 BMCWEB_LOG_ERROR("Callback Error: {}", ec.message()); 1177 return; 1178 } 1179 1180 BMCWEB_LOG_DEBUG("reading {} via inotify", bytesTransferred); 1181 1182 std::size_t index = 0; 1183 while ((index + iEventSize) <= bytesTransferred) 1184 { 1185 struct inotify_event event 1186 {}; 1187 std::memcpy(&event, &readBuffer[index], iEventSize); 1188 if (event.wd == dirWatchDesc) 1189 { 1190 if ((event.len == 0) || 1191 (index + iEventSize + event.len > bytesTransferred)) 1192 { 1193 index += (iEventSize + event.len); 1194 continue; 1195 } 1196 1197 std::string fileName(&readBuffer[index + iEventSize]); 1198 if (fileName != "redfish") 1199 { 1200 index += (iEventSize + event.len); 1201 continue; 1202 } 1203 1204 BMCWEB_LOG_DEBUG( 1205 "Redfish log file created/deleted. event.name: {}", 1206 fileName); 1207 if (event.mask == IN_CREATE) 1208 { 1209 if (fileWatchDesc != -1) 1210 { 1211 BMCWEB_LOG_DEBUG( 1212 "Remove and Add inotify watcher on " 1213 "redfish event log file"); 1214 // Remove existing inotify watcher and add 1215 // with new redfish event log file. 1216 inotify_rm_watch(inotifyFd, fileWatchDesc); 1217 fileWatchDesc = -1; 1218 } 1219 1220 fileWatchDesc = inotify_add_watch( 1221 inotifyFd, redfishEventLogFile, IN_MODIFY); 1222 if (fileWatchDesc == -1) 1223 { 1224 BMCWEB_LOG_ERROR("inotify_add_watch failed for " 1225 "redfish log file."); 1226 return; 1227 } 1228 1229 EventServiceManager::getInstance() 1230 .resetRedfishFilePosition(); 1231 EventServiceManager::getInstance() 1232 .readEventLogsFromFile(); 1233 } 1234 else if ((event.mask == IN_DELETE) || 1235 (event.mask == IN_MOVED_TO)) 1236 { 1237 if (fileWatchDesc != -1) 1238 { 1239 inotify_rm_watch(inotifyFd, fileWatchDesc); 1240 fileWatchDesc = -1; 1241 } 1242 } 1243 } 1244 else if (event.wd == fileWatchDesc) 1245 { 1246 if (event.mask == IN_MODIFY) 1247 { 1248 EventServiceManager::getInstance() 1249 .readEventLogsFromFile(); 1250 } 1251 } 1252 index += (iEventSize + event.len); 1253 } 1254 1255 watchRedfishEventLogFile(); 1256 }); 1257 } 1258 1259 static int startEventLogMonitor(boost::asio::io_context& ioc) 1260 { 1261 BMCWEB_LOG_DEBUG("starting Event Log Monitor"); 1262 1263 inotifyConn.emplace(ioc); 1264 inotifyFd = inotify_init1(IN_NONBLOCK); 1265 if (inotifyFd == -1) 1266 { 1267 BMCWEB_LOG_ERROR("inotify_init1 failed."); 1268 return -1; 1269 } 1270 1271 // Add watch on directory to handle redfish event log file 1272 // create/delete. 1273 dirWatchDesc = inotify_add_watch(inotifyFd, redfishEventLogDir, 1274 IN_CREATE | IN_MOVED_TO | IN_DELETE); 1275 if (dirWatchDesc == -1) 1276 { 1277 BMCWEB_LOG_ERROR( 1278 "inotify_add_watch failed for event log directory."); 1279 return -1; 1280 } 1281 1282 // Watch redfish event log file for modifications. 1283 fileWatchDesc = 1284 inotify_add_watch(inotifyFd, redfishEventLogFile, IN_MODIFY); 1285 if (fileWatchDesc == -1) 1286 { 1287 BMCWEB_LOG_ERROR("inotify_add_watch failed for redfish log file."); 1288 // Don't return error if file not exist. 1289 // Watch on directory will handle create/delete of file. 1290 } 1291 1292 // monitor redfish event log file 1293 inotifyConn->assign(inotifyFd); 1294 watchRedfishEventLogFile(); 1295 1296 return 0; 1297 } 1298 1299 static void stopEventLogMonitor() 1300 { 1301 inotifyConn.reset(); 1302 } 1303 1304 static void getReadingsForReport(sdbusplus::message_t& msg) 1305 { 1306 if (msg.is_method_error()) 1307 { 1308 BMCWEB_LOG_ERROR("TelemetryMonitor Signal error"); 1309 return; 1310 } 1311 1312 sdbusplus::message::object_path path(msg.get_path()); 1313 std::string id = path.filename(); 1314 if (id.empty()) 1315 { 1316 BMCWEB_LOG_ERROR("Failed to get Id from path"); 1317 return; 1318 } 1319 1320 std::string interface; 1321 dbus::utility::DBusPropertiesMap props; 1322 std::vector<std::string> invalidProps; 1323 msg.read(interface, props, invalidProps); 1324 1325 auto found = std::ranges::find_if(props, [](const auto& x) { 1326 return x.first == "Readings"; 1327 }); 1328 if (found == props.end()) 1329 { 1330 BMCWEB_LOG_INFO("Failed to get Readings from Report properties"); 1331 return; 1332 } 1333 1334 const telemetry::TimestampReadings* readings = 1335 std::get_if<telemetry::TimestampReadings>(&found->second); 1336 if (readings == nullptr) 1337 { 1338 BMCWEB_LOG_INFO("Failed to get Readings from Report properties"); 1339 return; 1340 } 1341 1342 for (const auto& it : 1343 EventServiceManager::getInstance().subscriptionsMap) 1344 { 1345 Subscription& entry = *it.second; 1346 if (entry.userSub.eventFormatType == metricReportFormatType) 1347 { 1348 entry.filterAndSendReports(id, *readings); 1349 } 1350 } 1351 } 1352 1353 void unregisterMetricReportSignal() 1354 { 1355 if (matchTelemetryMonitor) 1356 { 1357 BMCWEB_LOG_DEBUG("Metrics report signal - Unregister"); 1358 matchTelemetryMonitor.reset(); 1359 matchTelemetryMonitor = nullptr; 1360 } 1361 } 1362 1363 void registerMetricReportSignal() 1364 { 1365 if (!serviceEnabled || matchTelemetryMonitor) 1366 { 1367 BMCWEB_LOG_DEBUG("Not registering metric report signal."); 1368 return; 1369 } 1370 1371 BMCWEB_LOG_DEBUG("Metrics report signal - Register"); 1372 std::string matchStr = "type='signal',member='PropertiesChanged'," 1373 "interface='org.freedesktop.DBus.Properties'," 1374 "arg0=xyz.openbmc_project.Telemetry.Report"; 1375 1376 matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match_t>( 1377 *crow::connections::systemBus, matchStr, getReadingsForReport); 1378 } 1379 }; 1380 1381 } // namespace redfish 1382