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 {"EventType", "Event"}, 421 {"Severity", "OK"}, 422 {"Message", "Generated test event"}, 423 {"MessageId", "OpenBMC.0.2.TestEventLog"}, 424 {"MessageArgs", nlohmann::json::array()}, 425 {"EventTimestamp", 426 redfish::time_utils::getDateTimeOffsetNow().first}, 427 {"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(const nlohmann::json& eventMessageIn, 1015 const std::string& origin, 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 nlohmann::json eventMessage = eventMessageIn; 1024 // MemberId is 0 : since we are sending one event record. 1025 uint64_t memberId = 0; 1026 1027 nlohmann::json event = { 1028 {"EventId", eventId}, 1029 {"MemberId", memberId}, 1030 {"EventTimestamp", 1031 redfish::time_utils::getDateTimeOffsetNow().first}, 1032 {"OriginOfCondition", origin}}; 1033 for (nlohmann::json::iterator it = event.begin(); it != event.end(); 1034 ++it) 1035 { 1036 eventMessage[it.key()] = it.value(); 1037 } 1038 eventRecord.push_back(eventMessage); 1039 1040 for (const auto& it : this->subscriptionsMap) 1041 { 1042 std::shared_ptr<Subscription> entry = it.second; 1043 bool isSubscribed = false; 1044 // Search the resourceTypes list for the subscription. 1045 // If resourceTypes list is empty, don't filter events 1046 // send everything. 1047 if (!entry->resourceTypes.empty()) 1048 { 1049 for (const auto& resource : entry->resourceTypes) 1050 { 1051 if (resType == resource) 1052 { 1053 BMCWEB_LOG_INFO << "ResourceType " << resource 1054 << " found in the subscribed list"; 1055 isSubscribed = true; 1056 break; 1057 } 1058 } 1059 } 1060 else // resourceTypes list is empty. 1061 { 1062 isSubscribed = true; 1063 } 1064 if (isSubscribed) 1065 { 1066 nlohmann::json msgJson = { 1067 {"@odata.type", "#Event.v1_4_0.Event"}, 1068 {"Name", "Event Log"}, 1069 {"Id", eventId}, 1070 {"Events", eventRecord}}; 1071 1072 std::string strMsg = msgJson.dump( 1073 2, ' ', true, nlohmann::json::error_handler_t::replace); 1074 entry->sendEvent(strMsg); 1075 eventId++; // increament the eventId 1076 } 1077 else 1078 { 1079 BMCWEB_LOG_INFO << "Not subscribed to this resource"; 1080 } 1081 } 1082 } 1083 void sendBroadcastMsg(const std::string& broadcastMsg) 1084 { 1085 for (const auto& it : this->subscriptionsMap) 1086 { 1087 std::shared_ptr<Subscription> entry = it.second; 1088 nlohmann::json msgJson = { 1089 {"Timestamp", 1090 redfish::time_utils::getDateTimeOffsetNow().first}, 1091 {"OriginOfCondition", "/ibm/v1/HMC/BroadcastService"}, 1092 {"Name", "Broadcast Message"}, 1093 {"Message", broadcastMsg}}; 1094 1095 std::string strMsg = msgJson.dump( 1096 2, ' ', true, nlohmann::json::error_handler_t::replace); 1097 entry->sendEvent(strMsg); 1098 } 1099 } 1100 1101 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 1102 1103 void resetRedfishFilePosition() 1104 { 1105 // Control would be here when Redfish file is created. 1106 // Reset File Position as new file is created 1107 redfishLogFilePosition = 0; 1108 } 1109 1110 void cacheRedfishLogFile() 1111 { 1112 // Open the redfish file and read till the last record. 1113 1114 std::ifstream logStream(redfishEventLogFile); 1115 if (!logStream.good()) 1116 { 1117 BMCWEB_LOG_ERROR << " Redfish log file open failed \n"; 1118 return; 1119 } 1120 std::string logEntry; 1121 while (std::getline(logStream, logEntry)) 1122 { 1123 redfishLogFilePosition = logStream.tellg(); 1124 } 1125 1126 BMCWEB_LOG_DEBUG << "Next Log Position : " << redfishLogFilePosition; 1127 } 1128 1129 void readEventLogsFromFile() 1130 { 1131 std::ifstream logStream(redfishEventLogFile); 1132 if (!logStream.good()) 1133 { 1134 BMCWEB_LOG_ERROR << " Redfish log file open failed"; 1135 return; 1136 } 1137 1138 std::vector<EventLogObjectsType> eventRecords; 1139 1140 std::string logEntry; 1141 1142 // Get the read pointer to the next log to be read. 1143 logStream.seekg(redfishLogFilePosition); 1144 1145 while (std::getline(logStream, logEntry)) 1146 { 1147 // Update Pointer position 1148 redfishLogFilePosition = logStream.tellg(); 1149 1150 std::string idStr; 1151 if (!event_log::getUniqueEntryID(logEntry, idStr)) 1152 { 1153 continue; 1154 } 1155 1156 if (!serviceEnabled || noOfEventLogSubscribers == 0) 1157 { 1158 // If Service is not enabled, no need to compute 1159 // the remaining items below. 1160 // But, Loop must continue to keep track of Timestamp 1161 continue; 1162 } 1163 1164 std::string timestamp; 1165 std::string messageID; 1166 std::vector<std::string> messageArgs; 1167 if (event_log::getEventLogParams(logEntry, timestamp, messageID, 1168 messageArgs) != 0) 1169 { 1170 BMCWEB_LOG_DEBUG << "Read eventLog entry params failed"; 1171 continue; 1172 } 1173 1174 std::string registryName; 1175 std::string messageKey; 1176 event_log::getRegistryAndMessageKey(messageID, registryName, 1177 messageKey); 1178 if (registryName.empty() || messageKey.empty()) 1179 { 1180 continue; 1181 } 1182 1183 eventRecords.emplace_back(idStr, timestamp, messageID, registryName, 1184 messageKey, messageArgs); 1185 } 1186 1187 if (!serviceEnabled || noOfEventLogSubscribers == 0) 1188 { 1189 BMCWEB_LOG_DEBUG << "EventService disabled or no Subscriptions."; 1190 return; 1191 } 1192 1193 if (eventRecords.empty()) 1194 { 1195 // No Records to send 1196 BMCWEB_LOG_DEBUG << "No log entries available to be transferred."; 1197 return; 1198 } 1199 1200 for (const auto& it : this->subscriptionsMap) 1201 { 1202 std::shared_ptr<Subscription> entry = it.second; 1203 if (entry->eventFormatType == "Event") 1204 { 1205 entry->filterAndSendEventLogs(eventRecords); 1206 } 1207 } 1208 } 1209 1210 static void watchRedfishEventLogFile() 1211 { 1212 if (!inotifyConn) 1213 { 1214 return; 1215 } 1216 1217 static std::array<char, 1024> readBuffer; 1218 1219 inotifyConn->async_read_some(boost::asio::buffer(readBuffer), 1220 [&](const boost::system::error_code& ec, 1221 const std::size_t& bytesTransferred) { 1222 if (ec) 1223 { 1224 BMCWEB_LOG_ERROR << "Callback Error: " << ec.message(); 1225 return; 1226 } 1227 std::size_t index = 0; 1228 while ((index + iEventSize) <= bytesTransferred) 1229 { 1230 struct inotify_event event 1231 {}; 1232 std::memcpy(&event, &readBuffer[index], iEventSize); 1233 if (event.wd == dirWatchDesc) 1234 { 1235 if ((event.len == 0) || 1236 (index + iEventSize + event.len > bytesTransferred)) 1237 { 1238 index += (iEventSize + event.len); 1239 continue; 1240 } 1241 1242 std::string fileName(&readBuffer[index + iEventSize]); 1243 if (fileName != "redfish") 1244 { 1245 index += (iEventSize + event.len); 1246 continue; 1247 } 1248 1249 BMCWEB_LOG_DEBUG 1250 << "Redfish log file created/deleted. event.name: " 1251 << fileName; 1252 if (event.mask == IN_CREATE) 1253 { 1254 if (fileWatchDesc != -1) 1255 { 1256 BMCWEB_LOG_DEBUG 1257 << "Remove and Add inotify watcher on " 1258 "redfish event log file"; 1259 // Remove existing inotify watcher and add 1260 // with new redfish event log file. 1261 inotify_rm_watch(inotifyFd, fileWatchDesc); 1262 fileWatchDesc = -1; 1263 } 1264 1265 fileWatchDesc = inotify_add_watch( 1266 inotifyFd, redfishEventLogFile, IN_MODIFY); 1267 if (fileWatchDesc == -1) 1268 { 1269 BMCWEB_LOG_ERROR << "inotify_add_watch failed for " 1270 "redfish log file."; 1271 return; 1272 } 1273 1274 EventServiceManager::getInstance() 1275 .resetRedfishFilePosition(); 1276 EventServiceManager::getInstance() 1277 .readEventLogsFromFile(); 1278 } 1279 else if ((event.mask == IN_DELETE) || 1280 (event.mask == IN_MOVED_TO)) 1281 { 1282 if (fileWatchDesc != -1) 1283 { 1284 inotify_rm_watch(inotifyFd, fileWatchDesc); 1285 fileWatchDesc = -1; 1286 } 1287 } 1288 } 1289 else if (event.wd == fileWatchDesc) 1290 { 1291 if (event.mask == IN_MODIFY) 1292 { 1293 EventServiceManager::getInstance() 1294 .readEventLogsFromFile(); 1295 } 1296 } 1297 index += (iEventSize + event.len); 1298 } 1299 1300 watchRedfishEventLogFile(); 1301 }); 1302 } 1303 1304 static int startEventLogMonitor(boost::asio::io_context& ioc) 1305 { 1306 inotifyConn.emplace(ioc); 1307 inotifyFd = inotify_init1(IN_NONBLOCK); 1308 if (inotifyFd == -1) 1309 { 1310 BMCWEB_LOG_ERROR << "inotify_init1 failed."; 1311 return -1; 1312 } 1313 1314 // Add watch on directory to handle redfish event log file 1315 // create/delete. 1316 dirWatchDesc = inotify_add_watch(inotifyFd, redfishEventLogDir, 1317 IN_CREATE | IN_MOVED_TO | IN_DELETE); 1318 if (dirWatchDesc == -1) 1319 { 1320 BMCWEB_LOG_ERROR 1321 << "inotify_add_watch failed for event log directory."; 1322 return -1; 1323 } 1324 1325 // Watch redfish event log file for modifications. 1326 fileWatchDesc = 1327 inotify_add_watch(inotifyFd, redfishEventLogFile, IN_MODIFY); 1328 if (fileWatchDesc == -1) 1329 { 1330 BMCWEB_LOG_ERROR 1331 << "inotify_add_watch failed for redfish log file."; 1332 // Don't return error if file not exist. 1333 // Watch on directory will handle create/delete of file. 1334 } 1335 1336 // monitor redfish event log file 1337 inotifyConn->assign(inotifyFd); 1338 watchRedfishEventLogFile(); 1339 1340 return 0; 1341 } 1342 1343 #endif 1344 static void getReadingsForReport(sdbusplus::message_t& msg) 1345 { 1346 if (msg.is_method_error()) 1347 { 1348 BMCWEB_LOG_ERROR << "TelemetryMonitor Signal error"; 1349 return; 1350 } 1351 1352 sdbusplus::message::object_path path(msg.get_path()); 1353 std::string id = path.filename(); 1354 if (id.empty()) 1355 { 1356 BMCWEB_LOG_ERROR << "Failed to get Id from path"; 1357 return; 1358 } 1359 1360 std::string interface; 1361 dbus::utility::DBusPropertiesMap props; 1362 std::vector<std::string> invalidProps; 1363 msg.read(interface, props, invalidProps); 1364 1365 auto found = 1366 std::find_if(props.begin(), props.end(), 1367 [](const auto& x) { return x.first == "Readings"; }); 1368 if (found == props.end()) 1369 { 1370 BMCWEB_LOG_INFO << "Failed to get Readings from Report properties"; 1371 return; 1372 } 1373 1374 const telemetry::TimestampReadings* readings = 1375 std::get_if<telemetry::TimestampReadings>(&found->second); 1376 if (readings == nullptr) 1377 { 1378 BMCWEB_LOG_INFO << "Failed to get Readings from Report properties"; 1379 return; 1380 } 1381 1382 for (const auto& it : 1383 EventServiceManager::getInstance().subscriptionsMap) 1384 { 1385 Subscription& entry = *it.second; 1386 if (entry.eventFormatType == metricReportFormatType) 1387 { 1388 entry.filterAndSendReports(id, *readings); 1389 } 1390 } 1391 } 1392 1393 void unregisterMetricReportSignal() 1394 { 1395 if (matchTelemetryMonitor) 1396 { 1397 BMCWEB_LOG_DEBUG << "Metrics report signal - Unregister"; 1398 matchTelemetryMonitor.reset(); 1399 matchTelemetryMonitor = nullptr; 1400 } 1401 } 1402 1403 void registerMetricReportSignal() 1404 { 1405 if (!serviceEnabled || matchTelemetryMonitor) 1406 { 1407 BMCWEB_LOG_DEBUG << "Not registering metric report signal."; 1408 return; 1409 } 1410 1411 BMCWEB_LOG_DEBUG << "Metrics report signal - Register"; 1412 std::string matchStr = "type='signal',member='PropertiesChanged'," 1413 "interface='org.freedesktop.DBus.Properties'," 1414 "arg0=xyz.openbmc_project.Telemetry.Report"; 1415 1416 matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match_t>( 1417 *crow::connections::systemBus, matchStr, getReadingsForReport); 1418 } 1419 }; 1420 1421 } // namespace redfish 1422