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 "dbus_utility.hpp" 18 #include "error_messages.hpp" 19 #include "event_service_store.hpp" 20 #include "http_client.hpp" 21 #include "metric_report.hpp" 22 #include "persistent_data.hpp" 23 #include "random.hpp" 24 #include "registries.hpp" 25 #include "registries/base_message_registry.hpp" 26 #include "registries/openbmc_message_registry.hpp" 27 #include "registries/task_event_message_registry.hpp" 28 #include "server_sent_events.hpp" 29 #include "str_utility.hpp" 30 #include "utility.hpp" 31 #include "utils/json_utils.hpp" 32 33 #include <sys/inotify.h> 34 35 #include <boost/algorithm/string/classification.hpp> 36 #include <boost/asio/io_context.hpp> 37 #include <boost/container/flat_map.hpp> 38 #include <boost/url/format.hpp> 39 #include <sdbusplus/bus/match.hpp> 40 41 #include <cstdlib> 42 #include <ctime> 43 #include <fstream> 44 #include <memory> 45 #include <span> 46 47 namespace redfish 48 { 49 50 using ReadingsObjType = 51 std::vector<std::tuple<std::string, std::string, double, int32_t>>; 52 53 static constexpr const char* eventFormatType = "Event"; 54 static constexpr const char* metricReportFormatType = "MetricReport"; 55 56 static constexpr const char* eventServiceFile = 57 "/var/lib/bmcweb/eventservice_config.json"; 58 59 namespace registries 60 { 61 inline std::span<const MessageEntry> 62 getRegistryFromPrefix(const std::string& registryName) 63 { 64 if (task_event::header.registryPrefix == registryName) 65 { 66 return {task_event::registry}; 67 } 68 if (openbmc::header.registryPrefix == registryName) 69 { 70 return {openbmc::registry}; 71 } 72 if (base::header.registryPrefix == registryName) 73 { 74 return {base::registry}; 75 } 76 return {openbmc::registry}; 77 } 78 } // namespace registries 79 80 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 81 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 82 static std::optional<boost::asio::posix::stream_descriptor> inotifyConn; 83 static constexpr const char* redfishEventLogDir = "/var/log"; 84 static constexpr const char* redfishEventLogFile = "/var/log/redfish"; 85 static constexpr const size_t iEventSize = sizeof(inotify_event); 86 87 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 88 static int inotifyFd = -1; 89 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 90 static int dirWatchDesc = -1; 91 // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) 92 static int fileWatchDesc = -1; 93 94 // <ID, timestamp, RedfishLogId, registryPrefix, MessageId, MessageArgs> 95 using EventLogObjectsType = 96 std::tuple<std::string, std::string, std::string, std::string, std::string, 97 std::vector<std::string>>; 98 99 namespace registries 100 { 101 static const Message* 102 getMsgFromRegistry(const std::string& messageKey, 103 const std::span<const MessageEntry>& registry) 104 { 105 std::span<const MessageEntry>::iterator messageIt = 106 std::find_if(registry.begin(), registry.end(), 107 [&messageKey](const MessageEntry& messageEntry) { 108 return messageKey == messageEntry.first; 109 }); 110 if (messageIt != registry.end()) 111 { 112 return &messageIt->second; 113 } 114 115 return nullptr; 116 } 117 118 static const Message* formatMessage(std::string_view messageID) 119 { 120 // Redfish MessageIds are in the form 121 // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find 122 // the right Message 123 std::vector<std::string> fields; 124 fields.reserve(4); 125 126 bmcweb::split(fields, messageID, '.'); 127 if (fields.size() != 4) 128 { 129 return nullptr; 130 } 131 const std::string& registryName = fields[0]; 132 const std::string& messageKey = fields[3]; 133 134 // Find the right registry and check it for the MessageKey 135 return getMsgFromRegistry(messageKey, getRegistryFromPrefix(registryName)); 136 } 137 } // namespace registries 138 139 namespace event_log 140 { 141 inline bool getUniqueEntryID(const std::string& logEntry, std::string& entryID) 142 { 143 static time_t prevTs = 0; 144 static int index = 0; 145 146 // Get the entry timestamp 147 std::time_t curTs = 0; 148 std::tm timeStruct = {}; 149 std::istringstream entryStream(logEntry); 150 if (entryStream >> std::get_time(&timeStruct, "%Y-%m-%dT%H:%M:%S")) 151 { 152 curTs = std::mktime(&timeStruct); 153 if (curTs == -1) 154 { 155 return false; 156 } 157 } 158 // If the timestamp isn't unique, increment the index 159 index = (curTs == prevTs) ? index + 1 : 0; 160 161 // Save the timestamp 162 prevTs = curTs; 163 164 entryID = std::to_string(curTs); 165 if (index > 0) 166 { 167 entryID += "_" + std::to_string(index); 168 } 169 return true; 170 } 171 172 inline int getEventLogParams(const std::string& logEntry, 173 std::string& timestamp, std::string& messageID, 174 std::vector<std::string>& messageArgs) 175 { 176 // The redfish log format is "<Timestamp> <MessageId>,<MessageArgs>" 177 // First get the Timestamp 178 size_t space = logEntry.find_first_of(' '); 179 if (space == std::string::npos) 180 { 181 return -EINVAL; 182 } 183 timestamp = logEntry.substr(0, space); 184 // Then get the log contents 185 size_t entryStart = logEntry.find_first_not_of(' ', space); 186 if (entryStart == std::string::npos) 187 { 188 return -EINVAL; 189 } 190 std::string_view entry(logEntry); 191 entry.remove_prefix(entryStart); 192 // Use split to separate the entry into its fields 193 std::vector<std::string> logEntryFields; 194 bmcweb::split(logEntryFields, entry, ','); 195 // We need at least a MessageId to be valid 196 if (logEntryFields.empty()) 197 { 198 return -EINVAL; 199 } 200 messageID = logEntryFields[0]; 201 202 // Get the MessageArgs from the log if there are any 203 if (logEntryFields.size() > 1) 204 { 205 const std::string& messageArgsStart = logEntryFields[1]; 206 // If the first string is empty, assume there are no MessageArgs 207 if (!messageArgsStart.empty()) 208 { 209 messageArgs.assign(logEntryFields.begin() + 1, 210 logEntryFields.end()); 211 } 212 } 213 214 return 0; 215 } 216 217 inline void getRegistryAndMessageKey(const std::string& messageID, 218 std::string& registryName, 219 std::string& messageKey) 220 { 221 // Redfish MessageIds are in the form 222 // RegistryName.MajorVersion.MinorVersion.MessageKey, so parse it to find 223 // the right Message 224 std::vector<std::string> fields; 225 fields.reserve(4); 226 bmcweb::split(fields, messageID, '.'); 227 if (fields.size() == 4) 228 { 229 registryName = fields[0]; 230 messageKey = fields[3]; 231 } 232 } 233 234 inline int formatEventLogEntry(const std::string& logEntryID, 235 const std::string& messageID, 236 const std::span<std::string_view> messageArgs, 237 std::string timestamp, 238 const std::string& customText, 239 nlohmann::json& logEntryJson) 240 { 241 // Get the Message from the MessageRegistry 242 const registries::Message* message = registries::formatMessage(messageID); 243 244 if (message == nullptr) 245 { 246 return -1; 247 } 248 249 std::string msg = redfish::registries::fillMessageArgs(messageArgs, 250 message->message); 251 if (msg.empty()) 252 { 253 return -1; 254 } 255 256 // Get the Created time from the timestamp. The log timestamp is in 257 // RFC3339 format which matches the Redfish format except for the 258 // fractional seconds between the '.' and the '+', so just remove them. 259 std::size_t dot = timestamp.find_first_of('.'); 260 std::size_t plus = timestamp.find_first_of('+', dot); 261 if (dot != std::string::npos && plus != std::string::npos) 262 { 263 timestamp.erase(dot, plus - dot); 264 } 265 266 // Fill in the log entry with the gathered data 267 logEntryJson["EventId"] = logEntryID; 268 logEntryJson["EventType"] = "Event"; 269 logEntryJson["Severity"] = message->messageSeverity; 270 logEntryJson["Message"] = std::move(msg); 271 logEntryJson["MessageId"] = messageID; 272 logEntryJson["MessageArgs"] = messageArgs; 273 logEntryJson["EventTimestamp"] = std::move(timestamp); 274 logEntryJson["Context"] = customText; 275 return 0; 276 } 277 278 } // namespace event_log 279 #endif 280 281 inline bool isFilterQuerySpecialChar(char c) 282 { 283 switch (c) 284 { 285 case '(': 286 case ')': 287 case '\'': 288 return true; 289 default: 290 return false; 291 } 292 } 293 294 inline bool 295 readSSEQueryParams(std::string sseFilter, std::string& formatType, 296 std::vector<std::string>& messageIds, 297 std::vector<std::string>& registryPrefixes, 298 std::vector<std::string>& metricReportDefinitions) 299 { 300 sseFilter.erase(std::remove_if(sseFilter.begin(), sseFilter.end(), 301 isFilterQuerySpecialChar), 302 sseFilter.end()); 303 304 std::vector<std::string> result; 305 306 // NOLINTNEXTLINE 307 bmcweb::split(result, sseFilter, ' '); 308 309 BMCWEB_LOG_DEBUG << "No of tokens in SEE query: " << result.size(); 310 311 constexpr uint8_t divisor = 4; 312 constexpr uint8_t minTokenSize = 3; 313 if (result.size() % divisor != minTokenSize) 314 { 315 BMCWEB_LOG_ERROR << "Invalid SSE filter specified."; 316 return false; 317 } 318 319 for (std::size_t i = 0; i < result.size(); i += divisor) 320 { 321 const std::string& key = result[i]; 322 const std::string& op = result[i + 1]; 323 const std::string& value = result[i + 2]; 324 325 if ((i + minTokenSize) < result.size()) 326 { 327 const std::string& separator = result[i + minTokenSize]; 328 // SSE supports only "or" and "and" in query params. 329 if ((separator != "or") && (separator != "and")) 330 { 331 BMCWEB_LOG_ERROR 332 << "Invalid group operator in SSE query parameters"; 333 return false; 334 } 335 } 336 337 // SSE supports only "eq" as per spec. 338 if (op != "eq") 339 { 340 BMCWEB_LOG_ERROR 341 << "Invalid assignment operator in SSE query parameters"; 342 return false; 343 } 344 345 BMCWEB_LOG_DEBUG << key << " : " << value; 346 if (key == "EventFormatType") 347 { 348 formatType = value; 349 } 350 else if (key == "MessageId") 351 { 352 messageIds.push_back(value); 353 } 354 else if (key == "RegistryPrefix") 355 { 356 registryPrefixes.push_back(value); 357 } 358 else if (key == "MetricReportDefinition") 359 { 360 metricReportDefinitions.push_back(value); 361 } 362 else 363 { 364 BMCWEB_LOG_ERROR << "Invalid property(" << key 365 << ")in SSE filter query."; 366 return false; 367 } 368 } 369 return true; 370 } 371 372 class Subscription : public persistent_data::UserSubscription 373 { 374 public: 375 Subscription(const Subscription&) = delete; 376 Subscription& operator=(const Subscription&) = delete; 377 Subscription(Subscription&&) = delete; 378 Subscription& operator=(Subscription&&) = delete; 379 380 Subscription(const std::string& inHost, uint16_t inPort, 381 const std::string& inPath, const std::string& inUriProto) : 382 host(inHost), 383 port(inPort), policy(std::make_shared<crow::ConnectionPolicy>()), 384 client(policy), path(inPath), uriProto(inUriProto) 385 { 386 // Subscription constructor 387 policy->invalidResp = retryRespHandler; 388 } 389 390 explicit Subscription( 391 const std::shared_ptr<boost::asio::ip::tcp::socket>& adaptor) : 392 policy(std::make_shared<crow::ConnectionPolicy>()), 393 client(policy), 394 sseConn(std::make_shared<crow::ServerSentEvents>(adaptor)) 395 {} 396 397 ~Subscription() = default; 398 399 bool sendEvent(std::string& msg) 400 { 401 persistent_data::EventServiceConfig eventServiceConfig = 402 persistent_data::EventServiceStore::getInstance() 403 .getEventServiceConfig(); 404 if (!eventServiceConfig.enabled) 405 { 406 return false; 407 } 408 409 bool useSSL = (uriProto == "https"); 410 // A connection pool will be created if one does not already exist 411 client.sendData(msg, host, port, path, useSSL, httpHeaders, 412 boost::beast::http::verb::post); 413 eventSeqNum++; 414 415 if (sseConn != nullptr) 416 { 417 sseConn->sendData(eventSeqNum, msg); 418 } 419 return true; 420 } 421 422 bool sendTestEventLog() 423 { 424 nlohmann::json logEntryArray; 425 logEntryArray.push_back({}); 426 nlohmann::json& logEntryJson = logEntryArray.back(); 427 428 logEntryJson["EventId"] = "TestID"; 429 logEntryJson["EventType"] = "Event"; 430 logEntryJson["Severity"] = "OK"; 431 logEntryJson["Message"] = "Generated test event"; 432 logEntryJson["MessageId"] = "OpenBMC.0.2.TestEventLog"; 433 logEntryJson["MessageArgs"] = nlohmann::json::array(); 434 logEntryJson["EventTimestamp"] = 435 redfish::time_utils::getDateTimeOffsetNow().first; 436 logEntryJson["Context"] = customText; 437 438 nlohmann::json msg; 439 msg["@odata.type"] = "#Event.v1_4_0.Event"; 440 msg["Id"] = std::to_string(eventSeqNum); 441 msg["Name"] = "Event Log"; 442 msg["Events"] = logEntryArray; 443 444 std::string strMsg = msg.dump(2, ' ', true, 445 nlohmann::json::error_handler_t::replace); 446 return this->sendEvent(strMsg); 447 } 448 449 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 450 void filterAndSendEventLogs( 451 const std::vector<EventLogObjectsType>& eventRecords) 452 { 453 nlohmann::json logEntryArray; 454 for (const EventLogObjectsType& logEntry : eventRecords) 455 { 456 const std::string& idStr = std::get<0>(logEntry); 457 const std::string& timestamp = std::get<1>(logEntry); 458 const std::string& messageID = std::get<2>(logEntry); 459 const std::string& registryName = std::get<3>(logEntry); 460 const std::string& messageKey = std::get<4>(logEntry); 461 const std::vector<std::string>& messageArgs = std::get<5>(logEntry); 462 463 // If registryPrefixes list is empty, don't filter events 464 // send everything. 465 if (!registryPrefixes.empty()) 466 { 467 auto obj = std::find(registryPrefixes.begin(), 468 registryPrefixes.end(), registryName); 469 if (obj == registryPrefixes.end()) 470 { 471 continue; 472 } 473 } 474 475 // If registryMsgIds list is empty, don't filter events 476 // send everything. 477 if (!registryMsgIds.empty()) 478 { 479 auto obj = std::find(registryMsgIds.begin(), 480 registryMsgIds.end(), messageKey); 481 if (obj == registryMsgIds.end()) 482 { 483 continue; 484 } 485 } 486 487 std::vector<std::string_view> messageArgsView(messageArgs.begin(), 488 messageArgs.end()); 489 490 logEntryArray.push_back({}); 491 nlohmann::json& bmcLogEntry = logEntryArray.back(); 492 if (event_log::formatEventLogEntry(idStr, messageID, 493 messageArgsView, timestamp, 494 customText, bmcLogEntry) != 0) 495 { 496 BMCWEB_LOG_DEBUG << "Read eventLog entry failed"; 497 continue; 498 } 499 } 500 501 if (logEntryArray.empty()) 502 { 503 BMCWEB_LOG_DEBUG << "No log entries available to be transferred."; 504 return; 505 } 506 507 nlohmann::json msg; 508 msg["@odata.type"] = "#Event.v1_4_0.Event"; 509 msg["Id"] = std::to_string(eventSeqNum); 510 msg["Name"] = "Event Log"; 511 msg["Events"] = logEntryArray; 512 513 std::string strMsg = msg.dump(2, ' ', true, 514 nlohmann::json::error_handler_t::replace); 515 this->sendEvent(strMsg); 516 } 517 #endif 518 519 void filterAndSendReports(const std::string& reportId, 520 const telemetry::TimestampReadings& var) 521 { 522 boost::urls::url mrdUri = boost::urls::format( 523 "/redfish/v1/TelemetryService/MetricReportDefinitions/{}", 524 reportId); 525 526 // Empty list means no filter. Send everything. 527 if (!metricReportDefinitions.empty()) 528 { 529 if (std::find(metricReportDefinitions.begin(), 530 metricReportDefinitions.end(), 531 mrdUri.buffer()) == metricReportDefinitions.end()) 532 { 533 return; 534 } 535 } 536 537 nlohmann::json msg; 538 if (!telemetry::fillReport(msg, reportId, var)) 539 { 540 BMCWEB_LOG_ERROR << "Failed to fill the MetricReport for DBus " 541 "Report with id " 542 << reportId; 543 return; 544 } 545 546 // Context is set by user during Event subscription and it must be 547 // set for MetricReport response. 548 if (!customText.empty()) 549 { 550 msg["Context"] = customText; 551 } 552 553 std::string strMsg = msg.dump(2, ' ', true, 554 nlohmann::json::error_handler_t::replace); 555 this->sendEvent(strMsg); 556 } 557 558 void updateRetryConfig(uint32_t retryAttempts, 559 uint32_t retryTimeoutInterval) 560 { 561 policy->maxRetryAttempts = retryAttempts; 562 policy->retryIntervalSecs = std::chrono::seconds(retryTimeoutInterval); 563 } 564 565 uint64_t getEventSeqNum() const 566 { 567 return eventSeqNum; 568 } 569 570 private: 571 uint64_t eventSeqNum = 1; 572 std::string host; 573 uint16_t port = 0; 574 std::shared_ptr<crow::ConnectionPolicy> policy; 575 crow::HttpClient client; 576 std::string path; 577 std::string uriProto; 578 std::shared_ptr<crow::ServerSentEvents> sseConn = nullptr; 579 580 // Check used to indicate what response codes are valid as part of our retry 581 // policy. 2XX is considered acceptable 582 static boost::system::error_code retryRespHandler(unsigned int respCode) 583 { 584 BMCWEB_LOG_DEBUG 585 << "Checking response code validity for SubscriptionEvent"; 586 if ((respCode < 200) || (respCode >= 300)) 587 { 588 return boost::system::errc::make_error_code( 589 boost::system::errc::result_out_of_range); 590 } 591 592 // Return 0 if the response code is valid 593 return boost::system::errc::make_error_code( 594 boost::system::errc::success); 595 } 596 }; 597 598 class EventServiceManager 599 { 600 private: 601 bool serviceEnabled = false; 602 uint32_t retryAttempts = 0; 603 uint32_t retryTimeoutInterval = 0; 604 605 EventServiceManager() 606 { 607 // Load config from persist store. 608 initConfig(); 609 } 610 611 std::streampos redfishLogFilePosition{0}; 612 size_t noOfEventLogSubscribers{0}; 613 size_t noOfMetricReportSubscribers{0}; 614 std::shared_ptr<sdbusplus::bus::match_t> matchTelemetryMonitor; 615 boost::container::flat_map<std::string, std::shared_ptr<Subscription>> 616 subscriptionsMap; 617 618 uint64_t eventId{1}; 619 620 public: 621 EventServiceManager(const EventServiceManager&) = delete; 622 EventServiceManager& operator=(const EventServiceManager&) = delete; 623 EventServiceManager(EventServiceManager&&) = delete; 624 EventServiceManager& operator=(EventServiceManager&&) = delete; 625 ~EventServiceManager() = default; 626 627 static EventServiceManager& getInstance() 628 { 629 static EventServiceManager handler; 630 return handler; 631 } 632 633 void initConfig() 634 { 635 loadOldBehavior(); 636 637 persistent_data::EventServiceConfig eventServiceConfig = 638 persistent_data::EventServiceStore::getInstance() 639 .getEventServiceConfig(); 640 641 serviceEnabled = eventServiceConfig.enabled; 642 retryAttempts = eventServiceConfig.retryAttempts; 643 retryTimeoutInterval = eventServiceConfig.retryTimeoutInterval; 644 645 for (const auto& it : persistent_data::EventServiceStore::getInstance() 646 .subscriptionsConfigMap) 647 { 648 std::shared_ptr<persistent_data::UserSubscription> newSub = 649 it.second; 650 651 std::string host; 652 std::string urlProto; 653 uint16_t port = 0; 654 std::string path; 655 bool status = crow::utility::validateAndSplitUrl( 656 newSub->destinationUrl, urlProto, host, port, path); 657 658 if (!status) 659 { 660 BMCWEB_LOG_ERROR 661 << "Failed to validate and split destination url"; 662 continue; 663 } 664 std::shared_ptr<Subscription> subValue = 665 std::make_shared<Subscription>(host, port, path, urlProto); 666 667 subValue->id = newSub->id; 668 subValue->destinationUrl = newSub->destinationUrl; 669 subValue->protocol = newSub->protocol; 670 subValue->retryPolicy = newSub->retryPolicy; 671 subValue->customText = newSub->customText; 672 subValue->eventFormatType = newSub->eventFormatType; 673 subValue->subscriptionType = newSub->subscriptionType; 674 subValue->registryMsgIds = newSub->registryMsgIds; 675 subValue->registryPrefixes = newSub->registryPrefixes; 676 subValue->resourceTypes = newSub->resourceTypes; 677 subValue->httpHeaders = newSub->httpHeaders; 678 subValue->metricReportDefinitions = newSub->metricReportDefinitions; 679 680 if (subValue->id.empty()) 681 { 682 BMCWEB_LOG_ERROR << "Failed to add subscription"; 683 } 684 subscriptionsMap.insert(std::pair(subValue->id, subValue)); 685 686 updateNoOfSubscribersCount(); 687 688 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 689 690 cacheRedfishLogFile(); 691 692 #endif 693 // Update retry configuration. 694 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 695 } 696 } 697 698 static void loadOldBehavior() 699 { 700 std::ifstream eventConfigFile(eventServiceFile); 701 if (!eventConfigFile.good()) 702 { 703 BMCWEB_LOG_DEBUG << "Old eventService config not exist"; 704 return; 705 } 706 auto jsonData = nlohmann::json::parse(eventConfigFile, nullptr, false); 707 if (jsonData.is_discarded()) 708 { 709 BMCWEB_LOG_ERROR << "Old eventService config parse error."; 710 return; 711 } 712 713 for (const auto& item : jsonData.items()) 714 { 715 if (item.key() == "Configuration") 716 { 717 persistent_data::EventServiceStore::getInstance() 718 .getEventServiceConfig() 719 .fromJson(item.value()); 720 } 721 else if (item.key() == "Subscriptions") 722 { 723 for (const auto& elem : item.value()) 724 { 725 std::shared_ptr<persistent_data::UserSubscription> 726 newSubscription = 727 persistent_data::UserSubscription::fromJson(elem, 728 true); 729 if (newSubscription == nullptr) 730 { 731 BMCWEB_LOG_ERROR << "Problem reading subscription " 732 "from old persistent store"; 733 continue; 734 } 735 736 std::uniform_int_distribution<uint32_t> dist(0); 737 bmcweb::OpenSSLGenerator gen; 738 739 std::string id; 740 741 int retry = 3; 742 while (retry != 0) 743 { 744 id = std::to_string(dist(gen)); 745 if (gen.error()) 746 { 747 retry = 0; 748 break; 749 } 750 newSubscription->id = id; 751 auto inserted = 752 persistent_data::EventServiceStore::getInstance() 753 .subscriptionsConfigMap.insert( 754 std::pair(id, newSubscription)); 755 if (inserted.second) 756 { 757 break; 758 } 759 --retry; 760 } 761 762 if (retry <= 0) 763 { 764 BMCWEB_LOG_ERROR 765 << "Failed to generate random number from old " 766 "persistent store"; 767 continue; 768 } 769 } 770 } 771 772 persistent_data::getConfig().writeData(); 773 std::remove(eventServiceFile); 774 BMCWEB_LOG_DEBUG << "Remove old eventservice config"; 775 } 776 } 777 778 void updateSubscriptionData() const 779 { 780 persistent_data::EventServiceStore::getInstance() 781 .eventServiceConfig.enabled = serviceEnabled; 782 persistent_data::EventServiceStore::getInstance() 783 .eventServiceConfig.retryAttempts = retryAttempts; 784 persistent_data::EventServiceStore::getInstance() 785 .eventServiceConfig.retryTimeoutInterval = retryTimeoutInterval; 786 787 persistent_data::getConfig().writeData(); 788 } 789 790 void setEventServiceConfig(const persistent_data::EventServiceConfig& cfg) 791 { 792 bool updateConfig = false; 793 bool updateRetryCfg = false; 794 795 if (serviceEnabled != cfg.enabled) 796 { 797 serviceEnabled = cfg.enabled; 798 if (serviceEnabled && noOfMetricReportSubscribers != 0U) 799 { 800 registerMetricReportSignal(); 801 } 802 else 803 { 804 unregisterMetricReportSignal(); 805 } 806 updateConfig = true; 807 } 808 809 if (retryAttempts != cfg.retryAttempts) 810 { 811 retryAttempts = cfg.retryAttempts; 812 updateConfig = true; 813 updateRetryCfg = true; 814 } 815 816 if (retryTimeoutInterval != cfg.retryTimeoutInterval) 817 { 818 retryTimeoutInterval = cfg.retryTimeoutInterval; 819 updateConfig = true; 820 updateRetryCfg = true; 821 } 822 823 if (updateConfig) 824 { 825 updateSubscriptionData(); 826 } 827 828 if (updateRetryCfg) 829 { 830 // Update the changed retry config to all subscriptions 831 for (const auto& it : 832 EventServiceManager::getInstance().subscriptionsMap) 833 { 834 std::shared_ptr<Subscription> entry = it.second; 835 entry->updateRetryConfig(retryAttempts, retryTimeoutInterval); 836 } 837 } 838 } 839 840 void updateNoOfSubscribersCount() 841 { 842 size_t eventLogSubCount = 0; 843 size_t metricReportSubCount = 0; 844 for (const auto& it : subscriptionsMap) 845 { 846 std::shared_ptr<Subscription> entry = it.second; 847 if (entry->eventFormatType == eventFormatType) 848 { 849 eventLogSubCount++; 850 } 851 else if (entry->eventFormatType == metricReportFormatType) 852 { 853 metricReportSubCount++; 854 } 855 } 856 857 noOfEventLogSubscribers = eventLogSubCount; 858 if (noOfMetricReportSubscribers != metricReportSubCount) 859 { 860 noOfMetricReportSubscribers = metricReportSubCount; 861 if (noOfMetricReportSubscribers != 0U) 862 { 863 registerMetricReportSignal(); 864 } 865 else 866 { 867 unregisterMetricReportSignal(); 868 } 869 } 870 } 871 872 std::shared_ptr<Subscription> getSubscription(const std::string& id) 873 { 874 auto obj = subscriptionsMap.find(id); 875 if (obj == subscriptionsMap.end()) 876 { 877 BMCWEB_LOG_ERROR << "No subscription exist with ID:" << id; 878 return nullptr; 879 } 880 std::shared_ptr<Subscription> subValue = obj->second; 881 return subValue; 882 } 883 884 std::string addSubscription(const std::shared_ptr<Subscription>& subValue, 885 const bool updateFile = true) 886 { 887 std::uniform_int_distribution<uint32_t> dist(0); 888 bmcweb::OpenSSLGenerator gen; 889 890 std::string id; 891 892 int retry = 3; 893 while (retry != 0) 894 { 895 id = std::to_string(dist(gen)); 896 if (gen.error()) 897 { 898 retry = 0; 899 break; 900 } 901 auto inserted = subscriptionsMap.insert(std::pair(id, subValue)); 902 if (inserted.second) 903 { 904 break; 905 } 906 --retry; 907 } 908 909 if (retry <= 0) 910 { 911 BMCWEB_LOG_ERROR << "Failed to generate random number"; 912 return ""; 913 } 914 915 std::shared_ptr<persistent_data::UserSubscription> newSub = 916 std::make_shared<persistent_data::UserSubscription>(); 917 newSub->id = id; 918 newSub->destinationUrl = subValue->destinationUrl; 919 newSub->protocol = subValue->protocol; 920 newSub->retryPolicy = subValue->retryPolicy; 921 newSub->customText = subValue->customText; 922 newSub->eventFormatType = subValue->eventFormatType; 923 newSub->subscriptionType = subValue->subscriptionType; 924 newSub->registryMsgIds = subValue->registryMsgIds; 925 newSub->registryPrefixes = subValue->registryPrefixes; 926 newSub->resourceTypes = subValue->resourceTypes; 927 newSub->httpHeaders = subValue->httpHeaders; 928 newSub->metricReportDefinitions = subValue->metricReportDefinitions; 929 persistent_data::EventServiceStore::getInstance() 930 .subscriptionsConfigMap.emplace(newSub->id, newSub); 931 932 updateNoOfSubscribersCount(); 933 934 if (updateFile) 935 { 936 updateSubscriptionData(); 937 } 938 939 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 940 if (redfishLogFilePosition != 0) 941 { 942 cacheRedfishLogFile(); 943 } 944 #endif 945 // Update retry configuration. 946 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 947 948 return id; 949 } 950 951 bool isSubscriptionExist(const std::string& id) 952 { 953 auto obj = subscriptionsMap.find(id); 954 return obj != subscriptionsMap.end(); 955 } 956 957 void deleteSubscription(const std::string& id) 958 { 959 auto obj = subscriptionsMap.find(id); 960 if (obj != subscriptionsMap.end()) 961 { 962 subscriptionsMap.erase(obj); 963 auto obj2 = persistent_data::EventServiceStore::getInstance() 964 .subscriptionsConfigMap.find(id); 965 persistent_data::EventServiceStore::getInstance() 966 .subscriptionsConfigMap.erase(obj2); 967 updateNoOfSubscribersCount(); 968 updateSubscriptionData(); 969 } 970 } 971 972 size_t getNumberOfSubscriptions() 973 { 974 return subscriptionsMap.size(); 975 } 976 977 std::vector<std::string> getAllIDs() 978 { 979 std::vector<std::string> idList; 980 for (const auto& it : subscriptionsMap) 981 { 982 idList.emplace_back(it.first); 983 } 984 return idList; 985 } 986 987 bool isDestinationExist(const std::string& destUrl) 988 { 989 for (const auto& it : subscriptionsMap) 990 { 991 std::shared_ptr<Subscription> entry = it.second; 992 if (entry->destinationUrl == destUrl) 993 { 994 BMCWEB_LOG_ERROR << "Destination exist already" << destUrl; 995 return true; 996 } 997 } 998 return false; 999 } 1000 1001 bool sendTestEventLog() 1002 { 1003 for (const auto& it : this->subscriptionsMap) 1004 { 1005 std::shared_ptr<Subscription> entry = it.second; 1006 if (!entry->sendTestEventLog()) 1007 { 1008 return false; 1009 } 1010 } 1011 return true; 1012 } 1013 1014 void sendEvent(nlohmann::json eventMessage, const std::string& origin, 1015 const std::string& resType) 1016 { 1017 if (!serviceEnabled || (noOfEventLogSubscribers == 0U)) 1018 { 1019 BMCWEB_LOG_DEBUG << "EventService disabled or no Subscriptions."; 1020 return; 1021 } 1022 nlohmann::json eventRecord = nlohmann::json::array(); 1023 1024 eventMessage["EventId"] = eventId; 1025 // MemberId is 0 : since we are sending one event record. 1026 eventMessage["MemberId"] = 0; 1027 eventMessage["EventTimestamp"] = 1028 redfish::time_utils::getDateTimeOffsetNow().first; 1029 eventMessage["OriginOfCondition"] = origin; 1030 1031 eventRecord.emplace_back(std::move(eventMessage)); 1032 1033 for (const auto& it : this->subscriptionsMap) 1034 { 1035 std::shared_ptr<Subscription> entry = it.second; 1036 bool isSubscribed = false; 1037 // Search the resourceTypes list for the subscription. 1038 // If resourceTypes list is empty, don't filter events 1039 // send everything. 1040 if (!entry->resourceTypes.empty()) 1041 { 1042 for (const auto& resource : entry->resourceTypes) 1043 { 1044 if (resType == resource) 1045 { 1046 BMCWEB_LOG_INFO << "ResourceType " << resource 1047 << " found in the subscribed list"; 1048 isSubscribed = true; 1049 break; 1050 } 1051 } 1052 } 1053 else // resourceTypes list is empty. 1054 { 1055 isSubscribed = true; 1056 } 1057 if (isSubscribed) 1058 { 1059 nlohmann::json msgJson; 1060 1061 msgJson["@odata.type"] = "#Event.v1_4_0.Event"; 1062 msgJson["Name"] = "Event Log"; 1063 msgJson["Id"] = eventId; 1064 msgJson["Events"] = eventRecord; 1065 1066 std::string strMsg = msgJson.dump( 1067 2, ' ', true, nlohmann::json::error_handler_t::replace); 1068 entry->sendEvent(strMsg); 1069 eventId++; // increament the eventId 1070 } 1071 else 1072 { 1073 BMCWEB_LOG_INFO << "Not subscribed to this resource"; 1074 } 1075 } 1076 } 1077 void sendBroadcastMsg(const std::string& broadcastMsg) 1078 { 1079 for (const auto& it : this->subscriptionsMap) 1080 { 1081 std::shared_ptr<Subscription> entry = it.second; 1082 nlohmann::json msgJson; 1083 msgJson["Timestamp"] = 1084 redfish::time_utils::getDateTimeOffsetNow().first; 1085 msgJson["OriginOfCondition"] = "/ibm/v1/HMC/BroadcastService"; 1086 msgJson["Name"] = "Broadcast Message"; 1087 msgJson["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 = inotify_add_watch(inotifyFd, redfishEventLogFile, 1321 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_t& 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_t>( 1411 *crow::connections::systemBus, matchStr, getReadingsForReport); 1412 } 1413 }; 1414 1415 } // namespace redfish 1416