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