blob: 43241b5914c8942e8a5a0f508aa2a4280ad3c870 [file] [log] [blame]
Alexander Hansen40fb5492025-10-28 17:56:12 +01001// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: Copyright 2019 IBM Corporation
3
Matt Spinlerb8323632019-09-20 15:11:04 -05004#include "elog_entry.hpp"
Matt Spinler131870c2019-09-25 13:29:04 -05005#include "extensions/openpower-pels/generic.hpp"
Matt Spinlercb6b0592019-07-16 15:58:51 -05006#include "extensions/openpower-pels/pel.hpp"
Matt Spinleraa659472019-10-23 09:26:48 -05007#include "mocks.hpp"
Matt Spinlercb6b0592019-07-16 15:58:51 -05008#include "pel_utils.hpp"
9
10#include <filesystem>
11#include <fstream>
Arya K Padmand8ae6182024-07-19 06:25:10 -050012#include <optional>
Matt Spinlercb6b0592019-07-16 15:58:51 -050013
14#include <gtest/gtest.h>
15
16namespace fs = std::filesystem;
17using namespace openpower::pels;
Matt Spinler0a90a852020-06-04 13:18:27 -050018using ::testing::_;
William A. Kennington IIIb41fa542021-05-29 14:45:16 -070019using ::testing::DoAll;
Matt Spinler56ad2a02020-03-26 14:00:52 -050020using ::testing::NiceMock;
Matt Spinler677381b2020-01-23 10:04:29 -060021using ::testing::Return;
Matt Spinler0a90a852020-06-04 13:18:27 -050022using ::testing::SetArgReferee;
Matt Spinlercb6b0592019-07-16 15:58:51 -050023
24class PELTest : public CleanLogID
Patrick Williams2544b412022-10-04 08:41:06 -050025{};
Matt Spinlercb6b0592019-07-16 15:58:51 -050026
Matt Spinler5b289b22020-03-26 14:27:19 -050027fs::path makeTempDir()
28{
29 char path[] = "/tmp/tempdirXXXXXX";
30 std::filesystem::path dir = mkdtemp(path);
31 return dir;
32}
33
34int writeFileAndGetFD(const fs::path& dir, const std::vector<uint8_t>& data)
35{
36 static size_t count = 0;
37 fs::path path = dir / (std::string{"file"} + std::to_string(count));
38 std::ofstream stream{path};
39 count++;
40
41 stream.write(reinterpret_cast<const char*>(data.data()), data.size());
42 stream.close();
43
44 FILE* fp = fopen(path.c_str(), "r");
45 return fileno(fp);
46}
47
Matt Spinlercb6b0592019-07-16 15:58:51 -050048TEST_F(PELTest, FlattenTest)
49{
Matt Spinler42828bd2019-10-11 10:39:30 -050050 auto data = pelDataFactory(TestPELType::pelSimple);
Matt Spinler42828bd2019-10-11 10:39:30 -050051 auto pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -050052
53 // Check a few fields
54 EXPECT_TRUE(pel->valid());
55 EXPECT_EQ(pel->id(), 0x80818283);
56 EXPECT_EQ(pel->plid(), 0x50515253);
Matt Spinler97d19b42019-10-29 11:34:03 -050057 EXPECT_EQ(pel->userHeader().subsystem(), 0x10);
58 EXPECT_EQ(pel->userHeader().actionFlags(), 0x80C0);
Matt Spinlercb6b0592019-07-16 15:58:51 -050059
60 // Test that data in == data out
61 auto flattenedData = pel->data();
Matt Spinlerf1b46ff2020-01-22 14:10:04 -060062 EXPECT_EQ(data, flattenedData);
63 EXPECT_EQ(flattenedData.size(), pel->size());
Matt Spinlercb6b0592019-07-16 15:58:51 -050064}
65
66TEST_F(PELTest, CommitTimeTest)
67{
Matt Spinler42828bd2019-10-11 10:39:30 -050068 auto data = pelDataFactory(TestPELType::pelSimple);
69 auto pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -050070
71 auto origTime = pel->commitTime();
72 pel->setCommitTime();
73 auto newTime = pel->commitTime();
74
Matt Spinlerf1b46ff2020-01-22 14:10:04 -060075 EXPECT_NE(origTime, newTime);
Matt Spinlercb6b0592019-07-16 15:58:51 -050076
77 // Make a new PEL and check new value is still there
78 auto newData = pel->data();
79 auto newPel = std::make_unique<PEL>(newData);
Matt Spinlerf1b46ff2020-01-22 14:10:04 -060080 EXPECT_EQ(newTime, newPel->commitTime());
Matt Spinlercb6b0592019-07-16 15:58:51 -050081}
82
83TEST_F(PELTest, AssignIDTest)
84{
Matt Spinler42828bd2019-10-11 10:39:30 -050085 auto data = pelDataFactory(TestPELType::pelSimple);
86 auto pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -050087
88 auto origID = pel->id();
89 pel->assignID();
90 auto newID = pel->id();
91
Matt Spinlerf1b46ff2020-01-22 14:10:04 -060092 EXPECT_NE(origID, newID);
Matt Spinlercb6b0592019-07-16 15:58:51 -050093
94 // Make a new PEL and check new value is still there
95 auto newData = pel->data();
96 auto newPel = std::make_unique<PEL>(newData);
Matt Spinlerf1b46ff2020-01-22 14:10:04 -060097 EXPECT_EQ(newID, newPel->id());
Matt Spinlercb6b0592019-07-16 15:58:51 -050098}
99
100TEST_F(PELTest, WithLogIDTest)
101{
Matt Spinler42828bd2019-10-11 10:39:30 -0500102 auto data = pelDataFactory(TestPELType::pelSimple);
103 auto pel = std::make_unique<PEL>(data, 0x42);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500104
105 EXPECT_TRUE(pel->valid());
106 EXPECT_EQ(pel->obmcLogID(), 0x42);
107}
108
109TEST_F(PELTest, InvalidPELTest)
110{
Matt Spinler42828bd2019-10-11 10:39:30 -0500111 auto data = pelDataFactory(TestPELType::pelSimple);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500112
113 // Too small
Matt Spinler42828bd2019-10-11 10:39:30 -0500114 data.resize(PrivateHeader::flattenedSize());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500115
Matt Spinler42828bd2019-10-11 10:39:30 -0500116 auto pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500117
Matt Spinler97d19b42019-10-29 11:34:03 -0500118 EXPECT_TRUE(pel->privateHeader().valid());
119 EXPECT_FALSE(pel->userHeader().valid());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500120 EXPECT_FALSE(pel->valid());
121
Matt Spinlercb6b0592019-07-16 15:58:51 -0500122 // Now corrupt the private header
Matt Spinler42828bd2019-10-11 10:39:30 -0500123 data = pelDataFactory(TestPELType::pelSimple);
124 data.at(0) = 0;
125 pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500126
Matt Spinler97d19b42019-10-29 11:34:03 -0500127 EXPECT_FALSE(pel->privateHeader().valid());
128 EXPECT_TRUE(pel->userHeader().valid());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500129 EXPECT_FALSE(pel->valid());
130}
131
132TEST_F(PELTest, EmptyDataTest)
133{
134 std::vector<uint8_t> data;
135 auto pel = std::make_unique<PEL>(data);
136
Matt Spinler97d19b42019-10-29 11:34:03 -0500137 EXPECT_FALSE(pel->privateHeader().valid());
138 EXPECT_FALSE(pel->userHeader().valid());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500139 EXPECT_FALSE(pel->valid());
140}
Matt Spinlerb8323632019-09-20 15:11:04 -0500141
142TEST_F(PELTest, CreateFromRegistryTest)
143{
144 message::Entry regEntry;
145 uint64_t timestamp = 5;
146
147 regEntry.name = "test";
148 regEntry.subsystem = 5;
149 regEntry.actionFlags = 0xC000;
Matt Spinlerbd716f02019-10-15 10:54:11 -0500150 regEntry.src.type = 0xBD;
151 regEntry.src.reasonCode = 0x1234;
Matt Spinlerb8323632019-09-20 15:11:04 -0500152
Patrick Williamse5940632024-11-22 20:47:58 -0500153 std::map<std::string, std::string> data{{"KEY1", "VALUE1"}};
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600154 AdditionalData ad{data};
Matt Spinler56ad2a02020-03-26 14:00:52 -0500155 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600156 NiceMock<MockJournal> journal;
Matt Spinler56ad2a02020-03-26 14:00:52 -0500157 PelFFDC ffdc;
Matt Spinlerbd716f02019-10-15 10:54:11 -0500158
Matt Spinler56ad2a02020-03-26 14:00:52 -0500159 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
Matt Spinler9d921092022-12-15 11:54:49 -0600160 ad, ffdc, dataIface, journal};
Matt Spinlerb8323632019-09-20 15:11:04 -0500161
162 EXPECT_TRUE(pel.valid());
Matt Spinler97d19b42019-10-29 11:34:03 -0500163 EXPECT_EQ(pel.privateHeader().obmcLogID(), 42);
164 EXPECT_EQ(pel.userHeader().severity(), 0x40);
Matt Spinlerb8323632019-09-20 15:11:04 -0500165
Matt Spinlerbd716f02019-10-15 10:54:11 -0500166 EXPECT_EQ(pel.primarySRC().value()->asciiString(),
167 "BD051234 ");
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600168
169 // Check that certain optional sections have been created
170 size_t mtmsCount = 0;
171 size_t euhCount = 0;
172 size_t udCount = 0;
173
174 for (const auto& section : pel.optionalSections())
175 {
176 if (section->header().id ==
177 static_cast<uint16_t>(SectionID::failingMTMS))
178 {
179 mtmsCount++;
180 }
181 else if (section->header().id ==
182 static_cast<uint16_t>(SectionID::extendedUserHeader))
183 {
184 euhCount++;
185 }
186 else if (section->header().id ==
187 static_cast<uint16_t>(SectionID::userData))
188 {
189 udCount++;
190 }
191 }
192
193 EXPECT_EQ(mtmsCount, 1);
194 EXPECT_EQ(euhCount, 1);
195 EXPECT_EQ(udCount, 2); // AD section and sysInfo section
Andrew Geisslerf8e750d2022-01-14 14:56:13 -0600196 ASSERT_FALSE(pel.isHwCalloutPresent());
Matt Spinler1f93c592020-09-10 10:43:08 -0500197
198 {
199 // The same thing, but without the action flags specified
200 // in the registry, so the constructor should set them.
201 regEntry.actionFlags = std::nullopt;
202
Patrick Williams075c7922024-08-16 15:19:49 -0400203 PEL pel2{regEntry, 42,
204 timestamp, phosphor::logging::Entry::Level::Error,
205 ad, ffdc,
206 dataIface, journal};
Matt Spinler1f93c592020-09-10 10:43:08 -0500207
208 EXPECT_EQ(pel2.userHeader().actionFlags(), 0xA800);
209 }
Matt Spinlerb8323632019-09-20 15:11:04 -0500210}
Matt Spinler131870c2019-09-25 13:29:04 -0500211
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500212// Test that when the AdditionalData size is over 16KB that
213// the PEL that's created is exactly 16KB since the UserData
214// section that contains all that data was pruned.
215TEST_F(PELTest, CreateTooBigADTest)
216{
217 message::Entry regEntry;
218 uint64_t timestamp = 5;
219
220 regEntry.name = "test";
221 regEntry.subsystem = 5;
222 regEntry.actionFlags = 0xC000;
223 regEntry.src.type = 0xBD;
224 regEntry.src.reasonCode = 0x1234;
Matt Spinler56ad2a02020-03-26 14:00:52 -0500225 PelFFDC ffdc;
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500226
227 // Over the 16KB max PEL size
Patrick Williamse5940632024-11-22 20:47:58 -0500228 std::map<std::string, std::string> data{{"KEY1", std::string(17000, 'G')}};
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500229 AdditionalData ad{data};
Matt Spinler56ad2a02020-03-26 14:00:52 -0500230 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600231 NiceMock<MockJournal> journal;
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500232
Matt Spinler56ad2a02020-03-26 14:00:52 -0500233 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
Matt Spinler9d921092022-12-15 11:54:49 -0600234 ad, ffdc, dataIface, journal};
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500235
236 EXPECT_TRUE(pel.valid());
237 EXPECT_EQ(pel.size(), 16384);
238
239 // Make sure that there are still 2 UD sections.
Matt Spinlerbe952d22022-07-01 11:30:11 -0500240 const auto& optSections = pel.optionalSections();
Patrick Williams075c7922024-08-16 15:19:49 -0400241 auto udCount = std::count_if(
242 optSections.begin(), optSections.end(), [](const auto& section) {
243 return section->header().id ==
244 static_cast<uint16_t>(SectionID::userData);
245 });
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500246
247 EXPECT_EQ(udCount, 2); // AD section and sysInfo section
248}
249
Matt Spinler131870c2019-09-25 13:29:04 -0500250// Test that we'll create Generic optional sections for sections that
251// there aren't explicit classes for.
252TEST_F(PELTest, GenericSectionTest)
253{
Matt Spinler42828bd2019-10-11 10:39:30 -0500254 auto data = pelDataFactory(TestPELType::pelSimple);
Matt Spinler131870c2019-09-25 13:29:04 -0500255
Patrick Williams075c7922024-08-16 15:19:49 -0400256 std::vector<uint8_t> section1{
257 0x58, 0x58, // ID 'XX'
258 0x00, 0x18, // Size
259 0x01, 0x02, // version, subtype
260 0x03, 0x04, // comp ID
Matt Spinler131870c2019-09-25 13:29:04 -0500261
Patrick Williams075c7922024-08-16 15:19:49 -0400262 // some data
263 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F,
264 0x09, 0x22, 0x3A, 0x00};
Matt Spinler131870c2019-09-25 13:29:04 -0500265
266 std::vector<uint8_t> section2{
267 0x59, 0x59, // ID 'YY'
268 0x00, 0x20, // Size
269 0x01, 0x02, // version, subtype
270 0x03, 0x04, // comp ID
271
272 // some data
273 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F,
274 0x09, 0x22, 0x3A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
275
276 // Add the new sections at the end
Matt Spinler42828bd2019-10-11 10:39:30 -0500277 data.insert(data.end(), section1.begin(), section1.end());
278 data.insert(data.end(), section2.begin(), section2.end());
Matt Spinler131870c2019-09-25 13:29:04 -0500279
280 // Increment the section count
Matt Spinler42828bd2019-10-11 10:39:30 -0500281 data.at(27) += 2;
282 auto origData = data;
Matt Spinler131870c2019-09-25 13:29:04 -0500283
Matt Spinler42828bd2019-10-11 10:39:30 -0500284 PEL pel{data};
Matt Spinler131870c2019-09-25 13:29:04 -0500285
286 const auto& sections = pel.optionalSections();
287
288 bool foundXX = false;
289 bool foundYY = false;
290
291 // Check that we can find these 2 Generic sections
292 for (const auto& section : sections)
293 {
294 if (section->header().id == 0x5858)
295 {
296 foundXX = true;
297 EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
298 }
299 else if (section->header().id == 0x5959)
300 {
301 foundYY = true;
302 EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
303 }
304 }
305
306 EXPECT_TRUE(foundXX);
307 EXPECT_TRUE(foundYY);
Matt Spinler07eefc52019-09-26 11:18:26 -0500308
309 // Now flatten and check
310 auto newData = pel.data();
311
312 EXPECT_EQ(origData, newData);
Matt Spinler131870c2019-09-25 13:29:04 -0500313}
314
315// Test that an invalid section will still get a Generic object
316TEST_F(PELTest, InvalidGenericTest)
317{
Matt Spinler42828bd2019-10-11 10:39:30 -0500318 auto data = pelDataFactory(TestPELType::pelSimple);
Matt Spinler131870c2019-09-25 13:29:04 -0500319
320 // Not a valid section
321 std::vector<uint8_t> section1{0x01, 0x02, 0x03};
322
Matt Spinler42828bd2019-10-11 10:39:30 -0500323 data.insert(data.end(), section1.begin(), section1.end());
Matt Spinler131870c2019-09-25 13:29:04 -0500324
325 // Increment the section count
Matt Spinler42828bd2019-10-11 10:39:30 -0500326 data.at(27) += 1;
Matt Spinler131870c2019-09-25 13:29:04 -0500327
Matt Spinler42828bd2019-10-11 10:39:30 -0500328 PEL pel{data};
Matt Spinler131870c2019-09-25 13:29:04 -0500329 EXPECT_FALSE(pel.valid());
330
331 const auto& sections = pel.optionalSections();
332
333 bool foundGeneric = false;
334 for (const auto& section : sections)
335 {
336 if (dynamic_cast<Generic*>(section.get()) != nullptr)
337 {
338 foundGeneric = true;
339 EXPECT_EQ(section->valid(), false);
340 break;
341 }
342 }
343
344 EXPECT_TRUE(foundGeneric);
345}
Matt Spinlerafa857c2019-10-24 13:03:46 -0500346
347// Create a UserData section out of AdditionalData
348TEST_F(PELTest, MakeUDSectionTest)
349{
Patrick Williamse5940632024-11-22 20:47:58 -0500350 std::map<std::string, std::string> ad{{"KEY1", "VALUE1"},
351 {"KEY2", "VALUE2"},
352 {"KEY3", "VALUE3"},
353 {"ESEL", "TEST"}};
Matt Spinlerafa857c2019-10-24 13:03:46 -0500354 AdditionalData additionalData{ad};
355
356 auto ud = util::makeADUserDataSection(additionalData);
357
358 EXPECT_TRUE(ud->valid());
359 EXPECT_EQ(ud->header().id, 0x5544);
360 EXPECT_EQ(ud->header().version, 0x01);
361 EXPECT_EQ(ud->header().subType, 0x01);
362 EXPECT_EQ(ud->header().componentID, 0x2000);
363
364 const auto& d = ud->data();
365
366 std::string jsonString{d.begin(), d.end()};
Matt Spinler53407be2019-11-18 09:16:31 -0600367
368 std::string expectedJSON =
Matt Spinlerafa857c2019-10-24 13:03:46 -0500369 R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":"VALUE3"})";
Matt Spinler53407be2019-11-18 09:16:31 -0600370
371 // The actual data is null padded to a 4B boundary.
372 std::vector<uint8_t> expectedData;
373 expectedData.resize(52, '\0');
374 memcpy(expectedData.data(), expectedJSON.data(), expectedJSON.size());
375
376 EXPECT_EQ(d, expectedData);
Matt Spinlerafa857c2019-10-24 13:03:46 -0500377
378 // Ensure we can read this as JSON
379 auto newJSON = nlohmann::json::parse(jsonString);
380 EXPECT_EQ(newJSON["KEY1"], "VALUE1");
381 EXPECT_EQ(newJSON["KEY2"], "VALUE2");
382 EXPECT_EQ(newJSON["KEY3"], "VALUE3");
Matt Spinler97d19b42019-10-29 11:34:03 -0500383}
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600384
385// Create the UserData section that contains system info
Matt Spinler677381b2020-01-23 10:04:29 -0600386TEST_F(PELTest, SysInfoSectionTest)
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600387{
388 MockDataInterface dataIface;
389
Matt Spinler677381b2020-01-23 10:04:29 -0600390 EXPECT_CALL(dataIface, getBMCFWVersionID()).WillOnce(Return("ABCD1234"));
Matt Spinler4aa23a12020-02-03 15:05:09 -0600391 EXPECT_CALL(dataIface, getBMCState()).WillOnce(Return("State.Ready"));
392 EXPECT_CALL(dataIface, getChassisState()).WillOnce(Return("State.On"));
393 EXPECT_CALL(dataIface, getHostState()).WillOnce(Return("State.Off"));
Sumit Kumar2c36fdd2021-09-21 03:12:11 -0500394 EXPECT_CALL(dataIface, getBootState())
395 .WillOnce(Return("State.SystemInitComplete"));
Ben Tynere32b7e72021-05-18 12:38:40 -0500396 EXPECT_CALL(dataIface, getSystemIMKeyword())
397 .WillOnce(Return(std::vector<uint8_t>{0, 1, 0x55, 0xAA}));
Matt Spinler677381b2020-01-23 10:04:29 -0600398
Patrick Williamse5940632024-11-22 20:47:58 -0500399 std::map<std::string, std::string> ad{{"_PID", std::to_string(getpid())}};
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600400 AdditionalData additionalData{ad};
401
402 auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface);
403
404 EXPECT_TRUE(ud->valid());
405 EXPECT_EQ(ud->header().id, 0x5544);
406 EXPECT_EQ(ud->header().version, 0x01);
407 EXPECT_EQ(ud->header().subType, 0x01);
408 EXPECT_EQ(ud->header().componentID, 0x2000);
409
410 // Pull out the JSON data and check it.
411 const auto& d = ud->data();
412 std::string jsonString{d.begin(), d.end()};
413 auto json = nlohmann::json::parse(jsonString);
414
Patrick Williamsd9f0d642021-04-21 15:43:21 -0500415 // Ensure the 'Process Name' entry contains the name of this test
416 // executable.
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600417 auto name = json["Process Name"].get<std::string>();
Patrick Williamsd9f0d642021-04-21 15:43:21 -0500418 auto found = (name.find("pel_test") != std::string::npos) ||
419 (name.find("test-openpower-pels-pel") != std::string::npos);
420 EXPECT_TRUE(found);
421 // @TODO(stwcx): remove 'pel_test' when removing autotools.
Matt Spinler677381b2020-01-23 10:04:29 -0600422
Matt Spinlerc2b8a512021-05-21 12:44:42 -0600423 auto version = json["FW Version ID"].get<std::string>();
Matt Spinler677381b2020-01-23 10:04:29 -0600424 EXPECT_EQ(version, "ABCD1234");
Matt Spinler4aa23a12020-02-03 15:05:09 -0600425
426 auto state = json["BMCState"].get<std::string>();
427 EXPECT_EQ(state, "Ready");
428
429 state = json["ChassisState"].get<std::string>();
430 EXPECT_EQ(state, "On");
431
432 state = json["HostState"].get<std::string>();
433 EXPECT_EQ(state, "Off");
Ben Tynere32b7e72021-05-18 12:38:40 -0500434
Sumit Kumar2c36fdd2021-09-21 03:12:11 -0500435 state = json["BootState"].get<std::string>();
436 EXPECT_EQ(state, "SystemInitComplete");
437
Ben Tynere32b7e72021-05-18 12:38:40 -0500438 auto keyword = json["System IM"].get<std::string>();
439 EXPECT_EQ(keyword, "000155AA");
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600440}
Matt Spinlerce3f4502020-01-22 15:44:35 -0600441
442// Test that the sections that override
443// virtual std::optional<std::string> Section::getJSON() const
444// return valid JSON.
445TEST_F(PELTest, SectionJSONTest)
446{
447 auto data = pelDataFactory(TestPELType::pelSimple);
448 PEL pel{data};
449
450 // Check that all JSON returned from the sections is
451 // parseable by nlohmann::json, which will throw an
452 // exception and fail the test if there is a problem.
453
454 // The getJSON() response needs to be wrapped in a { } to make
455 // actual valid JSON (PEL::toJSON() usually handles that).
456
Matt Spinlerb832aa52023-03-21 15:32:34 -0500457 auto jsonString = pel.privateHeader().getJSON('O');
Matt Spinlerce3f4502020-01-22 15:44:35 -0600458
459 // PrivateHeader always prints JSON
460 ASSERT_TRUE(jsonString);
461 *jsonString = '{' + *jsonString + '}';
462 auto json = nlohmann::json::parse(*jsonString);
463
Matt Spinlerb832aa52023-03-21 15:32:34 -0500464 jsonString = pel.userHeader().getJSON('O');
Matt Spinlerce3f4502020-01-22 15:44:35 -0600465
466 // UserHeader always prints JSON
467 ASSERT_TRUE(jsonString);
468 *jsonString = '{' + *jsonString + '}';
469 json = nlohmann::json::parse(*jsonString);
470
471 for (const auto& section : pel.optionalSections())
472 {
473 // The optional sections may or may not have implemented getJSON().
Matt Spinlerb832aa52023-03-21 15:32:34 -0500474 jsonString = section->getJSON('O');
Matt Spinlerce3f4502020-01-22 15:44:35 -0600475 if (jsonString)
476 {
477 *jsonString = '{' + *jsonString + '}';
478 auto json = nlohmann::json::parse(*jsonString);
479 }
480 }
481}
Matt Spinler5b289b22020-03-26 14:27:19 -0500482
483PelFFDCfile getJSONFFDC(const fs::path& dir)
484{
485 PelFFDCfile ffdc;
486 ffdc.format = UserDataFormat::json;
487 ffdc.subType = 5;
488 ffdc.version = 42;
489
490 auto inputJSON = R"({
491 "key1": "value1",
492 "key2": 42,
493 "key3" : [1, 2, 3, 4, 5],
494 "key4": {"key5": "value5"}
495 })"_json;
496
497 // Write the JSON to a file and get its descriptor.
498 auto s = inputJSON.dump();
499 std::vector<uint8_t> data{s.begin(), s.end()};
500 ffdc.fd = writeFileAndGetFD(dir, data);
501
502 return ffdc;
503}
504
505TEST_F(PELTest, MakeJSONFileUDSectionTest)
506{
507 auto dir = makeTempDir();
508
509 {
510 auto ffdc = getJSONFFDC(dir);
511
512 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
513 close(ffdc.fd);
514 ASSERT_TRUE(ud);
515 ASSERT_TRUE(ud->valid());
516 EXPECT_EQ(ud->header().id, 0x5544);
517
518 EXPECT_EQ(ud->header().version,
519 static_cast<uint8_t>(UserDataFormatVersion::json));
520 EXPECT_EQ(ud->header().subType,
521 static_cast<uint8_t>(UserDataFormat::json));
522 EXPECT_EQ(ud->header().componentID,
523 static_cast<uint16_t>(ComponentID::phosphorLogging));
524
525 // Pull the JSON back out of the the UserData section
526 const auto& d = ud->data();
527 std::string js{d.begin(), d.end()};
528 auto json = nlohmann::json::parse(js);
529
530 EXPECT_EQ("value1", json["key1"].get<std::string>());
531 EXPECT_EQ(42, json["key2"].get<int>());
532
533 std::vector<int> key3Values{1, 2, 3, 4, 5};
534 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>());
535
536 std::map<std::string, std::string> key4Values{{"key5", "value5"}};
537 auto actual = json["key4"].get<std::map<std::string, std::string>>();
538 EXPECT_EQ(key4Values, actual);
539 }
540
541 {
542 // A bad FD
543 PelFFDCfile ffdc;
544 ffdc.format = UserDataFormat::json;
545 ffdc.subType = 5;
546 ffdc.version = 42;
547 ffdc.fd = 10000;
548
549 // The section shouldn't get made
550 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
551 ASSERT_FALSE(ud);
552 }
553
554 fs::remove_all(dir);
555}
556
557PelFFDCfile getCBORFFDC(const fs::path& dir)
558{
559 PelFFDCfile ffdc;
560 ffdc.format = UserDataFormat::cbor;
561 ffdc.subType = 5;
562 ffdc.version = 42;
563
564 auto inputJSON = R"({
565 "key1": "value1",
566 "key2": 42,
567 "key3" : [1, 2, 3, 4, 5],
568 "key4": {"key5": "value5"}
569 })"_json;
570
571 // Convert the JSON to CBOR and write it to a file
572 auto data = nlohmann::json::to_cbor(inputJSON);
573 ffdc.fd = writeFileAndGetFD(dir, data);
574
575 return ffdc;
576}
577
578TEST_F(PELTest, MakeCBORFileUDSectionTest)
579{
580 auto dir = makeTempDir();
581
582 auto ffdc = getCBORFFDC(dir);
583 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
584 close(ffdc.fd);
585 ASSERT_TRUE(ud);
586 ASSERT_TRUE(ud->valid());
587 EXPECT_EQ(ud->header().id, 0x5544);
588
589 EXPECT_EQ(ud->header().version,
590 static_cast<uint8_t>(UserDataFormatVersion::cbor));
591 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::cbor));
592 EXPECT_EQ(ud->header().componentID,
593 static_cast<uint16_t>(ComponentID::phosphorLogging));
594
595 // Pull the CBOR back out of the PEL section
596 // The number of pad bytes to make the section be 4B aligned
597 // was added at the end, read it and then remove it and the
598 // padding before parsing it.
599 auto data = ud->data();
600 Stream stream{data};
601 stream.offset(data.size() - 4);
602 uint32_t pad;
603 stream >> pad;
604
605 data.resize(data.size() - 4 - pad);
606
607 auto json = nlohmann::json::from_cbor(data);
608
609 EXPECT_EQ("value1", json["key1"].get<std::string>());
610 EXPECT_EQ(42, json["key2"].get<int>());
611
612 std::vector<int> key3Values{1, 2, 3, 4, 5};
613 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>());
614
615 std::map<std::string, std::string> key4Values{{"key5", "value5"}};
616 auto actual = json["key4"].get<std::map<std::string, std::string>>();
617 EXPECT_EQ(key4Values, actual);
618
619 fs::remove_all(dir);
620}
621
622PelFFDCfile getTextFFDC(const fs::path& dir)
623{
624 PelFFDCfile ffdc;
625 ffdc.format = UserDataFormat::text;
626 ffdc.subType = 5;
627 ffdc.version = 42;
628
629 std::string text{"this is some text that will be used for FFDC"};
630 std::vector<uint8_t> data{text.begin(), text.end()};
631
632 ffdc.fd = writeFileAndGetFD(dir, data);
633
634 return ffdc;
635}
636
637TEST_F(PELTest, MakeTextFileUDSectionTest)
638{
639 auto dir = makeTempDir();
640
641 auto ffdc = getTextFFDC(dir);
642 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
643 close(ffdc.fd);
644 ASSERT_TRUE(ud);
645 ASSERT_TRUE(ud->valid());
646 EXPECT_EQ(ud->header().id, 0x5544);
647
648 EXPECT_EQ(ud->header().version,
649 static_cast<uint8_t>(UserDataFormatVersion::text));
650 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::text));
651 EXPECT_EQ(ud->header().componentID,
652 static_cast<uint16_t>(ComponentID::phosphorLogging));
653
654 // Get the text back out
655 std::string text{ud->data().begin(), ud->data().end()};
656 EXPECT_EQ(text, "this is some text that will be used for FFDC");
657
658 fs::remove_all(dir);
659}
660
661PelFFDCfile getCustomFFDC(const fs::path& dir, const std::vector<uint8_t>& data)
662{
663 PelFFDCfile ffdc;
664 ffdc.format = UserDataFormat::custom;
665 ffdc.subType = 5;
666 ffdc.version = 42;
667
668 ffdc.fd = writeFileAndGetFD(dir, data);
669
670 return ffdc;
671}
672
673TEST_F(PELTest, MakeCustomFileUDSectionTest)
674{
675 auto dir = makeTempDir();
676
677 {
678 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8};
679
680 auto ffdc = getCustomFFDC(dir, data);
681 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
682 close(ffdc.fd);
683 ASSERT_TRUE(ud);
684 ASSERT_TRUE(ud->valid());
685 EXPECT_EQ(ud->header().size, 8 + 8); // data size + header size
686 EXPECT_EQ(ud->header().id, 0x5544);
687
688 EXPECT_EQ(ud->header().version, 42);
689 EXPECT_EQ(ud->header().subType, 5);
690 EXPECT_EQ(ud->header().componentID, 0x2002);
691
692 // Get the data back out
693 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()};
694 EXPECT_EQ(data, newData);
695 }
696
697 // Do the same thing again, but make it be non 4B aligned
698 // so the data gets padded.
699 {
700 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8, 9};
701
702 auto ffdc = getCustomFFDC(dir, data);
703 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
704 close(ffdc.fd);
705 ASSERT_TRUE(ud);
706 ASSERT_TRUE(ud->valid());
707 EXPECT_EQ(ud->header().size, 12 + 8); // data size + header size
708 EXPECT_EQ(ud->header().id, 0x5544);
709
710 EXPECT_EQ(ud->header().version, 42);
711 EXPECT_EQ(ud->header().subType, 5);
712 EXPECT_EQ(ud->header().componentID, 0x2002);
713
714 // Get the data back out
715 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()};
716
717 // pad the original to 12B so we can compare
718 data.push_back(0);
719 data.push_back(0);
720 data.push_back(0);
721
722 EXPECT_EQ(data, newData);
723 }
724
725 fs::remove_all(dir);
726}
727
728// Test Adding FFDC from files to a PEL
729TEST_F(PELTest, CreateWithFFDCTest)
730{
731 auto dir = makeTempDir();
732 message::Entry regEntry;
733 uint64_t timestamp = 5;
734
735 regEntry.name = "test";
736 regEntry.subsystem = 5;
737 regEntry.actionFlags = 0xC000;
738 regEntry.src.type = 0xBD;
739 regEntry.src.reasonCode = 0x1234;
740
Patrick Williamse5940632024-11-22 20:47:58 -0500741 std::map<std::string, std::string> additionalData{{"KEY1", "VALUE1"}};
Matt Spinler5b289b22020-03-26 14:27:19 -0500742 AdditionalData ad{additionalData};
743 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600744 NiceMock<MockJournal> journal;
Matt Spinler5b289b22020-03-26 14:27:19 -0500745 PelFFDC ffdc;
746
747 std::vector<uint8_t> customData{1, 2, 3, 4, 5, 6, 7, 8};
748
749 // This will be trimmed when added
750 std::vector<uint8_t> hugeCustomData(17000, 0x42);
751
Matt Spinlerf904caf2025-05-09 11:46:45 -0500752 ffdc.emplace_back(getJSONFFDC(dir));
753 ffdc.emplace_back(getCBORFFDC(dir));
754 ffdc.emplace_back(getTextFFDC(dir));
755 ffdc.emplace_back(getCustomFFDC(dir, customData));
756 ffdc.emplace_back(getCustomFFDC(dir, hugeCustomData));
Matt Spinler5b289b22020-03-26 14:27:19 -0500757
758 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
Matt Spinler9d921092022-12-15 11:54:49 -0600759 ad, ffdc, dataIface, journal};
Matt Spinler5b289b22020-03-26 14:27:19 -0500760
761 EXPECT_TRUE(pel.valid());
762
763 // Clipped to the max
764 EXPECT_EQ(pel.size(), 16384);
765
766 // Check for the FFDC sections
767 size_t udCount = 0;
768 Section* ud = nullptr;
769
770 for (const auto& section : pel.optionalSections())
771 {
772 if (section->header().id == static_cast<uint16_t>(SectionID::userData))
773 {
774 udCount++;
775 ud = section.get();
776 }
777 }
778
779 EXPECT_EQ(udCount, 7); // AD section, sysInfo, 5 ffdc sections
780
781 // Check the last section was trimmed to
782 // something a bit less that 17000.
783 EXPECT_GT(ud->header().size, 14000);
784 EXPECT_LT(ud->header().size, 16000);
785
786 fs::remove_all(dir);
787}
Matt Spinler0a90a852020-06-04 13:18:27 -0500788
789// Create a PEL with device callouts
790TEST_F(PELTest, CreateWithDevCalloutsTest)
791{
792 message::Entry regEntry;
793 uint64_t timestamp = 5;
794
795 regEntry.name = "test";
796 regEntry.subsystem = 5;
797 regEntry.actionFlags = 0xC000;
798 regEntry.src.type = 0xBD;
799 regEntry.src.reasonCode = 0x1234;
800
801 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600802 NiceMock<MockJournal> journal;
Matt Spinler0a90a852020-06-04 13:18:27 -0500803 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)
Matt Spinler1ab66962020-10-29 13:21:44 -0500829 .WillRepeatedly(Return(names));
Matt Spinler0a90a852020-06-04 13:18:27 -0500830
Matt Spinler0d92b522021-06-16 13:28:17 -0600831 EXPECT_CALL(dataIface, expandLocationCode("P1", 0))
832 .Times(1)
Matt Spinler0a90a852020-06-04 13:18:27 -0500833 .WillOnce(Return("UXXX-P1"));
834
Matt Spinler2f9225a2020-08-05 12:58:49 -0500835 EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false))
Matt Spinlerbad056b2023-01-25 14:16:57 -0600836 .WillOnce(Return(std::vector<std::string>{
837 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"}));
Matt Spinler0a90a852020-06-04 13:18:27 -0500838
Patrick Williams075c7922024-08-16 15:19:49 -0400839 EXPECT_CALL(dataIface,
840 getHWCalloutFields(
841 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0",
842 _, _, _))
Matt Spinler0a90a852020-06-04 13:18:27 -0500843 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
844 SetArgReferee<3>("123456789ABC")));
845
846 auto dataPath = getPELReadOnlyDataPath();
847 std::ofstream file{dataPath / "systemA_dev_callouts.json"};
848 file << calloutJSON;
849 file.close();
850
851 {
Patrick Williamse5940632024-11-22 20:47:58 -0500852 std::map<std::string, std::string> data{
853 {"CALLOUT_ERRNO", "5"},
854 {"CALLOUT_DEVICE_PATH",
855 "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"}};
Matt Spinler0a90a852020-06-04 13:18:27 -0500856
857 AdditionalData ad{data};
858
Patrick Williams075c7922024-08-16 15:19:49 -0400859 PEL pel{regEntry, 42,
860 timestamp, phosphor::logging::Entry::Level::Error,
861 ad, ffdc,
862 dataIface, journal};
Matt Spinler0a90a852020-06-04 13:18:27 -0500863
864 ASSERT_TRUE(pel.primarySRC().value()->callouts());
865 auto& callouts = pel.primarySRC().value()->callouts()->callouts();
866 ASSERT_EQ(callouts.size(), 1);
Andrew Geisslerf8e750d2022-01-14 14:56:13 -0600867 ASSERT_TRUE(pel.isHwCalloutPresent());
Matt Spinler0a90a852020-06-04 13:18:27 -0500868
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
Arya K Padmand8ae6182024-07-19 06:25:10 -0500898 EXPECT_TRUE(
899 actualJSON.contains("/PEL Internal Debug Data/SRC"_json_pointer));
900 EXPECT_EQ(actualJSON["PEL Internal Debug Data"]["SRC"],
901 expectedJSON["PEL Internal Debug Data"]["SRC"]);
Matt Spinler0a90a852020-06-04 13:18:27 -0500902 }
903
904 {
905 // Device path not found (wrong i2c addr), so no callouts
Patrick Williamse5940632024-11-22 20:47:58 -0500906 std::map<std::string, std::string> data{
907 {"CALLOUT_ERRNO", "5"},
908 {"CALLOUT_DEVICE_PATH",
909 "/sys/devices/platform/ahb/ahb:apb/ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0099"}};
Matt Spinler0a90a852020-06-04 13:18:27 -0500910
911 AdditionalData ad{data};
912
Patrick Williams075c7922024-08-16 15:19:49 -0400913 PEL pel{regEntry, 42,
914 timestamp, phosphor::logging::Entry::Level::Error,
915 ad, ffdc,
916 dataIface, journal};
Matt Spinler0a90a852020-06-04 13:18:27 -0500917
918 // no callouts
919 EXPECT_FALSE(pel.primarySRC().value()->callouts());
920
921 // Now check that there was a UserData section
922 // that contains the lookup error.
923 const auto& section = pel.optionalSections().back();
924
925 ASSERT_EQ(section->header().id, 0x5544); // UD
926 auto ud = static_cast<UserData*>(section.get());
927
928 const auto& d = ud->data();
929
930 std::string jsonString{d.begin(), d.end()};
931
932 auto actualJSON = nlohmann::json::parse(jsonString);
933
934 auto expectedJSON =
935 "{\"PEL Internal Debug Data\":{\"SRC\":"
936 "[\"Problem looking up I2C callouts on 14 153: "
937 "[json.exception.out_of_range.403] key '153' not found\"]}}"_json;
938
Arya K Padmand8ae6182024-07-19 06:25:10 -0500939 EXPECT_TRUE(
940 actualJSON.contains("/PEL Internal Debug Data/SRC"_json_pointer));
941 EXPECT_EQ(actualJSON["PEL Internal Debug Data"]["SRC"],
942 expectedJSON["PEL Internal Debug Data"]["SRC"]);
Matt Spinler0a90a852020-06-04 13:18:27 -0500943 }
944
945 fs::remove_all(dataPath);
946}
Matt Spinlere513dbc2020-08-27 11:14:17 -0500947
948// Test PELs when the callouts are passed in using a JSON file.
949TEST_F(PELTest, CreateWithJSONCalloutsTest)
950{
951 PelFFDCfile ffdcFile;
952 ffdcFile.format = UserDataFormat::json;
953 ffdcFile.subType = 0xCA; // Callout JSON
954 ffdcFile.version = 1;
955
956 // Write these callouts to a JSON file and pass it into
Matt Spinler4efed0e2024-02-26 11:16:07 -0600957 // the PEL as an FFDC file. Also has a duplicate that
958 // will be removed.
Matt Spinlere513dbc2020-08-27 11:14:17 -0500959 auto inputJSON = R"([
960 {
961 "Priority": "H",
962 "LocationCode": "P0-C1"
963 },
964 {
965 "Priority": "M",
966 "Procedure": "PROCEDURE"
Matt Spinler4efed0e2024-02-26 11:16:07 -0600967 },
968 {
969 "Priority": "L",
970 "Procedure": "PROCEDURE"
Matt Spinlere513dbc2020-08-27 11:14:17 -0500971 }
972 ])"_json;
973
974 auto s = inputJSON.dump();
975 std::vector<uint8_t> data{s.begin(), s.end()};
976 auto dir = makeTempDir();
977 ffdcFile.fd = writeFileAndGetFD(dir, data);
978
979 PelFFDC ffdc;
980 ffdc.push_back(std::move(ffdcFile));
981
982 AdditionalData ad;
983 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600984 NiceMock<MockJournal> journal;
Matt Spinlere513dbc2020-08-27 11:14:17 -0500985
986 EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
987 .Times(1)
988 .WillOnce(Return("UXXX-P0-C1"));
989 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
990 .Times(1)
Matt Spinlerbad056b2023-01-25 14:16:57 -0600991 .WillOnce(Return(
992 std::vector<std::string>{"/inv/system/chassis/motherboard/bmc"}));
Matt Spinlere513dbc2020-08-27 11:14:17 -0500993 EXPECT_CALL(dataIface, getHWCalloutFields(
994 "/inv/system/chassis/motherboard/bmc", _, _, _))
995 .Times(1)
996 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
997 SetArgReferee<3>("123456789ABC")));
998
999 message::Entry regEntry;
1000 regEntry.name = "test";
1001 regEntry.subsystem = 5;
1002 regEntry.actionFlags = 0xC000;
1003 regEntry.src.type = 0xBD;
1004 regEntry.src.reasonCode = 0x1234;
1005
Matt Spinler9d921092022-12-15 11:54:49 -06001006 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error,
1007 ad, ffdc, dataIface, journal};
Matt Spinlere513dbc2020-08-27 11:14:17 -05001008
1009 ASSERT_TRUE(pel.valid());
1010 ASSERT_TRUE(pel.primarySRC().value()->callouts());
1011 const auto& callouts = pel.primarySRC().value()->callouts()->callouts();
1012 ASSERT_EQ(callouts.size(), 2);
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001013 ASSERT_TRUE(pel.isHwCalloutPresent());
Matt Spinlere513dbc2020-08-27 11:14:17 -05001014
1015 {
1016 EXPECT_EQ(callouts[0]->priority(), 'H');
1017 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1018
1019 auto& fru = callouts[0]->fruIdentity();
1020 EXPECT_EQ(fru->getPN().value(), "1234567");
1021 EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1022 EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1023 EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1024 }
1025 {
1026 EXPECT_EQ(callouts[1]->priority(), 'M');
1027 EXPECT_EQ(callouts[1]->locationCode(), "");
1028
1029 auto& fru = callouts[1]->fruIdentity();
1030 EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1031 EXPECT_EQ(fru->failingComponentType(),
1032 src::FRUIdentity::maintenanceProc);
1033 }
1034 fs::remove_all(dir);
1035}
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001036
1037// Test PELs with symblic FRU callout.
1038TEST_F(PELTest, CreateWithJSONSymblicCalloutTest)
1039{
1040 PelFFDCfile ffdcFile;
1041 ffdcFile.format = UserDataFormat::json;
1042 ffdcFile.subType = 0xCA; // Callout JSON
1043 ffdcFile.version = 1;
1044
1045 // Write these callouts to a JSON file and pass it into
1046 // the PEL as an FFDC file.
1047 auto inputJSON = R"([
1048 {
1049 "Priority": "M",
1050 "Procedure": "SVCDOCS"
1051 }
1052 ])"_json;
1053
1054 auto s = inputJSON.dump();
1055 std::vector<uint8_t> data{s.begin(), s.end()};
1056 auto dir = makeTempDir();
1057 ffdcFile.fd = writeFileAndGetFD(dir, data);
1058
1059 PelFFDC ffdc;
1060 ffdc.push_back(std::move(ffdcFile));
1061
1062 AdditionalData ad;
1063 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -06001064 NiceMock<MockJournal> journal;
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001065
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001066 message::Entry regEntry;
1067 regEntry.name = "test";
1068 regEntry.subsystem = 5;
1069 regEntry.actionFlags = 0xC000;
1070 regEntry.src.type = 0xBD;
1071 regEntry.src.reasonCode = 0x1234;
1072
Matt Spinler9d921092022-12-15 11:54:49 -06001073 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error,
1074 ad, ffdc, dataIface, journal};
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001075
1076 ASSERT_TRUE(pel.valid());
1077 ASSERT_TRUE(pel.primarySRC().value()->callouts());
1078 const auto& callouts = pel.primarySRC().value()->callouts()->callouts();
1079 ASSERT_EQ(callouts.size(), 1);
1080 ASSERT_FALSE(pel.isHwCalloutPresent());
1081
1082 {
1083 EXPECT_EQ(callouts[0]->priority(), 'M');
1084 EXPECT_EQ(callouts[0]->locationCode(), "");
1085
1086 auto& fru = callouts[0]->fruIdentity();
1087 EXPECT_EQ(fru->getMaintProc().value(), "SVCDOCS");
1088 }
1089 fs::remove_all(dir);
1090}
Matt Spinler9d921092022-12-15 11:54:49 -06001091
1092TEST_F(PELTest, FlattenLinesTest)
1093{
1094 std::vector<std::string> msgs{"test1 test2", "test3 test4", "test5 test6"};
1095
1096 auto buffer = util::flattenLines(msgs);
1097
1098 std::string string{"test1 test2\ntest3 test4\ntest5 test6\n"};
1099 std::vector<uint8_t> expected(string.begin(), string.end());
1100
1101 EXPECT_EQ(buffer, expected);
1102}
1103
1104void checkJournalSection(const std::unique_ptr<Section>& section,
1105 const std::string& expected)
1106{
1107 ASSERT_EQ(SectionID::userData,
1108 static_cast<SectionID>(section->header().id));
1109 ASSERT_EQ(UserDataFormat::text,
1110 static_cast<UserDataFormat>(section->header().subType));
1111 ASSERT_EQ(section->header().version,
1112 static_cast<uint8_t>(UserDataFormatVersion::text));
1113
1114 auto ud = static_cast<UserData*>(section.get());
1115
1116 std::vector<uint8_t> expectedData(expected.begin(), expected.end());
1117
1118 // PEL sections are 4B aligned so add padding before the compare
1119 while (expectedData.size() % 4 != 0)
1120 {
1121 expectedData.push_back('\0');
1122 }
1123
1124 EXPECT_EQ(ud->data(), expectedData);
1125}
1126
1127TEST_F(PELTest, CaptureJournalTest)
1128{
1129 message::Entry regEntry;
1130 uint64_t timestamp = 5;
1131
1132 regEntry.name = "test";
1133 regEntry.subsystem = 5;
1134 regEntry.actionFlags = 0xC000;
1135 regEntry.src.type = 0xBD;
1136 regEntry.src.reasonCode = 0x1234;
1137
Patrick Williamse5940632024-11-22 20:47:58 -05001138 std::map<std::string, std::string> data{};
Matt Spinler9d921092022-12-15 11:54:49 -06001139 AdditionalData ad{data};
1140 NiceMock<MockDataInterface> dataIface;
1141 NiceMock<MockJournal> journal;
1142 PelFFDC ffdc;
1143
Matt Spinler9d921092022-12-15 11:54:49 -06001144 size_t pelSectsWithOneUD{0};
1145
1146 {
1147 // Capture 5 lines from the journal into a single UD section
1148 message::JournalCapture jc = size_t{5};
1149 regEntry.journalCapture = jc;
1150
1151 std::vector<std::string> msgs{"test1 test2", "test3 test4",
1152 "test5 test6", "4", "5"};
1153
1154 EXPECT_CALL(journal, getMessages("", 5)).WillOnce(Return(msgs));
1155
Patrick Williams075c7922024-08-16 15:19:49 -04001156 PEL pel{regEntry, 42,
1157 timestamp, phosphor::logging::Entry::Level::Error,
1158 ad, ffdc,
1159 dataIface, journal};
Matt Spinler9d921092022-12-15 11:54:49 -06001160
1161 // Check the generated UserData section
1162 std::string expected{"test1 test2\ntest3 test4\ntest5 test6\n4\n5\n"};
1163
1164 checkJournalSection(pel.optionalSections().back(), expected);
1165
1166 // Save for upcoming testcases
1167 pelSectsWithOneUD = pel.privateHeader().sectionCount();
1168 }
1169
1170 {
1171 // Attempt to capture too many journal entries so the
1172 // section gets dropped.
1173 message::JournalCapture jc = size_t{1};
1174 regEntry.journalCapture = jc;
1175
1176 EXPECT_CALL(journal, sync()).Times(1);
1177
1178 // A 20000 byte line won't fit in a PEL
1179 EXPECT_CALL(journal, getMessages("", 1))
1180 .WillOnce(
1181 Return(std::vector<std::string>{std::string(20000, 'x')}));
1182
Patrick Williams075c7922024-08-16 15:19:49 -04001183 PEL pel{regEntry, 42,
1184 timestamp, phosphor::logging::Entry::Level::Error,
1185 ad, ffdc,
1186 dataIface, journal};
Matt Spinler9d921092022-12-15 11:54:49 -06001187
1188 // Check for 1 fewer sections than in the previous PEL
1189 EXPECT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD - 1);
1190 }
1191
1192 // Capture 3 different journal sections
1193 {
1194 message::AppCaptureList captureList{
1195 message::AppCapture{"app1", 3},
1196 message::AppCapture{"app2", 4},
1197 message::AppCapture{"app3", 1},
1198 };
1199 message::JournalCapture jc = captureList;
1200 regEntry.journalCapture = jc;
1201
1202 std::vector<std::string> app1{"A B", "C D", "E F"};
1203 std::vector<std::string> app2{"1 2", "3 4", "5 6", "7 8"};
1204 std::vector<std::string> app3{"a b c"};
1205
1206 std::string expected1{"A B\nC D\nE F\n"};
1207 std::string expected2{"1 2\n3 4\n5 6\n7 8\n"};
1208 std::string expected3{"a b c\n"};
1209
1210 EXPECT_CALL(journal, sync()).Times(1);
1211 EXPECT_CALL(journal, getMessages("app1", 3)).WillOnce(Return(app1));
1212 EXPECT_CALL(journal, getMessages("app2", 4)).WillOnce(Return(app2));
1213 EXPECT_CALL(journal, getMessages("app3", 1)).WillOnce(Return(app3));
1214
Patrick Williams075c7922024-08-16 15:19:49 -04001215 PEL pel{regEntry, 42,
1216 timestamp, phosphor::logging::Entry::Level::Error,
1217 ad, ffdc,
1218 dataIface, journal};
Matt Spinler9d921092022-12-15 11:54:49 -06001219
Patrick Williamse5940632024-11-22 20:47:58 -05001220 // Two more sections than the 1 extra UD section in the first
1221 // testcase
Matt Spinler9d921092022-12-15 11:54:49 -06001222 ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD + 2);
1223
1224 const auto& optionalSections = pel.optionalSections();
1225 auto numOptSections = optionalSections.size();
1226
1227 checkJournalSection(optionalSections[numOptSections - 3], expected1);
1228 checkJournalSection(optionalSections[numOptSections - 2], expected2);
1229 checkJournalSection(optionalSections[numOptSections - 1], expected3);
1230 }
1231
1232 {
1233 // One section gets saved, and one is too big and gets dropped
1234 message::AppCaptureList captureList{
1235 message::AppCapture{"app4", 2},
1236 message::AppCapture{"app5", 1},
1237 };
1238 message::JournalCapture jc = captureList;
1239 regEntry.journalCapture = jc;
1240
1241 std::vector<std::string> app4{"w x", "y z"};
1242 std::string expected4{"w x\ny z\n"};
1243
1244 EXPECT_CALL(journal, sync()).Times(1);
1245
1246 EXPECT_CALL(journal, getMessages("app4", 2)).WillOnce(Return(app4));
1247
1248 // A 20000 byte line won't fit in a PEL
1249 EXPECT_CALL(journal, getMessages("app5", 1))
1250 .WillOnce(
1251 Return(std::vector<std::string>{std::string(20000, 'x')}));
1252
Patrick Williams075c7922024-08-16 15:19:49 -04001253 PEL pel{regEntry, 42,
1254 timestamp, phosphor::logging::Entry::Level::Error,
1255 ad, ffdc,
1256 dataIface, journal};
Matt Spinler9d921092022-12-15 11:54:49 -06001257
1258 // The last section should have been dropped, so same as first TC
1259 ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD);
1260
1261 checkJournalSection(pel.optionalSections().back(), expected4);
1262 }
1263}
Arya K Padmand8ae6182024-07-19 06:25:10 -05001264
1265// API to collect and parse the User Data section of the PEL.
1266nlohmann::json getDIMMInfo(const auto& pel)
1267{
1268 nlohmann::json dimmInfo{};
1269 auto hasDIMMInfo = [&dimmInfo](const auto& optionalSection) {
1270 if (optionalSection->header().id !=
1271 static_cast<uint16_t>(SectionID::userData))
1272 {
1273 return false;
1274 }
1275 else
1276 {
1277 auto userData = static_cast<UserData*>(optionalSection.get());
1278
Patrick Williamse5940632024-11-22 20:47:58 -05001279 // convert the userdata section to string and then parse in to
1280 // json format
Arya K Padmand8ae6182024-07-19 06:25:10 -05001281 std::string userDataString{userData->data().begin(),
1282 userData->data().end()};
1283 nlohmann::json userDataJson = nlohmann::json::parse(userDataString);
1284
1285 if (userDataJson.contains("DIMMs Additional Info"))
1286 {
1287 dimmInfo = userDataJson.at("DIMMs Additional Info");
1288 }
1289 else if (
1290 userDataJson.contains(
1291 "/PEL Internal Debug Data/DIMMs Info Fetch Error"_json_pointer))
1292 {
1293 dimmInfo = userDataJson.at(
1294 "/PEL Internal Debug Data/DIMMs Info Fetch Error"_json_pointer);
1295 }
1296 else
1297 {
1298 return false;
1299 }
1300 return true;
1301 }
1302 };
1303 std::ranges::any_of(pel.optionalSections(), hasDIMMInfo);
1304
1305 return dimmInfo;
1306}
1307
1308// Test whether the DIMM callouts manufacturing info is getting added to the
1309// SysInfo User Data section of the PEL
1310TEST_F(PELTest, TestDimmsCalloutInfo)
1311{
1312 {
1313 message::Entry entry;
1314 uint64_t timestamp = 5;
1315 AdditionalData ad;
1316 NiceMock<MockDataInterface> dataIface;
1317 NiceMock<MockJournal> journal;
1318 PelFFDC ffdc;
1319
1320 // When callouts contain DIMM callouts.
1321 entry.callouts = R"(
1322 [
1323 {
1324 "CalloutList": [
1325 {
1326 "Priority": "high",
1327 "LocCode": "P0-DIMM0"
1328 },
1329 {
1330 "Priority": "low",
1331 "LocCode": "P0-DIMM1"
1332 }
1333 ]
1334 }
1335 ]
1336 )"_json;
1337
1338 EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM0", 0))
1339 .WillOnce(Return("U98D-P0-DIMM0"));
1340 EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM1", 0))
1341 .WillOnce(Return("U98D-P0-DIMM1"));
1342
1343 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM0", 0, false))
1344 .WillOnce(Return(std::vector<std::string>{
1345 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"}));
1346 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM1", 0, false))
1347 .WillOnce(Return(std::vector<std::string>{
1348 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm1"}));
1349
1350 std::vector<uint8_t> diValue{128, 74};
1351 EXPECT_CALL(dataIface, getDIProperty("U98D-P0-DIMM0"))
1352 .WillOnce(Return(diValue));
1353 EXPECT_CALL(dataIface, getDIProperty("U98D-P0-DIMM1"))
1354 .WillOnce(Return(diValue));
1355
1356 // Add some location code in expanded format to DIMM cache memory
1357 dataIface.addDIMMLocCode("U98D-P0-DIMM0", true);
1358 dataIface.addDIMMLocCode("U98D-P0-DIMM1", true);
1359
1360 PEL pel{entry, 42, timestamp, phosphor::logging::Entry::Level::Error,
1361 ad, ffdc, dataIface, journal};
1362 nlohmann::json dimmInfoJson = getDIMMInfo(pel);
1363
1364 nlohmann::json expected_data = R"(
1365 [
1366 {
1367 "Location Code": "U98D-P0-DIMM0",
1368 "DRAM Manufacturer ID": [
1369 "0x80",
1370 "0x4a"
1371 ]
1372 },
1373 {
1374 "Location Code": "U98D-P0-DIMM1",
1375 "DRAM Manufacturer ID": [
1376 "0x80",
1377 "0x4a"
1378 ]
1379 }
1380 ]
1381 )"_json;
1382 EXPECT_EQ(expected_data, dimmInfoJson);
1383 }
1384}
1385
1386// When PEL has FRU callouts but PHAL is not enabled.
Arya K Padmanced8ed72024-09-02 05:18:07 -05001387TEST_F(PELTest, TestNoDimmsCallout)
Arya K Padmand8ae6182024-07-19 06:25:10 -05001388{
1389 message::Entry entry;
1390 uint64_t timestamp = 5;
1391 AdditionalData ad;
1392 NiceMock<MockDataInterface> dataIface;
1393 NiceMock<MockJournal> journal;
1394 PelFFDC ffdc;
1395
1396 entry.callouts = R"(
1397 [
1398 {
1399 "CalloutList": [
1400 {
1401 "Priority": "high",
Arya K Padmanced8ed72024-09-02 05:18:07 -05001402 "LocCode": "P0-PROC0"
Arya K Padmand8ae6182024-07-19 06:25:10 -05001403 }
1404 ]
1405 }
1406 ]
1407 )"_json;
1408
Arya K Padmanced8ed72024-09-02 05:18:07 -05001409 EXPECT_CALL(dataIface, expandLocationCode("P0-PROC0", 0))
1410 .WillOnce(Return("U98D-P0-PROC0"));
Arya K Padmand8ae6182024-07-19 06:25:10 -05001411
Arya K Padmanced8ed72024-09-02 05:18:07 -05001412 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-PROC0", 0, false))
Arya K Padmand8ae6182024-07-19 06:25:10 -05001413 .WillOnce(Return(std::vector<std::string>{
Arya K Padmanced8ed72024-09-02 05:18:07 -05001414 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dcm0/cpu0"}));
1415
1416 // Add some location code in expanded format to DIMM cache memory
1417 dataIface.addDIMMLocCode("U98D-P0-PROC0", false);
Arya K Padmand8ae6182024-07-19 06:25:10 -05001418
1419 PEL pel{entry, 42, timestamp, phosphor::logging::Entry::Level::Error,
1420 ad, ffdc, dataIface, journal};
1421
1422 nlohmann::json dimmInfoJson = getDIMMInfo(pel);
1423
Arya K Padmanced8ed72024-09-02 05:18:07 -05001424 nlohmann::json expected_data{};
Arya K Padmand8ae6182024-07-19 06:25:10 -05001425
1426 EXPECT_EQ(expected_data, dimmInfoJson);
1427}
1428
1429// When the PEL doesn't contain any type of callouts
1430TEST_F(PELTest, TestDimmsCalloutInfoWithNoCallouts)
1431{
1432 message::Entry entry;
1433 uint64_t timestamp = 5;
1434 AdditionalData ad;
1435 NiceMock<MockDataInterface> dataIface;
1436 NiceMock<MockJournal> journal;
1437 PelFFDC ffdc;
1438
1439 PEL pel{entry, 42, timestamp, phosphor::logging::Entry::Level::Error,
1440 ad, ffdc, dataIface, journal};
1441
1442 nlohmann::json dimmInfoJson = getDIMMInfo(pel);
1443
1444 nlohmann::json expected_data{};
1445
1446 EXPECT_EQ(expected_data, dimmInfoJson);
1447}
1448
1449// When the PEL has DIMM callouts, but failed to fetch DI property value
1450TEST_F(PELTest, TestDimmsCalloutInfoDIFailure)
1451{
1452 {
1453 message::Entry entry;
1454 uint64_t timestamp = 5;
1455 AdditionalData ad;
1456 NiceMock<MockDataInterface> dataIface;
1457 NiceMock<MockJournal> journal;
1458 PelFFDC ffdc;
1459
1460 entry.callouts = R"(
1461 [
1462 {
1463 "CalloutList": [
1464 {
1465 "Priority": "high",
1466 "LocCode": "P0-DIMM0"
1467 }
1468 ]
1469 }
1470 ]
1471 )"_json;
1472
1473 EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM0", 0))
1474 .WillOnce(Return("U98D-P0-DIMM0"));
1475
1476 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM0", 0, false))
1477 .WillOnce(Return(std::vector<std::string>{
1478 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"}));
1479
1480 EXPECT_CALL(dataIface, getDIProperty("U98D-P0-DIMM0"))
1481 .WillOnce(Return(std::nullopt));
1482
1483 // Add some location code in expanded format to DIMM cache memory
1484 dataIface.addDIMMLocCode("U98D-P0-DIMM0", true);
1485
1486 PEL pel{entry, 42, timestamp, phosphor::logging::Entry::Level::Error,
1487 ad, ffdc, dataIface, journal};
1488
1489 nlohmann::json dimmInfoJson = getDIMMInfo(pel);
1490
1491 nlohmann::json expected_data = R"(
1492 [
1493 "Failed reading DI property from VINI Interface for the LocationCode:[U98D-P0-DIMM0]"
1494 ]
1495 )"_json;
1496
1497 EXPECT_EQ(expected_data, dimmInfoJson);
1498 }
1499}