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