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