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