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