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 {"EventType", "Event"}, 258 {"Severity", std::move(severity)}, 259 {"Message", std::move(msg)}, 260 {"MessageId", messageID}, 261 {"MessageArgs", messageArgs}, 262 {"EventTimestamp", std::move(timestamp)}, 263 {"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(msg, id, host, port, path, 396 httpHeaders, retryPolicyName); 397 eventSeqNum++; 398 399 if (sseConn != nullptr) 400 { 401 sseConn->sendData(eventSeqNum, msg); 402 } 403 return true; 404 } 405 406 bool sendTestEventLog() 407 { 408 nlohmann::json logEntryArray; 409 logEntryArray.push_back({}); 410 nlohmann::json& logEntryJson = logEntryArray.back(); 411 412 logEntryJson = { 413 {"EventId", "TestID"}, 414 {"EventType", "Event"}, 415 {"Severity", "OK"}, 416 {"Message", "Generated test event"}, 417 {"MessageId", "OpenBMC.0.2.TestEventLog"}, 418 {"MessageArgs", nlohmann::json::array()}, 419 {"EventTimestamp", crow::utility::getDateTimeOffsetNow().first}, 420 {"Context", customText}}; 421 422 nlohmann::json msg = {{"@odata.type", "#Event.v1_4_0.Event"}, 423 {"Id", std::to_string(eventSeqNum)}, 424 {"Name", "Event Log"}, 425 {"Events", logEntryArray}}; 426 427 std::string strMsg = 428 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 429 return this->sendEvent(strMsg); 430 } 431 432 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 433 void filterAndSendEventLogs( 434 const std::vector<EventLogObjectsType>& eventRecords) 435 { 436 nlohmann::json logEntryArray; 437 for (const EventLogObjectsType& logEntry : eventRecords) 438 { 439 const std::string& idStr = std::get<0>(logEntry); 440 const std::string& timestamp = std::get<1>(logEntry); 441 const std::string& messageID = std::get<2>(logEntry); 442 const std::string& registryName = std::get<3>(logEntry); 443 const std::string& messageKey = std::get<4>(logEntry); 444 const std::vector<std::string>& messageArgs = std::get<5>(logEntry); 445 446 // If registryPrefixes list is empty, don't filter events 447 // send everything. 448 if (!registryPrefixes.empty()) 449 { 450 auto obj = std::find(registryPrefixes.begin(), 451 registryPrefixes.end(), registryName); 452 if (obj == registryPrefixes.end()) 453 { 454 continue; 455 } 456 } 457 458 // If registryMsgIds list is empty, don't filter events 459 // send everything. 460 if (!registryMsgIds.empty()) 461 { 462 auto obj = std::find(registryMsgIds.begin(), 463 registryMsgIds.end(), messageKey); 464 if (obj == registryMsgIds.end()) 465 { 466 continue; 467 } 468 } 469 470 std::vector<std::string_view> messageArgsView(messageArgs.begin(), 471 messageArgs.end()); 472 473 logEntryArray.push_back({}); 474 nlohmann::json& bmcLogEntry = logEntryArray.back(); 475 if (event_log::formatEventLogEntry(idStr, messageID, 476 messageArgsView, timestamp, 477 customText, bmcLogEntry) != 0) 478 { 479 BMCWEB_LOG_DEBUG << "Read eventLog entry failed"; 480 continue; 481 } 482 } 483 484 if (logEntryArray.empty()) 485 { 486 BMCWEB_LOG_DEBUG << "No log entries available to be transferred."; 487 return; 488 } 489 490 nlohmann::json msg = {{"@odata.type", "#Event.v1_4_0.Event"}, 491 {"Id", std::to_string(eventSeqNum)}, 492 {"Name", "Event Log"}, 493 {"Events", logEntryArray}}; 494 495 std::string strMsg = 496 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 497 this->sendEvent(strMsg); 498 } 499 #endif 500 501 void filterAndSendReports(const std::string& reportId, 502 const telemetry::TimestampReadings& var) 503 { 504 boost::urls::url mrdUri = 505 crow::utility::urlFromPieces("redfish", "v1", "TelemetryService", 506 "MetricReportDefinitions", reportId); 507 508 // Empty list means no filter. Send everything. 509 if (!metricReportDefinitions.empty()) 510 { 511 if (std::find(metricReportDefinitions.begin(), 512 metricReportDefinitions.end(), 513 mrdUri.string()) == metricReportDefinitions.end()) 514 { 515 return; 516 } 517 } 518 519 nlohmann::json msg; 520 if (!telemetry::fillReport(msg, reportId, var)) 521 { 522 BMCWEB_LOG_ERROR << "Failed to fill the MetricReport for DBus " 523 "Report with id " 524 << reportId; 525 return; 526 } 527 528 std::string strMsg = 529 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace); 530 this->sendEvent(strMsg); 531 } 532 533 void updateRetryConfig(const uint32_t retryAttempts, 534 const uint32_t retryTimeoutInterval) 535 { 536 crow::HttpClient::getInstance().setRetryConfig( 537 retryAttempts, retryTimeoutInterval, retryPolicyName); 538 } 539 540 void updateRetryPolicy() 541 { 542 crow::HttpClient::getInstance().setRetryPolicy(retryPolicy, 543 retryPolicyName); 544 } 545 546 uint64_t getEventSeqNum() const 547 { 548 return eventSeqNum; 549 } 550 551 private: 552 uint64_t eventSeqNum; 553 std::string host; 554 uint16_t port = 0; 555 std::string path; 556 std::string uriProto; 557 std::shared_ptr<crow::ServerSentEvents> sseConn = nullptr; 558 std::string retryPolicyName = "SubscriptionEvent"; 559 }; 560 561 class EventServiceManager 562 { 563 private: 564 bool serviceEnabled = false; 565 uint32_t retryAttempts = 0; 566 uint32_t retryTimeoutInterval = 0; 567 568 EventServiceManager() 569 { 570 // Load config from persist store. 571 initConfig(); 572 } 573 574 std::streampos redfishLogFilePosition{0}; 575 size_t noOfEventLogSubscribers{0}; 576 size_t noOfMetricReportSubscribers{0}; 577 std::shared_ptr<sdbusplus::bus::match::match> matchTelemetryMonitor; 578 boost::container::flat_map<std::string, std::shared_ptr<Subscription>> 579 subscriptionsMap; 580 581 uint64_t eventId{1}; 582 583 public: 584 EventServiceManager(const EventServiceManager&) = delete; 585 EventServiceManager& operator=(const EventServiceManager&) = delete; 586 EventServiceManager(EventServiceManager&&) = delete; 587 EventServiceManager& operator=(EventServiceManager&&) = delete; 588 ~EventServiceManager() = default; 589 590 static EventServiceManager& getInstance() 591 { 592 static EventServiceManager handler; 593 return handler; 594 } 595 596 void initConfig() 597 { 598 loadOldBehavior(); 599 600 persistent_data::EventServiceConfig eventServiceConfig = 601 persistent_data::EventServiceStore::getInstance() 602 .getEventServiceConfig(); 603 604 serviceEnabled = eventServiceConfig.enabled; 605 retryAttempts = eventServiceConfig.retryAttempts; 606 retryTimeoutInterval = eventServiceConfig.retryTimeoutInterval; 607 608 for (const auto& it : persistent_data::EventServiceStore::getInstance() 609 .subscriptionsConfigMap) 610 { 611 std::shared_ptr<persistent_data::UserSubscription> newSub = 612 it.second; 613 614 std::string host; 615 std::string urlProto; 616 uint16_t port = 0; 617 std::string path; 618 bool status = crow::utility::validateAndSplitUrl( 619 newSub->destinationUrl, urlProto, host, port, path); 620 621 if (!status) 622 { 623 BMCWEB_LOG_ERROR 624 << "Failed to validate and split destination url"; 625 continue; 626 } 627 std::shared_ptr<Subscription> subValue = 628 std::make_shared<Subscription>(host, port, path, urlProto); 629 630 subValue->id = newSub->id; 631 subValue->destinationUrl = newSub->destinationUrl; 632 subValue->protocol = newSub->protocol; 633 subValue->retryPolicy = newSub->retryPolicy; 634 subValue->customText = newSub->customText; 635 subValue->eventFormatType = newSub->eventFormatType; 636 subValue->subscriptionType = newSub->subscriptionType; 637 subValue->registryMsgIds = newSub->registryMsgIds; 638 subValue->registryPrefixes = newSub->registryPrefixes; 639 subValue->resourceTypes = newSub->resourceTypes; 640 subValue->httpHeaders = newSub->httpHeaders; 641 subValue->metricReportDefinitions = newSub->metricReportDefinitions; 642 643 if (subValue->id.empty()) 644 { 645 BMCWEB_LOG_ERROR << "Failed to add subscription"; 646 } 647 subscriptionsMap.insert(std::pair(subValue->id, subValue)); 648 649 updateNoOfSubscribersCount(); 650 651 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 652 653 cacheRedfishLogFile(); 654 655 #endif 656 // Update retry configuration. 657 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 658 subValue->updateRetryPolicy(); 659 } 660 } 661 662 static void loadOldBehavior() 663 { 664 std::ifstream eventConfigFile(eventServiceFile); 665 if (!eventConfigFile.good()) 666 { 667 BMCWEB_LOG_DEBUG << "Old eventService config not exist"; 668 return; 669 } 670 auto jsonData = nlohmann::json::parse(eventConfigFile, nullptr, false); 671 if (jsonData.is_discarded()) 672 { 673 BMCWEB_LOG_ERROR << "Old eventService config parse error."; 674 return; 675 } 676 677 for (const auto& item : jsonData.items()) 678 { 679 if (item.key() == "Configuration") 680 { 681 persistent_data::EventServiceStore::getInstance() 682 .getEventServiceConfig() 683 .fromJson(item.value()); 684 } 685 else if (item.key() == "Subscriptions") 686 { 687 for (const auto& elem : item.value()) 688 { 689 std::shared_ptr<persistent_data::UserSubscription> 690 newSubscription = 691 persistent_data::UserSubscription::fromJson(elem, 692 true); 693 if (newSubscription == nullptr) 694 { 695 BMCWEB_LOG_ERROR << "Problem reading subscription " 696 "from old persistent store"; 697 continue; 698 } 699 700 std::uniform_int_distribution<uint32_t> dist(0); 701 bmcweb::OpenSSLGenerator gen; 702 703 std::string id; 704 705 int retry = 3; 706 while (retry != 0) 707 { 708 id = std::to_string(dist(gen)); 709 if (gen.error()) 710 { 711 retry = 0; 712 break; 713 } 714 newSubscription->id = id; 715 auto inserted = 716 persistent_data::EventServiceStore::getInstance() 717 .subscriptionsConfigMap.insert( 718 std::pair(id, newSubscription)); 719 if (inserted.second) 720 { 721 break; 722 } 723 --retry; 724 } 725 726 if (retry <= 0) 727 { 728 BMCWEB_LOG_ERROR 729 << "Failed to generate random number from old " 730 "persistent store"; 731 continue; 732 } 733 } 734 } 735 736 persistent_data::getConfig().writeData(); 737 std::remove(eventServiceFile); 738 BMCWEB_LOG_DEBUG << "Remove old eventservice config"; 739 } 740 } 741 742 void updateSubscriptionData() const 743 { 744 persistent_data::EventServiceStore::getInstance() 745 .eventServiceConfig.enabled = serviceEnabled; 746 persistent_data::EventServiceStore::getInstance() 747 .eventServiceConfig.retryAttempts = retryAttempts; 748 persistent_data::EventServiceStore::getInstance() 749 .eventServiceConfig.retryTimeoutInterval = retryTimeoutInterval; 750 751 persistent_data::getConfig().writeData(); 752 } 753 754 void setEventServiceConfig(const persistent_data::EventServiceConfig& cfg) 755 { 756 bool updateConfig = false; 757 bool updateRetryCfg = false; 758 759 if (serviceEnabled != cfg.enabled) 760 { 761 serviceEnabled = cfg.enabled; 762 if (serviceEnabled && noOfMetricReportSubscribers != 0U) 763 { 764 registerMetricReportSignal(); 765 } 766 else 767 { 768 unregisterMetricReportSignal(); 769 } 770 updateConfig = true; 771 } 772 773 if (retryAttempts != cfg.retryAttempts) 774 { 775 retryAttempts = cfg.retryAttempts; 776 updateConfig = true; 777 updateRetryCfg = true; 778 } 779 780 if (retryTimeoutInterval != cfg.retryTimeoutInterval) 781 { 782 retryTimeoutInterval = cfg.retryTimeoutInterval; 783 updateConfig = true; 784 updateRetryCfg = true; 785 } 786 787 if (updateConfig) 788 { 789 updateSubscriptionData(); 790 } 791 792 if (updateRetryCfg) 793 { 794 // Update the changed retry config to all subscriptions 795 for (const auto& it : 796 EventServiceManager::getInstance().subscriptionsMap) 797 { 798 std::shared_ptr<Subscription> entry = it.second; 799 entry->updateRetryConfig(retryAttempts, retryTimeoutInterval); 800 } 801 } 802 } 803 804 void updateNoOfSubscribersCount() 805 { 806 size_t eventLogSubCount = 0; 807 size_t metricReportSubCount = 0; 808 for (const auto& it : subscriptionsMap) 809 { 810 std::shared_ptr<Subscription> entry = it.second; 811 if (entry->eventFormatType == eventFormatType) 812 { 813 eventLogSubCount++; 814 } 815 else if (entry->eventFormatType == metricReportFormatType) 816 { 817 metricReportSubCount++; 818 } 819 } 820 821 noOfEventLogSubscribers = eventLogSubCount; 822 if (noOfMetricReportSubscribers != metricReportSubCount) 823 { 824 noOfMetricReportSubscribers = metricReportSubCount; 825 if (noOfMetricReportSubscribers != 0U) 826 { 827 registerMetricReportSignal(); 828 } 829 else 830 { 831 unregisterMetricReportSignal(); 832 } 833 } 834 } 835 836 std::shared_ptr<Subscription> getSubscription(const std::string& id) 837 { 838 auto obj = subscriptionsMap.find(id); 839 if (obj == subscriptionsMap.end()) 840 { 841 BMCWEB_LOG_ERROR << "No subscription exist with ID:" << id; 842 return nullptr; 843 } 844 std::shared_ptr<Subscription> subValue = obj->second; 845 return subValue; 846 } 847 848 std::string addSubscription(const std::shared_ptr<Subscription>& subValue, 849 const bool updateFile = true) 850 { 851 852 std::uniform_int_distribution<uint32_t> dist(0); 853 bmcweb::OpenSSLGenerator gen; 854 855 std::string id; 856 857 int retry = 3; 858 while (retry != 0) 859 { 860 id = std::to_string(dist(gen)); 861 if (gen.error()) 862 { 863 retry = 0; 864 break; 865 } 866 auto inserted = subscriptionsMap.insert(std::pair(id, subValue)); 867 if (inserted.second) 868 { 869 break; 870 } 871 --retry; 872 } 873 874 if (retry <= 0) 875 { 876 BMCWEB_LOG_ERROR << "Failed to generate random number"; 877 return ""; 878 } 879 880 std::shared_ptr<persistent_data::UserSubscription> newSub = 881 std::make_shared<persistent_data::UserSubscription>(); 882 newSub->id = id; 883 newSub->destinationUrl = subValue->destinationUrl; 884 newSub->protocol = subValue->protocol; 885 newSub->retryPolicy = subValue->retryPolicy; 886 newSub->customText = subValue->customText; 887 newSub->eventFormatType = subValue->eventFormatType; 888 newSub->subscriptionType = subValue->subscriptionType; 889 newSub->registryMsgIds = subValue->registryMsgIds; 890 newSub->registryPrefixes = subValue->registryPrefixes; 891 newSub->resourceTypes = subValue->resourceTypes; 892 newSub->httpHeaders = subValue->httpHeaders; 893 newSub->metricReportDefinitions = subValue->metricReportDefinitions; 894 persistent_data::EventServiceStore::getInstance() 895 .subscriptionsConfigMap.emplace(newSub->id, newSub); 896 897 updateNoOfSubscribersCount(); 898 899 if (updateFile) 900 { 901 updateSubscriptionData(); 902 } 903 904 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 905 if (redfishLogFilePosition != 0) 906 { 907 cacheRedfishLogFile(); 908 } 909 #endif 910 // Update retry configuration. 911 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 912 subValue->updateRetryPolicy(); 913 914 return id; 915 } 916 917 bool isSubscriptionExist(const std::string& id) 918 { 919 auto obj = subscriptionsMap.find(id); 920 return obj != subscriptionsMap.end(); 921 } 922 923 void deleteSubscription(const std::string& id) 924 { 925 auto obj = subscriptionsMap.find(id); 926 if (obj != subscriptionsMap.end()) 927 { 928 subscriptionsMap.erase(obj); 929 auto obj2 = persistent_data::EventServiceStore::getInstance() 930 .subscriptionsConfigMap.find(id); 931 persistent_data::EventServiceStore::getInstance() 932 .subscriptionsConfigMap.erase(obj2); 933 updateNoOfSubscribersCount(); 934 updateSubscriptionData(); 935 } 936 } 937 938 size_t getNumberOfSubscriptions() 939 { 940 return subscriptionsMap.size(); 941 } 942 943 std::vector<std::string> getAllIDs() 944 { 945 std::vector<std::string> idList; 946 for (const auto& it : subscriptionsMap) 947 { 948 idList.emplace_back(it.first); 949 } 950 return idList; 951 } 952 953 bool isDestinationExist(const std::string& destUrl) 954 { 955 for (const auto& it : subscriptionsMap) 956 { 957 std::shared_ptr<Subscription> entry = it.second; 958 if (entry->destinationUrl == destUrl) 959 { 960 BMCWEB_LOG_ERROR << "Destination exist already" << destUrl; 961 return true; 962 } 963 } 964 return false; 965 } 966 967 bool sendTestEventLog() 968 { 969 for (const auto& it : this->subscriptionsMap) 970 { 971 std::shared_ptr<Subscription> entry = it.second; 972 if (!entry->sendTestEventLog()) 973 { 974 return false; 975 } 976 } 977 return true; 978 } 979 980 void sendEvent(const nlohmann::json& eventMessageIn, 981 const std::string& origin, const std::string& resType) 982 { 983 if (!serviceEnabled || (noOfEventLogSubscribers == 0U)) 984 { 985 BMCWEB_LOG_DEBUG << "EventService disabled or no Subscriptions."; 986 return; 987 } 988 nlohmann::json eventRecord = nlohmann::json::array(); 989 nlohmann::json eventMessage = eventMessageIn; 990 // MemberId is 0 : since we are sending one event record. 991 uint64_t memberId = 0; 992 993 nlohmann::json event = { 994 {"EventId", eventId}, 995 {"MemberId", memberId}, 996 {"EventTimestamp", crow::utility::getDateTimeOffsetNow().first}, 997 {"OriginOfCondition", origin}}; 998 for (nlohmann::json::iterator it = event.begin(); it != event.end(); 999 ++it) 1000 { 1001 eventMessage[it.key()] = it.value(); 1002 } 1003 eventRecord.push_back(eventMessage); 1004 1005 for (const auto& it : this->subscriptionsMap) 1006 { 1007 std::shared_ptr<Subscription> entry = it.second; 1008 bool isSubscribed = false; 1009 // Search the resourceTypes list for the subscription. 1010 // If resourceTypes list is empty, don't filter events 1011 // send everything. 1012 if (!entry->resourceTypes.empty()) 1013 { 1014 for (const auto& resource : entry->resourceTypes) 1015 { 1016 if (resType == resource) 1017 { 1018 BMCWEB_LOG_INFO << "ResourceType " << resource 1019 << " found in the subscribed list"; 1020 isSubscribed = true; 1021 break; 1022 } 1023 } 1024 } 1025 else // resourceTypes list is empty. 1026 { 1027 isSubscribed = true; 1028 } 1029 if (isSubscribed) 1030 { 1031 nlohmann::json msgJson = { 1032 {"@odata.type", "#Event.v1_4_0.Event"}, 1033 {"Name", "Event Log"}, 1034 {"Id", eventId}, 1035 {"Events", eventRecord}}; 1036 1037 std::string strMsg = msgJson.dump( 1038 2, ' ', true, nlohmann::json::error_handler_t::replace); 1039 entry->sendEvent(strMsg); 1040 eventId++; // increament the eventId 1041 } 1042 else 1043 { 1044 BMCWEB_LOG_INFO << "Not subscribed to this resource"; 1045 } 1046 } 1047 } 1048 void sendBroadcastMsg(const std::string& broadcastMsg) 1049 { 1050 for (const auto& it : this->subscriptionsMap) 1051 { 1052 std::shared_ptr<Subscription> entry = it.second; 1053 nlohmann::json msgJson = { 1054 {"Timestamp", crow::utility::getDateTimeOffsetNow().first}, 1055 {"OriginOfCondition", "/ibm/v1/HMC/BroadcastService"}, 1056 {"Name", "Broadcast Message"}, 1057 {"Message", broadcastMsg}}; 1058 1059 std::string strMsg = msgJson.dump( 1060 2, ' ', true, nlohmann::json::error_handler_t::replace); 1061 entry->sendEvent(strMsg); 1062 } 1063 } 1064 1065 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 1066 1067 void resetRedfishFilePosition() 1068 { 1069 // Control would be here when Redfish file is created. 1070 // Reset File Position as new file is created 1071 redfishLogFilePosition = 0; 1072 } 1073 1074 void cacheRedfishLogFile() 1075 { 1076 // Open the redfish file and read till the last record. 1077 1078 std::ifstream logStream(redfishEventLogFile); 1079 if (!logStream.good()) 1080 { 1081 BMCWEB_LOG_ERROR << " Redfish log file open failed \n"; 1082 return; 1083 } 1084 std::string logEntry; 1085 while (std::getline(logStream, logEntry)) 1086 { 1087 redfishLogFilePosition = logStream.tellg(); 1088 } 1089 1090 BMCWEB_LOG_DEBUG << "Next Log Position : " << redfishLogFilePosition; 1091 } 1092 1093 void readEventLogsFromFile() 1094 { 1095 std::ifstream logStream(redfishEventLogFile); 1096 if (!logStream.good()) 1097 { 1098 BMCWEB_LOG_ERROR << " Redfish log file open failed"; 1099 return; 1100 } 1101 1102 std::vector<EventLogObjectsType> eventRecords; 1103 1104 std::string logEntry; 1105 1106 // Get the read pointer to the next log to be read. 1107 logStream.seekg(redfishLogFilePosition); 1108 1109 while (std::getline(logStream, logEntry)) 1110 { 1111 // Update Pointer position 1112 redfishLogFilePosition = logStream.tellg(); 1113 1114 std::string idStr; 1115 if (!event_log::getUniqueEntryID(logEntry, idStr)) 1116 { 1117 continue; 1118 } 1119 1120 if (!serviceEnabled || noOfEventLogSubscribers == 0) 1121 { 1122 // If Service is not enabled, no need to compute 1123 // the remaining items below. 1124 // But, Loop must continue to keep track of Timestamp 1125 continue; 1126 } 1127 1128 std::string timestamp; 1129 std::string messageID; 1130 std::vector<std::string> messageArgs; 1131 if (event_log::getEventLogParams(logEntry, timestamp, messageID, 1132 messageArgs) != 0) 1133 { 1134 BMCWEB_LOG_DEBUG << "Read eventLog entry params failed"; 1135 continue; 1136 } 1137 1138 std::string registryName; 1139 std::string messageKey; 1140 event_log::getRegistryAndMessageKey(messageID, registryName, 1141 messageKey); 1142 if (registryName.empty() || messageKey.empty()) 1143 { 1144 continue; 1145 } 1146 1147 eventRecords.emplace_back(idStr, timestamp, messageID, registryName, 1148 messageKey, messageArgs); 1149 } 1150 1151 if (!serviceEnabled || noOfEventLogSubscribers == 0) 1152 { 1153 BMCWEB_LOG_DEBUG << "EventService disabled or no Subscriptions."; 1154 return; 1155 } 1156 1157 if (eventRecords.empty()) 1158 { 1159 // No Records to send 1160 BMCWEB_LOG_DEBUG << "No log entries available to be transferred."; 1161 return; 1162 } 1163 1164 for (const auto& it : this->subscriptionsMap) 1165 { 1166 std::shared_ptr<Subscription> entry = it.second; 1167 if (entry->eventFormatType == "Event") 1168 { 1169 entry->filterAndSendEventLogs(eventRecords); 1170 } 1171 } 1172 } 1173 1174 static void watchRedfishEventLogFile() 1175 { 1176 if (!inotifyConn) 1177 { 1178 return; 1179 } 1180 1181 static std::array<char, 1024> readBuffer; 1182 1183 inotifyConn->async_read_some( 1184 boost::asio::buffer(readBuffer), 1185 [&](const boost::system::error_code& ec, 1186 const std::size_t& bytesTransferred) { 1187 if (ec) 1188 { 1189 BMCWEB_LOG_ERROR << "Callback Error: " << ec.message(); 1190 return; 1191 } 1192 std::size_t index = 0; 1193 while ((index + iEventSize) <= bytesTransferred) 1194 { 1195 struct inotify_event event 1196 {}; 1197 std::memcpy(&event, &readBuffer[index], iEventSize); 1198 if (event.wd == dirWatchDesc) 1199 { 1200 if ((event.len == 0) || 1201 (index + iEventSize + event.len > bytesTransferred)) 1202 { 1203 index += (iEventSize + event.len); 1204 continue; 1205 } 1206 1207 std::string fileName(&readBuffer[index + iEventSize]); 1208 if (fileName != "redfish") 1209 { 1210 index += (iEventSize + event.len); 1211 continue; 1212 } 1213 1214 BMCWEB_LOG_DEBUG 1215 << "Redfish log file created/deleted. event.name: " 1216 << fileName; 1217 if (event.mask == IN_CREATE) 1218 { 1219 if (fileWatchDesc != -1) 1220 { 1221 BMCWEB_LOG_DEBUG 1222 << "Remove and Add inotify watcher on " 1223 "redfish event log file"; 1224 // Remove existing inotify watcher and add 1225 // with new redfish event log file. 1226 inotify_rm_watch(inotifyFd, fileWatchDesc); 1227 fileWatchDesc = -1; 1228 } 1229 1230 fileWatchDesc = inotify_add_watch( 1231 inotifyFd, redfishEventLogFile, IN_MODIFY); 1232 if (fileWatchDesc == -1) 1233 { 1234 BMCWEB_LOG_ERROR 1235 << "inotify_add_watch failed for " 1236 "redfish log file."; 1237 return; 1238 } 1239 1240 EventServiceManager::getInstance() 1241 .resetRedfishFilePosition(); 1242 EventServiceManager::getInstance() 1243 .readEventLogsFromFile(); 1244 } 1245 else if ((event.mask == IN_DELETE) || 1246 (event.mask == IN_MOVED_TO)) 1247 { 1248 if (fileWatchDesc != -1) 1249 { 1250 inotify_rm_watch(inotifyFd, fileWatchDesc); 1251 fileWatchDesc = -1; 1252 } 1253 } 1254 } 1255 else if (event.wd == fileWatchDesc) 1256 { 1257 if (event.mask == IN_MODIFY) 1258 { 1259 EventServiceManager::getInstance() 1260 .readEventLogsFromFile(); 1261 } 1262 } 1263 index += (iEventSize + event.len); 1264 } 1265 1266 watchRedfishEventLogFile(); 1267 }); 1268 } 1269 1270 static int startEventLogMonitor(boost::asio::io_context& ioc) 1271 { 1272 inotifyConn.emplace(ioc); 1273 inotifyFd = inotify_init1(IN_NONBLOCK); 1274 if (inotifyFd == -1) 1275 { 1276 BMCWEB_LOG_ERROR << "inotify_init1 failed."; 1277 return -1; 1278 } 1279 1280 // Add watch on directory to handle redfish event log file 1281 // create/delete. 1282 dirWatchDesc = inotify_add_watch(inotifyFd, redfishEventLogDir, 1283 IN_CREATE | IN_MOVED_TO | IN_DELETE); 1284 if (dirWatchDesc == -1) 1285 { 1286 BMCWEB_LOG_ERROR 1287 << "inotify_add_watch failed for event log directory."; 1288 return -1; 1289 } 1290 1291 // Watch redfish event log file for modifications. 1292 fileWatchDesc = 1293 inotify_add_watch(inotifyFd, redfishEventLogFile, IN_MODIFY); 1294 if (fileWatchDesc == -1) 1295 { 1296 BMCWEB_LOG_ERROR 1297 << "inotify_add_watch failed for redfish log file."; 1298 // Don't return error if file not exist. 1299 // Watch on directory will handle create/delete of file. 1300 } 1301 1302 // monitor redfish event log file 1303 inotifyConn->assign(inotifyFd); 1304 watchRedfishEventLogFile(); 1305 1306 return 0; 1307 } 1308 1309 #endif 1310 static void getReadingsForReport(sdbusplus::message::message& msg) 1311 { 1312 if (msg.is_method_error()) 1313 { 1314 BMCWEB_LOG_ERROR << "TelemetryMonitor Signal error"; 1315 return; 1316 } 1317 1318 sdbusplus::message::object_path path(msg.get_path()); 1319 std::string id = path.filename(); 1320 if (id.empty()) 1321 { 1322 BMCWEB_LOG_ERROR << "Failed to get Id from path"; 1323 return; 1324 } 1325 1326 std::string interface; 1327 dbus::utility::DBusPropertiesMap props; 1328 std::vector<std::string> invalidProps; 1329 msg.read(interface, props, invalidProps); 1330 1331 auto found = 1332 std::find_if(props.begin(), props.end(), 1333 [](const auto& x) { return x.first == "Readings"; }); 1334 if (found == props.end()) 1335 { 1336 BMCWEB_LOG_INFO << "Failed to get Readings from Report properties"; 1337 return; 1338 } 1339 1340 const telemetry::TimestampReadings* readings = 1341 std::get_if<telemetry::TimestampReadings>(&found->second); 1342 if (readings == nullptr) 1343 { 1344 BMCWEB_LOG_INFO << "Failed to get Readings from Report properties"; 1345 return; 1346 } 1347 1348 for (const auto& it : 1349 EventServiceManager::getInstance().subscriptionsMap) 1350 { 1351 Subscription& entry = *it.second; 1352 if (entry.eventFormatType == metricReportFormatType) 1353 { 1354 entry.filterAndSendReports(id, *readings); 1355 } 1356 } 1357 } 1358 1359 void unregisterMetricReportSignal() 1360 { 1361 if (matchTelemetryMonitor) 1362 { 1363 BMCWEB_LOG_DEBUG << "Metrics report signal - Unregister"; 1364 matchTelemetryMonitor.reset(); 1365 matchTelemetryMonitor = nullptr; 1366 } 1367 } 1368 1369 void registerMetricReportSignal() 1370 { 1371 if (!serviceEnabled || matchTelemetryMonitor) 1372 { 1373 BMCWEB_LOG_DEBUG << "Not registering metric report signal."; 1374 return; 1375 } 1376 1377 BMCWEB_LOG_DEBUG << "Metrics report signal - Register"; 1378 std::string matchStr = "type='signal',member='PropertiesChanged'," 1379 "interface='org.freedesktop.DBus.Properties'," 1380 "arg0=xyz.openbmc_project.Telemetry.Report"; 1381 1382 matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match::match>( 1383 *crow::connections::systemBus, matchStr, getReadingsForReport); 1384 } 1385 }; 1386 1387 } // namespace redfish 1388