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