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