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