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