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