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