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 NiceMock<MockJournal> journal; 168 PelFFDC ffdc; 169 170 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 171 ad, ffdc, dataIface, journal}; 172 173 EXPECT_TRUE(pel.valid()); 174 EXPECT_EQ(pel.privateHeader().obmcLogID(), 42); 175 EXPECT_EQ(pel.userHeader().severity(), 0x40); 176 177 EXPECT_EQ(pel.primarySRC().value()->asciiString(), 178 "BD051234 "); 179 180 // Check that certain optional sections have been created 181 size_t mtmsCount = 0; 182 size_t euhCount = 0; 183 size_t udCount = 0; 184 185 for (const auto& section : pel.optionalSections()) 186 { 187 if (section->header().id == 188 static_cast<uint16_t>(SectionID::failingMTMS)) 189 { 190 mtmsCount++; 191 } 192 else if (section->header().id == 193 static_cast<uint16_t>(SectionID::extendedUserHeader)) 194 { 195 euhCount++; 196 } 197 else if (section->header().id == 198 static_cast<uint16_t>(SectionID::userData)) 199 { 200 udCount++; 201 } 202 } 203 204 EXPECT_EQ(mtmsCount, 1); 205 EXPECT_EQ(euhCount, 1); 206 EXPECT_EQ(udCount, 2); // AD section and sysInfo section 207 ASSERT_FALSE(pel.isHwCalloutPresent()); 208 209 { 210 // The same thing, but without the action flags specified 211 // in the registry, so the constructor should set them. 212 regEntry.actionFlags = std::nullopt; 213 214 PEL pel2{regEntry, 42, 215 timestamp, phosphor::logging::Entry::Level::Error, 216 ad, ffdc, 217 dataIface, journal}; 218 219 EXPECT_EQ(pel2.userHeader().actionFlags(), 0xA800); 220 } 221 } 222 223 // Test that when the AdditionalData size is over 16KB that 224 // the PEL that's created is exactly 16KB since the UserData 225 // section that contains all that data was pruned. 226 TEST_F(PELTest, CreateTooBigADTest) 227 { 228 message::Entry regEntry; 229 uint64_t timestamp = 5; 230 231 regEntry.name = "test"; 232 regEntry.subsystem = 5; 233 regEntry.actionFlags = 0xC000; 234 regEntry.src.type = 0xBD; 235 regEntry.src.reasonCode = 0x1234; 236 PelFFDC ffdc; 237 238 // Over the 16KB max PEL size 239 std::string bigAD{"KEY1="}; 240 bigAD += std::string(17000, 'G'); 241 242 std::vector<std::string> data{bigAD}; 243 AdditionalData ad{data}; 244 NiceMock<MockDataInterface> dataIface; 245 NiceMock<MockJournal> journal; 246 247 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 248 ad, ffdc, dataIface, journal}; 249 250 EXPECT_TRUE(pel.valid()); 251 EXPECT_EQ(pel.size(), 16384); 252 253 // Make sure that there are still 2 UD sections. 254 const auto& optSections = pel.optionalSections(); 255 auto udCount = std::count_if( 256 optSections.begin(), optSections.end(), [](const auto& section) { 257 return section->header().id == 258 static_cast<uint16_t>(SectionID::userData); 259 }); 260 261 EXPECT_EQ(udCount, 2); // AD section and sysInfo section 262 } 263 264 // Test that we'll create Generic optional sections for sections that 265 // there aren't explicit classes for. 266 TEST_F(PELTest, GenericSectionTest) 267 { 268 auto data = pelDataFactory(TestPELType::pelSimple); 269 270 std::vector<uint8_t> section1{ 271 0x58, 0x58, // ID 'XX' 272 0x00, 0x18, // Size 273 0x01, 0x02, // version, subtype 274 0x03, 0x04, // comp ID 275 276 // some data 277 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F, 278 0x09, 0x22, 0x3A, 0x00}; 279 280 std::vector<uint8_t> section2{ 281 0x59, 0x59, // ID 'YY' 282 0x00, 0x20, // Size 283 0x01, 0x02, // version, subtype 284 0x03, 0x04, // comp ID 285 286 // some data 287 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F, 288 0x09, 0x22, 0x3A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}; 289 290 // Add the new sections at the end 291 data.insert(data.end(), section1.begin(), section1.end()); 292 data.insert(data.end(), section2.begin(), section2.end()); 293 294 // Increment the section count 295 data.at(27) += 2; 296 auto origData = data; 297 298 PEL pel{data}; 299 300 const auto& sections = pel.optionalSections(); 301 302 bool foundXX = false; 303 bool foundYY = false; 304 305 // Check that we can find these 2 Generic sections 306 for (const auto& section : sections) 307 { 308 if (section->header().id == 0x5858) 309 { 310 foundXX = true; 311 EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr); 312 } 313 else if (section->header().id == 0x5959) 314 { 315 foundYY = true; 316 EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr); 317 } 318 } 319 320 EXPECT_TRUE(foundXX); 321 EXPECT_TRUE(foundYY); 322 323 // Now flatten and check 324 auto newData = pel.data(); 325 326 EXPECT_EQ(origData, newData); 327 } 328 329 // Test that an invalid section will still get a Generic object 330 TEST_F(PELTest, InvalidGenericTest) 331 { 332 auto data = pelDataFactory(TestPELType::pelSimple); 333 334 // Not a valid section 335 std::vector<uint8_t> section1{0x01, 0x02, 0x03}; 336 337 data.insert(data.end(), section1.begin(), section1.end()); 338 339 // Increment the section count 340 data.at(27) += 1; 341 342 PEL pel{data}; 343 EXPECT_FALSE(pel.valid()); 344 345 const auto& sections = pel.optionalSections(); 346 347 bool foundGeneric = false; 348 for (const auto& section : sections) 349 { 350 if (dynamic_cast<Generic*>(section.get()) != nullptr) 351 { 352 foundGeneric = true; 353 EXPECT_EQ(section->valid(), false); 354 break; 355 } 356 } 357 358 EXPECT_TRUE(foundGeneric); 359 } 360 361 // Create a UserData section out of AdditionalData 362 TEST_F(PELTest, MakeUDSectionTest) 363 { 364 std::vector<std::string> ad{"KEY1=VALUE1", "KEY2=VALUE2", "KEY3=VALUE3", 365 "ESEL=TEST"}; 366 AdditionalData additionalData{ad}; 367 368 auto ud = util::makeADUserDataSection(additionalData); 369 370 EXPECT_TRUE(ud->valid()); 371 EXPECT_EQ(ud->header().id, 0x5544); 372 EXPECT_EQ(ud->header().version, 0x01); 373 EXPECT_EQ(ud->header().subType, 0x01); 374 EXPECT_EQ(ud->header().componentID, 0x2000); 375 376 const auto& d = ud->data(); 377 378 std::string jsonString{d.begin(), d.end()}; 379 380 std::string expectedJSON = 381 R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":"VALUE3"})"; 382 383 // The actual data is null padded to a 4B boundary. 384 std::vector<uint8_t> expectedData; 385 expectedData.resize(52, '\0'); 386 memcpy(expectedData.data(), expectedJSON.data(), expectedJSON.size()); 387 388 EXPECT_EQ(d, expectedData); 389 390 // Ensure we can read this as JSON 391 auto newJSON = nlohmann::json::parse(jsonString); 392 EXPECT_EQ(newJSON["KEY1"], "VALUE1"); 393 EXPECT_EQ(newJSON["KEY2"], "VALUE2"); 394 EXPECT_EQ(newJSON["KEY3"], "VALUE3"); 395 } 396 397 // Create the UserData section that contains system info 398 TEST_F(PELTest, SysInfoSectionTest) 399 { 400 MockDataInterface dataIface; 401 402 EXPECT_CALL(dataIface, getBMCFWVersionID()).WillOnce(Return("ABCD1234")); 403 EXPECT_CALL(dataIface, getBMCState()).WillOnce(Return("State.Ready")); 404 EXPECT_CALL(dataIface, getChassisState()).WillOnce(Return("State.On")); 405 EXPECT_CALL(dataIface, getHostState()).WillOnce(Return("State.Off")); 406 EXPECT_CALL(dataIface, getBootState()) 407 .WillOnce(Return("State.SystemInitComplete")); 408 EXPECT_CALL(dataIface, getSystemIMKeyword()) 409 .WillOnce(Return(std::vector<uint8_t>{0, 1, 0x55, 0xAA})); 410 411 std::string pid = "_PID=" + std::to_string(getpid()); 412 std::vector<std::string> ad{pid}; 413 AdditionalData additionalData{ad}; 414 415 auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface); 416 417 EXPECT_TRUE(ud->valid()); 418 EXPECT_EQ(ud->header().id, 0x5544); 419 EXPECT_EQ(ud->header().version, 0x01); 420 EXPECT_EQ(ud->header().subType, 0x01); 421 EXPECT_EQ(ud->header().componentID, 0x2000); 422 423 // Pull out the JSON data and check it. 424 const auto& d = ud->data(); 425 std::string jsonString{d.begin(), d.end()}; 426 auto json = nlohmann::json::parse(jsonString); 427 428 // Ensure the 'Process Name' entry contains the name of this test 429 // executable. 430 auto name = json["Process Name"].get<std::string>(); 431 auto found = (name.find("pel_test") != std::string::npos) || 432 (name.find("test-openpower-pels-pel") != std::string::npos); 433 EXPECT_TRUE(found); 434 // @TODO(stwcx): remove 'pel_test' when removing autotools. 435 436 auto version = json["FW Version ID"].get<std::string>(); 437 EXPECT_EQ(version, "ABCD1234"); 438 439 auto state = json["BMCState"].get<std::string>(); 440 EXPECT_EQ(state, "Ready"); 441 442 state = json["ChassisState"].get<std::string>(); 443 EXPECT_EQ(state, "On"); 444 445 state = json["HostState"].get<std::string>(); 446 EXPECT_EQ(state, "Off"); 447 448 state = json["BootState"].get<std::string>(); 449 EXPECT_EQ(state, "SystemInitComplete"); 450 451 auto keyword = json["System IM"].get<std::string>(); 452 EXPECT_EQ(keyword, "000155AA"); 453 } 454 455 // Test that the sections that override 456 // virtual std::optional<std::string> Section::getJSON() const 457 // return valid JSON. 458 TEST_F(PELTest, SectionJSONTest) 459 { 460 auto data = pelDataFactory(TestPELType::pelSimple); 461 PEL pel{data}; 462 463 // Check that all JSON returned from the sections is 464 // parseable by nlohmann::json, which will throw an 465 // exception and fail the test if there is a problem. 466 467 // The getJSON() response needs to be wrapped in a { } to make 468 // actual valid JSON (PEL::toJSON() usually handles that). 469 470 auto jsonString = pel.privateHeader().getJSON('O'); 471 472 // PrivateHeader always prints JSON 473 ASSERT_TRUE(jsonString); 474 *jsonString = '{' + *jsonString + '}'; 475 auto json = nlohmann::json::parse(*jsonString); 476 477 jsonString = pel.userHeader().getJSON('O'); 478 479 // UserHeader always prints JSON 480 ASSERT_TRUE(jsonString); 481 *jsonString = '{' + *jsonString + '}'; 482 json = nlohmann::json::parse(*jsonString); 483 484 for (const auto& section : pel.optionalSections()) 485 { 486 // The optional sections may or may not have implemented getJSON(). 487 jsonString = section->getJSON('O'); 488 if (jsonString) 489 { 490 *jsonString = '{' + *jsonString + '}'; 491 auto json = nlohmann::json::parse(*jsonString); 492 } 493 } 494 } 495 496 PelFFDCfile getJSONFFDC(const fs::path& dir) 497 { 498 PelFFDCfile ffdc; 499 ffdc.format = UserDataFormat::json; 500 ffdc.subType = 5; 501 ffdc.version = 42; 502 503 auto inputJSON = R"({ 504 "key1": "value1", 505 "key2": 42, 506 "key3" : [1, 2, 3, 4, 5], 507 "key4": {"key5": "value5"} 508 })"_json; 509 510 // Write the JSON to a file and get its descriptor. 511 auto s = inputJSON.dump(); 512 std::vector<uint8_t> data{s.begin(), s.end()}; 513 ffdc.fd = writeFileAndGetFD(dir, data); 514 515 return ffdc; 516 } 517 518 TEST_F(PELTest, MakeJSONFileUDSectionTest) 519 { 520 auto dir = makeTempDir(); 521 522 { 523 auto ffdc = getJSONFFDC(dir); 524 525 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 526 close(ffdc.fd); 527 ASSERT_TRUE(ud); 528 ASSERT_TRUE(ud->valid()); 529 EXPECT_EQ(ud->header().id, 0x5544); 530 531 EXPECT_EQ(ud->header().version, 532 static_cast<uint8_t>(UserDataFormatVersion::json)); 533 EXPECT_EQ(ud->header().subType, 534 static_cast<uint8_t>(UserDataFormat::json)); 535 EXPECT_EQ(ud->header().componentID, 536 static_cast<uint16_t>(ComponentID::phosphorLogging)); 537 538 // Pull the JSON back out of the the UserData section 539 const auto& d = ud->data(); 540 std::string js{d.begin(), d.end()}; 541 auto json = nlohmann::json::parse(js); 542 543 EXPECT_EQ("value1", json["key1"].get<std::string>()); 544 EXPECT_EQ(42, json["key2"].get<int>()); 545 546 std::vector<int> key3Values{1, 2, 3, 4, 5}; 547 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>()); 548 549 std::map<std::string, std::string> key4Values{{"key5", "value5"}}; 550 auto actual = json["key4"].get<std::map<std::string, std::string>>(); 551 EXPECT_EQ(key4Values, actual); 552 } 553 554 { 555 // A bad FD 556 PelFFDCfile ffdc; 557 ffdc.format = UserDataFormat::json; 558 ffdc.subType = 5; 559 ffdc.version = 42; 560 ffdc.fd = 10000; 561 562 // The section shouldn't get made 563 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 564 ASSERT_FALSE(ud); 565 } 566 567 fs::remove_all(dir); 568 } 569 570 PelFFDCfile getCBORFFDC(const fs::path& dir) 571 { 572 PelFFDCfile ffdc; 573 ffdc.format = UserDataFormat::cbor; 574 ffdc.subType = 5; 575 ffdc.version = 42; 576 577 auto inputJSON = R"({ 578 "key1": "value1", 579 "key2": 42, 580 "key3" : [1, 2, 3, 4, 5], 581 "key4": {"key5": "value5"} 582 })"_json; 583 584 // Convert the JSON to CBOR and write it to a file 585 auto data = nlohmann::json::to_cbor(inputJSON); 586 ffdc.fd = writeFileAndGetFD(dir, data); 587 588 return ffdc; 589 } 590 591 TEST_F(PELTest, MakeCBORFileUDSectionTest) 592 { 593 auto dir = makeTempDir(); 594 595 auto ffdc = getCBORFFDC(dir); 596 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 597 close(ffdc.fd); 598 ASSERT_TRUE(ud); 599 ASSERT_TRUE(ud->valid()); 600 EXPECT_EQ(ud->header().id, 0x5544); 601 602 EXPECT_EQ(ud->header().version, 603 static_cast<uint8_t>(UserDataFormatVersion::cbor)); 604 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::cbor)); 605 EXPECT_EQ(ud->header().componentID, 606 static_cast<uint16_t>(ComponentID::phosphorLogging)); 607 608 // Pull the CBOR back out of the PEL section 609 // The number of pad bytes to make the section be 4B aligned 610 // was added at the end, read it and then remove it and the 611 // padding before parsing it. 612 auto data = ud->data(); 613 Stream stream{data}; 614 stream.offset(data.size() - 4); 615 uint32_t pad; 616 stream >> pad; 617 618 data.resize(data.size() - 4 - pad); 619 620 auto json = nlohmann::json::from_cbor(data); 621 622 EXPECT_EQ("value1", json["key1"].get<std::string>()); 623 EXPECT_EQ(42, json["key2"].get<int>()); 624 625 std::vector<int> key3Values{1, 2, 3, 4, 5}; 626 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>()); 627 628 std::map<std::string, std::string> key4Values{{"key5", "value5"}}; 629 auto actual = json["key4"].get<std::map<std::string, std::string>>(); 630 EXPECT_EQ(key4Values, actual); 631 632 fs::remove_all(dir); 633 } 634 635 PelFFDCfile getTextFFDC(const fs::path& dir) 636 { 637 PelFFDCfile ffdc; 638 ffdc.format = UserDataFormat::text; 639 ffdc.subType = 5; 640 ffdc.version = 42; 641 642 std::string text{"this is some text that will be used for FFDC"}; 643 std::vector<uint8_t> data{text.begin(), text.end()}; 644 645 ffdc.fd = writeFileAndGetFD(dir, data); 646 647 return ffdc; 648 } 649 650 TEST_F(PELTest, MakeTextFileUDSectionTest) 651 { 652 auto dir = makeTempDir(); 653 654 auto ffdc = getTextFFDC(dir); 655 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 656 close(ffdc.fd); 657 ASSERT_TRUE(ud); 658 ASSERT_TRUE(ud->valid()); 659 EXPECT_EQ(ud->header().id, 0x5544); 660 661 EXPECT_EQ(ud->header().version, 662 static_cast<uint8_t>(UserDataFormatVersion::text)); 663 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::text)); 664 EXPECT_EQ(ud->header().componentID, 665 static_cast<uint16_t>(ComponentID::phosphorLogging)); 666 667 // Get the text back out 668 std::string text{ud->data().begin(), ud->data().end()}; 669 EXPECT_EQ(text, "this is some text that will be used for FFDC"); 670 671 fs::remove_all(dir); 672 } 673 674 PelFFDCfile getCustomFFDC(const fs::path& dir, const std::vector<uint8_t>& data) 675 { 676 PelFFDCfile ffdc; 677 ffdc.format = UserDataFormat::custom; 678 ffdc.subType = 5; 679 ffdc.version = 42; 680 681 ffdc.fd = writeFileAndGetFD(dir, data); 682 683 return ffdc; 684 } 685 686 TEST_F(PELTest, MakeCustomFileUDSectionTest) 687 { 688 auto dir = makeTempDir(); 689 690 { 691 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8}; 692 693 auto ffdc = getCustomFFDC(dir, data); 694 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 695 close(ffdc.fd); 696 ASSERT_TRUE(ud); 697 ASSERT_TRUE(ud->valid()); 698 EXPECT_EQ(ud->header().size, 8 + 8); // data size + header size 699 EXPECT_EQ(ud->header().id, 0x5544); 700 701 EXPECT_EQ(ud->header().version, 42); 702 EXPECT_EQ(ud->header().subType, 5); 703 EXPECT_EQ(ud->header().componentID, 0x2002); 704 705 // Get the data back out 706 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()}; 707 EXPECT_EQ(data, newData); 708 } 709 710 // Do the same thing again, but make it be non 4B aligned 711 // so the data gets padded. 712 { 713 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8, 9}; 714 715 auto ffdc = getCustomFFDC(dir, data); 716 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 717 close(ffdc.fd); 718 ASSERT_TRUE(ud); 719 ASSERT_TRUE(ud->valid()); 720 EXPECT_EQ(ud->header().size, 12 + 8); // data size + header size 721 EXPECT_EQ(ud->header().id, 0x5544); 722 723 EXPECT_EQ(ud->header().version, 42); 724 EXPECT_EQ(ud->header().subType, 5); 725 EXPECT_EQ(ud->header().componentID, 0x2002); 726 727 // Get the data back out 728 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()}; 729 730 // pad the original to 12B so we can compare 731 data.push_back(0); 732 data.push_back(0); 733 data.push_back(0); 734 735 EXPECT_EQ(data, newData); 736 } 737 738 fs::remove_all(dir); 739 } 740 741 // Test Adding FFDC from files to a PEL 742 TEST_F(PELTest, CreateWithFFDCTest) 743 { 744 auto dir = makeTempDir(); 745 message::Entry regEntry; 746 uint64_t timestamp = 5; 747 748 regEntry.name = "test"; 749 regEntry.subsystem = 5; 750 regEntry.actionFlags = 0xC000; 751 regEntry.src.type = 0xBD; 752 regEntry.src.reasonCode = 0x1234; 753 754 std::vector<std::string> additionalData{"KEY1=VALUE1"}; 755 AdditionalData ad{additionalData}; 756 NiceMock<MockDataInterface> dataIface; 757 NiceMock<MockJournal> journal; 758 PelFFDC ffdc; 759 760 std::vector<uint8_t> customData{1, 2, 3, 4, 5, 6, 7, 8}; 761 762 // This will be trimmed when added 763 std::vector<uint8_t> hugeCustomData(17000, 0x42); 764 765 ffdc.emplace_back(std::move(getJSONFFDC(dir))); 766 ffdc.emplace_back(std::move(getCBORFFDC(dir))); 767 ffdc.emplace_back(std::move(getTextFFDC(dir))); 768 ffdc.emplace_back(std::move(getCustomFFDC(dir, customData))); 769 ffdc.emplace_back(std::move(getCustomFFDC(dir, hugeCustomData))); 770 771 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 772 ad, ffdc, dataIface, journal}; 773 774 EXPECT_TRUE(pel.valid()); 775 776 // Clipped to the max 777 EXPECT_EQ(pel.size(), 16384); 778 779 // Check for the FFDC sections 780 size_t udCount = 0; 781 Section* ud = nullptr; 782 783 for (const auto& section : pel.optionalSections()) 784 { 785 if (section->header().id == static_cast<uint16_t>(SectionID::userData)) 786 { 787 udCount++; 788 ud = section.get(); 789 } 790 } 791 792 EXPECT_EQ(udCount, 7); // AD section, sysInfo, 5 ffdc sections 793 794 // Check the last section was trimmed to 795 // something a bit less that 17000. 796 EXPECT_GT(ud->header().size, 14000); 797 EXPECT_LT(ud->header().size, 16000); 798 799 fs::remove_all(dir); 800 } 801 802 // Create a PEL with device callouts 803 TEST_F(PELTest, CreateWithDevCalloutsTest) 804 { 805 message::Entry regEntry; 806 uint64_t timestamp = 5; 807 808 regEntry.name = "test"; 809 regEntry.subsystem = 5; 810 regEntry.actionFlags = 0xC000; 811 regEntry.src.type = 0xBD; 812 regEntry.src.reasonCode = 0x1234; 813 814 NiceMock<MockDataInterface> dataIface; 815 NiceMock<MockJournal> journal; 816 PelFFDC ffdc; 817 818 const auto calloutJSON = R"( 819 { 820 "I2C": 821 { 822 "14": 823 { 824 "114": 825 { 826 "Callouts":[ 827 { 828 "Name": "/chassis/motherboard/cpu0", 829 "LocationCode": "P1", 830 "Priority": "H" 831 } 832 ], 833 "Dest": "proc 0 target" 834 } 835 } 836 } 837 })"; 838 839 std::vector<std::string> names{"systemA"}; 840 EXPECT_CALL(dataIface, getSystemNames) 841 .Times(2) 842 .WillRepeatedly(Return(names)); 843 844 EXPECT_CALL(dataIface, expandLocationCode("P1", 0)) 845 .Times(1) 846 .WillOnce(Return("UXXX-P1")); 847 848 EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false)) 849 .WillOnce(Return(std::vector<std::string>{ 850 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"})); 851 852 EXPECT_CALL(dataIface, 853 getHWCalloutFields( 854 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", 855 _, _, _)) 856 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"), 857 SetArgReferee<3>("123456789ABC"))); 858 859 auto dataPath = getPELReadOnlyDataPath(); 860 std::ofstream file{dataPath / "systemA_dev_callouts.json"}; 861 file << calloutJSON; 862 file.close(); 863 864 { 865 std::vector<std::string> data{ 866 "CALLOUT_ERRNO=5", 867 "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/" 868 "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"}; 869 870 AdditionalData ad{data}; 871 872 PEL pel{regEntry, 42, 873 timestamp, phosphor::logging::Entry::Level::Error, 874 ad, ffdc, 875 dataIface, journal}; 876 877 ASSERT_TRUE(pel.primarySRC().value()->callouts()); 878 auto& callouts = pel.primarySRC().value()->callouts()->callouts(); 879 ASSERT_EQ(callouts.size(), 1); 880 ASSERT_TRUE(pel.isHwCalloutPresent()); 881 882 EXPECT_EQ(callouts[0]->priority(), 'H'); 883 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P1"); 884 885 auto& fru = callouts[0]->fruIdentity(); 886 EXPECT_EQ(fru->getPN().value(), "1234567"); 887 EXPECT_EQ(fru->getCCIN().value(), "CCCC"); 888 EXPECT_EQ(fru->getSN().value(), "123456789ABC"); 889 890 const auto& section = pel.optionalSections().back(); 891 892 ASSERT_EQ(section->header().id, 0x5544); // UD 893 auto ud = static_cast<UserData*>(section.get()); 894 895 // Check that there was a UserData section added that 896 // contains debug details about the device. 897 const auto& d = ud->data(); 898 std::string jsonString{d.begin(), d.end()}; 899 auto actualJSON = nlohmann::json::parse(jsonString); 900 901 auto expectedJSON = R"( 902 { 903 "PEL Internal Debug Data": { 904 "SRC": [ 905 "I2C: bus: 14 address: 114 dest: proc 0 target" 906 ] 907 } 908 } 909 )"_json; 910 911 EXPECT_EQ(actualJSON, expectedJSON); 912 } 913 914 { 915 // Device path not found (wrong i2c addr), so no callouts 916 std::vector<std::string> data{ 917 "CALLOUT_ERRNO=5", 918 "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/" 919 "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0099"}; 920 921 AdditionalData ad{data}; 922 923 PEL pel{regEntry, 42, 924 timestamp, phosphor::logging::Entry::Level::Error, 925 ad, ffdc, 926 dataIface, journal}; 927 928 // no callouts 929 EXPECT_FALSE(pel.primarySRC().value()->callouts()); 930 931 // Now check that there was a UserData section 932 // that contains the lookup error. 933 const auto& section = pel.optionalSections().back(); 934 935 ASSERT_EQ(section->header().id, 0x5544); // UD 936 auto ud = static_cast<UserData*>(section.get()); 937 938 const auto& d = ud->data(); 939 940 std::string jsonString{d.begin(), d.end()}; 941 942 auto actualJSON = nlohmann::json::parse(jsonString); 943 944 auto expectedJSON = 945 "{\"PEL Internal Debug Data\":{\"SRC\":" 946 "[\"Problem looking up I2C callouts on 14 153: " 947 "[json.exception.out_of_range.403] key '153' not found\"]}}"_json; 948 949 EXPECT_EQ(actualJSON, expectedJSON); 950 } 951 952 fs::remove_all(dataPath); 953 } 954 955 // Test PELs when the callouts are passed in using a JSON file. 956 TEST_F(PELTest, CreateWithJSONCalloutsTest) 957 { 958 PelFFDCfile ffdcFile; 959 ffdcFile.format = UserDataFormat::json; 960 ffdcFile.subType = 0xCA; // Callout JSON 961 ffdcFile.version = 1; 962 963 // Write these callouts to a JSON file and pass it into 964 // the PEL as an FFDC file. Also has a duplicate that 965 // will be removed. 966 auto inputJSON = R"([ 967 { 968 "Priority": "H", 969 "LocationCode": "P0-C1" 970 }, 971 { 972 "Priority": "M", 973 "Procedure": "PROCEDURE" 974 }, 975 { 976 "Priority": "L", 977 "Procedure": "PROCEDURE" 978 } 979 ])"_json; 980 981 auto s = inputJSON.dump(); 982 std::vector<uint8_t> data{s.begin(), s.end()}; 983 auto dir = makeTempDir(); 984 ffdcFile.fd = writeFileAndGetFD(dir, data); 985 986 PelFFDC ffdc; 987 ffdc.push_back(std::move(ffdcFile)); 988 989 AdditionalData ad; 990 NiceMock<MockDataInterface> dataIface; 991 NiceMock<MockJournal> journal; 992 993 EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0)) 994 .Times(1) 995 .WillOnce(Return("UXXX-P0-C1")); 996 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false)) 997 .Times(1) 998 .WillOnce(Return( 999 std::vector<std::string>{"/inv/system/chassis/motherboard/bmc"})); 1000 EXPECT_CALL(dataIface, getHWCalloutFields( 1001 "/inv/system/chassis/motherboard/bmc", _, _, _)) 1002 .Times(1) 1003 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"), 1004 SetArgReferee<3>("123456789ABC"))); 1005 1006 message::Entry regEntry; 1007 regEntry.name = "test"; 1008 regEntry.subsystem = 5; 1009 regEntry.actionFlags = 0xC000; 1010 regEntry.src.type = 0xBD; 1011 regEntry.src.reasonCode = 0x1234; 1012 1013 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error, 1014 ad, ffdc, dataIface, journal}; 1015 1016 ASSERT_TRUE(pel.valid()); 1017 ASSERT_TRUE(pel.primarySRC().value()->callouts()); 1018 const auto& callouts = pel.primarySRC().value()->callouts()->callouts(); 1019 ASSERT_EQ(callouts.size(), 2); 1020 ASSERT_TRUE(pel.isHwCalloutPresent()); 1021 1022 { 1023 EXPECT_EQ(callouts[0]->priority(), 'H'); 1024 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1"); 1025 1026 auto& fru = callouts[0]->fruIdentity(); 1027 EXPECT_EQ(fru->getPN().value(), "1234567"); 1028 EXPECT_EQ(fru->getCCIN().value(), "CCCC"); 1029 EXPECT_EQ(fru->getSN().value(), "123456789ABC"); 1030 EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU); 1031 } 1032 { 1033 EXPECT_EQ(callouts[1]->priority(), 'M'); 1034 EXPECT_EQ(callouts[1]->locationCode(), ""); 1035 1036 auto& fru = callouts[1]->fruIdentity(); 1037 EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU"); 1038 EXPECT_EQ(fru->failingComponentType(), 1039 src::FRUIdentity::maintenanceProc); 1040 } 1041 fs::remove_all(dir); 1042 } 1043 1044 // Test PELs with symblic FRU callout. 1045 TEST_F(PELTest, CreateWithJSONSymblicCalloutTest) 1046 { 1047 PelFFDCfile ffdcFile; 1048 ffdcFile.format = UserDataFormat::json; 1049 ffdcFile.subType = 0xCA; // Callout JSON 1050 ffdcFile.version = 1; 1051 1052 // Write these callouts to a JSON file and pass it into 1053 // the PEL as an FFDC file. 1054 auto inputJSON = R"([ 1055 { 1056 "Priority": "M", 1057 "Procedure": "SVCDOCS" 1058 } 1059 ])"_json; 1060 1061 auto s = inputJSON.dump(); 1062 std::vector<uint8_t> data{s.begin(), s.end()}; 1063 auto dir = makeTempDir(); 1064 ffdcFile.fd = writeFileAndGetFD(dir, data); 1065 1066 PelFFDC ffdc; 1067 ffdc.push_back(std::move(ffdcFile)); 1068 1069 AdditionalData ad; 1070 NiceMock<MockDataInterface> dataIface; 1071 NiceMock<MockJournal> journal; 1072 1073 message::Entry regEntry; 1074 regEntry.name = "test"; 1075 regEntry.subsystem = 5; 1076 regEntry.actionFlags = 0xC000; 1077 regEntry.src.type = 0xBD; 1078 regEntry.src.reasonCode = 0x1234; 1079 1080 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error, 1081 ad, ffdc, dataIface, journal}; 1082 1083 ASSERT_TRUE(pel.valid()); 1084 ASSERT_TRUE(pel.primarySRC().value()->callouts()); 1085 const auto& callouts = pel.primarySRC().value()->callouts()->callouts(); 1086 ASSERT_EQ(callouts.size(), 1); 1087 ASSERT_FALSE(pel.isHwCalloutPresent()); 1088 1089 { 1090 EXPECT_EQ(callouts[0]->priority(), 'M'); 1091 EXPECT_EQ(callouts[0]->locationCode(), ""); 1092 1093 auto& fru = callouts[0]->fruIdentity(); 1094 EXPECT_EQ(fru->getMaintProc().value(), "SVCDOCS"); 1095 } 1096 fs::remove_all(dir); 1097 } 1098 1099 TEST_F(PELTest, FlattenLinesTest) 1100 { 1101 std::vector<std::string> msgs{"test1 test2", "test3 test4", "test5 test6"}; 1102 1103 auto buffer = util::flattenLines(msgs); 1104 1105 std::string string{"test1 test2\ntest3 test4\ntest5 test6\n"}; 1106 std::vector<uint8_t> expected(string.begin(), string.end()); 1107 1108 EXPECT_EQ(buffer, expected); 1109 } 1110 1111 void checkJournalSection(const std::unique_ptr<Section>& section, 1112 const std::string& expected) 1113 { 1114 ASSERT_EQ(SectionID::userData, 1115 static_cast<SectionID>(section->header().id)); 1116 ASSERT_EQ(UserDataFormat::text, 1117 static_cast<UserDataFormat>(section->header().subType)); 1118 ASSERT_EQ(section->header().version, 1119 static_cast<uint8_t>(UserDataFormatVersion::text)); 1120 1121 auto ud = static_cast<UserData*>(section.get()); 1122 1123 std::vector<uint8_t> expectedData(expected.begin(), expected.end()); 1124 1125 // PEL sections are 4B aligned so add padding before the compare 1126 while (expectedData.size() % 4 != 0) 1127 { 1128 expectedData.push_back('\0'); 1129 } 1130 1131 EXPECT_EQ(ud->data(), expectedData); 1132 } 1133 1134 TEST_F(PELTest, CaptureJournalTest) 1135 { 1136 message::Entry regEntry; 1137 uint64_t timestamp = 5; 1138 1139 regEntry.name = "test"; 1140 regEntry.subsystem = 5; 1141 regEntry.actionFlags = 0xC000; 1142 regEntry.src.type = 0xBD; 1143 regEntry.src.reasonCode = 0x1234; 1144 1145 std::vector<std::string> data; 1146 AdditionalData ad{data}; 1147 NiceMock<MockDataInterface> dataIface; 1148 NiceMock<MockJournal> journal; 1149 PelFFDC ffdc; 1150 1151 size_t pelSectsWithOneUD{0}; 1152 1153 { 1154 // Capture 5 lines from the journal into a single UD section 1155 message::JournalCapture jc = size_t{5}; 1156 regEntry.journalCapture = jc; 1157 1158 std::vector<std::string> msgs{"test1 test2", "test3 test4", 1159 "test5 test6", "4", "5"}; 1160 1161 EXPECT_CALL(journal, getMessages("", 5)).WillOnce(Return(msgs)); 1162 1163 PEL pel{regEntry, 42, 1164 timestamp, phosphor::logging::Entry::Level::Error, 1165 ad, ffdc, 1166 dataIface, journal}; 1167 1168 // Check the generated UserData section 1169 std::string expected{"test1 test2\ntest3 test4\ntest5 test6\n4\n5\n"}; 1170 1171 checkJournalSection(pel.optionalSections().back(), expected); 1172 1173 // Save for upcoming testcases 1174 pelSectsWithOneUD = pel.privateHeader().sectionCount(); 1175 } 1176 1177 { 1178 // Attempt to capture too many journal entries so the 1179 // section gets dropped. 1180 message::JournalCapture jc = size_t{1}; 1181 regEntry.journalCapture = jc; 1182 1183 EXPECT_CALL(journal, sync()).Times(1); 1184 1185 // A 20000 byte line won't fit in a PEL 1186 EXPECT_CALL(journal, getMessages("", 1)) 1187 .WillOnce( 1188 Return(std::vector<std::string>{std::string(20000, 'x')})); 1189 1190 PEL pel{regEntry, 42, 1191 timestamp, phosphor::logging::Entry::Level::Error, 1192 ad, ffdc, 1193 dataIface, journal}; 1194 1195 // Check for 1 fewer sections than in the previous PEL 1196 EXPECT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD - 1); 1197 } 1198 1199 // Capture 3 different journal sections 1200 { 1201 message::AppCaptureList captureList{ 1202 message::AppCapture{"app1", 3}, 1203 message::AppCapture{"app2", 4}, 1204 message::AppCapture{"app3", 1}, 1205 }; 1206 message::JournalCapture jc = captureList; 1207 regEntry.journalCapture = jc; 1208 1209 std::vector<std::string> app1{"A B", "C D", "E F"}; 1210 std::vector<std::string> app2{"1 2", "3 4", "5 6", "7 8"}; 1211 std::vector<std::string> app3{"a b c"}; 1212 1213 std::string expected1{"A B\nC D\nE F\n"}; 1214 std::string expected2{"1 2\n3 4\n5 6\n7 8\n"}; 1215 std::string expected3{"a b c\n"}; 1216 1217 EXPECT_CALL(journal, sync()).Times(1); 1218 EXPECT_CALL(journal, getMessages("app1", 3)).WillOnce(Return(app1)); 1219 EXPECT_CALL(journal, getMessages("app2", 4)).WillOnce(Return(app2)); 1220 EXPECT_CALL(journal, getMessages("app3", 1)).WillOnce(Return(app3)); 1221 1222 PEL pel{regEntry, 42, 1223 timestamp, phosphor::logging::Entry::Level::Error, 1224 ad, ffdc, 1225 dataIface, journal}; 1226 1227 // Two more sections than the 1 extra UD section in the first testcase 1228 ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD + 2); 1229 1230 const auto& optionalSections = pel.optionalSections(); 1231 auto numOptSections = optionalSections.size(); 1232 1233 checkJournalSection(optionalSections[numOptSections - 3], expected1); 1234 checkJournalSection(optionalSections[numOptSections - 2], expected2); 1235 checkJournalSection(optionalSections[numOptSections - 1], expected3); 1236 } 1237 1238 { 1239 // One section gets saved, and one is too big and gets dropped 1240 message::AppCaptureList captureList{ 1241 message::AppCapture{"app4", 2}, 1242 message::AppCapture{"app5", 1}, 1243 }; 1244 message::JournalCapture jc = captureList; 1245 regEntry.journalCapture = jc; 1246 1247 std::vector<std::string> app4{"w x", "y z"}; 1248 std::string expected4{"w x\ny z\n"}; 1249 1250 EXPECT_CALL(journal, sync()).Times(1); 1251 1252 EXPECT_CALL(journal, getMessages("app4", 2)).WillOnce(Return(app4)); 1253 1254 // A 20000 byte line won't fit in a PEL 1255 EXPECT_CALL(journal, getMessages("app5", 1)) 1256 .WillOnce( 1257 Return(std::vector<std::string>{std::string(20000, 'x')})); 1258 1259 PEL pel{regEntry, 42, 1260 timestamp, phosphor::logging::Entry::Level::Error, 1261 ad, ffdc, 1262 dataIface, journal}; 1263 1264 // The last section should have been dropped, so same as first TC 1265 ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD); 1266 1267 checkJournalSection(pel.optionalSections().back(), expected4); 1268 } 1269 } 1270