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(boost::urls::url_view url, boost::asio::io_context& ioc) : 360 policy(std::make_shared<crow::ConnectionPolicy>()) 361 { 362 destinationUrl = url; 363 client.emplace(ioc, policy); 364 // Subscription constructor 365 policy->invalidResp = retryRespHandler; 366 } 367 368 explicit Subscription(crow::sse_socket::Connection& connIn) : 369 sseConn(&connIn) 370 {} 371 372 ~Subscription() = default; 373 374 bool sendEvent(std::string&& msg) 375 { 376 persistent_data::EventServiceConfig eventServiceConfig = 377 persistent_data::EventServiceStore::getInstance() 378 .getEventServiceConfig(); 379 if (!eventServiceConfig.enabled) 380 { 381 return false; 382 } 383 384 // A connection pool will be created if one does not already exist 385 if (client) 386 { 387 client->sendData(std::move(msg), destinationUrl, httpHeaders, 388 boost::beast::http::verb::post); 389 return true; 390 } 391 392 if (sseConn != nullptr) 393 { 394 eventSeqNum++; 395 sseConn->sendEvent(std::to_string(eventSeqNum), msg); 396 } 397 return true; 398 } 399 400 bool sendTestEventLog() 401 { 402 nlohmann::json logEntryArray; 403 logEntryArray.push_back({}); 404 nlohmann::json& logEntryJson = logEntryArray.back(); 405 406 logEntryJson["EventId"] = "TestID"; 407 logEntryJson["EventType"] = "Event"; 408 logEntryJson["Severity"] = "OK"; 409 logEntryJson["Message"] = "Generated test event"; 410 logEntryJson["MessageId"] = "OpenBMC.0.2.TestEventLog"; 411 logEntryJson["MessageArgs"] = nlohmann::json::array(); 412 logEntryJson["EventTimestamp"] = 413 redfish::time_utils::getDateTimeOffsetNow().first; 414 logEntryJson["Context"] = customText; 415 416 nlohmann::json msg; 417 msg["@odata.type"] = "#Event.v1_4_0.Event"; 418 msg["Id"] = std::to_string(eventSeqNum); 419 msg["Name"] = "Event Log"; 420 msg["Events"] = logEntryArray; 421 422 std::string strMsg = msg.dump(2, ' ', true, 423 nlohmann::json::error_handler_t::replace); 424 return sendEvent(std::move(strMsg)); 425 } 426 427 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 428 void filterAndSendEventLogs( 429 const std::vector<EventLogObjectsType>& eventRecords) 430 { 431 nlohmann::json logEntryArray; 432 for (const EventLogObjectsType& logEntry : eventRecords) 433 { 434 const std::string& idStr = std::get<0>(logEntry); 435 const std::string& timestamp = std::get<1>(logEntry); 436 const std::string& messageID = std::get<2>(logEntry); 437 const std::string& registryName = std::get<3>(logEntry); 438 const std::string& messageKey = std::get<4>(logEntry); 439 const std::vector<std::string>& messageArgs = std::get<5>(logEntry); 440 441 // If registryPrefixes list is empty, don't filter events 442 // send everything. 443 if (!registryPrefixes.empty()) 444 { 445 auto obj = std::ranges::find(registryPrefixes, registryName); 446 if (obj == registryPrefixes.end()) 447 { 448 continue; 449 } 450 } 451 452 // If registryMsgIds list is empty, don't filter events 453 // send everything. 454 if (!registryMsgIds.empty()) 455 { 456 auto obj = std::ranges::find(registryMsgIds, messageKey); 457 if (obj == registryMsgIds.end()) 458 { 459 continue; 460 } 461 } 462 463 std::vector<std::string_view> messageArgsView(messageArgs.begin(), 464 messageArgs.end()); 465 466 logEntryArray.push_back({}); 467 nlohmann::json& bmcLogEntry = logEntryArray.back(); 468 if (event_log::formatEventLogEntry(idStr, messageID, 469 messageArgsView, timestamp, 470 customText, bmcLogEntry) != 0) 471 { 472 BMCWEB_LOG_DEBUG("Read eventLog entry failed"); 473 continue; 474 } 475 } 476 477 if (logEntryArray.empty()) 478 { 479 BMCWEB_LOG_DEBUG("No log entries available to be transferred."); 480 return; 481 } 482 483 nlohmann::json msg; 484 msg["@odata.type"] = "#Event.v1_4_0.Event"; 485 msg["Id"] = std::to_string(eventSeqNum); 486 msg["Name"] = "Event Log"; 487 msg["Events"] = logEntryArray; 488 std::string strMsg = msg.dump(2, ' ', true, 489 nlohmann::json::error_handler_t::replace); 490 sendEvent(std::move(strMsg)); 491 eventSeqNum++; 492 } 493 #endif 494 495 void filterAndSendReports(const std::string& reportId, 496 const telemetry::TimestampReadings& var) 497 { 498 boost::urls::url mrdUri = boost::urls::format( 499 "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", 500 reportId); 501 502 // Empty list means no filter. Send everything. 503 if (!metricReportDefinitions.empty()) 504 { 505 if (std::ranges::find(metricReportDefinitions, mrdUri.buffer()) == 506 metricReportDefinitions.end()) 507 { 508 return; 509 } 510 } 511 512 nlohmann::json msg; 513 if (!telemetry::fillReport(msg, reportId, var)) 514 { 515 BMCWEB_LOG_ERROR("Failed to fill the MetricReport for DBus " 516 "Report with id {}", 517 reportId); 518 return; 519 } 520 521 // Context is set by user during Event subscription and it must be 522 // set for MetricReport response. 523 if (!customText.empty()) 524 { 525 msg["Context"] = customText; 526 } 527 528 std::string strMsg = msg.dump(2, ' ', true, 529 nlohmann::json::error_handler_t::replace); 530 sendEvent(std::move(strMsg)); 531 } 532 533 void updateRetryConfig(uint32_t retryAttempts, 534 uint32_t retryTimeoutInterval) 535 { 536 policy->maxRetryAttempts = retryAttempts; 537 policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval); 538 } 539 540 uint64_t getEventSeqNum() const 541 { 542 return eventSeqNum; 543 } 544 545 void setSubscriptionId(const std::string& id2) 546 { 547 BMCWEB_LOG_DEBUG("Subscription ID: {}", id2); 548 subId = id2; 549 } 550 551 std::string getSubscriptionId() 552 { 553 return subId; 554 } 555 556 bool matchSseId(const crow::sse_socket::Connection& thisConn) 557 { 558 return &thisConn == sseConn; 559 } 560 561 private: 562 std::string subId; 563 uint64_t eventSeqNum = 1; 564 boost::urls::url host; 565 std::shared_ptr<crow::ConnectionPolicy> policy; 566 crow::sse_socket::Connection* sseConn = nullptr; 567 std::optional<crow::HttpClient> client; 568 std::string path; 569 std::string uriProto; 570 571 // Check used to indicate what response codes are valid as part of our retry 572 // policy. 2XX is considered acceptable 573 static boost::system::error_code retryRespHandler(unsigned int respCode) 574 { 575 BMCWEB_LOG_DEBUG( 576 "Checking response code validity for SubscriptionEvent"); 577 if ((respCode < 200) || (respCode >= 300)) 578 { 579 return boost::system::errc::make_error_code( 580 boost::system::errc::result_out_of_range); 581 } 582 583 // Return 0 if the response code is valid 584 return boost::system::errc::make_error_code( 585 boost::system::errc::success); 586 } 587 }; 588 589 class EventServiceManager 590 { 591 private: 592 bool serviceEnabled = false; 593 uint32_t retryAttempts = 0; 594 uint32_t retryTimeoutInterval = 0; 595 596 std::streampos redfishLogFilePosition{0}; 597 size_t noOfEventLogSubscribers{0}; 598 size_t noOfMetricReportSubscribers{0}; 599 std::shared_ptr<sdbusplus::bus::match_t> matchTelemetryMonitor; 600 boost::container::flat_map<std::string, std::shared_ptr<Subscription>> 601 subscriptionsMap; 602 603 uint64_t eventId{1}; 604 605 boost::asio::io_context& ioc; 606 607 public: 608 EventServiceManager(const EventServiceManager&) = delete; 609 EventServiceManager& operator=(const EventServiceManager&) = delete; 610 EventServiceManager(EventServiceManager&&) = delete; 611 EventServiceManager& operator=(EventServiceManager&&) = delete; 612 ~EventServiceManager() = default; 613 614 explicit EventServiceManager(boost::asio::io_context& iocIn) : ioc(iocIn) 615 { 616 // Load config from persist store. 617 initConfig(); 618 } 619 620 static EventServiceManager& 621 getInstance(boost::asio::io_context* ioc = nullptr) 622 { 623 static EventServiceManager handler(*ioc); 624 return handler; 625 } 626 627 void initConfig() 628 { 629 loadOldBehavior(); 630 631 persistent_data::EventServiceConfig eventServiceConfig = 632 persistent_data::EventServiceStore::getInstance() 633 .getEventServiceConfig(); 634 635 serviceEnabled = eventServiceConfig.enabled; 636 retryAttempts = eventServiceConfig.retryAttempts; 637 retryTimeoutInterval = eventServiceConfig.retryTimeoutInterval; 638 639 for (const auto& it : persistent_data::EventServiceStore::getInstance() 640 .subscriptionsConfigMap) 641 { 642 std::shared_ptr<persistent_data::UserSubscription> newSub = 643 it.second; 644 645 boost::urls::result<boost::urls::url> url = 646 boost::urls::parse_absolute_uri(newSub->destinationUrl); 647 648 if (!url) 649 { 650 BMCWEB_LOG_ERROR( 651 "Failed to validate and split destination url"); 652 continue; 653 } 654 std::shared_ptr<Subscription> subValue = 655 std::make_shared<Subscription>(*url, ioc); 656 657 subValue->id = newSub->id; 658 subValue->destinationUrl = newSub->destinationUrl; 659 subValue->protocol = newSub->protocol; 660 subValue->retryPolicy = newSub->retryPolicy; 661 subValue->customText = newSub->customText; 662 subValue->eventFormatType = newSub->eventFormatType; 663 subValue->subscriptionType = newSub->subscriptionType; 664 subValue->registryMsgIds = newSub->registryMsgIds; 665 subValue->registryPrefixes = newSub->registryPrefixes; 666 subValue->resourceTypes = newSub->resourceTypes; 667 subValue->httpHeaders = newSub->httpHeaders; 668 subValue->metricReportDefinitions = newSub->metricReportDefinitions; 669 670 if (subValue->id.empty()) 671 { 672 BMCWEB_LOG_ERROR("Failed to add subscription"); 673 } 674 subscriptionsMap.insert(std::pair(subValue->id, subValue)); 675 676 updateNoOfSubscribersCount(); 677 678 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 679 680 cacheRedfishLogFile(); 681 682 #endif 683 // Update retry configuration. 684 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 685 } 686 } 687 688 static void loadOldBehavior() 689 { 690 std::ifstream eventConfigFile(eventServiceFile); 691 if (!eventConfigFile.good()) 692 { 693 BMCWEB_LOG_DEBUG("Old eventService config not exist"); 694 return; 695 } 696 auto jsonData = nlohmann::json::parse(eventConfigFile, nullptr, false); 697 if (jsonData.is_discarded()) 698 { 699 BMCWEB_LOG_ERROR("Old eventService config parse error."); 700 return; 701 } 702 703 for (const auto& item : jsonData.items()) 704 { 705 if (item.key() == "Configuration") 706 { 707 persistent_data::EventServiceStore::getInstance() 708 .getEventServiceConfig() 709 .fromJson(item.value()); 710 } 711 else if (item.key() == "Subscriptions") 712 { 713 for (const auto& elem : item.value()) 714 { 715 std::shared_ptr<persistent_data::UserSubscription> 716 newSubscription = 717 persistent_data::UserSubscription::fromJson(elem, 718 true); 719 if (newSubscription == nullptr) 720 { 721 BMCWEB_LOG_ERROR("Problem reading subscription " 722 "from old persistent store"); 723 continue; 724 } 725 726 std::uniform_int_distribution<uint32_t> dist(0); 727 bmcweb::OpenSSLGenerator gen; 728 729 std::string id; 730 731 int retry = 3; 732 while (retry != 0) 733 { 734 id = std::to_string(dist(gen)); 735 if (gen.error()) 736 { 737 retry = 0; 738 break; 739 } 740 newSubscription->id = id; 741 auto inserted = 742 persistent_data::EventServiceStore::getInstance() 743 .subscriptionsConfigMap.insert( 744 std::pair(id, newSubscription)); 745 if (inserted.second) 746 { 747 break; 748 } 749 --retry; 750 } 751 752 if (retry <= 0) 753 { 754 BMCWEB_LOG_ERROR( 755 "Failed to generate random number from old " 756 "persistent store"); 757 continue; 758 } 759 } 760 } 761 762 persistent_data::getConfig().writeData(); 763 std::remove(eventServiceFile); 764 BMCWEB_LOG_DEBUG("Remove old eventservice config"); 765 } 766 } 767 768 void updateSubscriptionData() const 769 { 770 persistent_data::EventServiceStore::getInstance() 771 .eventServiceConfig.enabled = serviceEnabled; 772 persistent_data::EventServiceStore::getInstance() 773 .eventServiceConfig.retryAttempts = retryAttempts; 774 persistent_data::EventServiceStore::getInstance() 775 .eventServiceConfig.retryTimeoutInterval = retryTimeoutInterval; 776 777 persistent_data::getConfig().writeData(); 778 } 779 780 void setEventServiceConfig(const persistent_data::EventServiceConfig& cfg) 781 { 782 bool updateConfig = false; 783 bool updateRetryCfg = false; 784 785 if (serviceEnabled != cfg.enabled) 786 { 787 serviceEnabled = cfg.enabled; 788 if (serviceEnabled && noOfMetricReportSubscribers != 0U) 789 { 790 registerMetricReportSignal(); 791 } 792 else 793 { 794 unregisterMetricReportSignal(); 795 } 796 updateConfig = true; 797 } 798 799 if (retryAttempts != cfg.retryAttempts) 800 { 801 retryAttempts = cfg.retryAttempts; 802 updateConfig = true; 803 updateRetryCfg = true; 804 } 805 806 if (retryTimeoutInterval != cfg.retryTimeoutInterval) 807 { 808 retryTimeoutInterval = cfg.retryTimeoutInterval; 809 updateConfig = true; 810 updateRetryCfg = true; 811 } 812 813 if (updateConfig) 814 { 815 updateSubscriptionData(); 816 } 817 818 if (updateRetryCfg) 819 { 820 // Update the changed retry config to all subscriptions 821 for (const auto& it : 822 EventServiceManager::getInstance().subscriptionsMap) 823 { 824 Subscription& entry = *it.second; 825 entry.updateRetryConfig(retryAttempts, retryTimeoutInterval); 826 } 827 } 828 } 829 830 void updateNoOfSubscribersCount() 831 { 832 size_t eventLogSubCount = 0; 833 size_t metricReportSubCount = 0; 834 for (const auto& it : subscriptionsMap) 835 { 836 std::shared_ptr<Subscription> entry = it.second; 837 if (entry->eventFormatType == eventFormatType) 838 { 839 eventLogSubCount++; 840 } 841 else if (entry->eventFormatType == metricReportFormatType) 842 { 843 metricReportSubCount++; 844 } 845 } 846 847 noOfEventLogSubscribers = eventLogSubCount; 848 if (noOfMetricReportSubscribers != metricReportSubCount) 849 { 850 noOfMetricReportSubscribers = metricReportSubCount; 851 if (noOfMetricReportSubscribers != 0U) 852 { 853 registerMetricReportSignal(); 854 } 855 else 856 { 857 unregisterMetricReportSignal(); 858 } 859 } 860 } 861 862 std::shared_ptr<Subscription> getSubscription(const std::string& id) 863 { 864 auto obj = subscriptionsMap.find(id); 865 if (obj == subscriptionsMap.end()) 866 { 867 BMCWEB_LOG_ERROR("No subscription exist with ID:{}", id); 868 return nullptr; 869 } 870 std::shared_ptr<Subscription> subValue = obj->second; 871 return subValue; 872 } 873 874 std::string addSubscription(const std::shared_ptr<Subscription>& subValue, 875 const bool updateFile = true) 876 { 877 std::uniform_int_distribution<uint32_t> dist(0); 878 bmcweb::OpenSSLGenerator gen; 879 880 std::string id; 881 882 int retry = 3; 883 while (retry != 0) 884 { 885 id = std::to_string(dist(gen)); 886 if (gen.error()) 887 { 888 retry = 0; 889 break; 890 } 891 auto inserted = subscriptionsMap.insert(std::pair(id, subValue)); 892 if (inserted.second) 893 { 894 break; 895 } 896 --retry; 897 } 898 899 if (retry <= 0) 900 { 901 BMCWEB_LOG_ERROR("Failed to generate random number"); 902 return ""; 903 } 904 905 std::shared_ptr<persistent_data::UserSubscription> newSub = 906 std::make_shared<persistent_data::UserSubscription>(); 907 newSub->id = id; 908 newSub->destinationUrl = subValue->destinationUrl; 909 newSub->protocol = subValue->protocol; 910 newSub->retryPolicy = subValue->retryPolicy; 911 newSub->customText = subValue->customText; 912 newSub->eventFormatType = subValue->eventFormatType; 913 newSub->subscriptionType = subValue->subscriptionType; 914 newSub->registryMsgIds = subValue->registryMsgIds; 915 newSub->registryPrefixes = subValue->registryPrefixes; 916 newSub->resourceTypes = subValue->resourceTypes; 917 newSub->httpHeaders = subValue->httpHeaders; 918 newSub->metricReportDefinitions = subValue->metricReportDefinitions; 919 persistent_data::EventServiceStore::getInstance() 920 .subscriptionsConfigMap.emplace(newSub->id, newSub); 921 922 updateNoOfSubscribersCount(); 923 924 if (updateFile) 925 { 926 updateSubscriptionData(); 927 } 928 929 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 930 if (redfishLogFilePosition != 0) 931 { 932 cacheRedfishLogFile(); 933 } 934 #endif 935 // Update retry configuration. 936 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 937 938 // Set Subscription ID for back trace 939 subValue->setSubscriptionId(id); 940 return id; 941 } 942 943 bool isSubscriptionExist(const std::string& id) 944 { 945 auto obj = subscriptionsMap.find(id); 946 return obj != subscriptionsMap.end(); 947 } 948 949 void deleteSubscription(const std::string& id) 950 { 951 auto obj = subscriptionsMap.find(id); 952 if (obj != subscriptionsMap.end()) 953 { 954 subscriptionsMap.erase(obj); 955 auto obj2 = persistent_data::EventServiceStore::getInstance() 956 .subscriptionsConfigMap.find(id); 957 persistent_data::EventServiceStore::getInstance() 958 .subscriptionsConfigMap.erase(obj2); 959 updateNoOfSubscribersCount(); 960 updateSubscriptionData(); 961 } 962 } 963 964 void deleteSseSubscription(const crow::sse_socket::Connection& thisConn) 965 { 966 for (const auto& it : subscriptionsMap) 967 { 968 std::shared_ptr<Subscription> entry = it.second; 969 bool entryIsThisConn = entry->matchSseId(thisConn); 970 if (entryIsThisConn) 971 { 972 persistent_data::EventServiceStore::getInstance() 973 .subscriptionsConfigMap.erase( 974 it.second->getSubscriptionId()); 975 return; 976 } 977 } 978 } 979 980 size_t getNumberOfSubscriptions() const 981 { 982 return subscriptionsMap.size(); 983 } 984 985 size_t getNumberOfSSESubscriptions() const 986 { 987 auto size = std::ranges::count_if( 988 subscriptionsMap, 989 [](const std::pair<std::string, std::shared_ptr<Subscription>>& 990 entry) { 991 return (entry.second->subscriptionType == subscriptionTypeSSE); 992 }); 993 return static_cast<size_t>(size); 994 } 995 996 std::vector<std::string> getAllIDs() 997 { 998 std::vector<std::string> idList; 999 for (const auto& it : subscriptionsMap) 1000 { 1001 idList.emplace_back(it.first); 1002 } 1003 return idList; 1004 } 1005 1006 bool sendTestEventLog() 1007 { 1008 for (const auto& it : subscriptionsMap) 1009 { 1010 std::shared_ptr<Subscription> entry = it.second; 1011 if (!entry->sendTestEventLog()) 1012 { 1013 return false; 1014 } 1015 } 1016 return true; 1017 } 1018 1019 void sendEvent(nlohmann::json eventMessage, const std::string& origin, 1020 const std::string& resType) 1021 { 1022 if (!serviceEnabled || (noOfEventLogSubscribers == 0U)) 1023 { 1024 BMCWEB_LOG_DEBUG("EventService disabled or no Subscriptions."); 1025 return; 1026 } 1027 nlohmann::json eventRecord = nlohmann::json::array(); 1028 1029 eventMessage["EventId"] = eventId; 1030 // MemberId is 0 : since we are sending one event record. 1031 eventMessage["MemberId"] = 0; 1032 eventMessage["EventTimestamp"] = 1033 redfish::time_utils::getDateTimeOffsetNow().first; 1034 eventMessage["OriginOfCondition"] = origin; 1035 1036 eventRecord.emplace_back(std::move(eventMessage)); 1037 1038 for (const auto& it : subscriptionsMap) 1039 { 1040 std::shared_ptr<Subscription> entry = it.second; 1041 bool isSubscribed = false; 1042 // Search the resourceTypes list for the subscription. 1043 // If resourceTypes list is empty, don't filter events 1044 // send everything. 1045 if (!entry->resourceTypes.empty()) 1046 { 1047 for (const auto& resource : entry->resourceTypes) 1048 { 1049 if (resType == resource) 1050 { 1051 BMCWEB_LOG_INFO( 1052 "ResourceType {} found in the subscribed list", 1053 resource); 1054 isSubscribed = true; 1055 break; 1056 } 1057 } 1058 } 1059 else // resourceTypes list is empty. 1060 { 1061 isSubscribed = true; 1062 } 1063 if (isSubscribed) 1064 { 1065 nlohmann::json msgJson; 1066 1067 msgJson["@odata.type"] = "#Event.v1_4_0.Event"; 1068 msgJson["Name"] = "Event Log"; 1069 msgJson["Id"] = eventId; 1070 msgJson["Events"] = eventRecord; 1071 1072 std::string strMsg = msgJson.dump( 1073 2, ' ', true, nlohmann::json::error_handler_t::replace); 1074 entry->sendEvent(std::move(strMsg)); 1075 eventId++; // increament the eventId 1076 } 1077 else 1078 { 1079 BMCWEB_LOG_INFO("Not subscribed to this resource"); 1080 } 1081 } 1082 } 1083 1084 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 1085 1086 void resetRedfishFilePosition() 1087 { 1088 // Control would be here when Redfish file is created. 1089 // Reset File Position as new file is created 1090 redfishLogFilePosition = 0; 1091 } 1092 1093 void cacheRedfishLogFile() 1094 { 1095 // Open the redfish file and read till the last record. 1096 1097 std::ifstream logStream(redfishEventLogFile); 1098 if (!logStream.good()) 1099 { 1100 BMCWEB_LOG_ERROR(" Redfish log file open failed "); 1101 return; 1102 } 1103 std::string logEntry; 1104 while (std::getline(logStream, logEntry)) 1105 { 1106 redfishLogFilePosition = logStream.tellg(); 1107 } 1108 } 1109 1110 void readEventLogsFromFile() 1111 { 1112 std::ifstream logStream(redfishEventLogFile); 1113 if (!logStream.good()) 1114 { 1115 BMCWEB_LOG_ERROR(" Redfish log file open failed"); 1116 return; 1117 } 1118 1119 std::vector<EventLogObjectsType> eventRecords; 1120 1121 std::string logEntry; 1122 1123 // Get the read pointer to the next log to be read. 1124 logStream.seekg(redfishLogFilePosition); 1125 1126 while (std::getline(logStream, logEntry)) 1127 { 1128 // Update Pointer position 1129 redfishLogFilePosition = logStream.tellg(); 1130 1131 std::string idStr; 1132 if (!event_log::getUniqueEntryID(logEntry, idStr)) 1133 { 1134 continue; 1135 } 1136 1137 if (!serviceEnabled || noOfEventLogSubscribers == 0) 1138 { 1139 // If Service is not enabled, no need to compute 1140 // the remaining items below. 1141 // But, Loop must continue to keep track of Timestamp 1142 continue; 1143 } 1144 1145 std::string timestamp; 1146 std::string messageID; 1147 std::vector<std::string> messageArgs; 1148 if (event_log::getEventLogParams(logEntry, timestamp, messageID, 1149 messageArgs) != 0) 1150 { 1151 BMCWEB_LOG_DEBUG("Read eventLog entry params failed"); 1152 continue; 1153 } 1154 1155 std::string registryName; 1156 std::string messageKey; 1157 event_log::getRegistryAndMessageKey(messageID, registryName, 1158 messageKey); 1159 if (registryName.empty() || messageKey.empty()) 1160 { 1161 continue; 1162 } 1163 1164 eventRecords.emplace_back(idStr, timestamp, messageID, registryName, 1165 messageKey, messageArgs); 1166 } 1167 1168 if (!serviceEnabled || noOfEventLogSubscribers == 0) 1169 { 1170 BMCWEB_LOG_DEBUG("EventService disabled or no Subscriptions."); 1171 return; 1172 } 1173 1174 if (eventRecords.empty()) 1175 { 1176 // No Records to send 1177 BMCWEB_LOG_DEBUG("No log entries available to be transferred."); 1178 return; 1179 } 1180 1181 for (const auto& it : subscriptionsMap) 1182 { 1183 std::shared_ptr<Subscription> entry = it.second; 1184 if (entry->eventFormatType == "Event") 1185 { 1186 entry->filterAndSendEventLogs(eventRecords); 1187 } 1188 } 1189 } 1190 1191 static void watchRedfishEventLogFile() 1192 { 1193 if (!inotifyConn) 1194 { 1195 return; 1196 } 1197 1198 static std::array<char, 1024> readBuffer; 1199 1200 inotifyConn->async_read_some(boost::asio::buffer(readBuffer), 1201 [&](const boost::system::error_code& ec, 1202 const std::size_t& bytesTransferred) { 1203 if (ec) 1204 { 1205 BMCWEB_LOG_ERROR("Callback Error: {}", ec.message()); 1206 return; 1207 } 1208 std::size_t index = 0; 1209 while ((index + iEventSize) <= bytesTransferred) 1210 { 1211 struct inotify_event event 1212 {}; 1213 std::memcpy(&event, &readBuffer[index], iEventSize); 1214 if (event.wd == dirWatchDesc) 1215 { 1216 if ((event.len == 0) || 1217 (index + iEventSize + event.len > bytesTransferred)) 1218 { 1219 index += (iEventSize + event.len); 1220 continue; 1221 } 1222 1223 std::string fileName(&readBuffer[index + iEventSize]); 1224 if (fileName != "redfish") 1225 { 1226 index += (iEventSize + event.len); 1227 continue; 1228 } 1229 1230 BMCWEB_LOG_DEBUG( 1231 "Redfish log file created/deleted. event.name: {}", 1232 fileName); 1233 if (event.mask == IN_CREATE) 1234 { 1235 if (fileWatchDesc != -1) 1236 { 1237 BMCWEB_LOG_DEBUG( 1238 "Remove and Add inotify watcher on " 1239 "redfish event log file"); 1240 // Remove existing inotify watcher and add 1241 // with new redfish event log file. 1242 inotify_rm_watch(inotifyFd, fileWatchDesc); 1243 fileWatchDesc = -1; 1244 } 1245 1246 fileWatchDesc = inotify_add_watch( 1247 inotifyFd, redfishEventLogFile, IN_MODIFY); 1248 if (fileWatchDesc == -1) 1249 { 1250 BMCWEB_LOG_ERROR("inotify_add_watch failed for " 1251 "redfish log file."); 1252 return; 1253 } 1254 1255 EventServiceManager::getInstance() 1256 .resetRedfishFilePosition(); 1257 EventServiceManager::getInstance() 1258 .readEventLogsFromFile(); 1259 } 1260 else if ((event.mask == IN_DELETE) || 1261 (event.mask == IN_MOVED_TO)) 1262 { 1263 if (fileWatchDesc != -1) 1264 { 1265 inotify_rm_watch(inotifyFd, fileWatchDesc); 1266 fileWatchDesc = -1; 1267 } 1268 } 1269 } 1270 else if (event.wd == fileWatchDesc) 1271 { 1272 if (event.mask == IN_MODIFY) 1273 { 1274 EventServiceManager::getInstance() 1275 .readEventLogsFromFile(); 1276 } 1277 } 1278 index += (iEventSize + event.len); 1279 } 1280 1281 watchRedfishEventLogFile(); 1282 }); 1283 } 1284 1285 static int startEventLogMonitor(boost::asio::io_context& ioc) 1286 { 1287 inotifyConn.emplace(ioc); 1288 inotifyFd = inotify_init1(IN_NONBLOCK); 1289 if (inotifyFd == -1) 1290 { 1291 BMCWEB_LOG_ERROR("inotify_init1 failed."); 1292 return -1; 1293 } 1294 1295 // Add watch on directory to handle redfish event log file 1296 // create/delete. 1297 dirWatchDesc = inotify_add_watch(inotifyFd, redfishEventLogDir, 1298 IN_CREATE | IN_MOVED_TO | IN_DELETE); 1299 if (dirWatchDesc == -1) 1300 { 1301 BMCWEB_LOG_ERROR( 1302 "inotify_add_watch failed for event log directory."); 1303 return -1; 1304 } 1305 1306 // Watch redfish event log file for modifications. 1307 fileWatchDesc = inotify_add_watch(inotifyFd, redfishEventLogFile, 1308 IN_MODIFY); 1309 if (fileWatchDesc == -1) 1310 { 1311 BMCWEB_LOG_ERROR("inotify_add_watch failed for redfish log file."); 1312 // Don't return error if file not exist. 1313 // Watch on directory will handle create/delete of file. 1314 } 1315 1316 // monitor redfish event log file 1317 inotifyConn->assign(inotifyFd); 1318 watchRedfishEventLogFile(); 1319 1320 return 0; 1321 } 1322 1323 #endif 1324 static void getReadingsForReport(sdbusplus::message_t& msg) 1325 { 1326 if (msg.is_method_error()) 1327 { 1328 BMCWEB_LOG_ERROR("TelemetryMonitor Signal error"); 1329 return; 1330 } 1331 1332 sdbusplus::message::object_path path(msg.get_path()); 1333 std::string id = path.filename(); 1334 if (id.empty()) 1335 { 1336 BMCWEB_LOG_ERROR("Failed to get Id from path"); 1337 return; 1338 } 1339 1340 std::string interface; 1341 dbus::utility::DBusPropertiesMap props; 1342 std::vector<std::string> invalidProps; 1343 msg.read(interface, props, invalidProps); 1344 1345 auto found = std::ranges::find_if( 1346 props, [](const auto& x) { return x.first == "Readings"; }); 1347 if (found == props.end()) 1348 { 1349 BMCWEB_LOG_INFO("Failed to get Readings from Report properties"); 1350 return; 1351 } 1352 1353 const telemetry::TimestampReadings* readings = 1354 std::get_if<telemetry::TimestampReadings>(&found->second); 1355 if (readings == nullptr) 1356 { 1357 BMCWEB_LOG_INFO("Failed to get Readings from Report properties"); 1358 return; 1359 } 1360 1361 for (const auto& it : 1362 EventServiceManager::getInstance().subscriptionsMap) 1363 { 1364 Subscription& entry = *it.second; 1365 if (entry.eventFormatType == metricReportFormatType) 1366 { 1367 entry.filterAndSendReports(id, *readings); 1368 } 1369 } 1370 } 1371 1372 void unregisterMetricReportSignal() 1373 { 1374 if (matchTelemetryMonitor) 1375 { 1376 BMCWEB_LOG_DEBUG("Metrics report signal - Unregister"); 1377 matchTelemetryMonitor.reset(); 1378 matchTelemetryMonitor = nullptr; 1379 } 1380 } 1381 1382 void registerMetricReportSignal() 1383 { 1384 if (!serviceEnabled || matchTelemetryMonitor) 1385 { 1386 BMCWEB_LOG_DEBUG("Not registering metric report signal."); 1387 return; 1388 } 1389 1390 BMCWEB_LOG_DEBUG("Metrics report signal - Register"); 1391 std::string matchStr = "type='signal',member='PropertiesChanged'," 1392 "interface='org.freedesktop.DBus.Properties'," 1393 "arg0=xyz.openbmc_project.Telemetry.Report"; 1394 1395 matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match_t>( 1396 *crow::connections::systemBus, matchStr, getReadingsForReport); 1397 } 1398 }; 1399 1400 } // namespace redfish 1401