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