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(std::move(msg), destinationUrl, httpHeaders, 296 boost::beast::http::verb::post); 297 return true; 298 } 299 300 if (sseConn != nullptr) 301 { 302 eventSeqNum++; 303 sseConn->sendEvent(std::to_string(eventSeqNum), msg); 304 } 305 return true; 306 } 307 308 bool sendTestEventLog() 309 { 310 nlohmann::json logEntryArray; 311 logEntryArray.push_back({}); 312 nlohmann::json& logEntryJson = logEntryArray.back(); 313 314 logEntryJson["EventId"] = "TestID"; 315 logEntryJson["EventType"] = "Event"; 316 logEntryJson["Severity"] = "OK"; 317 logEntryJson["Message"] = "Generated test event"; 318 logEntryJson["MessageId"] = "OpenBMC.0.2.TestEventLog"; 319 logEntryJson["MessageArgs"] = nlohmann::json::array(); 320 logEntryJson["EventTimestamp"] = 321 redfish::time_utils::getDateTimeOffsetNow().first; 322 logEntryJson["Context"] = customText; 323 324 nlohmann::json msg; 325 msg["@odata.type"] = "#Event.v1_4_0.Event"; 326 msg["Id"] = std::to_string(eventSeqNum); 327 msg["Name"] = "Event Log"; 328 msg["Events"] = logEntryArray; 329 330 std::string strMsg = msg.dump(2, ' ', true, 331 nlohmann::json::error_handler_t::replace); 332 return sendEvent(std::move(strMsg)); 333 } 334 335 void filterAndSendEventLogs( 336 const std::vector<EventLogObjectsType>& eventRecords) 337 { 338 nlohmann::json logEntryArray; 339 for (const EventLogObjectsType& logEntry : eventRecords) 340 { 341 const std::string& idStr = std::get<0>(logEntry); 342 const std::string& timestamp = std::get<1>(logEntry); 343 const std::string& messageID = std::get<2>(logEntry); 344 const std::string& registryName = std::get<3>(logEntry); 345 const std::string& messageKey = std::get<4>(logEntry); 346 const std::vector<std::string>& messageArgs = std::get<5>(logEntry); 347 348 // If registryPrefixes list is empty, don't filter events 349 // send everything. 350 if (!registryPrefixes.empty()) 351 { 352 auto obj = std::ranges::find(registryPrefixes, registryName); 353 if (obj == registryPrefixes.end()) 354 { 355 continue; 356 } 357 } 358 359 // If registryMsgIds list is empty, don't filter events 360 // send everything. 361 if (!registryMsgIds.empty()) 362 { 363 auto obj = std::ranges::find(registryMsgIds, messageKey); 364 if (obj == registryMsgIds.end()) 365 { 366 continue; 367 } 368 } 369 370 std::vector<std::string_view> messageArgsView(messageArgs.begin(), 371 messageArgs.end()); 372 373 logEntryArray.push_back({}); 374 nlohmann::json& bmcLogEntry = logEntryArray.back(); 375 if (event_log::formatEventLogEntry(idStr, messageID, 376 messageArgsView, timestamp, 377 customText, bmcLogEntry) != 0) 378 { 379 BMCWEB_LOG_DEBUG("Read eventLog entry failed"); 380 continue; 381 } 382 } 383 384 if (logEntryArray.empty()) 385 { 386 BMCWEB_LOG_DEBUG("No log entries available to be transferred."); 387 return; 388 } 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 std::string strMsg = msg.dump(2, ' ', true, 396 nlohmann::json::error_handler_t::replace); 397 sendEvent(std::move(strMsg)); 398 eventSeqNum++; 399 } 400 401 void filterAndSendReports(const std::string& reportId, 402 const telemetry::TimestampReadings& var) 403 { 404 boost::urls::url mrdUri = boost::urls::format( 405 "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", 406 reportId); 407 408 // Empty list means no filter. Send everything. 409 if (!metricReportDefinitions.empty()) 410 { 411 if (std::ranges::find(metricReportDefinitions, mrdUri.buffer()) == 412 metricReportDefinitions.end()) 413 { 414 return; 415 } 416 } 417 418 nlohmann::json msg; 419 if (!telemetry::fillReport(msg, reportId, var)) 420 { 421 BMCWEB_LOG_ERROR("Failed to fill the MetricReport for DBus " 422 "Report with id {}", 423 reportId); 424 return; 425 } 426 427 // Context is set by user during Event subscription and it must be 428 // set for MetricReport response. 429 if (!customText.empty()) 430 { 431 msg["Context"] = customText; 432 } 433 434 std::string strMsg = msg.dump(2, ' ', true, 435 nlohmann::json::error_handler_t::replace); 436 sendEvent(std::move(strMsg)); 437 } 438 439 void updateRetryConfig(uint32_t retryAttempts, 440 uint32_t retryTimeoutInterval) 441 { 442 if (policy == nullptr) 443 { 444 BMCWEB_LOG_DEBUG("Retry policy was nullptr, ignoring set"); 445 return; 446 } 447 policy->maxRetryAttempts = retryAttempts; 448 policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval); 449 } 450 451 uint64_t getEventSeqNum() const 452 { 453 return eventSeqNum; 454 } 455 456 void setSubscriptionId(const std::string& id2) 457 { 458 BMCWEB_LOG_DEBUG("Subscription ID: {}", id2); 459 subId = id2; 460 } 461 462 std::string getSubscriptionId() 463 { 464 return subId; 465 } 466 467 bool matchSseId(const crow::sse_socket::Connection& thisConn) 468 { 469 return &thisConn == sseConn; 470 } 471 472 private: 473 std::string subId; 474 uint64_t eventSeqNum = 1; 475 boost::urls::url host; 476 std::shared_ptr<crow::ConnectionPolicy> policy; 477 crow::sse_socket::Connection* sseConn = nullptr; 478 std::optional<crow::HttpClient> client; 479 std::string path; 480 std::string uriProto; 481 482 // Check used to indicate what response codes are valid as part of our retry 483 // policy. 2XX is considered acceptable 484 static boost::system::error_code retryRespHandler(unsigned int respCode) 485 { 486 BMCWEB_LOG_DEBUG( 487 "Checking response code validity for SubscriptionEvent"); 488 if ((respCode < 200) || (respCode >= 300)) 489 { 490 return boost::system::errc::make_error_code( 491 boost::system::errc::result_out_of_range); 492 } 493 494 // Return 0 if the response code is valid 495 return boost::system::errc::make_error_code( 496 boost::system::errc::success); 497 } 498 }; 499 500 class EventServiceManager 501 { 502 private: 503 bool serviceEnabled = false; 504 uint32_t retryAttempts = 0; 505 uint32_t retryTimeoutInterval = 0; 506 507 std::streampos redfishLogFilePosition{0}; 508 size_t noOfEventLogSubscribers{0}; 509 size_t noOfMetricReportSubscribers{0}; 510 std::shared_ptr<sdbusplus::bus::match_t> matchTelemetryMonitor; 511 boost::container::flat_map<std::string, std::shared_ptr<Subscription>> 512 subscriptionsMap; 513 514 uint64_t eventId{1}; 515 516 boost::asio::io_context& ioc; 517 518 public: 519 EventServiceManager(const EventServiceManager&) = delete; 520 EventServiceManager& operator=(const EventServiceManager&) = delete; 521 EventServiceManager(EventServiceManager&&) = delete; 522 EventServiceManager& operator=(EventServiceManager&&) = delete; 523 ~EventServiceManager() = default; 524 525 explicit EventServiceManager(boost::asio::io_context& iocIn) : ioc(iocIn) 526 { 527 // Load config from persist store. 528 initConfig(); 529 } 530 531 static EventServiceManager& 532 getInstance(boost::asio::io_context* ioc = nullptr) 533 { 534 static EventServiceManager handler(*ioc); 535 return handler; 536 } 537 538 void initConfig() 539 { 540 loadOldBehavior(); 541 542 persistent_data::EventServiceConfig eventServiceConfig = 543 persistent_data::EventServiceStore::getInstance() 544 .getEventServiceConfig(); 545 546 serviceEnabled = eventServiceConfig.enabled; 547 retryAttempts = eventServiceConfig.retryAttempts; 548 retryTimeoutInterval = eventServiceConfig.retryTimeoutInterval; 549 550 for (const auto& it : persistent_data::EventServiceStore::getInstance() 551 .subscriptionsConfigMap) 552 { 553 std::shared_ptr<persistent_data::UserSubscription> newSub = 554 it.second; 555 556 boost::system::result<boost::urls::url> url = 557 boost::urls::parse_absolute_uri(newSub->destinationUrl); 558 559 if (!url) 560 { 561 BMCWEB_LOG_ERROR( 562 "Failed to validate and split destination url"); 563 continue; 564 } 565 std::shared_ptr<Subscription> subValue = 566 std::make_shared<Subscription>(*url, ioc); 567 568 subValue->id = newSub->id; 569 subValue->destinationUrl = newSub->destinationUrl; 570 subValue->protocol = newSub->protocol; 571 subValue->retryPolicy = newSub->retryPolicy; 572 subValue->customText = newSub->customText; 573 subValue->eventFormatType = newSub->eventFormatType; 574 subValue->subscriptionType = newSub->subscriptionType; 575 subValue->registryMsgIds = newSub->registryMsgIds; 576 subValue->registryPrefixes = newSub->registryPrefixes; 577 subValue->resourceTypes = newSub->resourceTypes; 578 subValue->httpHeaders = newSub->httpHeaders; 579 subValue->metricReportDefinitions = newSub->metricReportDefinitions; 580 581 if (subValue->id.empty()) 582 { 583 BMCWEB_LOG_ERROR("Failed to add subscription"); 584 } 585 subscriptionsMap.insert(std::pair(subValue->id, subValue)); 586 587 updateNoOfSubscribersCount(); 588 589 if constexpr (!BMCWEB_REDFISH_DBUS_LOG) 590 { 591 cacheRedfishLogFile(); 592 } 593 594 // Update retry configuration. 595 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 596 } 597 } 598 599 static void loadOldBehavior() 600 { 601 std::ifstream eventConfigFile(eventServiceFile); 602 if (!eventConfigFile.good()) 603 { 604 BMCWEB_LOG_DEBUG("Old eventService config not exist"); 605 return; 606 } 607 auto jsonData = nlohmann::json::parse(eventConfigFile, nullptr, false); 608 if (jsonData.is_discarded()) 609 { 610 BMCWEB_LOG_ERROR("Old eventService config parse error."); 611 return; 612 } 613 614 const nlohmann::json::object_t* obj = 615 jsonData.get_ptr<const nlohmann::json::object_t*>(); 616 for (const auto& item : *obj) 617 { 618 if (item.first == "Configuration") 619 { 620 persistent_data::EventServiceStore::getInstance() 621 .getEventServiceConfig() 622 .fromJson(item.second); 623 } 624 else if (item.first == "Subscriptions") 625 { 626 for (const auto& elem : item.second) 627 { 628 std::shared_ptr<persistent_data::UserSubscription> 629 newSubscription = 630 persistent_data::UserSubscription::fromJson(elem, 631 true); 632 if (newSubscription == nullptr) 633 { 634 BMCWEB_LOG_ERROR("Problem reading subscription " 635 "from old persistent store"); 636 continue; 637 } 638 639 std::uniform_int_distribution<uint32_t> dist(0); 640 bmcweb::OpenSSLGenerator gen; 641 642 std::string id; 643 644 int retry = 3; 645 while (retry != 0) 646 { 647 id = std::to_string(dist(gen)); 648 if (gen.error()) 649 { 650 retry = 0; 651 break; 652 } 653 newSubscription->id = id; 654 auto inserted = 655 persistent_data::EventServiceStore::getInstance() 656 .subscriptionsConfigMap.insert( 657 std::pair(id, newSubscription)); 658 if (inserted.second) 659 { 660 break; 661 } 662 --retry; 663 } 664 665 if (retry <= 0) 666 { 667 BMCWEB_LOG_ERROR( 668 "Failed to generate random number from old " 669 "persistent store"); 670 continue; 671 } 672 } 673 } 674 675 persistent_data::getConfig().writeData(); 676 std::error_code ec; 677 std::filesystem::remove(eventServiceFile, ec); 678 if (ec) 679 { 680 BMCWEB_LOG_DEBUG( 681 "Failed to remove old event service file. Ignoring"); 682 } 683 else 684 { 685 BMCWEB_LOG_DEBUG("Remove old eventservice config"); 686 } 687 } 688 } 689 690 void updateSubscriptionData() const 691 { 692 persistent_data::EventServiceStore::getInstance() 693 .eventServiceConfig.enabled = serviceEnabled; 694 persistent_data::EventServiceStore::getInstance() 695 .eventServiceConfig.retryAttempts = retryAttempts; 696 persistent_data::EventServiceStore::getInstance() 697 .eventServiceConfig.retryTimeoutInterval = retryTimeoutInterval; 698 699 persistent_data::getConfig().writeData(); 700 } 701 702 void setEventServiceConfig(const persistent_data::EventServiceConfig& cfg) 703 { 704 bool updateConfig = false; 705 bool updateRetryCfg = false; 706 707 if (serviceEnabled != cfg.enabled) 708 { 709 serviceEnabled = cfg.enabled; 710 if (serviceEnabled && noOfMetricReportSubscribers != 0U) 711 { 712 registerMetricReportSignal(); 713 } 714 else 715 { 716 unregisterMetricReportSignal(); 717 } 718 updateConfig = true; 719 } 720 721 if (retryAttempts != cfg.retryAttempts) 722 { 723 retryAttempts = cfg.retryAttempts; 724 updateConfig = true; 725 updateRetryCfg = true; 726 } 727 728 if (retryTimeoutInterval != cfg.retryTimeoutInterval) 729 { 730 retryTimeoutInterval = cfg.retryTimeoutInterval; 731 updateConfig = true; 732 updateRetryCfg = true; 733 } 734 735 if (updateConfig) 736 { 737 updateSubscriptionData(); 738 } 739 740 if (updateRetryCfg) 741 { 742 // Update the changed retry config to all subscriptions 743 for (const auto& it : 744 EventServiceManager::getInstance().subscriptionsMap) 745 { 746 Subscription& entry = *it.second; 747 entry.updateRetryConfig(retryAttempts, retryTimeoutInterval); 748 } 749 } 750 } 751 752 void updateNoOfSubscribersCount() 753 { 754 size_t eventLogSubCount = 0; 755 size_t metricReportSubCount = 0; 756 for (const auto& it : subscriptionsMap) 757 { 758 std::shared_ptr<Subscription> entry = it.second; 759 if (entry->eventFormatType == eventFormatType) 760 { 761 eventLogSubCount++; 762 } 763 else if (entry->eventFormatType == metricReportFormatType) 764 { 765 metricReportSubCount++; 766 } 767 } 768 769 noOfEventLogSubscribers = eventLogSubCount; 770 if (noOfMetricReportSubscribers != metricReportSubCount) 771 { 772 noOfMetricReportSubscribers = metricReportSubCount; 773 if (noOfMetricReportSubscribers != 0U) 774 { 775 registerMetricReportSignal(); 776 } 777 else 778 { 779 unregisterMetricReportSignal(); 780 } 781 } 782 } 783 784 std::shared_ptr<Subscription> getSubscription(const std::string& id) 785 { 786 auto obj = subscriptionsMap.find(id); 787 if (obj == subscriptionsMap.end()) 788 { 789 BMCWEB_LOG_ERROR("No subscription exist with ID:{}", id); 790 return nullptr; 791 } 792 std::shared_ptr<Subscription> subValue = obj->second; 793 return subValue; 794 } 795 796 std::string addSubscription(const std::shared_ptr<Subscription>& subValue, 797 const bool updateFile = true) 798 { 799 std::uniform_int_distribution<uint32_t> dist(0); 800 bmcweb::OpenSSLGenerator gen; 801 802 std::string id; 803 804 int retry = 3; 805 while (retry != 0) 806 { 807 id = std::to_string(dist(gen)); 808 if (gen.error()) 809 { 810 retry = 0; 811 break; 812 } 813 auto inserted = subscriptionsMap.insert(std::pair(id, subValue)); 814 if (inserted.second) 815 { 816 break; 817 } 818 --retry; 819 } 820 821 if (retry <= 0) 822 { 823 BMCWEB_LOG_ERROR("Failed to generate random number"); 824 return ""; 825 } 826 827 std::shared_ptr<persistent_data::UserSubscription> newSub = 828 std::make_shared<persistent_data::UserSubscription>(); 829 newSub->id = id; 830 newSub->destinationUrl = subValue->destinationUrl; 831 newSub->protocol = subValue->protocol; 832 newSub->retryPolicy = subValue->retryPolicy; 833 newSub->customText = subValue->customText; 834 newSub->eventFormatType = subValue->eventFormatType; 835 newSub->subscriptionType = subValue->subscriptionType; 836 newSub->registryMsgIds = subValue->registryMsgIds; 837 newSub->registryPrefixes = subValue->registryPrefixes; 838 newSub->resourceTypes = subValue->resourceTypes; 839 newSub->httpHeaders = subValue->httpHeaders; 840 newSub->metricReportDefinitions = subValue->metricReportDefinitions; 841 persistent_data::EventServiceStore::getInstance() 842 .subscriptionsConfigMap.emplace(newSub->id, newSub); 843 844 updateNoOfSubscribersCount(); 845 846 if (updateFile) 847 { 848 updateSubscriptionData(); 849 } 850 851 if constexpr (!BMCWEB_REDFISH_DBUS_LOG) 852 { 853 if (redfishLogFilePosition != 0) 854 { 855 cacheRedfishLogFile(); 856 } 857 } 858 // Update retry configuration. 859 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 860 861 // Set Subscription ID for back trace 862 subValue->setSubscriptionId(id); 863 return id; 864 } 865 866 bool isSubscriptionExist(const std::string& id) 867 { 868 auto obj = subscriptionsMap.find(id); 869 return obj != subscriptionsMap.end(); 870 } 871 872 void deleteSubscription(const std::string& id) 873 { 874 auto obj = subscriptionsMap.find(id); 875 if (obj != subscriptionsMap.end()) 876 { 877 subscriptionsMap.erase(obj); 878 auto obj2 = persistent_data::EventServiceStore::getInstance() 879 .subscriptionsConfigMap.find(id); 880 persistent_data::EventServiceStore::getInstance() 881 .subscriptionsConfigMap.erase(obj2); 882 updateNoOfSubscribersCount(); 883 updateSubscriptionData(); 884 } 885 } 886 887 void deleteSseSubscription(const crow::sse_socket::Connection& thisConn) 888 { 889 for (auto it = subscriptionsMap.begin(); it != subscriptionsMap.end();) 890 { 891 std::shared_ptr<Subscription> entry = it->second; 892 bool entryIsThisConn = entry->matchSseId(thisConn); 893 if (entryIsThisConn) 894 { 895 persistent_data::EventServiceStore::getInstance() 896 .subscriptionsConfigMap.erase( 897 it->second->getSubscriptionId()); 898 it = subscriptionsMap.erase(it); 899 return; 900 } 901 it++; 902 } 903 } 904 905 size_t getNumberOfSubscriptions() const 906 { 907 return subscriptionsMap.size(); 908 } 909 910 size_t getNumberOfSSESubscriptions() const 911 { 912 auto size = std::ranges::count_if( 913 subscriptionsMap, 914 [](const std::pair<std::string, std::shared_ptr<Subscription>>& 915 entry) { 916 return (entry.second->subscriptionType == subscriptionTypeSSE); 917 }); 918 return static_cast<size_t>(size); 919 } 920 921 std::vector<std::string> getAllIDs() 922 { 923 std::vector<std::string> idList; 924 for (const auto& it : subscriptionsMap) 925 { 926 idList.emplace_back(it.first); 927 } 928 return idList; 929 } 930 931 bool sendTestEventLog() 932 { 933 for (const auto& it : subscriptionsMap) 934 { 935 std::shared_ptr<Subscription> entry = it.second; 936 if (!entry->sendTestEventLog()) 937 { 938 return false; 939 } 940 } 941 return true; 942 } 943 944 void sendEvent(nlohmann::json eventMessage, const std::string& origin, 945 const std::string& resType) 946 { 947 if (!serviceEnabled || (noOfEventLogSubscribers == 0U)) 948 { 949 BMCWEB_LOG_DEBUG("EventService disabled or no Subscriptions."); 950 return; 951 } 952 nlohmann::json eventRecord = nlohmann::json::array(); 953 954 eventMessage["EventId"] = eventId; 955 // MemberId is 0 : since we are sending one event record. 956 eventMessage["MemberId"] = 0; 957 eventMessage["EventTimestamp"] = 958 redfish::time_utils::getDateTimeOffsetNow().first; 959 eventMessage["OriginOfCondition"] = origin; 960 961 eventRecord.emplace_back(std::move(eventMessage)); 962 963 for (const auto& it : subscriptionsMap) 964 { 965 std::shared_ptr<Subscription> entry = it.second; 966 bool isSubscribed = false; 967 // Search the resourceTypes list for the subscription. 968 // If resourceTypes list is empty, don't filter events 969 // send everything. 970 if (!entry->resourceTypes.empty()) 971 { 972 for (const auto& resource : entry->resourceTypes) 973 { 974 if (resType == resource) 975 { 976 BMCWEB_LOG_INFO( 977 "ResourceType {} found in the subscribed list", 978 resource); 979 isSubscribed = true; 980 break; 981 } 982 } 983 } 984 else // resourceTypes list is empty. 985 { 986 isSubscribed = true; 987 } 988 if (isSubscribed) 989 { 990 nlohmann::json msgJson; 991 992 msgJson["@odata.type"] = "#Event.v1_4_0.Event"; 993 msgJson["Name"] = "Event Log"; 994 msgJson["Id"] = eventId; 995 msgJson["Events"] = eventRecord; 996 997 std::string strMsg = msgJson.dump( 998 2, ' ', true, nlohmann::json::error_handler_t::replace); 999 entry->sendEvent(std::move(strMsg)); 1000 eventId++; // increment the eventId 1001 } 1002 else 1003 { 1004 BMCWEB_LOG_INFO("Not subscribed to this resource"); 1005 } 1006 } 1007 } 1008 1009 void resetRedfishFilePosition() 1010 { 1011 // Control would be here when Redfish file is created. 1012 // Reset File Position as new file is created 1013 redfishLogFilePosition = 0; 1014 } 1015 1016 void cacheRedfishLogFile() 1017 { 1018 // Open the redfish file and read till the last record. 1019 1020 std::ifstream logStream(redfishEventLogFile); 1021 if (!logStream.good()) 1022 { 1023 BMCWEB_LOG_ERROR(" Redfish log file open failed "); 1024 return; 1025 } 1026 std::string logEntry; 1027 while (std::getline(logStream, logEntry)) 1028 { 1029 redfishLogFilePosition = logStream.tellg(); 1030 } 1031 } 1032 1033 void readEventLogsFromFile() 1034 { 1035 std::ifstream logStream(redfishEventLogFile); 1036 if (!logStream.good()) 1037 { 1038 BMCWEB_LOG_ERROR(" Redfish log file open failed"); 1039 return; 1040 } 1041 1042 std::vector<EventLogObjectsType> eventRecords; 1043 1044 std::string logEntry; 1045 1046 // Get the read pointer to the next log to be read. 1047 logStream.seekg(redfishLogFilePosition); 1048 1049 while (std::getline(logStream, logEntry)) 1050 { 1051 // Update Pointer position 1052 redfishLogFilePosition = logStream.tellg(); 1053 1054 std::string idStr; 1055 if (!event_log::getUniqueEntryID(logEntry, idStr)) 1056 { 1057 continue; 1058 } 1059 1060 if (!serviceEnabled || noOfEventLogSubscribers == 0) 1061 { 1062 // If Service is not enabled, no need to compute 1063 // the remaining items below. 1064 // But, Loop must continue to keep track of Timestamp 1065 continue; 1066 } 1067 1068 std::string timestamp; 1069 std::string messageID; 1070 std::vector<std::string> messageArgs; 1071 if (event_log::getEventLogParams(logEntry, timestamp, messageID, 1072 messageArgs) != 0) 1073 { 1074 BMCWEB_LOG_DEBUG("Read eventLog entry params failed"); 1075 continue; 1076 } 1077 1078 std::string registryName; 1079 std::string messageKey; 1080 event_log::getRegistryAndMessageKey(messageID, registryName, 1081 messageKey); 1082 if (registryName.empty() || messageKey.empty()) 1083 { 1084 continue; 1085 } 1086 1087 eventRecords.emplace_back(idStr, timestamp, messageID, registryName, 1088 messageKey, messageArgs); 1089 } 1090 1091 if (!serviceEnabled || noOfEventLogSubscribers == 0) 1092 { 1093 BMCWEB_LOG_DEBUG("EventService disabled or no Subscriptions."); 1094 return; 1095 } 1096 1097 if (eventRecords.empty()) 1098 { 1099 // No Records to send 1100 BMCWEB_LOG_DEBUG("No log entries available to be transferred."); 1101 return; 1102 } 1103 1104 for (const auto& it : subscriptionsMap) 1105 { 1106 std::shared_ptr<Subscription> entry = it.second; 1107 if (entry->eventFormatType == "Event") 1108 { 1109 entry->filterAndSendEventLogs(eventRecords); 1110 } 1111 } 1112 } 1113 1114 static void watchRedfishEventLogFile() 1115 { 1116 if (!inotifyConn) 1117 { 1118 return; 1119 } 1120 1121 static std::array<char, 1024> readBuffer; 1122 1123 inotifyConn->async_read_some(boost::asio::buffer(readBuffer), 1124 [&](const boost::system::error_code& ec, 1125 const std::size_t& bytesTransferred) { 1126 if (ec == boost::asio::error::operation_aborted) 1127 { 1128 BMCWEB_LOG_DEBUG("Inotify was canceled (shutdown?)"); 1129 return; 1130 } 1131 if (ec) 1132 { 1133 BMCWEB_LOG_ERROR("Callback Error: {}", ec.message()); 1134 return; 1135 } 1136 std::size_t index = 0; 1137 while ((index + iEventSize) <= bytesTransferred) 1138 { 1139 struct inotify_event event 1140 {}; 1141 std::memcpy(&event, &readBuffer[index], iEventSize); 1142 if (event.wd == dirWatchDesc) 1143 { 1144 if ((event.len == 0) || 1145 (index + iEventSize + event.len > bytesTransferred)) 1146 { 1147 index += (iEventSize + event.len); 1148 continue; 1149 } 1150 1151 std::string fileName(&readBuffer[index + iEventSize]); 1152 if (fileName != "redfish") 1153 { 1154 index += (iEventSize + event.len); 1155 continue; 1156 } 1157 1158 BMCWEB_LOG_DEBUG( 1159 "Redfish log file created/deleted. event.name: {}", 1160 fileName); 1161 if (event.mask == IN_CREATE) 1162 { 1163 if (fileWatchDesc != -1) 1164 { 1165 BMCWEB_LOG_DEBUG( 1166 "Remove and Add inotify watcher on " 1167 "redfish event log file"); 1168 // Remove existing inotify watcher and add 1169 // with new redfish event log file. 1170 inotify_rm_watch(inotifyFd, fileWatchDesc); 1171 fileWatchDesc = -1; 1172 } 1173 1174 fileWatchDesc = inotify_add_watch( 1175 inotifyFd, redfishEventLogFile, IN_MODIFY); 1176 if (fileWatchDesc == -1) 1177 { 1178 BMCWEB_LOG_ERROR("inotify_add_watch failed for " 1179 "redfish log file."); 1180 return; 1181 } 1182 1183 EventServiceManager::getInstance() 1184 .resetRedfishFilePosition(); 1185 EventServiceManager::getInstance() 1186 .readEventLogsFromFile(); 1187 } 1188 else if ((event.mask == IN_DELETE) || 1189 (event.mask == IN_MOVED_TO)) 1190 { 1191 if (fileWatchDesc != -1) 1192 { 1193 inotify_rm_watch(inotifyFd, fileWatchDesc); 1194 fileWatchDesc = -1; 1195 } 1196 } 1197 } 1198 else if (event.wd == fileWatchDesc) 1199 { 1200 if (event.mask == IN_MODIFY) 1201 { 1202 EventServiceManager::getInstance() 1203 .readEventLogsFromFile(); 1204 } 1205 } 1206 index += (iEventSize + event.len); 1207 } 1208 1209 watchRedfishEventLogFile(); 1210 }); 1211 } 1212 1213 static int startEventLogMonitor(boost::asio::io_context& ioc) 1214 { 1215 inotifyConn.emplace(ioc); 1216 inotifyFd = inotify_init1(IN_NONBLOCK); 1217 if (inotifyFd == -1) 1218 { 1219 BMCWEB_LOG_ERROR("inotify_init1 failed."); 1220 return -1; 1221 } 1222 1223 // Add watch on directory to handle redfish event log file 1224 // create/delete. 1225 dirWatchDesc = inotify_add_watch(inotifyFd, redfishEventLogDir, 1226 IN_CREATE | IN_MOVED_TO | IN_DELETE); 1227 if (dirWatchDesc == -1) 1228 { 1229 BMCWEB_LOG_ERROR( 1230 "inotify_add_watch failed for event log directory."); 1231 return -1; 1232 } 1233 1234 // Watch redfish event log file for modifications. 1235 fileWatchDesc = inotify_add_watch(inotifyFd, redfishEventLogFile, 1236 IN_MODIFY); 1237 if (fileWatchDesc == -1) 1238 { 1239 BMCWEB_LOG_ERROR("inotify_add_watch failed for redfish log file."); 1240 // Don't return error if file not exist. 1241 // Watch on directory will handle create/delete of file. 1242 } 1243 1244 // monitor redfish event log file 1245 inotifyConn->assign(inotifyFd); 1246 watchRedfishEventLogFile(); 1247 1248 return 0; 1249 } 1250 1251 static void stopEventLogMonitor() 1252 { 1253 inotifyConn.reset(); 1254 } 1255 1256 static void getReadingsForReport(sdbusplus::message_t& msg) 1257 { 1258 if (msg.is_method_error()) 1259 { 1260 BMCWEB_LOG_ERROR("TelemetryMonitor Signal error"); 1261 return; 1262 } 1263 1264 sdbusplus::message::object_path path(msg.get_path()); 1265 std::string id = path.filename(); 1266 if (id.empty()) 1267 { 1268 BMCWEB_LOG_ERROR("Failed to get Id from path"); 1269 return; 1270 } 1271 1272 std::string interface; 1273 dbus::utility::DBusPropertiesMap props; 1274 std::vector<std::string> invalidProps; 1275 msg.read(interface, props, invalidProps); 1276 1277 auto found = std::ranges::find_if( 1278 props, [](const auto& x) { return x.first == "Readings"; }); 1279 if (found == props.end()) 1280 { 1281 BMCWEB_LOG_INFO("Failed to get Readings from Report properties"); 1282 return; 1283 } 1284 1285 const telemetry::TimestampReadings* readings = 1286 std::get_if<telemetry::TimestampReadings>(&found->second); 1287 if (readings == nullptr) 1288 { 1289 BMCWEB_LOG_INFO("Failed to get Readings from Report properties"); 1290 return; 1291 } 1292 1293 for (const auto& it : 1294 EventServiceManager::getInstance().subscriptionsMap) 1295 { 1296 Subscription& entry = *it.second; 1297 if (entry.eventFormatType == metricReportFormatType) 1298 { 1299 entry.filterAndSendReports(id, *readings); 1300 } 1301 } 1302 } 1303 1304 void unregisterMetricReportSignal() 1305 { 1306 if (matchTelemetryMonitor) 1307 { 1308 BMCWEB_LOG_DEBUG("Metrics report signal - Unregister"); 1309 matchTelemetryMonitor.reset(); 1310 matchTelemetryMonitor = nullptr; 1311 } 1312 } 1313 1314 void registerMetricReportSignal() 1315 { 1316 if (!serviceEnabled || matchTelemetryMonitor) 1317 { 1318 BMCWEB_LOG_DEBUG("Not registering metric report signal."); 1319 return; 1320 } 1321 1322 BMCWEB_LOG_DEBUG("Metrics report signal - Register"); 1323 std::string matchStr = "type='signal',member='PropertiesChanged'," 1324 "interface='org.freedesktop.DBus.Properties'," 1325 "arg0=xyz.openbmc_project.Telemetry.Report"; 1326 1327 matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match_t>( 1328 *crow::connections::systemBus, matchStr, getReadingsForReport); 1329 } 1330 }; 1331 1332 } // namespace redfish 1333