xref: /openbmc/phosphor-logging/test/openpower-pels/pel_manager_test.cpp (revision 4bec95da0d59160193fcb29f9b7a98fa2c7e0fa2)
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