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