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