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