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