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