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