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