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