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 #include <optional>
25
26 #include <gtest/gtest.h>
27
28 namespace fs = std::filesystem;
29 using namespace openpower::pels;
30 using ::testing::_;
31 using ::testing::DoAll;
32 using ::testing::NiceMock;
33 using ::testing::Return;
34 using ::testing::SetArgReferee;
35
36 class PELTest : public CleanLogID
37 {};
38
makeTempDir()39 fs::path makeTempDir()
40 {
41 char path[] = "/tmp/tempdirXXXXXX";
42 std::filesystem::path dir = mkdtemp(path);
43 return dir;
44 }
45
writeFileAndGetFD(const fs::path & dir,const std::vector<uint8_t> & data)46 int writeFileAndGetFD(const fs::path& dir, const std::vector<uint8_t>& data)
47 {
48 static size_t count = 0;
49 fs::path path = dir / (std::string{"file"} + std::to_string(count));
50 std::ofstream stream{path};
51 count++;
52
53 stream.write(reinterpret_cast<const char*>(data.data()), data.size());
54 stream.close();
55
56 FILE* fp = fopen(path.c_str(), "r");
57 return fileno(fp);
58 }
59
TEST_F(PELTest,FlattenTest)60 TEST_F(PELTest, FlattenTest)
61 {
62 auto data = pelDataFactory(TestPELType::pelSimple);
63 auto pel = std::make_unique<PEL>(data);
64
65 // Check a few fields
66 EXPECT_TRUE(pel->valid());
67 EXPECT_EQ(pel->id(), 0x80818283);
68 EXPECT_EQ(pel->plid(), 0x50515253);
69 EXPECT_EQ(pel->userHeader().subsystem(), 0x10);
70 EXPECT_EQ(pel->userHeader().actionFlags(), 0x80C0);
71
72 // Test that data in == data out
73 auto flattenedData = pel->data();
74 EXPECT_EQ(data, flattenedData);
75 EXPECT_EQ(flattenedData.size(), pel->size());
76 }
77
TEST_F(PELTest,CommitTimeTest)78 TEST_F(PELTest, CommitTimeTest)
79 {
80 auto data = pelDataFactory(TestPELType::pelSimple);
81 auto pel = std::make_unique<PEL>(data);
82
83 auto origTime = pel->commitTime();
84 pel->setCommitTime();
85 auto newTime = pel->commitTime();
86
87 EXPECT_NE(origTime, newTime);
88
89 // Make a new PEL and check new value is still there
90 auto newData = pel->data();
91 auto newPel = std::make_unique<PEL>(newData);
92 EXPECT_EQ(newTime, newPel->commitTime());
93 }
94
TEST_F(PELTest,AssignIDTest)95 TEST_F(PELTest, AssignIDTest)
96 {
97 auto data = pelDataFactory(TestPELType::pelSimple);
98 auto pel = std::make_unique<PEL>(data);
99
100 auto origID = pel->id();
101 pel->assignID();
102 auto newID = pel->id();
103
104 EXPECT_NE(origID, newID);
105
106 // Make a new PEL and check new value is still there
107 auto newData = pel->data();
108 auto newPel = std::make_unique<PEL>(newData);
109 EXPECT_EQ(newID, newPel->id());
110 }
111
TEST_F(PELTest,WithLogIDTest)112 TEST_F(PELTest, WithLogIDTest)
113 {
114 auto data = pelDataFactory(TestPELType::pelSimple);
115 auto pel = std::make_unique<PEL>(data, 0x42);
116
117 EXPECT_TRUE(pel->valid());
118 EXPECT_EQ(pel->obmcLogID(), 0x42);
119 }
120
TEST_F(PELTest,InvalidPELTest)121 TEST_F(PELTest, InvalidPELTest)
122 {
123 auto data = pelDataFactory(TestPELType::pelSimple);
124
125 // Too small
126 data.resize(PrivateHeader::flattenedSize());
127
128 auto pel = std::make_unique<PEL>(data);
129
130 EXPECT_TRUE(pel->privateHeader().valid());
131 EXPECT_FALSE(pel->userHeader().valid());
132 EXPECT_FALSE(pel->valid());
133
134 // Now corrupt the private header
135 data = pelDataFactory(TestPELType::pelSimple);
136 data.at(0) = 0;
137 pel = std::make_unique<PEL>(data);
138
139 EXPECT_FALSE(pel->privateHeader().valid());
140 EXPECT_TRUE(pel->userHeader().valid());
141 EXPECT_FALSE(pel->valid());
142 }
143
TEST_F(PELTest,EmptyDataTest)144 TEST_F(PELTest, EmptyDataTest)
145 {
146 std::vector<uint8_t> data;
147 auto pel = std::make_unique<PEL>(data);
148
149 EXPECT_FALSE(pel->privateHeader().valid());
150 EXPECT_FALSE(pel->userHeader().valid());
151 EXPECT_FALSE(pel->valid());
152 }
153
TEST_F(PELTest,CreateFromRegistryTest)154 TEST_F(PELTest, CreateFromRegistryTest)
155 {
156 message::Entry regEntry;
157 uint64_t timestamp = 5;
158
159 regEntry.name = "test";
160 regEntry.subsystem = 5;
161 regEntry.actionFlags = 0xC000;
162 regEntry.src.type = 0xBD;
163 regEntry.src.reasonCode = 0x1234;
164
165 std::map<std::string, std::string> data{{"KEY1", "VALUE1"}};
166 AdditionalData ad{data};
167 NiceMock<MockDataInterface> dataIface;
168 NiceMock<MockJournal> journal;
169 PelFFDC ffdc;
170
171 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
172 ad, ffdc, dataIface, journal};
173
174 EXPECT_TRUE(pel.valid());
175 EXPECT_EQ(pel.privateHeader().obmcLogID(), 42);
176 EXPECT_EQ(pel.userHeader().severity(), 0x40);
177
178 EXPECT_EQ(pel.primarySRC().value()->asciiString(),
179 "BD051234 ");
180
181 // Check that certain optional sections have been created
182 size_t mtmsCount = 0;
183 size_t euhCount = 0;
184 size_t udCount = 0;
185
186 for (const auto& section : pel.optionalSections())
187 {
188 if (section->header().id ==
189 static_cast<uint16_t>(SectionID::failingMTMS))
190 {
191 mtmsCount++;
192 }
193 else if (section->header().id ==
194 static_cast<uint16_t>(SectionID::extendedUserHeader))
195 {
196 euhCount++;
197 }
198 else if (section->header().id ==
199 static_cast<uint16_t>(SectionID::userData))
200 {
201 udCount++;
202 }
203 }
204
205 EXPECT_EQ(mtmsCount, 1);
206 EXPECT_EQ(euhCount, 1);
207 EXPECT_EQ(udCount, 2); // AD section and sysInfo section
208 ASSERT_FALSE(pel.isHwCalloutPresent());
209
210 {
211 // The same thing, but without the action flags specified
212 // in the registry, so the constructor should set them.
213 regEntry.actionFlags = std::nullopt;
214
215 PEL pel2{regEntry, 42,
216 timestamp, phosphor::logging::Entry::Level::Error,
217 ad, ffdc,
218 dataIface, journal};
219
220 EXPECT_EQ(pel2.userHeader().actionFlags(), 0xA800);
221 }
222 }
223
224 // Test that when the AdditionalData size is over 16KB that
225 // the PEL that's created is exactly 16KB since the UserData
226 // section that contains all that data was pruned.
TEST_F(PELTest,CreateTooBigADTest)227 TEST_F(PELTest, CreateTooBigADTest)
228 {
229 message::Entry regEntry;
230 uint64_t timestamp = 5;
231
232 regEntry.name = "test";
233 regEntry.subsystem = 5;
234 regEntry.actionFlags = 0xC000;
235 regEntry.src.type = 0xBD;
236 regEntry.src.reasonCode = 0x1234;
237 PelFFDC ffdc;
238
239 // Over the 16KB max PEL size
240 std::map<std::string, std::string> data{{"KEY1", std::string(17000, 'G')}};
241 AdditionalData ad{data};
242 NiceMock<MockDataInterface> dataIface;
243 NiceMock<MockJournal> journal;
244
245 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
246 ad, ffdc, dataIface, journal};
247
248 EXPECT_TRUE(pel.valid());
249 EXPECT_EQ(pel.size(), 16384);
250
251 // Make sure that there are still 2 UD sections.
252 const auto& optSections = pel.optionalSections();
253 auto udCount = std::count_if(
254 optSections.begin(), optSections.end(), [](const auto& section) {
255 return section->header().id ==
256 static_cast<uint16_t>(SectionID::userData);
257 });
258
259 EXPECT_EQ(udCount, 2); // AD section and sysInfo section
260 }
261
262 // Test that we'll create Generic optional sections for sections that
263 // there aren't explicit classes for.
TEST_F(PELTest,GenericSectionTest)264 TEST_F(PELTest, GenericSectionTest)
265 {
266 auto data = pelDataFactory(TestPELType::pelSimple);
267
268 std::vector<uint8_t> section1{
269 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, 0x20, 0x31, 0x06, 0x0F,
276 0x09, 0x22, 0x3A, 0x00};
277
278 std::vector<uint8_t> section2{
279 0x59, 0x59, // ID 'YY'
280 0x00, 0x20, // Size
281 0x01, 0x02, // version, subtype
282 0x03, 0x04, // comp ID
283
284 // some data
285 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F,
286 0x09, 0x22, 0x3A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
287
288 // Add the new sections at the end
289 data.insert(data.end(), section1.begin(), section1.end());
290 data.insert(data.end(), section2.begin(), section2.end());
291
292 // Increment the section count
293 data.at(27) += 2;
294 auto origData = data;
295
296 PEL pel{data};
297
298 const auto& sections = pel.optionalSections();
299
300 bool foundXX = false;
301 bool foundYY = false;
302
303 // Check that we can find these 2 Generic sections
304 for (const auto& section : sections)
305 {
306 if (section->header().id == 0x5858)
307 {
308 foundXX = true;
309 EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
310 }
311 else if (section->header().id == 0x5959)
312 {
313 foundYY = true;
314 EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
315 }
316 }
317
318 EXPECT_TRUE(foundXX);
319 EXPECT_TRUE(foundYY);
320
321 // Now flatten and check
322 auto newData = pel.data();
323
324 EXPECT_EQ(origData, newData);
325 }
326
327 // Test that an invalid section will still get a Generic object
TEST_F(PELTest,InvalidGenericTest)328 TEST_F(PELTest, InvalidGenericTest)
329 {
330 auto data = pelDataFactory(TestPELType::pelSimple);
331
332 // Not a valid section
333 std::vector<uint8_t> section1{0x01, 0x02, 0x03};
334
335 data.insert(data.end(), section1.begin(), section1.end());
336
337 // Increment the section count
338 data.at(27) += 1;
339
340 PEL pel{data};
341 EXPECT_FALSE(pel.valid());
342
343 const auto& sections = pel.optionalSections();
344
345 bool foundGeneric = false;
346 for (const auto& section : sections)
347 {
348 if (dynamic_cast<Generic*>(section.get()) != nullptr)
349 {
350 foundGeneric = true;
351 EXPECT_EQ(section->valid(), false);
352 break;
353 }
354 }
355
356 EXPECT_TRUE(foundGeneric);
357 }
358
359 // Create a UserData section out of AdditionalData
TEST_F(PELTest,MakeUDSectionTest)360 TEST_F(PELTest, MakeUDSectionTest)
361 {
362 std::map<std::string, std::string> ad{{"KEY1", "VALUE1"},
363 {"KEY2", "VALUE2"},
364 {"KEY3", "VALUE3"},
365 {"ESEL", "TEST"}};
366 AdditionalData additionalData{ad};
367
368 auto ud = util::makeADUserDataSection(additionalData);
369
370 EXPECT_TRUE(ud->valid());
371 EXPECT_EQ(ud->header().id, 0x5544);
372 EXPECT_EQ(ud->header().version, 0x01);
373 EXPECT_EQ(ud->header().subType, 0x01);
374 EXPECT_EQ(ud->header().componentID, 0x2000);
375
376 const auto& d = ud->data();
377
378 std::string jsonString{d.begin(), d.end()};
379
380 std::string expectedJSON =
381 R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":"VALUE3"})";
382
383 // The actual data is null padded to a 4B boundary.
384 std::vector<uint8_t> expectedData;
385 expectedData.resize(52, '\0');
386 memcpy(expectedData.data(), expectedJSON.data(), expectedJSON.size());
387
388 EXPECT_EQ(d, expectedData);
389
390 // Ensure we can read this as JSON
391 auto newJSON = nlohmann::json::parse(jsonString);
392 EXPECT_EQ(newJSON["KEY1"], "VALUE1");
393 EXPECT_EQ(newJSON["KEY2"], "VALUE2");
394 EXPECT_EQ(newJSON["KEY3"], "VALUE3");
395 }
396
397 // Create the UserData section that contains system info
TEST_F(PELTest,SysInfoSectionTest)398 TEST_F(PELTest, SysInfoSectionTest)
399 {
400 MockDataInterface dataIface;
401
402 EXPECT_CALL(dataIface, getBMCFWVersionID()).WillOnce(Return("ABCD1234"));
403 EXPECT_CALL(dataIface, getBMCState()).WillOnce(Return("State.Ready"));
404 EXPECT_CALL(dataIface, getChassisState()).WillOnce(Return("State.On"));
405 EXPECT_CALL(dataIface, getHostState()).WillOnce(Return("State.Off"));
406 EXPECT_CALL(dataIface, getBootState())
407 .WillOnce(Return("State.SystemInitComplete"));
408 EXPECT_CALL(dataIface, getSystemIMKeyword())
409 .WillOnce(Return(std::vector<uint8_t>{0, 1, 0x55, 0xAA}));
410
411 std::map<std::string, std::string> ad{{"_PID", std::to_string(getpid())}};
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.
TEST_F(PELTest,SectionJSONTest)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
getJSONFFDC(const fs::path & dir)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
TEST_F(PELTest,MakeJSONFileUDSectionTest)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
getCBORFFDC(const fs::path & dir)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
TEST_F(PELTest,MakeCBORFileUDSectionTest)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
getTextFFDC(const fs::path & dir)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
TEST_F(PELTest,MakeTextFileUDSectionTest)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
getCustomFFDC(const fs::path & dir,const std::vector<uint8_t> & data)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
TEST_F(PELTest,MakeCustomFileUDSectionTest)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
TEST_F(PELTest,CreateWithFFDCTest)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::map<std::string, 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
TEST_F(PELTest,CreateWithDevCalloutsTest)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(dataIface,
852 getHWCalloutFields(
853 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0",
854 _, _, _))
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::map<std::string, std::string> data{
865 {"CALLOUT_ERRNO", "5"},
866 {"CALLOUT_DEVICE_PATH",
867 "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"}};
868
869 AdditionalData ad{data};
870
871 PEL pel{regEntry, 42,
872 timestamp, phosphor::logging::Entry::Level::Error,
873 ad, ffdc,
874 dataIface, journal};
875
876 ASSERT_TRUE(pel.primarySRC().value()->callouts());
877 auto& callouts = pel.primarySRC().value()->callouts()->callouts();
878 ASSERT_EQ(callouts.size(), 1);
879 ASSERT_TRUE(pel.isHwCalloutPresent());
880
881 EXPECT_EQ(callouts[0]->priority(), 'H');
882 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P1");
883
884 auto& fru = callouts[0]->fruIdentity();
885 EXPECT_EQ(fru->getPN().value(), "1234567");
886 EXPECT_EQ(fru->getCCIN().value(), "CCCC");
887 EXPECT_EQ(fru->getSN().value(), "123456789ABC");
888
889 const auto& section = pel.optionalSections().back();
890
891 ASSERT_EQ(section->header().id, 0x5544); // UD
892 auto ud = static_cast<UserData*>(section.get());
893
894 // Check that there was a UserData section added that
895 // contains debug details about the device.
896 const auto& d = ud->data();
897 std::string jsonString{d.begin(), d.end()};
898 auto actualJSON = nlohmann::json::parse(jsonString);
899
900 auto expectedJSON = R"(
901 {
902 "PEL Internal Debug Data": {
903 "SRC": [
904 "I2C: bus: 14 address: 114 dest: proc 0 target"
905 ]
906 }
907 }
908 )"_json;
909
910 EXPECT_TRUE(
911 actualJSON.contains("/PEL Internal Debug Data/SRC"_json_pointer));
912 EXPECT_EQ(actualJSON["PEL Internal Debug Data"]["SRC"],
913 expectedJSON["PEL Internal Debug Data"]["SRC"]);
914 }
915
916 {
917 // Device path not found (wrong i2c addr), so no callouts
918 std::map<std::string, std::string> data{
919 {"CALLOUT_ERRNO", "5"},
920 {"CALLOUT_DEVICE_PATH",
921 "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0099"}};
922
923 AdditionalData ad{data};
924
925 PEL pel{regEntry, 42,
926 timestamp, phosphor::logging::Entry::Level::Error,
927 ad, ffdc,
928 dataIface, journal};
929
930 // no callouts
931 EXPECT_FALSE(pel.primarySRC().value()->callouts());
932
933 // Now check that there was a UserData section
934 // that contains the lookup error.
935 const auto& section = pel.optionalSections().back();
936
937 ASSERT_EQ(section->header().id, 0x5544); // UD
938 auto ud = static_cast<UserData*>(section.get());
939
940 const auto& d = ud->data();
941
942 std::string jsonString{d.begin(), d.end()};
943
944 auto actualJSON = nlohmann::json::parse(jsonString);
945
946 auto expectedJSON =
947 "{\"PEL Internal Debug Data\":{\"SRC\":"
948 "[\"Problem looking up I2C callouts on 14 153: "
949 "[json.exception.out_of_range.403] key '153' not found\"]}}"_json;
950
951 EXPECT_TRUE(
952 actualJSON.contains("/PEL Internal Debug Data/SRC"_json_pointer));
953 EXPECT_EQ(actualJSON["PEL Internal Debug Data"]["SRC"],
954 expectedJSON["PEL Internal Debug Data"]["SRC"]);
955 }
956
957 fs::remove_all(dataPath);
958 }
959
960 // Test PELs when the callouts are passed in using a JSON file.
TEST_F(PELTest,CreateWithJSONCalloutsTest)961 TEST_F(PELTest, CreateWithJSONCalloutsTest)
962 {
963 PelFFDCfile ffdcFile;
964 ffdcFile.format = UserDataFormat::json;
965 ffdcFile.subType = 0xCA; // Callout JSON
966 ffdcFile.version = 1;
967
968 // Write these callouts to a JSON file and pass it into
969 // the PEL as an FFDC file. Also has a duplicate that
970 // will be removed.
971 auto inputJSON = R"([
972 {
973 "Priority": "H",
974 "LocationCode": "P0-C1"
975 },
976 {
977 "Priority": "M",
978 "Procedure": "PROCEDURE"
979 },
980 {
981 "Priority": "L",
982 "Procedure": "PROCEDURE"
983 }
984 ])"_json;
985
986 auto s = inputJSON.dump();
987 std::vector<uint8_t> data{s.begin(), s.end()};
988 auto dir = makeTempDir();
989 ffdcFile.fd = writeFileAndGetFD(dir, data);
990
991 PelFFDC ffdc;
992 ffdc.push_back(std::move(ffdcFile));
993
994 AdditionalData ad;
995 NiceMock<MockDataInterface> dataIface;
996 NiceMock<MockJournal> journal;
997
998 EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
999 .Times(1)
1000 .WillOnce(Return("UXXX-P0-C1"));
1001 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1002 .Times(1)
1003 .WillOnce(Return(
1004 std::vector<std::string>{"/inv/system/chassis/motherboard/bmc"}));
1005 EXPECT_CALL(dataIface, getHWCalloutFields(
1006 "/inv/system/chassis/motherboard/bmc", _, _, _))
1007 .Times(1)
1008 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1009 SetArgReferee<3>("123456789ABC")));
1010
1011 message::Entry regEntry;
1012 regEntry.name = "test";
1013 regEntry.subsystem = 5;
1014 regEntry.actionFlags = 0xC000;
1015 regEntry.src.type = 0xBD;
1016 regEntry.src.reasonCode = 0x1234;
1017
1018 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error,
1019 ad, ffdc, dataIface, journal};
1020
1021 ASSERT_TRUE(pel.valid());
1022 ASSERT_TRUE(pel.primarySRC().value()->callouts());
1023 const auto& callouts = pel.primarySRC().value()->callouts()->callouts();
1024 ASSERT_EQ(callouts.size(), 2);
1025 ASSERT_TRUE(pel.isHwCalloutPresent());
1026
1027 {
1028 EXPECT_EQ(callouts[0]->priority(), 'H');
1029 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1030
1031 auto& fru = callouts[0]->fruIdentity();
1032 EXPECT_EQ(fru->getPN().value(), "1234567");
1033 EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1034 EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1035 EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1036 }
1037 {
1038 EXPECT_EQ(callouts[1]->priority(), 'M');
1039 EXPECT_EQ(callouts[1]->locationCode(), "");
1040
1041 auto& fru = callouts[1]->fruIdentity();
1042 EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1043 EXPECT_EQ(fru->failingComponentType(),
1044 src::FRUIdentity::maintenanceProc);
1045 }
1046 fs::remove_all(dir);
1047 }
1048
1049 // Test PELs with symblic FRU callout.
TEST_F(PELTest,CreateWithJSONSymblicCalloutTest)1050 TEST_F(PELTest, CreateWithJSONSymblicCalloutTest)
1051 {
1052 PelFFDCfile ffdcFile;
1053 ffdcFile.format = UserDataFormat::json;
1054 ffdcFile.subType = 0xCA; // Callout JSON
1055 ffdcFile.version = 1;
1056
1057 // Write these callouts to a JSON file and pass it into
1058 // the PEL as an FFDC file.
1059 auto inputJSON = R"([
1060 {
1061 "Priority": "M",
1062 "Procedure": "SVCDOCS"
1063 }
1064 ])"_json;
1065
1066 auto s = inputJSON.dump();
1067 std::vector<uint8_t> data{s.begin(), s.end()};
1068 auto dir = makeTempDir();
1069 ffdcFile.fd = writeFileAndGetFD(dir, data);
1070
1071 PelFFDC ffdc;
1072 ffdc.push_back(std::move(ffdcFile));
1073
1074 AdditionalData ad;
1075 NiceMock<MockDataInterface> dataIface;
1076 NiceMock<MockJournal> journal;
1077
1078 message::Entry regEntry;
1079 regEntry.name = "test";
1080 regEntry.subsystem = 5;
1081 regEntry.actionFlags = 0xC000;
1082 regEntry.src.type = 0xBD;
1083 regEntry.src.reasonCode = 0x1234;
1084
1085 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error,
1086 ad, ffdc, dataIface, journal};
1087
1088 ASSERT_TRUE(pel.valid());
1089 ASSERT_TRUE(pel.primarySRC().value()->callouts());
1090 const auto& callouts = pel.primarySRC().value()->callouts()->callouts();
1091 ASSERT_EQ(callouts.size(), 1);
1092 ASSERT_FALSE(pel.isHwCalloutPresent());
1093
1094 {
1095 EXPECT_EQ(callouts[0]->priority(), 'M');
1096 EXPECT_EQ(callouts[0]->locationCode(), "");
1097
1098 auto& fru = callouts[0]->fruIdentity();
1099 EXPECT_EQ(fru->getMaintProc().value(), "SVCDOCS");
1100 }
1101 fs::remove_all(dir);
1102 }
1103
TEST_F(PELTest,FlattenLinesTest)1104 TEST_F(PELTest, FlattenLinesTest)
1105 {
1106 std::vector<std::string> msgs{"test1 test2", "test3 test4", "test5 test6"};
1107
1108 auto buffer = util::flattenLines(msgs);
1109
1110 std::string string{"test1 test2\ntest3 test4\ntest5 test6\n"};
1111 std::vector<uint8_t> expected(string.begin(), string.end());
1112
1113 EXPECT_EQ(buffer, expected);
1114 }
1115
checkJournalSection(const std::unique_ptr<Section> & section,const std::string & expected)1116 void checkJournalSection(const std::unique_ptr<Section>& section,
1117 const std::string& expected)
1118 {
1119 ASSERT_EQ(SectionID::userData,
1120 static_cast<SectionID>(section->header().id));
1121 ASSERT_EQ(UserDataFormat::text,
1122 static_cast<UserDataFormat>(section->header().subType));
1123 ASSERT_EQ(section->header().version,
1124 static_cast<uint8_t>(UserDataFormatVersion::text));
1125
1126 auto ud = static_cast<UserData*>(section.get());
1127
1128 std::vector<uint8_t> expectedData(expected.begin(), expected.end());
1129
1130 // PEL sections are 4B aligned so add padding before the compare
1131 while (expectedData.size() % 4 != 0)
1132 {
1133 expectedData.push_back('\0');
1134 }
1135
1136 EXPECT_EQ(ud->data(), expectedData);
1137 }
1138
TEST_F(PELTest,CaptureJournalTest)1139 TEST_F(PELTest, CaptureJournalTest)
1140 {
1141 message::Entry regEntry;
1142 uint64_t timestamp = 5;
1143
1144 regEntry.name = "test";
1145 regEntry.subsystem = 5;
1146 regEntry.actionFlags = 0xC000;
1147 regEntry.src.type = 0xBD;
1148 regEntry.src.reasonCode = 0x1234;
1149
1150 std::map<std::string, std::string> data{};
1151 AdditionalData ad{data};
1152 NiceMock<MockDataInterface> dataIface;
1153 NiceMock<MockJournal> journal;
1154 PelFFDC ffdc;
1155
1156 size_t pelSectsWithOneUD{0};
1157
1158 {
1159 // Capture 5 lines from the journal into a single UD section
1160 message::JournalCapture jc = size_t{5};
1161 regEntry.journalCapture = jc;
1162
1163 std::vector<std::string> msgs{"test1 test2", "test3 test4",
1164 "test5 test6", "4", "5"};
1165
1166 EXPECT_CALL(journal, getMessages("", 5)).WillOnce(Return(msgs));
1167
1168 PEL pel{regEntry, 42,
1169 timestamp, phosphor::logging::Entry::Level::Error,
1170 ad, ffdc,
1171 dataIface, journal};
1172
1173 // Check the generated UserData section
1174 std::string expected{"test1 test2\ntest3 test4\ntest5 test6\n4\n5\n"};
1175
1176 checkJournalSection(pel.optionalSections().back(), expected);
1177
1178 // Save for upcoming testcases
1179 pelSectsWithOneUD = pel.privateHeader().sectionCount();
1180 }
1181
1182 {
1183 // Attempt to capture too many journal entries so the
1184 // section gets dropped.
1185 message::JournalCapture jc = size_t{1};
1186 regEntry.journalCapture = jc;
1187
1188 EXPECT_CALL(journal, sync()).Times(1);
1189
1190 // A 20000 byte line won't fit in a PEL
1191 EXPECT_CALL(journal, getMessages("", 1))
1192 .WillOnce(
1193 Return(std::vector<std::string>{std::string(20000, 'x')}));
1194
1195 PEL pel{regEntry, 42,
1196 timestamp, phosphor::logging::Entry::Level::Error,
1197 ad, ffdc,
1198 dataIface, journal};
1199
1200 // Check for 1 fewer sections than in the previous PEL
1201 EXPECT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD - 1);
1202 }
1203
1204 // Capture 3 different journal sections
1205 {
1206 message::AppCaptureList captureList{
1207 message::AppCapture{"app1", 3},
1208 message::AppCapture{"app2", 4},
1209 message::AppCapture{"app3", 1},
1210 };
1211 message::JournalCapture jc = captureList;
1212 regEntry.journalCapture = jc;
1213
1214 std::vector<std::string> app1{"A B", "C D", "E F"};
1215 std::vector<std::string> app2{"1 2", "3 4", "5 6", "7 8"};
1216 std::vector<std::string> app3{"a b c"};
1217
1218 std::string expected1{"A B\nC D\nE F\n"};
1219 std::string expected2{"1 2\n3 4\n5 6\n7 8\n"};
1220 std::string expected3{"a b c\n"};
1221
1222 EXPECT_CALL(journal, sync()).Times(1);
1223 EXPECT_CALL(journal, getMessages("app1", 3)).WillOnce(Return(app1));
1224 EXPECT_CALL(journal, getMessages("app2", 4)).WillOnce(Return(app2));
1225 EXPECT_CALL(journal, getMessages("app3", 1)).WillOnce(Return(app3));
1226
1227 PEL pel{regEntry, 42,
1228 timestamp, phosphor::logging::Entry::Level::Error,
1229 ad, ffdc,
1230 dataIface, journal};
1231
1232 // Two more sections than the 1 extra UD section in the first
1233 // testcase
1234 ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD + 2);
1235
1236 const auto& optionalSections = pel.optionalSections();
1237 auto numOptSections = optionalSections.size();
1238
1239 checkJournalSection(optionalSections[numOptSections - 3], expected1);
1240 checkJournalSection(optionalSections[numOptSections - 2], expected2);
1241 checkJournalSection(optionalSections[numOptSections - 1], expected3);
1242 }
1243
1244 {
1245 // One section gets saved, and one is too big and gets dropped
1246 message::AppCaptureList captureList{
1247 message::AppCapture{"app4", 2},
1248 message::AppCapture{"app5", 1},
1249 };
1250 message::JournalCapture jc = captureList;
1251 regEntry.journalCapture = jc;
1252
1253 std::vector<std::string> app4{"w x", "y z"};
1254 std::string expected4{"w x\ny z\n"};
1255
1256 EXPECT_CALL(journal, sync()).Times(1);
1257
1258 EXPECT_CALL(journal, getMessages("app4", 2)).WillOnce(Return(app4));
1259
1260 // A 20000 byte line won't fit in a PEL
1261 EXPECT_CALL(journal, getMessages("app5", 1))
1262 .WillOnce(
1263 Return(std::vector<std::string>{std::string(20000, 'x')}));
1264
1265 PEL pel{regEntry, 42,
1266 timestamp, phosphor::logging::Entry::Level::Error,
1267 ad, ffdc,
1268 dataIface, journal};
1269
1270 // The last section should have been dropped, so same as first TC
1271 ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD);
1272
1273 checkJournalSection(pel.optionalSections().back(), expected4);
1274 }
1275 }
1276
1277 // API to collect and parse the User Data section of the PEL.
getDIMMInfo(const auto & pel)1278 nlohmann::json getDIMMInfo(const auto& pel)
1279 {
1280 nlohmann::json dimmInfo{};
1281 auto hasDIMMInfo = [&dimmInfo](const auto& optionalSection) {
1282 if (optionalSection->header().id !=
1283 static_cast<uint16_t>(SectionID::userData))
1284 {
1285 return false;
1286 }
1287 else
1288 {
1289 auto userData = static_cast<UserData*>(optionalSection.get());
1290
1291 // convert the userdata section to string and then parse in to
1292 // json format
1293 std::string userDataString{userData->data().begin(),
1294 userData->data().end()};
1295 nlohmann::json userDataJson = nlohmann::json::parse(userDataString);
1296
1297 if (userDataJson.contains("DIMMs Additional Info"))
1298 {
1299 dimmInfo = userDataJson.at("DIMMs Additional Info");
1300 }
1301 else if (
1302 userDataJson.contains(
1303 "/PEL Internal Debug Data/DIMMs Info Fetch Error"_json_pointer))
1304 {
1305 dimmInfo = userDataJson.at(
1306 "/PEL Internal Debug Data/DIMMs Info Fetch Error"_json_pointer);
1307 }
1308 else
1309 {
1310 return false;
1311 }
1312 return true;
1313 }
1314 };
1315 std::ranges::any_of(pel.optionalSections(), hasDIMMInfo);
1316
1317 return dimmInfo;
1318 }
1319
1320 // Test whether the DIMM callouts manufacturing info is getting added to the
1321 // SysInfo User Data section of the PEL
TEST_F(PELTest,TestDimmsCalloutInfo)1322 TEST_F(PELTest, TestDimmsCalloutInfo)
1323 {
1324 {
1325 message::Entry entry;
1326 uint64_t timestamp = 5;
1327 AdditionalData ad;
1328 NiceMock<MockDataInterface> dataIface;
1329 NiceMock<MockJournal> journal;
1330 PelFFDC ffdc;
1331
1332 // When callouts contain DIMM callouts.
1333 entry.callouts = R"(
1334 [
1335 {
1336 "CalloutList": [
1337 {
1338 "Priority": "high",
1339 "LocCode": "P0-DIMM0"
1340 },
1341 {
1342 "Priority": "low",
1343 "LocCode": "P0-DIMM1"
1344 }
1345 ]
1346 }
1347 ]
1348 )"_json;
1349
1350 EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM0", 0))
1351 .WillOnce(Return("U98D-P0-DIMM0"));
1352 EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM1", 0))
1353 .WillOnce(Return("U98D-P0-DIMM1"));
1354
1355 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM0", 0, false))
1356 .WillOnce(Return(std::vector<std::string>{
1357 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"}));
1358 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM1", 0, false))
1359 .WillOnce(Return(std::vector<std::string>{
1360 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm1"}));
1361
1362 std::vector<uint8_t> diValue{128, 74};
1363 EXPECT_CALL(dataIface, getDIProperty("U98D-P0-DIMM0"))
1364 .WillOnce(Return(diValue));
1365 EXPECT_CALL(dataIface, getDIProperty("U98D-P0-DIMM1"))
1366 .WillOnce(Return(diValue));
1367
1368 // Add some location code in expanded format to DIMM cache memory
1369 dataIface.addDIMMLocCode("U98D-P0-DIMM0", true);
1370 dataIface.addDIMMLocCode("U98D-P0-DIMM1", true);
1371
1372 PEL pel{entry, 42, timestamp, phosphor::logging::Entry::Level::Error,
1373 ad, ffdc, dataIface, journal};
1374 nlohmann::json dimmInfoJson = getDIMMInfo(pel);
1375
1376 nlohmann::json expected_data = R"(
1377 [
1378 {
1379 "Location Code": "U98D-P0-DIMM0",
1380 "DRAM Manufacturer ID": [
1381 "0x80",
1382 "0x4a"
1383 ]
1384 },
1385 {
1386 "Location Code": "U98D-P0-DIMM1",
1387 "DRAM Manufacturer ID": [
1388 "0x80",
1389 "0x4a"
1390 ]
1391 }
1392 ]
1393 )"_json;
1394 EXPECT_EQ(expected_data, dimmInfoJson);
1395 }
1396 }
1397
1398 // When PEL has FRU callouts but PHAL is not enabled.
TEST_F(PELTest,TestNoDimmsCallout)1399 TEST_F(PELTest, TestNoDimmsCallout)
1400 {
1401 message::Entry entry;
1402 uint64_t timestamp = 5;
1403 AdditionalData ad;
1404 NiceMock<MockDataInterface> dataIface;
1405 NiceMock<MockJournal> journal;
1406 PelFFDC ffdc;
1407
1408 entry.callouts = R"(
1409 [
1410 {
1411 "CalloutList": [
1412 {
1413 "Priority": "high",
1414 "LocCode": "P0-PROC0"
1415 }
1416 ]
1417 }
1418 ]
1419 )"_json;
1420
1421 EXPECT_CALL(dataIface, expandLocationCode("P0-PROC0", 0))
1422 .WillOnce(Return("U98D-P0-PROC0"));
1423
1424 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-PROC0", 0, false))
1425 .WillOnce(Return(std::vector<std::string>{
1426 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dcm0/cpu0"}));
1427
1428 // Add some location code in expanded format to DIMM cache memory
1429 dataIface.addDIMMLocCode("U98D-P0-PROC0", false);
1430
1431 PEL pel{entry, 42, timestamp, phosphor::logging::Entry::Level::Error,
1432 ad, ffdc, dataIface, journal};
1433
1434 nlohmann::json dimmInfoJson = getDIMMInfo(pel);
1435
1436 nlohmann::json expected_data{};
1437
1438 EXPECT_EQ(expected_data, dimmInfoJson);
1439 }
1440
1441 // When the PEL doesn't contain any type of callouts
TEST_F(PELTest,TestDimmsCalloutInfoWithNoCallouts)1442 TEST_F(PELTest, TestDimmsCalloutInfoWithNoCallouts)
1443 {
1444 message::Entry entry;
1445 uint64_t timestamp = 5;
1446 AdditionalData ad;
1447 NiceMock<MockDataInterface> dataIface;
1448 NiceMock<MockJournal> journal;
1449 PelFFDC ffdc;
1450
1451 PEL pel{entry, 42, timestamp, phosphor::logging::Entry::Level::Error,
1452 ad, ffdc, dataIface, journal};
1453
1454 nlohmann::json dimmInfoJson = getDIMMInfo(pel);
1455
1456 nlohmann::json expected_data{};
1457
1458 EXPECT_EQ(expected_data, dimmInfoJson);
1459 }
1460
1461 // When the PEL has DIMM callouts, but failed to fetch DI property value
TEST_F(PELTest,TestDimmsCalloutInfoDIFailure)1462 TEST_F(PELTest, TestDimmsCalloutInfoDIFailure)
1463 {
1464 {
1465 message::Entry entry;
1466 uint64_t timestamp = 5;
1467 AdditionalData ad;
1468 NiceMock<MockDataInterface> dataIface;
1469 NiceMock<MockJournal> journal;
1470 PelFFDC ffdc;
1471
1472 entry.callouts = R"(
1473 [
1474 {
1475 "CalloutList": [
1476 {
1477 "Priority": "high",
1478 "LocCode": "P0-DIMM0"
1479 }
1480 ]
1481 }
1482 ]
1483 )"_json;
1484
1485 EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM0", 0))
1486 .WillOnce(Return("U98D-P0-DIMM0"));
1487
1488 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM0", 0, false))
1489 .WillOnce(Return(std::vector<std::string>{
1490 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"}));
1491
1492 EXPECT_CALL(dataIface, getDIProperty("U98D-P0-DIMM0"))
1493 .WillOnce(Return(std::nullopt));
1494
1495 // Add some location code in expanded format to DIMM cache memory
1496 dataIface.addDIMMLocCode("U98D-P0-DIMM0", true);
1497
1498 PEL pel{entry, 42, timestamp, phosphor::logging::Entry::Level::Error,
1499 ad, ffdc, dataIface, journal};
1500
1501 nlohmann::json dimmInfoJson = getDIMMInfo(pel);
1502
1503 nlohmann::json expected_data = R"(
1504 [
1505 "Failed reading DI property from VINI Interface for the LocationCode:[U98D-P0-DIMM0]"
1506 ]
1507 )"_json;
1508
1509 EXPECT_EQ(expected_data, dimmInfoJson);
1510 }
1511 }
1512