pseq: Find config file using new compatible intf
Add support for finding the JSON configuration file using a list of
compatible names from the new
xyz.openbmc_project.Inventory.Decorator.Compatible D-Bus interface.
Change-Id: Ic24bafd1d77413015aa8eac7b312dbd604a10995
Signed-off-by: Shawn McCarney <shawnmm@us.ibm.com>
diff --git a/phosphor-power-sequencer/src/config_file_parser.cpp b/phosphor-power-sequencer/src/config_file_parser.cpp
index fe9c661..83754b1 100644
--- a/phosphor-power-sequencer/src/config_file_parser.cpp
+++ b/phosphor-power-sequencer/src/config_file_parser.cpp
@@ -23,10 +23,51 @@
#include <optional>
using json = nlohmann::json;
+namespace fs = std::filesystem;
namespace phosphor::power::sequencer::config_file_parser
{
+const std::filesystem::path standardConfigFileDirectory{
+ "/usr/share/phosphor-power-sequencer"};
+
+std::filesystem::path
+ find(const std::vector<std::string>& compatibleSystemTypes,
+ const std::filesystem::path& configFileDir)
+{
+ fs::path pathName, possiblePath;
+ std::string fileName;
+
+ for (const std::string& systemType : compatibleSystemTypes)
+ {
+ // Look for file name that is entire system type + ".json"
+ // Example: com.acme.Hardware.Chassis.Model.MegaServer.json
+ fileName = systemType + ".json";
+ possiblePath = configFileDir / fileName;
+ if (fs::is_regular_file(possiblePath))
+ {
+ pathName = possiblePath;
+ break;
+ }
+
+ // Look for file name that is last node of system type + ".json"
+ // Example: MegaServer.json
+ std::string::size_type pos = systemType.rfind('.');
+ if ((pos != std::string::npos) && ((systemType.size() - pos) > 1))
+ {
+ fileName = systemType.substr(pos + 1) + ".json";
+ possiblePath = configFileDir / fileName;
+ if (fs::is_regular_file(possiblePath))
+ {
+ pathName = possiblePath;
+ break;
+ }
+ }
+ }
+
+ return pathName;
+}
+
std::vector<std::unique_ptr<Rail>> parse(const std::filesystem::path& pathName)
{
try
diff --git a/phosphor-power-sequencer/src/config_file_parser.hpp b/phosphor-power-sequencer/src/config_file_parser.hpp
index dbd41d7..77d9650 100644
--- a/phosphor-power-sequencer/src/config_file_parser.hpp
+++ b/phosphor-power-sequencer/src/config_file_parser.hpp
@@ -30,6 +30,36 @@
{
/**
+ * Standard JSON configuration file directory on the BMC.
+ */
+extern const std::filesystem::path standardConfigFileDirectory;
+
+/**
+ * Finds the JSON configuration file for the current system based on the
+ * specified compatible system types.
+ *
+ * This is required when a single BMC firmware image supports multiple system
+ * types and some system types require different configuration files.
+ *
+ * The compatible system types must be ordered from most to least specific.
+ * Example:
+ * - com.acme.Hardware.Chassis.Model.MegaServer4CPU
+ * - com.acme.Hardware.Chassis.Model.MegaServer
+ * - com.acme.Hardware.Chassis.Model.Server
+ *
+ * Throws an exception if an error occurs.
+ *
+ * @param compatibleSystemTypes compatible system types for the current system
+ * ordered from most to least specific
+ * @param configFileDir directory containing configuration files
+ * @return path to the JSON configuration file, or an empty path if none was
+ * found
+ */
+std::filesystem::path find(
+ const std::vector<std::string>& compatibleSystemTypes,
+ const std::filesystem::path& configFileDir = standardConfigFileDirectory);
+
+/**
* Parses the specified JSON configuration file.
*
* Returns the corresponding C++ Rail objects.
diff --git a/phosphor-power-sequencer/test/config_file_parser_tests.cpp b/phosphor-power-sequencer/test/config_file_parser_tests.cpp
index f28d827..6f8a878 100644
--- a/phosphor-power-sequencer/test/config_file_parser_tests.cpp
+++ b/phosphor-power-sequencer/test/config_file_parser_tests.cpp
@@ -17,6 +17,7 @@
#include "config_file_parser_error.hpp"
#include "rail.hpp"
#include "temporary_file.hpp"
+#include "temporary_subdirectory.hpp"
#include <sys/stat.h> // for chmod()
@@ -37,23 +38,209 @@
using namespace phosphor::power::sequencer;
using namespace phosphor::power::sequencer::config_file_parser;
using namespace phosphor::power::sequencer::config_file_parser::internal;
+using namespace phosphor::power::util;
using json = nlohmann::json;
-using TemporaryFile = phosphor::power::util::TemporaryFile;
+namespace fs = std::filesystem;
-void writeConfigFile(const std::filesystem::path& pathName,
- const std::string& contents)
+void writeConfigFile(const fs::path& pathName, const std::string& contents)
{
std::ofstream file{pathName};
file << contents;
}
-void writeConfigFile(const std::filesystem::path& pathName,
- const json& contents)
+void writeConfigFile(const fs::path& pathName, const json& contents)
{
std::ofstream file{pathName};
file << contents;
}
+TEST(ConfigFileParserTests, Find)
+{
+ std::vector<std::string> compatibleSystemTypes{
+ "com.acme.Hardware.Chassis.Model.MegaServer4CPU",
+ "com.acme.Hardware.Chassis.Model.MegaServer",
+ "com.acme.Hardware.Chassis.Model.Server"};
+
+ // Test where works: Fully qualified system type: First in list
+ {
+ TemporarySubDirectory configFileDir;
+ fs::path configFileDirPath = configFileDir.getPath();
+
+ fs::path configFilePath = configFileDirPath;
+ configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer4CPU.json";
+ writeConfigFile(configFilePath, std::string{""});
+
+ fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
+ EXPECT_EQ(pathFound, configFilePath);
+ }
+
+ // Test where works: Fully qualified system type: Second in list
+ {
+ TemporarySubDirectory configFileDir;
+ fs::path configFileDirPath = configFileDir.getPath();
+
+ fs::path configFilePath = configFileDirPath;
+ configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer.json";
+ writeConfigFile(configFilePath, std::string{""});
+
+ fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
+ EXPECT_EQ(pathFound, configFilePath);
+ }
+
+ // Test where works: Last node in system type: Second in list
+ {
+ TemporarySubDirectory configFileDir;
+ fs::path configFileDirPath = configFileDir.getPath();
+
+ fs::path configFilePath = configFileDirPath;
+ configFilePath /= "MegaServer.json";
+ writeConfigFile(configFilePath, std::string{""});
+
+ fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
+ EXPECT_EQ(pathFound, configFilePath);
+ }
+
+ // Test where works: Last node in system type: Last in list
+ {
+ TemporarySubDirectory configFileDir;
+ fs::path configFileDirPath = configFileDir.getPath();
+
+ fs::path configFilePath = configFileDirPath;
+ configFilePath /= "Server.json";
+ writeConfigFile(configFilePath, std::string{""});
+
+ fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
+ EXPECT_EQ(pathFound, configFilePath);
+ }
+
+ // Test where works: System type has no '.'
+ {
+ TemporarySubDirectory configFileDir;
+ fs::path configFileDirPath = configFileDir.getPath();
+
+ fs::path configFilePath = configFileDirPath;
+ configFilePath /= "Server.json";
+ writeConfigFile(configFilePath, std::string{""});
+
+ std::vector<std::string> noDotSystemTypes{"MegaServer4CPU",
+ "MegaServer", "Server"};
+ fs::path pathFound = find(noDotSystemTypes, configFileDirPath);
+ EXPECT_EQ(pathFound, configFilePath);
+ }
+
+ // Test where fails: System type list is empty
+ {
+ TemporarySubDirectory configFileDir;
+ fs::path configFileDirPath = configFileDir.getPath();
+
+ fs::path configFilePath = configFileDirPath;
+ configFilePath /= "Server.json";
+ writeConfigFile(configFilePath, std::string{""});
+
+ std::vector<std::string> emptySystemTypes{};
+ fs::path pathFound = find(emptySystemTypes, configFileDirPath);
+ EXPECT_TRUE(pathFound.empty());
+ }
+
+ // Test where fails: Configuration file directory is empty
+ {
+ TemporarySubDirectory configFileDir;
+ fs::path configFileDirPath = configFileDir.getPath();
+
+ fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
+ EXPECT_TRUE(pathFound.empty());
+ }
+
+ // Test where fails: Configuration file directory does not exist
+ {
+ fs::path configFileDirPath{"/tmp/does_not_exist_XYZ"};
+
+ fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
+ EXPECT_TRUE(pathFound.empty());
+ }
+
+ // Test where fails: Configuration file directory is not readable
+ {
+ TemporarySubDirectory configFileDir;
+ fs::path configFileDirPath = configFileDir.getPath();
+ fs::permissions(configFileDirPath, fs::perms::none);
+
+ EXPECT_THROW(find(compatibleSystemTypes, configFileDirPath),
+ std::exception);
+
+ fs::permissions(configFileDirPath, fs::perms::owner_all);
+ }
+
+ // Test where fails: No matching file name found
+ {
+ TemporarySubDirectory configFileDir;
+ fs::path configFileDirPath = configFileDir.getPath();
+
+ fs::path configFilePath = configFileDirPath;
+ configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer";
+ writeConfigFile(configFilePath, std::string{""});
+
+ fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
+ EXPECT_TRUE(pathFound.empty());
+ }
+
+ // Test where fails: Matching file name is a directory: Fully qualified
+ {
+ TemporarySubDirectory configFileDir;
+ fs::path configFileDirPath = configFileDir.getPath();
+
+ fs::path configFilePath = configFileDirPath;
+ configFilePath /= "com.acme.Hardware.Chassis.Model.MegaServer4CPU.json";
+ fs::create_directory(configFilePath);
+
+ fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
+ EXPECT_TRUE(pathFound.empty());
+ }
+
+ // Test where fails: Matching file name is a directory: Last node
+ {
+ TemporarySubDirectory configFileDir;
+ fs::path configFileDirPath = configFileDir.getPath();
+
+ fs::path configFilePath = configFileDirPath;
+ configFilePath /= "MegaServer.json";
+ fs::create_directory(configFilePath);
+
+ fs::path pathFound = find(compatibleSystemTypes, configFileDirPath);
+ EXPECT_TRUE(pathFound.empty());
+ }
+
+ // Test where fails: System type has no '.'
+ {
+ TemporarySubDirectory configFileDir;
+ fs::path configFileDirPath = configFileDir.getPath();
+
+ fs::path configFilePath = configFileDirPath;
+ configFilePath /= "MegaServer2CPU.json";
+ writeConfigFile(configFilePath, std::string{""});
+
+ std::vector<std::string> noDotSystemTypes{"MegaServer4CPU",
+ "MegaServer", "Server", ""};
+ fs::path pathFound = find(noDotSystemTypes, configFileDirPath);
+ EXPECT_TRUE(pathFound.empty());
+ }
+
+ // Test where fails: System type ends with '.'
+ {
+ TemporarySubDirectory configFileDir;
+ fs::path configFileDirPath = configFileDir.getPath();
+
+ fs::path configFilePath = configFileDirPath;
+ configFilePath /= "MegaServer4CPU.json";
+ writeConfigFile(configFilePath, std::string{""});
+
+ std::vector<std::string> dotAtEndSystemTypes{
+ "com.acme.Hardware.Chassis.Model.MegaServer4CPU.", "a.", "."};
+ fs::path pathFound = find(dotAtEndSystemTypes, configFileDirPath);
+ EXPECT_TRUE(pathFound.empty());
+ }
+}
+
TEST(ConfigFileParserTests, Parse)
{
// Test where works
@@ -76,7 +263,7 @@
)"_json;
TemporaryFile configFile;
- std::filesystem::path pathName{configFile.getPath()};
+ fs::path pathName{configFile.getPath()};
writeConfigFile(pathName, configFileContents);
std::vector<std::unique_ptr<Rail>> rails = parse(pathName);
@@ -88,7 +275,7 @@
// Test where fails: File does not exist
{
- std::filesystem::path pathName{"/tmp/non_existent_file"};
+ fs::path pathName{"/tmp/non_existent_file"};
EXPECT_THROW(parse(pathName), ConfigFileParserError);
}
@@ -105,7 +292,7 @@
)"_json;
TemporaryFile configFile;
- std::filesystem::path pathName{configFile.getPath()};
+ fs::path pathName{configFile.getPath()};
writeConfigFile(pathName, configFileContents);
chmod(pathName.c_str(), 0222);
@@ -117,7 +304,7 @@
const std::string configFileContents = "] foo [";
TemporaryFile configFile;
- std::filesystem::path pathName{configFile.getPath()};
+ fs::path pathName{configFile.getPath()};
writeConfigFile(pathName, configFileContents);
EXPECT_THROW(parse(pathName), ConfigFileParserError);
@@ -128,7 +315,7 @@
const json configFileContents = R"( [ "foo", "bar" ] )"_json;
TemporaryFile configFile;
- std::filesystem::path pathName{configFile.getPath()};
+ fs::path pathName{configFile.getPath()};
writeConfigFile(pathName, configFileContents);
EXPECT_THROW(parse(pathName), ConfigFileParserError);
diff --git a/phosphor-power-sequencer/test/pmbus_driver_device_tests.cpp b/phosphor-power-sequencer/test/pmbus_driver_device_tests.cpp
index 92f7916..1fedb36 100644
--- a/phosphor-power-sequencer/test/pmbus_driver_device_tests.cpp
+++ b/phosphor-power-sequencer/test/pmbus_driver_device_tests.cpp
@@ -20,9 +20,7 @@
#include "pmbus_driver_device.hpp"
#include "rail.hpp"
#include "services.hpp"
-
-#include <errno.h>
-#include <stdlib.h> // for mkdtemp()
+#include "temporary_subdirectory.hpp"
#include <cstdint>
#include <exception>
@@ -42,6 +40,8 @@
using namespace phosphor::power::sequencer;
using namespace phosphor::pmbus;
+using namespace phosphor::power::util;
+namespace fs = std::filesystem;
using ::testing::Return;
using ::testing::Throw;
@@ -56,34 +56,7 @@
*/
PMBusDriverDeviceTests() : ::testing::Test{}
{
- char pathTemplate[] = "/tmp/pmbus_driver_device_testsXXXXXX";
- char* retVal = mkdtemp(pathTemplate);
- if (retVal == nullptr)
- {
- throw std::runtime_error{std::format(
- "Unable to create temporary directory: errno={}", errno)};
- }
- tempDir = fs::path{pathTemplate};
- }
-
- /**
- * Destructor.
- *
- * Deletes the temporary directory created in the constructor.
- */
- virtual ~PMBusDriverDeviceTests()
- {
- try
- {
- if (!tempDir.empty() && fs::exists(tempDir))
- {
- fs::remove_all(tempDir);
- }
- }
- catch (...)
- {
- // Destructors must not throw exceptions
- }
+ tempDirPath = tempDir.getPath();
}
/**
@@ -115,16 +88,21 @@
*/
void createFile(const std::string& name, const std::string& contents = "")
{
- fs::path path{tempDir / name};
+ fs::path path{tempDirPath / name};
std::ofstream out{path};
out << contents;
out.close();
}
/**
- * Temporary directory that is used to create simulated sysfs / hmmon files.
+ * Temporary subdirectory used to create simulated sysfs / hmmon files.
*/
- fs::path tempDir{};
+ TemporarySubDirectory tempDir;
+
+ /**
+ * Path to temporary subdirectory.
+ */
+ fs::path tempDirPath;
};
TEST_F(PMBusDriverDeviceTests, Constructor)
@@ -427,7 +405,7 @@
MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
EXPECT_CALL(pmbus, getPath(Type::Hwmon))
.Times(1)
- .WillOnce(Return(tempDir));
+ .WillOnce(Return(tempDirPath));
EXPECT_CALL(pmbus, readString("in13_label", Type::Hwmon))
.Times(1)
.WillOnce(Return("vout10")); // PAGE number 9 + 1
@@ -456,7 +434,7 @@
MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
EXPECT_CALL(pmbus, getPath(Type::Hwmon))
.Times(1)
- .WillOnce(Return(tempDir));
+ .WillOnce(Return(tempDirPath));
EXPECT_CALL(pmbus, readString("in13_label", Type::Hwmon))
.Times(1)
.WillOnce(Return("vout9")); // PAGE number 8 + 1
@@ -496,7 +474,7 @@
MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
EXPECT_CALL(pmbus, getPath(Type::Hwmon))
.Times(1)
- .WillOnce(Return(tempDir));
+ .WillOnce(Return(tempDirPath));
EXPECT_CALL(pmbus, readString("in1_label", Type::Hwmon))
.Times(1)
.WillOnce(Return("vout7")); // PAGE number 6 + 1
@@ -525,7 +503,7 @@
MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
EXPECT_CALL(pmbus, getPath(Type::Hwmon))
.Times(1)
- .WillOnce(Return(tempDir));
+ .WillOnce(Return(tempDirPath));
EXPECT_CALL(pmbus, readString("in1_label", Type::Hwmon))
.Times(1)
.WillOnce(Return("vout8")); // PAGE number 7 + 1
@@ -570,7 +548,7 @@
MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
EXPECT_CALL(pmbus, getPath(Type::Hwmon))
.Times(1)
- .WillOnce(Return(tempDir));
+ .WillOnce(Return(tempDirPath));
EXPECT_CALL(pmbus, readString).Times(0);
const std::map<uint8_t, unsigned int>& map =
@@ -603,7 +581,7 @@
MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
EXPECT_CALL(pmbus, getPath(Type::Hwmon))
.Times(1)
- .WillOnce(Return(tempDir));
+ .WillOnce(Return(tempDirPath));
EXPECT_CALL(pmbus, readString("in9_label", Type::Hwmon))
.Times(1)
.WillOnce(Return("vout4")); // PAGE number 3 + 1
@@ -645,7 +623,7 @@
MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
EXPECT_CALL(pmbus, getPath(Type::Hwmon))
.Times(1)
- .WillOnce(Return(tempDir / "in9_label"));
+ .WillOnce(Return(tempDirPath / "in9_label"));
EXPECT_CALL(pmbus, readString).Times(0);
const std::map<uint8_t, unsigned int>& map =
@@ -667,7 +645,7 @@
MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
EXPECT_CALL(pmbus, getPath(Type::Hwmon))
.Times(1)
- .WillOnce(Return(tempDir / "does_not_exist"));
+ .WillOnce(Return(tempDirPath / "does_not_exist"));
EXPECT_CALL(pmbus, readString).Times(0);
const std::map<uint8_t, unsigned int>& map =
@@ -683,7 +661,7 @@
createFile("in0_label");
// Change temporary directory to be unreadable
- fs::permissions(tempDir, fs::perms::none);
+ fs::permissions(tempDirPath, fs::perms::none);
MockServices services;
@@ -697,7 +675,7 @@
MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
EXPECT_CALL(pmbus, getPath(Type::Hwmon))
.Times(1)
- .WillOnce(Return(tempDir));
+ .WillOnce(Return(tempDirPath));
EXPECT_CALL(pmbus, readString).Times(0);
try
@@ -711,7 +689,7 @@
}
// Change temporary directory to be readable/writable
- fs::permissions(tempDir, fs::perms::owner_all);
+ fs::permissions(tempDirPath, fs::perms::owner_all);
}
}
@@ -735,7 +713,7 @@
MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
EXPECT_CALL(pmbus, getPath(Type::Hwmon))
.Times(1)
- .WillOnce(Return(tempDir));
+ .WillOnce(Return(tempDirPath));
EXPECT_CALL(pmbus, readString("in0_label", Type::Hwmon))
.Times(1)
.WillOnce(Return("vout7")); // PAGE number 6 + 1
@@ -770,7 +748,7 @@
MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
EXPECT_CALL(pmbus, getPath(Type::Hwmon))
.Times(1)
- .WillOnce(Return(tempDir));
+ .WillOnce(Return(tempDirPath));
EXPECT_CALL(pmbus, readString("in0_label", Type::Hwmon))
.Times(1)
.WillOnce(Return("vout7")); // PAGE number 6 + 1
@@ -817,7 +795,7 @@
MockPMBus& pmbus = static_cast<MockPMBus&>(device.getPMBusInterface());
EXPECT_CALL(pmbus, getPath(Type::Hwmon))
.Times(2)
- .WillRepeatedly(Return(tempDir));
+ .WillRepeatedly(Return(tempDirPath));
EXPECT_CALL(pmbus, readString("in1_label", Type::Hwmon))
.Times(2)
.WillRepeatedly(Return("vout7")); // PAGE number 6 + 1