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