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