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 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 bool sendEventToSubscriber(std::string&& msg) 281 { 282 persistent_data::EventServiceConfig eventServiceConfig = 283 persistent_data::EventServiceStore::getInstance() 284 .getEventServiceConfig(); 285 if (!eventServiceConfig.enabled) 286 { 287 return false; 288 } 289 290 if (client) 291 { 292 client->sendData(std::move(msg), userSub.destinationUrl, 293 static_cast<ensuressl::VerifyCertificate>( 294 userSub.verifyCertificate), 295 userSub.httpHeaders, 296 boost::beast::http::verb::post); 297 return true; 298 } 299 300 if (sseConn != nullptr) 301 { 302 eventSeqNum++; 303 sseConn->sendSseEvent(std::to_string(eventSeqNum), msg); 304 } 305 return true; 306 } 307 308 bool sendTestEventLog() 309 { 310 nlohmann::json::array_t logEntryArray; 311 nlohmann::json& logEntryJson = logEntryArray.emplace_back(); 312 313 logEntryJson["EventId"] = "TestID"; 314 logEntryJson["Severity"] = log_entry::EventSeverity::OK; 315 logEntryJson["Message"] = "Generated test event"; 316 logEntryJson["MessageId"] = "OpenBMC.0.2.TestEventLog"; 317 // MemberId is 0 : since we are sending one event record. 318 logEntryJson["MemberId"] = "0"; 319 logEntryJson["MessageArgs"] = nlohmann::json::array(); 320 logEntryJson["EventTimestamp"] = 321 redfish::time_utils::getDateTimeOffsetNow().first; 322 logEntryJson["Context"] = userSub.customText; 323 324 nlohmann::json msg; 325 msg["@odata.type"] = "#Event.v1_4_0.Event"; 326 msg["Id"] = std::to_string(eventSeqNum); 327 msg["Name"] = "Event Log"; 328 msg["Events"] = logEntryArray; 329 330 std::string strMsg = 331 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 332 return sendEventToSubscriber(std::move(strMsg)); 333 } 334 335 void filterAndSendEventLogs( 336 const std::vector<EventLogObjectsType>& eventRecords) 337 { 338 nlohmann::json::array_t logEntryArray; 339 for (const EventLogObjectsType& logEntry : eventRecords) 340 { 341 std::vector<std::string_view> messageArgsView( 342 logEntry.messageArgs.begin(), logEntry.messageArgs.end()); 343 344 nlohmann::json::object_t bmcLogEntry; 345 if (event_log::formatEventLogEntry( 346 logEntry.id, logEntry.messageId, messageArgsView, 347 logEntry.timestamp, userSub.customText, bmcLogEntry) != 0) 348 { 349 BMCWEB_LOG_DEBUG("Read eventLog entry failed"); 350 continue; 351 } 352 353 if (!eventMatchesFilter(userSub, bmcLogEntry, "")) 354 { 355 BMCWEB_LOG_DEBUG("Event {} did not match the filter", 356 nlohmann::json(bmcLogEntry).dump()); 357 continue; 358 } 359 360 if (filter) 361 { 362 if (!memberMatches(bmcLogEntry, *filter)) 363 { 364 BMCWEB_LOG_DEBUG("Filter didn't match"); 365 continue; 366 } 367 } 368 369 logEntryArray.emplace_back(std::move(bmcLogEntry)); 370 } 371 372 if (logEntryArray.empty()) 373 { 374 BMCWEB_LOG_DEBUG("No log entries available to be transferred."); 375 return; 376 } 377 378 nlohmann::json msg; 379 msg["@odata.type"] = "#Event.v1_4_0.Event"; 380 msg["Id"] = std::to_string(eventSeqNum); 381 msg["Name"] = "Event Log"; 382 msg["Events"] = std::move(logEntryArray); 383 std::string strMsg = 384 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 385 sendEventToSubscriber(std::move(strMsg)); 386 eventSeqNum++; 387 } 388 389 void filterAndSendReports(const std::string& reportId, 390 const telemetry::TimestampReadings& var) 391 { 392 boost::urls::url mrdUri = boost::urls::format( 393 "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", 394 reportId); 395 396 // Empty list means no filter. Send everything. 397 if (!userSub.metricReportDefinitions.empty()) 398 { 399 if (std::ranges::find(userSub.metricReportDefinitions, 400 mrdUri.buffer()) == 401 userSub.metricReportDefinitions.end()) 402 { 403 return; 404 } 405 } 406 407 nlohmann::json msg; 408 if (!telemetry::fillReport(msg, reportId, var)) 409 { 410 BMCWEB_LOG_ERROR("Failed to fill the MetricReport for DBus " 411 "Report with id {}", 412 reportId); 413 return; 414 } 415 416 // Context is set by user during Event subscription and it must be 417 // set for MetricReport response. 418 if (!userSub.customText.empty()) 419 { 420 msg["Context"] = userSub.customText; 421 } 422 423 std::string strMsg = 424 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 425 sendEventToSubscriber(std::move(strMsg)); 426 } 427 428 void updateRetryConfig(uint32_t retryAttempts, 429 uint32_t retryTimeoutInterval) 430 { 431 if (policy == nullptr) 432 { 433 BMCWEB_LOG_DEBUG("Retry policy was nullptr, ignoring set"); 434 return; 435 } 436 policy->maxRetryAttempts = retryAttempts; 437 policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval); 438 } 439 440 uint64_t getEventSeqNum() const 441 { 442 return eventSeqNum; 443 } 444 445 void setSubscriptionId(const std::string& idIn) 446 { 447 BMCWEB_LOG_DEBUG("Subscription ID: {}", idIn); 448 userSub.id = idIn; 449 } 450 451 std::string getSubscriptionId() const 452 { 453 return userSub.id; 454 } 455 456 bool matchSseId(const crow::sse_socket::Connection& thisConn) 457 { 458 return &thisConn == sseConn; 459 } 460 461 // Check used to indicate what response codes are valid as part of our retry 462 // policy. 2XX is considered acceptable 463 static boost::system::error_code retryRespHandler(unsigned int respCode) 464 { 465 BMCWEB_LOG_DEBUG( 466 "Checking response code validity for SubscriptionEvent"); 467 if ((respCode < 200) || (respCode >= 300)) 468 { 469 return boost::system::errc::make_error_code( 470 boost::system::errc::result_out_of_range); 471 } 472 473 // Return 0 if the response code is valid 474 return boost::system::errc::make_error_code( 475 boost::system::errc::success); 476 } 477 478 persistent_data::UserSubscription userSub; 479 480 private: 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 // Set Subscription ID for back trace 813 subValue->setSubscriptionId(id); 814 815 persistent_data::UserSubscription newSub(subValue->userSub); 816 817 persistent_data::EventServiceStore::getInstance() 818 .subscriptionsConfigMap.emplace(newSub.id, newSub); 819 820 updateNoOfSubscribersCount(); 821 822 if constexpr (!BMCWEB_REDFISH_DBUS_LOG) 823 { 824 if (redfishLogFilePosition != 0) 825 { 826 cacheRedfishLogFile(); 827 } 828 } 829 // Update retry configuration. 830 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 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