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