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 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 171 ad, ffdc, dataIface}; 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.isCalloutPresent()); 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{ 215 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 216 ad, ffdc, dataIface}; 217 218 EXPECT_EQ(pel2.userHeader().actionFlags(), 0xA800); 219 } 220 } 221 222 // Test that when the AdditionalData size is over 16KB that 223 // the PEL that's created is exactly 16KB since the UserData 224 // section that contains all that data was pruned. 225 TEST_F(PELTest, CreateTooBigADTest) 226 { 227 message::Entry regEntry; 228 uint64_t timestamp = 5; 229 230 regEntry.name = "test"; 231 regEntry.subsystem = 5; 232 regEntry.actionFlags = 0xC000; 233 regEntry.src.type = 0xBD; 234 regEntry.src.reasonCode = 0x1234; 235 PelFFDC ffdc; 236 237 // Over the 16KB max PEL size 238 std::string bigAD{"KEY1="}; 239 bigAD += std::string(17000, 'G'); 240 241 std::vector<std::string> data{bigAD}; 242 AdditionalData ad{data}; 243 NiceMock<MockDataInterface> dataIface; 244 245 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 246 ad, ffdc, dataIface}; 247 248 EXPECT_TRUE(pel.valid()); 249 EXPECT_EQ(pel.size(), 16384); 250 251 // Make sure that there are still 2 UD sections. 252 size_t udCount = 0; 253 for (const auto& section : pel.optionalSections()) 254 { 255 if (section->header().id == static_cast<uint16_t>(SectionID::userData)) 256 { 257 udCount++; 258 } 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{0x58, 0x58, // ID 'XX' 271 0x00, 0x18, // Size 272 0x01, 0x02, // version, subtype 273 0x03, 0x04, // comp ID 274 275 // some data 276 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 277 0x20, 0x31, 0x06, 0x0F, 0x09, 0x22, 0x3A, 278 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, getSystemIMKeyword()) 407 .WillOnce(Return(std::vector<uint8_t>{0, 1, 0x55, 0xAA})); 408 409 std::string pid = "_PID=" + std::to_string(getpid()); 410 std::vector<std::string> ad{pid}; 411 AdditionalData additionalData{ad}; 412 413 auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface); 414 415 EXPECT_TRUE(ud->valid()); 416 EXPECT_EQ(ud->header().id, 0x5544); 417 EXPECT_EQ(ud->header().version, 0x01); 418 EXPECT_EQ(ud->header().subType, 0x01); 419 EXPECT_EQ(ud->header().componentID, 0x2000); 420 421 // Pull out the JSON data and check it. 422 const auto& d = ud->data(); 423 std::string jsonString{d.begin(), d.end()}; 424 auto json = nlohmann::json::parse(jsonString); 425 426 // Ensure the 'Process Name' entry contains the name of this test 427 // executable. 428 auto name = json["Process Name"].get<std::string>(); 429 auto found = (name.find("pel_test") != std::string::npos) || 430 (name.find("test-openpower-pels-pel") != std::string::npos); 431 EXPECT_TRUE(found); 432 // @TODO(stwcx): remove 'pel_test' when removing autotools. 433 434 auto version = json["FW Version ID"].get<std::string>(); 435 EXPECT_EQ(version, "ABCD1234"); 436 437 auto state = json["BMCState"].get<std::string>(); 438 EXPECT_EQ(state, "Ready"); 439 440 state = json["ChassisState"].get<std::string>(); 441 EXPECT_EQ(state, "On"); 442 443 state = json["HostState"].get<std::string>(); 444 EXPECT_EQ(state, "Off"); 445 446 auto keyword = json["System IM"].get<std::string>(); 447 EXPECT_EQ(keyword, "000155AA"); 448 } 449 450 // Test that the sections that override 451 // virtual std::optional<std::string> Section::getJSON() const 452 // return valid JSON. 453 TEST_F(PELTest, SectionJSONTest) 454 { 455 auto data = pelDataFactory(TestPELType::pelSimple); 456 PEL pel{data}; 457 458 // Check that all JSON returned from the sections is 459 // parseable by nlohmann::json, which will throw an 460 // exception and fail the test if there is a problem. 461 462 // The getJSON() response needs to be wrapped in a { } to make 463 // actual valid JSON (PEL::toJSON() usually handles that). 464 465 auto jsonString = pel.privateHeader().getJSON(); 466 467 // PrivateHeader always prints JSON 468 ASSERT_TRUE(jsonString); 469 *jsonString = '{' + *jsonString + '}'; 470 auto json = nlohmann::json::parse(*jsonString); 471 472 jsonString = pel.userHeader().getJSON(); 473 474 // UserHeader always prints JSON 475 ASSERT_TRUE(jsonString); 476 *jsonString = '{' + *jsonString + '}'; 477 json = nlohmann::json::parse(*jsonString); 478 479 for (const auto& section : pel.optionalSections()) 480 { 481 // The optional sections may or may not have implemented getJSON(). 482 jsonString = section->getJSON(); 483 if (jsonString) 484 { 485 *jsonString = '{' + *jsonString + '}'; 486 auto json = nlohmann::json::parse(*jsonString); 487 } 488 } 489 } 490 491 PelFFDCfile getJSONFFDC(const fs::path& dir) 492 { 493 PelFFDCfile ffdc; 494 ffdc.format = UserDataFormat::json; 495 ffdc.subType = 5; 496 ffdc.version = 42; 497 498 auto inputJSON = R"({ 499 "key1": "value1", 500 "key2": 42, 501 "key3" : [1, 2, 3, 4, 5], 502 "key4": {"key5": "value5"} 503 })"_json; 504 505 // Write the JSON to a file and get its descriptor. 506 auto s = inputJSON.dump(); 507 std::vector<uint8_t> data{s.begin(), s.end()}; 508 ffdc.fd = writeFileAndGetFD(dir, data); 509 510 return ffdc; 511 } 512 513 TEST_F(PELTest, MakeJSONFileUDSectionTest) 514 { 515 auto dir = makeTempDir(); 516 517 { 518 auto ffdc = getJSONFFDC(dir); 519 520 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 521 close(ffdc.fd); 522 ASSERT_TRUE(ud); 523 ASSERT_TRUE(ud->valid()); 524 EXPECT_EQ(ud->header().id, 0x5544); 525 526 EXPECT_EQ(ud->header().version, 527 static_cast<uint8_t>(UserDataFormatVersion::json)); 528 EXPECT_EQ(ud->header().subType, 529 static_cast<uint8_t>(UserDataFormat::json)); 530 EXPECT_EQ(ud->header().componentID, 531 static_cast<uint16_t>(ComponentID::phosphorLogging)); 532 533 // Pull the JSON back out of the the UserData section 534 const auto& d = ud->data(); 535 std::string js{d.begin(), d.end()}; 536 auto json = nlohmann::json::parse(js); 537 538 EXPECT_EQ("value1", json["key1"].get<std::string>()); 539 EXPECT_EQ(42, json["key2"].get<int>()); 540 541 std::vector<int> key3Values{1, 2, 3, 4, 5}; 542 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>()); 543 544 std::map<std::string, std::string> key4Values{{"key5", "value5"}}; 545 auto actual = json["key4"].get<std::map<std::string, std::string>>(); 546 EXPECT_EQ(key4Values, actual); 547 } 548 549 { 550 // A bad FD 551 PelFFDCfile ffdc; 552 ffdc.format = UserDataFormat::json; 553 ffdc.subType = 5; 554 ffdc.version = 42; 555 ffdc.fd = 10000; 556 557 // The section shouldn't get made 558 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 559 ASSERT_FALSE(ud); 560 } 561 562 fs::remove_all(dir); 563 } 564 565 PelFFDCfile getCBORFFDC(const fs::path& dir) 566 { 567 PelFFDCfile ffdc; 568 ffdc.format = UserDataFormat::cbor; 569 ffdc.subType = 5; 570 ffdc.version = 42; 571 572 auto inputJSON = R"({ 573 "key1": "value1", 574 "key2": 42, 575 "key3" : [1, 2, 3, 4, 5], 576 "key4": {"key5": "value5"} 577 })"_json; 578 579 // Convert the JSON to CBOR and write it to a file 580 auto data = nlohmann::json::to_cbor(inputJSON); 581 ffdc.fd = writeFileAndGetFD(dir, data); 582 583 return ffdc; 584 } 585 586 TEST_F(PELTest, MakeCBORFileUDSectionTest) 587 { 588 auto dir = makeTempDir(); 589 590 auto ffdc = getCBORFFDC(dir); 591 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 592 close(ffdc.fd); 593 ASSERT_TRUE(ud); 594 ASSERT_TRUE(ud->valid()); 595 EXPECT_EQ(ud->header().id, 0x5544); 596 597 EXPECT_EQ(ud->header().version, 598 static_cast<uint8_t>(UserDataFormatVersion::cbor)); 599 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::cbor)); 600 EXPECT_EQ(ud->header().componentID, 601 static_cast<uint16_t>(ComponentID::phosphorLogging)); 602 603 // Pull the CBOR back out of the PEL section 604 // The number of pad bytes to make the section be 4B aligned 605 // was added at the end, read it and then remove it and the 606 // padding before parsing it. 607 auto data = ud->data(); 608 Stream stream{data}; 609 stream.offset(data.size() - 4); 610 uint32_t pad; 611 stream >> pad; 612 613 data.resize(data.size() - 4 - pad); 614 615 auto json = nlohmann::json::from_cbor(data); 616 617 EXPECT_EQ("value1", json["key1"].get<std::string>()); 618 EXPECT_EQ(42, json["key2"].get<int>()); 619 620 std::vector<int> key3Values{1, 2, 3, 4, 5}; 621 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>()); 622 623 std::map<std::string, std::string> key4Values{{"key5", "value5"}}; 624 auto actual = json["key4"].get<std::map<std::string, std::string>>(); 625 EXPECT_EQ(key4Values, actual); 626 627 fs::remove_all(dir); 628 } 629 630 PelFFDCfile getTextFFDC(const fs::path& dir) 631 { 632 PelFFDCfile ffdc; 633 ffdc.format = UserDataFormat::text; 634 ffdc.subType = 5; 635 ffdc.version = 42; 636 637 std::string text{"this is some text that will be used for FFDC"}; 638 std::vector<uint8_t> data{text.begin(), text.end()}; 639 640 ffdc.fd = writeFileAndGetFD(dir, data); 641 642 return ffdc; 643 } 644 645 TEST_F(PELTest, MakeTextFileUDSectionTest) 646 { 647 auto dir = makeTempDir(); 648 649 auto ffdc = getTextFFDC(dir); 650 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 651 close(ffdc.fd); 652 ASSERT_TRUE(ud); 653 ASSERT_TRUE(ud->valid()); 654 EXPECT_EQ(ud->header().id, 0x5544); 655 656 EXPECT_EQ(ud->header().version, 657 static_cast<uint8_t>(UserDataFormatVersion::text)); 658 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::text)); 659 EXPECT_EQ(ud->header().componentID, 660 static_cast<uint16_t>(ComponentID::phosphorLogging)); 661 662 // Get the text back out 663 std::string text{ud->data().begin(), ud->data().end()}; 664 EXPECT_EQ(text, "this is some text that will be used for FFDC"); 665 666 fs::remove_all(dir); 667 } 668 669 PelFFDCfile getCustomFFDC(const fs::path& dir, const std::vector<uint8_t>& data) 670 { 671 PelFFDCfile ffdc; 672 ffdc.format = UserDataFormat::custom; 673 ffdc.subType = 5; 674 ffdc.version = 42; 675 676 ffdc.fd = writeFileAndGetFD(dir, data); 677 678 return ffdc; 679 } 680 681 TEST_F(PELTest, MakeCustomFileUDSectionTest) 682 { 683 auto dir = makeTempDir(); 684 685 { 686 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8}; 687 688 auto ffdc = getCustomFFDC(dir, data); 689 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 690 close(ffdc.fd); 691 ASSERT_TRUE(ud); 692 ASSERT_TRUE(ud->valid()); 693 EXPECT_EQ(ud->header().size, 8 + 8); // data size + header size 694 EXPECT_EQ(ud->header().id, 0x5544); 695 696 EXPECT_EQ(ud->header().version, 42); 697 EXPECT_EQ(ud->header().subType, 5); 698 EXPECT_EQ(ud->header().componentID, 0x2002); 699 700 // Get the data back out 701 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()}; 702 EXPECT_EQ(data, newData); 703 } 704 705 // Do the same thing again, but make it be non 4B aligned 706 // so the data gets padded. 707 { 708 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8, 9}; 709 710 auto ffdc = getCustomFFDC(dir, data); 711 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 712 close(ffdc.fd); 713 ASSERT_TRUE(ud); 714 ASSERT_TRUE(ud->valid()); 715 EXPECT_EQ(ud->header().size, 12 + 8); // data size + header size 716 EXPECT_EQ(ud->header().id, 0x5544); 717 718 EXPECT_EQ(ud->header().version, 42); 719 EXPECT_EQ(ud->header().subType, 5); 720 EXPECT_EQ(ud->header().componentID, 0x2002); 721 722 // Get the data back out 723 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()}; 724 725 // pad the original to 12B so we can compare 726 data.push_back(0); 727 data.push_back(0); 728 data.push_back(0); 729 730 EXPECT_EQ(data, newData); 731 } 732 733 fs::remove_all(dir); 734 } 735 736 // Test Adding FFDC from files to a PEL 737 TEST_F(PELTest, CreateWithFFDCTest) 738 { 739 auto dir = makeTempDir(); 740 message::Entry regEntry; 741 uint64_t timestamp = 5; 742 743 regEntry.name = "test"; 744 regEntry.subsystem = 5; 745 regEntry.actionFlags = 0xC000; 746 regEntry.src.type = 0xBD; 747 regEntry.src.reasonCode = 0x1234; 748 749 std::vector<std::string> additionalData{"KEY1=VALUE1"}; 750 AdditionalData ad{additionalData}; 751 NiceMock<MockDataInterface> dataIface; 752 PelFFDC ffdc; 753 754 std::vector<uint8_t> customData{1, 2, 3, 4, 5, 6, 7, 8}; 755 756 // This will be trimmed when added 757 std::vector<uint8_t> hugeCustomData(17000, 0x42); 758 759 ffdc.emplace_back(std::move(getJSONFFDC(dir))); 760 ffdc.emplace_back(std::move(getCBORFFDC(dir))); 761 ffdc.emplace_back(std::move(getTextFFDC(dir))); 762 ffdc.emplace_back(std::move(getCustomFFDC(dir, customData))); 763 ffdc.emplace_back(std::move(getCustomFFDC(dir, hugeCustomData))); 764 765 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 766 ad, ffdc, dataIface}; 767 768 EXPECT_TRUE(pel.valid()); 769 770 // Clipped to the max 771 EXPECT_EQ(pel.size(), 16384); 772 773 // Check for the FFDC sections 774 size_t udCount = 0; 775 Section* ud = nullptr; 776 777 for (const auto& section : pel.optionalSections()) 778 { 779 if (section->header().id == static_cast<uint16_t>(SectionID::userData)) 780 { 781 udCount++; 782 ud = section.get(); 783 } 784 } 785 786 EXPECT_EQ(udCount, 7); // AD section, sysInfo, 5 ffdc sections 787 788 // Check the last section was trimmed to 789 // something a bit less that 17000. 790 EXPECT_GT(ud->header().size, 14000); 791 EXPECT_LT(ud->header().size, 16000); 792 793 fs::remove_all(dir); 794 } 795 796 // Create a PEL with device callouts 797 TEST_F(PELTest, CreateWithDevCalloutsTest) 798 { 799 message::Entry regEntry; 800 uint64_t timestamp = 5; 801 802 regEntry.name = "test"; 803 regEntry.subsystem = 5; 804 regEntry.actionFlags = 0xC000; 805 regEntry.src.type = 0xBD; 806 regEntry.src.reasonCode = 0x1234; 807 808 NiceMock<MockDataInterface> dataIface; 809 PelFFDC ffdc; 810 811 const auto calloutJSON = R"( 812 { 813 "I2C": 814 { 815 "14": 816 { 817 "114": 818 { 819 "Callouts":[ 820 { 821 "Name": "/chassis/motherboard/cpu0", 822 "LocationCode": "P1", 823 "Priority": "H" 824 } 825 ], 826 "Dest": "proc 0 target" 827 } 828 } 829 } 830 })"; 831 832 std::vector<std::string> names{"systemA"}; 833 EXPECT_CALL(dataIface, getSystemNames) 834 .Times(2) 835 .WillRepeatedly(Return(names)); 836 837 EXPECT_CALL(dataIface, 838 getLocationCode( 839 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0")) 840 .WillOnce(Return("UXXX-P1")); 841 842 EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false)) 843 .WillOnce( 844 Return("/xyz/openbmc_project/inventory/chassis/motherboard/cpu0")); 845 846 EXPECT_CALL( 847 dataIface, 848 getHWCalloutFields( 849 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _, _)) 850 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"), 851 SetArgReferee<3>("123456789ABC"))); 852 853 auto dataPath = getPELReadOnlyDataPath(); 854 std::ofstream file{dataPath / "systemA_dev_callouts.json"}; 855 file << calloutJSON; 856 file.close(); 857 858 { 859 std::vector<std::string> data{ 860 "CALLOUT_ERRNO=5", 861 "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/" 862 "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"}; 863 864 AdditionalData ad{data}; 865 866 PEL pel{ 867 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 868 ad, ffdc, dataIface}; 869 870 ASSERT_TRUE(pel.primarySRC().value()->callouts()); 871 auto& callouts = pel.primarySRC().value()->callouts()->callouts(); 872 ASSERT_EQ(callouts.size(), 1); 873 ASSERT_TRUE(pel.isCalloutPresent()); 874 875 EXPECT_EQ(callouts[0]->priority(), 'H'); 876 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P1"); 877 878 auto& fru = callouts[0]->fruIdentity(); 879 EXPECT_EQ(fru->getPN().value(), "1234567"); 880 EXPECT_EQ(fru->getCCIN().value(), "CCCC"); 881 EXPECT_EQ(fru->getSN().value(), "123456789ABC"); 882 883 const auto& section = pel.optionalSections().back(); 884 885 ASSERT_EQ(section->header().id, 0x5544); // UD 886 auto ud = static_cast<UserData*>(section.get()); 887 888 // Check that there was a UserData section added that 889 // contains debug details about the device. 890 const auto& d = ud->data(); 891 std::string jsonString{d.begin(), d.end()}; 892 auto actualJSON = nlohmann::json::parse(jsonString); 893 894 auto expectedJSON = R"( 895 { 896 "PEL Internal Debug Data": { 897 "SRC": [ 898 "I2C: bus: 14 address: 114 dest: proc 0 target" 899 ] 900 } 901 } 902 )"_json; 903 904 EXPECT_EQ(actualJSON, expectedJSON); 905 } 906 907 { 908 // Device path not found (wrong i2c addr), so no callouts 909 std::vector<std::string> data{ 910 "CALLOUT_ERRNO=5", 911 "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/" 912 "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0099"}; 913 914 AdditionalData ad{data}; 915 916 PEL pel{ 917 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 918 ad, ffdc, dataIface}; 919 920 // no callouts 921 EXPECT_FALSE(pel.primarySRC().value()->callouts()); 922 923 // Now check that there was a UserData section 924 // that contains the lookup error. 925 const auto& section = pel.optionalSections().back(); 926 927 ASSERT_EQ(section->header().id, 0x5544); // UD 928 auto ud = static_cast<UserData*>(section.get()); 929 930 const auto& d = ud->data(); 931 932 std::string jsonString{d.begin(), d.end()}; 933 934 auto actualJSON = nlohmann::json::parse(jsonString); 935 936 auto expectedJSON = 937 "{\"PEL Internal Debug Data\":{\"SRC\":" 938 "[\"Problem looking up I2C callouts on 14 153: " 939 "[json.exception.out_of_range.403] key '153' not found\"]}}"_json; 940 941 EXPECT_EQ(actualJSON, expectedJSON); 942 } 943 944 fs::remove_all(dataPath); 945 } 946 947 // Test PELs when the callouts are passed in using a JSON file. 948 TEST_F(PELTest, CreateWithJSONCalloutsTest) 949 { 950 PelFFDCfile ffdcFile; 951 ffdcFile.format = UserDataFormat::json; 952 ffdcFile.subType = 0xCA; // Callout JSON 953 ffdcFile.version = 1; 954 955 // Write these callouts to a JSON file and pass it into 956 // the PEL as an FFDC file. 957 auto inputJSON = R"([ 958 { 959 "Priority": "H", 960 "LocationCode": "P0-C1" 961 }, 962 { 963 "Priority": "M", 964 "Procedure": "PROCEDURE" 965 } 966 ])"_json; 967 968 auto s = inputJSON.dump(); 969 std::vector<uint8_t> data{s.begin(), s.end()}; 970 auto dir = makeTempDir(); 971 ffdcFile.fd = writeFileAndGetFD(dir, data); 972 973 PelFFDC ffdc; 974 ffdc.push_back(std::move(ffdcFile)); 975 976 AdditionalData ad; 977 NiceMock<MockDataInterface> dataIface; 978 979 EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0)) 980 .Times(1) 981 .WillOnce(Return("UXXX-P0-C1")); 982 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false)) 983 .Times(1) 984 .WillOnce(Return("/inv/system/chassis/motherboard/bmc")); 985 EXPECT_CALL(dataIface, getHWCalloutFields( 986 "/inv/system/chassis/motherboard/bmc", _, _, _)) 987 .Times(1) 988 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"), 989 SetArgReferee<3>("123456789ABC"))); 990 991 message::Entry regEntry; 992 regEntry.name = "test"; 993 regEntry.subsystem = 5; 994 regEntry.actionFlags = 0xC000; 995 regEntry.src.type = 0xBD; 996 regEntry.src.reasonCode = 0x1234; 997 998 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error, 999 ad, ffdc, dataIface}; 1000 1001 ASSERT_TRUE(pel.valid()); 1002 ASSERT_TRUE(pel.primarySRC().value()->callouts()); 1003 const auto& callouts = pel.primarySRC().value()->callouts()->callouts(); 1004 ASSERT_EQ(callouts.size(), 2); 1005 1006 { 1007 EXPECT_EQ(callouts[0]->priority(), 'H'); 1008 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1"); 1009 1010 auto& fru = callouts[0]->fruIdentity(); 1011 EXPECT_EQ(fru->getPN().value(), "1234567"); 1012 EXPECT_EQ(fru->getCCIN().value(), "CCCC"); 1013 EXPECT_EQ(fru->getSN().value(), "123456789ABC"); 1014 EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU); 1015 } 1016 { 1017 EXPECT_EQ(callouts[1]->priority(), 'M'); 1018 EXPECT_EQ(callouts[1]->locationCode(), ""); 1019 1020 auto& fru = callouts[1]->fruIdentity(); 1021 EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU"); 1022 EXPECT_EQ(fru->failingComponentType(), 1023 src::FRUIdentity::maintenanceProc); 1024 } 1025 fs::remove_all(dir); 1026 } 1027