| /** |
| * Copyright © 2019 IBM Corporation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| #include "extensions/openpower-pels/manager.hpp" |
| #include "log_manager.hpp" |
| #include "pel_utils.hpp" |
| |
| #include <fstream> |
| #include <regex> |
| #include <xyz/openbmc_project/Common/error.hpp> |
| |
| #include <gtest/gtest.h> |
| |
| using namespace openpower::pels; |
| namespace fs = std::filesystem; |
| |
| class TestLogger |
| { |
| public: |
| void log(const std::string& name, phosphor::logging::Entry::Level level, |
| const EventLogger::ADMap& additionalData) |
| { |
| errName = name; |
| errLevel = level; |
| ad = additionalData; |
| } |
| |
| std::string errName; |
| phosphor::logging::Entry::Level errLevel; |
| EventLogger::ADMap ad; |
| }; |
| |
| class ManagerTest : public CleanPELFiles |
| { |
| public: |
| ManagerTest() : logManager(bus, "logging_path") |
| { |
| sd_event_default(&sdEvent); |
| bus.attach_event(sdEvent, SD_EVENT_PRIORITY_NORMAL); |
| } |
| |
| ~ManagerTest() |
| { |
| sd_event_unref(sdEvent); |
| } |
| |
| sdbusplus::bus::bus bus = sdbusplus::bus::new_default(); |
| phosphor::logging::internal::Manager logManager; |
| sd_event* sdEvent; |
| TestLogger logger; |
| }; |
| |
| fs::path makeTempDir() |
| { |
| char path[] = "/tmp/tempnameXXXXXX"; |
| std::filesystem::path dir = mkdtemp(path); |
| return dir; |
| } |
| |
| std::optional<fs::path> findAnyPELInRepo() |
| { |
| // PELs are named <timestamp>_<ID> |
| std::regex expr{"\\d+_\\d+"}; |
| |
| for (auto& f : fs::directory_iterator(getPELRepoPath() / "logs")) |
| { |
| if (std::regex_search(f.path().string(), expr)) |
| { |
| return f.path(); |
| } |
| } |
| return std::nullopt; |
| } |
| |
| // Test that using the RAWPEL=<file> with the Manager::create() call gets |
| // a PEL saved in the repository. |
| TEST_F(ManagerTest, TestCreateWithPEL) |
| { |
| std::unique_ptr<DataInterfaceBase> dataIface = |
| std::make_unique<DataInterface>(bus); |
| |
| openpower::pels::Manager manager{ |
| logManager, std::move(dataIface), |
| std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1, |
| std::placeholders::_2, std::placeholders::_3)}; |
| |
| // Create a PEL, write it to a file, and pass that filename into |
| // the create function. |
| auto data = pelDataFactory(TestPELType::pelSimple); |
| |
| fs::path pelFilename = makeTempDir() / "rawpel"; |
| std::ofstream pelFile{pelFilename}; |
| pelFile.write(reinterpret_cast<const char*>(data.data()), data.size()); |
| pelFile.close(); |
| |
| std::string adItem = "RAWPEL=" + pelFilename.string(); |
| std::vector<std::string> additionalData{adItem}; |
| std::vector<std::string> associations; |
| |
| manager.create("error message", 42, 0, |
| phosphor::logging::Entry::Level::Error, additionalData, |
| associations); |
| |
| // Find the file in the PEL repository directory |
| auto pelPathInRepo = findAnyPELInRepo(); |
| |
| EXPECT_TRUE(pelPathInRepo); |
| |
| // Now remove it based on its OpenBMC event log ID |
| manager.erase(42); |
| |
| pelPathInRepo = findAnyPELInRepo(); |
| |
| EXPECT_FALSE(pelPathInRepo); |
| |
| fs::remove_all(pelFilename.parent_path()); |
| } |
| |
| TEST_F(ManagerTest, TestCreateWithInvalidPEL) |
| { |
| std::unique_ptr<DataInterfaceBase> dataIface = |
| std::make_unique<DataInterface>(bus); |
| |
| openpower::pels::Manager manager{ |
| logManager, std::move(dataIface), |
| std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1, |
| std::placeholders::_2, std::placeholders::_3)}; |
| |
| // Create a PEL, write it to a file, and pass that filename into |
| // the create function. |
| auto data = pelDataFactory(TestPELType::pelSimple); |
| |
| // Truncate it to make it invalid. |
| data.resize(200); |
| |
| fs::path pelFilename = makeTempDir() / "rawpel"; |
| std::ofstream pelFile{pelFilename}; |
| pelFile.write(reinterpret_cast<const char*>(data.data()), data.size()); |
| pelFile.close(); |
| |
| std::string adItem = "RAWPEL=" + pelFilename.string(); |
| std::vector<std::string> additionalData{adItem}; |
| std::vector<std::string> associations; |
| |
| manager.create("error message", 42, 0, |
| phosphor::logging::Entry::Level::Error, additionalData, |
| associations); |
| |
| // Run the event loop to log the bad PEL event |
| sdeventplus::Event e{sdEvent}; |
| e.run(std::chrono::milliseconds(1)); |
| |
| PEL invalidPEL{data}; |
| EXPECT_EQ(logger.errName, "org.open_power.Logging.Error.BadHostPEL"); |
| EXPECT_EQ(logger.errLevel, phosphor::logging::Entry::Level::Error); |
| EXPECT_EQ(std::stoi(logger.ad["PLID"], nullptr, 16), invalidPEL.plid()); |
| EXPECT_EQ(logger.ad["OBMC_LOG_ID"], "42"); |
| EXPECT_EQ(logger.ad["SRC"], (*invalidPEL.primarySRC())->asciiString()); |
| EXPECT_EQ(logger.ad["PEL_SIZE"], std::to_string(data.size())); |
| |
| fs::remove_all(pelFilename.parent_path()); |
| } |
| |
| // Test that the message registry can be used to build a PEL. |
| TEST_F(ManagerTest, TestCreateWithMessageRegistry) |
| { |
| const auto registry = R"( |
| { |
| "PELs": |
| [ |
| { |
| "Name": "xyz.openbmc_project.Error.Test", |
| "Subsystem": "power_supply", |
| "ActionFlags": ["service_action", "report"], |
| "SRC": |
| { |
| "ReasonCode": "0x2030" |
| }, |
| "Documentation": |
| { |
| "Description": "A PGOOD Fault", |
| "Message": "PS had a PGOOD Fault" |
| } |
| } |
| ] |
| } |
| )"; |
| |
| auto path = getMessageRegistryPath(); |
| fs::create_directories(path); |
| path /= "message_registry.json"; |
| |
| std::ofstream registryFile{path}; |
| registryFile << registry; |
| registryFile.close(); |
| |
| std::unique_ptr<DataInterfaceBase> dataIface = |
| std::make_unique<DataInterface>(logManager.getBus()); |
| |
| openpower::pels::Manager manager{ |
| logManager, std::move(dataIface), |
| std::bind(std::mem_fn(&TestLogger::log), &logger, std::placeholders::_1, |
| std::placeholders::_2, std::placeholders::_3)}; |
| |
| std::vector<std::string> additionalData; |
| std::vector<std::string> associations; |
| |
| // Create the event log to create the PEL from. |
| manager.create("xyz.openbmc_project.Error.Test", 33, 0, |
| phosphor::logging::Entry::Level::Error, additionalData, |
| associations); |
| |
| // Ensure a PEL was created in the repository |
| auto pelFile = findAnyPELInRepo(); |
| ASSERT_TRUE(pelFile); |
| |
| auto data = readPELFile(*pelFile); |
| PEL pel(*data); |
| |
| // Spot check it. Other testcases cover the details. |
| EXPECT_TRUE(pel.valid()); |
| EXPECT_EQ(pel.obmcLogID(), 33); |
| EXPECT_EQ(pel.primarySRC().value()->asciiString(), |
| "BD612030 "); |
| |
| // Remove it |
| manager.erase(33); |
| pelFile = findAnyPELInRepo(); |
| EXPECT_FALSE(pelFile); |
| |
| // Create an event log that can't be found in the registry. |
| manager.create("xyz.openbmc_project.Error.Foo", 33, 0, |
| phosphor::logging::Entry::Level::Error, additionalData, |
| associations); |
| |
| // Currently, no PEL should be created. Eventually, a 'missing registry |
| // entry' PEL will be there. |
| pelFile = findAnyPELInRepo(); |
| EXPECT_FALSE(pelFile); |
| } |
| |
| TEST_F(ManagerTest, TestDBusMethods) |
| { |
| std::unique_ptr<DataInterfaceBase> dataIface = |
| std::make_unique<DataInterface>(bus); |
| |
| Manager manager{logManager, std::move(dataIface), |
| std::bind(std::mem_fn(&TestLogger::log), &logger, |
| std::placeholders::_1, std::placeholders::_2, |
| std::placeholders::_3)}; |
| |
| // Create a PEL, write it to a file, and pass that filename into |
| // the create function so there's one in the repo. |
| auto data = pelDataFactory(TestPELType::pelSimple); |
| |
| fs::path pelFilename = makeTempDir() / "rawpel"; |
| std::ofstream pelFile{pelFilename}; |
| pelFile.write(reinterpret_cast<const char*>(data.data()), data.size()); |
| pelFile.close(); |
| |
| std::string adItem = "RAWPEL=" + pelFilename.string(); |
| std::vector<std::string> additionalData{adItem}; |
| std::vector<std::string> associations; |
| |
| manager.create("error message", 42, 0, |
| phosphor::logging::Entry::Level::Error, additionalData, |
| associations); |
| |
| // getPELFromOBMCID |
| auto newData = manager.getPELFromOBMCID(42); |
| EXPECT_EQ(newData.size(), data.size()); |
| |
| // Read the PEL to get the ID for later |
| PEL pel{newData}; |
| auto id = pel.id(); |
| |
| EXPECT_THROW( |
| manager.getPELFromOBMCID(id + 1), |
| sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); |
| |
| // getPEL |
| auto unixfd = manager.getPEL(id); |
| |
| // Get the size |
| struct stat s; |
| int r = fstat(unixfd, &s); |
| ASSERT_EQ(r, 0); |
| auto size = s.st_size; |
| |
| // Open the FD and check the contents |
| FILE* fp = fdopen(unixfd, "r"); |
| ASSERT_NE(fp, nullptr); |
| |
| std::vector<uint8_t> fdData; |
| fdData.resize(size); |
| r = fread(fdData.data(), 1, size, fp); |
| EXPECT_EQ(r, size); |
| |
| EXPECT_EQ(newData, fdData); |
| |
| fclose(fp); |
| |
| // Run the event loop to close the FD |
| sdeventplus::Event e{sdEvent}; |
| e.run(std::chrono::milliseconds(1)); |
| |
| EXPECT_THROW( |
| manager.getPEL(id + 1), |
| sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); |
| |
| // hostAck |
| manager.hostAck(id); |
| |
| EXPECT_THROW( |
| manager.hostAck(id + 1), |
| sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); |
| |
| // hostReject |
| manager.hostReject(id, Manager::RejectionReason::BadPEL); |
| |
| // Run the event loop to log the bad PEL event |
| e.run(std::chrono::milliseconds(1)); |
| |
| EXPECT_EQ(logger.errName, "org.open_power.Logging.Error.SentBadPELToHost"); |
| EXPECT_EQ(id, std::stoi(logger.ad["BAD_ID"], nullptr, 16)); |
| |
| manager.hostReject(id, Manager::RejectionReason::HostFull); |
| |
| EXPECT_THROW( |
| manager.hostReject(id + 1, Manager::RejectionReason::BadPEL), |
| sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument); |
| |
| fs::remove_all(pelFilename.parent_path()); |
| } |