| #include <cstdint> | |
| #include <filesystem> // NOLINT | |
| #include <fstream> | |
| #include <memory> | |
| #include <string> | |
| #include <system_error> // NOLINT | |
| #include <vector> | |
| #include <gmock/gmock.h> // IWYU pragma: keep | |
| #include <gtest/gtest.h> // IWYU pragma: keep | |
| #include "absl/cleanup/cleanup.h" | |
| #include "absl/functional/any_invocable.h" | |
| #include "absl/log/log.h" | |
| #include "boost/system/error_code.hpp" // NOLINT | |
| #include "bm_config.h" | |
| #include "http_request.hpp" | |
| #include "http_response.hpp" | |
| #include "logging.hpp" | |
| #include "async_resp.hpp" | |
| #include "dbus_utility.hpp" | |
| #include "bios.hpp" | |
| #include "test/redfish-core/lib/snapshot_fixture.hpp" | |
| #include <nlohmann/json.hpp> | |
| #include "managed_store.hpp" | |
| #include "test/g3/mock_managed_store.hpp" | |
| // 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::SimulateFailedAsyncPostDbusCallThreadSafeAction; | |
| using ::managedStore::SimulateSuccessfulAsyncPostDbusCallThreadSafeAction; | |
| 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, | |
| // NOLINTNEXTLINE(whitespace/line_length): Test name is too long. | |
| 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, | |
| // NOLINTNEXTLINE(whitespace/line_length): Test name is too long. | |
| 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); | |
| std::error_code ec; | |
| std::filesystem::create_directories(path.parent_path(), ec); | |
| if (ec) { | |
| LOG(ERROR) << "Failed to create directory " << path.parent_path() | |
| << ": " << ec.message(); | |
| FAIL(); | |
| } | |
| if (std::filesystem::exists(path)) { | |
| // remove file if already exists | |
| 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::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("/tmp/soc_firmware/", ec); | |
| if (ec) { | |
| LOG(WARNING) << "Failed to remove test directory /tmp/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 = | |
| "/tmp/soc_firmware/host/vmtppr.cfg"; | |
| static inline const std::vector<std::string> biosSettingFilePathSet = { | |
| "/tmp/soc_firmware/host0/vmtppr.cfg", | |
| "/tmp/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(); | |
| // Now that CreateRequest sets Content-Type, it should proceed to business | |
| // logic and return not_found for non-existent system in multi-host mode. | |
| 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( | |
| *dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| 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) { | |
| // creates empty testing blob file | |
| createEmptyBlobFiles(); | |
| // Ensure directories exist for both hosts (for reading/writing) | |
| std::error_code ec; | |
| std::filesystem::create_directories("/tmp/soc_firmware/host0", ec); | |
| std::filesystem::create_directories("/tmp/soc_firmware/host1", ec); | |
| // Simulate a successful DBus Call | |
| // Setup dbus mocking | |
| EXPECT_CALL( | |
| *dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| 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 |