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::beast::tcp_stream>& 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 // A connection pool will be created if one does not already exist 400 crow::HttpClient::getInstance().sendData( 401 msg, id, host, port, path, httpHeaders, 402 boost::beast::http::verb::post, retryPolicyName); 403 eventSeqNum++; 404 405 if (sseConn != nullptr) 406 { 407 sseConn->sendData(eventSeqNum, msg); 408 } 409 return true; 410 } 411 412 bool sendTestEventLog() 413 { 414 nlohmann::json logEntryArray; 415 logEntryArray.push_back({}); 416 nlohmann::json& logEntryJson = logEntryArray.back(); 417 418 logEntryJson = {{"EventId", "TestID"}, 419 {"EventType", "Event"}, 420 {"Severity", "OK"}, 421 {"Message", "Generated test event"}, 422 {"MessageId", "OpenBMC.0.2.TestEventLog"}, 423 {"MessageArgs", nlohmann::json::array()}, 424 {"EventTimestamp", 425 redfish::time_utils::getDateTimeOffsetNow().first}, 426 {"Context", customText}}; 427 428 nlohmann::json msg; 429 msg["@odata.type"] = "#Event.v1_4_0.Event"; 430 msg["Id"] = std::to_string(eventSeqNum); 431 msg["Name"] = "Event Log"; 432 msg["Events"] = logEntryArray; 433 434 std::string strMsg = 435 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 436 return this->sendEvent(strMsg); 437 } 438 439 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 440 void filterAndSendEventLogs( 441 const std::vector<EventLogObjectsType>& eventRecords) 442 { 443 nlohmann::json logEntryArray; 444 for (const EventLogObjectsType& logEntry : eventRecords) 445 { 446 const std::string& idStr = std::get<0>(logEntry); 447 const std::string& timestamp = std::get<1>(logEntry); 448 const std::string& messageID = std::get<2>(logEntry); 449 const std::string& registryName = std::get<3>(logEntry); 450 const std::string& messageKey = std::get<4>(logEntry); 451 const std::vector<std::string>& messageArgs = std::get<5>(logEntry); 452 453 // If registryPrefixes list is empty, don't filter events 454 // send everything. 455 if (!registryPrefixes.empty()) 456 { 457 auto obj = std::find(registryPrefixes.begin(), 458 registryPrefixes.end(), registryName); 459 if (obj == registryPrefixes.end()) 460 { 461 continue; 462 } 463 } 464 465 // If registryMsgIds list is empty, don't filter events 466 // send everything. 467 if (!registryMsgIds.empty()) 468 { 469 auto obj = std::find(registryMsgIds.begin(), 470 registryMsgIds.end(), messageKey); 471 if (obj == registryMsgIds.end()) 472 { 473 continue; 474 } 475 } 476 477 std::vector<std::string_view> messageArgsView(messageArgs.begin(), 478 messageArgs.end()); 479 480 logEntryArray.push_back({}); 481 nlohmann::json& bmcLogEntry = logEntryArray.back(); 482 if (event_log::formatEventLogEntry(idStr, messageID, 483 messageArgsView, timestamp, 484 customText, bmcLogEntry) != 0) 485 { 486 BMCWEB_LOG_DEBUG << "Read eventLog entry failed"; 487 continue; 488 } 489 } 490 491 if (logEntryArray.empty()) 492 { 493 BMCWEB_LOG_DEBUG << "No log entries available to be transferred."; 494 return; 495 } 496 497 nlohmann::json msg; 498 msg["@odata.type"] = "#Event.v1_4_0.Event"; 499 msg["Id"] = std::to_string(eventSeqNum); 500 msg["Name"] = "Event Log"; 501 msg["Events"] = logEntryArray; 502 503 std::string strMsg = 504 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 505 this->sendEvent(strMsg); 506 } 507 #endif 508 509 void filterAndSendReports(const std::string& reportId, 510 const telemetry::TimestampReadings& var) 511 { 512 boost::urls::url mrdUri = 513 crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", 514 "MetricReportDefinitions", reportId); 515 516 // Empty list means no filter. Send everything. 517 if (!metricReportDefinitions.empty()) 518 { 519 if (std::find(metricReportDefinitions.begin(), 520 metricReportDefinitions.end(), 521 mrdUri.string()) == metricReportDefinitions.end()) 522 { 523 return; 524 } 525 } 526 527 nlohmann::json msg; 528 if (!telemetry::fillReport(msg, reportId, var)) 529 { 530 BMCWEB_LOG_ERROR << "Failed to fill the MetricReport for DBus " 531 "Report with id " 532 << reportId; 533 return; 534 } 535 536 // Context is set by user during Event subscription and it must be 537 // set for MetricReport response. 538 if (!customText.empty()) 539 { 540 msg["Context"] = customText; 541 } 542 543 std::string strMsg = 544 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 545 this->sendEvent(strMsg); 546 } 547 548 void updateRetryConfig(const uint32_t retryAttempts, 549 const uint32_t retryTimeoutInterval) 550 { 551 crow::HttpClient::getInstance().setRetryConfig( 552 retryAttempts, retryTimeoutInterval, retryRespHandler, 553 retryPolicyName); 554 } 555 556 void updateRetryPolicy() 557 { 558 crow::HttpClient::getInstance().setRetryPolicy(retryPolicy, 559 retryPolicyName); 560 } 561 562 uint64_t getEventSeqNum() const 563 { 564 return eventSeqNum; 565 } 566 567 private: 568 uint64_t eventSeqNum; 569 std::string host; 570 uint16_t port = 0; 571 std::string path; 572 std::string uriProto; 573 std::shared_ptr<crow::ServerSentEvents> sseConn = nullptr; 574 std::string retryPolicyName = "SubscriptionEvent"; 575 576 // Check used to indicate what response codes are valid as part of our retry 577 // policy. 2XX is considered acceptable 578 static boost::system::error_code retryRespHandler(unsigned int respCode) 579 { 580 BMCWEB_LOG_DEBUG 581 << "Checking response code validity for SubscriptionEvent"; 582 if ((respCode < 200) || (respCode >= 300)) 583 { 584 return boost::system::errc::make_error_code( 585 boost::system::errc::result_out_of_range); 586 } 587 588 // Return 0 if the response code is valid 589 return boost::system::errc::make_error_code( 590 boost::system::errc::success); 591 } 592 }; 593 594 class EventServiceManager 595 { 596 private: 597 bool serviceEnabled = false; 598 uint32_t retryAttempts = 0; 599 uint32_t retryTimeoutInterval = 0; 600 601 EventServiceManager() 602 { 603 // Load config from persist store. 604 initConfig(); 605 } 606 607 std::streampos redfishLogFilePosition{0}; 608 size_t noOfEventLogSubscribers{0}; 609 size_t noOfMetricReportSubscribers{0}; 610 std::shared_ptr<sdbusplus::bus::match_t> matchTelemetryMonitor; 611 boost::container::flat_map<std::string, std::shared_ptr<Subscription>> 612 subscriptionsMap; 613 614 uint64_t eventId{1}; 615 616 public: 617 EventServiceManager(const EventServiceManager&) = delete; 618 EventServiceManager& operator=(const EventServiceManager&) = delete; 619 EventServiceManager(EventServiceManager&&) = delete; 620 EventServiceManager& operator=(EventServiceManager&&) = delete; 621 ~EventServiceManager() = default; 622 623 static EventServiceManager& getInstance() 624 { 625 static EventServiceManager handler; 626 return handler; 627 } 628 629 void initConfig() 630 { 631 loadOldBehavior(); 632 633 persistent_data::EventServiceConfig eventServiceConfig = 634 persistent_data::EventServiceStore::getInstance() 635 .getEventServiceConfig(); 636 637 serviceEnabled = eventServiceConfig.enabled; 638 retryAttempts = eventServiceConfig.retryAttempts; 639 retryTimeoutInterval = eventServiceConfig.retryTimeoutInterval; 640 641 for (const auto& it : persistent_data::EventServiceStore::getInstance() 642 .subscriptionsConfigMap) 643 { 644 std::shared_ptr<persistent_data::UserSubscription> newSub = 645 it.second; 646 647 std::string host; 648 std::string urlProto; 649 uint16_t port = 0; 650 std::string path; 651 bool status = crow::utility::validateAndSplitUrl( 652 newSub->destinationUrl, urlProto, host, port, path); 653 654 if (!status) 655 { 656 BMCWEB_LOG_ERROR 657 << "Failed to validate and split destination url"; 658 continue; 659 } 660 std::shared_ptr<Subscription> subValue = 661 std::make_shared<Subscription>(host, port, path, urlProto); 662 663 subValue->id = newSub->id; 664 subValue->destinationUrl = newSub->destinationUrl; 665 subValue->protocol = newSub->protocol; 666 subValue->retryPolicy = newSub->retryPolicy; 667 subValue->customText = newSub->customText; 668 subValue->eventFormatType = newSub->eventFormatType; 669 subValue->subscriptionType = newSub->subscriptionType; 670 subValue->registryMsgIds = newSub->registryMsgIds; 671 subValue->registryPrefixes = newSub->registryPrefixes; 672 subValue->resourceTypes = newSub->resourceTypes; 673 subValue->httpHeaders = newSub->httpHeaders; 674 subValue->metricReportDefinitions = newSub->metricReportDefinitions; 675 676 if (subValue->id.empty()) 677 { 678 BMCWEB_LOG_ERROR << "Failed to add subscription"; 679 } 680 subscriptionsMap.insert(std::pair(subValue->id, subValue)); 681 682 updateNoOfSubscribersCount(); 683 684 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 685 686 cacheRedfishLogFile(); 687 688 #endif 689 // Update retry configuration. 690 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 691 subValue->updateRetryPolicy(); 692 } 693 } 694 695 static void loadOldBehavior() 696 { 697 std::ifstream eventConfigFile(eventServiceFile); 698 if (!eventConfigFile.good()) 699 { 700 BMCWEB_LOG_DEBUG << "Old eventService config not exist"; 701 return; 702 } 703 auto jsonData = nlohmann::json::parse(eventConfigFile, nullptr, false); 704 if (jsonData.is_discarded()) 705 { 706 BMCWEB_LOG_ERROR << "Old eventService config parse error."; 707 return; 708 } 709 710 for (const auto& item : jsonData.items()) 711 { 712 if (item.key() == "Configuration") 713 { 714 persistent_data::EventServiceStore::getInstance() 715 .getEventServiceConfig() 716 .fromJson(item.value()); 717 } 718 else if (item.key() == "Subscriptions") 719 { 720 for (const auto& elem : item.value()) 721 { 722 std::shared_ptr<persistent_data::UserSubscription> 723 newSubscription = 724 persistent_data::UserSubscription::fromJson(elem, 725 true); 726 if (newSubscription == nullptr) 727 { 728 BMCWEB_LOG_ERROR << "Problem reading subscription " 729 "from old persistent store"; 730 continue; 731 } 732 733 std::uniform_int_distribution<uint32_t> dist(0); 734 bmcweb::OpenSSLGenerator gen; 735 736 std::string id; 737 738 int retry = 3; 739 while (retry != 0) 740 { 741 id = std::to_string(dist(gen)); 742 if (gen.error()) 743 { 744 retry = 0; 745 break; 746 } 747 newSubscription->id = id; 748 auto inserted = 749 persistent_data::EventServiceStore::getInstance() 750 .subscriptionsConfigMap.insert( 751 std::pair(id, newSubscription)); 752 if (inserted.second) 753 { 754 break; 755 } 756 --retry; 757 } 758 759 if (retry <= 0) 760 { 761 BMCWEB_LOG_ERROR 762 << "Failed to generate random number from old " 763 "persistent store"; 764 continue; 765 } 766 } 767 } 768 769 persistent_data::getConfig().writeData(); 770 std::remove(eventServiceFile); 771 BMCWEB_LOG_DEBUG << "Remove old eventservice config"; 772 } 773 } 774 775 void updateSubscriptionData() const 776 { 777 persistent_data::EventServiceStore::getInstance() 778 .eventServiceConfig.enabled = serviceEnabled; 779 persistent_data::EventServiceStore::getInstance() 780 .eventServiceConfig.retryAttempts = retryAttempts; 781 persistent_data::EventServiceStore::getInstance() 782 .eventServiceConfig.retryTimeoutInterval = retryTimeoutInterval; 783 784 persistent_data::getConfig().writeData(); 785 } 786 787 void setEventServiceConfig(const persistent_data::EventServiceConfig& cfg) 788 { 789 bool updateConfig = false; 790 bool updateRetryCfg = false; 791 792 if (serviceEnabled != cfg.enabled) 793 { 794 serviceEnabled = cfg.enabled; 795 if (serviceEnabled && noOfMetricReportSubscribers != 0U) 796 { 797 registerMetricReportSignal(); 798 } 799 else 800 { 801 unregisterMetricReportSignal(); 802 } 803 updateConfig = true; 804 } 805 806 if (retryAttempts != cfg.retryAttempts) 807 { 808 retryAttempts = cfg.retryAttempts; 809 updateConfig = true; 810 updateRetryCfg = true; 811 } 812 813 if (retryTimeoutInterval != cfg.retryTimeoutInterval) 814 { 815 retryTimeoutInterval = cfg.retryTimeoutInterval; 816 updateConfig = true; 817 updateRetryCfg = true; 818 } 819 820 if (updateConfig) 821 { 822 updateSubscriptionData(); 823 } 824 825 if (updateRetryCfg) 826 { 827 // Update the changed retry config to all subscriptions 828 for (const auto& it : 829 EventServiceManager::getInstance().subscriptionsMap) 830 { 831 std::shared_ptr<Subscription> entry = it.second; 832 entry->updateRetryConfig(retryAttempts, retryTimeoutInterval); 833 } 834 } 835 } 836 837 void updateNoOfSubscribersCount() 838 { 839 size_t eventLogSubCount = 0; 840 size_t metricReportSubCount = 0; 841 for (const auto& it : subscriptionsMap) 842 { 843 std::shared_ptr<Subscription> entry = it.second; 844 if (entry->eventFormatType == eventFormatType) 845 { 846 eventLogSubCount++; 847 } 848 else if (entry->eventFormatType == metricReportFormatType) 849 { 850 metricReportSubCount++; 851 } 852 } 853 854 noOfEventLogSubscribers = eventLogSubCount; 855 if (noOfMetricReportSubscribers != metricReportSubCount) 856 { 857 noOfMetricReportSubscribers = metricReportSubCount; 858 if (noOfMetricReportSubscribers != 0U) 859 { 860 registerMetricReportSignal(); 861 } 862 else 863 { 864 unregisterMetricReportSignal(); 865 } 866 } 867 } 868 869 std::shared_ptr<Subscription> getSubscription(const std::string& id) 870 { 871 auto obj = subscriptionsMap.find(id); 872 if (obj == subscriptionsMap.end()) 873 { 874 BMCWEB_LOG_ERROR << "No subscription exist with ID:" << id; 875 return nullptr; 876 } 877 std::shared_ptr<Subscription> subValue = obj->second; 878 return subValue; 879 } 880 881 std::string addSubscription(const std::shared_ptr<Subscription>& subValue, 882 const bool updateFile = true) 883 { 884 885 std::uniform_int_distribution<uint32_t> dist(0); 886 bmcweb::OpenSSLGenerator gen; 887 888 std::string id; 889 890 int retry = 3; 891 while (retry != 0) 892 { 893 id = std::to_string(dist(gen)); 894 if (gen.error()) 895 { 896 retry = 0; 897 break; 898 } 899 auto inserted = subscriptionsMap.insert(std::pair(id, subValue)); 900 if (inserted.second) 901 { 902 break; 903 } 904 --retry; 905 } 906 907 if (retry <= 0) 908 { 909 BMCWEB_LOG_ERROR << "Failed to generate random number"; 910 return ""; 911 } 912 913 std::shared_ptr<persistent_data::UserSubscription> newSub = 914 std::make_shared<persistent_data::UserSubscription>(); 915 newSub->id = id; 916 newSub->destinationUrl = subValue->destinationUrl; 917 newSub->protocol = subValue->protocol; 918 newSub->retryPolicy = subValue->retryPolicy; 919 newSub->customText = subValue->customText; 920 newSub->eventFormatType = subValue->eventFormatType; 921 newSub->subscriptionType = subValue->subscriptionType; 922 newSub->registryMsgIds = subValue->registryMsgIds; 923 newSub->registryPrefixes = subValue->registryPrefixes; 924 newSub->resourceTypes = subValue->resourceTypes; 925 newSub->httpHeaders = subValue->httpHeaders; 926 newSub->metricReportDefinitions = subValue->metricReportDefinitions; 927 persistent_data::EventServiceStore::getInstance() 928 .subscriptionsConfigMap.emplace(newSub->id, newSub); 929 930 updateNoOfSubscribersCount(); 931 932 if (updateFile) 933 { 934 updateSubscriptionData(); 935 } 936 937 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 938 if (redfishLogFilePosition != 0) 939 { 940 cacheRedfishLogFile(); 941 } 942 #endif 943 // Update retry configuration. 944 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 945 subValue->updateRetryPolicy(); 946 947 return id; 948 } 949 950 bool isSubscriptionExist(const std::string& id) 951 { 952 auto obj = subscriptionsMap.find(id); 953 return obj != subscriptionsMap.end(); 954 } 955 956 void deleteSubscription(const std::string& id) 957 { 958 auto obj = subscriptionsMap.find(id); 959 if (obj != subscriptionsMap.end()) 960 { 961 subscriptionsMap.erase(obj); 962 auto obj2 = persistent_data::EventServiceStore::getInstance() 963 .subscriptionsConfigMap.find(id); 964 persistent_data::EventServiceStore::getInstance() 965 .subscriptionsConfigMap.erase(obj2); 966 updateNoOfSubscribersCount(); 967 updateSubscriptionData(); 968 } 969 } 970 971 size_t getNumberOfSubscriptions() 972 { 973 return subscriptionsMap.size(); 974 } 975 976 std::vector<std::string> getAllIDs() 977 { 978 std::vector<std::string> idList; 979 for (const auto& it : subscriptionsMap) 980 { 981 idList.emplace_back(it.first); 982 } 983 return idList; 984 } 985 986 bool isDestinationExist(const std::string& destUrl) 987 { 988 for (const auto& it : subscriptionsMap) 989 { 990 std::shared_ptr<Subscription> entry = it.second; 991 if (entry->destinationUrl == destUrl) 992 { 993 BMCWEB_LOG_ERROR << "Destination exist already" << destUrl; 994 return true; 995 } 996 } 997 return false; 998 } 999 1000 bool sendTestEventLog() 1001 { 1002 for (const auto& it : this->subscriptionsMap) 1003 { 1004 std::shared_ptr<Subscription> entry = it.second; 1005 if (!entry->sendTestEventLog()) 1006 { 1007 return false; 1008 } 1009 } 1010 return true; 1011 } 1012 1013 void sendEvent(const nlohmann::json& eventMessageIn, 1014 const std::string& origin, const std::string& resType) 1015 { 1016 if (!serviceEnabled || (noOfEventLogSubscribers == 0U)) 1017 { 1018 BMCWEB_LOG_DEBUG << "EventService disabled or no Subscriptions."; 1019 return; 1020 } 1021 nlohmann::json eventRecord = nlohmann::json::array(); 1022 nlohmann::json eventMessage = eventMessageIn; 1023 // MemberId is 0 : since we are sending one event record. 1024 uint64_t memberId = 0; 1025 1026 nlohmann::json event = { 1027 {"EventId", eventId}, 1028 {"MemberId", memberId}, 1029 {"EventTimestamp", 1030 redfish::time_utils::getDateTimeOffsetNow().first}, 1031 {"OriginOfCondition", origin}}; 1032 for (nlohmann::json::iterator it = event.begin(); it != event.end(); 1033 ++it) 1034 { 1035 eventMessage[it.key()] = it.value(); 1036 } 1037 eventRecord.push_back(eventMessage); 1038 1039 for (const auto& it : this->subscriptionsMap) 1040 { 1041 std::shared_ptr<Subscription> entry = it.second; 1042 bool isSubscribed = false; 1043 // Search the resourceTypes list for the subscription. 1044 // If resourceTypes list is empty, don't filter events 1045 // send everything. 1046 if (!entry->resourceTypes.empty()) 1047 { 1048 for (const auto& resource : entry->resourceTypes) 1049 { 1050 if (resType == resource) 1051 { 1052 BMCWEB_LOG_INFO << "ResourceType " << resource 1053 << " found in the subscribed list"; 1054 isSubscribed = true; 1055 break; 1056 } 1057 } 1058 } 1059 else // resourceTypes list is empty. 1060 { 1061 isSubscribed = true; 1062 } 1063 if (isSubscribed) 1064 { 1065 nlohmann::json msgJson = { 1066 {"@odata.type", "#Event.v1_4_0.Event"}, 1067 {"Name", "Event Log"}, 1068 {"Id", eventId}, 1069 {"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 {"Timestamp", 1089 redfish::time_utils::getDateTimeOffsetNow().first}, 1090 {"OriginOfCondition", "/ibm/v1/HMC/BroadcastService"}, 1091 {"Name", "Broadcast Message"}, 1092 {"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