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