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