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