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::NiceMock; 31 using ::testing::Return; 32 using ::testing::SetArgReferee; 33 34 class PELTest : public CleanLogID 35 { 36 }; 37 38 fs::path makeTempDir() 39 { 40 char path[] = "/tmp/tempdirXXXXXX"; 41 std::filesystem::path dir = mkdtemp(path); 42 return dir; 43 } 44 45 int writeFileAndGetFD(const fs::path& dir, const std::vector<uint8_t>& data) 46 { 47 static size_t count = 0; 48 fs::path path = dir / (std::string{"file"} + std::to_string(count)); 49 std::ofstream stream{path}; 50 count++; 51 52 stream.write(reinterpret_cast<const char*>(data.data()), data.size()); 53 stream.close(); 54 55 FILE* fp = fopen(path.c_str(), "r"); 56 return fileno(fp); 57 } 58 59 TEST_F(PELTest, FlattenTest) 60 { 61 auto data = pelDataFactory(TestPELType::pelSimple); 62 auto pel = std::make_unique<PEL>(data); 63 64 // Check a few fields 65 EXPECT_TRUE(pel->valid()); 66 EXPECT_EQ(pel->id(), 0x80818283); 67 EXPECT_EQ(pel->plid(), 0x50515253); 68 EXPECT_EQ(pel->userHeader().subsystem(), 0x10); 69 EXPECT_EQ(pel->userHeader().actionFlags(), 0x80C0); 70 71 // Test that data in == data out 72 auto flattenedData = pel->data(); 73 EXPECT_EQ(data, flattenedData); 74 EXPECT_EQ(flattenedData.size(), pel->size()); 75 } 76 77 TEST_F(PELTest, CommitTimeTest) 78 { 79 auto data = pelDataFactory(TestPELType::pelSimple); 80 auto pel = std::make_unique<PEL>(data); 81 82 auto origTime = pel->commitTime(); 83 pel->setCommitTime(); 84 auto newTime = pel->commitTime(); 85 86 EXPECT_NE(origTime, newTime); 87 88 // Make a new PEL and check new value is still there 89 auto newData = pel->data(); 90 auto newPel = std::make_unique<PEL>(newData); 91 EXPECT_EQ(newTime, newPel->commitTime()); 92 } 93 94 TEST_F(PELTest, AssignIDTest) 95 { 96 auto data = pelDataFactory(TestPELType::pelSimple); 97 auto pel = std::make_unique<PEL>(data); 98 99 auto origID = pel->id(); 100 pel->assignID(); 101 auto newID = pel->id(); 102 103 EXPECT_NE(origID, newID); 104 105 // Make a new PEL and check new value is still there 106 auto newData = pel->data(); 107 auto newPel = std::make_unique<PEL>(newData); 108 EXPECT_EQ(newID, newPel->id()); 109 } 110 111 TEST_F(PELTest, WithLogIDTest) 112 { 113 auto data = pelDataFactory(TestPELType::pelSimple); 114 auto pel = std::make_unique<PEL>(data, 0x42); 115 116 EXPECT_TRUE(pel->valid()); 117 EXPECT_EQ(pel->obmcLogID(), 0x42); 118 } 119 120 TEST_F(PELTest, InvalidPELTest) 121 { 122 auto data = pelDataFactory(TestPELType::pelSimple); 123 124 // Too small 125 data.resize(PrivateHeader::flattenedSize()); 126 127 auto pel = std::make_unique<PEL>(data); 128 129 EXPECT_TRUE(pel->privateHeader().valid()); 130 EXPECT_FALSE(pel->userHeader().valid()); 131 EXPECT_FALSE(pel->valid()); 132 133 // Now corrupt the private header 134 data = pelDataFactory(TestPELType::pelSimple); 135 data.at(0) = 0; 136 pel = std::make_unique<PEL>(data); 137 138 EXPECT_FALSE(pel->privateHeader().valid()); 139 EXPECT_TRUE(pel->userHeader().valid()); 140 EXPECT_FALSE(pel->valid()); 141 } 142 143 TEST_F(PELTest, EmptyDataTest) 144 { 145 std::vector<uint8_t> data; 146 auto pel = std::make_unique<PEL>(data); 147 148 EXPECT_FALSE(pel->privateHeader().valid()); 149 EXPECT_FALSE(pel->userHeader().valid()); 150 EXPECT_FALSE(pel->valid()); 151 } 152 153 TEST_F(PELTest, CreateFromRegistryTest) 154 { 155 message::Entry regEntry; 156 uint64_t timestamp = 5; 157 158 regEntry.name = "test"; 159 regEntry.subsystem = 5; 160 regEntry.actionFlags = 0xC000; 161 regEntry.src.type = 0xBD; 162 regEntry.src.reasonCode = 0x1234; 163 164 std::vector<std::string> data{"KEY1=VALUE1"}; 165 AdditionalData ad{data}; 166 NiceMock<MockDataInterface> dataIface; 167 PelFFDC ffdc; 168 169 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 170 ad, ffdc, dataIface}; 171 172 EXPECT_TRUE(pel.valid()); 173 EXPECT_EQ(pel.privateHeader().obmcLogID(), 42); 174 EXPECT_EQ(pel.userHeader().severity(), 0x40); 175 176 EXPECT_EQ(pel.primarySRC().value()->asciiString(), 177 "BD051234 "); 178 179 // Check that certain optional sections have been created 180 size_t mtmsCount = 0; 181 size_t euhCount = 0; 182 size_t udCount = 0; 183 184 for (const auto& section : pel.optionalSections()) 185 { 186 if (section->header().id == 187 static_cast<uint16_t>(SectionID::failingMTMS)) 188 { 189 mtmsCount++; 190 } 191 else if (section->header().id == 192 static_cast<uint16_t>(SectionID::extendedUserHeader)) 193 { 194 euhCount++; 195 } 196 else if (section->header().id == 197 static_cast<uint16_t>(SectionID::userData)) 198 { 199 udCount++; 200 } 201 } 202 203 EXPECT_EQ(mtmsCount, 1); 204 EXPECT_EQ(euhCount, 1); 205 EXPECT_EQ(udCount, 2); // AD section and sysInfo section 206 ASSERT_FALSE(pel.isCalloutPresent()); 207 208 { 209 // The same thing, but without the action flags specified 210 // in the registry, so the constructor should set them. 211 regEntry.actionFlags = std::nullopt; 212 213 PEL pel2{ 214 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 215 ad, ffdc, dataIface}; 216 217 EXPECT_EQ(pel2.userHeader().actionFlags(), 0xA800); 218 } 219 } 220 221 // Test that when the AdditionalData size is over 16KB that 222 // the PEL that's created is exactly 16KB since the UserData 223 // section that contains all that data was pruned. 224 TEST_F(PELTest, CreateTooBigADTest) 225 { 226 message::Entry regEntry; 227 uint64_t timestamp = 5; 228 229 regEntry.name = "test"; 230 regEntry.subsystem = 5; 231 regEntry.actionFlags = 0xC000; 232 regEntry.src.type = 0xBD; 233 regEntry.src.reasonCode = 0x1234; 234 PelFFDC ffdc; 235 236 // Over the 16KB max PEL size 237 std::string bigAD{"KEY1="}; 238 bigAD += std::string(17000, 'G'); 239 240 std::vector<std::string> data{bigAD}; 241 AdditionalData ad{data}; 242 NiceMock<MockDataInterface> dataIface; 243 244 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 245 ad, ffdc, dataIface}; 246 247 EXPECT_TRUE(pel.valid()); 248 EXPECT_EQ(pel.size(), 16384); 249 250 // Make sure that there are still 2 UD sections. 251 size_t udCount = 0; 252 for (const auto& section : pel.optionalSections()) 253 { 254 if (section->header().id == static_cast<uint16_t>(SectionID::userData)) 255 { 256 udCount++; 257 } 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 406 std::string pid = "_PID=" + std::to_string(getpid()); 407 std::vector<std::string> ad{pid}; 408 AdditionalData additionalData{ad}; 409 410 auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface); 411 412 EXPECT_TRUE(ud->valid()); 413 EXPECT_EQ(ud->header().id, 0x5544); 414 EXPECT_EQ(ud->header().version, 0x01); 415 EXPECT_EQ(ud->header().subType, 0x01); 416 EXPECT_EQ(ud->header().componentID, 0x2000); 417 418 // Pull out the JSON data and check it. 419 const auto& d = ud->data(); 420 std::string jsonString{d.begin(), d.end()}; 421 auto json = nlohmann::json::parse(jsonString); 422 423 // Ensure the 'Process Name' entry contains the name of this test 424 // executable. 425 auto name = json["Process Name"].get<std::string>(); 426 auto found = (name.find("pel_test") != std::string::npos) || 427 (name.find("test-openpower-pels-pel") != std::string::npos); 428 EXPECT_TRUE(found); 429 // @TODO(stwcx): remove 'pel_test' when removing autotools. 430 431 auto version = json["BMC Version ID"].get<std::string>(); 432 EXPECT_EQ(version, "ABCD1234"); 433 434 auto state = json["BMCState"].get<std::string>(); 435 EXPECT_EQ(state, "Ready"); 436 437 state = json["ChassisState"].get<std::string>(); 438 EXPECT_EQ(state, "On"); 439 440 state = json["HostState"].get<std::string>(); 441 EXPECT_EQ(state, "Off"); 442 } 443 444 // Test that the sections that override 445 // virtual std::optional<std::string> Section::getJSON() const 446 // return valid JSON. 447 TEST_F(PELTest, SectionJSONTest) 448 { 449 auto data = pelDataFactory(TestPELType::pelSimple); 450 PEL pel{data}; 451 452 // Check that all JSON returned from the sections is 453 // parseable by nlohmann::json, which will throw an 454 // exception and fail the test if there is a problem. 455 456 // The getJSON() response needs to be wrapped in a { } to make 457 // actual valid JSON (PEL::toJSON() usually handles that). 458 459 auto jsonString = pel.privateHeader().getJSON(); 460 461 // PrivateHeader always prints JSON 462 ASSERT_TRUE(jsonString); 463 *jsonString = '{' + *jsonString + '}'; 464 auto json = nlohmann::json::parse(*jsonString); 465 466 jsonString = pel.userHeader().getJSON(); 467 468 // UserHeader always prints JSON 469 ASSERT_TRUE(jsonString); 470 *jsonString = '{' + *jsonString + '}'; 471 json = nlohmann::json::parse(*jsonString); 472 473 for (const auto& section : pel.optionalSections()) 474 { 475 // The optional sections may or may not have implemented getJSON(). 476 jsonString = section->getJSON(); 477 if (jsonString) 478 { 479 *jsonString = '{' + *jsonString + '}'; 480 auto json = nlohmann::json::parse(*jsonString); 481 } 482 } 483 } 484 485 PelFFDCfile getJSONFFDC(const fs::path& dir) 486 { 487 PelFFDCfile ffdc; 488 ffdc.format = UserDataFormat::json; 489 ffdc.subType = 5; 490 ffdc.version = 42; 491 492 auto inputJSON = R"({ 493 "key1": "value1", 494 "key2": 42, 495 "key3" : [1, 2, 3, 4, 5], 496 "key4": {"key5": "value5"} 497 })"_json; 498 499 // Write the JSON to a file and get its descriptor. 500 auto s = inputJSON.dump(); 501 std::vector<uint8_t> data{s.begin(), s.end()}; 502 ffdc.fd = writeFileAndGetFD(dir, data); 503 504 return ffdc; 505 } 506 507 TEST_F(PELTest, MakeJSONFileUDSectionTest) 508 { 509 auto dir = makeTempDir(); 510 511 { 512 auto ffdc = getJSONFFDC(dir); 513 514 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 515 close(ffdc.fd); 516 ASSERT_TRUE(ud); 517 ASSERT_TRUE(ud->valid()); 518 EXPECT_EQ(ud->header().id, 0x5544); 519 520 EXPECT_EQ(ud->header().version, 521 static_cast<uint8_t>(UserDataFormatVersion::json)); 522 EXPECT_EQ(ud->header().subType, 523 static_cast<uint8_t>(UserDataFormat::json)); 524 EXPECT_EQ(ud->header().componentID, 525 static_cast<uint16_t>(ComponentID::phosphorLogging)); 526 527 // Pull the JSON back out of the the UserData section 528 const auto& d = ud->data(); 529 std::string js{d.begin(), d.end()}; 530 auto json = nlohmann::json::parse(js); 531 532 EXPECT_EQ("value1", json["key1"].get<std::string>()); 533 EXPECT_EQ(42, json["key2"].get<int>()); 534 535 std::vector<int> key3Values{1, 2, 3, 4, 5}; 536 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>()); 537 538 std::map<std::string, std::string> key4Values{{"key5", "value5"}}; 539 auto actual = json["key4"].get<std::map<std::string, std::string>>(); 540 EXPECT_EQ(key4Values, actual); 541 } 542 543 { 544 // A bad FD 545 PelFFDCfile ffdc; 546 ffdc.format = UserDataFormat::json; 547 ffdc.subType = 5; 548 ffdc.version = 42; 549 ffdc.fd = 10000; 550 551 // The section shouldn't get made 552 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 553 ASSERT_FALSE(ud); 554 } 555 556 fs::remove_all(dir); 557 } 558 559 PelFFDCfile getCBORFFDC(const fs::path& dir) 560 { 561 PelFFDCfile ffdc; 562 ffdc.format = UserDataFormat::cbor; 563 ffdc.subType = 5; 564 ffdc.version = 42; 565 566 auto inputJSON = R"({ 567 "key1": "value1", 568 "key2": 42, 569 "key3" : [1, 2, 3, 4, 5], 570 "key4": {"key5": "value5"} 571 })"_json; 572 573 // Convert the JSON to CBOR and write it to a file 574 auto data = nlohmann::json::to_cbor(inputJSON); 575 ffdc.fd = writeFileAndGetFD(dir, data); 576 577 return ffdc; 578 } 579 580 TEST_F(PELTest, MakeCBORFileUDSectionTest) 581 { 582 auto dir = makeTempDir(); 583 584 auto ffdc = getCBORFFDC(dir); 585 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 586 close(ffdc.fd); 587 ASSERT_TRUE(ud); 588 ASSERT_TRUE(ud->valid()); 589 EXPECT_EQ(ud->header().id, 0x5544); 590 591 EXPECT_EQ(ud->header().version, 592 static_cast<uint8_t>(UserDataFormatVersion::cbor)); 593 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::cbor)); 594 EXPECT_EQ(ud->header().componentID, 595 static_cast<uint16_t>(ComponentID::phosphorLogging)); 596 597 // Pull the CBOR back out of the PEL section 598 // The number of pad bytes to make the section be 4B aligned 599 // was added at the end, read it and then remove it and the 600 // padding before parsing it. 601 auto data = ud->data(); 602 Stream stream{data}; 603 stream.offset(data.size() - 4); 604 uint32_t pad; 605 stream >> pad; 606 607 data.resize(data.size() - 4 - pad); 608 609 auto json = nlohmann::json::from_cbor(data); 610 611 EXPECT_EQ("value1", json["key1"].get<std::string>()); 612 EXPECT_EQ(42, json["key2"].get<int>()); 613 614 std::vector<int> key3Values{1, 2, 3, 4, 5}; 615 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>()); 616 617 std::map<std::string, std::string> key4Values{{"key5", "value5"}}; 618 auto actual = json["key4"].get<std::map<std::string, std::string>>(); 619 EXPECT_EQ(key4Values, actual); 620 621 fs::remove_all(dir); 622 } 623 624 PelFFDCfile getTextFFDC(const fs::path& dir) 625 { 626 PelFFDCfile ffdc; 627 ffdc.format = UserDataFormat::text; 628 ffdc.subType = 5; 629 ffdc.version = 42; 630 631 std::string text{"this is some text that will be used for FFDC"}; 632 std::vector<uint8_t> data{text.begin(), text.end()}; 633 634 ffdc.fd = writeFileAndGetFD(dir, data); 635 636 return ffdc; 637 } 638 639 TEST_F(PELTest, MakeTextFileUDSectionTest) 640 { 641 auto dir = makeTempDir(); 642 643 auto ffdc = getTextFFDC(dir); 644 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 645 close(ffdc.fd); 646 ASSERT_TRUE(ud); 647 ASSERT_TRUE(ud->valid()); 648 EXPECT_EQ(ud->header().id, 0x5544); 649 650 EXPECT_EQ(ud->header().version, 651 static_cast<uint8_t>(UserDataFormatVersion::text)); 652 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::text)); 653 EXPECT_EQ(ud->header().componentID, 654 static_cast<uint16_t>(ComponentID::phosphorLogging)); 655 656 // Get the text back out 657 std::string text{ud->data().begin(), ud->data().end()}; 658 EXPECT_EQ(text, "this is some text that will be used for FFDC"); 659 660 fs::remove_all(dir); 661 } 662 663 PelFFDCfile getCustomFFDC(const fs::path& dir, const std::vector<uint8_t>& data) 664 { 665 PelFFDCfile ffdc; 666 ffdc.format = UserDataFormat::custom; 667 ffdc.subType = 5; 668 ffdc.version = 42; 669 670 ffdc.fd = writeFileAndGetFD(dir, data); 671 672 return ffdc; 673 } 674 675 TEST_F(PELTest, MakeCustomFileUDSectionTest) 676 { 677 auto dir = makeTempDir(); 678 679 { 680 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8}; 681 682 auto ffdc = getCustomFFDC(dir, data); 683 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 684 close(ffdc.fd); 685 ASSERT_TRUE(ud); 686 ASSERT_TRUE(ud->valid()); 687 EXPECT_EQ(ud->header().size, 8 + 8); // data size + header size 688 EXPECT_EQ(ud->header().id, 0x5544); 689 690 EXPECT_EQ(ud->header().version, 42); 691 EXPECT_EQ(ud->header().subType, 5); 692 EXPECT_EQ(ud->header().componentID, 0x2002); 693 694 // Get the data back out 695 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()}; 696 EXPECT_EQ(data, newData); 697 } 698 699 // Do the same thing again, but make it be non 4B aligned 700 // so the data gets padded. 701 { 702 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8, 9}; 703 704 auto ffdc = getCustomFFDC(dir, data); 705 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc); 706 close(ffdc.fd); 707 ASSERT_TRUE(ud); 708 ASSERT_TRUE(ud->valid()); 709 EXPECT_EQ(ud->header().size, 12 + 8); // data size + header size 710 EXPECT_EQ(ud->header().id, 0x5544); 711 712 EXPECT_EQ(ud->header().version, 42); 713 EXPECT_EQ(ud->header().subType, 5); 714 EXPECT_EQ(ud->header().componentID, 0x2002); 715 716 // Get the data back out 717 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()}; 718 719 // pad the original to 12B so we can compare 720 data.push_back(0); 721 data.push_back(0); 722 data.push_back(0); 723 724 EXPECT_EQ(data, newData); 725 } 726 727 fs::remove_all(dir); 728 } 729 730 // Test Adding FFDC from files to a PEL 731 TEST_F(PELTest, CreateWithFFDCTest) 732 { 733 auto dir = makeTempDir(); 734 message::Entry regEntry; 735 uint64_t timestamp = 5; 736 737 regEntry.name = "test"; 738 regEntry.subsystem = 5; 739 regEntry.actionFlags = 0xC000; 740 regEntry.src.type = 0xBD; 741 regEntry.src.reasonCode = 0x1234; 742 743 std::vector<std::string> additionalData{"KEY1=VALUE1"}; 744 AdditionalData ad{additionalData}; 745 NiceMock<MockDataInterface> dataIface; 746 PelFFDC ffdc; 747 748 std::vector<uint8_t> customData{1, 2, 3, 4, 5, 6, 7, 8}; 749 750 // This will be trimmed when added 751 std::vector<uint8_t> hugeCustomData(17000, 0x42); 752 753 ffdc.emplace_back(std::move(getJSONFFDC(dir))); 754 ffdc.emplace_back(std::move(getCBORFFDC(dir))); 755 ffdc.emplace_back(std::move(getTextFFDC(dir))); 756 ffdc.emplace_back(std::move(getCustomFFDC(dir, customData))); 757 ffdc.emplace_back(std::move(getCustomFFDC(dir, hugeCustomData))); 758 759 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 760 ad, ffdc, dataIface}; 761 762 EXPECT_TRUE(pel.valid()); 763 764 // Clipped to the max 765 EXPECT_EQ(pel.size(), 16384); 766 767 // Check for the FFDC sections 768 size_t udCount = 0; 769 Section* ud = nullptr; 770 771 for (const auto& section : pel.optionalSections()) 772 { 773 if (section->header().id == static_cast<uint16_t>(SectionID::userData)) 774 { 775 udCount++; 776 ud = section.get(); 777 } 778 } 779 780 EXPECT_EQ(udCount, 7); // AD section, sysInfo, 5 ffdc sections 781 782 // Check the last section was trimmed to 783 // something a bit less that 17000. 784 EXPECT_GT(ud->header().size, 14000); 785 EXPECT_LT(ud->header().size, 16000); 786 787 fs::remove_all(dir); 788 } 789 790 // Create a PEL with device callouts 791 TEST_F(PELTest, CreateWithDevCalloutsTest) 792 { 793 message::Entry regEntry; 794 uint64_t timestamp = 5; 795 796 regEntry.name = "test"; 797 regEntry.subsystem = 5; 798 regEntry.actionFlags = 0xC000; 799 regEntry.src.type = 0xBD; 800 regEntry.src.reasonCode = 0x1234; 801 802 NiceMock<MockDataInterface> dataIface; 803 PelFFDC ffdc; 804 805 const auto calloutJSON = R"( 806 { 807 "I2C": 808 { 809 "14": 810 { 811 "114": 812 { 813 "Callouts":[ 814 { 815 "Name": "/chassis/motherboard/cpu0", 816 "LocationCode": "P1", 817 "Priority": "H" 818 } 819 ], 820 "Dest": "proc 0 target" 821 } 822 } 823 } 824 })"; 825 826 std::vector<std::string> names{"systemA"}; 827 EXPECT_CALL(dataIface, getSystemNames) 828 .Times(2) 829 .WillRepeatedly(Return(names)); 830 831 EXPECT_CALL(dataIface, 832 getLocationCode( 833 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0")) 834 .WillOnce(Return("UXXX-P1")); 835 836 EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false)) 837 .WillOnce( 838 Return("/xyz/openbmc_project/inventory/chassis/motherboard/cpu0")); 839 840 EXPECT_CALL( 841 dataIface, 842 getHWCalloutFields( 843 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _, _)) 844 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"), 845 SetArgReferee<3>("123456789ABC"))); 846 847 auto dataPath = getPELReadOnlyDataPath(); 848 std::ofstream file{dataPath / "systemA_dev_callouts.json"}; 849 file << calloutJSON; 850 file.close(); 851 852 { 853 std::vector<std::string> data{ 854 "CALLOUT_ERRNO=5", 855 "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/" 856 "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"}; 857 858 AdditionalData ad{data}; 859 860 PEL pel{ 861 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 862 ad, ffdc, dataIface}; 863 864 ASSERT_TRUE(pel.primarySRC().value()->callouts()); 865 auto& callouts = pel.primarySRC().value()->callouts()->callouts(); 866 ASSERT_EQ(callouts.size(), 1); 867 ASSERT_TRUE(pel.isCalloutPresent()); 868 869 EXPECT_EQ(callouts[0]->priority(), 'H'); 870 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P1"); 871 872 auto& fru = callouts[0]->fruIdentity(); 873 EXPECT_EQ(fru->getPN().value(), "1234567"); 874 EXPECT_EQ(fru->getCCIN().value(), "CCCC"); 875 EXPECT_EQ(fru->getSN().value(), "123456789ABC"); 876 877 const auto& section = pel.optionalSections().back(); 878 879 ASSERT_EQ(section->header().id, 0x5544); // UD 880 auto ud = static_cast<UserData*>(section.get()); 881 882 // Check that there was a UserData section added that 883 // contains debug details about the device. 884 const auto& d = ud->data(); 885 std::string jsonString{d.begin(), d.end()}; 886 auto actualJSON = nlohmann::json::parse(jsonString); 887 888 auto expectedJSON = R"( 889 { 890 "PEL Internal Debug Data": { 891 "SRC": [ 892 "I2C: bus: 14 address: 114 dest: proc 0 target" 893 ] 894 } 895 } 896 )"_json; 897 898 EXPECT_EQ(actualJSON, expectedJSON); 899 } 900 901 { 902 // Device path not found (wrong i2c addr), so no callouts 903 std::vector<std::string> data{ 904 "CALLOUT_ERRNO=5", 905 "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/" 906 "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0099"}; 907 908 AdditionalData ad{data}; 909 910 PEL pel{ 911 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error, 912 ad, ffdc, dataIface}; 913 914 // no callouts 915 EXPECT_FALSE(pel.primarySRC().value()->callouts()); 916 917 // Now check that there was a UserData section 918 // that contains the lookup error. 919 const auto& section = pel.optionalSections().back(); 920 921 ASSERT_EQ(section->header().id, 0x5544); // UD 922 auto ud = static_cast<UserData*>(section.get()); 923 924 const auto& d = ud->data(); 925 926 std::string jsonString{d.begin(), d.end()}; 927 928 auto actualJSON = nlohmann::json::parse(jsonString); 929 930 auto expectedJSON = 931 "{\"PEL Internal Debug Data\":{\"SRC\":" 932 "[\"Problem looking up I2C callouts on 14 153: " 933 "[json.exception.out_of_range.403] key '153' not found\"]}}"_json; 934 935 EXPECT_EQ(actualJSON, expectedJSON); 936 } 937 938 fs::remove_all(dataPath); 939 } 940 941 // Test PELs when the callouts are passed in using a JSON file. 942 TEST_F(PELTest, CreateWithJSONCalloutsTest) 943 { 944 PelFFDCfile ffdcFile; 945 ffdcFile.format = UserDataFormat::json; 946 ffdcFile.subType = 0xCA; // Callout JSON 947 ffdcFile.version = 1; 948 949 // Write these callouts to a JSON file and pass it into 950 // the PEL as an FFDC file. 951 auto inputJSON = R"([ 952 { 953 "Priority": "H", 954 "LocationCode": "P0-C1" 955 }, 956 { 957 "Priority": "M", 958 "Procedure": "PROCEDURE" 959 } 960 ])"_json; 961 962 auto s = inputJSON.dump(); 963 std::vector<uint8_t> data{s.begin(), s.end()}; 964 auto dir = makeTempDir(); 965 ffdcFile.fd = writeFileAndGetFD(dir, data); 966 967 PelFFDC ffdc; 968 ffdc.push_back(std::move(ffdcFile)); 969 970 AdditionalData ad; 971 NiceMock<MockDataInterface> dataIface; 972 973 EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0)) 974 .Times(1) 975 .WillOnce(Return("UXXX-P0-C1")); 976 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false)) 977 .Times(1) 978 .WillOnce(Return("/inv/system/chassis/motherboard/bmc")); 979 EXPECT_CALL(dataIface, getHWCalloutFields( 980 "/inv/system/chassis/motherboard/bmc", _, _, _)) 981 .Times(1) 982 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"), 983 SetArgReferee<3>("123456789ABC"))); 984 985 message::Entry regEntry; 986 regEntry.name = "test"; 987 regEntry.subsystem = 5; 988 regEntry.actionFlags = 0xC000; 989 regEntry.src.type = 0xBD; 990 regEntry.src.reasonCode = 0x1234; 991 992 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error, 993 ad, ffdc, dataIface}; 994 995 ASSERT_TRUE(pel.valid()); 996 ASSERT_TRUE(pel.primarySRC().value()->callouts()); 997 const auto& callouts = pel.primarySRC().value()->callouts()->callouts(); 998 ASSERT_EQ(callouts.size(), 2); 999 1000 { 1001 EXPECT_EQ(callouts[0]->priority(), 'H'); 1002 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1"); 1003 1004 auto& fru = callouts[0]->fruIdentity(); 1005 EXPECT_EQ(fru->getPN().value(), "1234567"); 1006 EXPECT_EQ(fru->getCCIN().value(), "CCCC"); 1007 EXPECT_EQ(fru->getSN().value(), "123456789ABC"); 1008 EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU); 1009 } 1010 { 1011 EXPECT_EQ(callouts[1]->priority(), 'M'); 1012 EXPECT_EQ(callouts[1]->locationCode(), ""); 1013 1014 auto& fru = callouts[1]->fruIdentity(); 1015 EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU"); 1016 EXPECT_EQ(fru->failingComponentType(), 1017 src::FRUIdentity::maintenanceProc); 1018 } 1019 fs::remove_all(dir); 1020 } 1021