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 "elog_entry.hpp" 17 #include "extensions/openpower-pels/generic.hpp" 18 #include "extensions/openpower-pels/pel.hpp" 19 #include "mocks.hpp" 20 #include "pel_utils.hpp" 21 22 #include <filesystem> 23 #include <fstream> 24 25 #include <gtest/gtest.h> 26 27 namespace fs = std::filesystem; 28 using namespace openpower::pels; 29 using ::testing::_; 30 using ::testing::DoAll; 31 using ::testing::NiceMock; 32 using ::testing::Return; 33 using ::testing::SetArgReferee; 34 35 class PELTest : public CleanLogID 36 { 37 }; 38 39 fs::path makeTempDir() 40 { 41 char path[] = "/tmp/tempdirXXXXXX"; 42 std::filesystem::path dir = mkdtemp(path); 43 return dir; 44 } 45 46 int writeFileAndGetFD(const fs::path& dir, const std::vector<uint8_t>& data) 47 { 48 static size_t count = 0; 49 fs::path path = dir / (std::string{"file"} + std::to_string(count)); 50 std::ofstream stream{path}; 51 count++; 52 53 stream.write(reinterpret_cast<const char*>(data.data()), data.size()); 54 stream.close(); 55 56 FILE* fp = fopen(path.c_str(), "r"); 57 return fileno(fp); 58 } 59 60 TEST_F(PELTest, FlattenTest) 61 { 62 auto data = pelDataFactory(TestPELType::pelSimple); 63 auto pel = std::make_unique<PEL>(data); 64 65 // Check a few fields 66 EXPECT_TRUE(pel->valid()); 67 EXPECT_EQ(pel->id(), 0x80818283); 68 EXPECT_EQ(pel->plid(), 0x50515253); 69 EXPECT_EQ(pel->userHeader().subsystem(), 0x10); 70 EXPECT_EQ(pel->userHeader().actionFlags(), 0x80C0); 71 72 // Test that data in == data out 73 auto flattenedData = pel->data(); 74 EXPECT_EQ(data, flattenedData); 75 EXPECT_EQ(flattenedData.size(), pel->size()); 76 } 77 78 TEST_F(PELTest, CommitTimeTest) 79 { 80 auto data = pelDataFactory(TestPELType::pelSimple); 81 auto pel = std::make_unique<PEL>(data); 82 83 auto origTime = pel->commitTime(); 84 pel->setCommitTime(); 85 auto newTime = pel->commitTime(); 86 87 EXPECT_NE(origTime, newTime); 88 89 // Make a new PEL and check new value is still there 90 auto newData = pel->data(); 91 auto newPel = std::make_unique<PEL>(newData); 92 EXPECT_EQ(newTime, newPel->commitTime()); 93 } 94 95 TEST_F(PELTest, AssignIDTest) 96 { 97 auto data = pelDataFactory(TestPELType::pelSimple); 98 auto pel = std::make_unique<PEL>(data); 99 100 auto origID = pel->id(); 101 pel->assignID(); 102 auto newID = pel->id(); 103 104 EXPECT_NE(origID, newID); 105 106 // Make a new PEL and check new value is still there 107 auto newData = pel->data(); 108 auto newPel = std::make_unique<PEL>(newData); 109 EXPECT_EQ(newID, newPel->id()); 110 } 111 112 TEST_F(PELTest, WithLogIDTest) 113 { 114 auto data = pelDataFactory(TestPELType::pelSimple); 115 auto pel = std::make_unique<PEL>(data, 0x42); 116 117 EXPECT_TRUE(pel->valid()); 118 EXPECT_EQ(pel->obmcLogID(), 0x42); 119 } 120 121 TEST_F(PELTest, InvalidPELTest) 122 { 123 auto data = pelDataFactory(TestPELType::pelSimple); 124 125 // Too small 126 data.resize(PrivateHeader::flattenedSize()); 127 128 auto pel = std::make_unique<PEL>(data); 129 130 EXPECT_TRUE(pel->privateHeader().valid()); 131 EXPECT_FALSE(pel->userHeader().valid()); 132 EXPECT_FALSE(pel->valid()); 133 134 // Now corrupt the private header 135 data = pelDataFactory(TestPELType::pelSimple); 136 data.at(0) = 0; 137 pel = std::make_unique<PEL>(data); 138 139 EXPECT_FALSE(pel->privateHeader().valid()); 140 EXPECT_TRUE(pel->userHeader().valid()); 141 EXPECT_FALSE(pel->valid()); 142 } 143 144 TEST_F(PELTest, EmptyDataTest) 145 { 146 std::vector<uint8_t> data; 147 auto pel = std::make_unique<PEL>(data); 148 149 EXPECT_FALSE(pel->privateHeader().valid()); 150 EXPECT_FALSE(pel->userHeader().valid()); 151 EXPECT_FALSE(pel->valid()); 152 } 153 154 TEST_F(PELTest, CreateFromRegistryTest) 155 { 156 message::Entry regEntry; 157 uint64_t timestamp = 5; 158 159 regEntry.name = "test"; 160 regEntry.subsystem = 5; 161 regEntry.actionFlags = 0xC000; 162 regEntry.src.type = 0xBD; 163 regEntry.src.reasonCode = 0x1234; 164 165 std::vector<std::string> data{"KEY1=VALUE1"}; 166 AdditionalData ad{data}; 167 NiceMock<MockDataInterface> dataIface; 168 PelFFDC ffdc; 169 170 std::vector<std::string> dumpType{"bmc/entry", "resource/entry", 171 "system/entry"}; 172 EXPECT_CALL(dataIface, checkDumpStatus(dumpType)) 173 .WillRepeatedly(Return(std::vector<bool>{false, false, false})); 174 175 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 176 ad, ffdc, dataIface}; 177 178 EXPECT_TRUE(pel.valid()); 179 EXPECT_EQ(pel.privateHeader().obmcLogID(), 42); 180 EXPECT_EQ(pel.userHeader().severity(), 0x40); 181 182 EXPECT_EQ(pel.primarySRC().value()->asciiString(), 183 "BD051234 "); 184 185 // Check that certain optional sections have been created 186 size_t mtmsCount = 0; 187 size_t euhCount = 0; 188 size_t udCount = 0; 189 190 for (const auto& section : pel.optionalSections()) 191 { 192 if (section->header().id == 193 static_cast<uint16_t>(SectionID::failingMTMS)) 194 { 195 mtmsCount++; 196 } 197 else if (section->header().id == 198 static_cast<uint16_t>(SectionID::extendedUserHeader)) 199 { 200 euhCount++; 201 } 202 else if (section->header().id == 203 static_cast<uint16_t>(SectionID::userData)) 204 { 205 udCount++; 206 } 207 } 208 209 EXPECT_EQ(mtmsCount, 1); 210 EXPECT_EQ(euhCount, 1); 211 EXPECT_EQ(udCount, 2); // AD section and sysInfo section 212 ASSERT_FALSE(pel.isHwCalloutPresent()); 213 214 { 215 // The same thing, but without the action flags specified 216 // in the registry, so the constructor should set them. 217 regEntry.actionFlags = std::nullopt; 218 219 PEL pel2{ 220 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 221 ad, ffdc, dataIface}; 222 223 EXPECT_EQ(pel2.userHeader().actionFlags(), 0xA800); 224 } 225 } 226 227 // Test that when the AdditionalData size is over 16KB that 228 // the PEL that's created is exactly 16KB since the UserData 229 // section that contains all that data was pruned. 230 TEST_F(PELTest, CreateTooBigADTest) 231 { 232 message::Entry regEntry; 233 uint64_t timestamp = 5; 234 235 regEntry.name = "test"; 236 regEntry.subsystem = 5; 237 regEntry.actionFlags = 0xC000; 238 regEntry.src.type = 0xBD; 239 regEntry.src.reasonCode = 0x1234; 240 PelFFDC ffdc; 241 242 // Over the 16KB max PEL size 243 std::string bigAD{"KEY1="}; 244 bigAD += std::string(17000, 'G'); 245 246 std::vector<std::string> data{bigAD}; 247 AdditionalData ad{data}; 248 NiceMock<MockDataInterface> dataIface; 249 250 std::vector<std::string> dumpType{"bmc/entry", "resource/entry", 251 "system/entry"}; 252 EXPECT_CALL(dataIface, checkDumpStatus(dumpType)) 253 .WillOnce(Return(std::vector<bool>{false, false, false})); 254 255 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 256 ad, ffdc, dataIface}; 257 258 EXPECT_TRUE(pel.valid()); 259 EXPECT_EQ(pel.size(), 16384); 260 261 // Make sure that there are still 2 UD sections. 262 const auto& optSections = pel.optionalSections(); 263 auto udCount = std::count_if( 264 optSections.begin(), optSections.end(), [](const auto& section) { 265 return section->header().id == 266 static_cast<uint16_t>(SectionID::userData); 267 }); 268 269 EXPECT_EQ(udCount, 2); // AD section and sysInfo section 270 } 271 272 // Test that we'll create Generic optional sections for sections that 273 // there aren't explicit classes for. 274 TEST_F(PELTest, GenericSectionTest) 275 { 276 auto data = pelDataFactory(TestPELType::pelSimple); 277 278 std::vector<uint8_t> section1{0x58, 0x58, // ID 'XX' 279 0x00, 0x18, // Size 280 0x01, 0x02, // version, subtype 281 0x03, 0x04, // comp ID 282 283 // some data 284 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 285 0x20, 0x31, 0x06, 0x0F, 0x09, 0x22, 0x3A, 286 0x00}; 287 288 std::vector<uint8_t> section2{ 289 0x59, 0x59, // ID 'YY' 290 0x00, 0x20, // Size 291 0x01, 0x02, // version, subtype 292 0x03, 0x04, // comp ID 293 294 // some data 295 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F, 296 0x09, 0x22, 0x3A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; 297 298 // Add the new sections at the end 299 data.insert(data.end(), section1.begin(), section1.end()); 300 data.insert(data.end(), section2.begin(), section2.end()); 301 302 // Increment the section count 303 data.at(27) += 2; 304 auto origData = data; 305 306 PEL pel{data}; 307 308 const auto& sections = pel.optionalSections(); 309 310 bool foundXX = false; 311 bool foundYY = false; 312 313 // Check that we can find these 2 Generic sections 314 for (const auto& section : sections) 315 { 316 if (section->header().id == 0x5858) 317 { 318 foundXX = true; 319 EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr); 320 } 321 else if (section->header().id == 0x5959) 322 { 323 foundYY = true; 324 EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr); 325 } 326 } 327 328 EXPECT_TRUE(foundXX); 329 EXPECT_TRUE(foundYY); 330 331 // Now flatten and check 332 auto newData = pel.data(); 333 334 EXPECT_EQ(origData, newData); 335 } 336 337 // Test that an invalid section will still get a Generic object 338 TEST_F(PELTest, InvalidGenericTest) 339 { 340 auto data = pelDataFactory(TestPELType::pelSimple); 341 342 // Not a valid section 343 std::vector<uint8_t> section1{0x01, 0x02, 0x03}; 344 345 data.insert(data.end(), section1.begin(), section1.end()); 346 347 // Increment the section count 348 data.at(27) += 1; 349 350 PEL pel{data}; 351 EXPECT_FALSE(pel.valid()); 352 353 const auto& sections = pel.optionalSections(); 354 355 bool foundGeneric = false; 356 for (const auto& section : sections) 357 { 358 if (dynamic_cast<Generic*>(section.get()) != nullptr) 359 { 360 foundGeneric = true; 361 EXPECT_EQ(section->valid(), false); 362 break; 363 } 364 } 365 366 EXPECT_TRUE(foundGeneric); 367 } 368 369 // Create a UserData section out of AdditionalData 370 TEST_F(PELTest, MakeUDSectionTest) 371 { 372 std::vector<std::string> ad{"KEY1=VALUE1", "KEY2=VALUE2", "KEY3=VALUE3", 373 "ESEL=TEST"}; 374 AdditionalData additionalData{ad}; 375 376 auto ud = util::makeADUserDataSection(additionalData); 377 378 EXPECT_TRUE(ud->valid()); 379 EXPECT_EQ(ud->header().id, 0x5544); 380 EXPECT_EQ(ud->header().version, 0x01); 381 EXPECT_EQ(ud->header().subType, 0x01); 382 EXPECT_EQ(ud->header().componentID, 0x2000); 383 384 const auto& d = ud->data(); 385 386 std::string jsonString{d.begin(), d.end()}; 387 388 std::string expectedJSON = 389 R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":"VALUE3"})"; 390 391 // The actual data is null padded to a 4B boundary. 392 std::vector<uint8_t> expectedData; 393 expectedData.resize(52, '\0'); 394 memcpy(expectedData.data(), expectedJSON.data(), expectedJSON.size()); 395 396 EXPECT_EQ(d, expectedData); 397 398 // Ensure we can read this as JSON 399 auto newJSON = nlohmann::json::parse(jsonString); 400 EXPECT_EQ(newJSON["KEY1"], "VALUE1"); 401 EXPECT_EQ(newJSON["KEY2"], "VALUE2"); 402 EXPECT_EQ(newJSON["KEY3"], "VALUE3"); 403 } 404 405 // Create the UserData section that contains system info 406 TEST_F(PELTest, SysInfoSectionTest) 407 { 408 MockDataInterface dataIface; 409 410 EXPECT_CALL(dataIface, getBMCFWVersionID()).WillOnce(Return("ABCD1234")); 411 EXPECT_CALL(dataIface, getBMCState()).WillOnce(Return("State.Ready")); 412 EXPECT_CALL(dataIface, getChassisState()).WillOnce(Return("State.On")); 413 EXPECT_CALL(dataIface, getHostState()).WillOnce(Return("State.Off")); 414 EXPECT_CALL(dataIface, getBootState()) 415 .WillOnce(Return("State.SystemInitComplete")); 416 EXPECT_CALL(dataIface, getSystemIMKeyword()) 417 .WillOnce(Return(std::vector<uint8_t>{0, 1, 0x55, 0xAA})); 418 419 std::string pid = "_PID=" + std::to_string(getpid()); 420 std::vector<std::string> ad{pid}; 421 AdditionalData additionalData{ad}; 422 423 auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface); 424 425 EXPECT_TRUE(ud->valid()); 426 EXPECT_EQ(ud->header().id, 0x5544); 427 EXPECT_EQ(ud->header().version, 0x01); 428 EXPECT_EQ(ud->header().subType, 0x01); 429 EXPECT_EQ(ud->header().componentID, 0x2000); 430 431 // Pull out the JSON data and check it. 432 const auto& d = ud->data(); 433 std::string jsonString{d.begin(), d.end()}; 434 auto json = nlohmann::json::parse(jsonString); 435 436 // Ensure the 'Process Name' entry contains the name of this test 437 // executable. 438 auto name = json["Process Name"].get<std::string>(); 439 auto found = (name.find("pel_test") != std::string::npos) || 440 (name.find("test-openpower-pels-pel") != std::string::npos); 441 EXPECT_TRUE(found); 442 // @TODO(stwcx): remove 'pel_test' when removing autotools. 443 444 auto version = json["FW Version ID"].get<std::string>(); 445 EXPECT_EQ(version, "ABCD1234"); 446 447 auto state = json["BMCState"].get<std::string>(); 448 EXPECT_EQ(state, "Ready"); 449 450 state = json["ChassisState"].get<std::string>(); 451 EXPECT_EQ(state, "On"); 452 453 state = json["HostState"].get<std::string>(); 454 EXPECT_EQ(state, "Off"); 455 456 state = json["BootState"].get<std::string>(); 457 EXPECT_EQ(state, "SystemInitComplete"); 458 459 auto keyword = json["System IM"].get<std::string>(); 460 EXPECT_EQ(keyword, "000155AA"); 461 } 462 463 // Test that the sections that override 464 // virtual std::optional<std::string> Section::getJSON() const 465 // return valid JSON. 466 TEST_F(PELTest, SectionJSONTest) 467 { 468 auto data = pelDataFactory(TestPELType::pelSimple); 469 PEL pel{data}; 470 471 // Check that all JSON returned from the sections is 472 // parseable by nlohmann::json, which will throw an 473 // exception and fail the test if there is a problem. 474 475 // The getJSON() response needs to be wrapped in a { } to make 476 // actual valid JSON (PEL::toJSON() usually handles that). 477 478 auto jsonString = pel.privateHeader().getJSON(); 479 480 // PrivateHeader always prints JSON 481 ASSERT_TRUE(jsonString); 482 *jsonString = '{' + *jsonString + '}'; 483 auto json = nlohmann::json::parse(*jsonString); 484 485 jsonString = pel.userHeader().getJSON(); 486 487 // UserHeader always prints JSON 488 ASSERT_TRUE(jsonString); 489 *jsonString = '{' + *jsonString + '}'; 490 json = nlohmann::json::parse(*jsonString); 491 492 for (const auto& section : pel.optionalSections()) 493 { 494 // The optional sections may or may not have implemented getJSON(). 495 jsonString = section->getJSON(); 496 if (jsonString) 497 { 498 *jsonString = '{' + *jsonString + '}'; 499 auto json = nlohmann::json::parse(*jsonString); 500 } 501 } 502 } 503 504 PelFFDCfile getJSONFFDC(const fs::path& dir) 505 { 506 PelFFDCfile ffdc; 507 ffdc.format = UserDataFormat::json; 508 ffdc.subType = 5; 509 ffdc.version = 42; 510 511 auto inputJSON = R"({ 512 "key1": "value1", 513 "key2": 42, 514 "key3" : [1, 2, 3, 4, 5], 515 "key4": {"key5": "value5"} 516 })"_json; 517 518 // Write the JSON to a file and get its descriptor. 519 auto s = inputJSON.dump(); 520 std::vector<uint8_t> data{s.begin(), s.end()}; 521 ffdc.fd = writeFileAndGetFD(dir, data); 522 523 return ffdc; 524 } 525 526 TEST_F(PELTest, MakeJSONFileUDSectionTest) 527 { 528 auto dir = makeTempDir(); 529 530 { 531 auto ffdc = getJSONFFDC(dir); 532 533 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 534 close(ffdc.fd); 535 ASSERT_TRUE(ud); 536 ASSERT_TRUE(ud->valid()); 537 EXPECT_EQ(ud->header().id, 0x5544); 538 539 EXPECT_EQ(ud->header().version, 540 static_cast<uint8_t>(UserDataFormatVersion::json)); 541 EXPECT_EQ(ud->header().subType, 542 static_cast<uint8_t>(UserDataFormat::json)); 543 EXPECT_EQ(ud->header().componentID, 544 static_cast<uint16_t>(ComponentID::phosphorLogging)); 545 546 // Pull the JSON back out of the the UserData section 547 const auto& d = ud->data(); 548 std::string js{d.begin(), d.end()}; 549 auto json = nlohmann::json::parse(js); 550 551 EXPECT_EQ("value1", json["key1"].get<std::string>()); 552 EXPECT_EQ(42, json["key2"].get<int>()); 553 554 std::vector<int> key3Values{1, 2, 3, 4, 5}; 555 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>()); 556 557 std::map<std::string, std::string> key4Values{{"key5", "value5"}}; 558 auto actual = json["key4"].get<std::map<std::string, std::string>>(); 559 EXPECT_EQ(key4Values, actual); 560 } 561 562 { 563 // A bad FD 564 PelFFDCfile ffdc; 565 ffdc.format = UserDataFormat::json; 566 ffdc.subType = 5; 567 ffdc.version = 42; 568 ffdc.fd = 10000; 569 570 // The section shouldn't get made 571 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 572 ASSERT_FALSE(ud); 573 } 574 575 fs::remove_all(dir); 576 } 577 578 PelFFDCfile getCBORFFDC(const fs::path& dir) 579 { 580 PelFFDCfile ffdc; 581 ffdc.format = UserDataFormat::cbor; 582 ffdc.subType = 5; 583 ffdc.version = 42; 584 585 auto inputJSON = R"({ 586 "key1": "value1", 587 "key2": 42, 588 "key3" : [1, 2, 3, 4, 5], 589 "key4": {"key5": "value5"} 590 })"_json; 591 592 // Convert the JSON to CBOR and write it to a file 593 auto data = nlohmann::json::to_cbor(inputJSON); 594 ffdc.fd = writeFileAndGetFD(dir, data); 595 596 return ffdc; 597 } 598 599 TEST_F(PELTest, MakeCBORFileUDSectionTest) 600 { 601 auto dir = makeTempDir(); 602 603 auto ffdc = getCBORFFDC(dir); 604 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 605 close(ffdc.fd); 606 ASSERT_TRUE(ud); 607 ASSERT_TRUE(ud->valid()); 608 EXPECT_EQ(ud->header().id, 0x5544); 609 610 EXPECT_EQ(ud->header().version, 611 static_cast<uint8_t>(UserDataFormatVersion::cbor)); 612 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::cbor)); 613 EXPECT_EQ(ud->header().componentID, 614 static_cast<uint16_t>(ComponentID::phosphorLogging)); 615 616 // Pull the CBOR back out of the PEL section 617 // The number of pad bytes to make the section be 4B aligned 618 // was added at the end, read it and then remove it and the 619 // padding before parsing it. 620 auto data = ud->data(); 621 Stream stream{data}; 622 stream.offset(data.size() - 4); 623 uint32_t pad; 624 stream >> pad; 625 626 data.resize(data.size() - 4 - pad); 627 628 auto json = nlohmann::json::from_cbor(data); 629 630 EXPECT_EQ("value1", json["key1"].get<std::string>()); 631 EXPECT_EQ(42, json["key2"].get<int>()); 632 633 std::vector<int> key3Values{1, 2, 3, 4, 5}; 634 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>()); 635 636 std::map<std::string, std::string> key4Values{{"key5", "value5"}}; 637 auto actual = json["key4"].get<std::map<std::string, std::string>>(); 638 EXPECT_EQ(key4Values, actual); 639 640 fs::remove_all(dir); 641 } 642 643 PelFFDCfile getTextFFDC(const fs::path& dir) 644 { 645 PelFFDCfile ffdc; 646 ffdc.format = UserDataFormat::text; 647 ffdc.subType = 5; 648 ffdc.version = 42; 649 650 std::string text{"this is some text that will be used for FFDC"}; 651 std::vector<uint8_t> data{text.begin(), text.end()}; 652 653 ffdc.fd = writeFileAndGetFD(dir, data); 654 655 return ffdc; 656 } 657 658 TEST_F(PELTest, MakeTextFileUDSectionTest) 659 { 660 auto dir = makeTempDir(); 661 662 auto ffdc = getTextFFDC(dir); 663 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 664 close(ffdc.fd); 665 ASSERT_TRUE(ud); 666 ASSERT_TRUE(ud->valid()); 667 EXPECT_EQ(ud->header().id, 0x5544); 668 669 EXPECT_EQ(ud->header().version, 670 static_cast<uint8_t>(UserDataFormatVersion::text)); 671 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::text)); 672 EXPECT_EQ(ud->header().componentID, 673 static_cast<uint16_t>(ComponentID::phosphorLogging)); 674 675 // Get the text back out 676 std::string text{ud->data().begin(), ud->data().end()}; 677 EXPECT_EQ(text, "this is some text that will be used for FFDC"); 678 679 fs::remove_all(dir); 680 } 681 682 PelFFDCfile getCustomFFDC(const fs::path& dir, const std::vector<uint8_t>& data) 683 { 684 PelFFDCfile ffdc; 685 ffdc.format = UserDataFormat::custom; 686 ffdc.subType = 5; 687 ffdc.version = 42; 688 689 ffdc.fd = writeFileAndGetFD(dir, data); 690 691 return ffdc; 692 } 693 694 TEST_F(PELTest, MakeCustomFileUDSectionTest) 695 { 696 auto dir = makeTempDir(); 697 698 { 699 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8}; 700 701 auto ffdc = getCustomFFDC(dir, data); 702 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 703 close(ffdc.fd); 704 ASSERT_TRUE(ud); 705 ASSERT_TRUE(ud->valid()); 706 EXPECT_EQ(ud->header().size, 8 + 8); // data size + header size 707 EXPECT_EQ(ud->header().id, 0x5544); 708 709 EXPECT_EQ(ud->header().version, 42); 710 EXPECT_EQ(ud->header().subType, 5); 711 EXPECT_EQ(ud->header().componentID, 0x2002); 712 713 // Get the data back out 714 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()}; 715 EXPECT_EQ(data, newData); 716 } 717 718 // Do the same thing again, but make it be non 4B aligned 719 // so the data gets padded. 720 { 721 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8, 9}; 722 723 auto ffdc = getCustomFFDC(dir, data); 724 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 725 close(ffdc.fd); 726 ASSERT_TRUE(ud); 727 ASSERT_TRUE(ud->valid()); 728 EXPECT_EQ(ud->header().size, 12 + 8); // data size + header size 729 EXPECT_EQ(ud->header().id, 0x5544); 730 731 EXPECT_EQ(ud->header().version, 42); 732 EXPECT_EQ(ud->header().subType, 5); 733 EXPECT_EQ(ud->header().componentID, 0x2002); 734 735 // Get the data back out 736 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()}; 737 738 // pad the original to 12B so we can compare 739 data.push_back(0); 740 data.push_back(0); 741 data.push_back(0); 742 743 EXPECT_EQ(data, newData); 744 } 745 746 fs::remove_all(dir); 747 } 748 749 // Test Adding FFDC from files to a PEL 750 TEST_F(PELTest, CreateWithFFDCTest) 751 { 752 auto dir = makeTempDir(); 753 message::Entry regEntry; 754 uint64_t timestamp = 5; 755 756 regEntry.name = "test"; 757 regEntry.subsystem = 5; 758 regEntry.actionFlags = 0xC000; 759 regEntry.src.type = 0xBD; 760 regEntry.src.reasonCode = 0x1234; 761 762 std::vector<std::string> additionalData{"KEY1=VALUE1"}; 763 AdditionalData ad{additionalData}; 764 NiceMock<MockDataInterface> dataIface; 765 PelFFDC ffdc; 766 767 std::vector<uint8_t> customData{1, 2, 3, 4, 5, 6, 7, 8}; 768 769 // This will be trimmed when added 770 std::vector<uint8_t> hugeCustomData(17000, 0x42); 771 772 ffdc.emplace_back(std::move(getJSONFFDC(dir))); 773 ffdc.emplace_back(std::move(getCBORFFDC(dir))); 774 ffdc.emplace_back(std::move(getTextFFDC(dir))); 775 ffdc.emplace_back(std::move(getCustomFFDC(dir, customData))); 776 ffdc.emplace_back(std::move(getCustomFFDC(dir, hugeCustomData))); 777 778 std::vector<std::string> dumpType{"bmc/entry", "resource/entry", 779 "system/entry"}; 780 EXPECT_CALL(dataIface, checkDumpStatus(dumpType)) 781 .WillOnce(Return(std::vector<bool>{false, false, false})); 782 783 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 784 ad, ffdc, dataIface}; 785 786 EXPECT_TRUE(pel.valid()); 787 788 // Clipped to the max 789 EXPECT_EQ(pel.size(), 16384); 790 791 // Check for the FFDC sections 792 size_t udCount = 0; 793 Section* ud = nullptr; 794 795 for (const auto& section : pel.optionalSections()) 796 { 797 if (section->header().id == static_cast<uint16_t>(SectionID::userData)) 798 { 799 udCount++; 800 ud = section.get(); 801 } 802 } 803 804 EXPECT_EQ(udCount, 7); // AD section, sysInfo, 5 ffdc sections 805 806 // Check the last section was trimmed to 807 // something a bit less that 17000. 808 EXPECT_GT(ud->header().size, 14000); 809 EXPECT_LT(ud->header().size, 16000); 810 811 fs::remove_all(dir); 812 } 813 814 // Create a PEL with device callouts 815 TEST_F(PELTest, CreateWithDevCalloutsTest) 816 { 817 message::Entry regEntry; 818 uint64_t timestamp = 5; 819 820 regEntry.name = "test"; 821 regEntry.subsystem = 5; 822 regEntry.actionFlags = 0xC000; 823 regEntry.src.type = 0xBD; 824 regEntry.src.reasonCode = 0x1234; 825 826 NiceMock<MockDataInterface> dataIface; 827 PelFFDC ffdc; 828 829 const auto calloutJSON = R"( 830 { 831 "I2C": 832 { 833 "14": 834 { 835 "114": 836 { 837 "Callouts":[ 838 { 839 "Name": "/chassis/motherboard/cpu0", 840 "LocationCode": "P1", 841 "Priority": "H" 842 } 843 ], 844 "Dest": "proc 0 target" 845 } 846 } 847 } 848 })"; 849 850 std::vector<std::string> names{"systemA"}; 851 EXPECT_CALL(dataIface, getSystemNames) 852 .Times(2) 853 .WillRepeatedly(Return(names)); 854 855 EXPECT_CALL(dataIface, expandLocationCode("P1", 0)) 856 .Times(1) 857 .WillOnce(Return("UXXX-P1")); 858 859 EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false)) 860 .WillOnce( 861 Return("/xyz/openbmc_project/inventory/chassis/motherboard/cpu0")); 862 863 EXPECT_CALL( 864 dataIface, 865 getHWCalloutFields( 866 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _, _)) 867 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"), 868 SetArgReferee<3>("123456789ABC"))); 869 870 std::vector<std::string> dumpType{"bmc/entry", "resource/entry", 871 "system/entry"}; 872 EXPECT_CALL(dataIface, checkDumpStatus(dumpType)) 873 .WillRepeatedly(Return(std::vector<bool>{false, false, false})); 874 875 auto dataPath = getPELReadOnlyDataPath(); 876 std::ofstream file{dataPath / "systemA_dev_callouts.json"}; 877 file << calloutJSON; 878 file.close(); 879 880 { 881 std::vector<std::string> data{ 882 "CALLOUT_ERRNO=5", 883 "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/" 884 "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"}; 885 886 AdditionalData ad{data}; 887 888 PEL pel{ 889 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 890 ad, ffdc, dataIface}; 891 892 ASSERT_TRUE(pel.primarySRC().value()->callouts()); 893 auto& callouts = pel.primarySRC().value()->callouts()->callouts(); 894 ASSERT_EQ(callouts.size(), 1); 895 ASSERT_TRUE(pel.isHwCalloutPresent()); 896 897 EXPECT_EQ(callouts[0]->priority(), 'H'); 898 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P1"); 899 900 auto& fru = callouts[0]->fruIdentity(); 901 EXPECT_EQ(fru->getPN().value(), "1234567"); 902 EXPECT_EQ(fru->getCCIN().value(), "CCCC"); 903 EXPECT_EQ(fru->getSN().value(), "123456789ABC"); 904 905 const auto& section = pel.optionalSections().back(); 906 907 ASSERT_EQ(section->header().id, 0x5544); // UD 908 auto ud = static_cast<UserData*>(section.get()); 909 910 // Check that there was a UserData section added that 911 // contains debug details about the device. 912 const auto& d = ud->data(); 913 std::string jsonString{d.begin(), d.end()}; 914 auto actualJSON = nlohmann::json::parse(jsonString); 915 916 auto expectedJSON = R"( 917 { 918 "PEL Internal Debug Data": { 919 "SRC": [ 920 "I2C: bus: 14 address: 114 dest: proc 0 target" 921 ] 922 } 923 } 924 )"_json; 925 926 EXPECT_EQ(actualJSON, expectedJSON); 927 } 928 929 { 930 // Device path not found (wrong i2c addr), so no callouts 931 std::vector<std::string> data{ 932 "CALLOUT_ERRNO=5", 933 "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/" 934 "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0099"}; 935 936 AdditionalData ad{data}; 937 938 PEL pel{ 939 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 940 ad, ffdc, dataIface}; 941 942 // no callouts 943 EXPECT_FALSE(pel.primarySRC().value()->callouts()); 944 945 // Now check that there was a UserData section 946 // that contains the lookup error. 947 const auto& section = pel.optionalSections().back(); 948 949 ASSERT_EQ(section->header().id, 0x5544); // UD 950 auto ud = static_cast<UserData*>(section.get()); 951 952 const auto& d = ud->data(); 953 954 std::string jsonString{d.begin(), d.end()}; 955 956 auto actualJSON = nlohmann::json::parse(jsonString); 957 958 auto expectedJSON = 959 "{\"PEL Internal Debug Data\":{\"SRC\":" 960 "[\"Problem looking up I2C callouts on 14 153: " 961 "[json.exception.out_of_range.403] key '153' not found\"]}}"_json; 962 963 EXPECT_EQ(actualJSON, expectedJSON); 964 } 965 966 fs::remove_all(dataPath); 967 } 968 969 // Test PELs when the callouts are passed in using a JSON file. 970 TEST_F(PELTest, CreateWithJSONCalloutsTest) 971 { 972 PelFFDCfile ffdcFile; 973 ffdcFile.format = UserDataFormat::json; 974 ffdcFile.subType = 0xCA; // Callout JSON 975 ffdcFile.version = 1; 976 977 // Write these callouts to a JSON file and pass it into 978 // the PEL as an FFDC file. 979 auto inputJSON = R"([ 980 { 981 "Priority": "H", 982 "LocationCode": "P0-C1" 983 }, 984 { 985 "Priority": "M", 986 "Procedure": "PROCEDURE" 987 } 988 ])"_json; 989 990 auto s = inputJSON.dump(); 991 std::vector<uint8_t> data{s.begin(), s.end()}; 992 auto dir = makeTempDir(); 993 ffdcFile.fd = writeFileAndGetFD(dir, data); 994 995 PelFFDC ffdc; 996 ffdc.push_back(std::move(ffdcFile)); 997 998 AdditionalData ad; 999 NiceMock<MockDataInterface> dataIface; 1000 1001 EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0)) 1002 .Times(1) 1003 .WillOnce(Return("UXXX-P0-C1")); 1004 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false)) 1005 .Times(1) 1006 .WillOnce(Return("/inv/system/chassis/motherboard/bmc")); 1007 EXPECT_CALL(dataIface, getHWCalloutFields( 1008 "/inv/system/chassis/motherboard/bmc", _, _, _)) 1009 .Times(1) 1010 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"), 1011 SetArgReferee<3>("123456789ABC"))); 1012 1013 std::vector<std::string> dumpType{"bmc/entry", "resource/entry", 1014 "system/entry"}; 1015 EXPECT_CALL(dataIface, checkDumpStatus(dumpType)) 1016 .WillOnce(Return(std::vector<bool>{false, false, false})); 1017 1018 message::Entry regEntry; 1019 regEntry.name = "test"; 1020 regEntry.subsystem = 5; 1021 regEntry.actionFlags = 0xC000; 1022 regEntry.src.type = 0xBD; 1023 regEntry.src.reasonCode = 0x1234; 1024 1025 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error, 1026 ad, ffdc, dataIface}; 1027 1028 ASSERT_TRUE(pel.valid()); 1029 ASSERT_TRUE(pel.primarySRC().value()->callouts()); 1030 const auto& callouts = pel.primarySRC().value()->callouts()->callouts(); 1031 ASSERT_EQ(callouts.size(), 2); 1032 ASSERT_TRUE(pel.isHwCalloutPresent()); 1033 1034 { 1035 EXPECT_EQ(callouts[0]->priority(), 'H'); 1036 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1"); 1037 1038 auto& fru = callouts[0]->fruIdentity(); 1039 EXPECT_EQ(fru->getPN().value(), "1234567"); 1040 EXPECT_EQ(fru->getCCIN().value(), "CCCC"); 1041 EXPECT_EQ(fru->getSN().value(), "123456789ABC"); 1042 EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU); 1043 } 1044 { 1045 EXPECT_EQ(callouts[1]->priority(), 'M'); 1046 EXPECT_EQ(callouts[1]->locationCode(), ""); 1047 1048 auto& fru = callouts[1]->fruIdentity(); 1049 EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU"); 1050 EXPECT_EQ(fru->failingComponentType(), 1051 src::FRUIdentity::maintenanceProc); 1052 } 1053 fs::remove_all(dir); 1054 } 1055 1056 // Test PELs with symblic FRU callout. 1057 TEST_F(PELTest, CreateWithJSONSymblicCalloutTest) 1058 { 1059 PelFFDCfile ffdcFile; 1060 ffdcFile.format = UserDataFormat::json; 1061 ffdcFile.subType = 0xCA; // Callout JSON 1062 ffdcFile.version = 1; 1063 1064 // Write these callouts to a JSON file and pass it into 1065 // the PEL as an FFDC file. 1066 auto inputJSON = R"([ 1067 { 1068 "Priority": "M", 1069 "Procedure": "SVCDOCS" 1070 } 1071 ])"_json; 1072 1073 auto s = inputJSON.dump(); 1074 std::vector<uint8_t> data{s.begin(), s.end()}; 1075 auto dir = makeTempDir(); 1076 ffdcFile.fd = writeFileAndGetFD(dir, data); 1077 1078 PelFFDC ffdc; 1079 ffdc.push_back(std::move(ffdcFile)); 1080 1081 AdditionalData ad; 1082 NiceMock<MockDataInterface> dataIface; 1083 1084 std::vector<std::string> dumpType{"bmc/entry", "resource/entry", 1085 "system/entry"}; 1086 EXPECT_CALL(dataIface, checkDumpStatus(dumpType)) 1087 .WillRepeatedly(Return(std::vector<bool>{false, false, false})); 1088 1089 message::Entry regEntry; 1090 regEntry.name = "test"; 1091 regEntry.subsystem = 5; 1092 regEntry.actionFlags = 0xC000; 1093 regEntry.src.type = 0xBD; 1094 regEntry.src.reasonCode = 0x1234; 1095 1096 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error, 1097 ad, ffdc, dataIface}; 1098 1099 ASSERT_TRUE(pel.valid()); 1100 ASSERT_TRUE(pel.primarySRC().value()->callouts()); 1101 const auto& callouts = pel.primarySRC().value()->callouts()->callouts(); 1102 ASSERT_EQ(callouts.size(), 1); 1103 ASSERT_FALSE(pel.isHwCalloutPresent()); 1104 1105 { 1106 EXPECT_EQ(callouts[0]->priority(), 'M'); 1107 EXPECT_EQ(callouts[0]->locationCode(), ""); 1108 1109 auto& fru = callouts[0]->fruIdentity(); 1110 EXPECT_EQ(fru->getMaintProc().value(), "SVCDOCS"); 1111 } 1112 fs::remove_all(dir); 1113 } 1114