blob: 45328ccfe5ba743e41d8fc7c1e55ac56357c1748 [file] [log] [blame]
Matt Spinler97f7abc2019-11-06 09:40:23 -06001/**
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 */
Matt Spinlerb8323632019-09-20 15:11:04 -050016#include "elog_entry.hpp"
Matt Spinler131870c2019-09-25 13:29:04 -050017#include "extensions/openpower-pels/generic.hpp"
Matt Spinlercb6b0592019-07-16 15:58:51 -050018#include "extensions/openpower-pels/pel.hpp"
Matt Spinleraa659472019-10-23 09:26:48 -050019#include "mocks.hpp"
Matt Spinlercb6b0592019-07-16 15:58:51 -050020#include "pel_utils.hpp"
21
22#include <filesystem>
23#include <fstream>
Arya K Padmand8ae6182024-07-19 06:25:10 -050024#include <optional>
Matt Spinlercb6b0592019-07-16 15:58:51 -050025
26#include <gtest/gtest.h>
27
28namespace fs = std::filesystem;
29using namespace openpower::pels;
Matt Spinler0a90a852020-06-04 13:18:27 -050030using ::testing::_;
William A. Kennington IIIb41fa542021-05-29 14:45:16 -070031using ::testing::DoAll;
Matt Spinler56ad2a02020-03-26 14:00:52 -050032using ::testing::NiceMock;
Matt Spinler677381b2020-01-23 10:04:29 -060033using ::testing::Return;
Matt Spinler0a90a852020-06-04 13:18:27 -050034using ::testing::SetArgReferee;
Matt Spinlercb6b0592019-07-16 15:58:51 -050035
36class PELTest : public CleanLogID
Patrick Williams2544b412022-10-04 08:41:06 -050037{};
Matt Spinlercb6b0592019-07-16 15:58:51 -050038
Matt Spinler5b289b22020-03-26 14:27:19 -050039fs::path makeTempDir()
40{
41 char path[] = "/tmp/tempdirXXXXXX";
42 std::filesystem::path dir = mkdtemp(path);
43 return dir;
44}
45
46int 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
Matt Spinlercb6b0592019-07-16 15:58:51 -050060TEST_F(PELTest, FlattenTest)
61{
Matt Spinler42828bd2019-10-11 10:39:30 -050062 auto data = pelDataFactory(TestPELType::pelSimple);
Matt Spinler42828bd2019-10-11 10:39:30 -050063 auto pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -050064
65 // Check a few fields
66 EXPECT_TRUE(pel->valid());
67 EXPECT_EQ(pel->id(), 0x80818283);
68 EXPECT_EQ(pel->plid(), 0x50515253);
Matt Spinler97d19b42019-10-29 11:34:03 -050069 EXPECT_EQ(pel->userHeader().subsystem(), 0x10);
70 EXPECT_EQ(pel->userHeader().actionFlags(), 0x80C0);
Matt Spinlercb6b0592019-07-16 15:58:51 -050071
72 // Test that data in == data out
73 auto flattenedData = pel->data();
Matt Spinlerf1b46ff2020-01-22 14:10:04 -060074 EXPECT_EQ(data, flattenedData);
75 EXPECT_EQ(flattenedData.size(), pel->size());
Matt Spinlercb6b0592019-07-16 15:58:51 -050076}
77
78TEST_F(PELTest, CommitTimeTest)
79{
Matt Spinler42828bd2019-10-11 10:39:30 -050080 auto data = pelDataFactory(TestPELType::pelSimple);
81 auto pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -050082
83 auto origTime = pel->commitTime();
84 pel->setCommitTime();
85 auto newTime = pel->commitTime();
86
Matt Spinlerf1b46ff2020-01-22 14:10:04 -060087 EXPECT_NE(origTime, newTime);
Matt Spinlercb6b0592019-07-16 15:58:51 -050088
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);
Matt Spinlerf1b46ff2020-01-22 14:10:04 -060092 EXPECT_EQ(newTime, newPel->commitTime());
Matt Spinlercb6b0592019-07-16 15:58:51 -050093}
94
95TEST_F(PELTest, AssignIDTest)
96{
Matt Spinler42828bd2019-10-11 10:39:30 -050097 auto data = pelDataFactory(TestPELType::pelSimple);
98 auto pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -050099
100 auto origID = pel->id();
101 pel->assignID();
102 auto newID = pel->id();
103
Matt Spinlerf1b46ff2020-01-22 14:10:04 -0600104 EXPECT_NE(origID, newID);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500105
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);
Matt Spinlerf1b46ff2020-01-22 14:10:04 -0600109 EXPECT_EQ(newID, newPel->id());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500110}
111
112TEST_F(PELTest, WithLogIDTest)
113{
Matt Spinler42828bd2019-10-11 10:39:30 -0500114 auto data = pelDataFactory(TestPELType::pelSimple);
115 auto pel = std::make_unique<PEL>(data, 0x42);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500116
117 EXPECT_TRUE(pel->valid());
118 EXPECT_EQ(pel->obmcLogID(), 0x42);
119}
120
121TEST_F(PELTest, InvalidPELTest)
122{
Matt Spinler42828bd2019-10-11 10:39:30 -0500123 auto data = pelDataFactory(TestPELType::pelSimple);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500124
125 // Too small
Matt Spinler42828bd2019-10-11 10:39:30 -0500126 data.resize(PrivateHeader::flattenedSize());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500127
Matt Spinler42828bd2019-10-11 10:39:30 -0500128 auto pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500129
Matt Spinler97d19b42019-10-29 11:34:03 -0500130 EXPECT_TRUE(pel->privateHeader().valid());
131 EXPECT_FALSE(pel->userHeader().valid());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500132 EXPECT_FALSE(pel->valid());
133
Matt Spinlercb6b0592019-07-16 15:58:51 -0500134 // Now corrupt the private header
Matt Spinler42828bd2019-10-11 10:39:30 -0500135 data = pelDataFactory(TestPELType::pelSimple);
136 data.at(0) = 0;
137 pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500138
Matt Spinler97d19b42019-10-29 11:34:03 -0500139 EXPECT_FALSE(pel->privateHeader().valid());
140 EXPECT_TRUE(pel->userHeader().valid());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500141 EXPECT_FALSE(pel->valid());
142}
143
144TEST_F(PELTest, EmptyDataTest)
145{
146 std::vector<uint8_t> data;
147 auto pel = std::make_unique<PEL>(data);
148
Matt Spinler97d19b42019-10-29 11:34:03 -0500149 EXPECT_FALSE(pel->privateHeader().valid());
150 EXPECT_FALSE(pel->userHeader().valid());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500151 EXPECT_FALSE(pel->valid());
152}
Matt Spinlerb8323632019-09-20 15:11:04 -0500153
154TEST_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;
Matt Spinlerbd716f02019-10-15 10:54:11 -0500162 regEntry.src.type = 0xBD;
163 regEntry.src.reasonCode = 0x1234;
Matt Spinlerb8323632019-09-20 15:11:04 -0500164
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600165 std::vector<std::string> data{"KEY1=VALUE1"};
166 AdditionalData ad{data};
Matt Spinler56ad2a02020-03-26 14:00:52 -0500167 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600168 NiceMock<MockJournal> journal;
Matt Spinler56ad2a02020-03-26 14:00:52 -0500169 PelFFDC ffdc;
Matt Spinlerbd716f02019-10-15 10:54:11 -0500170
Matt Spinler56ad2a02020-03-26 14:00:52 -0500171 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
Matt Spinler9d921092022-12-15 11:54:49 -0600172 ad, ffdc, dataIface, journal};
Matt Spinlerb8323632019-09-20 15:11:04 -0500173
174 EXPECT_TRUE(pel.valid());
Matt Spinler97d19b42019-10-29 11:34:03 -0500175 EXPECT_EQ(pel.privateHeader().obmcLogID(), 42);
176 EXPECT_EQ(pel.userHeader().severity(), 0x40);
Matt Spinlerb8323632019-09-20 15:11:04 -0500177
Matt Spinlerbd716f02019-10-15 10:54:11 -0500178 EXPECT_EQ(pel.primarySRC().value()->asciiString(),
179 "BD051234 ");
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600180
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
Andrew Geisslerf8e750d2022-01-14 14:56:13 -0600208 ASSERT_FALSE(pel.isHwCalloutPresent());
Matt Spinler1f93c592020-09-10 10:43:08 -0500209
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
Patrick Williams075c7922024-08-16 15:19:49 -0400215 PEL pel2{regEntry, 42,
216 timestamp, phosphor::logging::Entry::Level::Error,
217 ad, ffdc,
218 dataIface, journal};
Matt Spinler1f93c592020-09-10 10:43:08 -0500219
220 EXPECT_EQ(pel2.userHeader().actionFlags(), 0xA800);
221 }
Matt Spinlerb8323632019-09-20 15:11:04 -0500222}
Matt Spinler131870c2019-09-25 13:29:04 -0500223
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500224// 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.
227TEST_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;
Matt Spinler56ad2a02020-03-26 14:00:52 -0500237 PelFFDC ffdc;
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500238
239 // Over the 16KB max PEL size
240 std::string bigAD{"KEY1="};
241 bigAD += std::string(17000, 'G');
242
243 std::vector<std::string> data{bigAD};
244 AdditionalData ad{data};
Matt Spinler56ad2a02020-03-26 14:00:52 -0500245 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600246 NiceMock<MockJournal> journal;
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500247
Matt Spinler56ad2a02020-03-26 14:00:52 -0500248 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
Matt Spinler9d921092022-12-15 11:54:49 -0600249 ad, ffdc, dataIface, journal};
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500250
251 EXPECT_TRUE(pel.valid());
252 EXPECT_EQ(pel.size(), 16384);
253
254 // Make sure that there are still 2 UD sections.
Matt Spinlerbe952d22022-07-01 11:30:11 -0500255 const auto& optSections = pel.optionalSections();
Patrick Williams075c7922024-08-16 15:19:49 -0400256 auto udCount = std::count_if(
257 optSections.begin(), optSections.end(), [](const auto& section) {
258 return section->header().id ==
259 static_cast<uint16_t>(SectionID::userData);
260 });
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500261
262 EXPECT_EQ(udCount, 2); // AD section and sysInfo section
263}
264
Matt Spinler131870c2019-09-25 13:29:04 -0500265// Test that we'll create Generic optional sections for sections that
266// there aren't explicit classes for.
267TEST_F(PELTest, GenericSectionTest)
268{
Matt Spinler42828bd2019-10-11 10:39:30 -0500269 auto data = pelDataFactory(TestPELType::pelSimple);
Matt Spinler131870c2019-09-25 13:29:04 -0500270
Patrick Williams075c7922024-08-16 15:19:49 -0400271 std::vector<uint8_t> section1{
272 0x58, 0x58, // ID 'XX'
273 0x00, 0x18, // Size
274 0x01, 0x02, // version, subtype
275 0x03, 0x04, // comp ID
Matt Spinler131870c2019-09-25 13:29:04 -0500276
Patrick Williams075c7922024-08-16 15:19:49 -0400277 // some data
278 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F,
279 0x09, 0x22, 0x3A, 0x00};
Matt Spinler131870c2019-09-25 13:29:04 -0500280
281 std::vector<uint8_t> section2{
282 0x59, 0x59, // ID 'YY'
283 0x00, 0x20, // Size
284 0x01, 0x02, // version, subtype
285 0x03, 0x04, // comp ID
286
287 // some data
288 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F,
289 0x09, 0x22, 0x3A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
290
291 // Add the new sections at the end
Matt Spinler42828bd2019-10-11 10:39:30 -0500292 data.insert(data.end(), section1.begin(), section1.end());
293 data.insert(data.end(), section2.begin(), section2.end());
Matt Spinler131870c2019-09-25 13:29:04 -0500294
295 // Increment the section count
Matt Spinler42828bd2019-10-11 10:39:30 -0500296 data.at(27) += 2;
297 auto origData = data;
Matt Spinler131870c2019-09-25 13:29:04 -0500298
Matt Spinler42828bd2019-10-11 10:39:30 -0500299 PEL pel{data};
Matt Spinler131870c2019-09-25 13:29:04 -0500300
301 const auto& sections = pel.optionalSections();
302
303 bool foundXX = false;
304 bool foundYY = false;
305
306 // Check that we can find these 2 Generic sections
307 for (const auto& section : sections)
308 {
309 if (section->header().id == 0x5858)
310 {
311 foundXX = true;
312 EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
313 }
314 else if (section->header().id == 0x5959)
315 {
316 foundYY = true;
317 EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
318 }
319 }
320
321 EXPECT_TRUE(foundXX);
322 EXPECT_TRUE(foundYY);
Matt Spinler07eefc52019-09-26 11:18:26 -0500323
324 // Now flatten and check
325 auto newData = pel.data();
326
327 EXPECT_EQ(origData, newData);
Matt Spinler131870c2019-09-25 13:29:04 -0500328}
329
330// Test that an invalid section will still get a Generic object
331TEST_F(PELTest, InvalidGenericTest)
332{
Matt Spinler42828bd2019-10-11 10:39:30 -0500333 auto data = pelDataFactory(TestPELType::pelSimple);
Matt Spinler131870c2019-09-25 13:29:04 -0500334
335 // Not a valid section
336 std::vector<uint8_t> section1{0x01, 0x02, 0x03};
337
Matt Spinler42828bd2019-10-11 10:39:30 -0500338 data.insert(data.end(), section1.begin(), section1.end());
Matt Spinler131870c2019-09-25 13:29:04 -0500339
340 // Increment the section count
Matt Spinler42828bd2019-10-11 10:39:30 -0500341 data.at(27) += 1;
Matt Spinler131870c2019-09-25 13:29:04 -0500342
Matt Spinler42828bd2019-10-11 10:39:30 -0500343 PEL pel{data};
Matt Spinler131870c2019-09-25 13:29:04 -0500344 EXPECT_FALSE(pel.valid());
345
346 const auto& sections = pel.optionalSections();
347
348 bool foundGeneric = false;
349 for (const auto& section : sections)
350 {
351 if (dynamic_cast<Generic*>(section.get()) != nullptr)
352 {
353 foundGeneric = true;
354 EXPECT_EQ(section->valid(), false);
355 break;
356 }
357 }
358
359 EXPECT_TRUE(foundGeneric);
360}
Matt Spinlerafa857c2019-10-24 13:03:46 -0500361
362// Create a UserData section out of AdditionalData
363TEST_F(PELTest, MakeUDSectionTest)
364{
365 std::vector<std::string> ad{"KEY1=VALUE1", "KEY2=VALUE2", "KEY3=VALUE3",
366 "ESEL=TEST"};
367 AdditionalData additionalData{ad};
368
369 auto ud = util::makeADUserDataSection(additionalData);
370
371 EXPECT_TRUE(ud->valid());
372 EXPECT_EQ(ud->header().id, 0x5544);
373 EXPECT_EQ(ud->header().version, 0x01);
374 EXPECT_EQ(ud->header().subType, 0x01);
375 EXPECT_EQ(ud->header().componentID, 0x2000);
376
377 const auto& d = ud->data();
378
379 std::string jsonString{d.begin(), d.end()};
Matt Spinler53407be2019-11-18 09:16:31 -0600380
381 std::string expectedJSON =
Matt Spinlerafa857c2019-10-24 13:03:46 -0500382 R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":"VALUE3"})";
Matt Spinler53407be2019-11-18 09:16:31 -0600383
384 // The actual data is null padded to a 4B boundary.
385 std::vector<uint8_t> expectedData;
386 expectedData.resize(52, '\0');
387 memcpy(expectedData.data(), expectedJSON.data(), expectedJSON.size());
388
389 EXPECT_EQ(d, expectedData);
Matt Spinlerafa857c2019-10-24 13:03:46 -0500390
391 // Ensure we can read this as JSON
392 auto newJSON = nlohmann::json::parse(jsonString);
393 EXPECT_EQ(newJSON["KEY1"], "VALUE1");
394 EXPECT_EQ(newJSON["KEY2"], "VALUE2");
395 EXPECT_EQ(newJSON["KEY3"], "VALUE3");
Matt Spinler97d19b42019-10-29 11:34:03 -0500396}
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600397
398// Create the UserData section that contains system info
Matt Spinler677381b2020-01-23 10:04:29 -0600399TEST_F(PELTest, SysInfoSectionTest)
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600400{
401 MockDataInterface dataIface;
402
Matt Spinler677381b2020-01-23 10:04:29 -0600403 EXPECT_CALL(dataIface, getBMCFWVersionID()).WillOnce(Return("ABCD1234"));
Matt Spinler4aa23a12020-02-03 15:05:09 -0600404 EXPECT_CALL(dataIface, getBMCState()).WillOnce(Return("State.Ready"));
405 EXPECT_CALL(dataIface, getChassisState()).WillOnce(Return("State.On"));
406 EXPECT_CALL(dataIface, getHostState()).WillOnce(Return("State.Off"));
Sumit Kumar2c36fdd2021-09-21 03:12:11 -0500407 EXPECT_CALL(dataIface, getBootState())
408 .WillOnce(Return("State.SystemInitComplete"));
Ben Tynere32b7e72021-05-18 12:38:40 -0500409 EXPECT_CALL(dataIface, getSystemIMKeyword())
410 .WillOnce(Return(std::vector<uint8_t>{0, 1, 0x55, 0xAA}));
Matt Spinler677381b2020-01-23 10:04:29 -0600411
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600412 std::string pid = "_PID=" + std::to_string(getpid());
413 std::vector<std::string> ad{pid};
414 AdditionalData additionalData{ad};
415
416 auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface);
417
418 EXPECT_TRUE(ud->valid());
419 EXPECT_EQ(ud->header().id, 0x5544);
420 EXPECT_EQ(ud->header().version, 0x01);
421 EXPECT_EQ(ud->header().subType, 0x01);
422 EXPECT_EQ(ud->header().componentID, 0x2000);
423
424 // Pull out the JSON data and check it.
425 const auto& d = ud->data();
426 std::string jsonString{d.begin(), d.end()};
427 auto json = nlohmann::json::parse(jsonString);
428
Patrick Williamsd9f0d642021-04-21 15:43:21 -0500429 // Ensure the 'Process Name' entry contains the name of this test
430 // executable.
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600431 auto name = json["Process Name"].get<std::string>();
Patrick Williamsd9f0d642021-04-21 15:43:21 -0500432 auto found = (name.find("pel_test") != std::string::npos) ||
433 (name.find("test-openpower-pels-pel") != std::string::npos);
434 EXPECT_TRUE(found);
435 // @TODO(stwcx): remove 'pel_test' when removing autotools.
Matt Spinler677381b2020-01-23 10:04:29 -0600436
Matt Spinlerc2b8a512021-05-21 12:44:42 -0600437 auto version = json["FW Version ID"].get<std::string>();
Matt Spinler677381b2020-01-23 10:04:29 -0600438 EXPECT_EQ(version, "ABCD1234");
Matt Spinler4aa23a12020-02-03 15:05:09 -0600439
440 auto state = json["BMCState"].get<std::string>();
441 EXPECT_EQ(state, "Ready");
442
443 state = json["ChassisState"].get<std::string>();
444 EXPECT_EQ(state, "On");
445
446 state = json["HostState"].get<std::string>();
447 EXPECT_EQ(state, "Off");
Ben Tynere32b7e72021-05-18 12:38:40 -0500448
Sumit Kumar2c36fdd2021-09-21 03:12:11 -0500449 state = json["BootState"].get<std::string>();
450 EXPECT_EQ(state, "SystemInitComplete");
451
Ben Tynere32b7e72021-05-18 12:38:40 -0500452 auto keyword = json["System IM"].get<std::string>();
453 EXPECT_EQ(keyword, "000155AA");
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600454}
Matt Spinlerce3f4502020-01-22 15:44:35 -0600455
456// Test that the sections that override
457// virtual std::optional<std::string> Section::getJSON() const
458// return valid JSON.
459TEST_F(PELTest, SectionJSONTest)
460{
461 auto data = pelDataFactory(TestPELType::pelSimple);
462 PEL pel{data};
463
464 // Check that all JSON returned from the sections is
465 // parseable by nlohmann::json, which will throw an
466 // exception and fail the test if there is a problem.
467
468 // The getJSON() response needs to be wrapped in a { } to make
469 // actual valid JSON (PEL::toJSON() usually handles that).
470
Matt Spinlerb832aa52023-03-21 15:32:34 -0500471 auto jsonString = pel.privateHeader().getJSON('O');
Matt Spinlerce3f4502020-01-22 15:44:35 -0600472
473 // PrivateHeader always prints JSON
474 ASSERT_TRUE(jsonString);
475 *jsonString = '{' + *jsonString + '}';
476 auto json = nlohmann::json::parse(*jsonString);
477
Matt Spinlerb832aa52023-03-21 15:32:34 -0500478 jsonString = pel.userHeader().getJSON('O');
Matt Spinlerce3f4502020-01-22 15:44:35 -0600479
480 // UserHeader always prints JSON
481 ASSERT_TRUE(jsonString);
482 *jsonString = '{' + *jsonString + '}';
483 json = nlohmann::json::parse(*jsonString);
484
485 for (const auto& section : pel.optionalSections())
486 {
487 // The optional sections may or may not have implemented getJSON().
Matt Spinlerb832aa52023-03-21 15:32:34 -0500488 jsonString = section->getJSON('O');
Matt Spinlerce3f4502020-01-22 15:44:35 -0600489 if (jsonString)
490 {
491 *jsonString = '{' + *jsonString + '}';
492 auto json = nlohmann::json::parse(*jsonString);
493 }
494 }
495}
Matt Spinler5b289b22020-03-26 14:27:19 -0500496
497PelFFDCfile getJSONFFDC(const fs::path& dir)
498{
499 PelFFDCfile ffdc;
500 ffdc.format = UserDataFormat::json;
501 ffdc.subType = 5;
502 ffdc.version = 42;
503
504 auto inputJSON = R"({
505 "key1": "value1",
506 "key2": 42,
507 "key3" : [1, 2, 3, 4, 5],
508 "key4": {"key5": "value5"}
509 })"_json;
510
511 // Write the JSON to a file and get its descriptor.
512 auto s = inputJSON.dump();
513 std::vector<uint8_t> data{s.begin(), s.end()};
514 ffdc.fd = writeFileAndGetFD(dir, data);
515
516 return ffdc;
517}
518
519TEST_F(PELTest, MakeJSONFileUDSectionTest)
520{
521 auto dir = makeTempDir();
522
523 {
524 auto ffdc = getJSONFFDC(dir);
525
526 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
527 close(ffdc.fd);
528 ASSERT_TRUE(ud);
529 ASSERT_TRUE(ud->valid());
530 EXPECT_EQ(ud->header().id, 0x5544);
531
532 EXPECT_EQ(ud->header().version,
533 static_cast<uint8_t>(UserDataFormatVersion::json));
534 EXPECT_EQ(ud->header().subType,
535 static_cast<uint8_t>(UserDataFormat::json));
536 EXPECT_EQ(ud->header().componentID,
537 static_cast<uint16_t>(ComponentID::phosphorLogging));
538
539 // Pull the JSON back out of the the UserData section
540 const auto& d = ud->data();
541 std::string js{d.begin(), d.end()};
542 auto json = nlohmann::json::parse(js);
543
544 EXPECT_EQ("value1", json["key1"].get<std::string>());
545 EXPECT_EQ(42, json["key2"].get<int>());
546
547 std::vector<int> key3Values{1, 2, 3, 4, 5};
548 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>());
549
550 std::map<std::string, std::string> key4Values{{"key5", "value5"}};
551 auto actual = json["key4"].get<std::map<std::string, std::string>>();
552 EXPECT_EQ(key4Values, actual);
553 }
554
555 {
556 // A bad FD
557 PelFFDCfile ffdc;
558 ffdc.format = UserDataFormat::json;
559 ffdc.subType = 5;
560 ffdc.version = 42;
561 ffdc.fd = 10000;
562
563 // The section shouldn't get made
564 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
565 ASSERT_FALSE(ud);
566 }
567
568 fs::remove_all(dir);
569}
570
571PelFFDCfile getCBORFFDC(const fs::path& dir)
572{
573 PelFFDCfile ffdc;
574 ffdc.format = UserDataFormat::cbor;
575 ffdc.subType = 5;
576 ffdc.version = 42;
577
578 auto inputJSON = R"({
579 "key1": "value1",
580 "key2": 42,
581 "key3" : [1, 2, 3, 4, 5],
582 "key4": {"key5": "value5"}
583 })"_json;
584
585 // Convert the JSON to CBOR and write it to a file
586 auto data = nlohmann::json::to_cbor(inputJSON);
587 ffdc.fd = writeFileAndGetFD(dir, data);
588
589 return ffdc;
590}
591
592TEST_F(PELTest, MakeCBORFileUDSectionTest)
593{
594 auto dir = makeTempDir();
595
596 auto ffdc = getCBORFFDC(dir);
597 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
598 close(ffdc.fd);
599 ASSERT_TRUE(ud);
600 ASSERT_TRUE(ud->valid());
601 EXPECT_EQ(ud->header().id, 0x5544);
602
603 EXPECT_EQ(ud->header().version,
604 static_cast<uint8_t>(UserDataFormatVersion::cbor));
605 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::cbor));
606 EXPECT_EQ(ud->header().componentID,
607 static_cast<uint16_t>(ComponentID::phosphorLogging));
608
609 // Pull the CBOR back out of the PEL section
610 // The number of pad bytes to make the section be 4B aligned
611 // was added at the end, read it and then remove it and the
612 // padding before parsing it.
613 auto data = ud->data();
614 Stream stream{data};
615 stream.offset(data.size() - 4);
616 uint32_t pad;
617 stream >> pad;
618
619 data.resize(data.size() - 4 - pad);
620
621 auto json = nlohmann::json::from_cbor(data);
622
623 EXPECT_EQ("value1", json["key1"].get<std::string>());
624 EXPECT_EQ(42, json["key2"].get<int>());
625
626 std::vector<int> key3Values{1, 2, 3, 4, 5};
627 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>());
628
629 std::map<std::string, std::string> key4Values{{"key5", "value5"}};
630 auto actual = json["key4"].get<std::map<std::string, std::string>>();
631 EXPECT_EQ(key4Values, actual);
632
633 fs::remove_all(dir);
634}
635
636PelFFDCfile getTextFFDC(const fs::path& dir)
637{
638 PelFFDCfile ffdc;
639 ffdc.format = UserDataFormat::text;
640 ffdc.subType = 5;
641 ffdc.version = 42;
642
643 std::string text{"this is some text that will be used for FFDC"};
644 std::vector<uint8_t> data{text.begin(), text.end()};
645
646 ffdc.fd = writeFileAndGetFD(dir, data);
647
648 return ffdc;
649}
650
651TEST_F(PELTest, MakeTextFileUDSectionTest)
652{
653 auto dir = makeTempDir();
654
655 auto ffdc = getTextFFDC(dir);
656 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
657 close(ffdc.fd);
658 ASSERT_TRUE(ud);
659 ASSERT_TRUE(ud->valid());
660 EXPECT_EQ(ud->header().id, 0x5544);
661
662 EXPECT_EQ(ud->header().version,
663 static_cast<uint8_t>(UserDataFormatVersion::text));
664 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::text));
665 EXPECT_EQ(ud->header().componentID,
666 static_cast<uint16_t>(ComponentID::phosphorLogging));
667
668 // Get the text back out
669 std::string text{ud->data().begin(), ud->data().end()};
670 EXPECT_EQ(text, "this is some text that will be used for FFDC");
671
672 fs::remove_all(dir);
673}
674
675PelFFDCfile getCustomFFDC(const fs::path& dir, const std::vector<uint8_t>& data)
676{
677 PelFFDCfile ffdc;
678 ffdc.format = UserDataFormat::custom;
679 ffdc.subType = 5;
680 ffdc.version = 42;
681
682 ffdc.fd = writeFileAndGetFD(dir, data);
683
684 return ffdc;
685}
686
687TEST_F(PELTest, MakeCustomFileUDSectionTest)
688{
689 auto dir = makeTempDir();
690
691 {
692 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8};
693
694 auto ffdc = getCustomFFDC(dir, data);
695 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
696 close(ffdc.fd);
697 ASSERT_TRUE(ud);
698 ASSERT_TRUE(ud->valid());
699 EXPECT_EQ(ud->header().size, 8 + 8); // data size + header size
700 EXPECT_EQ(ud->header().id, 0x5544);
701
702 EXPECT_EQ(ud->header().version, 42);
703 EXPECT_EQ(ud->header().subType, 5);
704 EXPECT_EQ(ud->header().componentID, 0x2002);
705
706 // Get the data back out
707 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()};
708 EXPECT_EQ(data, newData);
709 }
710
711 // Do the same thing again, but make it be non 4B aligned
712 // so the data gets padded.
713 {
714 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8, 9};
715
716 auto ffdc = getCustomFFDC(dir, data);
717 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
718 close(ffdc.fd);
719 ASSERT_TRUE(ud);
720 ASSERT_TRUE(ud->valid());
721 EXPECT_EQ(ud->header().size, 12 + 8); // data size + header size
722 EXPECT_EQ(ud->header().id, 0x5544);
723
724 EXPECT_EQ(ud->header().version, 42);
725 EXPECT_EQ(ud->header().subType, 5);
726 EXPECT_EQ(ud->header().componentID, 0x2002);
727
728 // Get the data back out
729 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()};
730
731 // pad the original to 12B so we can compare
732 data.push_back(0);
733 data.push_back(0);
734 data.push_back(0);
735
736 EXPECT_EQ(data, newData);
737 }
738
739 fs::remove_all(dir);
740}
741
742// Test Adding FFDC from files to a PEL
743TEST_F(PELTest, CreateWithFFDCTest)
744{
745 auto dir = makeTempDir();
746 message::Entry regEntry;
747 uint64_t timestamp = 5;
748
749 regEntry.name = "test";
750 regEntry.subsystem = 5;
751 regEntry.actionFlags = 0xC000;
752 regEntry.src.type = 0xBD;
753 regEntry.src.reasonCode = 0x1234;
754
755 std::vector<std::string> additionalData{"KEY1=VALUE1"};
756 AdditionalData ad{additionalData};
757 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600758 NiceMock<MockJournal> journal;
Matt Spinler5b289b22020-03-26 14:27:19 -0500759 PelFFDC ffdc;
760
761 std::vector<uint8_t> customData{1, 2, 3, 4, 5, 6, 7, 8};
762
763 // This will be trimmed when added
764 std::vector<uint8_t> hugeCustomData(17000, 0x42);
765
766 ffdc.emplace_back(std::move(getJSONFFDC(dir)));
767 ffdc.emplace_back(std::move(getCBORFFDC(dir)));
768 ffdc.emplace_back(std::move(getTextFFDC(dir)));
769 ffdc.emplace_back(std::move(getCustomFFDC(dir, customData)));
770 ffdc.emplace_back(std::move(getCustomFFDC(dir, hugeCustomData)));
771
772 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
Matt Spinler9d921092022-12-15 11:54:49 -0600773 ad, ffdc, dataIface, journal};
Matt Spinler5b289b22020-03-26 14:27:19 -0500774
775 EXPECT_TRUE(pel.valid());
776
777 // Clipped to the max
778 EXPECT_EQ(pel.size(), 16384);
779
780 // Check for the FFDC sections
781 size_t udCount = 0;
782 Section* ud = nullptr;
783
784 for (const auto& section : pel.optionalSections())
785 {
786 if (section->header().id == static_cast<uint16_t>(SectionID::userData))
787 {
788 udCount++;
789 ud = section.get();
790 }
791 }
792
793 EXPECT_EQ(udCount, 7); // AD section, sysInfo, 5 ffdc sections
794
795 // Check the last section was trimmed to
796 // something a bit less that 17000.
797 EXPECT_GT(ud->header().size, 14000);
798 EXPECT_LT(ud->header().size, 16000);
799
800 fs::remove_all(dir);
801}
Matt Spinler0a90a852020-06-04 13:18:27 -0500802
803// Create a PEL with device callouts
804TEST_F(PELTest, CreateWithDevCalloutsTest)
805{
806 message::Entry regEntry;
807 uint64_t timestamp = 5;
808
809 regEntry.name = "test";
810 regEntry.subsystem = 5;
811 regEntry.actionFlags = 0xC000;
812 regEntry.src.type = 0xBD;
813 regEntry.src.reasonCode = 0x1234;
814
815 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600816 NiceMock<MockJournal> journal;
Matt Spinler0a90a852020-06-04 13:18:27 -0500817 PelFFDC ffdc;
818
819 const auto calloutJSON = R"(
820 {
821 "I2C":
822 {
823 "14":
824 {
825 "114":
826 {
827 "Callouts":[
828 {
829 "Name": "/chassis/motherboard/cpu0",
830 "LocationCode": "P1",
831 "Priority": "H"
832 }
833 ],
834 "Dest": "proc 0 target"
835 }
836 }
837 }
838 })";
839
840 std::vector<std::string> names{"systemA"};
841 EXPECT_CALL(dataIface, getSystemNames)
842 .Times(2)
Matt Spinler1ab66962020-10-29 13:21:44 -0500843 .WillRepeatedly(Return(names));
Matt Spinler0a90a852020-06-04 13:18:27 -0500844
Matt Spinler0d92b522021-06-16 13:28:17 -0600845 EXPECT_CALL(dataIface, expandLocationCode("P1", 0))
846 .Times(1)
Matt Spinler0a90a852020-06-04 13:18:27 -0500847 .WillOnce(Return("UXXX-P1"));
848
Matt Spinler2f9225a2020-08-05 12:58:49 -0500849 EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false))
Matt Spinlerbad056b2023-01-25 14:16:57 -0600850 .WillOnce(Return(std::vector<std::string>{
851 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"}));
Matt Spinler0a90a852020-06-04 13:18:27 -0500852
Patrick Williams075c7922024-08-16 15:19:49 -0400853 EXPECT_CALL(dataIface,
854 getHWCalloutFields(
855 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0",
856 _, _, _))
Matt Spinler0a90a852020-06-04 13:18:27 -0500857 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
858 SetArgReferee<3>("123456789ABC")));
859
860 auto dataPath = getPELReadOnlyDataPath();
861 std::ofstream file{dataPath / "systemA_dev_callouts.json"};
862 file << calloutJSON;
863 file.close();
864
865 {
866 std::vector<std::string> data{
867 "CALLOUT_ERRNO=5",
868 "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
869 "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"};
870
871 AdditionalData ad{data};
872
Patrick Williams075c7922024-08-16 15:19:49 -0400873 PEL pel{regEntry, 42,
874 timestamp, phosphor::logging::Entry::Level::Error,
875 ad, ffdc,
876 dataIface, journal};
Matt Spinler0a90a852020-06-04 13:18:27 -0500877
878 ASSERT_TRUE(pel.primarySRC().value()->callouts());
879 auto& callouts = pel.primarySRC().value()->callouts()->callouts();
880 ASSERT_EQ(callouts.size(), 1);
Andrew Geisslerf8e750d2022-01-14 14:56:13 -0600881 ASSERT_TRUE(pel.isHwCalloutPresent());
Matt Spinler0a90a852020-06-04 13:18:27 -0500882
883 EXPECT_EQ(callouts[0]->priority(), 'H');
884 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P1");
885
886 auto& fru = callouts[0]->fruIdentity();
887 EXPECT_EQ(fru->getPN().value(), "1234567");
888 EXPECT_EQ(fru->getCCIN().value(), "CCCC");
889 EXPECT_EQ(fru->getSN().value(), "123456789ABC");
890
891 const auto& section = pel.optionalSections().back();
892
893 ASSERT_EQ(section->header().id, 0x5544); // UD
894 auto ud = static_cast<UserData*>(section.get());
895
896 // Check that there was a UserData section added that
897 // contains debug details about the device.
898 const auto& d = ud->data();
899 std::string jsonString{d.begin(), d.end()};
900 auto actualJSON = nlohmann::json::parse(jsonString);
901
902 auto expectedJSON = R"(
903 {
904 "PEL Internal Debug Data": {
905 "SRC": [
906 "I2C: bus: 14 address: 114 dest: proc 0 target"
907 ]
908 }
909 }
910 )"_json;
911
Arya K Padmand8ae6182024-07-19 06:25:10 -0500912 EXPECT_TRUE(
913 actualJSON.contains("/PEL Internal Debug Data/SRC"_json_pointer));
914 EXPECT_EQ(actualJSON["PEL Internal Debug Data"]["SRC"],
915 expectedJSON["PEL Internal Debug Data"]["SRC"]);
Matt Spinler0a90a852020-06-04 13:18:27 -0500916 }
917
918 {
919 // Device path not found (wrong i2c addr), so no callouts
920 std::vector<std::string> data{
921 "CALLOUT_ERRNO=5",
922 "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
923 "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0099"};
924
925 AdditionalData ad{data};
926
Patrick Williams075c7922024-08-16 15:19:49 -0400927 PEL pel{regEntry, 42,
928 timestamp, phosphor::logging::Entry::Level::Error,
929 ad, ffdc,
930 dataIface, journal};
Matt Spinler0a90a852020-06-04 13:18:27 -0500931
932 // no callouts
933 EXPECT_FALSE(pel.primarySRC().value()->callouts());
934
935 // Now check that there was a UserData section
936 // that contains the lookup error.
937 const auto& section = pel.optionalSections().back();
938
939 ASSERT_EQ(section->header().id, 0x5544); // UD
940 auto ud = static_cast<UserData*>(section.get());
941
942 const auto& d = ud->data();
943
944 std::string jsonString{d.begin(), d.end()};
945
946 auto actualJSON = nlohmann::json::parse(jsonString);
947
948 auto expectedJSON =
949 "{\"PEL Internal Debug Data\":{\"SRC\":"
950 "[\"Problem looking up I2C callouts on 14 153: "
951 "[json.exception.out_of_range.403] key '153' not found\"]}}"_json;
952
Arya K Padmand8ae6182024-07-19 06:25:10 -0500953 EXPECT_TRUE(
954 actualJSON.contains("/PEL Internal Debug Data/SRC"_json_pointer));
955 EXPECT_EQ(actualJSON["PEL Internal Debug Data"]["SRC"],
956 expectedJSON["PEL Internal Debug Data"]["SRC"]);
Matt Spinler0a90a852020-06-04 13:18:27 -0500957 }
958
959 fs::remove_all(dataPath);
960}
Matt Spinlere513dbc2020-08-27 11:14:17 -0500961
962// Test PELs when the callouts are passed in using a JSON file.
963TEST_F(PELTest, CreateWithJSONCalloutsTest)
964{
965 PelFFDCfile ffdcFile;
966 ffdcFile.format = UserDataFormat::json;
967 ffdcFile.subType = 0xCA; // Callout JSON
968 ffdcFile.version = 1;
969
970 // Write these callouts to a JSON file and pass it into
Matt Spinler4efed0e2024-02-26 11:16:07 -0600971 // the PEL as an FFDC file. Also has a duplicate that
972 // will be removed.
Matt Spinlere513dbc2020-08-27 11:14:17 -0500973 auto inputJSON = R"([
974 {
975 "Priority": "H",
976 "LocationCode": "P0-C1"
977 },
978 {
979 "Priority": "M",
980 "Procedure": "PROCEDURE"
Matt Spinler4efed0e2024-02-26 11:16:07 -0600981 },
982 {
983 "Priority": "L",
984 "Procedure": "PROCEDURE"
Matt Spinlere513dbc2020-08-27 11:14:17 -0500985 }
986 ])"_json;
987
988 auto s = inputJSON.dump();
989 std::vector<uint8_t> data{s.begin(), s.end()};
990 auto dir = makeTempDir();
991 ffdcFile.fd = writeFileAndGetFD(dir, data);
992
993 PelFFDC ffdc;
994 ffdc.push_back(std::move(ffdcFile));
995
996 AdditionalData ad;
997 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600998 NiceMock<MockJournal> journal;
Matt Spinlere513dbc2020-08-27 11:14:17 -0500999
1000 EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1001 .Times(1)
1002 .WillOnce(Return("UXXX-P0-C1"));
1003 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1004 .Times(1)
Matt Spinlerbad056b2023-01-25 14:16:57 -06001005 .WillOnce(Return(
1006 std::vector<std::string>{"/inv/system/chassis/motherboard/bmc"}));
Matt Spinlere513dbc2020-08-27 11:14:17 -05001007 EXPECT_CALL(dataIface, getHWCalloutFields(
1008 "/inv/system/chassis/motherboard/bmc", _, _, _))
1009 .Times(1)
1010 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1011 SetArgReferee<3>("123456789ABC")));
1012
1013 message::Entry regEntry;
1014 regEntry.name = "test";
1015 regEntry.subsystem = 5;
1016 regEntry.actionFlags = 0xC000;
1017 regEntry.src.type = 0xBD;
1018 regEntry.src.reasonCode = 0x1234;
1019
Matt Spinler9d921092022-12-15 11:54:49 -06001020 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error,
1021 ad, ffdc, dataIface, journal};
Matt Spinlere513dbc2020-08-27 11:14:17 -05001022
1023 ASSERT_TRUE(pel.valid());
1024 ASSERT_TRUE(pel.primarySRC().value()->callouts());
1025 const auto& callouts = pel.primarySRC().value()->callouts()->callouts();
1026 ASSERT_EQ(callouts.size(), 2);
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001027 ASSERT_TRUE(pel.isHwCalloutPresent());
Matt Spinlere513dbc2020-08-27 11:14:17 -05001028
1029 {
1030 EXPECT_EQ(callouts[0]->priority(), 'H');
1031 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1032
1033 auto& fru = callouts[0]->fruIdentity();
1034 EXPECT_EQ(fru->getPN().value(), "1234567");
1035 EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1036 EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1037 EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1038 }
1039 {
1040 EXPECT_EQ(callouts[1]->priority(), 'M');
1041 EXPECT_EQ(callouts[1]->locationCode(), "");
1042
1043 auto& fru = callouts[1]->fruIdentity();
1044 EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1045 EXPECT_EQ(fru->failingComponentType(),
1046 src::FRUIdentity::maintenanceProc);
1047 }
1048 fs::remove_all(dir);
1049}
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001050
1051// Test PELs with symblic FRU callout.
1052TEST_F(PELTest, CreateWithJSONSymblicCalloutTest)
1053{
1054 PelFFDCfile ffdcFile;
1055 ffdcFile.format = UserDataFormat::json;
1056 ffdcFile.subType = 0xCA; // Callout JSON
1057 ffdcFile.version = 1;
1058
1059 // Write these callouts to a JSON file and pass it into
1060 // the PEL as an FFDC file.
1061 auto inputJSON = R"([
1062 {
1063 "Priority": "M",
1064 "Procedure": "SVCDOCS"
1065 }
1066 ])"_json;
1067
1068 auto s = inputJSON.dump();
1069 std::vector<uint8_t> data{s.begin(), s.end()};
1070 auto dir = makeTempDir();
1071 ffdcFile.fd = writeFileAndGetFD(dir, data);
1072
1073 PelFFDC ffdc;
1074 ffdc.push_back(std::move(ffdcFile));
1075
1076 AdditionalData ad;
1077 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -06001078 NiceMock<MockJournal> journal;
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001079
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001080 message::Entry regEntry;
1081 regEntry.name = "test";
1082 regEntry.subsystem = 5;
1083 regEntry.actionFlags = 0xC000;
1084 regEntry.src.type = 0xBD;
1085 regEntry.src.reasonCode = 0x1234;
1086
Matt Spinler9d921092022-12-15 11:54:49 -06001087 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error,
1088 ad, ffdc, dataIface, journal};
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001089
1090 ASSERT_TRUE(pel.valid());
1091 ASSERT_TRUE(pel.primarySRC().value()->callouts());
1092 const auto& callouts = pel.primarySRC().value()->callouts()->callouts();
1093 ASSERT_EQ(callouts.size(), 1);
1094 ASSERT_FALSE(pel.isHwCalloutPresent());
1095
1096 {
1097 EXPECT_EQ(callouts[0]->priority(), 'M');
1098 EXPECT_EQ(callouts[0]->locationCode(), "");
1099
1100 auto& fru = callouts[0]->fruIdentity();
1101 EXPECT_EQ(fru->getMaintProc().value(), "SVCDOCS");
1102 }
1103 fs::remove_all(dir);
1104}
Matt Spinler9d921092022-12-15 11:54:49 -06001105
1106TEST_F(PELTest, FlattenLinesTest)
1107{
1108 std::vector<std::string> msgs{"test1 test2", "test3 test4", "test5 test6"};
1109
1110 auto buffer = util::flattenLines(msgs);
1111
1112 std::string string{"test1 test2\ntest3 test4\ntest5 test6\n"};
1113 std::vector<uint8_t> expected(string.begin(), string.end());
1114
1115 EXPECT_EQ(buffer, expected);
1116}
1117
1118void checkJournalSection(const std::unique_ptr<Section>& section,
1119 const std::string& expected)
1120{
1121 ASSERT_EQ(SectionID::userData,
1122 static_cast<SectionID>(section->header().id));
1123 ASSERT_EQ(UserDataFormat::text,
1124 static_cast<UserDataFormat>(section->header().subType));
1125 ASSERT_EQ(section->header().version,
1126 static_cast<uint8_t>(UserDataFormatVersion::text));
1127
1128 auto ud = static_cast<UserData*>(section.get());
1129
1130 std::vector<uint8_t> expectedData(expected.begin(), expected.end());
1131
1132 // PEL sections are 4B aligned so add padding before the compare
1133 while (expectedData.size() % 4 != 0)
1134 {
1135 expectedData.push_back('\0');
1136 }
1137
1138 EXPECT_EQ(ud->data(), expectedData);
1139}
1140
1141TEST_F(PELTest, CaptureJournalTest)
1142{
1143 message::Entry regEntry;
1144 uint64_t timestamp = 5;
1145
1146 regEntry.name = "test";
1147 regEntry.subsystem = 5;
1148 regEntry.actionFlags = 0xC000;
1149 regEntry.src.type = 0xBD;
1150 regEntry.src.reasonCode = 0x1234;
1151
1152 std::vector<std::string> data;
1153 AdditionalData ad{data};
1154 NiceMock<MockDataInterface> dataIface;
1155 NiceMock<MockJournal> journal;
1156 PelFFDC ffdc;
1157
Matt Spinler9d921092022-12-15 11:54:49 -06001158 size_t pelSectsWithOneUD{0};
1159
1160 {
1161 // Capture 5 lines from the journal into a single UD section
1162 message::JournalCapture jc = size_t{5};
1163 regEntry.journalCapture = jc;
1164
1165 std::vector<std::string> msgs{"test1 test2", "test3 test4",
1166 "test5 test6", "4", "5"};
1167
1168 EXPECT_CALL(journal, getMessages("", 5)).WillOnce(Return(msgs));
1169
Patrick Williams075c7922024-08-16 15:19:49 -04001170 PEL pel{regEntry, 42,
1171 timestamp, phosphor::logging::Entry::Level::Error,
1172 ad, ffdc,
1173 dataIface, journal};
Matt Spinler9d921092022-12-15 11:54:49 -06001174
1175 // Check the generated UserData section
1176 std::string expected{"test1 test2\ntest3 test4\ntest5 test6\n4\n5\n"};
1177
1178 checkJournalSection(pel.optionalSections().back(), expected);
1179
1180 // Save for upcoming testcases
1181 pelSectsWithOneUD = pel.privateHeader().sectionCount();
1182 }
1183
1184 {
1185 // Attempt to capture too many journal entries so the
1186 // section gets dropped.
1187 message::JournalCapture jc = size_t{1};
1188 regEntry.journalCapture = jc;
1189
1190 EXPECT_CALL(journal, sync()).Times(1);
1191
1192 // A 20000 byte line won't fit in a PEL
1193 EXPECT_CALL(journal, getMessages("", 1))
1194 .WillOnce(
1195 Return(std::vector<std::string>{std::string(20000, 'x')}));
1196
Patrick Williams075c7922024-08-16 15:19:49 -04001197 PEL pel{regEntry, 42,
1198 timestamp, phosphor::logging::Entry::Level::Error,
1199 ad, ffdc,
1200 dataIface, journal};
Matt Spinler9d921092022-12-15 11:54:49 -06001201
1202 // Check for 1 fewer sections than in the previous PEL
1203 EXPECT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD - 1);
1204 }
1205
1206 // Capture 3 different journal sections
1207 {
1208 message::AppCaptureList captureList{
1209 message::AppCapture{"app1", 3},
1210 message::AppCapture{"app2", 4},
1211 message::AppCapture{"app3", 1},
1212 };
1213 message::JournalCapture jc = captureList;
1214 regEntry.journalCapture = jc;
1215
1216 std::vector<std::string> app1{"A B", "C D", "E F"};
1217 std::vector<std::string> app2{"1 2", "3 4", "5 6", "7 8"};
1218 std::vector<std::string> app3{"a b c"};
1219
1220 std::string expected1{"A B\nC D\nE F\n"};
1221 std::string expected2{"1 2\n3 4\n5 6\n7 8\n"};
1222 std::string expected3{"a b c\n"};
1223
1224 EXPECT_CALL(journal, sync()).Times(1);
1225 EXPECT_CALL(journal, getMessages("app1", 3)).WillOnce(Return(app1));
1226 EXPECT_CALL(journal, getMessages("app2", 4)).WillOnce(Return(app2));
1227 EXPECT_CALL(journal, getMessages("app3", 1)).WillOnce(Return(app3));
1228
Patrick Williams075c7922024-08-16 15:19:49 -04001229 PEL pel{regEntry, 42,
1230 timestamp, phosphor::logging::Entry::Level::Error,
1231 ad, ffdc,
1232 dataIface, journal};
Matt Spinler9d921092022-12-15 11:54:49 -06001233
1234 // Two more sections than the 1 extra UD section in the first testcase
1235 ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD + 2);
1236
1237 const auto& optionalSections = pel.optionalSections();
1238 auto numOptSections = optionalSections.size();
1239
1240 checkJournalSection(optionalSections[numOptSections - 3], expected1);
1241 checkJournalSection(optionalSections[numOptSections - 2], expected2);
1242 checkJournalSection(optionalSections[numOptSections - 1], expected3);
1243 }
1244
1245 {
1246 // One section gets saved, and one is too big and gets dropped
1247 message::AppCaptureList captureList{
1248 message::AppCapture{"app4", 2},
1249 message::AppCapture{"app5", 1},
1250 };
1251 message::JournalCapture jc = captureList;
1252 regEntry.journalCapture = jc;
1253
1254 std::vector<std::string> app4{"w x", "y z"};
1255 std::string expected4{"w x\ny z\n"};
1256
1257 EXPECT_CALL(journal, sync()).Times(1);
1258
1259 EXPECT_CALL(journal, getMessages("app4", 2)).WillOnce(Return(app4));
1260
1261 // A 20000 byte line won't fit in a PEL
1262 EXPECT_CALL(journal, getMessages("app5", 1))
1263 .WillOnce(
1264 Return(std::vector<std::string>{std::string(20000, 'x')}));
1265
Patrick Williams075c7922024-08-16 15:19:49 -04001266 PEL pel{regEntry, 42,
1267 timestamp, phosphor::logging::Entry::Level::Error,
1268 ad, ffdc,
1269 dataIface, journal};
Matt Spinler9d921092022-12-15 11:54:49 -06001270
1271 // The last section should have been dropped, so same as first TC
1272 ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD);
1273
1274 checkJournalSection(pel.optionalSections().back(), expected4);
1275 }
1276}
Arya K Padmand8ae6182024-07-19 06:25:10 -05001277
1278// API to collect and parse the User Data section of the PEL.
1279nlohmann::json getDIMMInfo(const auto& pel)
1280{
1281 nlohmann::json dimmInfo{};
1282 auto hasDIMMInfo = [&dimmInfo](const auto& optionalSection) {
1283 if (optionalSection->header().id !=
1284 static_cast<uint16_t>(SectionID::userData))
1285 {
1286 return false;
1287 }
1288 else
1289 {
1290 auto userData = static_cast<UserData*>(optionalSection.get());
1291
1292 // convert the userdata section to string and then parse in to json
1293 // format
1294 std::string userDataString{userData->data().begin(),
1295 userData->data().end()};
1296 nlohmann::json userDataJson = nlohmann::json::parse(userDataString);
1297
1298 if (userDataJson.contains("DIMMs Additional Info"))
1299 {
1300 dimmInfo = userDataJson.at("DIMMs Additional Info");
1301 }
1302 else if (
1303 userDataJson.contains(
1304 "/PEL Internal Debug Data/DIMMs Info Fetch Error"_json_pointer))
1305 {
1306 dimmInfo = userDataJson.at(
1307 "/PEL Internal Debug Data/DIMMs Info Fetch Error"_json_pointer);
1308 }
1309 else
1310 {
1311 return false;
1312 }
1313 return true;
1314 }
1315 };
1316 std::ranges::any_of(pel.optionalSections(), hasDIMMInfo);
1317
1318 return dimmInfo;
1319}
1320
1321// Test whether the DIMM callouts manufacturing info is getting added to the
1322// SysInfo User Data section of the PEL
1323TEST_F(PELTest, TestDimmsCalloutInfo)
1324{
1325 {
1326 message::Entry entry;
1327 uint64_t timestamp = 5;
1328 AdditionalData ad;
1329 NiceMock<MockDataInterface> dataIface;
1330 NiceMock<MockJournal> journal;
1331 PelFFDC ffdc;
1332
1333 // When callouts contain DIMM callouts.
1334 entry.callouts = R"(
1335 [
1336 {
1337 "CalloutList": [
1338 {
1339 "Priority": "high",
1340 "LocCode": "P0-DIMM0"
1341 },
1342 {
1343 "Priority": "low",
1344 "LocCode": "P0-DIMM1"
1345 }
1346 ]
1347 }
1348 ]
1349 )"_json;
1350
1351 EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM0", 0))
1352 .WillOnce(Return("U98D-P0-DIMM0"));
1353 EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM1", 0))
1354 .WillOnce(Return("U98D-P0-DIMM1"));
1355
1356 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM0", 0, false))
1357 .WillOnce(Return(std::vector<std::string>{
1358 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"}));
1359 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM1", 0, false))
1360 .WillOnce(Return(std::vector<std::string>{
1361 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm1"}));
1362
1363 std::vector<uint8_t> diValue{128, 74};
1364 EXPECT_CALL(dataIface, getDIProperty("U98D-P0-DIMM0"))
1365 .WillOnce(Return(diValue));
1366 EXPECT_CALL(dataIface, getDIProperty("U98D-P0-DIMM1"))
1367 .WillOnce(Return(diValue));
1368
1369 // Add some location code in expanded format to DIMM cache memory
1370 dataIface.addDIMMLocCode("U98D-P0-DIMM0", true);
1371 dataIface.addDIMMLocCode("U98D-P0-DIMM1", true);
1372
1373 PEL pel{entry, 42, timestamp, phosphor::logging::Entry::Level::Error,
1374 ad, ffdc, dataIface, journal};
1375 nlohmann::json dimmInfoJson = getDIMMInfo(pel);
1376
1377 nlohmann::json expected_data = R"(
1378 [
1379 {
1380 "Location Code": "U98D-P0-DIMM0",
1381 "DRAM Manufacturer ID": [
1382 "0x80",
1383 "0x4a"
1384 ]
1385 },
1386 {
1387 "Location Code": "U98D-P0-DIMM1",
1388 "DRAM Manufacturer ID": [
1389 "0x80",
1390 "0x4a"
1391 ]
1392 }
1393 ]
1394 )"_json;
1395 EXPECT_EQ(expected_data, dimmInfoJson);
1396 }
1397}
1398
1399// When PEL has FRU callouts but PHAL is not enabled.
1400TEST_F(PELTest, TestDimmsCalloutInfoWithNoPHAL)
1401{
1402 message::Entry entry;
1403 uint64_t timestamp = 5;
1404 AdditionalData ad;
1405 NiceMock<MockDataInterface> dataIface;
1406 NiceMock<MockJournal> journal;
1407 PelFFDC ffdc;
1408
1409 entry.callouts = R"(
1410 [
1411 {
1412 "CalloutList": [
1413 {
1414 "Priority": "high",
1415 "LocCode": "P0-DIMM0"
1416 },
1417 {
1418 "Priority": "low",
1419 "LocCode": "P0-DIMM1"
1420 }
1421 ]
1422 }
1423 ]
1424 )"_json;
1425
1426 EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM0", 0))
1427 .WillOnce(Return("U98D-P0-DIMM0"));
1428 EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM1", 0))
1429 .WillOnce(Return("U98D-P0-DIMM1"));
1430
1431 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM0", 0, false))
1432 .WillOnce(Return(std::vector<std::string>{
1433 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"}));
1434 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM1", 0, false))
1435 .WillOnce(Return(std::vector<std::string>{
1436 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm1"}));
1437
1438 PEL pel{entry, 42, timestamp, phosphor::logging::Entry::Level::Error,
1439 ad, ffdc, dataIface, journal};
1440
1441 nlohmann::json dimmInfoJson = getDIMMInfo(pel);
1442
1443 nlohmann::json expected_data = R"(
1444 [
1445 "PHAL feature is not enabled, so the LocationCode:[U98D-P0-DIMM0] cannot be determined as DIMM",
1446 "PHAL feature is not enabled, so the LocationCode:[U98D-P0-DIMM1] cannot be determined as DIMM"
1447 ]
1448 )"_json;
1449
1450 EXPECT_EQ(expected_data, dimmInfoJson);
1451}
1452
1453// When the PEL doesn't contain any type of callouts
1454TEST_F(PELTest, TestDimmsCalloutInfoWithNoCallouts)
1455{
1456 message::Entry entry;
1457 uint64_t timestamp = 5;
1458 AdditionalData ad;
1459 NiceMock<MockDataInterface> dataIface;
1460 NiceMock<MockJournal> journal;
1461 PelFFDC ffdc;
1462
1463 PEL pel{entry, 42, timestamp, phosphor::logging::Entry::Level::Error,
1464 ad, ffdc, dataIface, journal};
1465
1466 nlohmann::json dimmInfoJson = getDIMMInfo(pel);
1467
1468 nlohmann::json expected_data{};
1469
1470 EXPECT_EQ(expected_data, dimmInfoJson);
1471}
1472
1473// When the PEL has DIMM callouts, but failed to fetch DI property value
1474TEST_F(PELTest, TestDimmsCalloutInfoDIFailure)
1475{
1476 {
1477 message::Entry entry;
1478 uint64_t timestamp = 5;
1479 AdditionalData ad;
1480 NiceMock<MockDataInterface> dataIface;
1481 NiceMock<MockJournal> journal;
1482 PelFFDC ffdc;
1483
1484 entry.callouts = R"(
1485 [
1486 {
1487 "CalloutList": [
1488 {
1489 "Priority": "high",
1490 "LocCode": "P0-DIMM0"
1491 }
1492 ]
1493 }
1494 ]
1495 )"_json;
1496
1497 EXPECT_CALL(dataIface, expandLocationCode("P0-DIMM0", 0))
1498 .WillOnce(Return("U98D-P0-DIMM0"));
1499
1500 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-DIMM0", 0, false))
1501 .WillOnce(Return(std::vector<std::string>{
1502 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"}));
1503
1504 EXPECT_CALL(dataIface, getDIProperty("U98D-P0-DIMM0"))
1505 .WillOnce(Return(std::nullopt));
1506
1507 // Add some location code in expanded format to DIMM cache memory
1508 dataIface.addDIMMLocCode("U98D-P0-DIMM0", true);
1509
1510 PEL pel{entry, 42, timestamp, phosphor::logging::Entry::Level::Error,
1511 ad, ffdc, dataIface, journal};
1512
1513 nlohmann::json dimmInfoJson = getDIMMInfo(pel);
1514
1515 nlohmann::json expected_data = R"(
1516 [
1517 "Failed reading DI property from VINI Interface for the LocationCode:[U98D-P0-DIMM0]"
1518 ]
1519 )"_json;
1520
1521 EXPECT_EQ(expected_data, dimmInfoJson);
1522 }
1523}