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