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