blob: 49b8e96e0e4bebd85498a37c61ffa3dfb83ad988 [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::_;
Matt Spinler56ad2a02020-03-26 14:00:52 -050030using ::testing::NiceMock;
Matt Spinler677381b2020-01-23 10:04:29 -060031using ::testing::Return;
Matt Spinler0a90a852020-06-04 13:18:27 -050032using ::testing::SetArgReferee;
Matt Spinlercb6b0592019-07-16 15:58:51 -050033
34class PELTest : public CleanLogID
35{
36};
37
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;
167 PelFFDC ffdc;
Matt Spinlerbd716f02019-10-15 10:54:11 -0500168
Matt Spinler56ad2a02020-03-26 14:00:52 -0500169 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
170 ad, ffdc, dataIface};
Matt Spinlerb8323632019-09-20 15:11:04 -0500171
172 EXPECT_TRUE(pel.valid());
Matt Spinler97d19b42019-10-29 11:34:03 -0500173 EXPECT_EQ(pel.privateHeader().obmcLogID(), 42);
174 EXPECT_EQ(pel.userHeader().severity(), 0x40);
Matt Spinlerb8323632019-09-20 15:11:04 -0500175
Matt Spinlerbd716f02019-10-15 10:54:11 -0500176 EXPECT_EQ(pel.primarySRC().value()->asciiString(),
177 "BD051234 ");
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600178
179 // Check that certain optional sections have been created
180 size_t mtmsCount = 0;
181 size_t euhCount = 0;
182 size_t udCount = 0;
183
184 for (const auto& section : pel.optionalSections())
185 {
186 if (section->header().id ==
187 static_cast<uint16_t>(SectionID::failingMTMS))
188 {
189 mtmsCount++;
190 }
191 else if (section->header().id ==
192 static_cast<uint16_t>(SectionID::extendedUserHeader))
193 {
194 euhCount++;
195 }
196 else if (section->header().id ==
197 static_cast<uint16_t>(SectionID::userData))
198 {
199 udCount++;
200 }
201 }
202
203 EXPECT_EQ(mtmsCount, 1);
204 EXPECT_EQ(euhCount, 1);
205 EXPECT_EQ(udCount, 2); // AD section and sysInfo section
Andrew Geissler44fc3162020-07-09 09:21:31 -0500206 ASSERT_FALSE(pel.isCalloutPresent());
Matt Spinler1f93c592020-09-10 10:43:08 -0500207
208 {
209 // The same thing, but without the action flags specified
210 // in the registry, so the constructor should set them.
211 regEntry.actionFlags = std::nullopt;
212
213 PEL pel2{
214 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
215 ad, ffdc, dataIface};
216
217 EXPECT_EQ(pel2.userHeader().actionFlags(), 0xA800);
218 }
Matt Spinlerb8323632019-09-20 15:11:04 -0500219}
Matt Spinler131870c2019-09-25 13:29:04 -0500220
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500221// Test that when the AdditionalData size is over 16KB that
222// the PEL that's created is exactly 16KB since the UserData
223// section that contains all that data was pruned.
224TEST_F(PELTest, CreateTooBigADTest)
225{
226 message::Entry regEntry;
227 uint64_t timestamp = 5;
228
229 regEntry.name = "test";
230 regEntry.subsystem = 5;
231 regEntry.actionFlags = 0xC000;
232 regEntry.src.type = 0xBD;
233 regEntry.src.reasonCode = 0x1234;
Matt Spinler56ad2a02020-03-26 14:00:52 -0500234 PelFFDC ffdc;
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500235
236 // Over the 16KB max PEL size
237 std::string bigAD{"KEY1="};
238 bigAD += std::string(17000, 'G');
239
240 std::vector<std::string> data{bigAD};
241 AdditionalData ad{data};
Matt Spinler56ad2a02020-03-26 14:00:52 -0500242 NiceMock<MockDataInterface> dataIface;
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500243
Matt Spinler56ad2a02020-03-26 14:00:52 -0500244 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
245 ad, ffdc, dataIface};
Matt Spinler9b7e94f2020-03-24 15:44:41 -0500246
247 EXPECT_TRUE(pel.valid());
248 EXPECT_EQ(pel.size(), 16384);
249
250 // Make sure that there are still 2 UD sections.
251 size_t udCount = 0;
252 for (const auto& section : pel.optionalSections())
253 {
254 if (section->header().id == static_cast<uint16_t>(SectionID::userData))
255 {
256 udCount++;
257 }
258 }
259
260 EXPECT_EQ(udCount, 2); // AD section and sysInfo section
261}
262
Matt Spinler131870c2019-09-25 13:29:04 -0500263// Test that we'll create Generic optional sections for sections that
264// there aren't explicit classes for.
265TEST_F(PELTest, GenericSectionTest)
266{
Matt Spinler42828bd2019-10-11 10:39:30 -0500267 auto data = pelDataFactory(TestPELType::pelSimple);
Matt Spinler131870c2019-09-25 13:29:04 -0500268
269 std::vector<uint8_t> section1{0x58, 0x58, // ID 'XX'
270 0x00, 0x18, // Size
271 0x01, 0x02, // version, subtype
272 0x03, 0x04, // comp ID
273
274 // some data
275 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63,
276 0x20, 0x31, 0x06, 0x0F, 0x09, 0x22, 0x3A,
277 0x00};
278
279 std::vector<uint8_t> section2{
280 0x59, 0x59, // ID 'YY'
281 0x00, 0x20, // Size
282 0x01, 0x02, // version, subtype
283 0x03, 0x04, // comp ID
284
285 // some data
286 0x20, 0x30, 0x05, 0x09, 0x11, 0x1E, 0x1, 0x63, 0x20, 0x31, 0x06, 0x0F,
287 0x09, 0x22, 0x3A, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08};
288
289 // Add the new sections at the end
Matt Spinler42828bd2019-10-11 10:39:30 -0500290 data.insert(data.end(), section1.begin(), section1.end());
291 data.insert(data.end(), section2.begin(), section2.end());
Matt Spinler131870c2019-09-25 13:29:04 -0500292
293 // Increment the section count
Matt Spinler42828bd2019-10-11 10:39:30 -0500294 data.at(27) += 2;
295 auto origData = data;
Matt Spinler131870c2019-09-25 13:29:04 -0500296
Matt Spinler42828bd2019-10-11 10:39:30 -0500297 PEL pel{data};
Matt Spinler131870c2019-09-25 13:29:04 -0500298
299 const auto& sections = pel.optionalSections();
300
301 bool foundXX = false;
302 bool foundYY = false;
303
304 // Check that we can find these 2 Generic sections
305 for (const auto& section : sections)
306 {
307 if (section->header().id == 0x5858)
308 {
309 foundXX = true;
310 EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
311 }
312 else if (section->header().id == 0x5959)
313 {
314 foundYY = true;
315 EXPECT_NE(dynamic_cast<Generic*>(section.get()), nullptr);
316 }
317 }
318
319 EXPECT_TRUE(foundXX);
320 EXPECT_TRUE(foundYY);
Matt Spinler07eefc52019-09-26 11:18:26 -0500321
322 // Now flatten and check
323 auto newData = pel.data();
324
325 EXPECT_EQ(origData, newData);
Matt Spinler131870c2019-09-25 13:29:04 -0500326}
327
328// Test that an invalid section will still get a Generic object
329TEST_F(PELTest, InvalidGenericTest)
330{
Matt Spinler42828bd2019-10-11 10:39:30 -0500331 auto data = pelDataFactory(TestPELType::pelSimple);
Matt Spinler131870c2019-09-25 13:29:04 -0500332
333 // Not a valid section
334 std::vector<uint8_t> section1{0x01, 0x02, 0x03};
335
Matt Spinler42828bd2019-10-11 10:39:30 -0500336 data.insert(data.end(), section1.begin(), section1.end());
Matt Spinler131870c2019-09-25 13:29:04 -0500337
338 // Increment the section count
Matt Spinler42828bd2019-10-11 10:39:30 -0500339 data.at(27) += 1;
Matt Spinler131870c2019-09-25 13:29:04 -0500340
Matt Spinler42828bd2019-10-11 10:39:30 -0500341 PEL pel{data};
Matt Spinler131870c2019-09-25 13:29:04 -0500342 EXPECT_FALSE(pel.valid());
343
344 const auto& sections = pel.optionalSections();
345
346 bool foundGeneric = false;
347 for (const auto& section : sections)
348 {
349 if (dynamic_cast<Generic*>(section.get()) != nullptr)
350 {
351 foundGeneric = true;
352 EXPECT_EQ(section->valid(), false);
353 break;
354 }
355 }
356
357 EXPECT_TRUE(foundGeneric);
358}
Matt Spinlerafa857c2019-10-24 13:03:46 -0500359
360// Create a UserData section out of AdditionalData
361TEST_F(PELTest, MakeUDSectionTest)
362{
363 std::vector<std::string> ad{"KEY1=VALUE1", "KEY2=VALUE2", "KEY3=VALUE3",
364 "ESEL=TEST"};
365 AdditionalData additionalData{ad};
366
367 auto ud = util::makeADUserDataSection(additionalData);
368
369 EXPECT_TRUE(ud->valid());
370 EXPECT_EQ(ud->header().id, 0x5544);
371 EXPECT_EQ(ud->header().version, 0x01);
372 EXPECT_EQ(ud->header().subType, 0x01);
373 EXPECT_EQ(ud->header().componentID, 0x2000);
374
375 const auto& d = ud->data();
376
377 std::string jsonString{d.begin(), d.end()};
Matt Spinler53407be2019-11-18 09:16:31 -0600378
379 std::string expectedJSON =
Matt Spinlerafa857c2019-10-24 13:03:46 -0500380 R"({"KEY1":"VALUE1","KEY2":"VALUE2","KEY3":"VALUE3"})";
Matt Spinler53407be2019-11-18 09:16:31 -0600381
382 // The actual data is null padded to a 4B boundary.
383 std::vector<uint8_t> expectedData;
384 expectedData.resize(52, '\0');
385 memcpy(expectedData.data(), expectedJSON.data(), expectedJSON.size());
386
387 EXPECT_EQ(d, expectedData);
Matt Spinlerafa857c2019-10-24 13:03:46 -0500388
389 // Ensure we can read this as JSON
390 auto newJSON = nlohmann::json::parse(jsonString);
391 EXPECT_EQ(newJSON["KEY1"], "VALUE1");
392 EXPECT_EQ(newJSON["KEY2"], "VALUE2");
393 EXPECT_EQ(newJSON["KEY3"], "VALUE3");
Matt Spinler97d19b42019-10-29 11:34:03 -0500394}
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600395
396// Create the UserData section that contains system info
Matt Spinler677381b2020-01-23 10:04:29 -0600397TEST_F(PELTest, SysInfoSectionTest)
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600398{
399 MockDataInterface dataIface;
400
Matt Spinler677381b2020-01-23 10:04:29 -0600401 EXPECT_CALL(dataIface, getBMCFWVersionID()).WillOnce(Return("ABCD1234"));
Matt Spinler4aa23a12020-02-03 15:05:09 -0600402 EXPECT_CALL(dataIface, getBMCState()).WillOnce(Return("State.Ready"));
403 EXPECT_CALL(dataIface, getChassisState()).WillOnce(Return("State.On"));
404 EXPECT_CALL(dataIface, getHostState()).WillOnce(Return("State.Off"));
Matt Spinler677381b2020-01-23 10:04:29 -0600405
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600406 std::string pid = "_PID=" + std::to_string(getpid());
407 std::vector<std::string> ad{pid};
408 AdditionalData additionalData{ad};
409
410 auto ud = util::makeSysInfoUserDataSection(additionalData, dataIface);
411
412 EXPECT_TRUE(ud->valid());
413 EXPECT_EQ(ud->header().id, 0x5544);
414 EXPECT_EQ(ud->header().version, 0x01);
415 EXPECT_EQ(ud->header().subType, 0x01);
416 EXPECT_EQ(ud->header().componentID, 0x2000);
417
418 // Pull out the JSON data and check it.
419 const auto& d = ud->data();
420 std::string jsonString{d.begin(), d.end()};
421 auto json = nlohmann::json::parse(jsonString);
422
Patrick Williamsd9f0d642021-04-21 15:43:21 -0500423 // Ensure the 'Process Name' entry contains the name of this test
424 // executable.
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600425 auto name = json["Process Name"].get<std::string>();
Patrick Williamsd9f0d642021-04-21 15:43:21 -0500426 auto found = (name.find("pel_test") != std::string::npos) ||
427 (name.find("test-openpower-pels-pel") != std::string::npos);
428 EXPECT_TRUE(found);
429 // @TODO(stwcx): remove 'pel_test' when removing autotools.
Matt Spinler677381b2020-01-23 10:04:29 -0600430
431 auto version = json["BMC Version ID"].get<std::string>();
432 EXPECT_EQ(version, "ABCD1234");
Matt Spinler4aa23a12020-02-03 15:05:09 -0600433
434 auto state = json["BMCState"].get<std::string>();
435 EXPECT_EQ(state, "Ready");
436
437 state = json["ChassisState"].get<std::string>();
438 EXPECT_EQ(state, "On");
439
440 state = json["HostState"].get<std::string>();
441 EXPECT_EQ(state, "Off");
Matt Spinler4dcd3f42020-01-22 14:55:07 -0600442}
Matt Spinlerce3f4502020-01-22 15:44:35 -0600443
444// Test that the sections that override
445// virtual std::optional<std::string> Section::getJSON() const
446// return valid JSON.
447TEST_F(PELTest, SectionJSONTest)
448{
449 auto data = pelDataFactory(TestPELType::pelSimple);
450 PEL pel{data};
451
452 // Check that all JSON returned from the sections is
453 // parseable by nlohmann::json, which will throw an
454 // exception and fail the test if there is a problem.
455
456 // The getJSON() response needs to be wrapped in a { } to make
457 // actual valid JSON (PEL::toJSON() usually handles that).
458
459 auto jsonString = pel.privateHeader().getJSON();
460
461 // PrivateHeader always prints JSON
462 ASSERT_TRUE(jsonString);
463 *jsonString = '{' + *jsonString + '}';
464 auto json = nlohmann::json::parse(*jsonString);
465
466 jsonString = pel.userHeader().getJSON();
467
468 // UserHeader always prints JSON
469 ASSERT_TRUE(jsonString);
470 *jsonString = '{' + *jsonString + '}';
471 json = nlohmann::json::parse(*jsonString);
472
473 for (const auto& section : pel.optionalSections())
474 {
475 // The optional sections may or may not have implemented getJSON().
476 jsonString = section->getJSON();
477 if (jsonString)
478 {
479 *jsonString = '{' + *jsonString + '}';
480 auto json = nlohmann::json::parse(*jsonString);
481 }
482 }
483}
Matt Spinler5b289b22020-03-26 14:27:19 -0500484
485PelFFDCfile getJSONFFDC(const fs::path& dir)
486{
487 PelFFDCfile ffdc;
488 ffdc.format = UserDataFormat::json;
489 ffdc.subType = 5;
490 ffdc.version = 42;
491
492 auto inputJSON = R"({
493 "key1": "value1",
494 "key2": 42,
495 "key3" : [1, 2, 3, 4, 5],
496 "key4": {"key5": "value5"}
497 })"_json;
498
499 // Write the JSON to a file and get its descriptor.
500 auto s = inputJSON.dump();
501 std::vector<uint8_t> data{s.begin(), s.end()};
502 ffdc.fd = writeFileAndGetFD(dir, data);
503
504 return ffdc;
505}
506
507TEST_F(PELTest, MakeJSONFileUDSectionTest)
508{
509 auto dir = makeTempDir();
510
511 {
512 auto ffdc = getJSONFFDC(dir);
513
514 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
515 close(ffdc.fd);
516 ASSERT_TRUE(ud);
517 ASSERT_TRUE(ud->valid());
518 EXPECT_EQ(ud->header().id, 0x5544);
519
520 EXPECT_EQ(ud->header().version,
521 static_cast<uint8_t>(UserDataFormatVersion::json));
522 EXPECT_EQ(ud->header().subType,
523 static_cast<uint8_t>(UserDataFormat::json));
524 EXPECT_EQ(ud->header().componentID,
525 static_cast<uint16_t>(ComponentID::phosphorLogging));
526
527 // Pull the JSON back out of the the UserData section
528 const auto& d = ud->data();
529 std::string js{d.begin(), d.end()};
530 auto json = nlohmann::json::parse(js);
531
532 EXPECT_EQ("value1", json["key1"].get<std::string>());
533 EXPECT_EQ(42, json["key2"].get<int>());
534
535 std::vector<int> key3Values{1, 2, 3, 4, 5};
536 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>());
537
538 std::map<std::string, std::string> key4Values{{"key5", "value5"}};
539 auto actual = json["key4"].get<std::map<std::string, std::string>>();
540 EXPECT_EQ(key4Values, actual);
541 }
542
543 {
544 // A bad FD
545 PelFFDCfile ffdc;
546 ffdc.format = UserDataFormat::json;
547 ffdc.subType = 5;
548 ffdc.version = 42;
549 ffdc.fd = 10000;
550
551 // The section shouldn't get made
552 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
553 ASSERT_FALSE(ud);
554 }
555
556 fs::remove_all(dir);
557}
558
559PelFFDCfile getCBORFFDC(const fs::path& dir)
560{
561 PelFFDCfile ffdc;
562 ffdc.format = UserDataFormat::cbor;
563 ffdc.subType = 5;
564 ffdc.version = 42;
565
566 auto inputJSON = R"({
567 "key1": "value1",
568 "key2": 42,
569 "key3" : [1, 2, 3, 4, 5],
570 "key4": {"key5": "value5"}
571 })"_json;
572
573 // Convert the JSON to CBOR and write it to a file
574 auto data = nlohmann::json::to_cbor(inputJSON);
575 ffdc.fd = writeFileAndGetFD(dir, data);
576
577 return ffdc;
578}
579
580TEST_F(PELTest, MakeCBORFileUDSectionTest)
581{
582 auto dir = makeTempDir();
583
584 auto ffdc = getCBORFFDC(dir);
585 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
586 close(ffdc.fd);
587 ASSERT_TRUE(ud);
588 ASSERT_TRUE(ud->valid());
589 EXPECT_EQ(ud->header().id, 0x5544);
590
591 EXPECT_EQ(ud->header().version,
592 static_cast<uint8_t>(UserDataFormatVersion::cbor));
593 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::cbor));
594 EXPECT_EQ(ud->header().componentID,
595 static_cast<uint16_t>(ComponentID::phosphorLogging));
596
597 // Pull the CBOR back out of the PEL section
598 // The number of pad bytes to make the section be 4B aligned
599 // was added at the end, read it and then remove it and the
600 // padding before parsing it.
601 auto data = ud->data();
602 Stream stream{data};
603 stream.offset(data.size() - 4);
604 uint32_t pad;
605 stream >> pad;
606
607 data.resize(data.size() - 4 - pad);
608
609 auto json = nlohmann::json::from_cbor(data);
610
611 EXPECT_EQ("value1", json["key1"].get<std::string>());
612 EXPECT_EQ(42, json["key2"].get<int>());
613
614 std::vector<int> key3Values{1, 2, 3, 4, 5};
615 EXPECT_EQ(key3Values, json["key3"].get<std::vector<int>>());
616
617 std::map<std::string, std::string> key4Values{{"key5", "value5"}};
618 auto actual = json["key4"].get<std::map<std::string, std::string>>();
619 EXPECT_EQ(key4Values, actual);
620
621 fs::remove_all(dir);
622}
623
624PelFFDCfile getTextFFDC(const fs::path& dir)
625{
626 PelFFDCfile ffdc;
627 ffdc.format = UserDataFormat::text;
628 ffdc.subType = 5;
629 ffdc.version = 42;
630
631 std::string text{"this is some text that will be used for FFDC"};
632 std::vector<uint8_t> data{text.begin(), text.end()};
633
634 ffdc.fd = writeFileAndGetFD(dir, data);
635
636 return ffdc;
637}
638
639TEST_F(PELTest, MakeTextFileUDSectionTest)
640{
641 auto dir = makeTempDir();
642
643 auto ffdc = getTextFFDC(dir);
644 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
645 close(ffdc.fd);
646 ASSERT_TRUE(ud);
647 ASSERT_TRUE(ud->valid());
648 EXPECT_EQ(ud->header().id, 0x5544);
649
650 EXPECT_EQ(ud->header().version,
651 static_cast<uint8_t>(UserDataFormatVersion::text));
652 EXPECT_EQ(ud->header().subType, static_cast<uint8_t>(UserDataFormat::text));
653 EXPECT_EQ(ud->header().componentID,
654 static_cast<uint16_t>(ComponentID::phosphorLogging));
655
656 // Get the text back out
657 std::string text{ud->data().begin(), ud->data().end()};
658 EXPECT_EQ(text, "this is some text that will be used for FFDC");
659
660 fs::remove_all(dir);
661}
662
663PelFFDCfile getCustomFFDC(const fs::path& dir, const std::vector<uint8_t>& data)
664{
665 PelFFDCfile ffdc;
666 ffdc.format = UserDataFormat::custom;
667 ffdc.subType = 5;
668 ffdc.version = 42;
669
670 ffdc.fd = writeFileAndGetFD(dir, data);
671
672 return ffdc;
673}
674
675TEST_F(PELTest, MakeCustomFileUDSectionTest)
676{
677 auto dir = makeTempDir();
678
679 {
680 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8};
681
682 auto ffdc = getCustomFFDC(dir, data);
683 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
684 close(ffdc.fd);
685 ASSERT_TRUE(ud);
686 ASSERT_TRUE(ud->valid());
687 EXPECT_EQ(ud->header().size, 8 + 8); // data size + header size
688 EXPECT_EQ(ud->header().id, 0x5544);
689
690 EXPECT_EQ(ud->header().version, 42);
691 EXPECT_EQ(ud->header().subType, 5);
692 EXPECT_EQ(ud->header().componentID, 0x2002);
693
694 // Get the data back out
695 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()};
696 EXPECT_EQ(data, newData);
697 }
698
699 // Do the same thing again, but make it be non 4B aligned
700 // so the data gets padded.
701 {
702 std::vector<uint8_t> data{1, 2, 3, 4, 5, 6, 7, 8, 9};
703
704 auto ffdc = getCustomFFDC(dir, data);
705 auto ud = util::makeFFDCuserDataSection(0x2002, ffdc);
706 close(ffdc.fd);
707 ASSERT_TRUE(ud);
708 ASSERT_TRUE(ud->valid());
709 EXPECT_EQ(ud->header().size, 12 + 8); // data size + header size
710 EXPECT_EQ(ud->header().id, 0x5544);
711
712 EXPECT_EQ(ud->header().version, 42);
713 EXPECT_EQ(ud->header().subType, 5);
714 EXPECT_EQ(ud->header().componentID, 0x2002);
715
716 // Get the data back out
717 std::vector<uint8_t> newData{ud->data().begin(), ud->data().end()};
718
719 // pad the original to 12B so we can compare
720 data.push_back(0);
721 data.push_back(0);
722 data.push_back(0);
723
724 EXPECT_EQ(data, newData);
725 }
726
727 fs::remove_all(dir);
728}
729
730// Test Adding FFDC from files to a PEL
731TEST_F(PELTest, CreateWithFFDCTest)
732{
733 auto dir = makeTempDir();
734 message::Entry regEntry;
735 uint64_t timestamp = 5;
736
737 regEntry.name = "test";
738 regEntry.subsystem = 5;
739 regEntry.actionFlags = 0xC000;
740 regEntry.src.type = 0xBD;
741 regEntry.src.reasonCode = 0x1234;
742
743 std::vector<std::string> additionalData{"KEY1=VALUE1"};
744 AdditionalData ad{additionalData};
745 NiceMock<MockDataInterface> dataIface;
746 PelFFDC ffdc;
747
748 std::vector<uint8_t> customData{1, 2, 3, 4, 5, 6, 7, 8};
749
750 // This will be trimmed when added
751 std::vector<uint8_t> hugeCustomData(17000, 0x42);
752
753 ffdc.emplace_back(std::move(getJSONFFDC(dir)));
754 ffdc.emplace_back(std::move(getCBORFFDC(dir)));
755 ffdc.emplace_back(std::move(getTextFFDC(dir)));
756 ffdc.emplace_back(std::move(getCustomFFDC(dir, customData)));
757 ffdc.emplace_back(std::move(getCustomFFDC(dir, hugeCustomData)));
758
759 PEL pel{regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
760 ad, ffdc, dataIface};
761
762 EXPECT_TRUE(pel.valid());
763
764 // Clipped to the max
765 EXPECT_EQ(pel.size(), 16384);
766
767 // Check for the FFDC sections
768 size_t udCount = 0;
769 Section* ud = nullptr;
770
771 for (const auto& section : pel.optionalSections())
772 {
773 if (section->header().id == static_cast<uint16_t>(SectionID::userData))
774 {
775 udCount++;
776 ud = section.get();
777 }
778 }
779
780 EXPECT_EQ(udCount, 7); // AD section, sysInfo, 5 ffdc sections
781
782 // Check the last section was trimmed to
783 // something a bit less that 17000.
784 EXPECT_GT(ud->header().size, 14000);
785 EXPECT_LT(ud->header().size, 16000);
786
787 fs::remove_all(dir);
788}
Matt Spinler0a90a852020-06-04 13:18:27 -0500789
790// Create a PEL with device callouts
791TEST_F(PELTest, CreateWithDevCalloutsTest)
792{
793 message::Entry regEntry;
794 uint64_t timestamp = 5;
795
796 regEntry.name = "test";
797 regEntry.subsystem = 5;
798 regEntry.actionFlags = 0xC000;
799 regEntry.src.type = 0xBD;
800 regEntry.src.reasonCode = 0x1234;
801
802 NiceMock<MockDataInterface> dataIface;
803 PelFFDC ffdc;
804
805 const auto calloutJSON = R"(
806 {
807 "I2C":
808 {
809 "14":
810 {
811 "114":
812 {
813 "Callouts":[
814 {
815 "Name": "/chassis/motherboard/cpu0",
816 "LocationCode": "P1",
817 "Priority": "H"
818 }
819 ],
820 "Dest": "proc 0 target"
821 }
822 }
823 }
824 })";
825
826 std::vector<std::string> names{"systemA"};
827 EXPECT_CALL(dataIface, getSystemNames)
828 .Times(2)
Matt Spinler1ab66962020-10-29 13:21:44 -0500829 .WillRepeatedly(Return(names));
Matt Spinler0a90a852020-06-04 13:18:27 -0500830
831 EXPECT_CALL(dataIface,
832 getLocationCode(
833 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"))
834 .WillOnce(Return("UXXX-P1"));
835
Matt Spinler2f9225a2020-08-05 12:58:49 -0500836 EXPECT_CALL(dataIface, getInventoryFromLocCode("P1", 0, false))
Matt Spinler0a90a852020-06-04 13:18:27 -0500837 .WillOnce(
838 Return("/xyz/openbmc_project/inventory/chassis/motherboard/cpu0"));
839
840 EXPECT_CALL(
841 dataIface,
842 getHWCalloutFields(
843 "/xyz/openbmc_project/inventory/chassis/motherboard/cpu0", _, _, _))
844 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
845 SetArgReferee<3>("123456789ABC")));
846
847 auto dataPath = getPELReadOnlyDataPath();
848 std::ofstream file{dataPath / "systemA_dev_callouts.json"};
849 file << calloutJSON;
850 file.close();
851
852 {
853 std::vector<std::string> data{
854 "CALLOUT_ERRNO=5",
855 "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
856 "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0072"};
857
858 AdditionalData ad{data};
859
860 PEL pel{
861 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
862 ad, ffdc, dataIface};
863
864 ASSERT_TRUE(pel.primarySRC().value()->callouts());
865 auto& callouts = pel.primarySRC().value()->callouts()->callouts();
866 ASSERT_EQ(callouts.size(), 1);
Andrew Geissler44fc3162020-07-09 09:21:31 -0500867 ASSERT_TRUE(pel.isCalloutPresent());
Matt Spinler0a90a852020-06-04 13:18:27 -0500868
869 EXPECT_EQ(callouts[0]->priority(), 'H');
870 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P1");
871
872 auto& fru = callouts[0]->fruIdentity();
873 EXPECT_EQ(fru->getPN().value(), "1234567");
874 EXPECT_EQ(fru->getCCIN().value(), "CCCC");
875 EXPECT_EQ(fru->getSN().value(), "123456789ABC");
876
877 const auto& section = pel.optionalSections().back();
878
879 ASSERT_EQ(section->header().id, 0x5544); // UD
880 auto ud = static_cast<UserData*>(section.get());
881
882 // Check that there was a UserData section added that
883 // contains debug details about the device.
884 const auto& d = ud->data();
885 std::string jsonString{d.begin(), d.end()};
886 auto actualJSON = nlohmann::json::parse(jsonString);
887
888 auto expectedJSON = R"(
889 {
890 "PEL Internal Debug Data": {
891 "SRC": [
892 "I2C: bus: 14 address: 114 dest: proc 0 target"
893 ]
894 }
895 }
896 )"_json;
897
898 EXPECT_EQ(actualJSON, expectedJSON);
899 }
900
901 {
902 // Device path not found (wrong i2c addr), so no callouts
903 std::vector<std::string> data{
904 "CALLOUT_ERRNO=5",
905 "CALLOUT_DEVICE_PATH=/sys/devices/platform/ahb/ahb:apb/"
906 "ahb:apb:bus@1e78a000/1e78a340.i2c-bus/i2c-14/14-0099"};
907
908 AdditionalData ad{data};
909
910 PEL pel{
911 regEntry, 42, timestamp, phosphor::logging::Entry::Level::Error,
912 ad, ffdc, dataIface};
913
914 // no callouts
915 EXPECT_FALSE(pel.primarySRC().value()->callouts());
916
917 // Now check that there was a UserData section
918 // that contains the lookup error.
919 const auto& section = pel.optionalSections().back();
920
921 ASSERT_EQ(section->header().id, 0x5544); // UD
922 auto ud = static_cast<UserData*>(section.get());
923
924 const auto& d = ud->data();
925
926 std::string jsonString{d.begin(), d.end()};
927
928 auto actualJSON = nlohmann::json::parse(jsonString);
929
930 auto expectedJSON =
931 "{\"PEL Internal Debug Data\":{\"SRC\":"
932 "[\"Problem looking up I2C callouts on 14 153: "
933 "[json.exception.out_of_range.403] key '153' not found\"]}}"_json;
934
935 EXPECT_EQ(actualJSON, expectedJSON);
936 }
937
938 fs::remove_all(dataPath);
939}
Matt Spinlere513dbc2020-08-27 11:14:17 -0500940
941// Test PELs when the callouts are passed in using a JSON file.
942TEST_F(PELTest, CreateWithJSONCalloutsTest)
943{
944 PelFFDCfile ffdcFile;
945 ffdcFile.format = UserDataFormat::json;
946 ffdcFile.subType = 0xCA; // Callout JSON
947 ffdcFile.version = 1;
948
949 // Write these callouts to a JSON file and pass it into
950 // the PEL as an FFDC file.
951 auto inputJSON = R"([
952 {
953 "Priority": "H",
954 "LocationCode": "P0-C1"
955 },
956 {
957 "Priority": "M",
958 "Procedure": "PROCEDURE"
959 }
960 ])"_json;
961
962 auto s = inputJSON.dump();
963 std::vector<uint8_t> data{s.begin(), s.end()};
964 auto dir = makeTempDir();
965 ffdcFile.fd = writeFileAndGetFD(dir, data);
966
967 PelFFDC ffdc;
968 ffdc.push_back(std::move(ffdcFile));
969
970 AdditionalData ad;
971 NiceMock<MockDataInterface> dataIface;
972
973 EXPECT_CALL(dataIface, expandLocationCode("P0-C1", 0))
974 .Times(1)
975 .WillOnce(Return("UXXX-P0-C1"));
976 EXPECT_CALL(dataIface, getInventoryFromLocCode("P0-C1", 0, false))
977 .Times(1)
978 .WillOnce(Return("/inv/system/chassis/motherboard/bmc"));
979 EXPECT_CALL(dataIface, getHWCalloutFields(
980 "/inv/system/chassis/motherboard/bmc", _, _, _))
981 .Times(1)
982 .WillOnce(DoAll(SetArgReferee<1>("1234567"), SetArgReferee<2>("CCCC"),
983 SetArgReferee<3>("123456789ABC")));
984
985 message::Entry regEntry;
986 regEntry.name = "test";
987 regEntry.subsystem = 5;
988 regEntry.actionFlags = 0xC000;
989 regEntry.src.type = 0xBD;
990 regEntry.src.reasonCode = 0x1234;
991
992 PEL pel{regEntry, 42, 5, phosphor::logging::Entry::Level::Error,
993 ad, ffdc, dataIface};
994
995 ASSERT_TRUE(pel.valid());
996 ASSERT_TRUE(pel.primarySRC().value()->callouts());
997 const auto& callouts = pel.primarySRC().value()->callouts()->callouts();
998 ASSERT_EQ(callouts.size(), 2);
999
1000 {
1001 EXPECT_EQ(callouts[0]->priority(), 'H');
1002 EXPECT_EQ(callouts[0]->locationCode(), "UXXX-P0-C1");
1003
1004 auto& fru = callouts[0]->fruIdentity();
1005 EXPECT_EQ(fru->getPN().value(), "1234567");
1006 EXPECT_EQ(fru->getCCIN().value(), "CCCC");
1007 EXPECT_EQ(fru->getSN().value(), "123456789ABC");
1008 EXPECT_EQ(fru->failingComponentType(), src::FRUIdentity::hardwareFRU);
1009 }
1010 {
1011 EXPECT_EQ(callouts[1]->priority(), 'M');
1012 EXPECT_EQ(callouts[1]->locationCode(), "");
1013
1014 auto& fru = callouts[1]->fruIdentity();
1015 EXPECT_EQ(fru->getMaintProc().value(), "PROCEDU");
1016 EXPECT_EQ(fru->failingComponentType(),
1017 src::FRUIdentity::maintenanceProc);
1018 }
1019 fs::remove_all(dir);
1020}