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