| #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(ResourceIdFromNameString, ResourceIdFromNameString) |
| { |
| EXPECT_EQ(resourceIdFromNameString("system10"), "10"); |
| } |
| |
| TEST_F(SnapshotFixture, |
| SingleHostPlatformHandleBiosServiceGetTestReturnsCorrectResponse) |
| { |
| handleBiosServiceGet(app_, CreateRequest(), share_async_resp_, "system"); |
| |
| RunIoUntilDone(); |
| |
| nlohmann::json& json = share_async_resp_->res.jsonValue; |
| EXPECT_EQ(json["@odata.id"], "/redfish/v1/Systems/system/Bios"); |
| EXPECT_EQ(json["@odata.type"], "#Bios.v1_1_0.Bios"); |
| EXPECT_EQ(json["Actions"]["#Bios.ResetBios"]["target"], |
| "/redfish/v1/Systems/system/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>> systemObjectsPathsStatusOr = |
| managedStore::GetManagedObjectStore()->getMockObjectFromManagedStore( |
| key); |
| ASSERT_TRUE(systemObjectsPathsStatusOr.ok()); |
| |
| // TODO(b/416746677): Uncomment after the bug is fixed |
| // ASSERT_EQ(systemObjectsPathsStatusOr.value()->managedType, |
| // ManagedType::kManagedSubtreePaths); |
| |
| std::optional<dbus::utility::MapperGetSubTreePathsResponse> |
| originalSystemObjectsPaths = |
| systemObjectsPathsStatusOr.value()->managedSubtreePaths; |
| |
| ASSERT_TRUE(originalSystemObjectsPaths.has_value()); |
| |
| ASSERT_TRUE( |
| 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( |
| 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() |
| { |
| if (std::filesystem::exists(biosSettingFilePath)) |
| { |
| // remove file if already exists |
| std::error_code ec; |
| std::filesystem::remove(biosSettingFilePath, ec); |
| if (ec) |
| { |
| LOG(ERROR) << "BIOS setting blob file " << biosSettingFilePath |
| << " exists, failed to remove: " << ec.message(); |
| FAIL(); |
| } |
| } |
| // create a emtpy file to start |
| std::ofstream out(biosSettingFilePath); |
| out.close(); |
| } |
| |
| static void SetUpTestSuite() |
| { |
| SnapshotFixture::SetUpTestSuite(); |
| biosSettingFilepathFromConfig = biosSettingFilePath; |
| } |
| |
| static void TearDownTestSuite() |
| { |
| SnapshotFixture::TearDownTestSuite(); |
| std::error_code ec; |
| std::filesystem::remove(biosSettingFilePath, ec); |
| if (ec) |
| { |
| LOG(WARNING) << "Failed to remove BIOS setting blob file " |
| << biosSettingFilePath |
| << " in TearDownTestSuite():" << ec.message(); |
| } |
| } |
| |
| void TearDown() override |
| { |
| std::error_code ec; |
| std::filesystem::remove(biosSettingFilePath, ec); |
| if (ec) |
| { |
| LOG(WARNING) << "Failed to remove BIOS setting blob file " |
| << biosSettingFilePath |
| << " in TearDown():" << ec.message(); |
| } |
| } |
| |
| static inline const std::string biosSettingFilePath = |
| "/run/oem_bios_setting"; |
| }; |
| |
| 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_, "system"); |
| 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_, "system"); |
| 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 |
| 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::not_found); |
| } |
| |
| TEST_F(SingleHostBiosBlobPatchTest, PatchBiosSettingsBlobFileNotExist) |
| { |
| biosSettingFilepathFromConfig = "/non_exist/oem_bios_setting"; |
| |
| absl::Cleanup bioSettingFilePathResetter = []() { |
| biosSettingFilepathFromConfig = biosSettingFilePath; |
| }; |
| |
| // 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_, "system"); |
| 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 |
| crow::Request req = |
| CreateRequest("{\"Oem\": {\"Google\": {\"MemTestBlob\" : \"\"}}}"); |
| // peer is authenticated |
| req.peer_authenticated = true; |
| |
| handleBiosSettingPatch(share_async_resp_, biosSettingFilepathFromConfig, |
| false, std::nullopt); |
| |
| EXPECT_EQ(share_async_resp_->res.result(), |
| boost::beast::http::status::internal_server_error); |
| } |
| |
| TEST_F(SingleHostBiosBlobPatchTest, PatchBiosSettingsWithInvalidBlob) |
| { |
| |
| // no BIOS setting file if createEmptyBlobFile() is not called |
| // ABCDE= is a invalid base64 encoded string |
| crow::Request req = CreateRequest( |
| "{\"Oem\": {\"Google\": {\"MemTestBlob\" : \"ABCDE=\"}}}"); |
| // 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::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>> systemObjectsPathsStatusOr = |
| managedStore::GetManagedObjectStore()->getMockObjectFromManagedStore( |
| key); |
| ASSERT_TRUE(systemObjectsPathsStatusOr.ok()); |
| |
| // TODO(b/416746677): Uncomment after the bug is fixed |
| // ASSERT_EQ(systemObjectsPathsStatusOr.value()->managedType, |
| // ManagedType::kManagedSubtreePaths); |
| |
| std::optional<dbus::utility::MapperGetSubTreePathsResponse> |
| originalSystemObjectsPaths = |
| systemObjectsPathsStatusOr.value()->managedSubtreePaths; |
| |
| ASSERT_TRUE(originalSystemObjectsPaths.has_value()); |
| |
| ASSERT_TRUE( |
| 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( |
| 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_, "system"); |
| 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) |
| { |
| crow::Logger::setLogLevel(crow::LogLevel::Error); |
| |
| // prepare the BIOS setting file |
| createEmptyBlobFile(); |
| |
| handleBiosServiceGet(app_, CreateRequest(), share_async_resp_, "system"); |
| |
| 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(biosSettingFilePath)) |
| { |
| LOG(INFO) << biosSettingFilePath << " 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_, "system"); |
| 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_, "system"); |
| |
| 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 |