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