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