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