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