blob: 66cb6cf6a5047dcb53e99a07913b274a2977dc55 [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>
24
25#include <gtest/gtest.h>
26
27namespace fs = std::filesystem;
28using namespace openpower::pels;
Matt Spinler0a90a852020-06-04 13:18:27 -050029using ::testing::_;
William A. Kennington IIIb41fa542021-05-29 14:45:16 -070030using ::testing::DoAll;
Matt Spinler56ad2a02020-03-26 14:00:52 -050031using ::testing::NiceMock;
Matt Spinler677381b2020-01-23 10:04:29 -060032using ::testing::Return;
Matt Spinler0a90a852020-06-04 13:18:27 -050033using ::testing::SetArgReferee;
Matt Spinlercb6b0592019-07-16 15:58:51 -050034
35class PELTest : public CleanLogID
Patrick Williams2544b412022-10-04 08:41:06 -050036{};
Matt Spinlercb6b0592019-07-16 15:58:51 -050037
Matt Spinler5b289b22020-03-26 14:27:19 -050038fs::path makeTempDir()
39{
40 char path[] = "/tmp/tempdirXXXXXX";
41 std::filesystem::path dir = mkdtemp(path);
42 return dir;
43}
44
45int writeFileAndGetFD(const fs::path& dir, const std::vector<uint8_t>& data)
46{
47 static size_t count = 0;
48 fs::path path = dir / (std::string{"file"} + std::to_string(count));
49 std::ofstream stream{path};
50 count++;
51
52 stream.write(reinterpret_cast<const char*>(data.data()), data.size());
53 stream.close();
54
55 FILE* fp = fopen(path.c_str(), "r");
56 return fileno(fp);
57}
58
Matt Spinlercb6b0592019-07-16 15:58:51 -050059TEST_F(PELTest, FlattenTest)
60{
Matt Spinler42828bd2019-10-11 10:39:30 -050061 auto data = pelDataFactory(TestPELType::pelSimple);
Matt Spinler42828bd2019-10-11 10:39:30 -050062 auto pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -050063
64 // Check a few fields
65 EXPECT_TRUE(pel->valid());
66 EXPECT_EQ(pel->id(), 0x80818283);
67 EXPECT_EQ(pel->plid(), 0x50515253);
Matt Spinler97d19b42019-10-29 11:34:03 -050068 EXPECT_EQ(pel->userHeader().subsystem(), 0x10);
69 EXPECT_EQ(pel->userHeader().actionFlags(), 0x80C0);
Matt Spinlercb6b0592019-07-16 15:58:51 -050070
71 // Test that data in == data out
72 auto flattenedData = pel->data();
Matt Spinlerf1b46ff2020-01-22 14:10:04 -060073 EXPECT_EQ(data, flattenedData);
74 EXPECT_EQ(flattenedData.size(), pel->size());
Matt Spinlercb6b0592019-07-16 15:58:51 -050075}
76
77TEST_F(PELTest, CommitTimeTest)
78{
Matt Spinler42828bd2019-10-11 10:39:30 -050079 auto data = pelDataFactory(TestPELType::pelSimple);
80 auto pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -050081
82 auto origTime = pel->commitTime();
83 pel->setCommitTime();
84 auto newTime = pel->commitTime();
85
Matt Spinlerf1b46ff2020-01-22 14:10:04 -060086 EXPECT_NE(origTime, newTime);
Matt Spinlercb6b0592019-07-16 15:58:51 -050087
88 // Make a new PEL and check new value is still there
89 auto newData = pel->data();
90 auto newPel = std::make_unique<PEL>(newData);
Matt Spinlerf1b46ff2020-01-22 14:10:04 -060091 EXPECT_EQ(newTime, newPel->commitTime());
Matt Spinlercb6b0592019-07-16 15:58:51 -050092}
93
94TEST_F(PELTest, AssignIDTest)
95{
Matt Spinler42828bd2019-10-11 10:39:30 -050096 auto data = pelDataFactory(TestPELType::pelSimple);
97 auto pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -050098
99 auto origID = pel->id();
100 pel->assignID();
101 auto newID = pel->id();
102
Matt Spinlerf1b46ff2020-01-22 14:10:04 -0600103 EXPECT_NE(origID, newID);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500104
105 // Make a new PEL and check new value is still there
106 auto newData = pel->data();
107 auto newPel = std::make_unique<PEL>(newData);
Matt Spinlerf1b46ff2020-01-22 14:10:04 -0600108 EXPECT_EQ(newID, newPel->id());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500109}
110
111TEST_F(PELTest, WithLogIDTest)
112{
Matt Spinler42828bd2019-10-11 10:39:30 -0500113 auto data = pelDataFactory(TestPELType::pelSimple);
114 auto pel = std::make_unique<PEL>(data, 0x42);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500115
116 EXPECT_TRUE(pel->valid());
117 EXPECT_EQ(pel->obmcLogID(), 0x42);
118}
119
120TEST_F(PELTest, InvalidPELTest)
121{
Matt Spinler42828bd2019-10-11 10:39:30 -0500122 auto data = pelDataFactory(TestPELType::pelSimple);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500123
124 // Too small
Matt Spinler42828bd2019-10-11 10:39:30 -0500125 data.resize(PrivateHeader::flattenedSize());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500126
Matt Spinler42828bd2019-10-11 10:39:30 -0500127 auto pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500128
Matt Spinler97d19b42019-10-29 11:34:03 -0500129 EXPECT_TRUE(pel->privateHeader().valid());
130 EXPECT_FALSE(pel->userHeader().valid());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500131 EXPECT_FALSE(pel->valid());
132
Matt Spinlercb6b0592019-07-16 15:58:51 -0500133 // Now corrupt the private header
Matt Spinler42828bd2019-10-11 10:39:30 -0500134 data = pelDataFactory(TestPELType::pelSimple);
135 data.at(0) = 0;
136 pel = std::make_unique<PEL>(data);
Matt Spinlercb6b0592019-07-16 15:58:51 -0500137
Matt Spinler97d19b42019-10-29 11:34:03 -0500138 EXPECT_FALSE(pel->privateHeader().valid());
139 EXPECT_TRUE(pel->userHeader().valid());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500140 EXPECT_FALSE(pel->valid());
141}
142
143TEST_F(PELTest, EmptyDataTest)
144{
145 std::vector<uint8_t> data;
146 auto pel = std::make_unique<PEL>(data);
147
Matt Spinler97d19b42019-10-29 11:34:03 -0500148 EXPECT_FALSE(pel->privateHeader().valid());
149 EXPECT_FALSE(pel->userHeader().valid());
Matt Spinlercb6b0592019-07-16 15:58:51 -0500150 EXPECT_FALSE(pel->valid());
151}
Matt Spinlerb8323632019-09-20 15:11:04 -0500152
153TEST_F(PELTest, CreateFromRegistryTest)
154{
155 message::Entry regEntry;
156 uint64_t timestamp = 5;
157
158 regEntry.name = "test";
159 regEntry.subsystem = 5;
160 regEntry.actionFlags = 0xC000;
Matt Spinlerbd716f02019-10-15 10:54:11 -0500161 regEntry.src.type = 0xBD;
162 regEntry.src.reasonCode = 0x1234;
Matt Spinlerb8323632019-09-20 15:11:04 -0500163
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600164 std::vector<std::string> data{"KEY1=VALUE1"};
165 AdditionalData ad{data};
Matt Spinler56ad2a02020-03-26 14:00:52 -0500166 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600167 NiceMock<MockJournal> journal;
Matt Spinler56ad2a02020-03-26 14:00:52 -0500168 PelFFDC ffdc;
Matt Spinlerbd716f02019-10-15 10:54:11 -0500169
Sumit Kumar9d43a722021-08-24 09:46:19 -0500170 std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
171 "system/entry"};
172 EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
173 .WillRepeatedly(Return(std::vector<bool>{false, false, false}));
174
Matt Spinler56ad2a02020-03-26 14:00:52 -0500175 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
Matt Spinler9d921092022-12-15 11:54:49 -0600176 ad, ffdc, dataIface, journal};
Matt Spinlerb8323632019-09-20 15:11:04 -0500177
178 EXPECT_TRUE(pel.valid());
Matt Spinler97d19b42019-10-29 11:34:03 -0500179 EXPECT_EQ(pel.privateHeader().obmcLogID(), 42);
180 EXPECT_EQ(pel.userHeader().severity(), 0x40);
Matt Spinlerb8323632019-09-20 15:11:04 -0500181
Matt Spinlerbd716f02019-10-15 10:54:11 -0500182 EXPECT_EQ(pel.primarySRC().value()->asciiString(),
183 "BD051234 ");
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600184
185 // Check that certain optional sections have been created
186 size_t mtmsCount = 0;
187 size_t euhCount = 0;
188 size_t udCount = 0;
189
190 for (const auto& section : pel.optionalSections())
191 {
192 if (section->header().id ==
193 static_cast<uint16_t>(SectionID::failingMTMS))
194 {
195 mtmsCount++;
196 }
197 else if (section->header().id ==
198 static_cast<uint16_t>(SectionID::extendedUserHeader))
199 {
200 euhCount++;
201 }
202 else if (section->header().id ==
203 static_cast<uint16_t>(SectionID::userData))
204 {
205 udCount++;
206 }
207 }
208
209 EXPECT_EQ(mtmsCount, 1);
210 EXPECT_EQ(euhCount, 1);
211 EXPECT_EQ(udCount, 2); // AD section and sysInfo section
Andrew Geisslerf8e750d2022-01-14 14:56:13 -0600212 ASSERT_FALSE(pel.isHwCalloutPresent());
Matt Spinler1f93c592020-09-10 10:43:08 -0500213
214 {
215 // The same thing, but without the action flags specified
216 // in the registry, so the constructor should set them.
217 regEntry.actionFlags = std::nullopt;
218
219 PEL pel2{
220 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
Matt Spinler9d921092022-12-15 11:54:49 -0600221 ad, ffdc, dataIface, journal};
Matt Spinler1f93c592020-09-10 10:43:08 -0500222
223 EXPECT_EQ(pel2.userHeader().actionFlags(), 0xA800);
224 }
Matt Spinlerb8323632019-09-20 15:11:04 -0500225}
Matt Spinler131870c2019-09-25 13:29:04 -0500226
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500227// Test that when the AdditionalData size is over 16KB that
228// the PEL that's created is exactly 16KB since the UserData
229// section that contains all that data was pruned.
230TEST_F(PELTest, CreateTooBigADTest)
231{
232 message::Entry regEntry;
233 uint64_t timestamp = 5;
234
235 regEntry.name = "test";
236 regEntry.subsystem = 5;
237 regEntry.actionFlags = 0xC000;
238 regEntry.src.type = 0xBD;
239 regEntry.src.reasonCode = 0x1234;
Matt Spinler56ad2a02020-03-26 14:00:52 -0500240 PelFFDC ffdc;
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500241
242 // Over the 16KB max PEL size
243 std::string bigAD{"KEY1="};
244 bigAD += std::string(17000, 'G');
245
246 std::vector<std::string> data{bigAD};
247 AdditionalData ad{data};
Matt Spinler56ad2a02020-03-26 14:00:52 -0500248 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600249 NiceMock<MockJournal> journal;
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500250
Sumit Kumar9d43a722021-08-24 09:46:19 -0500251 std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
252 "system/entry"};
253 EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
254 .WillOnce(Return(std::vector<bool>{false, false, false}));
255
Matt Spinler56ad2a02020-03-26 14:00:52 -0500256 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
Matt Spinler9d921092022-12-15 11:54:49 -0600257 ad, ffdc, dataIface, journal};
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500258
259 EXPECT_TRUE(pel.valid());
260 EXPECT_EQ(pel.size(), 16384);
261
262 // Make sure that there are still 2 UD sections.
Matt Spinlerbe952d22022-07-01 11:30:11 -0500263 const auto& optSections = pel.optionalSections();
264 auto udCount = std::count_if(
265 optSections.begin(), optSections.end(), [](const auto& section) {
266 return section->header().id ==
267 static_cast<uint16_t>(SectionID::userData);
268 });
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500269
270 EXPECT_EQ(udCount, 2); // AD section and sysInfo section
271}
272
Matt Spinler131870c2019-09-25 13:29:04 -0500273// Test that we'll create Generic optional sections for sections that
274// there aren't explicit classes for.
275TEST_F(PELTest, GenericSectionTest)
276{
Matt Spinler42828bd2019-10-11 10:39:30 -0500277 auto data = pelDataFactory(TestPELType::pelSimple);
Matt Spinler131870c2019-09-25 13:29:04 -0500278
279 std::vector<uint8_t> section1{0x58, 0x58, // ID 'XX'
280 0x00, 0x18, // Size
281 0x01, 0x02, // version, subtype
282 0x03, 0x04, // comp ID
283
284 // some data
285 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63,
286 0x20, 0x31, 0x06, 0x0F, 0x09, 0x22, 0x3A,
287 0x00};
288
289 std::vector<uint8_t> section2{
290 0x59, 0x59, // ID 'YY'
291 0x00, 0x20, // Size
292 0x01, 0x02, // version, subtype
293 0x03, 0x04, // comp ID
294
295 // some data
296 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F,
297 0x09, 0x22, 0x3A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
298
299 // Add the new sections at the end
Matt Spinler42828bd2019-10-11 10:39:30 -0500300 data.insert(data.end(), section1.begin(), section1.end());
301 data.insert(data.end(), section2.begin(), section2.end());
Matt Spinler131870c2019-09-25 13:29:04 -0500302
303 // Increment the section count
Matt Spinler42828bd2019-10-11 10:39:30 -0500304 data.at(27) += 2;
305 auto origData = data;
Matt Spinler131870c2019-09-25 13:29:04 -0500306
Matt Spinler42828bd2019-10-11 10:39:30 -0500307 PEL pel{data};
Matt Spinler131870c2019-09-25 13:29:04 -0500308
309 const auto& sections = pel.optionalSections();
310
311 bool foundXX = false;
312 bool foundYY = false;
313
314 // Check that we can find these 2 Generic sections
315 for (const auto& section : sections)
316 {
317 if (section->header().id == 0x5858)
318 {
319 foundXX = true;
320 EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
321 }
322 else if (section->header().id == 0x5959)
323 {
324 foundYY = true;
325 EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
326 }
327 }
328
329 EXPECT_TRUE(foundXX);
330 EXPECT_TRUE(foundYY);
Matt Spinler07eefc52019-09-26 11:18:26 -0500331
332 // Now flatten and check
333 auto newData = pel.data();
334
335 EXPECT_EQ(origData, newData);
Matt Spinler131870c2019-09-25 13:29:04 -0500336}
337
338// Test that an invalid section will still get a Generic object
339TEST_F(PELTest, InvalidGenericTest)
340{
Matt Spinler42828bd2019-10-11 10:39:30 -0500341 auto data = pelDataFactory(TestPELType::pelSimple);
Matt Spinler131870c2019-09-25 13:29:04 -0500342
343 // Not a valid section
344 std::vector<uint8_t> section1{0x01, 0x02, 0x03};
345
Matt Spinler42828bd2019-10-11 10:39:30 -0500346 data.insert(data.end(), section1.begin(), section1.end());
Matt Spinler131870c2019-09-25 13:29:04 -0500347
348 // Increment the section count
Matt Spinler42828bd2019-10-11 10:39:30 -0500349 data.at(27) += 1;
Matt Spinler131870c2019-09-25 13:29:04 -0500350
Matt Spinler42828bd2019-10-11 10:39:30 -0500351 PEL pel{data};
Matt Spinler131870c2019-09-25 13:29:04 -0500352 EXPECT_FALSE(pel.valid());
353
354 const auto& sections = pel.optionalSections();
355
356 bool foundGeneric = false;
357 for (const auto& section : sections)
358 {
359 if (dynamic_cast<Generic*>(section.get()) != nullptr)
360 {
361 foundGeneric = true;
362 EXPECT_EQ(section->valid(), false);
363 break;
364 }
365 }
366
367 EXPECT_TRUE(foundGeneric);
368}
Matt Spinlerafa857c2019-10-24 13:03:46 -0500369
370// Create a UserData section out of AdditionalData
371TEST_F(PELTest, MakeUDSectionTest)
372{
373 std::vector<std::string> ad{"KEY1=VALUE1", "KEY2=VALUE2", "KEY3=VALUE3",
374 "ESEL=TEST"};
375 AdditionalData additionalData{ad};
376
377 auto ud = util::makeADUserDataSection(additionalData);
378
379 EXPECT_TRUE(ud->valid());
380 EXPECT_EQ(ud->header().id, 0x5544);
381 EXPECT_EQ(ud->header().version, 0x01);
382 EXPECT_EQ(ud->header().subType, 0x01);
383 EXPECT_EQ(ud->header().componentID, 0x2000);
384
385 const auto& d = ud->data();
386
387 std::string jsonString{d.begin(), d.end()};
Matt Spinler53407be2019-11-18 09:16:31 -0600388
389 std::string expectedJSON =
Matt Spinlerafa857c2019-10-24 13:03:46 -0500390 R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":"VALUE3"})";
Matt Spinler53407be2019-11-18 09:16:31 -0600391
392 // The actual data is null padded to a 4B boundary.
393 std::vector<uint8_t> expectedData;
394 expectedData.resize(52, '\0');
395 memcpy(expectedData.data(), expectedJSON.data(), expectedJSON.size());
396
397 EXPECT_EQ(d, expectedData);
Matt Spinlerafa857c2019-10-24 13:03:46 -0500398
399 // Ensure we can read this as JSON
400 auto newJSON = nlohmann::json::parse(jsonString);
401 EXPECT_EQ(newJSON["KEY1"], "VALUE1");
402 EXPECT_EQ(newJSON["KEY2"], "VALUE2");
403 EXPECT_EQ(newJSON["KEY3"], "VALUE3");
Matt Spinler97d19b42019-10-29 11:34:03 -0500404}
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600405
406// Create the UserData section that contains system info
Matt Spinler677381b2020-01-23 10:04:29 -0600407TEST_F(PELTest, SysInfoSectionTest)
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600408{
409 MockDataInterface dataIface;
410
Matt Spinler677381b2020-01-23 10:04:29 -0600411 EXPECT_CALL(dataIface, getBMCFWVersionID()).WillOnce(Return("ABCD1234"));
Matt Spinler4aa23a12020-02-03 15:05:09 -0600412 EXPECT_CALL(dataIface, getBMCState()).WillOnce(Return("State.Ready"));
413 EXPECT_CALL(dataIface, getChassisState()).WillOnce(Return("State.On"));
414 EXPECT_CALL(dataIface, getHostState()).WillOnce(Return("State.Off"));
Sumit Kumar2c36fdd2021-09-21 03:12:11 -0500415 EXPECT_CALL(dataIface, getBootState())
416 .WillOnce(Return("State.SystemInitComplete"));
Ben Tynere32b7e72021-05-18 12:38:40 -0500417 EXPECT_CALL(dataIface, getSystemIMKeyword())
418 .WillOnce(Return(std::vector<uint8_t>{0, 1, 0x55, 0xAA}));
Matt Spinler677381b2020-01-23 10:04:29 -0600419
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600420 std::string pid = "_PID=" + std::to_string(getpid());
421 std::vector<std::string> ad{pid};
422 AdditionalData additionalData{ad};
423
424 auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface);
425
426 EXPECT_TRUE(ud->valid());
427 EXPECT_EQ(ud->header().id, 0x5544);
428 EXPECT_EQ(ud->header().version, 0x01);
429 EXPECT_EQ(ud->header().subType, 0x01);
430 EXPECT_EQ(ud->header().componentID, 0x2000);
431
432 // Pull out the JSON data and check it.
433 const auto& d = ud->data();
434 std::string jsonString{d.begin(), d.end()};
435 auto json = nlohmann::json::parse(jsonString);
436
Patrick Williamsd9f0d642021-04-21 15:43:21 -0500437 // Ensure the 'Process Name' entry contains the name of this test
438 // executable.
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600439 auto name = json["Process Name"].get<std::string>();
Patrick Williamsd9f0d642021-04-21 15:43:21 -0500440 auto found = (name.find("pel_test") != std::string::npos) ||
441 (name.find("test-openpower-pels-pel") != std::string::npos);
442 EXPECT_TRUE(found);
443 // @TODO(stwcx): remove 'pel_test' when removing autotools.
Matt Spinler677381b2020-01-23 10:04:29 -0600444
Matt Spinlerc2b8a512021-05-21 12:44:42 -0600445 auto version = json["FW Version ID"].get<std::string>();
Matt Spinler677381b2020-01-23 10:04:29 -0600446 EXPECT_EQ(version, "ABCD1234");
Matt Spinler4aa23a12020-02-03 15:05:09 -0600447
448 auto state = json["BMCState"].get<std::string>();
449 EXPECT_EQ(state, "Ready");
450
451 state = json["ChassisState"].get<std::string>();
452 EXPECT_EQ(state, "On");
453
454 state = json["HostState"].get<std::string>();
455 EXPECT_EQ(state, "Off");
Ben Tynere32b7e72021-05-18 12:38:40 -0500456
Sumit Kumar2c36fdd2021-09-21 03:12:11 -0500457 state = json["BootState"].get<std::string>();
458 EXPECT_EQ(state, "SystemInitComplete");
459
Ben Tynere32b7e72021-05-18 12:38:40 -0500460 auto keyword = json["System IM"].get<std::string>();
461 EXPECT_EQ(keyword, "000155AA");
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600462}
Matt Spinlerce3f4502020-01-22 15:44:35 -0600463
464// Test that the sections that override
465// virtual std::optional<std::string> Section::getJSON() const
466// return valid JSON.
467TEST_F(PELTest, SectionJSONTest)
468{
469 auto data = pelDataFactory(TestPELType::pelSimple);
470 PEL pel{data};
471
472 // Check that all JSON returned from the sections is
473 // parseable by nlohmann::json, which will throw an
474 // exception and fail the test if there is a problem.
475
476 // The getJSON() response needs to be wrapped in a { } to make
477 // actual valid JSON (PEL::toJSON() usually handles that).
478
Matt Spinlerb832aa52023-03-21 15:32:34 -0500479 auto jsonString = pel.privateHeader().getJSON('O');
Matt Spinlerce3f4502020-01-22 15:44:35 -0600480
481 // PrivateHeader always prints JSON
482 ASSERT_TRUE(jsonString);
483 *jsonString = '{' + *jsonString + '}';
484 auto json = nlohmann::json::parse(*jsonString);
485
Matt Spinlerb832aa52023-03-21 15:32:34 -0500486 jsonString = pel.userHeader().getJSON('O');
Matt Spinlerce3f4502020-01-22 15:44:35 -0600487
488 // UserHeader always prints JSON
489 ASSERT_TRUE(jsonString);
490 *jsonString = '{' + *jsonString + '}';
491 json = nlohmann::json::parse(*jsonString);
492
493 for (const auto& section : pel.optionalSections())
494 {
495 // The optional sections may or may not have implemented getJSON().
Matt Spinlerb832aa52023-03-21 15:32:34 -0500496 jsonString = section->getJSON('O');
Matt Spinlerce3f4502020-01-22 15:44:35 -0600497 if (jsonString)
498 {
499 *jsonString = '{' + *jsonString + '}';
500 auto json = nlohmann::json::parse(*jsonString);
501 }
502 }
503}
Matt Spinler5b289b22020-03-26 14:27:19 -0500504
505PelFFDCfile getJSONFFDC(const fs::path& dir)
506{
507 PelFFDCfile ffdc;
508 ffdc.format = UserDataFormat::json;
509 ffdc.subType = 5;
510 ffdc.version = 42;
511
512 auto inputJSON = R"({
513 "key1": "value1",
514 "key2": 42,
515 "key3" : [1, 2, 3, 4, 5],
516 "key4": {"key5": "value5"}
517 })"_json;
518
519 // Write the JSON to a file and get its descriptor.
520 auto s = inputJSON.dump();
521 std::vector<uint8_t> data{s.begin(), s.end()};
522 ffdc.fd = writeFileAndGetFD(dir, data);
523
524 return ffdc;
525}
526
527TEST_F(PELTest, MakeJSONFileUDSectionTest)
528{
529 auto dir = makeTempDir();
530
531 {
532 auto ffdc = getJSONFFDC(dir);
533
534 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
535 close(ffdc.fd);
536 ASSERT_TRUE(ud);
537 ASSERT_TRUE(ud->valid());
538 EXPECT_EQ(ud->header().id, 0x5544);
539
540 EXPECT_EQ(ud->header().version,
541 static_cast<uint8_t>(UserDataFormatVersion::json));
542 EXPECT_EQ(ud->header().subType,
543 static_cast<uint8_t>(UserDataFormat::json));
544 EXPECT_EQ(ud->header().componentID,
545 static_cast<uint16_t>(ComponentID::phosphorLogging));
546
547 // Pull the JSON back out of the the UserData section
548 const auto& d = ud->data();
549 std::string js{d.begin(), d.end()};
550 auto json = nlohmann::json::parse(js);
551
552 EXPECT_EQ("value1", json["key1"].get<std::string>());
553 EXPECT_EQ(42, json["key2"].get<int>());
554
555 std::vector<int> key3Values{1, 2, 3, 4, 5};
556 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>());
557
558 std::map<std::string, std::string> key4Values{{"key5", "value5"}};
559 auto actual = json["key4"].get<std::map<std::string, std::string>>();
560 EXPECT_EQ(key4Values, actual);
561 }
562
563 {
564 // A bad FD
565 PelFFDCfile ffdc;
566 ffdc.format = UserDataFormat::json;
567 ffdc.subType = 5;
568 ffdc.version = 42;
569 ffdc.fd = 10000;
570
571 // The section shouldn't get made
572 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
573 ASSERT_FALSE(ud);
574 }
575
576 fs::remove_all(dir);
577}
578
579PelFFDCfile getCBORFFDC(const fs::path& dir)
580{
581 PelFFDCfile ffdc;
582 ffdc.format = UserDataFormat::cbor;
583 ffdc.subType = 5;
584 ffdc.version = 42;
585
586 auto inputJSON = R"({
587 "key1": "value1",
588 "key2": 42,
589 "key3" : [1, 2, 3, 4, 5],
590 "key4": {"key5": "value5"}
591 })"_json;
592
593 // Convert the JSON to CBOR and write it to a file
594 auto data = nlohmann::json::to_cbor(inputJSON);
595 ffdc.fd = writeFileAndGetFD(dir, data);
596
597 return ffdc;
598}
599
600TEST_F(PELTest, MakeCBORFileUDSectionTest)
601{
602 auto dir = makeTempDir();
603
604 auto ffdc = getCBORFFDC(dir);
605 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
606 close(ffdc.fd);
607 ASSERT_TRUE(ud);
608 ASSERT_TRUE(ud->valid());
609 EXPECT_EQ(ud->header().id, 0x5544);
610
611 EXPECT_EQ(ud->header().version,
612 static_cast<uint8_t>(UserDataFormatVersion::cbor));
613 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::cbor));
614 EXPECT_EQ(ud->header().componentID,
615 static_cast<uint16_t>(ComponentID::phosphorLogging));
616
617 // Pull the CBOR back out of the PEL section
618 // The number of pad bytes to make the section be 4B aligned
619 // was added at the end, read it and then remove it and the
620 // padding before parsing it.
621 auto data = ud->data();
622 Stream stream{data};
623 stream.offset(data.size() - 4);
624 uint32_t pad;
625 stream >> pad;
626
627 data.resize(data.size() - 4 - pad);
628
629 auto json = nlohmann::json::from_cbor(data);
630
631 EXPECT_EQ("value1", json["key1"].get<std::string>());
632 EXPECT_EQ(42, json["key2"].get<int>());
633
634 std::vector<int> key3Values{1, 2, 3, 4, 5};
635 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>());
636
637 std::map<std::string, std::string> key4Values{{"key5", "value5"}};
638 auto actual = json["key4"].get<std::map<std::string, std::string>>();
639 EXPECT_EQ(key4Values, actual);
640
641 fs::remove_all(dir);
642}
643
644PelFFDCfile getTextFFDC(const fs::path& dir)
645{
646 PelFFDCfile ffdc;
647 ffdc.format = UserDataFormat::text;
648 ffdc.subType = 5;
649 ffdc.version = 42;
650
651 std::string text{"this is some text that will be used for FFDC"};
652 std::vector<uint8_t> data{text.begin(), text.end()};
653
654 ffdc.fd = writeFileAndGetFD(dir, data);
655
656 return ffdc;
657}
658
659TEST_F(PELTest, MakeTextFileUDSectionTest)
660{
661 auto dir = makeTempDir();
662
663 auto ffdc = getTextFFDC(dir);
664 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
665 close(ffdc.fd);
666 ASSERT_TRUE(ud);
667 ASSERT_TRUE(ud->valid());
668 EXPECT_EQ(ud->header().id, 0x5544);
669
670 EXPECT_EQ(ud->header().version,
671 static_cast<uint8_t>(UserDataFormatVersion::text));
672 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::text));
673 EXPECT_EQ(ud->header().componentID,
674 static_cast<uint16_t>(ComponentID::phosphorLogging));
675
676 // Get the text back out
677 std::string text{ud->data().begin(), ud->data().end()};
678 EXPECT_EQ(text, "this is some text that will be used for FFDC");
679
680 fs::remove_all(dir);
681}
682
683PelFFDCfile getCustomFFDC(const fs::path& dir, const std::vector<uint8_t>& data)
684{
685 PelFFDCfile ffdc;
686 ffdc.format = UserDataFormat::custom;
687 ffdc.subType = 5;
688 ffdc.version = 42;
689
690 ffdc.fd = writeFileAndGetFD(dir, data);
691
692 return ffdc;
693}
694
695TEST_F(PELTest, MakeCustomFileUDSectionTest)
696{
697 auto dir = makeTempDir();
698
699 {
700 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8};
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, 8 + 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 EXPECT_EQ(data, newData);
717 }
718
719 // Do the same thing again, but make it be non 4B aligned
720 // so the data gets padded.
721 {
722 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8, 9};
723
724 auto ffdc = getCustomFFDC(dir, data);
725 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
726 close(ffdc.fd);
727 ASSERT_TRUE(ud);
728 ASSERT_TRUE(ud->valid());
729 EXPECT_EQ(ud->header().size, 12 + 8); // data size + header size
730 EXPECT_EQ(ud->header().id, 0x5544);
731
732 EXPECT_EQ(ud->header().version, 42);
733 EXPECT_EQ(ud->header().subType, 5);
734 EXPECT_EQ(ud->header().componentID, 0x2002);
735
736 // Get the data back out
737 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()};
738
739 // pad the original to 12B so we can compare
740 data.push_back(0);
741 data.push_back(0);
742 data.push_back(0);
743
744 EXPECT_EQ(data, newData);
745 }
746
747 fs::remove_all(dir);
748}
749
750// Test Adding FFDC from files to a PEL
751TEST_F(PELTest, CreateWithFFDCTest)
752{
753 auto dir = makeTempDir();
754 message::Entry regEntry;
755 uint64_t timestamp = 5;
756
757 regEntry.name = "test";
758 regEntry.subsystem = 5;
759 regEntry.actionFlags = 0xC000;
760 regEntry.src.type = 0xBD;
761 regEntry.src.reasonCode = 0x1234;
762
763 std::vector<std::string> additionalData{"KEY1=VALUE1"};
764 AdditionalData ad{additionalData};
765 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600766 NiceMock<MockJournal> journal;
Matt Spinler5b289b22020-03-26 14:27:19 -0500767 PelFFDC ffdc;
768
769 std::vector<uint8_t> customData{1, 2, 3, 4, 5, 6, 7, 8};
770
771 // This will be trimmed when added
772 std::vector<uint8_t> hugeCustomData(17000, 0x42);
773
774 ffdc.emplace_back(std::move(getJSONFFDC(dir)));
775 ffdc.emplace_back(std::move(getCBORFFDC(dir)));
776 ffdc.emplace_back(std::move(getTextFFDC(dir)));
777 ffdc.emplace_back(std::move(getCustomFFDC(dir, customData)));
778 ffdc.emplace_back(std::move(getCustomFFDC(dir, hugeCustomData)));
779
Sumit Kumar9d43a722021-08-24 09:46:19 -0500780 std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
781 "system/entry"};
782 EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
783 .WillOnce(Return(std::vector<bool>{false, false, false}));
784
Matt Spinler5b289b22020-03-26 14:27:19 -0500785 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
Matt Spinler9d921092022-12-15 11:54:49 -0600786 ad, ffdc, dataIface, journal};
Matt Spinler5b289b22020-03-26 14:27:19 -0500787
788 EXPECT_TRUE(pel.valid());
789
790 // Clipped to the max
791 EXPECT_EQ(pel.size(), 16384);
792
793 // Check for the FFDC sections
794 size_t udCount = 0;
795 Section* ud = nullptr;
796
797 for (const auto& section : pel.optionalSections())
798 {
799 if (section->header().id == static_cast<uint16_t>(SectionID::userData))
800 {
801 udCount++;
802 ud = section.get();
803 }
804 }
805
806 EXPECT_EQ(udCount, 7); // AD section, sysInfo, 5 ffdc sections
807
808 // Check the last section was trimmed to
809 // something a bit less that 17000.
810 EXPECT_GT(ud->header().size, 14000);
811 EXPECT_LT(ud->header().size, 16000);
812
813 fs::remove_all(dir);
814}
Matt Spinler0a90a852020-06-04 13:18:27 -0500815
816// Create a PEL with device callouts
817TEST_F(PELTest, CreateWithDevCalloutsTest)
818{
819 message::Entry regEntry;
820 uint64_t timestamp = 5;
821
822 regEntry.name = "test";
823 regEntry.subsystem = 5;
824 regEntry.actionFlags = 0xC000;
825 regEntry.src.type = 0xBD;
826 regEntry.src.reasonCode = 0x1234;
827
828 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -0600829 NiceMock<MockJournal> journal;
Matt Spinler0a90a852020-06-04 13:18:27 -0500830 PelFFDC ffdc;
831
832 const auto calloutJSON = R"(
833 {
834 "I2C":
835 {
836 "14":
837 {
838 "114":
839 {
840 "Callouts":[
841 {
842 "Name": "/chassis/motherboard/cpu0",
843 "LocationCode": "P1",
844 "Priority": "H"
845 }
846 ],
847 "Dest": "proc 0 target"
848 }
849 }
850 }
851 })";
852
853 std::vector<std::string> names{"systemA"};
854 EXPECT_CALL(dataIface, getSystemNames)
855 .Times(2)
Matt Spinler1ab66962020-10-29 13:21:44 -0500856 .WillRepeatedly(Return(names));
Matt Spinler0a90a852020-06-04 13:18:27 -0500857
Matt Spinler0d92b522021-06-16 13:28:17 -0600858 EXPECT_CALL(dataIface, expandLocationCode("P1", 0))
859 .Times(1)
Matt Spinler0a90a852020-06-04 13:18:27 -0500860 .WillOnce(Return("UXXX-P1"));
861
Matt Spinler2f9225a2020-08-05 12:58:49 -0500862 EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false))
Matt Spinlerbad056b2023-01-25 14:16:57 -0600863 .WillOnce(Return(std::vector<std::string>{
864 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"}));
Matt Spinler0a90a852020-06-04 13:18:27 -0500865
866 EXPECT_CALL(
867 dataIface,
868 getHWCalloutFields(
869 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _, _))
870 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
871 SetArgReferee<3>("123456789ABC")));
872
Sumit Kumar9d43a722021-08-24 09:46:19 -0500873 std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
874 "system/entry"};
875 EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
876 .WillRepeatedly(Return(std::vector<bool>{false, false, false}));
877
Matt Spinler0a90a852020-06-04 13:18:27 -0500878 auto dataPath = getPELReadOnlyDataPath();
879 std::ofstream file{dataPath / "systemA_dev_callouts.json"};
880 file << calloutJSON;
881 file.close();
882
883 {
884 std::vector<std::string> data{
885 "CALLOUT_ERRNO=5",
886 "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
887 "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"};
888
889 AdditionalData ad{data};
890
891 PEL pel{
892 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
Matt Spinler9d921092022-12-15 11:54:49 -0600893 ad, ffdc, dataIface, journal};
Matt Spinler0a90a852020-06-04 13:18:27 -0500894
895 ASSERT_TRUE(pel.primarySRC().value()->callouts());
896 auto& callouts = pel.primarySRC().value()->callouts()->callouts();
897 ASSERT_EQ(callouts.size(), 1);
Andrew Geisslerf8e750d2022-01-14 14:56:13 -0600898 ASSERT_TRUE(pel.isHwCalloutPresent());
Matt Spinler0a90a852020-06-04 13:18:27 -0500899
900 EXPECT_EQ(callouts[0]->priority(), 'H');
901 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P1");
902
903 auto& fru = callouts[0]->fruIdentity();
904 EXPECT_EQ(fru->getPN().value(), "1234567");
905 EXPECT_EQ(fru->getCCIN().value(), "CCCC");
906 EXPECT_EQ(fru->getSN().value(), "123456789ABC");
907
908 const auto& section = pel.optionalSections().back();
909
910 ASSERT_EQ(section->header().id, 0x5544); // UD
911 auto ud = static_cast<UserData*>(section.get());
912
913 // Check that there was a UserData section added that
914 // contains debug details about the device.
915 const auto& d = ud->data();
916 std::string jsonString{d.begin(), d.end()};
917 auto actualJSON = nlohmann::json::parse(jsonString);
918
919 auto expectedJSON = R"(
920 {
921 "PEL Internal Debug Data": {
922 "SRC": [
923 "I2C: bus: 14 address: 114 dest: proc 0 target"
924 ]
925 }
926 }
927 )"_json;
928
929 EXPECT_EQ(actualJSON, expectedJSON);
930 }
931
932 {
933 // Device path not found (wrong i2c addr), so no callouts
934 std::vector<std::string> data{
935 "CALLOUT_ERRNO=5",
936 "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
937 "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0099"};
938
939 AdditionalData ad{data};
940
941 PEL pel{
942 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
Matt Spinler9d921092022-12-15 11:54:49 -0600943 ad, ffdc, dataIface, journal};
Matt Spinler0a90a852020-06-04 13:18:27 -0500944
945 // no callouts
946 EXPECT_FALSE(pel.primarySRC().value()->callouts());
947
948 // Now check that there was a UserData section
949 // that contains the lookup error.
950 const auto& section = pel.optionalSections().back();
951
952 ASSERT_EQ(section->header().id, 0x5544); // UD
953 auto ud = static_cast<UserData*>(section.get());
954
955 const auto& d = ud->data();
956
957 std::string jsonString{d.begin(), d.end()};
958
959 auto actualJSON = nlohmann::json::parse(jsonString);
960
961 auto expectedJSON =
962 "{\"PEL Internal Debug Data\":{\"SRC\":"
963 "[\"Problem looking up I2C callouts on 14 153: "
964 "[json.exception.out_of_range.403] key '153' not found\"]}}"_json;
965
966 EXPECT_EQ(actualJSON, expectedJSON);
967 }
968
969 fs::remove_all(dataPath);
970}
Matt Spinlere513dbc2020-08-27 11:14:17 -0500971
972// Test PELs when the callouts are passed in using a JSON file.
973TEST_F(PELTest, CreateWithJSONCalloutsTest)
974{
975 PelFFDCfile ffdcFile;
976 ffdcFile.format = UserDataFormat::json;
977 ffdcFile.subType = 0xCA; // Callout JSON
978 ffdcFile.version = 1;
979
980 // Write these callouts to a JSON file and pass it into
981 // the PEL as an FFDC file.
982 auto inputJSON = R"([
983 {
984 "Priority": "H",
985 "LocationCode": "P0-C1"
986 },
987 {
988 "Priority": "M",
989 "Procedure": "PROCEDURE"
990 }
991 ])"_json;
992
993 auto s = inputJSON.dump();
994 std::vector<uint8_t> data{s.begin(), s.end()};
995 auto dir = makeTempDir();
996 ffdcFile.fd = writeFileAndGetFD(dir, data);
997
998 PelFFDC ffdc;
999 ffdc.push_back(std::move(ffdcFile));
1000
1001 AdditionalData ad;
1002 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -06001003 NiceMock<MockJournal> journal;
Matt Spinlere513dbc2020-08-27 11:14:17 -05001004
1005 EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
1006 .Times(1)
1007 .WillOnce(Return("UXXX-P0-C1"));
1008 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
1009 .Times(1)
Matt Spinlerbad056b2023-01-25 14:16:57 -06001010 .WillOnce(Return(
1011 std::vector<std::string>{"/inv/system/chassis/motherboard/bmc"}));
Matt Spinlere513dbc2020-08-27 11:14:17 -05001012 EXPECT_CALL(dataIface, getHWCalloutFields(
1013 "/inv/system/chassis/motherboard/bmc", _, _, _))
1014 .Times(1)
1015 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
1016 SetArgReferee<3>("123456789ABC")));
1017
Sumit Kumar9d43a722021-08-24 09:46:19 -05001018 std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1019 "system/entry"};
1020 EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1021 .WillOnce(Return(std::vector<bool>{false, false, false}));
1022
Matt Spinlere513dbc2020-08-27 11:14:17 -05001023 message::Entry regEntry;
1024 regEntry.name = "test";
1025 regEntry.subsystem = 5;
1026 regEntry.actionFlags = 0xC000;
1027 regEntry.src.type = 0xBD;
1028 regEntry.src.reasonCode = 0x1234;
1029
Matt Spinler9d921092022-12-15 11:54:49 -06001030 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error,
1031 ad, ffdc, dataIface, journal};
Matt Spinlere513dbc2020-08-27 11:14:17 -05001032
1033 ASSERT_TRUE(pel.valid());
1034 ASSERT_TRUE(pel.primarySRC().value()->callouts());
1035 const auto& callouts = pel.primarySRC().value()->callouts()->callouts();
1036 ASSERT_EQ(callouts.size(), 2);
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001037 ASSERT_TRUE(pel.isHwCalloutPresent());
Matt Spinlere513dbc2020-08-27 11:14:17 -05001038
1039 {
1040 EXPECT_EQ(callouts[0]->priority(), 'H');
1041 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1042
1043 auto& fru = callouts[0]->fruIdentity();
1044 EXPECT_EQ(fru->getPN().value(), "1234567");
1045 EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1046 EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1047 EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1048 }
1049 {
1050 EXPECT_EQ(callouts[1]->priority(), 'M');
1051 EXPECT_EQ(callouts[1]->locationCode(), "");
1052
1053 auto& fru = callouts[1]->fruIdentity();
1054 EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1055 EXPECT_EQ(fru->failingComponentType(),
1056 src::FRUIdentity::maintenanceProc);
1057 }
1058 fs::remove_all(dir);
1059}
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001060
1061// Test PELs with symblic FRU callout.
1062TEST_F(PELTest, CreateWithJSONSymblicCalloutTest)
1063{
1064 PelFFDCfile ffdcFile;
1065 ffdcFile.format = UserDataFormat::json;
1066 ffdcFile.subType = 0xCA; // Callout JSON
1067 ffdcFile.version = 1;
1068
1069 // Write these callouts to a JSON file and pass it into
1070 // the PEL as an FFDC file.
1071 auto inputJSON = R"([
1072 {
1073 "Priority": "M",
1074 "Procedure": "SVCDOCS"
1075 }
1076 ])"_json;
1077
1078 auto s = inputJSON.dump();
1079 std::vector<uint8_t> data{s.begin(), s.end()};
1080 auto dir = makeTempDir();
1081 ffdcFile.fd = writeFileAndGetFD(dir, data);
1082
1083 PelFFDC ffdc;
1084 ffdc.push_back(std::move(ffdcFile));
1085
1086 AdditionalData ad;
1087 NiceMock<MockDataInterface> dataIface;
Matt Spinler9d921092022-12-15 11:54:49 -06001088 NiceMock<MockJournal> journal;
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001089
1090 std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1091 "system/entry"};
1092 EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1093 .WillRepeatedly(Return(std::vector<bool>{false, false, false}));
1094
1095 message::Entry regEntry;
1096 regEntry.name = "test";
1097 regEntry.subsystem = 5;
1098 regEntry.actionFlags = 0xC000;
1099 regEntry.src.type = 0xBD;
1100 regEntry.src.reasonCode = 0x1234;
1101
Matt Spinler9d921092022-12-15 11:54:49 -06001102 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error,
1103 ad, ffdc, dataIface, journal};
Andrew Geisslerf8e750d2022-01-14 14:56:13 -06001104
1105 ASSERT_TRUE(pel.valid());
1106 ASSERT_TRUE(pel.primarySRC().value()->callouts());
1107 const auto& callouts = pel.primarySRC().value()->callouts()->callouts();
1108 ASSERT_EQ(callouts.size(), 1);
1109 ASSERT_FALSE(pel.isHwCalloutPresent());
1110
1111 {
1112 EXPECT_EQ(callouts[0]->priority(), 'M');
1113 EXPECT_EQ(callouts[0]->locationCode(), "");
1114
1115 auto& fru = callouts[0]->fruIdentity();
1116 EXPECT_EQ(fru->getMaintProc().value(), "SVCDOCS");
1117 }
1118 fs::remove_all(dir);
1119}
Matt Spinler9d921092022-12-15 11:54:49 -06001120
1121TEST_F(PELTest, FlattenLinesTest)
1122{
1123 std::vector<std::string> msgs{"test1 test2", "test3 test4", "test5 test6"};
1124
1125 auto buffer = util::flattenLines(msgs);
1126
1127 std::string string{"test1 test2\ntest3 test4\ntest5 test6\n"};
1128 std::vector<uint8_t> expected(string.begin(), string.end());
1129
1130 EXPECT_EQ(buffer, expected);
1131}
1132
1133void checkJournalSection(const std::unique_ptr<Section>& section,
1134 const std::string& expected)
1135{
1136 ASSERT_EQ(SectionID::userData,
1137 static_cast<SectionID>(section->header().id));
1138 ASSERT_EQ(UserDataFormat::text,
1139 static_cast<UserDataFormat>(section->header().subType));
1140 ASSERT_EQ(section->header().version,
1141 static_cast<uint8_t>(UserDataFormatVersion::text));
1142
1143 auto ud = static_cast<UserData*>(section.get());
1144
1145 std::vector<uint8_t> expectedData(expected.begin(), expected.end());
1146
1147 // PEL sections are 4B aligned so add padding before the compare
1148 while (expectedData.size() % 4 != 0)
1149 {
1150 expectedData.push_back('\0');
1151 }
1152
1153 EXPECT_EQ(ud->data(), expectedData);
1154}
1155
1156TEST_F(PELTest, CaptureJournalTest)
1157{
1158 message::Entry regEntry;
1159 uint64_t timestamp = 5;
1160
1161 regEntry.name = "test";
1162 regEntry.subsystem = 5;
1163 regEntry.actionFlags = 0xC000;
1164 regEntry.src.type = 0xBD;
1165 regEntry.src.reasonCode = 0x1234;
1166
1167 std::vector<std::string> data;
1168 AdditionalData ad{data};
1169 NiceMock<MockDataInterface> dataIface;
1170 NiceMock<MockJournal> journal;
1171 PelFFDC ffdc;
1172
1173 std::vector<std::string> dumpType{"bmc/entry", "resource/entry",
1174 "system/entry"};
1175 EXPECT_CALL(dataIface, checkDumpStatus(dumpType))
1176 .WillRepeatedly(Return(std::vector<bool>{false, false, false}));
1177
1178 size_t pelSectsWithOneUD{0};
1179
1180 {
1181 // Capture 5 lines from the journal into a single UD section
1182 message::JournalCapture jc = size_t{5};
1183 regEntry.journalCapture = jc;
1184
1185 std::vector<std::string> msgs{"test1 test2", "test3 test4",
1186 "test5 test6", "4", "5"};
1187
1188 EXPECT_CALL(journal, getMessages("", 5)).WillOnce(Return(msgs));
1189
1190 PEL pel{
1191 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
1192 ad, ffdc, dataIface, journal};
1193
1194 // Check the generated UserData section
1195 std::string expected{"test1 test2\ntest3 test4\ntest5 test6\n4\n5\n"};
1196
1197 checkJournalSection(pel.optionalSections().back(), expected);
1198
1199 // Save for upcoming testcases
1200 pelSectsWithOneUD = pel.privateHeader().sectionCount();
1201 }
1202
1203 {
1204 // Attempt to capture too many journal entries so the
1205 // section gets dropped.
1206 message::JournalCapture jc = size_t{1};
1207 regEntry.journalCapture = jc;
1208
1209 EXPECT_CALL(journal, sync()).Times(1);
1210
1211 // A 20000 byte line won't fit in a PEL
1212 EXPECT_CALL(journal, getMessages("", 1))
1213 .WillOnce(
1214 Return(std::vector<std::string>{std::string(20000, 'x')}));
1215
1216 PEL pel{
1217 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
1218 ad, ffdc, dataIface, journal};
1219
1220 // Check for 1 fewer sections than in the previous PEL
1221 EXPECT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD - 1);
1222 }
1223
1224 // Capture 3 different journal sections
1225 {
1226 message::AppCaptureList captureList{
1227 message::AppCapture{"app1", 3},
1228 message::AppCapture{"app2", 4},
1229 message::AppCapture{"app3", 1},
1230 };
1231 message::JournalCapture jc = captureList;
1232 regEntry.journalCapture = jc;
1233
1234 std::vector<std::string> app1{"A B", "C D", "E F"};
1235 std::vector<std::string> app2{"1 2", "3 4", "5 6", "7 8"};
1236 std::vector<std::string> app3{"a b c"};
1237
1238 std::string expected1{"A B\nC D\nE F\n"};
1239 std::string expected2{"1 2\n3 4\n5 6\n7 8\n"};
1240 std::string expected3{"a b c\n"};
1241
1242 EXPECT_CALL(journal, sync()).Times(1);
1243 EXPECT_CALL(journal, getMessages("app1", 3)).WillOnce(Return(app1));
1244 EXPECT_CALL(journal, getMessages("app2", 4)).WillOnce(Return(app2));
1245 EXPECT_CALL(journal, getMessages("app3", 1)).WillOnce(Return(app3));
1246
1247 PEL pel{
1248 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
1249 ad, ffdc, dataIface, journal};
1250
1251 // Two more sections than the 1 extra UD section in the first testcase
1252 ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD + 2);
1253
1254 const auto& optionalSections = pel.optionalSections();
1255 auto numOptSections = optionalSections.size();
1256
1257 checkJournalSection(optionalSections[numOptSections - 3], expected1);
1258 checkJournalSection(optionalSections[numOptSections - 2], expected2);
1259 checkJournalSection(optionalSections[numOptSections - 1], expected3);
1260 }
1261
1262 {
1263 // One section gets saved, and one is too big and gets dropped
1264 message::AppCaptureList captureList{
1265 message::AppCapture{"app4", 2},
1266 message::AppCapture{"app5", 1},
1267 };
1268 message::JournalCapture jc = captureList;
1269 regEntry.journalCapture = jc;
1270
1271 std::vector<std::string> app4{"w x", "y z"};
1272 std::string expected4{"w x\ny z\n"};
1273
1274 EXPECT_CALL(journal, sync()).Times(1);
1275
1276 EXPECT_CALL(journal, getMessages("app4", 2)).WillOnce(Return(app4));
1277
1278 // A 20000 byte line won't fit in a PEL
1279 EXPECT_CALL(journal, getMessages("app5", 1))
1280 .WillOnce(
1281 Return(std::vector<std::string>{std::string(20000, 'x')}));
1282
1283 PEL pel{
1284 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
1285 ad, ffdc, dataIface, journal};
1286
1287 // The last section should have been dropped, so same as first TC
1288 ASSERT_EQ(pel.privateHeader().sectionCount(), pelSectsWithOneUD);
1289
1290 checkJournalSection(pel.optionalSections().back(), expected4);
1291 }
1292}