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 conn = std::make_shared<crow::HttpClient>( 387 crow::connections::systemBus->get_io_context(), id, host, port, 388 path); 389 } 390 391 Subscription(const std::shared_ptr<boost::beast::tcp_stream>& adaptor) : 392 eventSeqNum(1) 393 { 394 sseConn = std::make_shared<crow::ServerSentEvents>(adaptor); 395 } 396 397 ~Subscription() = default; 398 399 void sendEvent(const std::string& msg) 400 { 401 if (conn != nullptr) 402 { 403 std::vector<std::pair<std::string, std::string>> reqHeaders; 404 for (const auto& header : httpHeaders) 405 { 406 for (const auto& item : header.items()) 407 { 408 std::string key = item.key(); 409 std::string val = item.value(); 410 reqHeaders.emplace_back(std::pair(key, val)); 411 } 412 } 413 conn->setHeaders(reqHeaders); 414 conn->sendData(msg); 415 this->eventSeqNum++; 416 } 417 418 if (sseConn != nullptr) 419 { 420 sseConn->sendData(eventSeqNum, msg); 421 } 422 } 423 424 void sendTestEventLog() 425 { 426 nlohmann::json logEntryArray; 427 logEntryArray.push_back({}); 428 nlohmann::json& logEntryJson = logEntryArray.back(); 429 430 logEntryJson = { 431 {"EventId", "TestID"}, 432 {"EventType", "Event"}, 433 {"Severity", "OK"}, 434 {"Message", "Generated test event"}, 435 {"MessageId", "OpenBMC.0.2.TestEventLog"}, 436 {"MessageArgs", nlohmann::json::array()}, 437 {"EventTimestamp", crow::utility::getDateTimeOffsetNow().first}, 438 {"Context", customText}}; 439 440 nlohmann::json msg = {{"@odata.type", "#Event.v1_4_0.Event"}, 441 {"Id", std::to_string(eventSeqNum)}, 442 {"Name", "Event Log"}, 443 {"Events", logEntryArray}}; 444 445 this->sendEvent( 446 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace)); 447 } 448 449 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 450 void filterAndSendEventLogs( 451 const std::vector<EventLogObjectsType>& eventRecords) 452 { 453 nlohmann::json logEntryArray; 454 for (const EventLogObjectsType& logEntry : eventRecords) 455 { 456 const std::string& idStr = std::get<0>(logEntry); 457 const std::string& timestamp = std::get<1>(logEntry); 458 const std::string& messageID = std::get<2>(logEntry); 459 const std::string& registryName = std::get<3>(logEntry); 460 const std::string& messageKey = std::get<4>(logEntry); 461 const std::vector<std::string>& messageArgs = std::get<5>(logEntry); 462 463 // If registryPrefixes list is empty, don't filter events 464 // send everything. 465 if (registryPrefixes.size()) 466 { 467 auto obj = std::find(registryPrefixes.begin(), 468 registryPrefixes.end(), registryName); 469 if (obj == registryPrefixes.end()) 470 { 471 continue; 472 } 473 } 474 475 // If registryMsgIds list is empty, don't filter events 476 // send everything. 477 if (registryMsgIds.size()) 478 { 479 auto obj = std::find(registryMsgIds.begin(), 480 registryMsgIds.end(), messageKey); 481 if (obj == registryMsgIds.end()) 482 { 483 continue; 484 } 485 } 486 487 logEntryArray.push_back({}); 488 nlohmann::json& bmcLogEntry = logEntryArray.back(); 489 if (event_log::formatEventLogEntry(idStr, messageID, messageArgs, 490 timestamp, customText, 491 bmcLogEntry) != 0) 492 { 493 BMCWEB_LOG_DEBUG << "Read eventLog entry failed"; 494 continue; 495 } 496 } 497 498 if (logEntryArray.size() < 1) 499 { 500 BMCWEB_LOG_DEBUG << "No log entries available to be transferred."; 501 return; 502 } 503 504 nlohmann::json msg = {{"@odata.type", "#Event.v1_4_0.Event"}, 505 {"Id", std::to_string(eventSeqNum)}, 506 {"Name", "Event Log"}, 507 {"Events", logEntryArray}}; 508 509 this->sendEvent( 510 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace)); 511 } 512 #endif 513 514 void filterAndSendReports(const std::string& id2, 515 const std::string& readingsTs, 516 const ReadingsObjType& readings) 517 { 518 std::string metricReportDef = 519 "/redfish/v1/TelemetryService/MetricReportDefinitions/" + id2; 520 521 // Empty list means no filter. Send everything. 522 if (metricReportDefinitions.size()) 523 { 524 if (std::find(metricReportDefinitions.begin(), 525 metricReportDefinitions.end(), 526 metricReportDef) == metricReportDefinitions.end()) 527 { 528 return; 529 } 530 } 531 532 nlohmann::json metricValuesArray = nlohmann::json::array(); 533 for (const auto& it : readings) 534 { 535 metricValuesArray.push_back({}); 536 nlohmann::json& entry = metricValuesArray.back(); 537 538 auto& [id, property, value, timestamp] = it; 539 540 entry = {{"MetricId", id}, 541 {"MetricProperty", property}, 542 {"MetricValue", std::to_string(value)}, 543 {"Timestamp", crow::utility::getDateTime(timestamp)}}; 544 } 545 546 nlohmann::json msg = { 547 {"@odata.id", "/redfish/v1/TelemetryService/MetricReports/" + id}, 548 {"@odata.type", "#MetricReport.v1_3_0.MetricReport"}, 549 {"Id", id2}, 550 {"Name", id2}, 551 {"Timestamp", readingsTs}, 552 {"MetricReportDefinition", {{"@odata.id", metricReportDef}}}, 553 {"MetricValues", metricValuesArray}}; 554 555 this->sendEvent( 556 msg.dump(2, ' ', true, nlohmann::json::error_handler_t::replace)); 557 } 558 559 void updateRetryConfig(const uint32_t retryAttempts, 560 const uint32_t retryTimeoutInterval) 561 { 562 if (conn != nullptr) 563 { 564 conn->setRetryConfig(retryAttempts, retryTimeoutInterval); 565 } 566 } 567 568 void updateRetryPolicy() 569 { 570 if (conn != nullptr) 571 { 572 conn->setRetryPolicy(retryPolicy); 573 } 574 } 575 576 uint64_t getEventSeqNum() 577 { 578 return eventSeqNum; 579 } 580 581 private: 582 uint64_t eventSeqNum; 583 std::string host; 584 std::string port; 585 std::string path; 586 std::string uriProto; 587 std::shared_ptr<crow::HttpClient> conn = nullptr; 588 std::shared_ptr<crow::ServerSentEvents> sseConn = nullptr; 589 }; 590 591 class EventServiceManager 592 { 593 private: 594 bool serviceEnabled; 595 uint32_t retryAttempts; 596 uint32_t retryTimeoutInterval; 597 598 EventServiceManager() 599 { 600 // Load config from persist store. 601 initConfig(); 602 } 603 604 std::string lastEventTStr; 605 size_t noOfEventLogSubscribers{0}; 606 size_t noOfMetricReportSubscribers{0}; 607 std::shared_ptr<sdbusplus::bus::match::match> matchTelemetryMonitor; 608 boost::container::flat_map<std::string, std::shared_ptr<Subscription>> 609 subscriptionsMap; 610 611 uint64_t eventId{1}; 612 613 public: 614 EventServiceManager(const EventServiceManager&) = delete; 615 EventServiceManager& operator=(const EventServiceManager&) = delete; 616 EventServiceManager(EventServiceManager&&) = delete; 617 EventServiceManager& operator=(EventServiceManager&&) = delete; 618 619 static EventServiceManager& getInstance() 620 { 621 static EventServiceManager handler; 622 return handler; 623 } 624 625 void initConfig() 626 { 627 loadOldBehavior(); 628 629 persistent_data::EventServiceConfig eventServiceConfig = 630 persistent_data::EventServiceStore::getInstance() 631 .getEventServiceConfig(); 632 633 serviceEnabled = eventServiceConfig.enabled; 634 retryAttempts = eventServiceConfig.retryAttempts; 635 retryTimeoutInterval = eventServiceConfig.retryTimeoutInterval; 636 637 for (const auto& it : persistent_data::EventServiceStore::getInstance() 638 .subscriptionsConfigMap) 639 { 640 std::shared_ptr<persistent_data::UserSubscription> newSub = 641 it.second; 642 643 std::string host; 644 std::string urlProto; 645 std::string port; 646 std::string path; 647 bool status = validateAndSplitUrl(newSub->destinationUrl, urlProto, 648 host, port, path); 649 650 if (!status) 651 { 652 BMCWEB_LOG_ERROR 653 << "Failed to validate and split destination url"; 654 continue; 655 } 656 std::shared_ptr<Subscription> subValue = 657 std::make_shared<Subscription>(host, port, path, urlProto); 658 659 subValue->id = newSub->id; 660 subValue->destinationUrl = newSub->destinationUrl; 661 subValue->protocol = newSub->protocol; 662 subValue->retryPolicy = newSub->retryPolicy; 663 subValue->customText = newSub->customText; 664 subValue->eventFormatType = newSub->eventFormatType; 665 subValue->subscriptionType = newSub->subscriptionType; 666 subValue->registryMsgIds = newSub->registryMsgIds; 667 subValue->registryPrefixes = newSub->registryPrefixes; 668 subValue->resourceTypes = newSub->resourceTypes; 669 subValue->httpHeaders = newSub->httpHeaders; 670 subValue->metricReportDefinitions = newSub->metricReportDefinitions; 671 672 if (subValue->id.empty()) 673 { 674 BMCWEB_LOG_ERROR << "Failed to add subscription"; 675 } 676 subscriptionsMap.insert(std::pair(subValue->id, subValue)); 677 678 updateNoOfSubscribersCount(); 679 680 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 681 if (lastEventTStr.empty()) 682 { 683 cacheLastEventTimestamp(); 684 } 685 #endif 686 // Update retry configuration. 687 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 688 subValue->updateRetryPolicy(); 689 } 690 return; 691 } 692 693 void loadOldBehavior() 694 { 695 std::ifstream eventConfigFile(eventServiceFile); 696 if (!eventConfigFile.good()) 697 { 698 BMCWEB_LOG_DEBUG << "Old eventService config not exist"; 699 return; 700 } 701 auto jsonData = nlohmann::json::parse(eventConfigFile, nullptr, false); 702 if (jsonData.is_discarded()) 703 { 704 BMCWEB_LOG_ERROR << "Old eventService config parse error."; 705 return; 706 } 707 708 for (const auto& item : jsonData.items()) 709 { 710 if (item.key() == "Configuration") 711 { 712 persistent_data::EventServiceStore::getInstance() 713 .getEventServiceConfig() 714 .fromJson(item.value()); 715 } 716 else if (item.key() == "Subscriptions") 717 { 718 for (const auto& elem : item.value()) 719 { 720 std::shared_ptr<persistent_data::UserSubscription> 721 newSubscription = 722 persistent_data::UserSubscription::fromJson(elem, 723 true); 724 if (newSubscription == nullptr) 725 { 726 BMCWEB_LOG_ERROR << "Problem reading subscription " 727 "from old persistent store"; 728 continue; 729 } 730 731 std::uniform_int_distribution<uint32_t> dist(0); 732 bmcweb::OpenSSLGenerator gen; 733 734 std::string id; 735 736 int retry = 3; 737 while (retry) 738 { 739 id = std::to_string(dist(gen)); 740 if (gen.error()) 741 { 742 retry = 0; 743 break; 744 } 745 newSubscription->id = id; 746 auto inserted = 747 persistent_data::EventServiceStore::getInstance() 748 .subscriptionsConfigMap.insert( 749 std::pair(id, newSubscription)); 750 if (inserted.second) 751 { 752 break; 753 } 754 --retry; 755 } 756 757 if (retry <= 0) 758 { 759 BMCWEB_LOG_ERROR 760 << "Failed to generate random number from old " 761 "persistent store"; 762 continue; 763 } 764 } 765 } 766 767 persistent_data::getConfig().writeData(); 768 std::remove(eventServiceFile); 769 BMCWEB_LOG_DEBUG << "Remove old eventservice config"; 770 } 771 } 772 773 void updateSubscriptionData() 774 { 775 persistent_data::EventServiceStore::getInstance() 776 .eventServiceConfig.enabled = serviceEnabled; 777 persistent_data::EventServiceStore::getInstance() 778 .eventServiceConfig.retryAttempts = retryAttempts; 779 persistent_data::EventServiceStore::getInstance() 780 .eventServiceConfig.retryTimeoutInterval = retryTimeoutInterval; 781 782 persistent_data::getConfig().writeData(); 783 } 784 785 void setEventServiceConfig(const persistent_data::EventServiceConfig& cfg) 786 { 787 bool updateConfig = false; 788 bool updateRetryCfg = false; 789 790 if (serviceEnabled != cfg.enabled) 791 { 792 serviceEnabled = cfg.enabled; 793 if (serviceEnabled && noOfMetricReportSubscribers) 794 { 795 registerMetricReportSignal(); 796 } 797 else 798 { 799 unregisterMetricReportSignal(); 800 } 801 updateConfig = true; 802 } 803 804 if (retryAttempts != cfg.retryAttempts) 805 { 806 retryAttempts = cfg.retryAttempts; 807 updateConfig = true; 808 updateRetryCfg = true; 809 } 810 811 if (retryTimeoutInterval != cfg.retryTimeoutInterval) 812 { 813 retryTimeoutInterval = cfg.retryTimeoutInterval; 814 updateConfig = true; 815 updateRetryCfg = true; 816 } 817 818 if (updateConfig) 819 { 820 updateSubscriptionData(); 821 } 822 823 if (updateRetryCfg) 824 { 825 // Update the changed retry config to all subscriptions 826 for (const auto& it : 827 EventServiceManager::getInstance().subscriptionsMap) 828 { 829 std::shared_ptr<Subscription> entry = it.second; 830 entry->updateRetryConfig(retryAttempts, retryTimeoutInterval); 831 } 832 } 833 } 834 835 void updateNoOfSubscribersCount() 836 { 837 size_t eventLogSubCount = 0; 838 size_t metricReportSubCount = 0; 839 for (const auto& it : subscriptionsMap) 840 { 841 std::shared_ptr<Subscription> entry = it.second; 842 if (entry->eventFormatType == eventFormatType) 843 { 844 eventLogSubCount++; 845 } 846 else if (entry->eventFormatType == metricReportFormatType) 847 { 848 metricReportSubCount++; 849 } 850 } 851 852 noOfEventLogSubscribers = eventLogSubCount; 853 if (noOfMetricReportSubscribers != metricReportSubCount) 854 { 855 noOfMetricReportSubscribers = metricReportSubCount; 856 if (noOfMetricReportSubscribers) 857 { 858 registerMetricReportSignal(); 859 } 860 else 861 { 862 unregisterMetricReportSignal(); 863 } 864 } 865 } 866 867 std::shared_ptr<Subscription> getSubscription(const std::string& id) 868 { 869 auto obj = subscriptionsMap.find(id); 870 if (obj == subscriptionsMap.end()) 871 { 872 BMCWEB_LOG_ERROR << "No subscription exist with ID:" << id; 873 return nullptr; 874 } 875 std::shared_ptr<Subscription> subValue = obj->second; 876 return subValue; 877 } 878 879 std::string addSubscription(const std::shared_ptr<Subscription>& subValue, 880 const bool updateFile = true) 881 { 882 883 std::uniform_int_distribution<uint32_t> dist(0); 884 bmcweb::OpenSSLGenerator gen; 885 886 std::string id; 887 888 int retry = 3; 889 while (retry) 890 { 891 id = std::to_string(dist(gen)); 892 if (gen.error()) 893 { 894 retry = 0; 895 break; 896 } 897 auto inserted = subscriptionsMap.insert(std::pair(id, subValue)); 898 if (inserted.second) 899 { 900 break; 901 } 902 --retry; 903 } 904 905 if (retry <= 0) 906 { 907 BMCWEB_LOG_ERROR << "Failed to generate random number"; 908 return std::string(""); 909 } 910 911 std::shared_ptr<persistent_data::UserSubscription> newSub = 912 std::make_shared<persistent_data::UserSubscription>(); 913 newSub->id = id; 914 newSub->destinationUrl = subValue->destinationUrl; 915 newSub->protocol = subValue->protocol; 916 newSub->retryPolicy = subValue->retryPolicy; 917 newSub->customText = subValue->customText; 918 newSub->eventFormatType = subValue->eventFormatType; 919 newSub->subscriptionType = subValue->subscriptionType; 920 newSub->registryMsgIds = subValue->registryMsgIds; 921 newSub->registryPrefixes = subValue->registryPrefixes; 922 newSub->resourceTypes = subValue->resourceTypes; 923 newSub->httpHeaders = subValue->httpHeaders; 924 newSub->metricReportDefinitions = subValue->metricReportDefinitions; 925 persistent_data::EventServiceStore::getInstance() 926 .subscriptionsConfigMap.emplace(newSub->id, newSub); 927 928 updateNoOfSubscribersCount(); 929 930 if (updateFile) 931 { 932 updateSubscriptionData(); 933 } 934 935 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 936 if (lastEventTStr.empty()) 937 { 938 cacheLastEventTimestamp(); 939 } 940 #endif 941 // Update retry configuration. 942 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 943 subValue->updateRetryPolicy(); 944 945 return id; 946 } 947 948 bool isSubscriptionExist(const std::string& id) 949 { 950 auto obj = subscriptionsMap.find(id); 951 if (obj == subscriptionsMap.end()) 952 { 953 return false; 954 } 955 return true; 956 } 957 958 void deleteSubscription(const std::string& id) 959 { 960 auto obj = subscriptionsMap.find(id); 961 if (obj != subscriptionsMap.end()) 962 { 963 subscriptionsMap.erase(obj); 964 auto obj2 = persistent_data::EventServiceStore::getInstance() 965 .subscriptionsConfigMap.find(id); 966 persistent_data::EventServiceStore::getInstance() 967 .subscriptionsConfigMap.erase(obj2); 968 updateNoOfSubscribersCount(); 969 updateSubscriptionData(); 970 } 971 } 972 973 size_t getNumberOfSubscriptions() 974 { 975 return subscriptionsMap.size(); 976 } 977 978 std::vector<std::string> getAllIDs() 979 { 980 std::vector<std::string> idList; 981 for (const auto& it : subscriptionsMap) 982 { 983 idList.emplace_back(it.first); 984 } 985 return idList; 986 } 987 988 bool isDestinationExist(const std::string& destUrl) 989 { 990 for (const auto& it : subscriptionsMap) 991 { 992 std::shared_ptr<Subscription> entry = it.second; 993 if (entry->destinationUrl == destUrl) 994 { 995 BMCWEB_LOG_ERROR << "Destination exist already" << destUrl; 996 return true; 997 } 998 } 999 return false; 1000 } 1001 1002 void sendTestEventLog() 1003 { 1004 for (const auto& it : this->subscriptionsMap) 1005 { 1006 std::shared_ptr<Subscription> entry = it.second; 1007 entry->sendTestEventLog(); 1008 } 1009 } 1010 1011 void sendEvent(const nlohmann::json& eventMessageIn, 1012 const std::string& origin, const std::string& resType) 1013 { 1014 nlohmann::json eventRecord = nlohmann::json::array(); 1015 nlohmann::json eventMessage = eventMessageIn; 1016 // MemberId is 0 : since we are sending one event record. 1017 uint64_t memberId = 0; 1018 1019 nlohmann::json event = { 1020 {"EventId", eventId}, 1021 {"MemberId", memberId}, 1022 {"EventTimestamp", crow::utility::getDateTimeOffsetNow().first}, 1023 {"OriginOfCondition", origin}}; 1024 for (nlohmann::json::iterator it = event.begin(); it != event.end(); 1025 ++it) 1026 { 1027 eventMessage[it.key()] = it.value(); 1028 } 1029 eventRecord.push_back(eventMessage); 1030 1031 for (const auto& it : this->subscriptionsMap) 1032 { 1033 std::shared_ptr<Subscription> entry = it.second; 1034 bool isSubscribed = false; 1035 // Search the resourceTypes list for the subscription. 1036 // If resourceTypes list is empty, don't filter events 1037 // send everything. 1038 if (entry->resourceTypes.size()) 1039 { 1040 for (const auto& resource : entry->resourceTypes) 1041 { 1042 if (resType == resource) 1043 { 1044 BMCWEB_LOG_INFO << "ResourceType " << resource 1045 << " found in the subscribed list"; 1046 isSubscribed = true; 1047 break; 1048 } 1049 } 1050 } 1051 else // resourceTypes list is empty. 1052 { 1053 isSubscribed = true; 1054 } 1055 if (isSubscribed) 1056 { 1057 nlohmann::json msgJson = { 1058 {"@odata.type", "#Event.v1_4_0.Event"}, 1059 {"Name", "Event Log"}, 1060 {"Id", eventId}, 1061 {"Events", eventRecord}}; 1062 entry->sendEvent(msgJson.dump( 1063 2, ' ', true, nlohmann::json::error_handler_t::replace)); 1064 eventId++; // increament the eventId 1065 } 1066 else 1067 { 1068 BMCWEB_LOG_INFO << "Not subscribed to this resource"; 1069 } 1070 } 1071 } 1072 void sendBroadcastMsg(const std::string& broadcastMsg) 1073 { 1074 for (const auto& it : this->subscriptionsMap) 1075 { 1076 std::shared_ptr<Subscription> entry = it.second; 1077 nlohmann::json msgJson = { 1078 {"Timestamp", crow::utility::getDateTimeOffsetNow().first}, 1079 {"OriginOfCondition", "/ibm/v1/HMC/BroadcastService"}, 1080 {"Name", "Broadcast Message"}, 1081 {"Message", broadcastMsg}}; 1082 entry->sendEvent(msgJson.dump( 1083 2, ' ', true, nlohmann::json::error_handler_t::replace)); 1084 } 1085 } 1086 1087 #ifndef BMCWEB_ENABLE_REDFISH_DBUS_LOG_ENTRIES 1088 void cacheLastEventTimestamp() 1089 { 1090 lastEventTStr.clear(); 1091 std::ifstream logStream(redfishEventLogFile); 1092 if (!logStream.good()) 1093 { 1094 BMCWEB_LOG_ERROR << " Redfish log file open failed \n"; 1095 return; 1096 } 1097 std::string logEntry; 1098 while (std::getline(logStream, logEntry)) 1099 { 1100 size_t space = logEntry.find_first_of(' '); 1101 if (space == std::string::npos) 1102 { 1103 // Shouldn't enter here but lets skip it. 1104 BMCWEB_LOG_DEBUG << "Invalid log entry found."; 1105 continue; 1106 } 1107 lastEventTStr = logEntry.substr(0, space); 1108 } 1109 BMCWEB_LOG_DEBUG << "Last Event time stamp set: " << lastEventTStr; 1110 } 1111 1112 void readEventLogsFromFile() 1113 { 1114 if (!serviceEnabled || !noOfEventLogSubscribers) 1115 { 1116 BMCWEB_LOG_DEBUG << "EventService disabled or no Subscriptions."; 1117 return; 1118 } 1119 std::ifstream logStream(redfishEventLogFile); 1120 if (!logStream.good()) 1121 { 1122 BMCWEB_LOG_ERROR << " Redfish log file open failed"; 1123 return; 1124 } 1125 1126 std::vector<EventLogObjectsType> eventRecords; 1127 1128 bool startLogCollection = false; 1129 bool firstEntry = true; 1130 1131 std::string logEntry; 1132 while (std::getline(logStream, logEntry)) 1133 { 1134 if (!startLogCollection && !lastEventTStr.empty()) 1135 { 1136 if (boost::starts_with(logEntry, lastEventTStr)) 1137 { 1138 startLogCollection = true; 1139 } 1140 continue; 1141 } 1142 1143 std::string idStr; 1144 if (!event_log::getUniqueEntryID(logEntry, idStr, firstEntry)) 1145 { 1146 continue; 1147 } 1148 firstEntry = false; 1149 1150 std::string timestamp; 1151 std::string messageID; 1152 std::vector<std::string> messageArgs; 1153 if (event_log::getEventLogParams(logEntry, timestamp, messageID, 1154 messageArgs) != 0) 1155 { 1156 BMCWEB_LOG_DEBUG << "Read eventLog entry params failed"; 1157 continue; 1158 } 1159 1160 std::string registryName; 1161 std::string messageKey; 1162 event_log::getRegistryAndMessageKey(messageID, registryName, 1163 messageKey); 1164 if (registryName.empty() || messageKey.empty()) 1165 { 1166 continue; 1167 } 1168 1169 lastEventTStr = timestamp; 1170 eventRecords.emplace_back(idStr, timestamp, messageID, registryName, 1171 messageKey, messageArgs); 1172 } 1173 1174 for (const auto& it : this->subscriptionsMap) 1175 { 1176 std::shared_ptr<Subscription> entry = it.second; 1177 if (entry->eventFormatType == "Event") 1178 { 1179 entry->filterAndSendEventLogs(eventRecords); 1180 } 1181 } 1182 } 1183 1184 static void watchRedfishEventLogFile() 1185 { 1186 if (!inotifyConn) 1187 { 1188 return; 1189 } 1190 1191 static std::array<char, 1024> readBuffer; 1192 1193 inotifyConn->async_read_some( 1194 boost::asio::buffer(readBuffer), 1195 [&](const boost::system::error_code& ec, 1196 const std::size_t& bytesTransferred) { 1197 if (ec) 1198 { 1199 BMCWEB_LOG_ERROR << "Callback Error: " << ec.message(); 1200 return; 1201 } 1202 std::size_t index = 0; 1203 while ((index + iEventSize) <= bytesTransferred) 1204 { 1205 struct inotify_event event; 1206 std::memcpy(&event, &readBuffer[index], iEventSize); 1207 if (event.wd == dirWatchDesc) 1208 { 1209 if ((event.len == 0) || 1210 (index + iEventSize + event.len > bytesTransferred)) 1211 { 1212 index += (iEventSize + event.len); 1213 continue; 1214 } 1215 1216 std::string fileName(&readBuffer[index + iEventSize], 1217 event.len); 1218 if (std::strcmp(fileName.c_str(), "redfish") != 0) 1219 { 1220 index += (iEventSize + event.len); 1221 continue; 1222 } 1223 1224 BMCWEB_LOG_DEBUG 1225 << "Redfish log file created/deleted. event.name: " 1226 << fileName; 1227 if (event.mask == IN_CREATE) 1228 { 1229 if (fileWatchDesc != -1) 1230 { 1231 BMCWEB_LOG_DEBUG 1232 << "Remove and Add inotify watcher on " 1233 "redfish event log file"; 1234 // Remove existing inotify watcher and add 1235 // with new redfish event log file. 1236 inotify_rm_watch(inotifyFd, fileWatchDesc); 1237 fileWatchDesc = -1; 1238 } 1239 1240 fileWatchDesc = inotify_add_watch( 1241 inotifyFd, redfishEventLogFile, IN_MODIFY); 1242 if (fileWatchDesc == -1) 1243 { 1244 BMCWEB_LOG_ERROR 1245 << "inotify_add_watch failed for " 1246 "redfish log file."; 1247 return; 1248 } 1249 1250 EventServiceManager::getInstance() 1251 .cacheLastEventTimestamp(); 1252 EventServiceManager::getInstance() 1253 .readEventLogsFromFile(); 1254 } 1255 else if ((event.mask == IN_DELETE) || 1256 (event.mask == IN_MOVED_TO)) 1257 { 1258 if (fileWatchDesc != -1) 1259 { 1260 inotify_rm_watch(inotifyFd, fileWatchDesc); 1261 fileWatchDesc = -1; 1262 } 1263 } 1264 } 1265 else if (event.wd == fileWatchDesc) 1266 { 1267 if (event.mask == IN_MODIFY) 1268 { 1269 EventServiceManager::getInstance() 1270 .readEventLogsFromFile(); 1271 } 1272 } 1273 index += (iEventSize + event.len); 1274 } 1275 1276 watchRedfishEventLogFile(); 1277 }); 1278 } 1279 1280 static int startEventLogMonitor(boost::asio::io_context& ioc) 1281 { 1282 inotifyConn.emplace(ioc); 1283 inotifyFd = inotify_init1(IN_NONBLOCK); 1284 if (inotifyFd == -1) 1285 { 1286 BMCWEB_LOG_ERROR << "inotify_init1 failed."; 1287 return -1; 1288 } 1289 1290 // Add watch on directory to handle redfish event log file 1291 // create/delete. 1292 dirWatchDesc = inotify_add_watch(inotifyFd, redfishEventLogDir, 1293 IN_CREATE | IN_MOVED_TO | IN_DELETE); 1294 if (dirWatchDesc == -1) 1295 { 1296 BMCWEB_LOG_ERROR 1297 << "inotify_add_watch failed for event log directory."; 1298 return -1; 1299 } 1300 1301 // Watch redfish event log file for modifications. 1302 fileWatchDesc = 1303 inotify_add_watch(inotifyFd, redfishEventLogFile, IN_MODIFY); 1304 if (fileWatchDesc == -1) 1305 { 1306 BMCWEB_LOG_ERROR 1307 << "inotify_add_watch failed for redfish log file."; 1308 // Don't return error if file not exist. 1309 // Watch on directory will handle create/delete of file. 1310 } 1311 1312 // monitor redfish event log file 1313 inotifyConn->assign(inotifyFd); 1314 watchRedfishEventLogFile(); 1315 1316 return 0; 1317 } 1318 1319 #endif 1320 1321 void getMetricReading(const std::string& service, 1322 const std::string& objPath, const std::string& intf) 1323 { 1324 std::size_t found = objPath.find_last_of('/'); 1325 if (found == std::string::npos) 1326 { 1327 BMCWEB_LOG_DEBUG << "Invalid objPath received"; 1328 return; 1329 } 1330 1331 std::string idStr = objPath.substr(found + 1); 1332 if (idStr.empty()) 1333 { 1334 BMCWEB_LOG_DEBUG << "Invalid ID in objPath"; 1335 return; 1336 } 1337 1338 crow::connections::systemBus->async_method_call( 1339 [idStr{std::move(idStr)}]( 1340 const boost::system::error_code ec, 1341 boost::container::flat_map< 1342 std::string, std::variant<int32_t, ReadingsObjType>>& 1343 resp) { 1344 if (ec) 1345 { 1346 BMCWEB_LOG_DEBUG 1347 << "D-Bus call failed to GetAll metric readings."; 1348 return; 1349 } 1350 1351 const int32_t* timestampPtr = 1352 std::get_if<int32_t>(&resp["Timestamp"]); 1353 if (!timestampPtr) 1354 { 1355 BMCWEB_LOG_DEBUG << "Failed to Get timestamp."; 1356 return; 1357 } 1358 1359 ReadingsObjType* readingsPtr = 1360 std::get_if<ReadingsObjType>(&resp["Readings"]); 1361 if (!readingsPtr) 1362 { 1363 BMCWEB_LOG_DEBUG << "Failed to Get Readings property."; 1364 return; 1365 } 1366 1367 if (!readingsPtr->size()) 1368 { 1369 BMCWEB_LOG_DEBUG << "No metrics report to be transferred"; 1370 return; 1371 } 1372 1373 for (const auto& it : 1374 EventServiceManager::getInstance().subscriptionsMap) 1375 { 1376 std::shared_ptr<Subscription> entry = it.second; 1377 if (entry->eventFormatType == metricReportFormatType) 1378 { 1379 entry->filterAndSendReports( 1380 idStr, crow::utility::getDateTime(*timestampPtr), 1381 *readingsPtr); 1382 } 1383 } 1384 }, 1385 service, objPath, "org.freedesktop.DBus.Properties", "GetAll", 1386 intf); 1387 } 1388 1389 void unregisterMetricReportSignal() 1390 { 1391 if (matchTelemetryMonitor) 1392 { 1393 BMCWEB_LOG_DEBUG << "Metrics report signal - Unregister"; 1394 matchTelemetryMonitor.reset(); 1395 matchTelemetryMonitor = nullptr; 1396 } 1397 } 1398 1399 void registerMetricReportSignal() 1400 { 1401 if (!serviceEnabled || matchTelemetryMonitor) 1402 { 1403 BMCWEB_LOG_DEBUG << "Not registering metric report signal."; 1404 return; 1405 } 1406 1407 BMCWEB_LOG_DEBUG << "Metrics report signal - Register"; 1408 std::string matchStr( 1409 "type='signal',member='ReportUpdate', " 1410 "interface='xyz.openbmc_project.MonitoringService.Report'"); 1411 1412 matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match::match>( 1413 *crow::connections::systemBus, matchStr, 1414 [this](sdbusplus::message::message& msg) { 1415 if (msg.is_method_error()) 1416 { 1417 BMCWEB_LOG_ERROR << "TelemetryMonitor Signal error"; 1418 return; 1419 } 1420 1421 std::string service = msg.get_sender(); 1422 std::string objPath = msg.get_path(); 1423 std::string intf = msg.get_interface(); 1424 getMetricReading(service, objPath, intf); 1425 }); 1426 } 1427 1428 bool validateAndSplitUrl(const std::string& destUrl, std::string& urlProto, 1429 std::string& host, std::string& port, 1430 std::string& path) 1431 { 1432 // Validate URL using regex expression 1433 // Format: <protocol>://<host>:<port>/<path> 1434 // protocol: http/https 1435 const std::regex urlRegex( 1436 "(http|https)://([^/\\x20\\x3f\\x23\\x3a]+):?([0-9]*)(/" 1437 "([^\\x20\\x23\\x3f]*\\x3f?([^\\x20\\x23\\x3f])*)?)"); 1438 std::cmatch match; 1439 if (!std::regex_match(destUrl.c_str(), match, urlRegex)) 1440 { 1441 BMCWEB_LOG_INFO << "Dest. url did not match "; 1442 return false; 1443 } 1444 1445 urlProto = std::string(match[1].first, match[1].second); 1446 if (urlProto == "http") 1447 { 1448 #ifndef BMCWEB_INSECURE_ENABLE_HTTP_PUSH_STYLE_EVENTING 1449 return false; 1450 #endif 1451 } 1452 1453 host = std::string(match[2].first, match[2].second); 1454 port = std::string(match[3].first, match[3].second); 1455 path = std::string(match[4].first, match[4].second); 1456 if (port.empty()) 1457 { 1458 if (urlProto == "http") 1459 { 1460 port = "80"; 1461 } 1462 else 1463 { 1464 port = "443"; 1465 } 1466 } 1467 if (path.empty()) 1468 { 1469 path = "/"; 1470 } 1471 return true; 1472 } 1473 }; 1474 1475 } // namespace redfish 1476