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