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 "dbus_utility.hpp" 18 #include "error_messages.hpp" 19 #include "event_log.hpp" 20 #include "event_matches_filter.hpp" 21 #include "event_service_store.hpp" 22 #include "filesystem_log_watcher.hpp" 23 #include "metric_report.hpp" 24 #include "ossl_random.hpp" 25 #include "persistent_data.hpp" 26 #include "subscription.hpp" 27 #include "utils/time_utils.hpp" 28 29 #include <sys/inotify.h> 30 31 #include <boost/asio/io_context.hpp> 32 #include <boost/circular_buffer.hpp> 33 #include <boost/container/flat_map.hpp> 34 #include <boost/url/format.hpp> 35 #include <boost/url/url_view_base.hpp> 36 #include <sdbusplus/bus/match.hpp> 37 38 #include <algorithm> 39 #include <cstdlib> 40 #include <ctime> 41 #include <format> 42 #include <fstream> 43 #include <memory> 44 #include <string> 45 #include <string_view> 46 #include <utility> 47 48 namespace redfish 49 { 50 51 static constexpr const char* eventFormatType = "Event"; 52 static constexpr const char* metricReportFormatType = "MetricReport"; 53 54 static constexpr const char* eventServiceFile = 55 "/var/lib/bmcweb/eventservice_config.json"; 56 57 static constexpr const char* redfishEventLogFile = "/var/log/redfish"; 58 59 class EventServiceManager 60 { 61 private: 62 bool serviceEnabled = false; 63 uint32_t retryAttempts = 0; 64 uint32_t retryTimeoutInterval = 0; 65 66 std::streampos redfishLogFilePosition{0}; 67 size_t noOfEventLogSubscribers{0}; 68 size_t noOfMetricReportSubscribers{0}; 69 std::shared_ptr<sdbusplus::bus::match_t> matchTelemetryMonitor; 70 boost::container::flat_map<std::string, std::shared_ptr<Subscription>> 71 subscriptionsMap; 72 73 uint64_t eventId{1}; 74 75 struct Event 76 { 77 std::string id; 78 nlohmann::json message; 79 }; 80 81 constexpr static size_t maxMessages = 200; 82 boost::circular_buffer<Event> messages{maxMessages}; 83 84 boost::asio::io_context& ioc; 85 86 public: 87 EventServiceManager(const EventServiceManager&) = delete; 88 EventServiceManager& operator=(const EventServiceManager&) = delete; 89 EventServiceManager(EventServiceManager&&) = delete; 90 EventServiceManager& operator=(EventServiceManager&&) = delete; 91 ~EventServiceManager() = default; 92 93 explicit EventServiceManager(boost::asio::io_context& iocIn) : ioc(iocIn) 94 { 95 // Load config from persist store. 96 initConfig(); 97 } 98 99 static EventServiceManager& 100 getInstance(boost::asio::io_context* ioc = nullptr) 101 { 102 static EventServiceManager handler(*ioc); 103 return handler; 104 } 105 106 void initConfig() 107 { 108 loadOldBehavior(); 109 110 persistent_data::EventServiceConfig eventServiceConfig = 111 persistent_data::EventServiceStore::getInstance() 112 .getEventServiceConfig(); 113 114 serviceEnabled = eventServiceConfig.enabled; 115 retryAttempts = eventServiceConfig.retryAttempts; 116 retryTimeoutInterval = eventServiceConfig.retryTimeoutInterval; 117 118 for (const auto& it : persistent_data::EventServiceStore::getInstance() 119 .subscriptionsConfigMap) 120 { 121 std::shared_ptr<persistent_data::UserSubscription> newSub = 122 it.second; 123 124 boost::system::result<boost::urls::url> url = 125 boost::urls::parse_absolute_uri(newSub->destinationUrl); 126 127 if (!url) 128 { 129 BMCWEB_LOG_ERROR( 130 "Failed to validate and split destination url"); 131 continue; 132 } 133 std::shared_ptr<Subscription> subValue = 134 std::make_shared<Subscription>(newSub, *url, ioc); 135 std::string id = subValue->userSub->id; 136 subValue->deleter = [id]() { 137 EventServiceManager::getInstance().deleteSubscription(id); 138 }; 139 140 subscriptionsMap.emplace(id, subValue); 141 142 updateNoOfSubscribersCount(); 143 144 if constexpr (!BMCWEB_REDFISH_DBUS_LOG) 145 { 146 cacheRedfishLogFile(); 147 } 148 149 // Update retry configuration. 150 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 151 } 152 } 153 154 static void loadOldBehavior() 155 { 156 std::ifstream eventConfigFile(eventServiceFile); 157 if (!eventConfigFile.good()) 158 { 159 BMCWEB_LOG_DEBUG("Old eventService config not exist"); 160 return; 161 } 162 auto jsonData = nlohmann::json::parse(eventConfigFile, nullptr, false); 163 if (jsonData.is_discarded()) 164 { 165 BMCWEB_LOG_ERROR("Old eventService config parse error."); 166 return; 167 } 168 169 const nlohmann::json::object_t* obj = 170 jsonData.get_ptr<const nlohmann::json::object_t*>(); 171 for (const auto& item : *obj) 172 { 173 if (item.first == "Configuration") 174 { 175 persistent_data::EventServiceStore::getInstance() 176 .getEventServiceConfig() 177 .fromJson(item.second); 178 } 179 else if (item.first == "Subscriptions") 180 { 181 for (const auto& elem : item.second) 182 { 183 std::optional<persistent_data::UserSubscription> 184 newSubscription = 185 persistent_data::UserSubscription::fromJson(elem, 186 true); 187 if (!newSubscription) 188 { 189 BMCWEB_LOG_ERROR("Problem reading subscription " 190 "from old persistent store"); 191 continue; 192 } 193 persistent_data::UserSubscription& newSub = 194 *newSubscription; 195 196 std::uniform_int_distribution<uint32_t> dist(0); 197 bmcweb::OpenSSLGenerator gen; 198 199 std::string id; 200 201 int retry = 3; 202 while (retry != 0) 203 { 204 id = std::to_string(dist(gen)); 205 if (gen.error()) 206 { 207 retry = 0; 208 break; 209 } 210 newSub.id = id; 211 auto inserted = 212 persistent_data::EventServiceStore::getInstance() 213 .subscriptionsConfigMap.insert(std::pair( 214 id, std::make_shared< 215 persistent_data::UserSubscription>( 216 newSub))); 217 if (inserted.second) 218 { 219 break; 220 } 221 --retry; 222 } 223 224 if (retry <= 0) 225 { 226 BMCWEB_LOG_ERROR( 227 "Failed to generate random number from old " 228 "persistent store"); 229 continue; 230 } 231 } 232 } 233 234 persistent_data::getConfig().writeData(); 235 std::error_code ec; 236 std::filesystem::remove(eventServiceFile, ec); 237 if (ec) 238 { 239 BMCWEB_LOG_DEBUG( 240 "Failed to remove old event service file. Ignoring"); 241 } 242 else 243 { 244 BMCWEB_LOG_DEBUG("Remove old eventservice config"); 245 } 246 } 247 } 248 249 void updateSubscriptionData() const 250 { 251 persistent_data::EventServiceStore::getInstance() 252 .eventServiceConfig.enabled = serviceEnabled; 253 persistent_data::EventServiceStore::getInstance() 254 .eventServiceConfig.retryAttempts = retryAttempts; 255 persistent_data::EventServiceStore::getInstance() 256 .eventServiceConfig.retryTimeoutInterval = retryTimeoutInterval; 257 258 persistent_data::getConfig().writeData(); 259 } 260 261 void setEventServiceConfig(const persistent_data::EventServiceConfig& cfg) 262 { 263 bool updateConfig = false; 264 bool updateRetryCfg = false; 265 266 if (serviceEnabled != cfg.enabled) 267 { 268 serviceEnabled = cfg.enabled; 269 if (serviceEnabled && noOfMetricReportSubscribers != 0U) 270 { 271 registerMetricReportSignal(); 272 } 273 else 274 { 275 unregisterMetricReportSignal(); 276 } 277 updateConfig = true; 278 } 279 280 if (retryAttempts != cfg.retryAttempts) 281 { 282 retryAttempts = cfg.retryAttempts; 283 updateConfig = true; 284 updateRetryCfg = true; 285 } 286 287 if (retryTimeoutInterval != cfg.retryTimeoutInterval) 288 { 289 retryTimeoutInterval = cfg.retryTimeoutInterval; 290 updateConfig = true; 291 updateRetryCfg = true; 292 } 293 294 if (updateConfig) 295 { 296 updateSubscriptionData(); 297 } 298 299 if (updateRetryCfg) 300 { 301 // Update the changed retry config to all subscriptions 302 for (const auto& it : 303 EventServiceManager::getInstance().subscriptionsMap) 304 { 305 Subscription& entry = *it.second; 306 entry.updateRetryConfig(retryAttempts, retryTimeoutInterval); 307 } 308 } 309 } 310 311 void updateNoOfSubscribersCount() 312 { 313 size_t eventLogSubCount = 0; 314 size_t metricReportSubCount = 0; 315 for (const auto& it : subscriptionsMap) 316 { 317 std::shared_ptr<Subscription> entry = it.second; 318 if (entry->userSub->eventFormatType == eventFormatType) 319 { 320 eventLogSubCount++; 321 } 322 else if (entry->userSub->eventFormatType == metricReportFormatType) 323 { 324 metricReportSubCount++; 325 } 326 } 327 328 noOfEventLogSubscribers = eventLogSubCount; 329 if (noOfMetricReportSubscribers != metricReportSubCount) 330 { 331 noOfMetricReportSubscribers = metricReportSubCount; 332 if (noOfMetricReportSubscribers != 0U) 333 { 334 registerMetricReportSignal(); 335 } 336 else 337 { 338 unregisterMetricReportSignal(); 339 } 340 } 341 } 342 343 std::shared_ptr<Subscription> getSubscription(const std::string& id) 344 { 345 auto obj = subscriptionsMap.find(id); 346 if (obj == subscriptionsMap.end()) 347 { 348 BMCWEB_LOG_ERROR("No subscription exist with ID:{}", id); 349 return nullptr; 350 } 351 std::shared_ptr<Subscription> subValue = obj->second; 352 return subValue; 353 } 354 355 std::string 356 addSubscriptionInternal(const std::shared_ptr<Subscription>& subValue) 357 { 358 std::uniform_int_distribution<uint32_t> dist(0); 359 bmcweb::OpenSSLGenerator gen; 360 361 std::string id; 362 363 int retry = 3; 364 while (retry != 0) 365 { 366 id = std::to_string(dist(gen)); 367 if (gen.error()) 368 { 369 retry = 0; 370 break; 371 } 372 auto inserted = subscriptionsMap.insert(std::pair(id, subValue)); 373 if (inserted.second) 374 { 375 break; 376 } 377 --retry; 378 } 379 380 if (retry <= 0) 381 { 382 BMCWEB_LOG_ERROR("Failed to generate random number"); 383 return ""; 384 } 385 386 // Set Subscription ID for back trace 387 subValue->userSub->id = id; 388 389 persistent_data::EventServiceStore::getInstance() 390 .subscriptionsConfigMap.emplace(id, subValue->userSub); 391 392 updateNoOfSubscribersCount(); 393 394 if constexpr (!BMCWEB_REDFISH_DBUS_LOG) 395 { 396 if (redfishLogFilePosition != 0) 397 { 398 cacheRedfishLogFile(); 399 } 400 } 401 // Update retry configuration. 402 subValue->updateRetryConfig(retryAttempts, retryTimeoutInterval); 403 404 return id; 405 } 406 407 std::string 408 addSSESubscription(const std::shared_ptr<Subscription>& subValue, 409 std::string_view lastEventId) 410 { 411 std::string id = addSubscriptionInternal(subValue); 412 413 if (!lastEventId.empty()) 414 { 415 BMCWEB_LOG_INFO("Attempting to find message for last id {}", 416 lastEventId); 417 boost::circular_buffer<Event>::iterator lastEvent = 418 std::find_if(messages.begin(), messages.end(), 419 [&lastEventId](const Event& event) { 420 return event.id == lastEventId; 421 }); 422 // Can't find a matching ID 423 if (lastEvent == messages.end()) 424 { 425 nlohmann::json msg = messages::eventBufferExceeded(); 426 // If the buffer overloaded, send all messages. 427 subValue->sendEventToSubscriber(msg); 428 lastEvent = messages.begin(); 429 } 430 else 431 { 432 // Skip the last event the user already has 433 lastEvent++; 434 } 435 436 for (boost::circular_buffer<Event>::const_iterator event = 437 lastEvent; 438 lastEvent != messages.end(); lastEvent++) 439 { 440 subValue->sendEventToSubscriber(event->message); 441 } 442 } 443 return id; 444 } 445 446 std::string 447 addPushSubscription(const std::shared_ptr<Subscription>& subValue) 448 { 449 std::string id = addSubscriptionInternal(subValue); 450 subValue->deleter = [id]() { 451 EventServiceManager::getInstance().deleteSubscription(id); 452 }; 453 updateSubscriptionData(); 454 return id; 455 } 456 457 bool isSubscriptionExist(const std::string& id) 458 { 459 auto obj = subscriptionsMap.find(id); 460 return obj != subscriptionsMap.end(); 461 } 462 463 bool deleteSubscription(const std::string& id) 464 { 465 auto obj = subscriptionsMap.find(id); 466 if (obj == subscriptionsMap.end()) 467 { 468 BMCWEB_LOG_WARNING("Could not find subscription with id {}", id); 469 return false; 470 } 471 subscriptionsMap.erase(obj); 472 auto& event = persistent_data::EventServiceStore::getInstance(); 473 auto persistentObj = event.subscriptionsConfigMap.find(id); 474 if (persistentObj == event.subscriptionsConfigMap.end()) 475 { 476 BMCWEB_LOG_ERROR("Subscription wasn't in persistent data"); 477 return true; 478 } 479 persistent_data::EventServiceStore::getInstance() 480 .subscriptionsConfigMap.erase(persistentObj); 481 updateNoOfSubscribersCount(); 482 updateSubscriptionData(); 483 484 return true; 485 } 486 487 void deleteSseSubscription(const crow::sse_socket::Connection& thisConn) 488 { 489 for (auto it = subscriptionsMap.begin(); it != subscriptionsMap.end();) 490 { 491 std::shared_ptr<Subscription> entry = it->second; 492 bool entryIsThisConn = entry->matchSseId(thisConn); 493 if (entryIsThisConn) 494 { 495 persistent_data::EventServiceStore::getInstance() 496 .subscriptionsConfigMap.erase(entry->userSub->id); 497 it = subscriptionsMap.erase(it); 498 return; 499 } 500 it++; 501 } 502 } 503 504 size_t getNumberOfSubscriptions() const 505 { 506 return subscriptionsMap.size(); 507 } 508 509 size_t getNumberOfSSESubscriptions() const 510 { 511 auto size = std::ranges::count_if( 512 subscriptionsMap, 513 [](const std::pair<std::string, std::shared_ptr<Subscription>>& 514 entry) { 515 return (entry.second->userSub->subscriptionType == 516 subscriptionTypeSSE); 517 }); 518 return static_cast<size_t>(size); 519 } 520 521 std::vector<std::string> getAllIDs() 522 { 523 std::vector<std::string> idList; 524 for (const auto& it : subscriptionsMap) 525 { 526 idList.emplace_back(it.first); 527 } 528 return idList; 529 } 530 531 bool sendTestEventLog() 532 { 533 for (const auto& it : subscriptionsMap) 534 { 535 std::shared_ptr<Subscription> entry = it.second; 536 if (!entry->sendTestEventLog()) 537 { 538 return false; 539 } 540 } 541 return true; 542 } 543 544 void sendEvent(nlohmann::json::object_t eventMessage, 545 std::string_view origin, std::string_view resourceType) 546 { 547 eventMessage["EventId"] = eventId; 548 549 eventMessage["EventTimestamp"] = 550 redfish::time_utils::getDateTimeOffsetNow().first; 551 eventMessage["OriginOfCondition"] = origin; 552 553 // MemberId is 0 : since we are sending one event record. 554 eventMessage["MemberId"] = "0"; 555 556 messages.push_back(Event(std::to_string(eventId), eventMessage)); 557 558 for (auto& it : subscriptionsMap) 559 { 560 std::shared_ptr<Subscription>& entry = it.second; 561 if (!eventMatchesFilter(*entry->userSub, eventMessage, 562 resourceType)) 563 { 564 BMCWEB_LOG_DEBUG("Filter didn't match"); 565 continue; 566 } 567 568 nlohmann::json::array_t eventRecord; 569 eventRecord.emplace_back(eventMessage); 570 571 nlohmann::json msgJson; 572 573 msgJson["@odata.type"] = "#Event.v1_4_0.Event"; 574 msgJson["Name"] = "Event Log"; 575 msgJson["Id"] = eventId; 576 msgJson["Events"] = std::move(eventRecord); 577 578 std::string strMsg = msgJson.dump( 579 2, ' ', true, nlohmann::json::error_handler_t::replace); 580 entry->sendEventToSubscriber(std::move(strMsg)); 581 eventId++; // increment the eventId 582 } 583 } 584 585 void resetRedfishFilePosition() 586 { 587 // Control would be here when Redfish file is created. 588 // Reset File Position as new file is created 589 redfishLogFilePosition = 0; 590 } 591 592 void cacheRedfishLogFile() 593 { 594 // Open the redfish file and read till the last record. 595 596 std::ifstream logStream(redfishEventLogFile); 597 if (!logStream.good()) 598 { 599 BMCWEB_LOG_ERROR(" Redfish log file open failed "); 600 return; 601 } 602 std::string logEntry; 603 while (std::getline(logStream, logEntry)) 604 { 605 redfishLogFilePosition = logStream.tellg(); 606 } 607 } 608 609 void readEventLogsFromFile() 610 { 611 std::ifstream logStream(redfishEventLogFile); 612 if (!logStream.good()) 613 { 614 BMCWEB_LOG_ERROR(" Redfish log file open failed"); 615 return; 616 } 617 618 std::vector<EventLogObjectsType> eventRecords; 619 620 std::string logEntry; 621 622 BMCWEB_LOG_DEBUG("Redfish log file: seek to {}", 623 static_cast<int>(redfishLogFilePosition)); 624 625 // Get the read pointer to the next log to be read. 626 logStream.seekg(redfishLogFilePosition); 627 628 while (std::getline(logStream, logEntry)) 629 { 630 BMCWEB_LOG_DEBUG("Redfish log file: found new event log entry"); 631 // Update Pointer position 632 redfishLogFilePosition = logStream.tellg(); 633 634 std::string idStr; 635 if (!event_log::getUniqueEntryID(logEntry, idStr)) 636 { 637 BMCWEB_LOG_DEBUG( 638 "Redfish log file: could not get unique entry id for {}", 639 logEntry); 640 continue; 641 } 642 643 if (!serviceEnabled || noOfEventLogSubscribers == 0) 644 { 645 // If Service is not enabled, no need to compute 646 // the remaining items below. 647 // But, Loop must continue to keep track of Timestamp 648 BMCWEB_LOG_DEBUG( 649 "Redfish log file: no subscribers / event service not enabled"); 650 continue; 651 } 652 653 std::string timestamp; 654 std::string messageID; 655 std::vector<std::string> messageArgs; 656 if (event_log::getEventLogParams(logEntry, timestamp, messageID, 657 messageArgs) != 0) 658 { 659 BMCWEB_LOG_DEBUG("Read eventLog entry params failed for {}", 660 logEntry); 661 continue; 662 } 663 664 eventRecords.emplace_back(idStr, timestamp, messageID, messageArgs); 665 } 666 667 if (!serviceEnabled || noOfEventLogSubscribers == 0) 668 { 669 BMCWEB_LOG_DEBUG("EventService disabled or no Subscriptions."); 670 return; 671 } 672 673 if (eventRecords.empty()) 674 { 675 // No Records to send 676 BMCWEB_LOG_DEBUG("No log entries available to be transferred."); 677 return; 678 } 679 680 for (const auto& it : subscriptionsMap) 681 { 682 std::shared_ptr<Subscription> entry = it.second; 683 if (entry->userSub->eventFormatType == "Event") 684 { 685 entry->filterAndSendEventLogs(eventRecords); 686 } 687 } 688 } 689 690 static void getReadingsForReport(sdbusplus::message_t& msg) 691 { 692 if (msg.is_method_error()) 693 { 694 BMCWEB_LOG_ERROR("TelemetryMonitor Signal error"); 695 return; 696 } 697 698 sdbusplus::message::object_path path(msg.get_path()); 699 std::string id = path.filename(); 700 if (id.empty()) 701 { 702 BMCWEB_LOG_ERROR("Failed to get Id from path"); 703 return; 704 } 705 706 std::string interface; 707 dbus::utility::DBusPropertiesMap props; 708 std::vector<std::string> invalidProps; 709 msg.read(interface, props, invalidProps); 710 711 auto found = std::ranges::find_if(props, [](const auto& x) { 712 return x.first == "Readings"; 713 }); 714 if (found == props.end()) 715 { 716 BMCWEB_LOG_INFO("Failed to get Readings from Report properties"); 717 return; 718 } 719 720 const telemetry::TimestampReadings* readings = 721 std::get_if<telemetry::TimestampReadings>(&found->second); 722 if (readings == nullptr) 723 { 724 BMCWEB_LOG_INFO("Failed to get Readings from Report properties"); 725 return; 726 } 727 728 for (const auto& it : 729 EventServiceManager::getInstance().subscriptionsMap) 730 { 731 Subscription& entry = *it.second; 732 if (entry.userSub->eventFormatType == metricReportFormatType) 733 { 734 entry.filterAndSendReports(id, *readings); 735 } 736 } 737 } 738 739 void unregisterMetricReportSignal() 740 { 741 if (matchTelemetryMonitor) 742 { 743 BMCWEB_LOG_DEBUG("Metrics report signal - Unregister"); 744 matchTelemetryMonitor.reset(); 745 matchTelemetryMonitor = nullptr; 746 } 747 } 748 749 void registerMetricReportSignal() 750 { 751 if (!serviceEnabled || matchTelemetryMonitor) 752 { 753 BMCWEB_LOG_DEBUG("Not registering metric report signal."); 754 return; 755 } 756 757 BMCWEB_LOG_DEBUG("Metrics report signal - Register"); 758 std::string matchStr = "type='signal',member='PropertiesChanged'," 759 "interface='org.freedesktop.DBus.Properties'," 760 "arg0=xyz.openbmc_project.Telemetry.Report"; 761 762 matchTelemetryMonitor = std::make_shared<sdbusplus::bus::match_t>( 763 *crow::connections::systemBus, matchStr, getReadingsForReport); 764 } 765 }; 766 767 } // namespace redfish 768