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