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