1 // SPDX-License-Identifier: Apache-2.0
2 // SPDX-FileCopyrightText: Copyright 2019 IBM Corporation
3
4 #include "extensions/openpower-pels/manager.hpp"
5 #include "log_manager.hpp"
6 #include "mocks.hpp"
7 #include "pel_utils.hpp"
8
9 #include <sdbusplus/test/sdbus_mock.hpp>
10 #include <xyz/openbmc_project/Common/error.hpp>
11
12 #include <fstream>
13 #include <regex>
14
15 #include <gtest/gtest.h>
16
17 using namespace openpower::pels;
18 namespace fs = std::filesystem;
19
20 using ::testing::NiceMock;
21 using ::testing::Return;
22 using json = nlohmann::json;
23
24 using Level = phosphor::logging::Entry::Level;
25
26 class TestLogger
27 {
28 public:
log(const std::string & name,Level level,const EventLogger::ADMap & additionalData)29 void log(const std::string& name, Level level,
30 const EventLogger::ADMap& additionalData)
31 {
32 errName = name;
33 errLevel = level;
34 ad = additionalData;
35 }
36
37 std::string errName;
38 Level errLevel;
39 EventLogger::ADMap ad;
40 };
41
42 class ManagerTest : public CleanPELFiles
43 {
44 public:
ManagerTest()45 ManagerTest() :
46 bus(sdbusplus::get_mocked_new(&sdbusInterface)),
47 logManager(bus, "logging_path")
48 {
49 sd_event_default(&sdEvent);
50 }
51
makeTempDir()52 fs::path makeTempDir()
53 {
54 char path[] = "/tmp/tempnameXXXXXX";
55 std::filesystem::path dir = mkdtemp(path);
56 dirsToRemove.push_back(dir);
57 return dir;
58 }
59
~ManagerTest()60 ~ManagerTest()
61 {
62 for (const auto& d : dirsToRemove)
63 {
64 std::filesystem::remove_all(d);
65 }
66 sd_event_unref(sdEvent);
67 }
68
69 NiceMock<sdbusplus::SdBusMock> sdbusInterface;
70 sdbusplus::bus_t bus;
71 phosphor::logging::internal::Manager logManager;
72 sd_event* sdEvent;
73 TestLogger logger;
74 std::vector<std::filesystem::path> dirsToRemove;
75 };
76
findAnyPELInRepo()77 std::optional<fs::path> findAnyPELInRepo()
78 {
79 // PELs are named <timestamp>_<ID>
80 std::regex expr{"\\d+_\\d+"};
81
82 for (auto& f : fs::directory_iterator(getPELRepoPath() / "logs"))
83 {
84 if (std::regex_search(f.path().string(), expr))
85 {
86 return f.path();
87 }
88 }
89 return std::nullopt;
90 }
91
countPELsInRepo()92 size_t countPELsInRepo()
93 {
94 size_t count = 0;
95 std::regex expr{"\\d+_\\d+"};
96
97 for (auto& f : fs::directory_iterator(getPELRepoPath() / "logs"))
98 {
99 if (std::regex_search(f.path().string(), expr))
100 {
101 count++;
102 }
103 }
104 return count;
105 }
106
deletePELFile(uint32_t id)107 void deletePELFile(uint32_t id)
108 {
109 char search[20];
110
111 sprintf(search, "\\d+_%.8X", id);
112 std::regex expr{search};
113
114 for (auto& f : fs::directory_iterator(getPELRepoPath() / "logs"))
115 {
116 if (std::regex_search(f.path().string(), expr))
117 {
118 fs::remove(f.path());
119 break;
120 }
121 }
122 }
123
124 // Test that using the RAWPEL=<file> with the Manager::create() call gets
125 // a PEL saved in the repository.
TEST_F(ManagerTest,TestCreateWithPEL)126 TEST_F(ManagerTest, TestCreateWithPEL)
127 {
128 std::unique_ptr<DataInterfaceBase> dataIface =
129 std::make_unique<MockDataInterface>();
130
131 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
132
133 Manager manager{logManager, std::move(dataIface),
134 std::bind_front(&TestLogger::log, &logger),
135 std::move(journal)};
136
137 // Create a PEL, write it to a file, and pass that filename into
138 // the create function.
139 auto data = pelDataFactory(TestPELType::pelSimple);
140
141 fs::path pelFilename = makeTempDir() / "rawpel";
142 std::ofstream pelFile{pelFilename};
143 pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
144 pelFile.close();
145
146 std::map<std::string, std::string> additionalData{
147 {"RAWPEL", pelFilename.string()}};
148 std::vector<std::string> associations;
149
150 manager.create("error message", 42, 0, Level::Error, additionalData,
151 associations);
152
153 // Find the file in the PEL repository directory
154 auto pelPathInRepo = findAnyPELInRepo();
155
156 EXPECT_TRUE(pelPathInRepo);
157
158 // Now remove it based on its OpenBMC event log ID
159 manager.erase(42);
160
161 pelPathInRepo = findAnyPELInRepo();
162
163 EXPECT_FALSE(pelPathInRepo);
164 }
165
TEST_F(ManagerTest,TestCreateWithInvalidPEL)166 TEST_F(ManagerTest, TestCreateWithInvalidPEL)
167 {
168 std::unique_ptr<DataInterfaceBase> dataIface =
169 std::make_unique<MockDataInterface>();
170
171 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
172
173 Manager manager{logManager, std::move(dataIface),
174 std::bind_front(&TestLogger::log, &logger),
175 std::move(journal)};
176
177 // Create a PEL, write it to a file, and pass that filename into
178 // the create function.
179 auto data = pelDataFactory(TestPELType::pelSimple);
180
181 // Truncate it to make it invalid.
182 data.resize(200);
183
184 fs::path pelFilename = makeTempDir() / "rawpel";
185 std::ofstream pelFile{pelFilename};
186 pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
187 pelFile.close();
188
189 std::map<std::string, std::string> additionalData{
190 {"RAWPEL", pelFilename.string()}};
191 std::vector<std::string> associations;
192
193 manager.create("error message", 42, 0, Level::Error, additionalData,
194 associations);
195
196 // Run the event loop to log the bad PEL event
197 sdeventplus::Event e{sdEvent};
198 e.run(std::chrono::milliseconds(1));
199
200 PEL invalidPEL{data};
201 EXPECT_EQ(logger.errName, "org.open_power.Logging.Error.BadHostPEL");
202 EXPECT_EQ(logger.errLevel, Level::Error);
203 EXPECT_EQ(std::stoi(logger.ad["PLID"], nullptr, 16), invalidPEL.plid());
204 EXPECT_EQ(logger.ad["OBMC_LOG_ID"], "42");
205 EXPECT_EQ(logger.ad["SRC"], (*invalidPEL.primarySRC())->asciiString());
206 EXPECT_EQ(logger.ad["PEL_SIZE"], std::to_string(data.size()));
207
208 // Check that the bad PEL data was saved to a file.
209 auto badPELData = readPELFile(getPELRepoPath() / "badPEL");
210 EXPECT_EQ(*badPELData, data);
211 }
212
213 // Test that the message registry can be used to build a PEL.
TEST_F(ManagerTest,TestCreateWithMessageRegistry)214 TEST_F(ManagerTest, TestCreateWithMessageRegistry)
215 {
216 const auto registry = R"(
217 {
218 "PELs":
219 [
220 {
221 "Name": "xyz.openbmc_project.Error.Test",
222 "Subsystem": "power_supply",
223 "ActionFlags": ["service_action", "report"],
224 "SRC":
225 {
226 "ReasonCode": "0x2030"
227 },
228 "Callouts": [
229 {
230 "CalloutList": [
231 {"Priority": "high", "Procedure": "BMC0001"},
232 {"Priority": "medium", "SymbolicFRU": "service_docs"}
233 ]
234 }
235 ],
236 "Documentation":
237 {
238 "Description": "A PGOOD Fault",
239 "Message": "PS had a PGOOD Fault"
240 }
241 },
242 {
243 "Name": "xyz.openbmc_project.Logging.Error.Default",
244 "Subsystem": "bmc_firmware",
245 "SRC":
246 {
247 "ReasonCode": "0x2031"
248 },
249 "Documentation":
250 {
251 "Description": "The entry used when no match found",
252 "Message": "This is a generic SRC"
253 }
254 }
255 ]
256 }
257 )";
258
259 auto path = getPELReadOnlyDataPath();
260 fs::create_directories(path);
261 path /= "message_registry.json";
262
263 std::ofstream registryFile{path};
264 registryFile << registry;
265 registryFile.close();
266
267 std::unique_ptr<DataInterfaceBase> dataIface =
268 std::make_unique<MockDataInterface>();
269
270 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
271
272 Manager manager{logManager, std::move(dataIface),
273 std::bind_front(&TestLogger::log, &logger),
274 std::move(journal)};
275
276 std::map<std::string, std::string> additionalData{{"FOO", "BAR"}};
277 std::vector<std::string> associations;
278
279 // Create the event log to create the PEL from.
280 manager.create("xyz.openbmc_project.Error.Test", 33, 0, Level::Error,
281 additionalData, associations);
282
283 // Ensure a PEL was created in the repository
284 auto pelFile = findAnyPELInRepo();
285 ASSERT_TRUE(pelFile);
286
287 auto data = readPELFile(*pelFile);
288 PEL pel(*data);
289
290 // Spot check it. Other testcases cover the details.
291 EXPECT_TRUE(pel.valid());
292 EXPECT_EQ(pel.obmcLogID(), 33);
293 EXPECT_EQ(pel.primarySRC().value()->asciiString(),
294 "BD612030 ");
295 // Check if the eventId creation is good
296 EXPECT_EQ(manager.getEventId(pel),
297 "BD612030 00000055 00000010 00000000 00000000 00000000 00000000 "
298 "00000000 00000000");
299 // Check if resolution property creation is good
300 EXPECT_EQ(manager.getResolution(pel),
301 "1. Priority: High, Procedure: BMC0001\n2. Priority: Medium, PN: "
302 "SVCDOCS\n");
303
304 // Remove it
305 manager.erase(33);
306 pelFile = findAnyPELInRepo();
307 EXPECT_FALSE(pelFile);
308
309 // Create an event log that can't be found in the registry.
310 // In this case, xyz.openbmc_project.Logging.Error.Default will
311 // be used as the key instead to find a registry match.
312 manager.create("xyz.openbmc_project.Error.Foo", 42, 0, Level::Error,
313 additionalData, associations);
314
315 // Ensure a PEL was still created in the repository
316 pelFile = findAnyPELInRepo();
317 ASSERT_TRUE(pelFile);
318
319 data = readPELFile(*pelFile);
320 PEL newPEL(*data);
321
322 EXPECT_TRUE(newPEL.valid());
323 EXPECT_EQ(newPEL.obmcLogID(), 42);
324 EXPECT_EQ(newPEL.primarySRC().value()->asciiString(),
325 "BD8D2031 ");
326
327 // Check for both the original AdditionalData item as well as
328 // the ERROR_NAME item that should contain the error message
329 // property that wasn't found.
330 std::string errorName;
331 std::string adItem;
332
333 for (const auto& section : newPEL.optionalSections())
334 {
335 if (SectionID::userData == static_cast<SectionID>(section->header().id))
336 {
337 if (UserDataFormat::json ==
338 static_cast<UserDataFormat>(section->header().subType))
339 {
340 auto ud = static_cast<UserData*>(section.get());
341
342 // Check that there was a UserData section added that
343 // contains debug details about the device.
344 const auto& d = ud->data();
345 std::string jsonString{d.begin(), d.end()};
346 auto json = nlohmann::json::parse(jsonString);
347
348 if (json.contains("ERROR_NAME"))
349 {
350 errorName = json["ERROR_NAME"].get<std::string>();
351 }
352
353 if (json.contains("FOO"))
354 {
355 adItem = json["FOO"].get<std::string>();
356 }
357 }
358 }
359 if (!errorName.empty())
360 {
361 break;
362 }
363 }
364
365 EXPECT_EQ(errorName, "xyz.openbmc_project.Error.Foo");
366 EXPECT_EQ(adItem, "BAR");
367 }
368
TEST_F(ManagerTest,TestDBusMethods)369 TEST_F(ManagerTest, TestDBusMethods)
370 {
371 std::unique_ptr<DataInterfaceBase> dataIface =
372 std::make_unique<MockDataInterface>();
373
374 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
375
376 Manager manager{logManager, std::move(dataIface),
377 std::bind_front(&TestLogger::log, &logger),
378 std::move(journal)};
379
380 // Create a PEL, write it to a file, and pass that filename into
381 // the create function so there's one in the repo.
382 auto data = pelDataFactory(TestPELType::pelSimple);
383
384 fs::path pelFilename = makeTempDir() / "rawpel";
385 std::ofstream pelFile{pelFilename};
386 pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
387 pelFile.close();
388
389 std::map<std::string, std::string> additionalData{
390 {"RAWPEL", pelFilename.string()}};
391 std::vector<std::string> associations;
392
393 manager.create("error message", 42, 0, Level::Error, additionalData,
394 associations);
395
396 // getPELFromOBMCID
397 auto newData = manager.getPELFromOBMCID(42);
398 EXPECT_EQ(newData.size(), data.size());
399
400 // Read the PEL to get the ID for later
401 PEL pel{newData};
402 auto id = pel.id();
403
404 EXPECT_THROW(
405 manager.getPELFromOBMCID(id + 1),
406 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
407
408 // getPEL
409 auto unixfd = manager.getPEL(id);
410
411 // Get the size
412 struct stat s;
413 int r = fstat(unixfd, &s);
414 ASSERT_EQ(r, 0);
415 auto size = s.st_size;
416
417 // Open the FD and check the contents
418 FILE* fp = fdopen(unixfd, "r");
419 ASSERT_NE(fp, nullptr);
420
421 std::vector<uint8_t> fdData;
422 fdData.resize(size);
423 r = fread(fdData.data(), 1, size, fp);
424 EXPECT_EQ(r, size);
425
426 EXPECT_EQ(newData, fdData);
427
428 fclose(fp);
429
430 // Run the event loop to close the FD
431 sdeventplus::Event e{sdEvent};
432 e.run(std::chrono::milliseconds(1));
433
434 EXPECT_THROW(
435 manager.getPEL(id + 1),
436 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
437
438 // hostAck
439 manager.hostAck(id);
440
441 EXPECT_THROW(
442 manager.hostAck(id + 1),
443 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
444
445 // hostReject
446 manager.hostReject(id, Manager::RejectionReason::BadPEL);
447
448 // Run the event loop to log the bad PEL event
449 e.run(std::chrono::milliseconds(1));
450
451 EXPECT_EQ(logger.errName, "org.open_power.Logging.Error.SentBadPELToHost");
452 EXPECT_EQ(id, std::stoi(logger.ad["BAD_ID"], nullptr, 16));
453
454 manager.hostReject(id, Manager::RejectionReason::HostFull);
455
456 EXPECT_THROW(
457 manager.hostReject(id + 1, Manager::RejectionReason::BadPEL),
458 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
459
460 // GetPELIdFromBMCLogId
461 EXPECT_EQ(pel.id(), manager.getPELIdFromBMCLogId(pel.obmcLogID()));
462 EXPECT_THROW(
463 manager.getPELIdFromBMCLogId(pel.obmcLogID() + 1),
464 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
465
466 // GetBMCLogIdFromPELId
467 EXPECT_EQ(pel.obmcLogID(), manager.getBMCLogIdFromPELId(pel.id()));
468 EXPECT_THROW(
469 manager.getBMCLogIdFromPELId(pel.id() + 1),
470 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
471 }
472
473 // An ESEL from the wild
474 const std::string esel{
475 "00 00 df 00 00 00 00 20 00 04 12 01 6f aa 00 00 "
476 "50 48 00 30 01 00 33 00 20 23 05 11 10 20 20 00 00 00 00 07 5c d5 50 db "
477 "42 00 00 10 00 00 00 00 00 00 00 00 00 00 00 00 90 00 00 4e 90 00 00 4e "
478 "55 48 00 18 01 00 09 00 8a 03 40 00 00 00 00 00 ff ff 00 00 00 00 00 00 "
479 "50 53 00 50 01 01 00 00 02 00 00 09 33 2d 00 48 00 00 00 e0 00 00 10 00 "
480 "00 00 00 00 00 20 00 00 00 0c 00 02 00 00 00 fa 00 00 0c e4 00 00 00 12 "
481 "42 43 38 41 33 33 32 44 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 20 "
482 "20 20 20 20 20 20 20 20 55 44 00 1c 01 06 01 00 02 54 41 4b 00 00 00 06 "
483 "00 00 00 55 00 01 f9 20 00 00 00 00 55 44 00 24 01 06 01 00 01 54 41 4b "
484 "00 00 00 05 00 00 00 00 00 00 00 00 00 00 00 00 23 01 00 02 00 05 00 00 "
485 "55 44 00 0c 01 0b 01 00 0f 01 00 00 55 44 00 10 01 04 01 00 0f 9f de 6a "
486 "00 01 00 00 55 44 00 7c 00 0c 01 00 00 13 0c 02 00 fa 0c e4 16 00 01 2c "
487 "0c 1c 16 00 00 fa 0a f0 14 00 00 fa 0b b8 14 00 00 be 09 60 12 00 01 2c "
488 "0d 7a 12 00 00 fa 0c 4e 10 00 00 fa 0c e4 10 00 00 be 0a 8c 16 00 01 2c "
489 "0c 1c 16 00 01 09 09 f6 16 00 00 fa 09 f6 14 00 00 fa 0b b8 14 00 00 fa "
490 "0a f0 14 00 00 be 08 ca 12 00 01 2c 0c e4 12 00 00 fa 0b 54 10 00 00 fa "
491 "0c 2d 10 00 00 be 08 ca 55 44 00 58 01 03 01 00 00 00 00 00 00 05 31 64 "
492 "00 00 00 00 00 05 0d d4 00 00 00 00 40 5f 06 e0 00 00 00 00 40 5d d2 00 "
493 "00 00 00 00 40 57 d3 d0 00 00 00 00 40 58 f6 a0 00 00 00 00 40 54 c9 34 "
494 "00 00 00 00 40 55 9a 10 00 00 00 00 40 4c 0a 80 00 00 00 00 00 00 27 14 "
495 "55 44 01 84 01 01 01 00 48 6f 73 74 62 6f 6f 74 20 42 75 69 6c 64 20 49 "
496 "44 3a 20 68 6f 73 74 62 6f 6f 74 2d 66 65 63 37 34 64 66 2d 70 30 61 38 "
497 "37 64 63 34 2f 68 62 69 63 6f 72 65 2e 62 69 6e 00 49 42 4d 2d 77 69 74 "
498 "68 65 72 73 70 6f 6f 6e 2d 4f 50 39 2d 76 32 2e 34 2d 39 2e 32 33 34 0a "
499 "09 6f 70 2d 62 75 69 6c 64 2d 38 32 66 34 63 66 30 0a 09 62 75 69 6c 64 "
500 "72 6f 6f 74 2d 32 30 31 39 2e 30 35 2e 32 2d 31 30 2d 67 38 39 35 39 31 "
501 "31 34 0a 09 73 6b 69 62 6f 6f 74 2d 76 36 2e 35 2d 31 38 2d 67 34 37 30 "
502 "66 66 62 35 66 32 39 64 37 0a 09 68 6f 73 74 62 6f 6f 74 2d 66 65 63 37 "
503 "34 64 66 2d 70 30 61 38 37 64 63 34 0a 09 6f 63 63 2d 65 34 35 39 37 61 "
504 "62 0a 09 6c 69 6e 75 78 2d 35 2e 32 2e 31 37 2d 6f 70 65 6e 70 6f 77 65 "
505 "72 31 2d 70 64 64 63 63 30 33 33 0a 09 70 65 74 69 74 62 6f 6f 74 2d 76 "
506 "31 2e 31 30 2e 34 0a 09 6d 61 63 68 69 6e 65 2d 78 6d 6c 2d 63 36 32 32 "
507 "63 62 35 2d 70 37 65 63 61 62 33 64 0a 09 68 6f 73 74 62 6f 6f 74 2d 62 "
508 "69 6e 61 72 69 65 73 2d 36 36 65 39 61 36 30 0a 09 63 61 70 70 2d 75 63 "
509 "6f 64 65 2d 70 39 2d 64 64 32 2d 76 34 0a 09 73 62 65 2d 36 30 33 33 30 "
510 "65 30 0a 09 68 63 6f 64 65 2d 68 77 30 39 32 31 31 39 61 2e 6f 70 6d 73 "
511 "74 0a 00 00 55 44 00 70 01 04 01 00 0f 9f de 6a 00 05 00 00 07 5f 1d f4 "
512 "30 32 43 59 34 37 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 "
513 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 "
514 "0b ac 54 02 59 41 31 39 33 34 36 39 37 30 35 38 00 00 00 00 00 00 05 22 "
515 "a1 58 01 8a 00 58 40 20 17 18 4d 2c 00 00 00 fc 01 a1 00 00 55 44 00 14 "
516 "01 08 01 00 00 00 00 01 00 00 00 5a 00 00 00 05 55 44 03 fc 01 15 31 00 "
517 "01 28 00 42 46 41 50 49 00 00 00 00 00 00 00 00 00 00 00 00 00 00 03 f4 "
518 "00 00 00 00 00 00 03 f4 00 00 00 0b 00 00 00 00 00 00 00 3d 2c 9b c2 84 "
519 "00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 09 "
520 "00 00 00 00 00 11 bd 20 00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 "
521 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 00 00 00 00 00 00 01 2c "
522 "00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0c 1c 00 00 00 64 00 00 00 3d "
523 "2c 9b d1 11 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 "
524 "00 00 00 0a 00 00 00 00 00 13 b5 a0 00 00 00 00 00 01 f8 80 00 00 00 00 "
525 "00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 00 00 00 00 "
526 "00 00 00 be 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0a 8c 00 00 00 64 "
527 "00 00 00 3d 2c 9b df 98 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 "
528 "00 00 00 00 00 00 00 0b 00 00 00 00 00 15 ae 20 00 00 00 00 00 01 f8 80 "
529 "00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 10 "
530 "00 00 00 00 00 00 00 fa 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0c e4 "
531 "00 00 00 64 00 00 00 3d 2c 9b ea b7 00 00 01 e4 00 48 43 4f fb ed 70 b1 "
532 "00 00 02 01 00 00 00 00 00 00 00 0c 00 00 00 00 00 17 a6 a0 00 00 00 00 "
533 "00 01 f8 80 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 "
534 "00 00 00 12 00 00 00 00 00 00 00 fa 00 00 00 00 00 00 07 d0 00 00 00 00 "
535 "00 00 0c 4e 00 00 00 64 00 00 00 3d 2c 9b f6 27 00 00 01 e4 00 48 43 4f "
536 "fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 0d 00 00 00 00 00 19 9f 20 "
537 "00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 "
538 "00 00 00 00 00 00 00 12 00 00 00 00 00 00 01 2c 00 00 00 00 00 00 07 d0 "
539 "00 00 00 00 00 00 0d 7a 00 00 00 64 00 00 00 3d 2c 9c 05 75 00 00 01 e4 "
540 "00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 0e 00 00 00 00 "
541 "00 1b 97 a0 00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 00 00 00 00 "
542 "00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 00 00 00 00 be 00 00 00 00 "
543 "00 00 07 d0 00 00 00 00 00 00 09 60 00 00 00 64 00 00 00 3d 2c 9c 11 29 "
544 "00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 00 00 00 0f "
545 "00 00 00 00 00 1d 90 20 00 00 00 00 00 01 f8 80 00 00 00 00 00 00 00 01 "
546 "00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 14 00 00 00 00 00 00 00 fa "
547 "00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0b b8 00 00 00 64 00 00 00 3d "
548 "2c 9c 1c 45 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 00 00 00 00 "
549 "00 00 00 10 00 00 00 00 00 1f 88 a0 00 00 00 00 00 01 f8 80 00 00 00 00 "
550 "00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 00 00 00 00 "
551 "00 00 00 fa 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0a f0 00 00 00 64 "
552 "00 00 00 3d 2c 9c 2b 14 00 00 01 e4 00 48 43 4f fb ed 70 b1 00 00 02 01 "
553 "00 00 00 00 00 00 00 11 00 00 00 00 00 21 81 20 00 00 00 00 00 01 f8 80 "
554 "00 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 16 "
555 "00 00 00 00 00 00 01 2c 00 00 00 00 00 00 07 d0 00 00 00 00 00 00 0c 1c "
556 "00 00 00 64 00 00 00 3d 2d 6d 8f 9e 00 00 01 e4 00 00 43 4f 52 d7 9c 36 "
557 "00 00 04 73 00 00 00 1c 00 00 00 3d 2d 6d 99 ac 00 00 01 e4 00 10 43 4f "
558 "3f f2 02 3d 00 00 05 58 00 00 00 00 02 00 00 01 00 00 00 00 00 00 00 40 "
559 "00 00 00 2c 55 44 00 30 01 15 31 00 01 28 00 42 46 41 50 49 5f 44 42 47 "
560 "00 00 00 00 00 00 00 00 00 00 00 28 00 00 00 00 00 00 00 28 00 00 00 00 "
561 "00 00 00 00 55 44 01 74 01 15 31 00 01 28 00 42 46 41 50 49 5f 49 00 00 "
562 "00 00 00 00 00 00 00 00 00 00 01 6c 00 00 00 00 00 00 01 6c 00 00 00 0b "
563 "00 00 00 00 00 00 00 3c 0d 52 18 5e 00 00 01 e4 00 08 43 4f 46 79 94 13 "
564 "00 00 0a 5b 00 00 00 00 00 00 2c 00 00 00 00 24 00 00 00 3c 0d 6b 26 6c "
565 "00 00 01 e4 00 00 43 4f 4e 9b 18 74 00 00 01 03 00 00 00 1c 00 00 00 3c "
566 "12 b9 2d 13 00 00 01 e4 00 00 43 4f ea 31 ed d4 00 00 05 c4 00 00 00 1c "
567 "00 00 00 3c 13 02 73 53 00 00 01 e4 00 00 43 4f ea 31 ed d4 00 00 05 c4 "
568 "00 00 00 1c 00 00 00 3c 13 04 7c 94 00 00 01 e4 00 00 43 4f ea 31 ed d4 "
569 "00 00 05 c4 00 00 00 1c 00 00 00 3c 13 06 ad e1 00 00 01 e4 00 00 43 4f "
570 "ea 31 ed d4 00 00 05 c4 00 00 00 1c 00 00 00 3c 13 07 3f 77 00 00 01 e4 "
571 "00 00 43 4f 5e 4a 55 32 00 00 10 f2 00 00 00 1c 00 00 00 3c 13 07 4e e4 "
572 "00 00 01 e4 00 00 43 4f 5e 4a 55 32 00 00 0d 68 00 00 00 1c 00 00 00 3c "
573 "13 36 79 18 00 00 01 e4 00 00 43 4f ea 31 ed d4 00 00 05 c4 00 00 00 1c "
574 "00 00 00 3d 2c 9c 36 70 00 00 01 e4 00 00 43 4f 23 45 90 97 00 00 02 47 "
575 "00 00 00 1c 00 00 00 3d 2d 6d a3 ed 00 00 01 e4 00 08 43 4f 74 3a 5b 1a "
576 "00 00 04 cc 00 00 00 00 02 00 00 01 00 00 00 24 55 44 00 30 01 15 31 00 "
577 "01 28 00 42 53 43 41 4e 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 28 "
578 "00 00 00 00 00 00 00 28 00 00 00 00 00 00 00 00"};
579
TEST_F(ManagerTest,TestESELToRawData)580 TEST_F(ManagerTest, TestESELToRawData)
581 {
582 auto data = Manager::eselToRawData(esel);
583
584 EXPECT_EQ(data.size(), 2464);
585
586 PEL pel{data};
587 EXPECT_TRUE(pel.valid());
588 }
589
TEST_F(ManagerTest,TestCreateWithESEL)590 TEST_F(ManagerTest, TestCreateWithESEL)
591 {
592 std::unique_ptr<DataInterfaceBase> dataIface =
593 std::make_unique<MockDataInterface>();
594
595 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
596
597 Manager manager{logManager, std::move(dataIface),
598 std::bind_front(&TestLogger::log, &logger),
599 std::move(journal)};
600
601 {
602 std::map<std::string, std::string> additionalData{{"ESEL", esel}};
603 std::vector<std::string> associations;
604
605 manager.create("error message", 37, 0, Level::Error, additionalData,
606 associations);
607
608 auto data = manager.getPELFromOBMCID(37);
609 PEL pel{data};
610 EXPECT_TRUE(pel.valid());
611 }
612
613 // Now an invalid one
614 {
615 std::string adItem = esel;
616
617 // Crop it
618 adItem.resize(adItem.size() - 300);
619
620 std::map<std::string, std::string> additionalData{{"ESEL", adItem}};
621 std::vector<std::string> associations;
622
623 manager.create("error message", 38, 0, Level::Error, additionalData,
624 associations);
625
626 EXPECT_THROW(
627 manager.getPELFromOBMCID(38),
628 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
629
630 // Run the event loop to log the bad PEL event
631 sdeventplus::Event e{sdEvent};
632 e.run(std::chrono::milliseconds(1));
633
634 EXPECT_EQ(logger.errName, "org.open_power.Logging.Error.BadHostPEL");
635 EXPECT_EQ(logger.errLevel, Level::Error);
636 }
637 }
638
639 // Test that PELs will be pruned when necessary
TEST_F(ManagerTest,TestPruning)640 TEST_F(ManagerTest, TestPruning)
641 {
642 sdeventplus::Event e{sdEvent};
643
644 std::unique_ptr<DataInterfaceBase> dataIface =
645 std::make_unique<MockDataInterface>();
646
647 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
648
649 Manager manager{logManager, std::move(dataIface),
650 std::bind_front(&TestLogger::log, &logger),
651 std::move(journal)};
652
653 // Create 25 1000B (4096B on disk each, which is what is used for
654 // pruning) BMC non-informational PELs in the 100KB repository. After
655 // the 24th one, the repo will be 96% full and a prune should be
656 // triggered to remove all but 7 to get under 30% full. Then when the
657 // 25th is added there will be 8 left.
658
659 auto dir = makeTempDir();
660 for (int i = 1; i <= 25; i++)
661 {
662 auto data = pelFactory(42, 'O', 0x40, 0x8800, 1000);
663
664 fs::path pelFilename = dir / "rawpel";
665 std::ofstream pelFile{pelFilename};
666 pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
667 pelFile.close();
668
669 std::map<std::string, std::string> additionalData{
670 {"RAWPEL", pelFilename.string()}};
671 std::vector<std::string> associations;
672
673 manager.create("error message", 42, 0, Level::Error, additionalData,
674 associations);
675
676 // Simulate the code getting back to the event loop
677 // after each create.
678 e.run(std::chrono::milliseconds(1));
679
680 if (i < 24)
681 {
682 EXPECT_EQ(countPELsInRepo(), i);
683 }
684 else if (i == 24)
685 {
686 // Prune occured
687 EXPECT_EQ(countPELsInRepo(), 7);
688 }
689 else // i == 25
690 {
691 EXPECT_EQ(countPELsInRepo(), 8);
692 }
693 }
694
695 try
696 {
697 // Make sure the 8 newest ones are still found.
698 for (uint32_t i = 0; i < 8; i++)
699 {
700 manager.getPEL(0x50000012 + i);
701 }
702 }
703 catch (
704 const sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument&
705 ex)
706 {
707 ADD_FAILURE() << "PELs should have all been found";
708 }
709 }
710
711 // Test that manually deleting a PEL file will be recognized by the code.
TEST_F(ManagerTest,TestPELManualDelete)712 TEST_F(ManagerTest, TestPELManualDelete)
713 {
714 sdeventplus::Event e{sdEvent};
715
716 std::unique_ptr<DataInterfaceBase> dataIface =
717 std::make_unique<MockDataInterface>();
718
719 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
720
721 Manager manager{logManager, std::move(dataIface),
722 std::bind_front(&TestLogger::log, &logger),
723 std::move(journal)};
724
725 auto data = pelDataFactory(TestPELType::pelSimple);
726 auto dir = makeTempDir();
727 fs::path pelFilename = dir / "rawpel";
728
729 std::map<std::string, std::string> additionalData{
730 {"RAWPEL", pelFilename.string()}};
731 std::vector<std::string> associations;
732
733 // Add 20 PELs, they will get incrementing IDs like
734 // 0x50000001, 0x50000002, etc.
735 for (int i = 1; i <= 20; i++)
736 {
737 std::ofstream pelFile{pelFilename};
738 pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
739 pelFile.close();
740
741 manager.create("error message", 42, 0, Level::Error, additionalData,
742 associations);
743
744 // Sanity check this ID is really there so we can test
745 // it was deleted later. This will throw an exception if
746 // not present.
747 manager.getPEL(0x50000000 + i);
748
749 // Run an event loop pass where the internal FD is deleted
750 // after the getPEL function call.
751 e.run(std::chrono::milliseconds(1));
752 }
753
754 EXPECT_EQ(countPELsInRepo(), 20);
755
756 deletePELFile(0x50000001);
757
758 // Run a single event loop pass so the inotify event can run
759 e.run(std::chrono::milliseconds(1));
760
761 EXPECT_EQ(countPELsInRepo(), 19);
762
763 EXPECT_THROW(
764 manager.getPEL(0x50000001),
765 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
766
767 // Delete a few more, they should all get handled in the same
768 // event loop pass
769 std::vector<uint32_t> toDelete{0x50000002, 0x50000003, 0x50000004,
770 0x50000005, 0x50000006};
771 std::for_each(toDelete.begin(), toDelete.end(),
772 [](auto i) { deletePELFile(i); });
773
774 e.run(std::chrono::milliseconds(1));
775
776 EXPECT_EQ(countPELsInRepo(), 14);
777
778 std::for_each(toDelete.begin(), toDelete.end(), [&manager](const auto i) {
779 EXPECT_THROW(
780 manager.getPEL(i),
781 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
782 });
783 }
784
785 // Test that deleting all PELs at once is handled OK.
TEST_F(ManagerTest,TestPELManualDeleteAll)786 TEST_F(ManagerTest, TestPELManualDeleteAll)
787 {
788 sdeventplus::Event e{sdEvent};
789
790 std::unique_ptr<DataInterfaceBase> dataIface =
791 std::make_unique<MockDataInterface>();
792
793 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
794
795 Manager manager{logManager, std::move(dataIface),
796 std::bind_front(&TestLogger::log, &logger),
797 std::move(journal)};
798
799 auto data = pelDataFactory(TestPELType::pelSimple);
800 auto dir = makeTempDir();
801 fs::path pelFilename = dir / "rawpel";
802
803 std::map<std::string, std::string> additionalData{
804 {"RAWPEL", pelFilename.string()}};
805 std::vector<std::string> associations;
806
807 // Add 200 PELs, they will get incrementing IDs like
808 // 0x50000001, 0x50000002, etc.
809 for (int i = 1; i <= 200; i++)
810 {
811 std::ofstream pelFile{pelFilename};
812 pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
813 pelFile.close();
814
815 manager.create("error message", 42, 0, Level::Error, additionalData,
816 associations);
817
818 // Sanity check this ID is really there so we can test
819 // it was deleted later. This will throw an exception if
820 // not present.
821 manager.getPEL(0x50000000 + i);
822
823 // Run an event loop pass where the internal FD is deleted
824 // after the getPEL function call.
825 e.run(std::chrono::milliseconds(1));
826 }
827
828 // Delete them all at once
829 auto logPath = getPELRepoPath() / "logs";
830 std::string cmd = "rm " + logPath.string() + "/*_*";
831
832 {
833 auto rc = system(cmd.c_str());
834 EXPECT_EQ(rc, 0);
835 }
836
837 EXPECT_EQ(countPELsInRepo(), 0);
838
839 // It will take 5 event loop passes to process them all
840 for (int i = 0; i < 5; i++)
841 {
842 e.run(std::chrono::milliseconds(1));
843 }
844
845 for (int i = 1; i <= 200; i++)
846 {
847 EXPECT_THROW(
848 manager.getPEL(0x50000000 + i),
849 sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument);
850 }
851 }
852
853 // Test that fault LEDs are turned on when PELs are created
TEST_F(ManagerTest,TestServiceIndicators)854 TEST_F(ManagerTest, TestServiceIndicators)
855 {
856 std::unique_ptr<DataInterfaceBase> dataIface =
857 std::make_unique<MockDataInterface>();
858
859 MockDataInterface* mockIface =
860 reinterpret_cast<MockDataInterface*>(dataIface.get());
861
862 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
863
864 Manager manager{logManager, std::move(dataIface),
865 std::bind_front(&TestLogger::log, &logger),
866 std::move(journal)};
867
868 // Add a PEL with a callout as if hostboot added it
869 {
870 EXPECT_CALL(*mockIface, getInventoryFromLocCode("U42", 0, true))
871 .WillOnce(
872 Return(std::vector<std::string>{"/system/chassis/processor"}));
873
874 EXPECT_CALL(*mockIface,
875 setFunctional("/system/chassis/processor", false))
876 .Times(1);
877
878 // This hostboot PEL has a single hardware callout in it.
879 auto data = pelFactory(1, 'B', 0x20, 0xA400, 500);
880
881 fs::path pelFilename = makeTempDir() / "rawpel";
882 std::ofstream pelFile{pelFilename};
883 pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
884 pelFile.close();
885
886 std::map<std::string, std::string> additionalData{
887 {"RAWPEL", pelFilename.string()}};
888 std::vector<std::string> associations;
889
890 manager.create("error message", 42, 0, Level::Error, additionalData,
891 associations);
892 }
893
894 // Add a BMC PEL with a callout that uses the message registry
895 {
896 std::vector<std::string> names{"systemA"};
897 EXPECT_CALL(*mockIface, getSystemNames)
898 .Times(1)
899 .WillOnce(Return(names));
900
901 EXPECT_CALL(*mockIface, expandLocationCode("P42-C23", 0))
902 .WillOnce(Return("U42-P42-C23"));
903
904 // First call to this is when building the Callout section
905 EXPECT_CALL(*mockIface, getInventoryFromLocCode("P42-C23", 0, false))
906 .WillOnce(
907 Return(std::vector<std::string>{"/system/chassis/processor"}));
908
909 // Second call to this is finding the associated LED group
910 EXPECT_CALL(*mockIface, getInventoryFromLocCode("U42-P42-C23", 0, true))
911 .WillOnce(
912 Return(std::vector<std::string>{"/system/chassis/processor"}));
913
914 EXPECT_CALL(*mockIface,
915 setFunctional("/system/chassis/processor", false))
916 .Times(1);
917
918 const auto registry = R"(
919 {
920 "PELs":
921 [
922 {
923 "Name": "xyz.openbmc_project.Error.Test",
924 "Subsystem": "power_supply",
925 "ActionFlags": ["service_action", "report"],
926 "SRC":
927 {
928 "ReasonCode": "0x2030"
929 },
930 "Callouts": [
931 {
932 "CalloutList": [
933 {"Priority": "high", "LocCode": "P42-C23"}
934 ]
935 }
936 ],
937 "Documentation":
938 {
939 "Description": "Test Error",
940 "Message": "Test Error"
941 }
942 }
943 ]
944 })";
945
946 auto path = getPELReadOnlyDataPath();
947 fs::create_directories(path);
948 path /= "message_registry.json";
949
950 std::ofstream registryFile{path};
951 registryFile << registry;
952 registryFile.close();
953
954 std::map<std::string, std::string> additionalData;
955 std::vector<std::string> associations;
956
957 manager.create("xyz.openbmc_project.Error.Test", 42, 0, Level::Error,
958 additionalData, associations);
959 }
960 }
961
962 // Test for duplicate PELs moved to archive folder
TEST_F(ManagerTest,TestDuplicatePEL)963 TEST_F(ManagerTest, TestDuplicatePEL)
964 {
965 sdeventplus::Event e{sdEvent};
966 size_t count = 0;
967
968 std::unique_ptr<DataInterfaceBase> dataIface =
969 std::make_unique<MockDataInterface>();
970
971 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
972
973 Manager manager{logManager, std::move(dataIface),
974 std::bind_front(&TestLogger::log, &logger),
975 std::move(journal)};
976
977 for (int i = 0; i < 2; i++)
978 {
979 // This hostboot PEL has a single hardware callout in it.
980 auto data = pelFactory(1, 'B', 0x20, 0xA400, 500);
981
982 fs::path pelFilename = makeTempDir() / "rawpel";
983 std::ofstream pelFile{pelFilename};
984 pelFile.write(reinterpret_cast<const char*>(data.data()), data.size());
985 pelFile.close();
986
987 std::map<std::string, std::string> additionalData{
988 {"RAWPEL", pelFilename.string()}};
989 std::vector<std::string> associations;
990
991 manager.create("error message", 42, 0, Level::Error, additionalData,
992 associations);
993
994 e.run(std::chrono::milliseconds(1));
995 }
996
997 for (auto& f :
998 fs::directory_iterator(getPELRepoPath() / "logs" / "archive"))
999 {
1000 if (fs::is_regular_file(f.path()))
1001 {
1002 count++;
1003 }
1004 }
1005
1006 // Get count of PELs in the repository & in archive directtory
1007 EXPECT_EQ(countPELsInRepo(), 1);
1008 EXPECT_EQ(count, 1);
1009 }
1010
1011 // Test termination bit set for pel with critical system termination
1012 // severity
TEST_F(ManagerTest,TestTerminateBitWithPELSevCriticalSysTerminate)1013 TEST_F(ManagerTest, TestTerminateBitWithPELSevCriticalSysTerminate)
1014 {
1015 const auto registry = R"(
1016 {
1017 "PELs":
1018 [
1019 {
1020 "Name": "xyz.openbmc_project.Error.Test",
1021 "Subsystem": "power_supply",
1022 "Severity": "critical_system_term",
1023 "ActionFlags": ["service_action", "report"],
1024 "SRC":
1025 {
1026 "ReasonCode": "0x2030"
1027 },
1028 "Documentation":
1029 {
1030 "Description": "A PGOOD Fault",
1031 "Message": "PS had a PGOOD Fault"
1032 }
1033 }
1034 ]
1035 }
1036 )";
1037
1038 auto path = getPELReadOnlyDataPath();
1039 fs::create_directories(path);
1040 path /= "message_registry.json";
1041
1042 std::ofstream registryFile{path};
1043 registryFile << registry;
1044 registryFile.close();
1045
1046 std::unique_ptr<DataInterfaceBase> dataIface =
1047 std::make_unique<MockDataInterface>();
1048
1049 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
1050
1051 Manager manager{logManager, std::move(dataIface),
1052 std::bind_front(&TestLogger::log, &logger),
1053 std::move(journal)};
1054
1055 std::map<std::string, std::string> additionalData{{"FOO", "BAR"}};
1056 std::vector<std::string> associations;
1057
1058 // Create the event log to create the PEL from.
1059 manager.create("xyz.openbmc_project.Error.Test", 33, 0, Level::Error,
1060 additionalData, associations);
1061
1062 // Ensure a PEL was created in the repository
1063 auto pelData = findAnyPELInRepo();
1064 ASSERT_TRUE(pelData);
1065
1066 auto getPELData = readPELFile(*pelData);
1067 PEL pel(*getPELData);
1068
1069 // Spot check it. Other testcases cover the details.
1070 EXPECT_TRUE(pel.valid());
1071
1072 // Check for terminate bit set
1073 auto& hexwords = pel.primarySRC().value()->hexwordData();
1074 EXPECT_EQ(hexwords[3] & 0x20000000, 0x20000000);
1075 }
1076
TEST_F(ManagerTest,TestSanitizeFieldforDBus)1077 TEST_F(ManagerTest, TestSanitizeFieldforDBus)
1078 {
1079 std::string base{"(test0!}\n\t ~"};
1080 auto string = base;
1081 string += char{' ' - 1};
1082 string += char{'~' + 1};
1083 string += char{0};
1084 string += char{static_cast<char>(0xFF)};
1085
1086 // convert the last four chars to spaces
1087 EXPECT_EQ(Manager::sanitizeFieldForDBus(string), base + " ");
1088 }
1089
TEST_F(ManagerTest,TestFruPlug)1090 TEST_F(ManagerTest, TestFruPlug)
1091 {
1092 const auto registry = R"(
1093 {
1094 "PELs":
1095 [{
1096 "Name": "xyz.openbmc_project.Fan.Error.Fault",
1097 "Subsystem": "power_fans",
1098 "ComponentID": "0x2800",
1099 "SRC":
1100 {
1101 "Type": "11",
1102 "ReasonCode": "0x76F0",
1103 "Words6To9": {},
1104 "DeconfigFlag": true
1105 },
1106 "Callouts": [{
1107 "CalloutList": [
1108 {"Priority": "low", "LocCode": "P0"},
1109 {"Priority": "high", "LocCode": "A3"}
1110 ]
1111 }],
1112 "Documentation": {
1113 "Description": "A Fan Fault",
1114 "Message": "Fan had a Fault"
1115 }
1116 }]
1117 }
1118 )";
1119
1120 auto path = getPELReadOnlyDataPath();
1121 fs::create_directories(path);
1122 path /= "message_registry.json";
1123
1124 std::ofstream registryFile{path};
1125 registryFile << registry;
1126 registryFile.close();
1127
1128 std::unique_ptr<DataInterfaceBase> dataIface =
1129 std::make_unique<MockDataInterface>();
1130
1131 MockDataInterface* mockIface =
1132 reinterpret_cast<MockDataInterface*>(dataIface.get());
1133
1134 // Set up the mock calls used when building callouts
1135 EXPECT_CALL(*mockIface, getInventoryFromLocCode("P0", 0, false))
1136 .WillRepeatedly(Return(std::vector<std::string>{"motherboard"}));
1137 EXPECT_CALL(*mockIface, expandLocationCode("P0", 0))
1138 .WillRepeatedly(Return("U1234-P0"));
1139 EXPECT_CALL(*mockIface, getInventoryFromLocCode("U1234-P0", 0, true))
1140 .WillRepeatedly(Return(std::vector<std::string>{"motherboard"}));
1141
1142 EXPECT_CALL(*mockIface, getInventoryFromLocCode("A3", 0, false))
1143 .WillRepeatedly(Return(std::vector<std::string>{"fan"}));
1144 EXPECT_CALL(*mockIface, expandLocationCode("A3", 0))
1145 .WillRepeatedly(Return("U1234-A3"));
1146 EXPECT_CALL(*mockIface, getInventoryFromLocCode("U1234-A3", 0, true))
1147 .WillRepeatedly(Return(std::vector<std::string>{"fan"}));
1148
1149 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
1150
1151 Manager manager{logManager, std::move(dataIface),
1152 std::bind_front(&TestLogger::log, &logger),
1153 std::move(journal)};
1154
1155 std::map<std::string, std::string> additionalData;
1156 std::vector<std::string> associations;
1157
1158 auto checkDeconfigured = [](bool deconfigured) {
1159 auto pelFile = findAnyPELInRepo();
1160 ASSERT_TRUE(pelFile);
1161
1162 auto data = readPELFile(*pelFile);
1163 PEL pel(*data);
1164 ASSERT_TRUE(pel.valid());
1165
1166 EXPECT_EQ(pel.primarySRC().value()->getErrorStatusFlag(
1167 SRC::ErrorStatusFlags::deconfigured),
1168 deconfigured);
1169 };
1170
1171 manager.create("xyz.openbmc_project.Fan.Error.Fault", 42, 0, Level::Error,
1172 additionalData, associations);
1173 checkDeconfigured(true);
1174
1175 // Replace A3 so PEL deconfigured flag should be set to false
1176 mockIface->fruPresent("U1234-A3");
1177 checkDeconfigured(false);
1178
1179 manager.erase(42);
1180
1181 // Create it again and replace a FRU not in the callout list.
1182 // Deconfig flag should stay on.
1183 manager.create("xyz.openbmc_project.Fan.Error.Fault", 43, 0, Level::Error,
1184 additionalData, associations);
1185 checkDeconfigured(true);
1186 mockIface->fruPresent("U1234-A4");
1187 checkDeconfigured(true);
1188 }
1189
createHWIsolatedCalloutFile()1190 std::pair<int, std::filesystem::path> createHWIsolatedCalloutFile()
1191 {
1192 json jsonCalloutDataList(nlohmann::json::value_t::array);
1193 json jsonDimmCallout;
1194
1195 jsonDimmCallout["LocationCode"] = "Ufcs-DIMM0";
1196 jsonDimmCallout["EntityPath"] = {35, 1, 0, 2, 0, 3, 0, 0, 0, 0, 0,
1197 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
1198 jsonDimmCallout["GuardType"] = "GARD_Predictive";
1199 jsonDimmCallout["Deconfigured"] = false;
1200 jsonDimmCallout["Guarded"] = true;
1201 jsonDimmCallout["Priority"] = "M";
1202 jsonCalloutDataList.emplace_back(std::move(jsonDimmCallout));
1203
1204 std::string calloutData(jsonCalloutDataList.dump());
1205 std::string calloutFile("/tmp/phalPELCalloutsJson.XXXXXX");
1206 int fileFD = -1;
1207
1208 fileFD = mkostemp(calloutFile.data(), O_RDWR);
1209 if (fileFD == -1)
1210 {
1211 perror("Failed to create PELCallouts file");
1212 return {-1, {}};
1213 }
1214
1215 ssize_t rc = write(fileFD, calloutData.c_str(), calloutData.size());
1216 if (rc == -1)
1217 {
1218 perror("Failed to write PELCallouts file");
1219 close(fileFD);
1220 return {-1, {}};
1221 }
1222
1223 // Ensure we seek to the beginning of the file
1224 rc = lseek(fileFD, 0, SEEK_SET);
1225 if (rc == -1)
1226 {
1227 perror("Failed to set SEEK_SET for PELCallouts file");
1228 close(fileFD);
1229 return {-1, {}};
1230 }
1231 return {fileFD, calloutFile};
1232 }
1233
appendFFDCEntry(int fd,uint8_t subTypeJson,uint8_t version,phosphor::logging::FFDCEntries & ffdcEntries)1234 void appendFFDCEntry(int fd, uint8_t subTypeJson, uint8_t version,
1235 phosphor::logging::FFDCEntries& ffdcEntries)
1236 {
1237 phosphor::logging::FFDCEntry ffdcEntry =
1238 std::make_tuple(sdbusplus::xyz::openbmc_project::Logging::server::
1239 Create::FFDCFormat::JSON,
1240 subTypeJson, version, fd);
1241 ffdcEntries.push_back(ffdcEntry);
1242 }
1243
TEST_F(ManagerTest,TestPELDeleteWithoutHWIsolation)1244 TEST_F(ManagerTest, TestPELDeleteWithoutHWIsolation)
1245 {
1246 const auto registry = R"(
1247 {
1248 "PELs":
1249 [{
1250 "Name": "xyz.openbmc_project.Error.Test",
1251 "SRC":
1252 {
1253 "ReasonCode": "0x2030"
1254 },
1255 "Documentation": {
1256 "Description": "Test Error",
1257 "Message": "Test Error"
1258 }
1259 }]
1260 }
1261 )";
1262
1263 auto path = getPELReadOnlyDataPath();
1264 fs::create_directories(path);
1265 path /= "message_registry.json";
1266
1267 std::ofstream registryFile{path};
1268 registryFile << registry;
1269 registryFile.close();
1270
1271 std::unique_ptr<DataInterfaceBase> dataIface =
1272 std::make_unique<MockDataInterface>();
1273
1274 MockDataInterface* mockIface =
1275 reinterpret_cast<MockDataInterface*>(dataIface.get());
1276
1277 EXPECT_CALL(*mockIface, getInventoryFromLocCode("Ufcs-DIMM0", 0, false))
1278 .WillOnce(Return(std::vector<std::string>{
1279 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"}));
1280
1281 // Mock the scenario where the hardware isolation guard is flagged
1282 // but is not associated, resulting in an empty list being returned.
1283 EXPECT_CALL(
1284 *mockIface,
1285 getAssociatedPaths(
1286 ::testing::StrEq(
1287 "/xyz/openbmc_project/logging/entry/42/isolated_hw_entry"),
1288 ::testing::StrEq("/"), 0,
1289 ::testing::ElementsAre(
1290 "xyz.openbmc_project.HardwareIsolation.Entry")))
1291 .WillRepeatedly(Return(std::vector<std::string>{}));
1292
1293 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
1294 Manager manager{logManager, std::move(dataIface),
1295 std::bind_front(&TestLogger::log, &logger),
1296 std::move(journal)};
1297 std::map<std::string, std::string> additionalData;
1298 std::vector<std::string> associations;
1299
1300 // Check when there's no PEL with given id.
1301 {
1302 EXPECT_FALSE(manager.isDeleteProhibited(42));
1303 }
1304 // creating without ffdcEntries
1305 manager.create("xyz.openbmc_project.Error.Test", 42, 0, Level::Error,
1306 additionalData, associations);
1307 auto pelFile = findAnyPELInRepo();
1308 auto data = readPELFile(*pelFile);
1309 PEL pel_unguarded(*data);
1310 {
1311 // Verify that the guard flag is false.
1312 EXPECT_FALSE(pel_unguarded.getGuardFlag());
1313 // Check that `isDeleteProhibited` returns false when the guard flag
1314 // is false.
1315 EXPECT_FALSE(manager.isDeleteProhibited(42));
1316 }
1317 manager.erase(42);
1318 EXPECT_FALSE(findAnyPELInRepo());
1319
1320 auto [fd, calloutFile] = createHWIsolatedCalloutFile();
1321 ASSERT_NE(fd, -1);
1322 uint8_t subTypeJson = 0xCA;
1323 uint8_t version = 0x01;
1324 phosphor::logging::FFDCEntries ffdcEntries;
1325 appendFFDCEntry(fd, subTypeJson, version, ffdcEntries);
1326 manager.create("xyz.openbmc_project.Error.Test", 42, 0, Level::Error,
1327 additionalData, associations, ffdcEntries);
1328 close(fd);
1329 std::filesystem::remove(calloutFile);
1330
1331 auto pelPathInRepo = findAnyPELInRepo();
1332 auto unguardedData = readPELFile(*pelPathInRepo);
1333 PEL pel(*unguardedData);
1334 {
1335 // Verify guard flag set to true
1336 EXPECT_TRUE(pel.getGuardFlag());
1337 // Check even if guard flag is true, if dbus call returns empty
1338 // array list then `isDeleteProhibited` returns false
1339 EXPECT_FALSE(manager.isDeleteProhibited(42));
1340 }
1341 manager.erase(42);
1342 }
1343
TEST_F(ManagerTest,TestPELDeleteWithHWIsolation)1344 TEST_F(ManagerTest, TestPELDeleteWithHWIsolation)
1345 {
1346 const auto registry = R"(
1347 {
1348 "PELs":
1349 [{
1350 "Name": "xyz.openbmc_project.Error.Test",
1351 "Severity": "critical_system_term",
1352 "SRC":
1353 {
1354 "ReasonCode": "0x2030"
1355 },
1356 "Documentation": {
1357 "Description": "Test Error",
1358 "Message": "Test Error"
1359 }
1360 }]
1361 }
1362 )";
1363
1364 auto path = getPELReadOnlyDataPath();
1365 fs::create_directories(path);
1366 path /= "message_registry.json";
1367
1368 std::ofstream registryFile{path};
1369 registryFile << registry;
1370 registryFile.close();
1371
1372 std::unique_ptr<DataInterfaceBase> dataIface =
1373 std::make_unique<MockDataInterface>();
1374
1375 MockDataInterface* mockIface =
1376 reinterpret_cast<MockDataInterface*>(dataIface.get());
1377
1378 EXPECT_CALL(*mockIface, getInventoryFromLocCode("Ufcs-DIMM0", 0, false))
1379 .WillOnce(Return(std::vector<std::string>{
1380 "/xyz/openbmc_project/inventory/system/chassis/motherboard/dimm0"}));
1381
1382 EXPECT_CALL(
1383 *mockIface,
1384 getAssociatedPaths(
1385 ::testing::StrEq(
1386 "/xyz/openbmc_project/logging/entry/42/isolated_hw_entry"),
1387 ::testing::StrEq("/"), 0,
1388 ::testing::ElementsAre(
1389 "xyz.openbmc_project.HardwareIsolation.Entry")))
1390 .WillRepeatedly(Return(std::vector<std::string>{
1391 "/xyz/openbmc_project/hardware_isolation/entry/1"}));
1392
1393 std::unique_ptr<JournalBase> journal = std::make_unique<MockJournal>();
1394 Manager manager{logManager, std::move(dataIface),
1395 std::bind_front(&TestLogger::log, &logger),
1396 std::move(journal)};
1397 std::map<std::string, std::string> additionalData;
1398 std::vector<std::string> associations;
1399
1400 auto [fd, calloutFile] = createHWIsolatedCalloutFile();
1401 ASSERT_NE(fd, -1);
1402 uint8_t subTypeJson = 0xCA;
1403 uint8_t version = 0x01;
1404 phosphor::logging::FFDCEntries ffdcEntries;
1405 appendFFDCEntry(fd, subTypeJson, version, ffdcEntries);
1406 manager.create("xyz.openbmc_project.Error.Test", 42, 0, Level::Error,
1407 additionalData, associations, ffdcEntries);
1408 close(fd);
1409 std::filesystem::remove(calloutFile);
1410
1411 auto pelFile = findAnyPELInRepo();
1412 EXPECT_TRUE(pelFile);
1413 auto data = readPELFile(*pelFile);
1414 PEL pel(*data);
1415 EXPECT_TRUE(pel.valid());
1416 // Test case where the guard flag is set to true and the hardware
1417 // isolation guard is associated, which should result in
1418 // `isDeleteProhibited` returning true as expected.
1419 EXPECT_TRUE(pel.getGuardFlag());
1420 EXPECT_TRUE(manager.isDeleteProhibited(42));
1421 manager.erase(42);
1422 }
1423