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 bool matchSseId(const crow::sse_socket::Connection& thisConn) 478 { 479 return &thisConn == sseConn; 480 } 481 482 // Check used to indicate what response codes are valid as part of our retry 483 // policy. 2XX is considered acceptable 484 static boost::system::error_code retryRespHandler(unsigned int respCode) 485 { 486 BMCWEB_LOG_DEBUG( 487 "Checking response code validity for SubscriptionEvent"); 488 if ((respCode < 200) || (respCode >= 300)) 489 { 490 return boost::system::errc::make_error_code( 491 boost::system::errc::result_out_of_range); 492 } 493 494 // Return 0 if the response code is valid 495 return boost::system::errc::make_error_code( 496 boost::system::errc::success); 497 } 498 499 persistent_data::UserSubscription userSub; 500 std::function<void()> deleter; 501 502 private: 503 uint64_t eventSeqNum = 1; 504 boost::urls::url host; 505 std::shared_ptr<crow::ConnectionPolicy> policy; 506 crow::sse_socket::Connection* sseConn = nullptr; 507 508 std::optional<crow::HttpClient> client; 509 510 public: 511 std::optional<filter_ast::LogicalAnd> filter; 512 }; 513 514 class EventServiceManager 515 { 516 private: 517 bool serviceEnabled = false; 518 uint32_t retryAttempts = 0; 519 uint32_t retryTimeoutInterval = 0; 520 521 std::streampos redfishLogFilePosition{0}; 522 size_t noOfEventLogSubscribers{0}; 523 size_t noOfMetricReportSubscribers{0}; 524 std::shared_ptr<sdbusplus::bus::match_t> matchTelemetryMonitor; 525 boost::container::flat_map<std::string, std::shared_ptr<Subscription>> 526 subscriptionsMap; 527 528 uint64_t eventId{1}; 529 530 struct Event 531 { 532 std::string id; 533 nlohmann::json message; 534 }; 535 536 constexpr static size_t maxMessages = 200; 537 boost::circular_buffer<Event> messages{maxMessages}; 538 539 boost::asio::io_context& ioc; 540 541 public: 542 EventServiceManager(const EventServiceManager&) = delete; 543 EventServiceManager& operator=(const EventServiceManager&) = delete; 544 EventServiceManager(EventServiceManager&&) = delete; 545 EventServiceManager& operator=(EventServiceManager&&) = delete; 546 ~EventServiceManager() = default; 547 548 explicit EventServiceManager(boost::asio::io_context& iocIn) : ioc(iocIn) 549 { 550 // Load config from persist store. 551 initConfig(); 552 } 553 554 static EventServiceManager& 555 getInstance(boost::asio::io_context* ioc = nullptr) 556 { 557 static EventServiceManager handler(*ioc); 558 return handler; 559 } 560 561 void initConfig() 562 { 563 loadOldBehavior(); 564 565 persistent_data::EventServiceConfig eventServiceConfig = 566 persistent_data::EventServiceStore::getInstance() 567 .getEventServiceConfig(); 568 569 serviceEnabled = eventServiceConfig.enabled; 570 retryAttempts = eventServiceConfig.retryAttempts; 571 retryTimeoutInterval = eventServiceConfig.retryTimeoutInterval; 572 573 for (const auto& it : persistent_data::EventServiceStore::getInstance() 574 .subscriptionsConfigMap) 575 { 576 const persistent_data::UserSubscription& newSub = it.second; 577 578 boost::system::result<boost::urls::url> url = 579 boost::urls::parse_absolute_uri(newSub.destinationUrl); 580 581 if (!url) 582 { 583 BMCWEB_LOG_ERROR( 584 "Failed to validate and split destination url"); 585 continue; 586 } 587 std::shared_ptr<Subscription> subValue = 588 std::make_shared<Subscription>(newSub, *url, ioc); 589 std::string id = subValue->userSub.id; 590 subValue->deleter = [id]() { 591 EventServiceManager::getInstance().deleteSubscription(id); 592 }; 593 594 subscriptionsMap.emplace(id, subValue); 595 596 updateNoOfSubscribersCount(); 597 598 if constexpr (!BMCWEB_REDFISH_DBUS_LOG) 599 { 600 cacheRedfishLogFile(); 601 } 602 603 // Update retry configuration. 604 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 605 } 606 } 607 608 static void loadOldBehavior() 609 { 610 std::ifstream eventConfigFile(eventServiceFile); 611 if (!eventConfigFile.good()) 612 { 613 BMCWEB_LOG_DEBUG("Old eventService config not exist"); 614 return; 615 } 616 auto jsonData = nlohmann::json::parse(eventConfigFile, nullptr, false); 617 if (jsonData.is_discarded()) 618 { 619 BMCWEB_LOG_ERROR("Old eventService config parse error."); 620 return; 621 } 622 623 const nlohmann::json::object_t* obj = 624 jsonData.get_ptr<const nlohmann::json::object_t*>(); 625 for (const auto& item : *obj) 626 { 627 if (item.first == "Configuration") 628 { 629 persistent_data::EventServiceStore::getInstance() 630 .getEventServiceConfig() 631 .fromJson(item.second); 632 } 633 else if (item.first == "Subscriptions") 634 { 635 for (const auto& elem : item.second) 636 { 637 std::optional<persistent_data::UserSubscription> 638 newSubscription = 639 persistent_data::UserSubscription::fromJson(elem, 640 true); 641 if (!newSubscription) 642 { 643 BMCWEB_LOG_ERROR("Problem reading subscription " 644 "from old persistent store"); 645 continue; 646 } 647 persistent_data::UserSubscription& newSub = 648 *newSubscription; 649 650 std::uniform_int_distribution<uint32_t> dist(0); 651 bmcweb::OpenSSLGenerator gen; 652 653 std::string id; 654 655 int retry = 3; 656 while (retry != 0) 657 { 658 id = std::to_string(dist(gen)); 659 if (gen.error()) 660 { 661 retry = 0; 662 break; 663 } 664 newSub.id = id; 665 auto inserted = 666 persistent_data::EventServiceStore::getInstance() 667 .subscriptionsConfigMap.insert( 668 std::pair(id, newSub)); 669 if (inserted.second) 670 { 671 break; 672 } 673 --retry; 674 } 675 676 if (retry <= 0) 677 { 678 BMCWEB_LOG_ERROR( 679 "Failed to generate random number from old " 680 "persistent store"); 681 continue; 682 } 683 } 684 } 685 686 persistent_data::getConfig().writeData(); 687 std::error_code ec; 688 std::filesystem::remove(eventServiceFile, ec); 689 if (ec) 690 { 691 BMCWEB_LOG_DEBUG( 692 "Failed to remove old event service file. Ignoring"); 693 } 694 else 695 { 696 BMCWEB_LOG_DEBUG("Remove old eventservice config"); 697 } 698 } 699 } 700 701 void updateSubscriptionData() const 702 { 703 persistent_data::EventServiceStore::getInstance() 704 .eventServiceConfig.enabled = serviceEnabled; 705 persistent_data::EventServiceStore::getInstance() 706 .eventServiceConfig.retryAttempts = retryAttempts; 707 persistent_data::EventServiceStore::getInstance() 708 .eventServiceConfig.retryTimeoutInterval = retryTimeoutInterval; 709 710 persistent_data::getConfig().writeData(); 711 } 712 713 void setEventServiceConfig(const persistent_data::EventServiceConfig& cfg) 714 { 715 bool updateConfig = false; 716 bool updateRetryCfg = false; 717 718 if (serviceEnabled != cfg.enabled) 719 { 720 serviceEnabled = cfg.enabled; 721 if (serviceEnabled && noOfMetricReportSubscribers != 0U) 722 { 723 registerMetricReportSignal(); 724 } 725 else 726 { 727 unregisterMetricReportSignal(); 728 } 729 updateConfig = true; 730 } 731 732 if (retryAttempts != cfg.retryAttempts) 733 { 734 retryAttempts = cfg.retryAttempts; 735 updateConfig = true; 736 updateRetryCfg = true; 737 } 738 739 if (retryTimeoutInterval != cfg.retryTimeoutInterval) 740 { 741 retryTimeoutInterval = cfg.retryTimeoutInterval; 742 updateConfig = true; 743 updateRetryCfg = true; 744 } 745 746 if (updateConfig) 747 { 748 updateSubscriptionData(); 749 } 750 751 if (updateRetryCfg) 752 { 753 // Update the changed retry config to all subscriptions 754 for (const auto& it : 755 EventServiceManager::getInstance().subscriptionsMap) 756 { 757 Subscription& entry = *it.second; 758 entry.updateRetryConfig(retryAttempts, retryTimeoutInterval); 759 } 760 } 761 } 762 763 void updateNoOfSubscribersCount() 764 { 765 size_t eventLogSubCount = 0; 766 size_t metricReportSubCount = 0; 767 for (const auto& it : subscriptionsMap) 768 { 769 std::shared_ptr<Subscription> entry = it.second; 770 if (entry->userSub.eventFormatType == eventFormatType) 771 { 772 eventLogSubCount++; 773 } 774 else if (entry->userSub.eventFormatType == metricReportFormatType) 775 { 776 metricReportSubCount++; 777 } 778 } 779 780 noOfEventLogSubscribers = eventLogSubCount; 781 if (noOfMetricReportSubscribers != metricReportSubCount) 782 { 783 noOfMetricReportSubscribers = metricReportSubCount; 784 if (noOfMetricReportSubscribers != 0U) 785 { 786 registerMetricReportSignal(); 787 } 788 else 789 { 790 unregisterMetricReportSignal(); 791 } 792 } 793 } 794 795 std::shared_ptr<Subscription> getSubscription(const std::string& id) 796 { 797 auto obj = subscriptionsMap.find(id); 798 if (obj == subscriptionsMap.end()) 799 { 800 BMCWEB_LOG_ERROR("No subscription exist with ID:{}", id); 801 return nullptr; 802 } 803 std::shared_ptr<Subscription> subValue = obj->second; 804 return subValue; 805 } 806 807 std::string 808 addSubscriptionInternal(const std::shared_ptr<Subscription>& subValue) 809 { 810 std::uniform_int_distribution<uint32_t> dist(0); 811 bmcweb::OpenSSLGenerator gen; 812 813 std::string id; 814 815 int retry = 3; 816 while (retry != 0) 817 { 818 id = std::to_string(dist(gen)); 819 if (gen.error()) 820 { 821 retry = 0; 822 break; 823 } 824 auto inserted = subscriptionsMap.insert(std::pair(id, subValue)); 825 if (inserted.second) 826 { 827 break; 828 } 829 --retry; 830 } 831 832 if (retry <= 0) 833 { 834 BMCWEB_LOG_ERROR("Failed to generate random number"); 835 return ""; 836 } 837 838 // Set Subscription ID for back trace 839 subValue->userSub.id = id; 840 841 persistent_data::UserSubscription newSub(subValue->userSub); 842 843 persistent_data::EventServiceStore::getInstance() 844 .subscriptionsConfigMap.emplace(newSub.id, newSub); 845 846 updateNoOfSubscribersCount(); 847 848 if constexpr (!BMCWEB_REDFISH_DBUS_LOG) 849 { 850 if (redfishLogFilePosition != 0) 851 { 852 cacheRedfishLogFile(); 853 } 854 } 855 // Update retry configuration. 856 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 857 858 return id; 859 } 860 861 std::string 862 addSSESubscription(const std::shared_ptr<Subscription>& subValue, 863 std::string_view lastEventId) 864 { 865 std::string id = addSubscriptionInternal(subValue); 866 867 if (!lastEventId.empty()) 868 { 869 BMCWEB_LOG_INFO("Attempting to find message for last id {}", 870 lastEventId); 871 boost::circular_buffer<Event>::iterator lastEvent = 872 std::find_if(messages.begin(), messages.end(), 873 [&lastEventId](const Event& event) { 874 return event.id == lastEventId; 875 }); 876 // Can't find a matching ID 877 if (lastEvent == messages.end()) 878 { 879 nlohmann::json msg = messages::eventBufferExceeded(); 880 // If the buffer overloaded, send all messages. 881 subValue->sendEventToSubscriber(msg); 882 lastEvent = messages.begin(); 883 } 884 else 885 { 886 // Skip the last event the user already has 887 lastEvent++; 888 } 889 890 for (boost::circular_buffer<Event>::const_iterator event = 891 lastEvent; 892 lastEvent != messages.end(); lastEvent++) 893 { 894 subValue->sendEventToSubscriber(event->message); 895 } 896 } 897 return id; 898 } 899 900 std::string 901 addPushSubscription(const std::shared_ptr<Subscription>& subValue) 902 { 903 std::string id = addSubscriptionInternal(subValue); 904 subValue->deleter = [id]() { 905 EventServiceManager::getInstance().deleteSubscription(id); 906 }; 907 updateSubscriptionData(); 908 return id; 909 } 910 911 bool isSubscriptionExist(const std::string& id) 912 { 913 auto obj = subscriptionsMap.find(id); 914 return obj != subscriptionsMap.end(); 915 } 916 917 bool deleteSubscription(const std::string& id) 918 { 919 auto obj = subscriptionsMap.find(id); 920 if (obj == subscriptionsMap.end()) 921 { 922 BMCWEB_LOG_WARNING("Could not find subscription with id {}", id); 923 return false; 924 } 925 subscriptionsMap.erase(obj); 926 auto& event = persistent_data::EventServiceStore::getInstance(); 927 auto persistentObj = event.subscriptionsConfigMap.find(id); 928 if (persistentObj == event.subscriptionsConfigMap.end()) 929 { 930 BMCWEB_LOG_ERROR("Subscription wasn't in persistent data"); 931 return true; 932 } 933 persistent_data::EventServiceStore::getInstance() 934 .subscriptionsConfigMap.erase(persistentObj); 935 updateNoOfSubscribersCount(); 936 updateSubscriptionData(); 937 938 return true; 939 } 940 941 void deleteSseSubscription(const crow::sse_socket::Connection& thisConn) 942 { 943 for (auto it = subscriptionsMap.begin(); it != subscriptionsMap.end();) 944 { 945 std::shared_ptr<Subscription> entry = it->second; 946 bool entryIsThisConn = entry->matchSseId(thisConn); 947 if (entryIsThisConn) 948 { 949 persistent_data::EventServiceStore::getInstance() 950 .subscriptionsConfigMap.erase(entry->userSub.id); 951 it = subscriptionsMap.erase(it); 952 return; 953 } 954 it++; 955 } 956 } 957 958 size_t getNumberOfSubscriptions() const 959 { 960 return subscriptionsMap.size(); 961 } 962 963 size_t getNumberOfSSESubscriptions() const 964 { 965 auto size = std::ranges::count_if( 966 subscriptionsMap, 967 [](const std::pair<std::string, std::shared_ptr<Subscription>>& 968 entry) { 969 return (entry.second->userSub.subscriptionType == 970 subscriptionTypeSSE); 971 }); 972 return static_cast<size_t>(size); 973 } 974 975 std::vector<std::string> getAllIDs() 976 { 977 std::vector<std::string> idList; 978 for (const auto& it : subscriptionsMap) 979 { 980 idList.emplace_back(it.first); 981 } 982 return idList; 983 } 984 985 bool sendTestEventLog() 986 { 987 for (const auto& it : subscriptionsMap) 988 { 989 std::shared_ptr<Subscription> entry = it.second; 990 if (!entry->sendTestEventLog()) 991 { 992 return false; 993 } 994 } 995 return true; 996 } 997 998 void sendEvent(nlohmann::json::object_t eventMessage, 999 std::string_view origin, std::string_view resourceType) 1000 { 1001 eventMessage["EventId"] = eventId; 1002 1003 eventMessage["EventTimestamp"] = 1004 redfish::time_utils::getDateTimeOffsetNow().first; 1005 eventMessage["OriginOfCondition"] = origin; 1006 1007 // MemberId is 0 : since we are sending one event record. 1008 eventMessage["MemberId"] = "0"; 1009 1010 messages.push_back(Event(std::to_string(eventId), eventMessage)); 1011 1012 for (auto& it : subscriptionsMap) 1013 { 1014 std::shared_ptr<Subscription>& entry = it.second; 1015 if (!eventMatchesFilter(entry->userSub, eventMessage, resourceType)) 1016 { 1017 BMCWEB_LOG_DEBUG("Filter didn't match"); 1018 continue; 1019 } 1020 1021 nlohmann::json::array_t eventRecord; 1022 eventRecord.emplace_back(eventMessage); 1023 1024 nlohmann::json msgJson; 1025 1026 msgJson["@odata.type"] = "#Event.v1_4_0.Event"; 1027 msgJson["Name"] = "Event Log"; 1028 msgJson["Id"] = eventId; 1029 msgJson["Events"] = std::move(eventRecord); 1030 1031 std::string strMsg = msgJson.dump( 1032 2, ' ', true, nlohmann::json::error_handler_t::replace); 1033 entry->sendEventToSubscriber(std::move(strMsg)); 1034 eventId++; // increment the eventId 1035 } 1036 } 1037 1038 void resetRedfishFilePosition() 1039 { 1040 // Control would be here when Redfish file is created. 1041 // Reset File Position as new file is created 1042 redfishLogFilePosition = 0; 1043 } 1044 1045 void cacheRedfishLogFile() 1046 { 1047 // Open the redfish file and read till the last record. 1048 1049 std::ifstream logStream(redfishEventLogFile); 1050 if (!logStream.good()) 1051 { 1052 BMCWEB_LOG_ERROR(" Redfish log file open failed "); 1053 return; 1054 } 1055 std::string logEntry; 1056 while (std::getline(logStream, logEntry)) 1057 { 1058 redfishLogFilePosition = logStream.tellg(); 1059 } 1060 } 1061 1062 void readEventLogsFromFile() 1063 { 1064 std::ifstream logStream(redfishEventLogFile); 1065 if (!logStream.good()) 1066 { 1067 BMCWEB_LOG_ERROR(" Redfish log file open failed"); 1068 return; 1069 } 1070 1071 std::vector<EventLogObjectsType> eventRecords; 1072 1073 std::string logEntry; 1074 1075 BMCWEB_LOG_DEBUG("Redfish log file: seek to {}", 1076 static_cast<int>(redfishLogFilePosition)); 1077 1078 // Get the read pointer to the next log to be read. 1079 logStream.seekg(redfishLogFilePosition); 1080 1081 while (std::getline(logStream, logEntry)) 1082 { 1083 BMCWEB_LOG_DEBUG("Redfish log file: found new event log entry"); 1084 // Update Pointer position 1085 redfishLogFilePosition = logStream.tellg(); 1086 1087 std::string idStr; 1088 if (!event_log::getUniqueEntryID(logEntry, idStr)) 1089 { 1090 BMCWEB_LOG_DEBUG( 1091 "Redfish log file: could not get unique entry id for {}", 1092 logEntry); 1093 continue; 1094 } 1095 1096 if (!serviceEnabled || noOfEventLogSubscribers == 0) 1097 { 1098 // If Service is not enabled, no need to compute 1099 // the remaining items below. 1100 // But, Loop must continue to keep track of Timestamp 1101 BMCWEB_LOG_DEBUG( 1102 "Redfish log file: no subscribers / event service not enabled"); 1103 continue; 1104 } 1105 1106 std::string timestamp; 1107 std::string messageID; 1108 std::vector<std::string> messageArgs; 1109 if (event_log::getEventLogParams(logEntry, timestamp, messageID, 1110 messageArgs) != 0) 1111 { 1112 BMCWEB_LOG_DEBUG("Read eventLog entry params failed for {}", 1113 logEntry); 1114 continue; 1115 } 1116 1117 eventRecords.emplace_back(idStr, timestamp, messageID, messageArgs); 1118 } 1119 1120 if (!serviceEnabled || noOfEventLogSubscribers == 0) 1121 { 1122 BMCWEB_LOG_DEBUG("EventService disabled or no Subscriptions."); 1123 return; 1124 } 1125 1126 if (eventRecords.empty()) 1127 { 1128 // No Records to send 1129 BMCWEB_LOG_DEBUG("No log entries available to be transferred."); 1130 return; 1131 } 1132 1133 for (const auto& it : subscriptionsMap) 1134 { 1135 std::shared_ptr<Subscription> entry = it.second; 1136 if (entry->userSub.eventFormatType == "Event") 1137 { 1138 entry->filterAndSendEventLogs(eventRecords); 1139 } 1140 } 1141 } 1142 1143 static void watchRedfishEventLogFile() 1144 { 1145 if (!inotifyConn) 1146 { 1147 BMCWEB_LOG_ERROR("inotify Connection is not present"); 1148 return; 1149 } 1150 1151 static std::array<char, 1024> readBuffer; 1152 1153 inotifyConn->async_read_some( 1154 boost::asio::buffer(readBuffer), 1155 [&](const boost::system::error_code& ec, 1156 const std::size_t& bytesTransferred) { 1157 if (ec == boost::asio::error::operation_aborted) 1158 { 1159 BMCWEB_LOG_DEBUG("Inotify was canceled (shutdown?)"); 1160 return; 1161 } 1162 if (ec) 1163 { 1164 BMCWEB_LOG_ERROR("Callback Error: {}", ec.message()); 1165 return; 1166 } 1167 1168 BMCWEB_LOG_DEBUG("reading {} via inotify", bytesTransferred); 1169 1170 std::size_t index = 0; 1171 while ((index + iEventSize) <= bytesTransferred) 1172 { 1173 struct inotify_event event 1174 {}; 1175 std::memcpy(&event, &readBuffer[index], iEventSize); 1176 if (event.wd == dirWatchDesc) 1177 { 1178 if ((event.len == 0) || 1179 (index + iEventSize + event.len > bytesTransferred)) 1180 { 1181 index += (iEventSize + event.len); 1182 continue; 1183 } 1184 1185 std::string fileName(&readBuffer[index + iEventSize]); 1186 if (fileName != "redfish") 1187 { 1188 index += (iEventSize + event.len); 1189 continue; 1190 } 1191 1192 BMCWEB_LOG_DEBUG( 1193 "Redfish log file created/deleted. event.name: {}", 1194 fileName); 1195 if (event.mask == IN_CREATE) 1196 { 1197 if (fileWatchDesc != -1) 1198 { 1199 BMCWEB_LOG_DEBUG( 1200 "Remove and Add inotify watcher on " 1201 "redfish event log file"); 1202 // Remove existing inotify watcher and add 1203 // with new redfish event log file. 1204 inotify_rm_watch(inotifyFd, fileWatchDesc); 1205 fileWatchDesc = -1; 1206 } 1207 1208 fileWatchDesc = inotify_add_watch( 1209 inotifyFd, redfishEventLogFile, IN_MODIFY); 1210 if (fileWatchDesc == -1) 1211 { 1212 BMCWEB_LOG_ERROR("inotify_add_watch failed for " 1213 "redfish log file."); 1214 return; 1215 } 1216 1217 EventServiceManager::getInstance() 1218 .resetRedfishFilePosition(); 1219 EventServiceManager::getInstance() 1220 .readEventLogsFromFile(); 1221 } 1222 else if ((event.mask == IN_DELETE) || 1223 (event.mask == IN_MOVED_TO)) 1224 { 1225 if (fileWatchDesc != -1) 1226 { 1227 inotify_rm_watch(inotifyFd, fileWatchDesc); 1228 fileWatchDesc = -1; 1229 } 1230 } 1231 } 1232 else if (event.wd == fileWatchDesc) 1233 { 1234 if (event.mask == IN_MODIFY) 1235 { 1236 EventServiceManager::getInstance() 1237 .readEventLogsFromFile(); 1238 } 1239 } 1240 index += (iEventSize + event.len); 1241 } 1242 1243 watchRedfishEventLogFile(); 1244 }); 1245 } 1246 1247 static int startEventLogMonitor(boost::asio::io_context& ioc) 1248 { 1249 BMCWEB_LOG_DEBUG("starting Event Log Monitor"); 1250 1251 inotifyConn.emplace(ioc); 1252 inotifyFd = inotify_init1(IN_NONBLOCK); 1253 if (inotifyFd == -1) 1254 { 1255 BMCWEB_LOG_ERROR("inotify_init1 failed."); 1256 return -1; 1257 } 1258 1259 // Add watch on directory to handle redfish event log file 1260 // create/delete. 1261 dirWatchDesc = inotify_add_watch(inotifyFd, redfishEventLogDir, 1262 IN_CREATE | IN_MOVED_TO | IN_DELETE); 1263 if (dirWatchDesc == -1) 1264 { 1265 BMCWEB_LOG_ERROR( 1266 "inotify_add_watch failed for event log directory."); 1267 return -1; 1268 } 1269 1270 // Watch redfish event log file for modifications. 1271 fileWatchDesc = 1272 inotify_add_watch(inotifyFd, redfishEventLogFile, IN_MODIFY); 1273 if (fileWatchDesc == -1) 1274 { 1275 BMCWEB_LOG_ERROR("inotify_add_watch failed for redfish log file."); 1276 // Don't return error if file not exist. 1277 // Watch on directory will handle create/delete of file. 1278 } 1279 1280 // monitor redfish event log file 1281 inotifyConn->assign(inotifyFd); 1282 watchRedfishEventLogFile(); 1283 1284 return 0; 1285 } 1286 1287 static void stopEventLogMonitor() 1288 { 1289 inotifyConn.reset(); 1290 } 1291 1292 static void getReadingsForReport(sdbusplus::message_t& msg) 1293 { 1294 if (msg.is_method_error()) 1295 { 1296 BMCWEB_LOG_ERROR("TelemetryMonitor Signal error"); 1297 return; 1298 } 1299 1300 sdbusplus::message::object_path path(msg.get_path()); 1301 std::string id = path.filename(); 1302 if (id.empty()) 1303 { 1304 BMCWEB_LOG_ERROR("Failed to get Id from path"); 1305 return; 1306 } 1307 1308 std::string interface; 1309 dbus::utility::DBusPropertiesMap props; 1310 std::vector<std::string> invalidProps; 1311 msg.read(interface, props, invalidProps); 1312 1313 auto found = std::ranges::find_if(props, [](const auto& x) { 1314 return x.first == "Readings"; 1315 }); 1316 if (found == props.end()) 1317 { 1318 BMCWEB_LOG_INFO("Failed to get Readings from Report properties"); 1319 return; 1320 } 1321 1322 const telemetry::TimestampReadings* readings = 1323 std::get_if<telemetry::TimestampReadings>(&found->second); 1324 if (readings == nullptr) 1325 { 1326 BMCWEB_LOG_INFO("Failed to get Readings from Report properties"); 1327 return; 1328 } 1329 1330 for (const auto& it : 1331 EventServiceManager::getInstance().subscriptionsMap) 1332 { 1333 Subscription& entry = *it.second; 1334 if (entry.userSub.eventFormatType == metricReportFormatType) 1335 { 1336 entry.filterAndSendReports(id, *readings); 1337 } 1338 } 1339 } 1340 1341 void unregisterMetricReportSignal() 1342 { 1343 if (matchTelemetryMonitor) 1344 { 1345 BMCWEB_LOG_DEBUG("Metrics report signal - Unregister"); 1346 matchTelemetryMonitor.reset(); 1347 matchTelemetryMonitor = nullptr; 1348 } 1349 } 1350 1351 void registerMetricReportSignal() 1352 { 1353 if (!serviceEnabled || matchTelemetryMonitor) 1354 { 1355 BMCWEB_LOG_DEBUG("Not registering metric report signal."); 1356 return; 1357 } 1358 1359 BMCWEB_LOG_DEBUG("Metrics report signal - Register"); 1360 std::string matchStr = "type='signal',member='PropertiesChanged'," 1361 "interface='org.freedesktop.DBus.Properties'," 1362 "arg0=xyz.openbmc_project.Telemetry.Report"; 1363 1364 matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match_t>( 1365 *crow::connections::systemBus, matchStr, getReadingsForReport); 1366 } 1367 }; 1368 1369 } // namespace redfish 1370