| #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 "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, |
| /*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 |