| #include "bios.hpp" | |
| #include <cstdint> // IWYU pragma: keep | |
| #include <filesystem> // NOLINT | |
| #include <fstream> | |
| #include <memory> | |
| #include <optional> | |
| #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" // IWYU pragma: keep | |
| #include "absl/log/log.h" | |
| #include "boost/system/error_code.hpp" // NOLINT | |
| #include "http_request.hpp" | |
| #include "logging.hpp" | |
| #include "dbus_utility.hpp" | |
| #include "test/redfish-core/lib/snapshot_fixture.hpp" | |
| #include <nlohmann/json.hpp> | |
| #include "managed_store.hpp" | |
| #include "managed_store_types.hpp" | |
| #include "test/g3/mock_managed_store.hpp" | |
| #include "test/g3/mock_managed_store_test.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::KeyType; | |
| using ::managedStore::ManagedType; | |
| using ::managedStore::ValueType; | |
| TEST(ResourceIdFromNameString, ResourceIdFromNameString) { | |
| EXPECT_EQ(resourceIdFromNameString("system10"), "10"); | |
| } | |
| TEST_F(SnapshotFixture, | |
| SingleHostPlatformHandleBiosServiceGetTestReturnsCorrectResponse) { | |
| #ifdef BMCWEB_ALLOW_NON_HARDCODED_SINGLEHOST_SYSTEM | |
| KeyType key(ManagedType::kManagedSubtreePaths, "/", 0, | |
| {"xyz.openbmc_project.Inventory.Item.System"}); | |
| absl::StatusOr<std::shared_ptr<ValueType>> systemObjectsPath = | |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()) | |
| ->getMockObjectFromManagedStore(key); | |
| ASSERT_TRUE(systemObjectsPath.ok()); | |
| std::optional<dbus::utility::MapperGetSubTreePathsResponse> | |
| originalSystemObjectsPaths = | |
| systemObjectsPath.value()->managedSubtreePaths; | |
| ASSERT_TRUE(originalSystemObjectsPaths.has_value()); | |
| dbus::utility::MapperGetSubTreePathsResponse mockSingleHostSystem1 = { | |
| "/xyz/openbmc_project/inventory/system1"}; | |
| ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()) | |
| ->upsertMockObjectIntoManagedStore( | |
| key, managedStore::MockManagedStoreTest::CreateValueType( | |
| mockSingleHostSystem1)) | |
| .ok()); | |
| absl::Cleanup mockManagedStoreResetter = [key, originalSystemObjectsPaths]() { | |
| ASSERT_TRUE( | |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()) | |
| ->upsertMockObjectIntoManagedStore( | |
| key, managedStore::MockManagedStoreTest::CreateValueType( | |
| originalSystemObjectsPaths.value())) | |
| .ok()); | |
| }; | |
| std::string originalPath = biosSettingFilepathFromConfig; | |
| biosSettingFilepathFromConfig = "/tmp/soc_firmware/host/vmtppr.cfg"; | |
| absl::Cleanup pathResetter = [originalPath]() { | |
| biosSettingFilepathFromConfig = originalPath; | |
| }; | |
| std::string systemName = "system1"; | |
| #else | |
| std::string systemName = "system"; | |
| #endif | |
| handleBiosServiceGet(app_, CreateRequest(), share_async_resp_, systemName); | |
| RunIoUntilDone(); | |
| nlohmann::json& json = share_async_resp_->res.jsonValue; | |
| EXPECT_EQ(json["@odata.id"], "/redfish/v1/Systems/" + systemName + "/Bios"); | |
| EXPECT_EQ(json["@odata.type"], "#Bios.v1_1_0.Bios"); | |
| EXPECT_EQ( | |
| json["Actions"]["#Bios.ResetBios"]["target"], | |
| "/redfish/v1/Systems/" + systemName + "/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, | |
| SingleHostPlatformHandleBiosServiceGetTestReturnsNotFound) { | |
| handleBiosServiceGet(app_, CreateRequest(), share_async_resp_, "system1"); | |
| RunIoUntilDone(); | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::not_found); | |
| } | |
| TEST_F(SnapshotFixture, | |
| SingleHostPlatformHandleBiosServiceGetSystemSubTreePathsResponseError) { | |
| // Looking for | |
| // kManagedSubtreePaths|/|0|xyz.openbmc_project.Inventory.Item.System | |
| KeyType key(ManagedType::kManagedSubtreePaths, "/", 0, | |
| {"xyz.openbmc_project.Inventory.Item.System"}); | |
| absl::StatusOr<std::shared_ptr<ValueType>> systemObjectsPath = | |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()) | |
| ->getMockObjectFromManagedStore(key); | |
| ASSERT_TRUE(systemObjectsPath.ok()); | |
| // TODO(b/416746677): Uncomment after the bug is fixed | |
| // ASSERT_EQ(systemObjectsPath.value()->managedType, | |
| // ManagedType::kManagedSubtreePaths); | |
| std::optional<dbus::utility::MapperGetSubTreePathsResponse> | |
| originalSystemObjectsPaths = | |
| systemObjectsPath.value()->managedSubtreePaths; | |
| ASSERT_TRUE(originalSystemObjectsPaths.has_value()); | |
| ASSERT_TRUE( | |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()) | |
| ->upsertMockObjectIntoManagedStore( | |
| key, managedStore::MockManagedStoreTest::CreateErrorValueType( | |
| originalSystemObjectsPaths.value(), | |
| boost::system::errc::make_error_code( | |
| boost::system::errc::io_error))) | |
| .ok()); | |
| absl::Cleanup mockManagedStoreResetter = [key, originalSystemObjectsPaths]() { | |
| ASSERT_TRUE( | |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()) | |
| ->upsertMockObjectIntoManagedStore( | |
| key, managedStore::MockManagedStoreTest::CreateValueType( | |
| originalSystemObjectsPaths.value())) | |
| .ok()); | |
| }; | |
| handleBiosServiceGet(app_, CreateRequest(), share_async_resp_, "system"); | |
| RunIoUntilDone(); | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::internal_server_error); | |
| } | |
| class SingleHostBiosBlobPatchTest : public SnapshotFixture { | |
| protected: | |
| static void createEmptyBlobFile() { | |
| std::filesystem::path filePath(actualBlobFilePath); | |
| std::error_code ec; | |
| std::filesystem::create_directories(filePath.parent_path(), ec); | |
| if (ec) { | |
| LOG(ERROR) << "Failed to create directory " << filePath.parent_path() | |
| << ": " << ec.message(); | |
| FAIL(); | |
| } | |
| if (std::filesystem::exists(actualBlobFilePath)) { | |
| // remove file if already exists | |
| std::error_code ec; | |
| std::filesystem::remove(actualBlobFilePath, ec); | |
| if (ec) { | |
| LOG(ERROR) << "BIOS setting blob file " << actualBlobFilePath | |
| << " exists, failed to remove: " << ec.message(); | |
| FAIL(); | |
| } | |
| } | |
| // create a empty file to start | |
| std::ofstream out(actualBlobFilePath); | |
| if (!out.good()) { | |
| LOG(ERROR) << "Failed to create or open file " << actualBlobFilePath; | |
| FAIL(); | |
| } | |
| out.close(); | |
| } | |
| static void SetUpTestSuite() { | |
| SnapshotFixture::SetUpTestSuite(); | |
| biosSettingFilepathFromConfig = biosSettingFilePathTemplate; | |
| } | |
| static void TearDownTestSuite() { | |
| SnapshotFixture::TearDownTestSuite(); | |
| std::error_code ec; | |
| std::filesystem::remove(actualBlobFilePath, ec); | |
| if (ec) { | |
| LOG(WARNING) << "Failed to remove BIOS setting blob file " | |
| << actualBlobFilePath | |
| << " in TearDownTestSuite():" << ec.message(); | |
| } | |
| } | |
| static void SetupSystemDbusMock(const std::string& systemName) { | |
| managedStore::KeyType systemKey( | |
| managedStore::ManagedType::kManagedSubtreePaths, "/", 0, | |
| {"xyz.openbmc_project.Inventory.Item.System"}); | |
| std::vector<std::string> mockSystems = {"/xyz/openbmc_project/inventory/" + | |
| systemName}; | |
| ASSERT_TRUE( | |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()) | |
| ->upsertMockObjectIntoManagedStore( | |
| systemKey, managedStore::MockManagedStoreTest::CreateValueType( | |
| mockSystems)) | |
| .ok()); | |
| } | |
| void TearDown() override { | |
| std::error_code ec; | |
| std::filesystem::remove(actualBlobFilePath, ec); | |
| if (ec) { | |
| LOG(WARNING) << "Failed to remove BIOS setting blob file " | |
| << actualBlobFilePath << " in TearDown():" << ec.message(); | |
| } | |
| } | |
| static std::string GetTestSystemName() { | |
| #ifdef BMCWEB_ALLOW_NON_HARDCODED_SINGLEHOST_SYSTEM | |
| return "system1"; | |
| #else | |
| return "system"; | |
| #endif | |
| } | |
| static inline const std::string biosSettingFilePathTemplate = | |
| #ifdef BMCWEB_ALLOW_NON_HARDCODED_SINGLEHOST_SYSTEM | |
| "/tmp/soc_firmware/host/vmtppr.cfg"; | |
| #else | |
| "/tmp/oem_bios_setting"; | |
| #endif | |
| static inline const std::string actualBlobFilePath = | |
| #ifdef BMCWEB_ALLOW_NON_HARDCODED_SINGLEHOST_SYSTEM | |
| "/tmp/soc_firmware/host0/vmtppr.cfg"; | |
| #else | |
| "/tmp/oem_bios_setting"; | |
| #endif | |
| }; | |
| TEST_F(SingleHostBiosBlobPatchTest, PatchBiosSettingsPeerNotAuthenticated) { | |
| // no BIOS setting file if createEmptyBlobFile() is not called | |
| crow::Request req = | |
| CreateRequest("{\"Oem\": {\"Google\": {\"MemTestBlob\" : \"AAEC\"}}}"); | |
| // peer_authenticated to false | |
| req.peer_authenticated = false; | |
| patchBiosSettings(app_, req, share_async_resp_, GetTestSystemName()); | |
| RunIoUntilDone(); | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::forbidden); | |
| } | |
| TEST_F(SingleHostBiosBlobPatchTest, PatchBiosSettingsRequestNoProperArgument) { | |
| // no BIOS setting file if createEmptyBlobFile() is not called | |
| crow::Request req = CreateRequest("{\"Whatever\": {\"AAEC\"}}}"); | |
| // peer is authenticated | |
| req.peer_authenticated = true; | |
| patchBiosSettings(app_, req, share_async_resp_, GetTestSystemName()); | |
| RunIoUntilDone(); | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::internal_server_error); | |
| } | |
| TEST_F(SingleHostBiosBlobPatchTest, PatchBiosSettingsToNonExistSystem) { | |
| // no BIOS setting file if createEmptyBlobFile() is not called | |
| #ifdef BMCWEB_ALLOW_NON_HARDCODED_SINGLEHOST_SYSTEM | |
| SetupSystemDbusMock(GetTestSystemName()); | |
| #endif | |
| crow::Request req = | |
| CreateRequest("{\"Oem\": {\"Google\": {\"MemTestBlob\" : \"AAEC\"}}}"); | |
| // peer is authenticated | |
| req.peer_authenticated = true; | |
| patchBiosSettings(app_, req, share_async_resp_, "system2"); | |
| RunIoUntilDone(); | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::not_found); | |
| } | |
| TEST_F(SingleHostBiosBlobPatchTest, PatchBiosSettingsBlobFileNotExist) { | |
| #ifdef BMCWEB_ALLOW_NON_HARDCODED_SINGLEHOST_SYSTEM | |
| SetupSystemDbusMock(GetTestSystemName()); | |
| #endif | |
| biosSettingFilepathFromConfig = "/non_exist/oem_bios_setting"; | |
| absl::Cleanup bioSettingFilePathResetter = []() { | |
| biosSettingFilepathFromConfig = biosSettingFilePathTemplate; | |
| }; | |
| // no BIOS setting file if createEmptyBlobFile() is not called | |
| crow::Request req = | |
| CreateRequest("{\"Oem\": {\"Google\": {\"MemTestBlob\" : \"AAEC\"}}}"); | |
| // peer is authenticated | |
| req.peer_authenticated = true; | |
| patchBiosSettings(app_, req, share_async_resp_, GetTestSystemName()); | |
| RunIoUntilDone(); | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::internal_server_error); | |
| } | |
| TEST_F(SingleHostBiosBlobPatchTest, PatchBiosSettingsWithNullBlob) { | |
| // no BIOS setting file if createEmptyBlobFile() is not called | |
| handleBiosSettingPatch(share_async_resp_, biosSettingFilepathFromConfig, | |
| /*multiHost=*/false, /*memTestBlob=*/std::nullopt, | |
| /*totalEnabledCores=*/std::nullopt, | |
| /*memoryCapacityGib=*/std::nullopt, | |
| /*systemName=*/"system"); | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::bad_request); | |
| } | |
| TEST_F(SingleHostBiosBlobPatchTest, PatchBiosSettingsWithInvalidBlob) { | |
| // no BIOS setting file if createEmptyBlobFile() is not called | |
| // ABCDE= is a invalid base64 encoded string | |
| #ifdef BMCWEB_ALLOW_NON_HARDCODED_SINGLEHOST_SYSTEM | |
| SetupSystemDbusMock(GetTestSystemName()); | |
| #endif | |
| crow::Request req = | |
| CreateRequest("{\"Oem\": {\"Google\": {\"MemTestBlob\" : \"ABCDE=\"}}}"); | |
| // peer is authenticated | |
| req.peer_authenticated = true; | |
| patchBiosSettings(app_, req, share_async_resp_, GetTestSystemName()); | |
| RunIoUntilDone(); | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::internal_server_error); | |
| } | |
| TEST_F(SingleHostBiosBlobPatchTest, | |
| PatchBiosSettingsGetSystemSubTreePathsResponseError) { | |
| // prepare the BIOS setting file | |
| createEmptyBlobFile(); | |
| // Looking for | |
| // kManagedSubtreePaths|/|0|xyz.openbmc_project.Inventory.Item.System | |
| KeyType key(ManagedType::kManagedSubtreePaths, "/", 0, | |
| {"xyz.openbmc_project.Inventory.Item.System"}); | |
| absl::StatusOr<std::shared_ptr<ValueType>> systemObjectsPath = | |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()) | |
| ->getMockObjectFromManagedStore(key); | |
| ASSERT_TRUE(systemObjectsPath.ok()); | |
| // TODO(b/416746677): Uncomment after the bug is fixed | |
| // ASSERT_EQ(systemObjectsPath.value()->managedType, | |
| // ManagedType::kManagedSubtreePaths); | |
| std::optional<dbus::utility::MapperGetSubTreePathsResponse> | |
| originalSystemObjectsPaths = | |
| systemObjectsPath.value()->managedSubtreePaths; | |
| ASSERT_TRUE(originalSystemObjectsPaths.has_value()); | |
| ASSERT_TRUE( | |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()) | |
| ->upsertMockObjectIntoManagedStore( | |
| key, managedStore::MockManagedStoreTest::CreateErrorValueType( | |
| originalSystemObjectsPaths.value(), | |
| boost::system::errc::make_error_code( | |
| boost::system::errc::io_error))) | |
| .ok()); | |
| absl::Cleanup mockManagedStoreResetter = [key, originalSystemObjectsPaths]() { | |
| ASSERT_TRUE( | |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()) | |
| ->upsertMockObjectIntoManagedStore( | |
| key, managedStore::MockManagedStoreTest::CreateValueType( | |
| originalSystemObjectsPaths.value())) | |
| .ok()); | |
| }; | |
| crow::Request req = | |
| CreateRequest("{\"Oem\": {\"Google\": {\"MemTestBlob\" : \"AAEC\"}}}"); | |
| // set authenticated to true | |
| req.peer_authenticated = true; | |
| patchBiosSettings(app_, req, share_async_resp_, GetTestSystemName()); | |
| RunIoUntilDone(); | |
| // PATCH request use no_content as convention to indicate a success | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::internal_server_error); | |
| } | |
| TEST_F(SingleHostBiosBlobPatchTest, PatchBiosSettingsSuccessfully) { | |
| #ifdef BMCWEB_ALLOW_NON_HARDCODED_SINGLEHOST_SYSTEM | |
| SetupSystemDbusMock(GetTestSystemName()); | |
| #endif | |
| crow::Logger::setLogLevel(crow::LogLevel::Error); | |
| // prepare the BIOS setting file | |
| createEmptyBlobFile(); | |
| #ifdef BMCWEB_ALLOW_NON_HARDCODED_SINGLEHOST_SYSTEM | |
| EXPECT_CALL( | |
| *dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( | |
| managedStore::GetManagedObjectStore()), | |
| PostDbusCallToIoContextThreadSafe( | |
| testing::_, | |
| testing::An< | |
| absl::AnyInvocable<void(const boost::system::error_code&)>&&>(), | |
| "xyz.openbmc_project.pldm", "/xyz/openbmc_project/pldm", | |
| "xyz.openbmc_project.PLDM.control", "loadSingleFileAndSyncCache", | |
| "/tmp/soc_firmware/host0/vmtppr.cfg", | |
| testing::Matcher<uint32_t>(uint32_t{0}))) | |
| .Times(1) | |
| .WillOnce( | |
| managedStore::SimulateSuccessfulAsyncPostDbusCallThreadSafeAction:: | |
| SimulateSuccessfulAsyncPostDbusCall()); | |
| #endif | |
| handleBiosServiceGet(app_, CreateRequest(), share_async_resp_, | |
| GetTestSystemName()); | |
| RunIoUntilDone(); | |
| nlohmann::json& json = share_async_resp_->res.jsonValue; | |
| EXPECT_EQ(json["Oem"]["Google"]["MemTestBlob"], ""); | |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); | |
| if (std::filesystem::exists(actualBlobFilePath)) { | |
| LOG(INFO) << actualBlobFilePath << " exists in test body"; | |
| } | |
| share_async_resp_->res.clear(); | |
| crow::Request req = | |
| CreateRequest("{\"Oem\": {\"Google\": {\"MemTestBlob\" : \"AAEC\"}}}"); | |
| // set authenticated to true | |
| req.peer_authenticated = true; | |
| patchBiosSettings(app_, req, share_async_resp_, GetTestSystemName()); | |
| RunIoUntilDone(); | |
| // PATCH request use no_content as convention to indicate a success | |
| EXPECT_EQ(share_async_resp_->res.result(), | |
| boost::beast::http::status::no_content); | |
| share_async_resp_->res.clear(); | |
| handleBiosServiceGet(app_, CreateRequest(), share_async_resp_, | |
| GetTestSystemName()); | |
| RunIoUntilDone(); | |
| json = share_async_resp_->res.jsonValue; | |
| EXPECT_EQ(json["Oem"]["Google"]["MemTestBlob"], "AAEC"); | |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); | |
| } | |
| } // namespace | |
| } // namespace redfish |