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