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