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