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 "persistent_data.hpp" 23 #include "random.hpp" 24 #include "registries.hpp" 25 #include "registries/base_message_registry.hpp" 26 #include "registries/openbmc_message_registry.hpp" 27 #include "registries/task_event_message_registry.hpp" 28 #include "server_sent_events.hpp" 29 #include "utility.hpp" 30 #include "utils/json_utils.hpp" 31 32 #include <sys/inotify.h> 33 34 #include <boost/algorithm/string/classification.hpp> 35 #include <boost/algorithm/string/split.hpp> 36 #include <boost/asio/io_context.hpp> 37 #include <boost/container/flat_map.hpp> 38 #include <sdbusplus/bus/match.hpp> 39 40 #include <cstdlib> 41 #include <ctime> 42 #include <fstream> 43 #include <memory> 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* eventServiceFile = 56 "/var/lib/bmcweb/eventservice_config.json"; 57 58 namespace registries 59 { 60 inline std::span<const MessageEntry> 61 getRegistryFromPrefix(const std::string& registryName) 62 { 63 if (task_event::header.registryPrefix == registryName) 64 { 65 return {task_event::registry}; 66 } 67 if (openbmc::header.registryPrefix == registryName) 68 { 69 return {openbmc::registry}; 70 } 71 if (base::header.registryPrefix == registryName) 72 { 73 return {base::registry}; 74 } 75 return {openbmc::registry}; 76 } 77 } // namespace registries 78 79 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 80 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 81 static std::optional<boost::asio::posix::stream_descriptor> inotifyConn; 82 static constexpr const char* redfishEventLogDir = "/var/log"; 83 static constexpr const char* redfishEventLogFile = "/var/log/redfish"; 84 static constexpr const size_t iEventSize = sizeof(inotify_event); 85 86 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 87 static int inotifyFd = -1; 88 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 89 static int dirWatchDesc = -1; 90 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 91 static int fileWatchDesc = -1; 92 93 // <ID, timestamp, RedfishLogId, registryPrefix, MessageId, MessageArgs> 94 using EventLogObjectsType = 95 std::tuple<std::string, std::string, std::string, std::string, std::string, 96 std::vector<std::string>>; 97 98 namespace registries 99 { 100 static const Message* 101 getMsgFromRegistry(const std::string& messageKey, 102 const std::span<const MessageEntry>& registry) 103 { 104 std::span<const MessageEntry>::iterator messageIt = 105 std::find_if(registry.begin(), registry.end(), 106 [&messageKey](const MessageEntry& messageEntry) { 107 return messageKey == messageEntry.first; 108 }); 109 if (messageIt != registry.end()) 110 { 111 return &messageIt->second; 112 } 113 114 return nullptr; 115 } 116 117 static const Message* formatMessage(const std::string_view& messageID) 118 { 119 // Redfish MessageIds are in the form 120 // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find 121 // the right Message 122 std::vector<std::string> fields; 123 fields.reserve(4); 124 boost::split(fields, messageID, boost::is_any_of(".")); 125 if (fields.size() != 4) 126 { 127 return nullptr; 128 } 129 const std::string& registryName = fields[0]; 130 const std::string& messageKey = fields[3]; 131 132 // Find the right registry and check it for the MessageKey 133 return getMsgFromRegistry(messageKey, getRegistryFromPrefix(registryName)); 134 } 135 } // namespace registries 136 137 namespace event_log 138 { 139 inline bool getUniqueEntryID(const std::string& logEntry, std::string& entryID) 140 { 141 static time_t prevTs = 0; 142 static int index = 0; 143 144 // Get the entry timestamp 145 std::time_t curTs = 0; 146 std::tm timeStruct = {}; 147 std::istringstream entryStream(logEntry); 148 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 149 { 150 curTs = std::mktime(&timeStruct); 151 if (curTs == -1) 152 { 153 return false; 154 } 155 } 156 // If the timestamp isn't unique, increment the index 157 index = (curTs == prevTs) ? index + 1 : 0; 158 159 // Save the timestamp 160 prevTs = curTs; 161 162 entryID = std::to_string(curTs); 163 if (index > 0) 164 { 165 entryID += "_" + std::to_string(index); 166 } 167 return true; 168 } 169 170 inline int getEventLogParams(const std::string& logEntry, 171 std::string& timestamp, std::string& messageID, 172 std::vector<std::string>& messageArgs) 173 { 174 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>" 175 // First get the Timestamp 176 size_t space = logEntry.find_first_of(' '); 177 if (space == std::string::npos) 178 { 179 return -EINVAL; 180 } 181 timestamp = logEntry.substr(0, space); 182 // Then get the log contents 183 size_t entryStart = logEntry.find_first_not_of(' ', space); 184 if (entryStart == std::string::npos) 185 { 186 return -EINVAL; 187 } 188 std::string_view entry(logEntry); 189 entry.remove_prefix(entryStart); 190 // Use split to separate the entry into its fields 191 std::vector<std::string> logEntryFields; 192 boost::split(logEntryFields, entry, boost::is_any_of(","), 193 boost::token_compress_on); 194 // We need at least a MessageId to be valid 195 if (logEntryFields.empty()) 196 { 197 return -EINVAL; 198 } 199 messageID = logEntryFields[0]; 200 201 // Get the MessageArgs from the log if there are any 202 if (logEntryFields.size() > 1) 203 { 204 const std::string& messageArgsStart = logEntryFields[1]; 205 // If the first string is empty, assume there are no MessageArgs 206 if (!messageArgsStart.empty()) 207 { 208 messageArgs.assign(logEntryFields.begin() + 1, 209 logEntryFields.end()); 210 } 211 } 212 213 return 0; 214 } 215 216 inline void getRegistryAndMessageKey(const std::string& messageID, 217 std::string& registryName, 218 std::string& messageKey) 219 { 220 // Redfish MessageIds are in the form 221 // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find 222 // the right Message 223 std::vector<std::string> fields; 224 fields.reserve(4); 225 boost::split(fields, messageID, boost::is_any_of(".")); 226 if (fields.size() == 4) 227 { 228 registryName = fields[0]; 229 messageKey = fields[3]; 230 } 231 } 232 233 inline int formatEventLogEntry(const std::string& logEntryID, 234 const std::string& messageID, 235 const std::span<std::string_view> messageArgs, 236 std::string timestamp, 237 const std::string& customText, 238 nlohmann::json& logEntryJson) 239 { 240 // Get the Message from the MessageRegistry 241 const registries::Message* message = registries::formatMessage(messageID); 242 243 if (message == nullptr) 244 { 245 return -1; 246 } 247 248 std::string msg = 249 redfish::registries::fillMessageArgs(messageArgs, message->message); 250 if (msg.empty()) 251 { 252 return -1; 253 } 254 255 // Get the Created time from the timestamp. The log timestamp is in 256 // RFC3339 format which matches the Redfish format except for the 257 // fractional seconds between the '.' and the '+', so just remove them. 258 std::size_t dot = timestamp.find_first_of('.'); 259 std::size_t plus = timestamp.find_first_of('+', dot); 260 if (dot != std::string::npos && plus != std::string::npos) 261 { 262 timestamp.erase(dot, plus - dot); 263 } 264 265 // Fill in the log entry with the gathered data 266 logEntryJson["EventId"] = logEntryID; 267 logEntryJson["EventType"] = "Event"; 268 logEntryJson["Severity"] = message->messageSeverity; 269 logEntryJson["Message"] = std::move(msg); 270 logEntryJson["MessageId"] = messageID; 271 logEntryJson["MessageArgs"] = messageArgs; 272 logEntryJson["EventTimestamp"] = std::move(timestamp); 273 logEntryJson["Context"] = customText; 274 return 0; 275 } 276 277 } // namespace event_log 278 #endif 279 280 inline bool isFilterQuerySpecialChar(char c) 281 { 282 switch (c) 283 { 284 case '(': 285 case ')': 286 case '\'': 287 return true; 288 default: 289 return false; 290 } 291 } 292 293 inline bool 294 readSSEQueryParams(std::string sseFilter, std::string& formatType, 295 std::vector<std::string>& messageIds, 296 std::vector<std::string>& registryPrefixes, 297 std::vector<std::string>& metricReportDefinitions) 298 { 299 sseFilter.erase(std::remove_if(sseFilter.begin(), sseFilter.end(), 300 isFilterQuerySpecialChar), 301 sseFilter.end()); 302 303 std::vector<std::string> result; 304 boost::split(result, sseFilter, boost::is_any_of(" "), 305 boost::token_compress_on); 306 307 BMCWEB_LOG_DEBUG << "No of tokens in SEE query: " << result.size(); 308 309 constexpr uint8_t divisor = 4; 310 constexpr uint8_t minTokenSize = 3; 311 if (result.size() % divisor != minTokenSize) 312 { 313 BMCWEB_LOG_ERROR << "Invalid SSE filter specified."; 314 return false; 315 } 316 317 for (std::size_t i = 0; i < result.size(); i += divisor) 318 { 319 const std::string& key = result[i]; 320 const std::string& op = result[i + 1]; 321 const std::string& value = result[i + 2]; 322 323 if ((i + minTokenSize) < result.size()) 324 { 325 const std::string& separator = result[i + minTokenSize]; 326 // SSE supports only "or" and "and" in query params. 327 if ((separator != "or") && (separator != "and")) 328 { 329 BMCWEB_LOG_ERROR 330 << "Invalid group operator in SSE query parameters"; 331 return false; 332 } 333 } 334 335 // SSE supports only "eq" as per spec. 336 if (op != "eq") 337 { 338 BMCWEB_LOG_ERROR 339 << "Invalid assignment operator in SSE query parameters"; 340 return false; 341 } 342 343 BMCWEB_LOG_DEBUG << key << " : " << value; 344 if (key == "EventFormatType") 345 { 346 formatType = value; 347 } 348 else if (key == "MessageId") 349 { 350 messageIds.push_back(value); 351 } 352 else if (key == "RegistryPrefix") 353 { 354 registryPrefixes.push_back(value); 355 } 356 else if (key == "MetricReportDefinition") 357 { 358 metricReportDefinitions.push_back(value); 359 } 360 else 361 { 362 BMCWEB_LOG_ERROR << "Invalid property(" << key 363 << ")in SSE filter query."; 364 return false; 365 } 366 } 367 return true; 368 } 369 370 class Subscription : public persistent_data::UserSubscription 371 { 372 public: 373 Subscription(const Subscription&) = delete; 374 Subscription& operator=(const Subscription&) = delete; 375 Subscription(Subscription&&) = delete; 376 Subscription& operator=(Subscription&&) = delete; 377 378 Subscription(const std::string& inHost, uint16_t inPort, 379 const std::string& inPath, const std::string& inUriProto) : 380 eventSeqNum(1), 381 host(inHost), port(inPort), path(inPath), uriProto(inUriProto) 382 { 383 // Subscription constructor 384 } 385 386 explicit Subscription( 387 const std::shared_ptr<boost::asio::ip::tcp::socket>& adaptor) : 388 eventSeqNum(1), 389 sseConn(std::make_shared<crow::ServerSentEvents>(adaptor)) 390 {} 391 392 ~Subscription() = default; 393 394 bool sendEvent(std::string& msg) 395 { 396 persistent_data::EventServiceConfig eventServiceConfig = 397 persistent_data::EventServiceStore::getInstance() 398 .getEventServiceConfig(); 399 if (!eventServiceConfig.enabled) 400 { 401 return false; 402 } 403 404 bool useSSL = (uriProto == "https"); 405 // A connection pool will be created if one does not already exist 406 crow::HttpClient::getInstance().sendData( 407 msg, id, host, port, path, useSSL, httpHeaders, 408 boost::beast::http::verb::post, retryPolicyName); 409 eventSeqNum++; 410 411 if (sseConn != nullptr) 412 { 413 sseConn->sendData(eventSeqNum, msg); 414 } 415 return true; 416 } 417 418 bool sendTestEventLog() 419 { 420 nlohmann::json logEntryArray; 421 logEntryArray.push_back({}); 422 nlohmann::json& logEntryJson = logEntryArray.back(); 423 424 logEntryJson["EventId"] = "TestID"; 425 logEntryJson["EventType"] = "Event"; 426 logEntryJson["Severity"] = "OK"; 427 logEntryJson["Message"] = "Generated test event"; 428 logEntryJson["MessageId"] = "OpenBMC.0.2.TestEventLog"; 429 logEntryJson["MessageArgs"] = nlohmann::json::array(); 430 logEntryJson["EventTimestamp"] = 431 redfish::time_utils::getDateTimeOffsetNow().first; 432 logEntryJson["Context"] = customText; 433 434 nlohmann::json msg; 435 msg["@odata.type"] = "#Event.v1_4_0.Event"; 436 msg["Id"] = std::to_string(eventSeqNum); 437 msg["Name"] = "Event Log"; 438 msg["Events"] = logEntryArray; 439 440 std::string strMsg = 441 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 442 return this->sendEvent(strMsg); 443 } 444 445 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 446 void filterAndSendEventLogs( 447 const std::vector<EventLogObjectsType>& eventRecords) 448 { 449 nlohmann::json logEntryArray; 450 for (const EventLogObjectsType& logEntry : eventRecords) 451 { 452 const std::string& idStr = std::get<0>(logEntry); 453 const std::string& timestamp = std::get<1>(logEntry); 454 const std::string& messageID = std::get<2>(logEntry); 455 const std::string& registryName = std::get<3>(logEntry); 456 const std::string& messageKey = std::get<4>(logEntry); 457 const std::vector<std::string>& messageArgs = std::get<5>(logEntry); 458 459 // If registryPrefixes list is empty, don't filter events 460 // send everything. 461 if (!registryPrefixes.empty()) 462 { 463 auto obj = std::find(registryPrefixes.begin(), 464 registryPrefixes.end(), registryName); 465 if (obj == registryPrefixes.end()) 466 { 467 continue; 468 } 469 } 470 471 // If registryMsgIds list is empty, don't filter events 472 // send everything. 473 if (!registryMsgIds.empty()) 474 { 475 auto obj = std::find(registryMsgIds.begin(), 476 registryMsgIds.end(), messageKey); 477 if (obj == registryMsgIds.end()) 478 { 479 continue; 480 } 481 } 482 483 std::vector<std::string_view> messageArgsView(messageArgs.begin(), 484 messageArgs.end()); 485 486 logEntryArray.push_back({}); 487 nlohmann::json& bmcLogEntry = logEntryArray.back(); 488 if (event_log::formatEventLogEntry(idStr, messageID, 489 messageArgsView, timestamp, 490 customText, bmcLogEntry) != 0) 491 { 492 BMCWEB_LOG_DEBUG << "Read eventLog entry failed"; 493 continue; 494 } 495 } 496 497 if (logEntryArray.empty()) 498 { 499 BMCWEB_LOG_DEBUG << "No log entries available to be transferred."; 500 return; 501 } 502 503 nlohmann::json msg; 504 msg["@odata.type"] = "#Event.v1_4_0.Event"; 505 msg["Id"] = std::to_string(eventSeqNum); 506 msg["Name"] = "Event Log"; 507 msg["Events"] = logEntryArray; 508 509 std::string strMsg = 510 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 511 this->sendEvent(strMsg); 512 } 513 #endif 514 515 void filterAndSendReports(const std::string& reportId, 516 const telemetry::TimestampReadings& var) 517 { 518 boost::urls::url mrdUri = 519 crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", 520 "MetricReportDefinitions", reportId); 521 522 // Empty list means no filter. Send everything. 523 if (!metricReportDefinitions.empty()) 524 { 525 if (std::find(metricReportDefinitions.begin(), 526 metricReportDefinitions.end(), 527 mrdUri.buffer()) == metricReportDefinitions.end()) 528 { 529 return; 530 } 531 } 532 533 nlohmann::json msg; 534 if (!telemetry::fillReport(msg, reportId, var)) 535 { 536 BMCWEB_LOG_ERROR << "Failed to fill the MetricReport for DBus " 537 "Report with id " 538 << reportId; 539 return; 540 } 541 542 // Context is set by user during Event subscription and it must be 543 // set for MetricReport response. 544 if (!customText.empty()) 545 { 546 msg["Context"] = customText; 547 } 548 549 std::string strMsg = 550 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 551 this->sendEvent(strMsg); 552 } 553 554 void updateRetryConfig(const uint32_t retryAttempts, 555 const uint32_t retryTimeoutInterval) 556 { 557 crow::HttpClient::getInstance().setRetryConfig( 558 retryAttempts, retryTimeoutInterval, retryRespHandler, 559 retryPolicyName); 560 } 561 562 void updateRetryPolicy() 563 { 564 crow::HttpClient::getInstance().setRetryPolicy(retryPolicy, 565 retryPolicyName); 566 } 567 568 uint64_t getEventSeqNum() const 569 { 570 return eventSeqNum; 571 } 572 573 private: 574 uint64_t eventSeqNum; 575 std::string host; 576 uint16_t port = 0; 577 std::string path; 578 std::string uriProto; 579 std::shared_ptr<crow::ServerSentEvents> sseConn = nullptr; 580 std::string retryPolicyName = "SubscriptionEvent"; 581 582 // Check used to indicate what response codes are valid as part of our retry 583 // policy. 2XX is considered acceptable 584 static boost::system::error_code retryRespHandler(unsigned int respCode) 585 { 586 BMCWEB_LOG_DEBUG 587 << "Checking response code validity for SubscriptionEvent"; 588 if ((respCode < 200) || (respCode >= 300)) 589 { 590 return boost::system::errc::make_error_code( 591 boost::system::errc::result_out_of_range); 592 } 593 594 // Return 0 if the response code is valid 595 return boost::system::errc::make_error_code( 596 boost::system::errc::success); 597 } 598 }; 599 600 class EventServiceManager 601 { 602 private: 603 bool serviceEnabled = false; 604 uint32_t retryAttempts = 0; 605 uint32_t retryTimeoutInterval = 0; 606 607 EventServiceManager() 608 { 609 // Load config from persist store. 610 initConfig(); 611 } 612 613 std::streampos redfishLogFilePosition{0}; 614 size_t noOfEventLogSubscribers{0}; 615 size_t noOfMetricReportSubscribers{0}; 616 std::shared_ptr<sdbusplus::bus::match_t> matchTelemetryMonitor; 617 boost::container::flat_map<std::string, std::shared_ptr<Subscription>> 618 subscriptionsMap; 619 620 uint64_t eventId{1}; 621 622 public: 623 EventServiceManager(const EventServiceManager&) = delete; 624 EventServiceManager& operator=(const EventServiceManager&) = delete; 625 EventServiceManager(EventServiceManager&&) = delete; 626 EventServiceManager& operator=(EventServiceManager&&) = delete; 627 ~EventServiceManager() = default; 628 629 static EventServiceManager& getInstance() 630 { 631 static EventServiceManager handler; 632 return handler; 633 } 634 635 void initConfig() 636 { 637 loadOldBehavior(); 638 639 persistent_data::EventServiceConfig eventServiceConfig = 640 persistent_data::EventServiceStore::getInstance() 641 .getEventServiceConfig(); 642 643 serviceEnabled = eventServiceConfig.enabled; 644 retryAttempts = eventServiceConfig.retryAttempts; 645 retryTimeoutInterval = eventServiceConfig.retryTimeoutInterval; 646 647 for (const auto& it : persistent_data::EventServiceStore::getInstance() 648 .subscriptionsConfigMap) 649 { 650 std::shared_ptr<persistent_data::UserSubscription> newSub = 651 it.second; 652 653 std::string host; 654 std::string urlProto; 655 uint16_t port = 0; 656 std::string path; 657 bool status = crow::utility::validateAndSplitUrl( 658 newSub->destinationUrl, urlProto, host, port, path); 659 660 if (!status) 661 { 662 BMCWEB_LOG_ERROR 663 << "Failed to validate and split destination url"; 664 continue; 665 } 666 std::shared_ptr<Subscription> subValue = 667 std::make_shared<Subscription>(host, port, path, urlProto); 668 669 subValue->id = newSub->id; 670 subValue->destinationUrl = newSub->destinationUrl; 671 subValue->protocol = newSub->protocol; 672 subValue->retryPolicy = newSub->retryPolicy; 673 subValue->customText = newSub->customText; 674 subValue->eventFormatType = newSub->eventFormatType; 675 subValue->subscriptionType = newSub->subscriptionType; 676 subValue->registryMsgIds = newSub->registryMsgIds; 677 subValue->registryPrefixes = newSub->registryPrefixes; 678 subValue->resourceTypes = newSub->resourceTypes; 679 subValue->httpHeaders = newSub->httpHeaders; 680 subValue->metricReportDefinitions = newSub->metricReportDefinitions; 681 682 if (subValue->id.empty()) 683 { 684 BMCWEB_LOG_ERROR << "Failed to add subscription"; 685 } 686 subscriptionsMap.insert(std::pair(subValue->id, subValue)); 687 688 updateNoOfSubscribersCount(); 689 690 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 691 692 cacheRedfishLogFile(); 693 694 #endif 695 // Update retry configuration. 696 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 697 subValue->updateRetryPolicy(); 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 std::shared_ptr<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 891 std::uniform_int_distribution<uint32_t> dist(0); 892 bmcweb::OpenSSLGenerator gen; 893 894 std::string id; 895 896 int retry = 3; 897 while (retry != 0) 898 { 899 id = std::to_string(dist(gen)); 900 if (gen.error()) 901 { 902 retry = 0; 903 break; 904 } 905 auto inserted = subscriptionsMap.insert(std::pair(id, subValue)); 906 if (inserted.second) 907 { 908 break; 909 } 910 --retry; 911 } 912 913 if (retry <= 0) 914 { 915 BMCWEB_LOG_ERROR << "Failed to generate random number"; 916 return ""; 917 } 918 919 std::shared_ptr<persistent_data::UserSubscription> newSub = 920 std::make_shared<persistent_data::UserSubscription>(); 921 newSub->id = id; 922 newSub->destinationUrl = subValue->destinationUrl; 923 newSub->protocol = subValue->protocol; 924 newSub->retryPolicy = subValue->retryPolicy; 925 newSub->customText = subValue->customText; 926 newSub->eventFormatType = subValue->eventFormatType; 927 newSub->subscriptionType = subValue->subscriptionType; 928 newSub->registryMsgIds = subValue->registryMsgIds; 929 newSub->registryPrefixes = subValue->registryPrefixes; 930 newSub->resourceTypes = subValue->resourceTypes; 931 newSub->httpHeaders = subValue->httpHeaders; 932 newSub->metricReportDefinitions = subValue->metricReportDefinitions; 933 persistent_data::EventServiceStore::getInstance() 934 .subscriptionsConfigMap.emplace(newSub->id, newSub); 935 936 updateNoOfSubscribersCount(); 937 938 if (updateFile) 939 { 940 updateSubscriptionData(); 941 } 942 943 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 944 if (redfishLogFilePosition != 0) 945 { 946 cacheRedfishLogFile(); 947 } 948 #endif 949 // Update retry configuration. 950 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 951 subValue->updateRetryPolicy(); 952 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 size_t getNumberOfSubscriptions() 978 { 979 return subscriptionsMap.size(); 980 } 981 982 std::vector<std::string> getAllIDs() 983 { 984 std::vector<std::string> idList; 985 for (const auto& it : subscriptionsMap) 986 { 987 idList.emplace_back(it.first); 988 } 989 return idList; 990 } 991 992 bool isDestinationExist(const std::string& destUrl) 993 { 994 for (const auto& it : subscriptionsMap) 995 { 996 std::shared_ptr<Subscription> entry = it.second; 997 if (entry->destinationUrl == destUrl) 998 { 999 BMCWEB_LOG_ERROR << "Destination exist already" << destUrl; 1000 return true; 1001 } 1002 } 1003 return false; 1004 } 1005 1006 bool sendTestEventLog() 1007 { 1008 for (const auto& it : this->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 : this->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 << "ResourceType " << resource 1052 << " found in the subscribed list"; 1053 isSubscribed = true; 1054 break; 1055 } 1056 } 1057 } 1058 else // resourceTypes list is empty. 1059 { 1060 isSubscribed = true; 1061 } 1062 if (isSubscribed) 1063 { 1064 nlohmann::json msgJson; 1065 1066 msgJson["@odata.type"] = "#Event.v1_4_0.Event"; 1067 msgJson["Name"] = "Event Log"; 1068 msgJson["Id"] = eventId; 1069 msgJson["Events"] = eventRecord; 1070 1071 std::string strMsg = msgJson.dump( 1072 2, ' ', true, nlohmann::json::error_handler_t::replace); 1073 entry->sendEvent(strMsg); 1074 eventId++; // increament the eventId 1075 } 1076 else 1077 { 1078 BMCWEB_LOG_INFO << "Not subscribed to this resource"; 1079 } 1080 } 1081 } 1082 void sendBroadcastMsg(const std::string& broadcastMsg) 1083 { 1084 for (const auto& it : this->subscriptionsMap) 1085 { 1086 std::shared_ptr<Subscription> entry = it.second; 1087 nlohmann::json msgJson; 1088 msgJson["Timestamp"] = 1089 redfish::time_utils::getDateTimeOffsetNow().first; 1090 msgJson["OriginOfCondition"] = "/ibm/v1/HMC/BroadcastService"; 1091 msgJson["Name"] = "Broadcast Message"; 1092 msgJson["Message"] = broadcastMsg; 1093 1094 std::string strMsg = msgJson.dump( 1095 2, ' ', true, nlohmann::json::error_handler_t::replace); 1096 entry->sendEvent(strMsg); 1097 } 1098 } 1099 1100 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 1101 1102 void resetRedfishFilePosition() 1103 { 1104 // Control would be here when Redfish file is created. 1105 // Reset File Position as new file is created 1106 redfishLogFilePosition = 0; 1107 } 1108 1109 void cacheRedfishLogFile() 1110 { 1111 // Open the redfish file and read till the last record. 1112 1113 std::ifstream logStream(redfishEventLogFile); 1114 if (!logStream.good()) 1115 { 1116 BMCWEB_LOG_ERROR << " Redfish log file open failed \n"; 1117 return; 1118 } 1119 std::string logEntry; 1120 while (std::getline(logStream, logEntry)) 1121 { 1122 redfishLogFilePosition = logStream.tellg(); 1123 } 1124 1125 BMCWEB_LOG_DEBUG << "Next Log Position : " << redfishLogFilePosition; 1126 } 1127 1128 void readEventLogsFromFile() 1129 { 1130 std::ifstream logStream(redfishEventLogFile); 1131 if (!logStream.good()) 1132 { 1133 BMCWEB_LOG_ERROR << " Redfish log file open failed"; 1134 return; 1135 } 1136 1137 std::vector<EventLogObjectsType> eventRecords; 1138 1139 std::string logEntry; 1140 1141 // Get the read pointer to the next log to be read. 1142 logStream.seekg(redfishLogFilePosition); 1143 1144 while (std::getline(logStream, logEntry)) 1145 { 1146 // Update Pointer position 1147 redfishLogFilePosition = logStream.tellg(); 1148 1149 std::string idStr; 1150 if (!event_log::getUniqueEntryID(logEntry, idStr)) 1151 { 1152 continue; 1153 } 1154 1155 if (!serviceEnabled || noOfEventLogSubscribers == 0) 1156 { 1157 // If Service is not enabled, no need to compute 1158 // the remaining items below. 1159 // But, Loop must continue to keep track of Timestamp 1160 continue; 1161 } 1162 1163 std::string timestamp; 1164 std::string messageID; 1165 std::vector<std::string> messageArgs; 1166 if (event_log::getEventLogParams(logEntry, timestamp, messageID, 1167 messageArgs) != 0) 1168 { 1169 BMCWEB_LOG_DEBUG << "Read eventLog entry params failed"; 1170 continue; 1171 } 1172 1173 std::string registryName; 1174 std::string messageKey; 1175 event_log::getRegistryAndMessageKey(messageID, registryName, 1176 messageKey); 1177 if (registryName.empty() || messageKey.empty()) 1178 { 1179 continue; 1180 } 1181 1182 eventRecords.emplace_back(idStr, timestamp, messageID, registryName, 1183 messageKey, messageArgs); 1184 } 1185 1186 if (!serviceEnabled || noOfEventLogSubscribers == 0) 1187 { 1188 BMCWEB_LOG_DEBUG << "EventService disabled or no Subscriptions."; 1189 return; 1190 } 1191 1192 if (eventRecords.empty()) 1193 { 1194 // No Records to send 1195 BMCWEB_LOG_DEBUG << "No log entries available to be transferred."; 1196 return; 1197 } 1198 1199 for (const auto& it : this->subscriptionsMap) 1200 { 1201 std::shared_ptr<Subscription> entry = it.second; 1202 if (entry->eventFormatType == "Event") 1203 { 1204 entry->filterAndSendEventLogs(eventRecords); 1205 } 1206 } 1207 } 1208 1209 static void watchRedfishEventLogFile() 1210 { 1211 if (!inotifyConn) 1212 { 1213 return; 1214 } 1215 1216 static std::array<char, 1024> readBuffer; 1217 1218 inotifyConn->async_read_some(boost::asio::buffer(readBuffer), 1219 [&](const boost::system::error_code& ec, 1220 const std::size_t& bytesTransferred) { 1221 if (ec) 1222 { 1223 BMCWEB_LOG_ERROR << "Callback Error: " << ec.message(); 1224 return; 1225 } 1226 std::size_t index = 0; 1227 while ((index + iEventSize) <= bytesTransferred) 1228 { 1229 struct inotify_event event 1230 {}; 1231 std::memcpy(&event, &readBuffer[index], iEventSize); 1232 if (event.wd == dirWatchDesc) 1233 { 1234 if ((event.len == 0) || 1235 (index + iEventSize + event.len > bytesTransferred)) 1236 { 1237 index += (iEventSize + event.len); 1238 continue; 1239 } 1240 1241 std::string fileName(&readBuffer[index + iEventSize]); 1242 if (fileName != "redfish") 1243 { 1244 index += (iEventSize + event.len); 1245 continue; 1246 } 1247 1248 BMCWEB_LOG_DEBUG 1249 << "Redfish log file created/deleted. event.name: " 1250 << fileName; 1251 if (event.mask == IN_CREATE) 1252 { 1253 if (fileWatchDesc != -1) 1254 { 1255 BMCWEB_LOG_DEBUG 1256 << "Remove and Add inotify watcher on " 1257 "redfish event log file"; 1258 // Remove existing inotify watcher and add 1259 // with new redfish event log file. 1260 inotify_rm_watch(inotifyFd, fileWatchDesc); 1261 fileWatchDesc = -1; 1262 } 1263 1264 fileWatchDesc = inotify_add_watch( 1265 inotifyFd, redfishEventLogFile, IN_MODIFY); 1266 if (fileWatchDesc == -1) 1267 { 1268 BMCWEB_LOG_ERROR << "inotify_add_watch failed for " 1269 "redfish log file."; 1270 return; 1271 } 1272 1273 EventServiceManager::getInstance() 1274 .resetRedfishFilePosition(); 1275 EventServiceManager::getInstance() 1276 .readEventLogsFromFile(); 1277 } 1278 else if ((event.mask == IN_DELETE) || 1279 (event.mask == IN_MOVED_TO)) 1280 { 1281 if (fileWatchDesc != -1) 1282 { 1283 inotify_rm_watch(inotifyFd, fileWatchDesc); 1284 fileWatchDesc = -1; 1285 } 1286 } 1287 } 1288 else if (event.wd == fileWatchDesc) 1289 { 1290 if (event.mask == IN_MODIFY) 1291 { 1292 EventServiceManager::getInstance() 1293 .readEventLogsFromFile(); 1294 } 1295 } 1296 index += (iEventSize + event.len); 1297 } 1298 1299 watchRedfishEventLogFile(); 1300 }); 1301 } 1302 1303 static int startEventLogMonitor(boost::asio::io_context& ioc) 1304 { 1305 inotifyConn.emplace(ioc); 1306 inotifyFd = inotify_init1(IN_NONBLOCK); 1307 if (inotifyFd == -1) 1308 { 1309 BMCWEB_LOG_ERROR << "inotify_init1 failed."; 1310 return -1; 1311 } 1312 1313 // Add watch on directory to handle redfish event log file 1314 // create/delete. 1315 dirWatchDesc = inotify_add_watch(inotifyFd, redfishEventLogDir, 1316 IN_CREATE | IN_MOVED_TO | IN_DELETE); 1317 if (dirWatchDesc == -1) 1318 { 1319 BMCWEB_LOG_ERROR 1320 << "inotify_add_watch failed for event log directory."; 1321 return -1; 1322 } 1323 1324 // Watch redfish event log file for modifications. 1325 fileWatchDesc = 1326 inotify_add_watch(inotifyFd, redfishEventLogFile, IN_MODIFY); 1327 if (fileWatchDesc == -1) 1328 { 1329 BMCWEB_LOG_ERROR 1330 << "inotify_add_watch failed for redfish log file."; 1331 // Don't return error if file not exist. 1332 // Watch on directory will handle create/delete of file. 1333 } 1334 1335 // monitor redfish event log file 1336 inotifyConn->assign(inotifyFd); 1337 watchRedfishEventLogFile(); 1338 1339 return 0; 1340 } 1341 1342 #endif 1343 static void getReadingsForReport(sdbusplus::message_t& msg) 1344 { 1345 if (msg.is_method_error()) 1346 { 1347 BMCWEB_LOG_ERROR << "TelemetryMonitor Signal error"; 1348 return; 1349 } 1350 1351 sdbusplus::message::object_path path(msg.get_path()); 1352 std::string id = path.filename(); 1353 if (id.empty()) 1354 { 1355 BMCWEB_LOG_ERROR << "Failed to get Id from path"; 1356 return; 1357 } 1358 1359 std::string interface; 1360 dbus::utility::DBusPropertiesMap props; 1361 std::vector<std::string> invalidProps; 1362 msg.read(interface, props, invalidProps); 1363 1364 auto found = 1365 std::find_if(props.begin(), props.end(), 1366 [](const auto& x) { return x.first == "Readings"; }); 1367 if (found == props.end()) 1368 { 1369 BMCWEB_LOG_INFO << "Failed to get Readings from Report properties"; 1370 return; 1371 } 1372 1373 const telemetry::TimestampReadings* readings = 1374 std::get_if<telemetry::TimestampReadings>(&found->second); 1375 if (readings == nullptr) 1376 { 1377 BMCWEB_LOG_INFO << "Failed to get Readings from Report properties"; 1378 return; 1379 } 1380 1381 for (const auto& it : 1382 EventServiceManager::getInstance().subscriptionsMap) 1383 { 1384 Subscription& entry = *it.second; 1385 if (entry.eventFormatType == metricReportFormatType) 1386 { 1387 entry.filterAndSendReports(id, *readings); 1388 } 1389 } 1390 } 1391 1392 void unregisterMetricReportSignal() 1393 { 1394 if (matchTelemetryMonitor) 1395 { 1396 BMCWEB_LOG_DEBUG << "Metrics report signal - Unregister"; 1397 matchTelemetryMonitor.reset(); 1398 matchTelemetryMonitor = nullptr; 1399 } 1400 } 1401 1402 void registerMetricReportSignal() 1403 { 1404 if (!serviceEnabled || matchTelemetryMonitor) 1405 { 1406 BMCWEB_LOG_DEBUG << "Not registering metric report signal."; 1407 return; 1408 } 1409 1410 BMCWEB_LOG_DEBUG << "Metrics report signal - Register"; 1411 std::string matchStr = "type='signal',member='PropertiesChanged'," 1412 "interface='org.freedesktop.DBus.Properties'," 1413 "arg0=xyz.openbmc_project.Telemetry.Report"; 1414 1415 matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match_t>( 1416 *crow::connections::systemBus, matchStr, getReadingsForReport); 1417 } 1418 }; 1419 1420 } // namespace redfish 1421