|  | #include "absl/cleanup/cleanup.h" | 
|  | #include "absl/log/log.h" | 
|  | #include "bmcweb_config.h" | 
|  |  | 
|  | #include "app.hpp" | 
|  | #include "async_resp.hpp" | 
|  | #include "bios.hpp" | 
|  | #include "http_response.hpp" | 
|  | #include "nlohmann/json.hpp" | 
|  | #include "snapshot_fixture.hpp" | 
|  |  | 
|  | #include <boost/system/error_code.hpp> | 
|  |  | 
|  | #include <filesystem> | 
|  | #include <fstream> | 
|  | #include <memory> | 
|  | #include <optional> | 
|  | #include <vector> | 
|  |  | 
|  | #include <gmock/gmock.h> // IWYU pragma: keep | 
|  | #include <gtest/gtest.h> // IWYU pragma: keep | 
|  |  | 
|  | // IWYU pragma: no_include <gtest/gtest-message.h> | 
|  | // IWYU pragma: no_include <gtest/gtest-test-part.h> | 
|  | // IWYU pragma: no_include "gtest/gtest_pred_impl.h" | 
|  | // IWYU pragma: no_include <gmock/gmock-matchers.h> | 
|  | // IWYU pragma: no_include <gtest/gtest-matchers.h> | 
|  |  | 
|  | namespace redfish | 
|  | { | 
|  | namespace | 
|  | { | 
|  |  | 
|  | using ::dbus::utility::DbusVariantType; | 
|  | using ::managedStore::KeyType; | 
|  | using ::managedStore::ManagedObjectStoreContext; | 
|  | using ::managedStore::ManagedType; | 
|  | using ::managedStore::SimulateFailedAsyncPostDbusCallThreadSafeAction; | 
|  | using ::managedStore::SimulateSuccessfulAsyncPostDbusCallThreadSafeAction; | 
|  | using ::managedStore::ValueType; | 
|  | using ::testing::_; | 
|  | using ::testing::An; | 
|  |  | 
|  | TEST_F(SnapshotFixture_Platform23, | 
|  | MultiHostPlatformHandleBiosServiceGetTestNonExistSystem) | 
|  | { | 
|  | handleBiosServiceGet(app_, CreateRequest(), share_async_resp_, "system3"); | 
|  |  | 
|  | RunIoUntilDone(); | 
|  |  | 
|  | EXPECT_EQ(share_async_resp_->res.result(), | 
|  | boost::beast::http::status::not_found); | 
|  | } | 
|  |  | 
|  | TEST_F(SnapshotFixture_Platform23, | 
|  | MultiHostPlatformHandleBiosServiceGetTestReturnsNotFound) | 
|  | { | 
|  | handleBiosServiceGet(app_, CreateRequest(), share_async_resp_, "system"); | 
|  |  | 
|  | RunIoUntilDone(); | 
|  |  | 
|  | EXPECT_EQ(share_async_resp_->res.result(), | 
|  | boost::beast::http::status::not_found); | 
|  | } | 
|  |  | 
|  | TEST_F( | 
|  | SnapshotFixture_Platform23, | 
|  | MultiHostPlatformHandleBiosServiceGetTestReturnsCorrectResponseValidBlobPathWithNoBlobContent) | 
|  | { | 
|  | crow::Logger::setLogLevel(crow::LogLevel::Error); | 
|  | handleBiosServiceGet(app_, CreateRequest(), share_async_resp_, "system1"); | 
|  |  | 
|  | RunIoUntilDone(); | 
|  |  | 
|  | nlohmann::json& json = share_async_resp_->res.jsonValue; | 
|  | EXPECT_EQ(json["@odata.id"], "/redfish/v1/Systems/system1/Bios"); | 
|  | EXPECT_EQ(json["@odata.type"], "#Bios.v1_1_0.Bios"); | 
|  | EXPECT_EQ(json["Actions"]["#Bios.ResetBios"]["target"], | 
|  | "/redfish/v1/Systems/system1/Bios/Actions/Bios.ResetBios"); | 
|  | EXPECT_EQ(json["Description"], "BIOS Configuration Service"); | 
|  | EXPECT_EQ(json["Id"], "BIOS"); | 
|  | EXPECT_EQ(json["Links"]["ActiveSoftwareImage"]["@odata.id"], | 
|  | "/redfish/v1/UpdateService/FirmwareInventory/bios_active"); | 
|  |  | 
|  | EXPECT_TRUE(json["Links"]["SoftwareImages"].is_array()); | 
|  | EXPECT_EQ(json["Links"]["SoftwareImages"].size(), 1); | 
|  | EXPECT_EQ(json["Links"]["SoftwareImages"][0]["@odata.id"], | 
|  | "/redfish/v1/UpdateService/FirmwareInventory/bios_active"); | 
|  | EXPECT_EQ(json["Links"]["SoftwareImages@odata.count"], 1); | 
|  | EXPECT_EQ(json["Name"], "BIOS Configuration"); | 
|  | EXPECT_EQ(json["Oem"]["Google"]["MemTestBlob"], ""); | 
|  |  | 
|  | EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); | 
|  | } | 
|  |  | 
|  | TEST_F( | 
|  | SnapshotFixture_Platform23, | 
|  | MultiHostPlatformHandleBiosServiceGetTestReturnsCorrectResponsInvalidBlobPath) | 
|  | { | 
|  | std::string originalPath = biosSettingFilepathFromConfig; | 
|  | biosSettingFilepathFromConfig = "/illegal/bios/setting/path"; | 
|  |  | 
|  | absl::Cleanup bioSettingFilePathResetter = [originalPath]() { | 
|  | biosSettingFilepathFromConfig = originalPath; | 
|  | }; | 
|  |  | 
|  | handleBiosServiceGet(app_, CreateRequest(), share_async_resp_, "system1"); | 
|  |  | 
|  | RunIoUntilDone(); | 
|  |  | 
|  | nlohmann::json& json = share_async_resp_->res.jsonValue; | 
|  | EXPECT_EQ(json["@odata.id"], "/redfish/v1/Systems/system1/Bios"); | 
|  | EXPECT_EQ(json["@odata.type"], "#Bios.v1_1_0.Bios"); | 
|  | EXPECT_EQ(json["Actions"]["#Bios.ResetBios"]["target"], | 
|  | "/redfish/v1/Systems/system1/Bios/Actions/Bios.ResetBios"); | 
|  | EXPECT_EQ(json["Description"], "BIOS Configuration Service"); | 
|  | EXPECT_EQ(json["Id"], "BIOS"); | 
|  | EXPECT_EQ(json["Links"]["ActiveSoftwareImage"]["@odata.id"], | 
|  | "/redfish/v1/UpdateService/FirmwareInventory/bios_active"); | 
|  |  | 
|  | EXPECT_TRUE(json["Links"]["SoftwareImages"].is_array()); | 
|  | EXPECT_EQ(json["Links"]["SoftwareImages"].size(), 1); | 
|  | EXPECT_EQ(json["Links"]["SoftwareImages"][0]["@odata.id"], | 
|  | "/redfish/v1/UpdateService/FirmwareInventory/bios_active"); | 
|  | EXPECT_EQ(json["Links"]["SoftwareImages@odata.count"], 1); | 
|  | EXPECT_EQ(json["Name"], "BIOS Configuration"); | 
|  |  | 
|  | EXPECT_TRUE(json["Oem"]["Google"]["MemTestBlob"].is_null()); | 
|  |  | 
|  | EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); | 
|  | } | 
|  |  | 
|  | TEST(HandleBios, handleBiosWithInvalidSystemId) | 
|  | { | 
|  | auto share_async_resp = std::make_shared<bmcweb::AsyncResp>(); | 
|  |  | 
|  | handleBios(share_async_resp, "system", true); | 
|  |  | 
|  | nlohmann::json& json = share_async_resp->res.jsonValue; | 
|  | EXPECT_TRUE(json.find("Oem") == json.end()); | 
|  | } | 
|  |  | 
|  | class MultiHostBiosBlobPatchTest : public SnapshotFixture_Platform23 | 
|  | { | 
|  | protected: | 
|  | static void createEmptyBlobFiles() | 
|  | { | 
|  | for (auto& biosSettingFilePath : biosSettingFilePathSet) | 
|  | { | 
|  | std::filesystem::path path(biosSettingFilePath); | 
|  | if (std::filesystem::exists(path)) | 
|  | { | 
|  | // remove file if already exists | 
|  | std::error_code ec; | 
|  | std::filesystem::remove(path, ec); | 
|  | if (ec) | 
|  | { | 
|  | LOG(ERROR) << "BIOS setting blob file " << path | 
|  | << " exists, failed to remove: " << ec.message(); | 
|  | FAIL(); | 
|  | } | 
|  | } | 
|  | // create emtpy file | 
|  | std::error_code ec; | 
|  | std::filesystem::create_directories(path.parent_path()); | 
|  |  | 
|  | std::ofstream out(path); | 
|  |  | 
|  | out.close(); | 
|  | } | 
|  | } | 
|  |  | 
|  | static void SetUpTestSuite() | 
|  | { | 
|  | SnapshotFixture_Platform23::SetUpTestSuite(); | 
|  | biosSettingFilepathFromConfig = biosSettingFilePathFromConfigOrignal; | 
|  | } | 
|  |  | 
|  | static void TearDownTestSuite() | 
|  | { | 
|  | SnapshotFixture_Platform23::TearDownTestSuite(); | 
|  | std::error_code ec; | 
|  |  | 
|  | // remove files | 
|  | for (auto& biosSettingFilePath : biosSettingFilePathSet) | 
|  | { | 
|  | std::filesystem::remove(biosSettingFilePath, ec); | 
|  | if (ec) | 
|  | { | 
|  | LOG(WARNING) << "Failed to remove BIOS setting blob file " | 
|  | << biosSettingFilePath | 
|  | << " in TearDownTestSuite():" << ec.message(); | 
|  | ec.clear(); | 
|  | } | 
|  | } | 
|  |  | 
|  | // remove directories | 
|  | std::filesystem::remove_all("/run/soc_firmware/", ec); | 
|  | if (ec) | 
|  | { | 
|  | LOG(WARNING) | 
|  | << "Failed to remove test directory /run/soc_firmware/ in TearDownTestSuite():" | 
|  | << ec.message(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void TearDown() override | 
|  | { | 
|  | for (auto& biosSettingFilePath : biosSettingFilePathSet) | 
|  | { | 
|  | if (std::filesystem::exists(biosSettingFilePath)) | 
|  | { | 
|  | // remove file if exists | 
|  | std::error_code ec; | 
|  | std::filesystem::remove(biosSettingFilePath, ec); | 
|  | if (ec) | 
|  | { | 
|  | LOG(ERROR) | 
|  | << "BIOS setting blob file " << biosSettingFilePath | 
|  | << " exists, but failed to remove: " << ec.message(); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // reset filepath | 
|  | biosSettingFilepathFromConfig = biosSettingFilePathFromConfigOrignal; | 
|  | } | 
|  |  | 
|  | static inline const std::string biosSettingFilePathFromConfigOrignal = | 
|  | "/run/soc_firmware/host/vmtppr.cfg"; | 
|  | static inline const std::vector<std::string> biosSettingFilePathSet = { | 
|  | "/run/soc_firmware/host0/vmtppr.cfg", | 
|  | "/run/soc_firmware/host1/vmtppr.cfg", | 
|  | }; | 
|  | }; | 
|  |  | 
|  | TEST_F(MultiHostBiosBlobPatchTest, PatchBiosSettingsToNonExistSystem) | 
|  | { | 
|  | // no BIOS setting file if createEmptyBlobFiles() is not called | 
|  |  | 
|  | crow::Request req = | 
|  | CreateRequest("{\"Oem\": {\"Google\": {\"MemTestBlob\" : \"AAEC\"}}}"); | 
|  | // peer is authenticated | 
|  | req.peer_authenticated = true; | 
|  |  | 
|  | patchBiosSettings(app_, req, share_async_resp_, "system"); | 
|  | RunIoUntilDone(); | 
|  |  | 
|  | EXPECT_EQ(share_async_resp_->res.result(), | 
|  | boost::beast::http::status::not_found); | 
|  | } | 
|  |  | 
|  | TEST_F(MultiHostBiosBlobPatchTest, PatchBiosSettingsInvalidFilePath) | 
|  | { | 
|  | // no BIOS setting file if createEmptyBlobFiles() is not called | 
|  | crow::Logger::setLogLevel(crow::LogLevel::Error); | 
|  |  | 
|  | crow::Request req = | 
|  | CreateRequest("{\"Oem\": {\"Google\": {\"MemTestBlob\" : \"AAEC\"}}}"); | 
|  | // peer is authenticated | 
|  | req.peer_authenticated = true; | 
|  |  | 
|  | // set an invalid BIOS setting path for multihost platform. | 
|  | biosSettingFilepathFromConfig = "run/soc_firmware/whatever/vmtppr.cfg"; | 
|  |  | 
|  | patchBiosSettings(app_, req, share_async_resp_, "system1"); | 
|  | RunIoUntilDone(); | 
|  |  | 
|  | EXPECT_EQ(share_async_resp_->res.result(), | 
|  | boost::beast::http::status::internal_server_error); | 
|  | } | 
|  |  | 
|  | TEST_F(MultiHostBiosBlobPatchTest, PatchBiosSettingsFailedWithPldmFailure) | 
|  | { | 
|  | crow::Logger::setLogLevel(crow::LogLevel::Error); | 
|  |  | 
|  | // creates empty testing blob file | 
|  | createEmptyBlobFiles(); | 
|  |  | 
|  | // Simulate a Failed DBus Call | 
|  | // Setup dbus mocking | 
|  | EXPECT_CALL( | 
|  | *managedStore::GetManagedObjectStore(), | 
|  | PostDbusCallToIoContextThreadSafe( | 
|  | _, | 
|  | An<absl::AnyInvocable<void(const boost::system::error_code&)>&&>(), | 
|  | std::string(kPldmServiceName) + "0", std::string(kPldmObjectPath), | 
|  | std::string(kPldmControlInterface), | 
|  | std::string(kPldmLoadSingleFileMethod), | 
|  | static_cast<uint16_t>(BIOS_SETTING_FILE_HANDLE))) | 
|  | .Times(1) | 
|  | .WillOnce(SimulateFailedAsyncPostDbusCallThreadSafeAction:: | 
|  | SimulateFailedAsyncPostDbusCall()); | 
|  |  | 
|  | crow::Request req = | 
|  | CreateRequest("{\"Oem\": {\"Google\": {\"MemTestBlob\" : \"AAEC\"}}}"); | 
|  | // peer is authenticated | 
|  | req.peer_authenticated = true; | 
|  |  | 
|  | patchBiosSettings(app_, req, share_async_resp_, "system1"); | 
|  | RunIoUntilDone(); | 
|  |  | 
|  | EXPECT_EQ(share_async_resp_->res.result(), | 
|  | boost::beast::http::status::internal_server_error); | 
|  | } | 
|  |  | 
|  | TEST_F(MultiHostBiosBlobPatchTest, PatchBiosSettingsSuccessfully) | 
|  | { | 
|  | crow::Logger::setLogLevel(crow::LogLevel::Error); | 
|  |  | 
|  | // creates empty testing blob file | 
|  | createEmptyBlobFiles(); | 
|  |  | 
|  | // Simulate a successful DBus Call | 
|  | // Setup dbus mocking | 
|  | EXPECT_CALL( | 
|  | *managedStore::GetManagedObjectStore(), | 
|  | PostDbusCallToIoContextThreadSafe( | 
|  | _, | 
|  | An<absl::AnyInvocable<void(const boost::system::error_code&)>&&>(), | 
|  | std::string(kPldmServiceName) + "0", std::string(kPldmObjectPath), | 
|  | std::string(kPldmControlInterface), | 
|  | std::string(kPldmLoadSingleFileMethod), | 
|  | static_cast<uint16_t>(BIOS_SETTING_FILE_HANDLE))) | 
|  | .Times(1) | 
|  | .WillOnce(SimulateSuccessfulAsyncPostDbusCallThreadSafeAction:: | 
|  | SimulateSuccessfulAsyncPostDbusCall()); | 
|  |  | 
|  | crow::Request req = | 
|  | CreateRequest("{\"Oem\": {\"Google\": {\"MemTestBlob\" : \"AAEC\"}}}"); | 
|  | // peer is authenticated | 
|  | req.peer_authenticated = true; | 
|  |  | 
|  | // patch to system1 | 
|  | patchBiosSettings(app_, req, share_async_resp_, "system1"); | 
|  | RunIoUntilDone(); | 
|  |  | 
|  | EXPECT_EQ(share_async_resp_->res.result(), | 
|  | boost::beast::http::status::no_content); | 
|  |  | 
|  | share_async_resp_->res.clear(); | 
|  | handleBiosServiceGet(app_, CreateRequest(), share_async_resp_, "system2"); | 
|  | RunIoUntilDone(); | 
|  |  | 
|  | // ensure the patch to system1 does not affect system2. | 
|  | EXPECT_EQ(share_async_resp_->res.jsonValue["Oem"]["Google"]["MemTestBlob"], ""); | 
|  | EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); | 
|  | } | 
|  |  | 
|  | } // namespace | 
|  | } // namespace redfish |