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