1 // SPDX-License-Identifier: Apache-2.0 2 // SPDX-FileCopyrightText: Copyright 2019 IBM Corporation 3 4 #include "extensions/openpower-pels/manager.hpp" 5 #include "log_manager.hpp" 6 #include "mocks.hpp" 7 #include "pel_utils.hpp" 8 9 #include <sdbusplus/test/sdbus_mock.hpp> 10 #include <xyz/openbmc_project/Common/error.hpp> 11 12 #include <fstream> 13 #include <regex> 14 15 #include <gtest/gtest.h> 16 17 using namespace openpower::pels; 18 namespace fs = std::filesystem; 19 20 using ::testing::NiceMock; 21 using ::testing::Return; 22 using json = nlohmann::json; 23 24 using Level = phosphor::logging::Entry::Level; 25 26 class TestLogger 27 { 28 public: 29 void log(const std::string& name, Level level, 30 const EventLogger::ADMap& additionalData) 31 { 32 errName = name; 33 errLevel = level; 34 ad = additionalData; 35 } 36 37 std::string errName; 38 Level errLevel; 39 EventLogger::ADMap ad; 40 }; 41 42 class ManagerTest : public CleanPELFiles 43 { 44 public: 45 ManagerTest() : 46 bus(sdbusplus::get_mocked_new(&sdbusInterface)), 47 logManager(bus, "logging_path") 48 { 49 sd_event_default(&sdEvent); 50 } 51 52 fs::path makeTempDir() 53 { 54 char path[] = "/tmp/tempnameXXXXXX"; 55 std::filesystem::path dir = mkdtemp(path); 56 dirsToRemove.push_back(dir); 57 return dir; 58 } 59 60 ~ManagerTest() 61 { 62 for (const auto& d : dirsToRemove) 63 { 64 std::filesystem::remove_all(d); 65 } 66 sd_event_unref(sdEvent); 67 } 68 69 NiceMock<sdbusplus::SdBusMock> sdbusInterface; 70 sdbusplus::bus_t bus; 71 phosphor::logging::internal::Manager logManager; 72 sd_event* sdEvent; 73 TestLogger logger; 74 std::vector<std::filesystem::path> dirsToRemove; 75 }; 76 77 std::optional<fs::path> findAnyPELInRepo() 78 { 79 // PELs are named <timestamp>_<ID> 80 std::regex expr{"\\d+_\\d+"}; 81 82 for (auto& f : fs::directory_iterator(getPELRepoPath() / "logs")) 83 { 84 if (std::regex_search(f.path().string(), expr)) 85 { 86 return f.path(); 87 } 88 } 89 return std::nullopt; 90 } 91 92 size_t countPELsInRepo() 93 { 94 size_t count = 0; 95 std::regex expr{"\\d+_\\d+"}; 96 97 for (auto& f : fs::directory_iterator(getPELRepoPath() / "logs")) 98 { 99 if (std::regex_search(f.path().string(), expr)) 100 { 101 count++; 102 } 103 } 104 return count; 105 } 106 107 void deletePELFile(uint32_t id) 108 { 109 char search[20]; 110 111 sprintf(search, "\\d+_%.8X", id); 112 std::regex expr{search}; 113 114 for (auto& f : fs::directory_iterator(getPELRepoPath() / "logs")) 115 { 116 if (std::regex_search(f.path().string(), expr)) 117 { 118 fs::remove(f.path()); 119 break; 120 } 121 } 122 } 123 124 // Test that using the RAWPEL=<file> with the Manager::create() call gets 125 // a PEL saved in the repository. 126 TEST_F(ManagerTest, TestCreateWithPEL) 127 { 128 std::unique_ptr<DataInterfaceBase> dataIface = 129 std::make_unique<MockDataInterface>(); 130 131 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>(); 132 133 Manager manager{logManager, std::move(dataIface), 134 std::bind_front(&TestLogger::log, &logger), 135 std::move(journal)}; 136 137 // Create a PEL, write it to a file, and pass that filename into 138 // the create function. 139 auto data = pelDataFactory(TestPELType::pelSimple); 140 141 fs::path pelFilename = makeTempDir() / "rawpel"; 142 std::ofstream pelFile{pelFilename}; 143 pelFile.write(reinterpret_cast<const char*>(data.data()), data.size()); 144 pelFile.close(); 145 146 std::map<std::string, std::string> additionalData{ 147 {"RAWPEL", pelFilename.string()}}; 148 std::vector<std::string> associations; 149 150 manager.create("error message", 42, 0, Level::Error, additionalData, 151 associations); 152 153 // Find the file in the PEL repository directory 154 auto pelPathInRepo = findAnyPELInRepo(); 155 156 EXPECT_TRUE(pelPathInRepo); 157 158 // Now remove it based on its OpenBMC event log ID 159 manager.erase(42); 160 161 pelPathInRepo = findAnyPELInRepo(); 162 163 EXPECT_FALSE(pelPathInRepo); 164 } 165 166 TEST_F(ManagerTest, TestCreateWithInvalidPEL) 167 { 168 std::unique_ptr<DataInterfaceBase> dataIface = 169 std::make_unique<MockDataInterface>(); 170 171 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>(); 172 173 Manager manager{logManager, std::move(dataIface), 174 std::bind_front(&TestLogger::log, &logger), 175 std::move(journal)}; 176 177 // Create a PEL, write it to a file, and pass that filename into 178 // the create function. 179 auto data = pelDataFactory(TestPELType::pelSimple); 180 181 // Truncate it to make it invalid. 182 data.resize(200); 183 184 fs::path pelFilename = makeTempDir() / "rawpel"; 185 std::ofstream pelFile{pelFilename}; 186 pelFile.write(reinterpret_cast<const char*>(data.data()), data.size()); 187 pelFile.close(); 188 189 std::map<std::string, std::string> additionalData{ 190 {"RAWPEL", pelFilename.string()}}; 191 std::vector<std::string> associations; 192 193 manager.create("error message", 42, 0, Level::Error, additionalData, 194 associations); 195 196 // Run the event loop to log the bad PEL event 197 sdeventplus::Event e{sdEvent}; 198 e.run(std::chrono::milliseconds(1)); 199 200 PEL invalidPEL{data}; 201 EXPECT_EQ(logger.errName, "org.open_power.Logging.Error.BadHostPEL"); 202 EXPECT_EQ(logger.errLevel, Level::Error); 203 EXPECT_EQ(std::stoi(logger.ad["PLID"], nullptr, 16), invalidPEL.plid()); 204 EXPECT_EQ(logger.ad["OBMC_LOG_ID"], "42"); 205 EXPECT_EQ(logger.ad["SRC"], (*invalidPEL.primarySRC())->asciiString()); 206 EXPECT_EQ(logger.ad["PEL_SIZE"], std::to_string(data.size())); 207 208 // Check that the bad PEL data was saved to a file. 209 auto badPELData = readPELFile(getPELRepoPath() / "badPEL"); 210 EXPECT_EQ(*badPELData, data); 211 } 212 213 // Test that the message registry can be used to build a PEL. 214 TEST_F(ManagerTest, TestCreateWithMessageRegistry) 215 { 216 const auto registry = R"( 217 { 218 "PELs": 219 [ 220 { 221 "Name": "xyz.openbmc_project.Error.Test", 222 "Subsystem": "power_supply", 223 "ActionFlags": ["service_action", "report"], 224 "SRC": 225 { 226 "ReasonCode": "0x2030" 227 }, 228 "Callouts": [ 229 { 230 "CalloutList": [ 231 {"Priority": "high", "Procedure": "BMC0001"}, 232 {"Priority": "medium", "SymbolicFRU": "service_docs"} 233 ] 234 } 235 ], 236 "Documentation": 237 { 238 "Description": "A PGOOD Fault", 239 "Message": "PS had a PGOOD Fault" 240 } 241 }, 242 { 243 "Name": "xyz.openbmc_project.Logging.Error.Default", 244 "Subsystem": "bmc_firmware", 245 "SRC": 246 { 247 "ReasonCode": "0x2031" 248 }, 249 "Documentation": 250 { 251 "Description": "The entry used when no match found", 252 "Message": "This is a generic SRC" 253 } 254 } 255 ] 256 } 257 )"; 258 259 auto path = getPELReadOnlyDataPath(); 260 fs::create_directories(path); 261 path /= "message_registry.json"; 262 263 std::ofstream registryFile{path}; 264 registryFile << registry; 265 registryFile.close(); 266 267 std::unique_ptr<DataInterfaceBase> dataIface = 268 std::make_unique<MockDataInterface>(); 269 270 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>(); 271 272 Manager manager{logManager, std::move(dataIface), 273 std::bind_front(&TestLogger::log, &logger), 274 std::move(journal)}; 275 276 std::map<std::string, std::string> additionalData{{"FOO", "BAR"}}; 277 std::vector<std::string> associations; 278 279 // Create the event log to create the PEL from. 280 manager.create("xyz.openbmc_project.Error.Test", 33, 0, Level::Error, 281 additionalData, associations); 282 283 // Ensure a PEL was created in the repository 284 auto pelFile = findAnyPELInRepo(); 285 ASSERT_TRUE(pelFile); 286 287 auto data = readPELFile(*pelFile); 288 PEL pel(*data); 289 290 // Spot check it. Other testcases cover the details. 291 EXPECT_TRUE(pel.valid()); 292 EXPECT_EQ(pel.obmcLogID(), 33); 293 EXPECT_EQ(pel.primarySRC().value()->asciiString(), 294 "BD612030 "); 295 // Check if the eventId creation is good 296 EXPECT_EQ(manager.getEventId(pel), 297 "BD612030 00000055 00000010 00000000 00000000 00000000 00000000 " 298 "00000000 00000000"); 299 // Check if resolution property creation is good 300 EXPECT_EQ(manager.getResolution(pel), 301 "1. Priority: High, Procedure: BMC0001\n2. Priority: Medium, PN: " 302 "SVCDOCS\n"); 303 304 // Remove it 305 manager.erase(33); 306 pelFile = findAnyPELInRepo(); 307 EXPECT_FALSE(pelFile); 308 309 // Create an event log that can't be found in the registry. 310 // In this case, xyz.openbmc_project.Logging.Error.Default will 311 // be used as the key instead to find a registry match. 312 manager.create("xyz.openbmc_project.Error.Foo", 42, 0, Level::Error, 313 additionalData, associations); 314 315 // Ensure a PEL was still created in the repository 316 pelFile = findAnyPELInRepo(); 317 ASSERT_TRUE(pelFile); 318 319 data = readPELFile(*pelFile); 320 PEL newPEL(*data); 321 322 EXPECT_TRUE(newPEL.valid()); 323 EXPECT_EQ(newPEL.obmcLogID(), 42); 324 EXPECT_EQ(newPEL.primarySRC().value()->asciiString(), 325 "BD8D2031 "); 326 327 // Check for both the original AdditionalData item as well as 328 // the ERROR_NAME item that should contain the error message 329 // property that wasn't found. 330 std::string errorName; 331 std::string adItem; 332 333 for (const auto& section : newPEL.optionalSections()) 334 { 335 if (SectionID::userData == static_cast<SectionID>(section->header().id)) 336 { 337 if (UserDataFormat::json == 338 static_cast<UserDataFormat>(section->header().subType)) 339 { 340 auto ud = static_cast<UserData*>(section.get()); 341 342 // Check that there was a UserData section added that 343 // contains debug details about the device. 344 const auto& d = ud->data(); 345 std::string jsonString{d.begin(), d.end()}; 346 auto json = nlohmann::json::parse(jsonString); 347 348 if (json.contains("ERROR_NAME")) 349 { 350 errorName = json["ERROR_NAME"].get<std::string>(); 351 } 352 353 if (json.contains("FOO")) 354 { 355 adItem = json["FOO"].get<std::string>(); 356 } 357 } 358 } 359 if (!errorName.empty()) 360 { 361 break; 362 } 363 } 364 365 EXPECT_EQ(errorName, "xyz.openbmc_project.Error.Foo"); 366 EXPECT_EQ(adItem, "BAR"); 367 } 368 369 TEST_F(ManagerTest, TestDBusMethods) 370 { 371 std::unique_ptr<DataInterfaceBase> dataIface = 372 std::make_unique<MockDataInterface>(); 373 374 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>(); 375 376 Manager manager{logManager, std::move(dataIface), 377 std::bind_front(&TestLogger::log, &logger), 378 std::move(journal)}; 379 380 // Create a PEL, write it to a file, and pass that filename into 381 // the create function so there's one in the repo. 382 auto data = pelDataFactory(TestPELType::pelSimple); 383 384 fs::path pelFilename = makeTempDir() / "rawpel"; 385 std::ofstream pelFile{pelFilename}; 386 pelFile.write(reinterpret_cast<const char*>(data.data()), data.size()); 387 pelFile.close(); 388 389 std::map<std::string, std::string> additionalData{ 390 {"RAWPEL", pelFilename.string()}}; 391 std::vector<std::string> associations; 392 393 manager.create("error message", 42, 0, Level::Error, additionalData, 394 associations); 395 396 // getPELFromOBMCID 397 auto newData = manager.getPELFromOBMCID(42); 398 EXPECT_EQ(newData.size(), data.size()); 399 400 // Read the PEL to get the ID for later 401 PEL pel{newData}; 402 auto id = pel.id(); 403 404 EXPECT_THROW( 405 manager.getPELFromOBMCID(id + 1), 406 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); 407 408 // getPEL 409 auto unixfd = manager.getPEL(id); 410 411 // Get the size 412 struct stat s; 413 int r = fstat(unixfd, &s); 414 ASSERT_EQ(r, 0); 415 auto size = s.st_size; 416 417 // Open the FD and check the contents 418 FILE* fp = fdopen(unixfd, "r"); 419 ASSERT_NE(fp, nullptr); 420 421 std::vector<uint8_t> fdData; 422 fdData.resize(size); 423 r = fread(fdData.data(), 1, size, fp); 424 EXPECT_EQ(r, size); 425 426 EXPECT_EQ(newData, fdData); 427 428 fclose(fp); 429 430 // Run the event loop to close the FD 431 sdeventplus::Event e{sdEvent}; 432 e.run(std::chrono::milliseconds(1)); 433 434 EXPECT_THROW( 435 manager.getPEL(id + 1), 436 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); 437 438 // hostAck 439 manager.hostAck(id); 440 441 EXPECT_THROW( 442 manager.hostAck(id + 1), 443 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); 444 445 // hostReject 446 manager.hostReject(id, Manager::RejectionReason::BadPEL); 447 448 // Run the event loop to log the bad PEL event 449 e.run(std::chrono::milliseconds(1)); 450 451 EXPECT_EQ(logger.errName, "org.open_power.Logging.Error.SentBadPELToHost"); 452 EXPECT_EQ(id, std::stoi(logger.ad["BAD_ID"], nullptr, 16)); 453 454 manager.hostReject(id, Manager::RejectionReason::HostFull); 455 456 EXPECT_THROW( 457 manager.hostReject(id + 1, Manager::RejectionReason::BadPEL), 458 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); 459 460 // GetPELIdFromBMCLogId 461 EXPECT_EQ(pel.id(), manager.getPELIdFromBMCLogId(pel.obmcLogID())); 462 EXPECT_THROW( 463 manager.getPELIdFromBMCLogId(pel.obmcLogID() + 1), 464 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); 465 466 // GetBMCLogIdFromPELId 467 EXPECT_EQ(pel.obmcLogID(), manager.getBMCLogIdFromPELId(pel.id())); 468 EXPECT_THROW( 469 manager.getBMCLogIdFromPELId(pel.id() + 1), 470 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); 471 } 472 473 // An ESEL from the wild 474 const std::string esel{ 475 "00 00 df 00 00 00 00 20 00 04 12 01 6f aa 00 00 " 476 "50 48 00 30 01 00 33 00 20 23 05 11 10 20 20 00 00 00 00 07 5c d5 50 db " 477 "42 00 00 10 00 00 00 00 00 00 00 00 00 00 00 00 90 00 00 4e 90 00 00 4e " 478 "55 48 00 18 01 00 09 00 8a 03 40 00 00 00 00 00 ff ff 00 00 00 00 00 00 " 479 "50 53 00 50 01 01 00 00 02 00 00 09 33 2d 00 48 00 00 00 e0 00 00 10 00 " 480 "00 00 00 00 00 20 00 00 00 0c 00 02 00 00 00 fa 00 00 0c e4 00 00 00 12 " 481 "42 43 38 41 33 33 32 44 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 " 482 "20 20 20 20 20 20 20 20 55 44 00 1c 01 06 01 00 02 54 41 4b 00 00 00 06 " 483 "00 00 00 55 00 01 f9 20 00 00 00 00 55 44 00 24 01 06 01 00 01 54 41 4b " 484 "00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 00 23 01 00 02 00 05 00 00 " 485 "55 44 00 0c 01 0b 01 00 0f 01 00 00 55 44 00 10 01 04 01 00 0f 9f de 6a " 486 "00 01 00 00 55 44 00 7c 00 0c 01 00 00 13 0c 02 00 fa 0c e4 16 00 01 2c " 487 "0c 1c 16 00 00 fa 0a f0 14 00 00 fa 0b b8 14 00 00 be 09 60 12 00 01 2c " 488 "0d 7a 12 00 00 fa 0c 4e 10 00 00 fa 0c e4 10 00 00 be 0a 8c 16 00 01 2c " 489 "0c 1c 16 00 01 09 09 f6 16 00 00 fa 09 f6 14 00 00 fa 0b b8 14 00 00 fa " 490 "0a f0 14 00 00 be 08 ca 12 00 01 2c 0c e4 12 00 00 fa 0b 54 10 00 00 fa " 491 "0c 2d 10 00 00 be 08 ca 55 44 00 58 01 03 01 00 00 00 00 00 00 05 31 64 " 492 "00 00 00 00 00 05 0d d4 00 00 00 00 40 5f 06 e0 00 00 00 00 40 5d d2 00 " 493 "00 00 00 00 40 57 d3 d0 00 00 00 00 40 58 f6 a0 00 00 00 00 40 54 c9 34 " 494 "00 00 00 00 40 55 9a 10 00 00 00 00 40 4c 0a 80 00 00 00 00 00 00 27 14 " 495 "55 44 01 84 01 01 01 00 48 6f 73 74 62 6f 6f 74 20 42 75 69 6c 64 20 49 " 496 "44 3a 20 68 6f 73 74 62 6f 6f 74 2d 66 65 63 37 34 64 66 2d 70 30 61 38 " 497 "37 64 63 34 2f 68 62 69 63 6f 72 65 2e 62 69 6e 00 49 42 4d 2d 77 69 74 " 498 "68 65 72 73 70 6f 6f 6e 2d 4f 50 39 2d 76 32 2e 34 2d 39 2e 32 33 34 0a " 499 "09 6f 70 2d 62 75 69 6c 64 2d 38 32 66 34 63 66 30 0a 09 62 75 69 6c 64 " 500 "72 6f 6f 74 2d 32 30 31 39 2e 30 35 2e 32 2d 31 30 2d 67 38 39 35 39 31 " 501 "31 34 0a 09 73 6b 69 62 6f 6f 74 2d 76 36 2e 35 2d 31 38 2d 67 34 37 30 " 502 "66 66 62 35 66 32 39 64 37 0a 09 68 6f 73 74 62 6f 6f 74 2d 66 65 63 37 " 503 "34 64 66 2d 70 30 61 38 37 64 63 34 0a 09 6f 63 63 2d 65 34 35 39 37 61 " 504 "62 0a 09 6c 69 6e 75 78 2d 35 2e 32 2e 31 37 2d 6f 70 65 6e 70 6f 77 65 " 505 "72 31 2d 70 64 64 63 63 30 33 33 0a 09 70 65 74 69 74 62 6f 6f 74 2d 76 " 506 "31 2e 31 30 2e 34 0a 09 6d 61 63 68 69 6e 65 2d 78 6d 6c 2d 63 36 32 32 " 507 "63 62 35 2d 70 37 65 63 61 62 33 64 0a 09 68 6f 73 74 62 6f 6f 74 2d 62 " 508 "69 6e 61 72 69 65 73 2d 36 36 65 39 61 36 30 0a 09 63 61 70 70 2d 75 63 " 509 "6f 64 65 2d 70 39 2d 64 64 32 2d 76 34 0a 09 73 62 65 2d 36 30 33 33 30 " 510 "65 30 0a 09 68 63 6f 64 65 2d 68 77 30 39 32 31 31 39 61 2e 6f 70 6d 73 " 511 "74 0a 00 00 55 44 00 70 01 04 01 00 0f 9f de 6a 00 05 00 00 07 5f 1d f4 " 512 "30 32 43 59 34 37 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " 513 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 " 514 "0b ac 54 02 59 41 31 39 33 34 36 39 37 30 35 38 00 00 00 00 00 00 05 22 " 515 "a1 58 01 8a 00 58 40 20 17 18 4d 2c 00 00 00 fc 01 a1 00 00 55 44 00 14 " 516 "01 08 01 00 00 00 00 01 00 00 00 5a 00 00 00 05 55 44 03 fc 01 15 31 00 " 517 "01 28 00 42 46 41 50 49 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 f4 " 518 "00 00 00 00 00 00 03 f4 00 00 00 0b 00 00 00 00 00 00 00 3d 2c 9b c2 84 " 519 "00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 09 " 520 "00 00 00 00 00 11 bd 20 00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 " 521 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 00 00 00 00 00 00 01 2c " 522 "00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0c 1c 00 00 00 64 00 00 00 3d " 523 "2c 9b d1 11 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 " 524 "00 00 00 0a 00 00 00 00 00 13 b5 a0 00 00 00 00 00 01 f8 80 00 00 00 00 " 525 "00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 00 00 00 00 " 526 "00 00 00 be 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0a 8c 00 00 00 64 " 527 "00 00 00 3d 2c 9b df 98 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 " 528 "00 00 00 00 00 00 00 0b 00 00 00 00 00 15 ae 20 00 00 00 00 00 01 f8 80 " 529 "00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 " 530 "00 00 00 00 00 00 00 fa 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0c e4 " 531 "00 00 00 64 00 00 00 3d 2c 9b ea b7 00 00 01 e4 00 48 43 4f fb ed 70 b1 " 532 "00 00 02 01 00 00 00 00 00 00 00 0c 00 00 00 00 00 17 a6 a0 00 00 00 00 " 533 "00 01 f8 80 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 " 534 "00 00 00 12 00 00 00 00 00 00 00 fa 00 00 00 00 00 00 07 d0 00 00 00 00 " 535 "00 00 0c 4e 00 00 00 64 00 00 00 3d 2c 9b f6 27 00 00 01 e4 00 48 43 4f " 536 "fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 0d 00 00 00 00 00 19 9f 20 " 537 "00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 " 538 "00 00 00 00 00 00 00 12 00 00 00 00 00 00 01 2c 00 00 00 00 00 00 07 d0 " 539 "00 00 00 00 00 00 0d 7a 00 00 00 64 00 00 00 3d 2c 9c 05 75 00 00 01 e4 " 540 "00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 0e 00 00 00 00 " 541 "00 1b 97 a0 00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 00 00 00 00 " 542 "00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 00 00 00 00 be 00 00 00 00 " 543 "00 00 07 d0 00 00 00 00 00 00 09 60 00 00 00 64 00 00 00 3d 2c 9c 11 29 " 544 "00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 0f " 545 "00 00 00 00 00 1d 90 20 00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 " 546 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 00 00 00 00 fa " 547 "00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0b b8 00 00 00 64 00 00 00 3d " 548 "2c 9c 1c 45 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 " 549 "00 00 00 10 00 00 00 00 00 1f 88 a0 00 00 00 00 00 01 f8 80 00 00 00 00 " 550 "00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 00 00 00 00 " 551 "00 00 00 fa 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0a f0 00 00 00 64 " 552 "00 00 00 3d 2c 9c 2b 14 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 " 553 "00 00 00 00 00 00 00 11 00 00 00 00 00 21 81 20 00 00 00 00 00 01 f8 80 " 554 "00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 " 555 "00 00 00 00 00 00 01 2c 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0c 1c " 556 "00 00 00 64 00 00 00 3d 2d 6d 8f 9e 00 00 01 e4 00 00 43 4f 52 d7 9c 36 " 557 "00 00 04 73 00 00 00 1c 00 00 00 3d 2d 6d 99 ac 00 00 01 e4 00 10 43 4f " 558 "3f f2 02 3d 00 00 05 58 00 00 00 00 02 00 00 01 00 00 00 00 00 00 00 40 " 559 "00 00 00 2c 55 44 00 30 01 15 31 00 01 28 00 42 46 41 50 49 5f 44 42 47 " 560 "00 00 00 00 00 00 00 00 00 00 00 28 00 00 00 00 00 00 00 28 00 00 00 00 " 561 "00 00 00 00 55 44 01 74 01 15 31 00 01 28 00 42 46 41 50 49 5f 49 00 00 " 562 "00 00 00 00 00 00 00 00 00 00 01 6c 00 00 00 00 00 00 01 6c 00 00 00 0b " 563 "00 00 00 00 00 00 00 3c 0d 52 18 5e 00 00 01 e4 00 08 43 4f 46 79 94 13 " 564 "00 00 0a 5b 00 00 00 00 00 00 2c 00 00 00 00 24 00 00 00 3c 0d 6b 26 6c " 565 "00 00 01 e4 00 00 43 4f 4e 9b 18 74 00 00 01 03 00 00 00 1c 00 00 00 3c " 566 "12 b9 2d 13 00 00 01 e4 00 00 43 4f ea 31 ed d4 00 00 05 c4 00 00 00 1c " 567 "00 00 00 3c 13 02 73 53 00 00 01 e4 00 00 43 4f ea 31 ed d4 00 00 05 c4 " 568 "00 00 00 1c 00 00 00 3c 13 04 7c 94 00 00 01 e4 00 00 43 4f ea 31 ed d4 " 569 "00 00 05 c4 00 00 00 1c 00 00 00 3c 13 06 ad e1 00 00 01 e4 00 00 43 4f " 570 "ea 31 ed d4 00 00 05 c4 00 00 00 1c 00 00 00 3c 13 07 3f 77 00 00 01 e4 " 571 "00 00 43 4f 5e 4a 55 32 00 00 10 f2 00 00 00 1c 00 00 00 3c 13 07 4e e4 " 572 "00 00 01 e4 00 00 43 4f 5e 4a 55 32 00 00 0d 68 00 00 00 1c 00 00 00 3c " 573 "13 36 79 18 00 00 01 e4 00 00 43 4f ea 31 ed d4 00 00 05 c4 00 00 00 1c " 574 "00 00 00 3d 2c 9c 36 70 00 00 01 e4 00 00 43 4f 23 45 90 97 00 00 02 47 " 575 "00 00 00 1c 00 00 00 3d 2d 6d a3 ed 00 00 01 e4 00 08 43 4f 74 3a 5b 1a " 576 "00 00 04 cc 00 00 00 00 02 00 00 01 00 00 00 24 55 44 00 30 01 15 31 00 " 577 "01 28 00 42 53 43 41 4e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 28 " 578 "00 00 00 00 00 00 00 28 00 00 00 00 00 00 00 00"}; 579 580 TEST_F(ManagerTest, TestESELToRawData) 581 { 582 auto data = Manager::eselToRawData(esel); 583 584 EXPECT_EQ(data.size(), 2464); 585 586 PEL pel{data}; 587 EXPECT_TRUE(pel.valid()); 588 } 589 590 TEST_F(ManagerTest, TestCreateWithESEL) 591 { 592 std::unique_ptr<DataInterfaceBase> dataIface = 593 std::make_unique<MockDataInterface>(); 594 595 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>(); 596 597 Manager manager{logManager, std::move(dataIface), 598 std::bind_front(&TestLogger::log, &logger), 599 std::move(journal)}; 600 601 { 602 std::map<std::string, std::string> additionalData{{"ESEL", esel}}; 603 std::vector<std::string> associations; 604 605 manager.create("error message", 37, 0, Level::Error, additionalData, 606 associations); 607 608 auto data = manager.getPELFromOBMCID(37); 609 PEL pel{data}; 610 EXPECT_TRUE(pel.valid()); 611 } 612 613 // Now an invalid one 614 { 615 std::string adItem = esel; 616 617 // Crop it 618 adItem.resize(adItem.size() - 300); 619 620 std::map<std::string, std::string> additionalData{{"ESEL", adItem}}; 621 std::vector<std::string> associations; 622 623 manager.create("error message", 38, 0, Level::Error, additionalData, 624 associations); 625 626 EXPECT_THROW( 627 manager.getPELFromOBMCID(38), 628 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); 629 630 // Run the event loop to log the bad PEL event 631 sdeventplus::Event e{sdEvent}; 632 e.run(std::chrono::milliseconds(1)); 633 634 EXPECT_EQ(logger.errName, "org.open_power.Logging.Error.BadHostPEL"); 635 EXPECT_EQ(logger.errLevel, Level::Error); 636 } 637 } 638 639 // Test that PELs will be pruned when necessary 640 TEST_F(ManagerTest, TestPruning) 641 { 642 sdeventplus::Event e{sdEvent}; 643 644 std::unique_ptr<DataInterfaceBase> dataIface = 645 std::make_unique<MockDataInterface>(); 646 647 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>(); 648 649 Manager manager{logManager, std::move(dataIface), 650 std::bind_front(&TestLogger::log, &logger), 651 std::move(journal)}; 652 653 // Create 25 1000B (4096B on disk each, which is what is used for 654 // pruning) BMC non-informational PELs in the 100KB repository. After 655 // the 24th one, the repo will be 96% full and a prune should be 656 // triggered to remove all but 7 to get under 30% full. Then when the 657 // 25th is added there will be 8 left. 658 659 auto dir = makeTempDir(); 660 for (int i = 1; i <= 25; i++) 661 { 662 auto data = pelFactory(42, 'O', 0x40, 0x8800, 1000); 663 664 fs::path pelFilename = dir / "rawpel"; 665 std::ofstream pelFile{pelFilename}; 666 pelFile.write(reinterpret_cast<const char*>(data.data()), data.size()); 667 pelFile.close(); 668 669 std::map<std::string, std::string> additionalData{ 670 {"RAWPEL", pelFilename.string()}}; 671 std::vector<std::string> associations; 672 673 manager.create("error message", 42, 0, Level::Error, additionalData, 674 associations); 675 676 // Simulate the code getting back to the event loop 677 // after each create. 678 e.run(std::chrono::milliseconds(1)); 679 680 if (i < 24) 681 { 682 EXPECT_EQ(countPELsInRepo(), i); 683 } 684 else if (i == 24) 685 { 686 // Prune occured 687 EXPECT_EQ(countPELsInRepo(), 7); 688 } 689 else // i == 25 690 { 691 EXPECT_EQ(countPELsInRepo(), 8); 692 } 693 } 694 695 try 696 { 697 // Make sure the 8 newest ones are still found. 698 for (uint32_t i = 0; i < 8; i++) 699 { 700 manager.getPEL(0x50000012 + i); 701 } 702 } 703 catch ( 704 const sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument& 705 ex) 706 { 707 ADD_FAILURE() << "PELs should have all been found"; 708 } 709 } 710 711 // Test that manually deleting a PEL file will be recognized by the code. 712 TEST_F(ManagerTest, TestPELManualDelete) 713 { 714 sdeventplus::Event e{sdEvent}; 715 716 std::unique_ptr<DataInterfaceBase> dataIface = 717 std::make_unique<MockDataInterface>(); 718 719 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>(); 720 721 Manager manager{logManager, std::move(dataIface), 722 std::bind_front(&TestLogger::log, &logger), 723 std::move(journal)}; 724 725 auto data = pelDataFactory(TestPELType::pelSimple); 726 auto dir = makeTempDir(); 727 fs::path pelFilename = dir / "rawpel"; 728 729 std::map<std::string, std::string> additionalData{ 730 {"RAWPEL", pelFilename.string()}}; 731 std::vector<std::string> associations; 732 733 // Add 20 PELs, they will get incrementing IDs like 734 // 0x50000001, 0x50000002, etc. 735 for (int i = 1; i <= 20; i++) 736 { 737 std::ofstream pelFile{pelFilename}; 738 pelFile.write(reinterpret_cast<const char*>(data.data()), data.size()); 739 pelFile.close(); 740 741 manager.create("error message", 42, 0, Level::Error, additionalData, 742 associations); 743 744 // Sanity check this ID is really there so we can test 745 // it was deleted later. This will throw an exception if 746 // not present. 747 manager.getPEL(0x50000000 + i); 748 749 // Run an event loop pass where the internal FD is deleted 750 // after the getPEL function call. 751 e.run(std::chrono::milliseconds(1)); 752 } 753 754 EXPECT_EQ(countPELsInRepo(), 20); 755 756 deletePELFile(0x50000001); 757 758 // Run a single event loop pass so the inotify event can run 759 e.run(std::chrono::milliseconds(1)); 760 761 EXPECT_EQ(countPELsInRepo(), 19); 762 763 EXPECT_THROW( 764 manager.getPEL(0x50000001), 765 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); 766 767 // Delete a few more, they should all get handled in the same 768 // event loop pass 769 std::vector<uint32_t> toDelete{0x50000002, 0x50000003, 0x50000004, 770 0x50000005, 0x50000006}; 771 std::for_each(toDelete.begin(), toDelete.end(), 772 [](auto i) { deletePELFile(i); }); 773 774 e.run(std::chrono::milliseconds(1)); 775 776 EXPECT_EQ(countPELsInRepo(), 14); 777 778 std::for_each(toDelete.begin(), toDelete.end(), [&manager](const auto i) { 779 EXPECT_THROW( 780 manager.getPEL(i), 781 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); 782 }); 783 } 784 785 // Test that deleting all PELs at once is handled OK. 786 TEST_F(ManagerTest, TestPELManualDeleteAll) 787 { 788 sdeventplus::Event e{sdEvent}; 789 790 std::unique_ptr<DataInterfaceBase> dataIface = 791 std::make_unique<MockDataInterface>(); 792 793 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>(); 794 795 Manager manager{logManager, std::move(dataIface), 796 std::bind_front(&TestLogger::log, &logger), 797 std::move(journal)}; 798 799 auto data = pelDataFactory(TestPELType::pelSimple); 800 auto dir = makeTempDir(); 801 fs::path pelFilename = dir / "rawpel"; 802 803 std::map<std::string, std::string> additionalData{ 804 {"RAWPEL", pelFilename.string()}}; 805 std::vector<std::string> associations; 806 807 // Add 200 PELs, they will get incrementing IDs like 808 // 0x50000001, 0x50000002, etc. 809 for (int i = 1; i <= 200; i++) 810 { 811 std::ofstream pelFile{pelFilename}; 812 pelFile.write(reinterpret_cast<const char*>(data.data()), data.size()); 813 pelFile.close(); 814 815 manager.create("error message", 42, 0, Level::Error, additionalData, 816 associations); 817 818 // Sanity check this ID is really there so we can test 819 // it was deleted later. This will throw an exception if 820 // not present. 821 manager.getPEL(0x50000000 + i); 822 823 // Run an event loop pass where the internal FD is deleted 824 // after the getPEL function call. 825 e.run(std::chrono::milliseconds(1)); 826 } 827 828 // Delete them all at once 829 auto logPath = getPELRepoPath() / "logs"; 830 std::string cmd = "rm " + logPath.string() + "/*_*"; 831 832 { 833 auto rc = system(cmd.c_str()); 834 EXPECT_EQ(rc, 0); 835 } 836 837 EXPECT_EQ(countPELsInRepo(), 0); 838 839 // It will take 5 event loop passes to process them all 840 for (int i = 0; i < 5; i++) 841 { 842 e.run(std::chrono::milliseconds(1)); 843 } 844 845 for (int i = 1; i <= 200; i++) 846 { 847 EXPECT_THROW( 848 manager.getPEL(0x50000000 + i), 849 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); 850 } 851 } 852 853 // Test that fault LEDs are turned on when PELs are created 854 TEST_F(ManagerTest, TestServiceIndicators) 855 { 856 std::unique_ptr<DataInterfaceBase> dataIface = 857 std::make_unique<MockDataInterface>(); 858 859 MockDataInterface* mockIface = 860 reinterpret_cast<MockDataInterface*>(dataIface.get()); 861 862 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>(); 863 864 Manager manager{logManager, std::move(dataIface), 865 std::bind_front(&TestLogger::log, &logger), 866 std::move(journal)}; 867 868 // Add a PEL with a callout as if hostboot added it 869 { 870 EXPECT_CALL(*mockIface, getInventoryFromLocCode("U42", 0, true)) 871 .WillOnce( 872 Return(std::vector<std::string>{"/system/chassis/processor"})); 873 874 EXPECT_CALL(*mockIface, 875 setFunctional("/system/chassis/processor", false)) 876 .Times(1); 877 878 // This hostboot PEL has a single hardware callout in it. 879 auto data = pelFactory(1, 'B', 0x20, 0xA400, 500); 880 881 fs::path pelFilename = makeTempDir() / "rawpel"; 882 std::ofstream pelFile{pelFilename}; 883 pelFile.write(reinterpret_cast<const char*>(data.data()), data.size()); 884 pelFile.close(); 885 886 std::map<std::string, std::string> additionalData{ 887 {"RAWPEL", pelFilename.string()}}; 888 std::vector<std::string> associations; 889 890 manager.create("error message", 42, 0, Level::Error, additionalData, 891 associations); 892 } 893 894 // Add a BMC PEL with a callout that uses the message registry 895 { 896 std::vector<std::string> names{"systemA"}; 897 EXPECT_CALL(*mockIface, getSystemNames) 898 .Times(1) 899 .WillOnce(Return(names)); 900 901 EXPECT_CALL(*mockIface, expandLocationCode("P42-C23", 0)) 902 .WillOnce(Return("U42-P42-C23")); 903 904 // First call to this is when building the Callout section 905 EXPECT_CALL(*mockIface, getInventoryFromLocCode("P42-C23", 0, false)) 906 .WillOnce( 907 Return(std::vector<std::string>{"/system/chassis/processor"})); 908 909 // Second call to this is finding the associated LED group 910 EXPECT_CALL(*mockIface, getInventoryFromLocCode("U42-P42-C23", 0, true)) 911 .WillOnce( 912 Return(std::vector<std::string>{"/system/chassis/processor"})); 913 914 EXPECT_CALL(*mockIface, 915 setFunctional("/system/chassis/processor", false)) 916 .Times(1); 917 918 const auto registry = R"( 919 { 920 "PELs": 921 [ 922 { 923 "Name": "xyz.openbmc_project.Error.Test", 924 "Subsystem": "power_supply", 925 "ActionFlags": ["service_action", "report"], 926 "SRC": 927 { 928 "ReasonCode": "0x2030" 929 }, 930 "Callouts": [ 931 { 932 "CalloutList": [ 933 {"Priority": "high", "LocCode": "P42-C23"} 934 ] 935 } 936 ], 937 "Documentation": 938 { 939 "Description": "Test Error", 940 "Message": "Test Error" 941 } 942 } 943 ] 944 })"; 945 946 auto path = getPELReadOnlyDataPath(); 947 fs::create_directories(path); 948 path /= "message_registry.json"; 949 950 std::ofstream registryFile{path}; 951 registryFile << registry; 952 registryFile.close(); 953 954 std::map<std::string, std::string> additionalData; 955 std::vector<std::string> associations; 956 957 manager.create("xyz.openbmc_project.Error.Test", 42, 0, Level::Error, 958 additionalData, associations); 959 } 960 } 961 962 // Test for duplicate PELs moved to archive folder 963 TEST_F(ManagerTest, TestDuplicatePEL) 964 { 965 sdeventplus::Event e{sdEvent}; 966 size_t count = 0; 967 968 std::unique_ptr<DataInterfaceBase> dataIface = 969 std::make_unique<MockDataInterface>(); 970 971 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>(); 972 973 Manager manager{logManager, std::move(dataIface), 974 std::bind_front(&TestLogger::log, &logger), 975 std::move(journal)}; 976 977 for (int i = 0; i < 2; i++) 978 { 979 // This hostboot PEL has a single hardware callout in it. 980 auto data = pelFactory(1, 'B', 0x20, 0xA400, 500); 981 982 fs::path pelFilename = makeTempDir() / "rawpel"; 983 std::ofstream pelFile{pelFilename}; 984 pelFile.write(reinterpret_cast<const char*>(data.data()), data.size()); 985 pelFile.close(); 986 987 std::map<std::string, std::string> additionalData{ 988 {"RAWPEL", pelFilename.string()}}; 989 std::vector<std::string> associations; 990 991 manager.create("error message", 42, 0, Level::Error, additionalData, 992 associations); 993 994 e.run(std::chrono::milliseconds(1)); 995 } 996 997 for (auto& f : 998 fs::directory_iterator(getPELRepoPath() / "logs" / "archive")) 999 { 1000 if (fs::is_regular_file(f.path())) 1001 { 1002 count++; 1003 } 1004 } 1005 1006 // Get count of PELs in the repository & in archive directtory 1007 EXPECT_EQ(countPELsInRepo(), 1); 1008 EXPECT_EQ(count, 1); 1009 } 1010 1011 // Test termination bit set for pel with critical system termination 1012 // severity 1013 TEST_F(ManagerTest, TestTerminateBitWithPELSevCriticalSysTerminate) 1014 { 1015 const auto registry = R"( 1016 { 1017 "PELs": 1018 [ 1019 { 1020 "Name": "xyz.openbmc_project.Error.Test", 1021 "Subsystem": "power_supply", 1022 "Severity": "critical_system_term", 1023 "ActionFlags": ["service_action", "report"], 1024 "SRC": 1025 { 1026 "ReasonCode": "0x2030" 1027 }, 1028 "Documentation": 1029 { 1030 "Description": "A PGOOD Fault", 1031 "Message": "PS had a PGOOD Fault" 1032 } 1033 } 1034 ] 1035 } 1036 )"; 1037 1038 auto path = getPELReadOnlyDataPath(); 1039 fs::create_directories(path); 1040 path /= "message_registry.json"; 1041 1042 std::ofstream registryFile{path}; 1043 registryFile << registry; 1044 registryFile.close(); 1045 1046 std::unique_ptr<DataInterfaceBase> dataIface = 1047 std::make_unique<MockDataInterface>(); 1048 1049 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>(); 1050 1051 Manager manager{logManager, std::move(dataIface), 1052 std::bind_front(&TestLogger::log, &logger), 1053 std::move(journal)}; 1054 1055 std::map<std::string, std::string> additionalData{{"FOO", "BAR"}}; 1056 std::vector<std::string> associations; 1057 1058 // Create the event log to create the PEL from. 1059 manager.create("xyz.openbmc_project.Error.Test", 33, 0, Level::Error, 1060 additionalData, associations); 1061 1062 // Ensure a PEL was created in the repository 1063 auto pelData = findAnyPELInRepo(); 1064 ASSERT_TRUE(pelData); 1065 1066 auto getPELData = readPELFile(*pelData); 1067 PEL pel(*getPELData); 1068 1069 // Spot check it. Other testcases cover the details. 1070 EXPECT_TRUE(pel.valid()); 1071 1072 // Check for terminate bit set 1073 auto& hexwords = pel.primarySRC().value()->hexwordData(); 1074 EXPECT_EQ(hexwords[3] & 0x20000000, 0x20000000); 1075 } 1076 1077 TEST_F(ManagerTest, TestSanitizeFieldforDBus) 1078 { 1079 std::string base{"(test0!}\n\t ~"}; 1080 auto string = base; 1081 string += char{' ' - 1}; 1082 string += char{'~' + 1}; 1083 string += char{0}; 1084 string += char{static_cast<char>(0xFF)}; 1085 1086 // convert the last four chars to spaces 1087 EXPECT_EQ(Manager::sanitizeFieldForDBus(string), base + " "); 1088 } 1089 1090 TEST_F(ManagerTest, TestFruPlug) 1091 { 1092 const auto registry = R"( 1093 { 1094 "PELs": 1095 [{ 1096 "Name": "xyz.openbmc_project.Fan.Error.Fault", 1097 "Subsystem": "power_fans", 1098 "ComponentID": "0x2800", 1099 "SRC": 1100 { 1101 "Type": "11", 1102 "ReasonCode": "0x76F0", 1103 "Words6To9": {}, 1104 "DeconfigFlag": true 1105 }, 1106 "Callouts": [{ 1107 "CalloutList": [ 1108 {"Priority": "low", "LocCode": "P0"}, 1109 {"Priority": "high", "LocCode": "A3"} 1110 ] 1111 }], 1112 "Documentation": { 1113 "Description": "A Fan Fault", 1114 "Message": "Fan had a Fault" 1115 } 1116 }] 1117 } 1118 )"; 1119 1120 auto path = getPELReadOnlyDataPath(); 1121 fs::create_directories(path); 1122 path /= "message_registry.json"; 1123 1124 std::ofstream registryFile{path}; 1125 registryFile << registry; 1126 registryFile.close(); 1127 1128 std::unique_ptr<DataInterfaceBase> dataIface = 1129 std::make_unique<MockDataInterface>(); 1130 1131 MockDataInterface* mockIface = 1132 reinterpret_cast<MockDataInterface*>(dataIface.get()); 1133 1134 // Set up the mock calls used when building callouts 1135 EXPECT_CALL(*mockIface, getInventoryFromLocCode("P0", 0, false)) 1136 .WillRepeatedly(Return(std::vector<std::string>{"motherboard"})); 1137 EXPECT_CALL(*mockIface, expandLocationCode("P0", 0)) 1138 .WillRepeatedly(Return("U1234-P0")); 1139 EXPECT_CALL(*mockIface, getInventoryFromLocCode("U1234-P0", 0, true)) 1140 .WillRepeatedly(Return(std::vector<std::string>{"motherboard"})); 1141 1142 EXPECT_CALL(*mockIface, getInventoryFromLocCode("A3", 0, false)) 1143 .WillRepeatedly(Return(std::vector<std::string>{"fan"})); 1144 EXPECT_CALL(*mockIface, expandLocationCode("A3", 0)) 1145 .WillRepeatedly(Return("U1234-A3")); 1146 EXPECT_CALL(*mockIface, getInventoryFromLocCode("U1234-A3", 0, true)) 1147 .WillRepeatedly(Return(std::vector<std::string>{"fan"})); 1148 1149 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>(); 1150 1151 Manager manager{logManager, std::move(dataIface), 1152 std::bind_front(&TestLogger::log, &logger), 1153 std::move(journal)}; 1154 1155 std::map<std::string, std::string> additionalData; 1156 std::vector<std::string> associations; 1157 1158 auto checkDeconfigured = [](bool deconfigured) { 1159 auto pelFile = findAnyPELInRepo(); 1160 ASSERT_TRUE(pelFile); 1161 1162 auto data = readPELFile(*pelFile); 1163 PEL pel(*data); 1164 ASSERT_TRUE(pel.valid()); 1165 1166 EXPECT_EQ(pel.primarySRC().value()->getErrorStatusFlag( 1167 SRC::ErrorStatusFlags::deconfigured), 1168 deconfigured); 1169 }; 1170 1171 manager.create("xyz.openbmc_project.Fan.Error.Fault", 42, 0, Level::Error, 1172 additionalData, associations); 1173 checkDeconfigured(true); 1174 1175 // Replace A3 so PEL deconfigured flag should be set to false 1176 mockIface->fruPresent("U1234-A3"); 1177 checkDeconfigured(false); 1178 1179 manager.erase(42); 1180 1181 // Create it again and replace a FRU not in the callout list. 1182 // Deconfig flag should stay on. 1183 manager.create("xyz.openbmc_project.Fan.Error.Fault", 43, 0, Level::Error, 1184 additionalData, associations); 1185 checkDeconfigured(true); 1186 mockIface->fruPresent("U1234-A4"); 1187 checkDeconfigured(true); 1188 } 1189 1190 std::pair<int, std::filesystem::path> createHWIsolatedCalloutFile() 1191 { 1192 json jsonCalloutDataList(nlohmann::json::value_t::array); 1193 json jsonDimmCallout; 1194 1195 jsonDimmCallout["LocationCode"] = "Ufcs-DIMM0"; 1196 jsonDimmCallout["EntityPath"] = {35, 1, 0, 2, 0, 3, 0, 0, 0, 0, 0, 1197 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; 1198 jsonDimmCallout["GuardType"] = "GARD_Predictive"; 1199 jsonDimmCallout["Deconfigured"] = false; 1200 jsonDimmCallout["Guarded"] = true; 1201 jsonDimmCallout["Priority"] = "M"; 1202 jsonCalloutDataList.emplace_back(std::move(jsonDimmCallout)); 1203 1204 std::string calloutData(jsonCalloutDataList.dump()); 1205 std::string calloutFile("/tmp/phalPELCalloutsJson.XXXXXX"); 1206 int fileFD = -1; 1207 1208 fileFD = mkostemp(calloutFile.data(), O_RDWR); 1209 if (fileFD == -1) 1210 { 1211 perror("Failed to create PELCallouts file"); 1212 return {-1, {}}; 1213 } 1214 1215 ssize_t rc = write(fileFD, calloutData.c_str(), calloutData.size()); 1216 if (rc == -1) 1217 { 1218 perror("Failed to write PELCallouts file"); 1219 close(fileFD); 1220 return {-1, {}}; 1221 } 1222 1223 // Ensure we seek to the beginning of the file 1224 rc = lseek(fileFD, 0, SEEK_SET); 1225 if (rc == -1) 1226 { 1227 perror("Failed to set SEEK_SET for PELCallouts file"); 1228 close(fileFD); 1229 return {-1, {}}; 1230 } 1231 return {fileFD, calloutFile}; 1232 } 1233 1234 void appendFFDCEntry(int fd, uint8_t subTypeJson, uint8_t version, 1235 phosphor::logging::FFDCEntries& ffdcEntries) 1236 { 1237 phosphor::logging::FFDCEntry ffdcEntry = 1238 std::make_tuple(sdbusplus::xyz::openbmc_project::Logging::server:: 1239 Create::FFDCFormat::JSON, 1240 subTypeJson, version, fd); 1241 ffdcEntries.push_back(ffdcEntry); 1242 } 1243 1244 TEST_F(ManagerTest, TestPELDeleteWithoutHWIsolation) 1245 { 1246 const auto registry = R"( 1247 { 1248 "PELs": 1249 [{ 1250 "Name": "xyz.openbmc_project.Error.Test", 1251 "SRC": 1252 { 1253 "ReasonCode": "0x2030" 1254 }, 1255 "Documentation": { 1256 "Description": "Test Error", 1257 "Message": "Test Error" 1258 } 1259 }] 1260 } 1261 )"; 1262 1263 auto path = getPELReadOnlyDataPath(); 1264 fs::create_directories(path); 1265 path /= "message_registry.json"; 1266 1267 std::ofstream registryFile{path}; 1268 registryFile << registry; 1269 registryFile.close(); 1270 1271 std::unique_ptr<DataInterfaceBase> dataIface = 1272 std::make_unique<MockDataInterface>(); 1273 1274 MockDataInterface* mockIface = 1275 reinterpret_cast<MockDataInterface*>(dataIface.get()); 1276 1277 EXPECT_CALL(*mockIface, getInventoryFromLocCode("Ufcs-DIMM0", 0, false)) 1278 .WillOnce(Return(std::vector<std::string>{ 1279 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"})); 1280 1281 // Mock the scenario where the hardware isolation guard is flagged 1282 // but is not associated, resulting in an empty list being returned. 1283 EXPECT_CALL( 1284 *mockIface, 1285 getAssociatedPaths( 1286 ::testing::StrEq( 1287 "/xyz/openbmc_project/logging/entry/42/isolated_hw_entry"), 1288 ::testing::StrEq("/"), 0, 1289 ::testing::ElementsAre( 1290 "xyz.openbmc_project.HardwareIsolation.Entry"))) 1291 .WillRepeatedly(Return(std::vector<std::string>{})); 1292 1293 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>(); 1294 Manager manager{logManager, std::move(dataIface), 1295 std::bind_front(&TestLogger::log, &logger), 1296 std::move(journal)}; 1297 std::map<std::string, std::string> additionalData; 1298 std::vector<std::string> associations; 1299 1300 // Check when there's no PEL with given id. 1301 { 1302 EXPECT_FALSE(manager.isDeleteProhibited(42)); 1303 } 1304 // creating without ffdcEntries 1305 manager.create("xyz.openbmc_project.Error.Test", 42, 0, Level::Error, 1306 additionalData, associations); 1307 auto pelFile = findAnyPELInRepo(); 1308 auto data = readPELFile(*pelFile); 1309 PEL pel_unguarded(*data); 1310 { 1311 // Verify that the guard flag is false. 1312 EXPECT_FALSE(pel_unguarded.getGuardFlag()); 1313 // Check that `isDeleteProhibited` returns false when the guard flag 1314 // is false. 1315 EXPECT_FALSE(manager.isDeleteProhibited(42)); 1316 } 1317 manager.erase(42); 1318 EXPECT_FALSE(findAnyPELInRepo()); 1319 1320 auto [fd, calloutFile] = createHWIsolatedCalloutFile(); 1321 ASSERT_NE(fd, -1); 1322 uint8_t subTypeJson = 0xCA; 1323 uint8_t version = 0x01; 1324 phosphor::logging::FFDCEntries ffdcEntries; 1325 appendFFDCEntry(fd, subTypeJson, version, ffdcEntries); 1326 manager.create("xyz.openbmc_project.Error.Test", 42, 0, Level::Error, 1327 additionalData, associations, ffdcEntries); 1328 close(fd); 1329 std::filesystem::remove(calloutFile); 1330 1331 auto pelPathInRepo = findAnyPELInRepo(); 1332 auto unguardedData = readPELFile(*pelPathInRepo); 1333 PEL pel(*unguardedData); 1334 { 1335 // Verify guard flag set to true 1336 EXPECT_TRUE(pel.getGuardFlag()); 1337 // Check even if guard flag is true, if dbus call returns empty 1338 // array list then `isDeleteProhibited` returns false 1339 EXPECT_FALSE(manager.isDeleteProhibited(42)); 1340 } 1341 manager.erase(42); 1342 } 1343 1344 TEST_F(ManagerTest, TestPELDeleteWithHWIsolation) 1345 { 1346 const auto registry = R"( 1347 { 1348 "PELs": 1349 [{ 1350 "Name": "xyz.openbmc_project.Error.Test", 1351 "Severity": "critical_system_term", 1352 "SRC": 1353 { 1354 "ReasonCode": "0x2030" 1355 }, 1356 "Documentation": { 1357 "Description": "Test Error", 1358 "Message": "Test Error" 1359 } 1360 }] 1361 } 1362 )"; 1363 1364 auto path = getPELReadOnlyDataPath(); 1365 fs::create_directories(path); 1366 path /= "message_registry.json"; 1367 1368 std::ofstream registryFile{path}; 1369 registryFile << registry; 1370 registryFile.close(); 1371 1372 std::unique_ptr<DataInterfaceBase> dataIface = 1373 std::make_unique<MockDataInterface>(); 1374 1375 MockDataInterface* mockIface = 1376 reinterpret_cast<MockDataInterface*>(dataIface.get()); 1377 1378 EXPECT_CALL(*mockIface, getInventoryFromLocCode("Ufcs-DIMM0", 0, false)) 1379 .WillOnce(Return(std::vector<std::string>{ 1380 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"})); 1381 1382 EXPECT_CALL( 1383 *mockIface, 1384 getAssociatedPaths( 1385 ::testing::StrEq( 1386 "/xyz/openbmc_project/logging/entry/42/isolated_hw_entry"), 1387 ::testing::StrEq("/"), 0, 1388 ::testing::ElementsAre( 1389 "xyz.openbmc_project.HardwareIsolation.Entry"))) 1390 .WillRepeatedly(Return(std::vector<std::string>{ 1391 "/xyz/openbmc_project/hardware_isolation/entry/1"})); 1392 1393 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>(); 1394 Manager manager{logManager, std::move(dataIface), 1395 std::bind_front(&TestLogger::log, &logger), 1396 std::move(journal)}; 1397 std::map<std::string, std::string> additionalData; 1398 std::vector<std::string> associations; 1399 1400 auto [fd, calloutFile] = createHWIsolatedCalloutFile(); 1401 ASSERT_NE(fd, -1); 1402 uint8_t subTypeJson = 0xCA; 1403 uint8_t version = 0x01; 1404 phosphor::logging::FFDCEntries ffdcEntries; 1405 appendFFDCEntry(fd, subTypeJson, version, ffdcEntries); 1406 manager.create("xyz.openbmc_project.Error.Test", 42, 0, Level::Error, 1407 additionalData, associations, ffdcEntries); 1408 close(fd); 1409 std::filesystem::remove(calloutFile); 1410 1411 auto pelFile = findAnyPELInRepo(); 1412 EXPECT_TRUE(pelFile); 1413 auto data = readPELFile(*pelFile); 1414 PEL pel(*data); 1415 EXPECT_TRUE(pel.valid()); 1416 // Test case where the guard flag is set to true and the hardware 1417 // isolation guard is associated, which should result in 1418 // `isDeleteProhibited` returning true as expected. 1419 EXPECT_TRUE(pel.getGuardFlag()); 1420 EXPECT_TRUE(manager.isDeleteProhibited(42)); 1421 manager.erase(42); 1422 } 1423