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