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