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