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