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