| #include "google/google_service_nvme.hpp" |
| |
| #include <systemd/sd-bus.h> |
| |
| #include <cstdint> |
| #include <memory> |
| #include <string> |
| #include <string_view> |
| #include <system_error> // NOLINT |
| #include <tuple> |
| #include <utility> |
| #include <vector> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include "absl/functional/any_invocable.h" |
| #include "app.hpp" |
| #include "http_request.hpp" |
| #include "async_resp.hpp" |
| #include "dbus_utility.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" |
| #include "sdbusplus/message.hpp" |
| #include "sdbusplus/message/native_types.hpp" |
| #include "sdbusplus/test/sdbus_mock.hpp" |
| |
| namespace crow::google_api { |
| namespace { |
| |
| using ::managedStore::KeyType; |
| using ::managedStore::ManagedType; |
| using ::testing::_; |
| using ::testing::An; |
| using ::testing::MakePolymorphicAction; |
| using ::testing::NiceMock; |
| using ::testing::PolymorphicAction; |
| using ::testing::Return; |
| |
| constexpr std::string_view kNvmeId = "nvme_0"; |
| constexpr std::string_view kControllerId = "controller_0"; |
| constexpr std::string_view kControllerPath = |
| "/xyz/openbmc_project/inventory/system/board/nvme_0/controller_0"; |
| constexpr std::string_view kNvmePath = |
| "/xyz/openbmc_project/inventory/system/board/nvme_0"; |
| |
| class GoogleServiceNvmeTest : public ::managedStore::MockManagedStoreTest { |
| protected: |
| GoogleServiceNvmeTest() : app_(true) { |
| share_async_resp_ = std::make_shared<bmcweb::AsyncResp>(); |
| } |
| |
| void SetUp() override { |
| ::managedStore::MockManagedStoreTest::SetUp(); |
| |
| // Mock NVMe Controller object |
| KeyType nvmeKey(ManagedType::kManagedSubtree, |
| "/xyz/openbmc_project/inventory", 0, |
| {"xyz.openbmc_project.Inventory.Item.Drive"}); |
| |
| dbus::utility::MapperGetSubTreeResponse subtree = { |
| {std::string(kNvmePath), |
| {{"xyz.openbmc_project.Inventory.Item.Drive", |
| {"xyz.openbmc_project.Inventory.Item.Drive"}}}}}; |
| |
| EXPECT_TRUE( |
| dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( |
| managedStore::GetManagedObjectStore()) |
| ->upsertMockObjectIntoManagedStore( |
| nvmeKey, |
| managedStore::MockManagedStoreTest::CreateValueType(subtree)) |
| .ok()); |
| |
| // Mock Drive Protocol |
| KeyType protocolKey(ManagedType::kManagedProperty, |
| "xyz.openbmc_project.Inventory.Item.Drive", |
| sdbusplus::message::object_path(std::string(kNvmePath)), |
| "xyz.openbmc_project.Inventory.Item.Drive", "Protocol"); |
| |
| dbus::utility::DbusVariantType protocolVariant = std::string( |
| "xyz.openbmc_project.Inventory.Item.Drive.DriveProtocol.NVMe"); |
| EXPECT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( |
| managedStore::GetManagedObjectStore()) |
| ->upsertMockObjectIntoManagedStore( |
| protocolKey, |
| managedStore::MockManagedStoreTest::CreateValueType( |
| protocolVariant)) |
| .ok()); |
| |
| // Mock storage controller association |
| KeyType storageControllerKey( |
| ManagedType::kManagedProperty, "xyz.openbmc_project.ObjectMapper", |
| sdbusplus::message::object_path(std::string(kNvmePath) + |
| "/storage_controller"), |
| "xyz.openbmc_project.Association", "endpoints"); |
| |
| std::vector<std::string> endpoints = {std::string(kControllerPath)}; |
| dbus::utility::DbusVariantType endpointsVariant = endpoints; |
| |
| EXPECT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( |
| managedStore::GetManagedObjectStore()) |
| ->upsertMockObjectIntoManagedStore( |
| storageControllerKey, |
| managedStore::MockManagedStoreTest::CreateValueType( |
| endpointsVariant)) |
| .ok()); |
| |
| // Mock Lockdown Interface |
| KeyType lockdownKey(ManagedType::kManagedMapperObject, |
| std::string(kControllerPath), |
| {"xyz.openbmc_project.NVMe.Lockdown"}); |
| dbus::utility::MapperGetObject lockdownObject = { |
| {"xyz.openbmc_project.NVMe.Lockdown", |
| {{"xyz.openbmc_project.NVMe.Lockdown", {}}}}}; |
| |
| EXPECT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( |
| managedStore::GetManagedObjectStore()) |
| ->upsertMockObjectIntoManagedStore( |
| lockdownKey, |
| managedStore::MockManagedStoreTest::CreateValueType( |
| lockdownObject)) |
| .ok()); |
| } |
| |
| crow::Request CreateRequest(std::string_view body) { |
| std::error_code ec; |
| boost::beast::http::request<boost::beast::http::string_body> beastReq; |
| beastReq.method(boost::beast::http::verb::post); |
| beastReq.target( |
| "/google/v1/NVMe/nvme_0/Controllers/controller_0/Actions/" |
| "NVMeController.LockdownInband"); |
| beastReq.set(boost::beast::http::field::content_type, "application/json"); |
| beastReq.body() = std::string(body); |
| beastReq.prepare_payload(); |
| return crow::Request(beastReq, ec); |
| } |
| |
| std::shared_ptr<bmcweb::AsyncResp> share_async_resp_; |
| App app_; |
| }; |
| |
| class SimulateSuccessfulAsyncPostDbusCallThreadSafeWithMsgAndValueAction { |
| const std::tuple<uint32_t, uint32_t, uint32_t, std::string, uint32_t> value_; |
| |
| public: |
| explicit SimulateSuccessfulAsyncPostDbusCallThreadSafeWithMsgAndValueAction( |
| const std::tuple<uint32_t, uint32_t, uint32_t, std::string, uint32_t>& |
| value) |
| : value_(value) {} |
| |
| template <typename Result, typename ArgumentTuple> |
| Result Perform(const ArgumentTuple& args) const { |
| auto callback = std::make_shared<absl::AnyInvocable<void( |
| const boost::system::error_code&, const sdbusplus::message_t&, |
| const std::tuple<uint32_t, uint32_t, uint32_t, std::string, |
| uint32_t>&)>>( |
| std::move(std::get<1>(const_cast<ArgumentTuple&>(args)))); |
| managedStore::GetManagedObjectStore()->GetIoContext().post( |
| [callback, val = value_]() mutable { |
| // Mock sdbus message |
| auto sdbus = std::make_shared<NiceMock<sdbusplus::SdBusMock>>(); |
| EXPECT_CALL(*sdbus, sd_bus_message_ref(_)) |
| .WillRepeatedly(Return(nullptr)); |
| EXPECT_CALL(*sdbus, sd_bus_message_get_error(_)) |
| .WillRepeatedly(Return(nullptr)); |
| |
| sdbusplus::message_t msg(nullptr, sdbus.get()); |
| |
| std::move (*callback)(boost::system::error_code(), msg, val); |
| }); |
| } |
| |
| static PolymorphicAction< |
| SimulateSuccessfulAsyncPostDbusCallThreadSafeWithMsgAndValueAction> |
| SimulateSuccessfulAsyncPostDbusCallWithMsgAndValue( |
| const std::tuple<uint32_t, uint32_t, uint32_t, std::string, uint32_t>& |
| value) { |
| return MakePolymorphicAction( |
| SimulateSuccessfulAsyncPostDbusCallThreadSafeWithMsgAndValueAction( |
| value)); |
| } |
| }; |
| |
| TEST_F(GoogleServiceNvmeTest, LockdownInbandProhibitFalse) { |
| EXPECT_CALL( |
| *dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( |
| managedStore::GetManagedObjectStore()), |
| PostDbusCallToIoContextThreadSafe( |
| _, |
| An<absl::AnyInvocable<void( |
| const boost::system::error_code&, const sdbusplus::message_t&, |
| const std::tuple<uint32_t, uint32_t, uint32_t, std::string, |
| uint32_t>&)>&&>(), |
| "xyz.openbmc_project.NVMe.Lockdown", std::string(kControllerPath), |
| "xyz.openbmc_project.NVMe.Lockdown", "LockdownInband", 0, |
| std::vector<uint8_t>{}, std::vector<uint8_t>{}, |
| std::vector<uint8_t>{})) |
| .Times(1) |
| .WillOnce( |
| SimulateSuccessfulAsyncPostDbusCallThreadSafeWithMsgAndValueAction:: |
| SimulateSuccessfulAsyncPostDbusCallWithMsgAndValue( |
| std::make_tuple(uint32_t{0}, uint32_t{0}, uint32_t{0}, |
| std::string{""}, uint32_t{0}))); |
| |
| crow::Request req = CreateRequest(R"({"Prohibit": false})"); |
| |
| handleGoogleNvmeControllerLockdownInband(app_, req, share_async_resp_, |
| std::string(kNvmeId), |
| std::string(kControllerId)); |
| |
| RunIoUntilDone(); |
| |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); |
| } |
| |
| TEST_F(GoogleServiceNvmeTest, LockdownInbandProhibitTrue) { |
| EXPECT_CALL( |
| *dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( |
| managedStore::GetManagedObjectStore()), |
| PostDbusCallToIoContextThreadSafe( |
| _, |
| An<absl::AnyInvocable<void( |
| const boost::system::error_code&, const sdbusplus::message_t&, |
| const std::tuple<uint32_t, uint32_t, uint32_t, std::string, |
| uint32_t>&)>&&>(), |
| "xyz.openbmc_project.NVMe.Lockdown", std::string(kControllerPath), |
| "xyz.openbmc_project.NVMe.Lockdown", "LockdownInband", 1, |
| std::vector<uint8_t>{}, std::vector<uint8_t>{}, |
| std::vector<uint8_t>{})) |
| .Times(1) |
| .WillOnce( |
| SimulateSuccessfulAsyncPostDbusCallThreadSafeWithMsgAndValueAction:: |
| SimulateSuccessfulAsyncPostDbusCallWithMsgAndValue( |
| std::make_tuple(0, 0, 0, "", 0))); |
| |
| crow::Request req = CreateRequest(R"({"Prohibit": true})"); |
| |
| handleGoogleNvmeControllerLockdownInband(app_, req, share_async_resp_, |
| std::string(kNvmeId), |
| std::string(kControllerId)); |
| |
| RunIoUntilDone(); |
| |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); |
| } |
| |
| TEST_F(GoogleServiceNvmeTest, LockdownInbandWithAdminCmds) { |
| std::vector<uint8_t> expectedAdminCmds = {0x1, 0x2, 0x3}; |
| |
| EXPECT_CALL( |
| *dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( |
| managedStore::GetManagedObjectStore()), |
| PostDbusCallToIoContextThreadSafe( |
| _, |
| An<absl::AnyInvocable<void( |
| const boost::system::error_code&, const sdbusplus::message_t&, |
| const std::tuple<uint32_t, uint32_t, uint32_t, std::string, |
| uint32_t>&)>&&>(), |
| "xyz.openbmc_project.NVMe.Lockdown", std::string(kControllerPath), |
| "xyz.openbmc_project.NVMe.Lockdown", "LockdownInband", 0, |
| expectedAdminCmds, std::vector<uint8_t>{}, std::vector<uint8_t>{})) |
| .Times(1) |
| .WillOnce( |
| SimulateSuccessfulAsyncPostDbusCallThreadSafeWithMsgAndValueAction:: |
| SimulateSuccessfulAsyncPostDbusCallWithMsgAndValue( |
| std::make_tuple(0, 0, 0, "", 0))); |
| |
| crow::Request req = |
| CreateRequest(R"({"Prohibit": false, "AdminCmds": [1, 2, 3]})"); |
| |
| handleGoogleNvmeControllerLockdownInband(app_, req, share_async_resp_, |
| std::string(kNvmeId), |
| std::string(kControllerId)); |
| |
| RunIoUntilDone(); |
| |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); |
| } |
| |
| TEST_F(GoogleServiceNvmeTest, LockdownInbandAllParams) { |
| std::vector<uint8_t> expectedAdminCmds = {0xA}; |
| std::vector<uint8_t> expectedFeatures = {0xB, 0xC}; |
| std::vector<uint8_t> expectedLogPages = {0xD}; |
| |
| EXPECT_CALL( |
| *dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( |
| managedStore::GetManagedObjectStore()), |
| PostDbusCallToIoContextThreadSafe( |
| _, |
| An<absl::AnyInvocable<void( |
| const boost::system::error_code&, const sdbusplus::message_t&, |
| const std::tuple<uint32_t, uint32_t, uint32_t, std::string, |
| uint32_t>&)>&&>(), |
| "xyz.openbmc_project.NVMe.Lockdown", std::string(kControllerPath), |
| "xyz.openbmc_project.NVMe.Lockdown", "LockdownInband", 1, |
| expectedAdminCmds, expectedFeatures, expectedLogPages)) |
| .Times(1) |
| .WillOnce( |
| SimulateSuccessfulAsyncPostDbusCallThreadSafeWithMsgAndValueAction:: |
| SimulateSuccessfulAsyncPostDbusCallWithMsgAndValue( |
| std::make_tuple(0, 0, 0, "", 0))); |
| |
| crow::Request req = CreateRequest( |
| R"({"Prohibit": true, "AdminCmds": [10], "Features": [11, 12], "LogPages": [13]})"); |
| |
| handleGoogleNvmeControllerLockdownInband(app_, req, share_async_resp_, |
| std::string(kNvmeId), |
| std::string(kControllerId)); |
| |
| RunIoUntilDone(); |
| |
| EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok); |
| } |
| |
| TEST_F(GoogleServiceNvmeTest, LockdownInbandDbusError) { |
| NiceMock<sdbusplus::SdBusMock> sdbus; |
| ::sd_bus_error err = SD_BUS_ERROR_NULL; |
| err.name = "org.freedesktop.DBus.Error.Failed"; |
| err.message = "D-Bus call failed"; |
| |
| EXPECT_CALL(sdbus, sd_bus_message_get_error(_)).WillRepeatedly(Return(&err)); |
| EXPECT_CALL(sdbus, sd_bus_message_ref(_)).WillRepeatedly(Return(nullptr)); |
| |
| sdbusplus::message_t msg(nullptr, &sdbus); |
| |
| EXPECT_CALL( |
| *dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( |
| managedStore::GetManagedObjectStore()), |
| PostDbusCallToIoContextThreadSafe( |
| _, |
| An<absl::AnyInvocable<void( |
| const boost::system::error_code&, const sdbusplus::message_t&, |
| const std::tuple<uint32_t, uint32_t, uint32_t, std::string, |
| uint32_t>&)>&&>(), |
| _, _, _, _, _, _, _, _)) |
| .Times(1) |
| .WillOnce([msg](auto&&, auto&& callback, auto&&...) mutable { |
| auto callback_in = std::make_shared<absl::AnyInvocable<void( |
| const boost::system::error_code&, const sdbusplus::message_t&, |
| const std::tuple<uint32_t, uint32_t, uint32_t, std::string, |
| uint32_t>&)>>( |
| std::forward<decltype(callback)>(callback)); |
| managedStore::GetManagedObjectStore()->GetIoContext().post( |
| [callback_in, msg]() mutable { |
| std::move (*callback_in)(boost::system::error_code(), msg, {}); |
| }); |
| }); |
| |
| crow::Request req = CreateRequest(R"({"Prohibit": false})"); |
| |
| handleGoogleNvmeControllerLockdownInband(app_, req, share_async_resp_, |
| std::string(kNvmeId), |
| std::string(kControllerId)); |
| |
| RunIoUntilDone(); |
| |
| EXPECT_EQ(share_async_resp_->res.result(), |
| boost::beast::http::status::internal_server_error); |
| EXPECT_EQ(share_async_resp_->res.jsonValue["error"]["message"], |
| "D-Bus call failed"); |
| } |
| |
| TEST_F(GoogleServiceNvmeTest, LockdownInbandBoostError) { |
| NiceMock<sdbusplus::SdBusMock> sdbus; |
| EXPECT_CALL(sdbus, sd_bus_message_get_error(_)) |
| .WillRepeatedly(Return(nullptr)); |
| EXPECT_CALL(sdbus, sd_bus_message_ref(_)).WillRepeatedly(Return(nullptr)); |
| |
| sdbusplus::message_t msg(nullptr, &sdbus); |
| |
| EXPECT_CALL( |
| *dynamic_cast<managedStore::MockSerializedManagedObjectStore*>( |
| managedStore::GetManagedObjectStore()), |
| PostDbusCallToIoContextThreadSafe( |
| _, |
| An<absl::AnyInvocable<void( |
| const boost::system::error_code&, const sdbusplus::message_t&, |
| const std::tuple<uint32_t, uint32_t, uint32_t, std::string, |
| uint32_t>&)>&&>(), |
| _, _, _, _, _, _, _, _)) |
| .Times(1) |
| .WillOnce([msg](auto&&, auto&& callback, auto&&...) mutable { |
| auto callback_in = std::make_shared<absl::AnyInvocable<void( |
| const boost::system::error_code&, const sdbusplus::message_t&, |
| const std::tuple<uint32_t, uint32_t, uint32_t, std::string, |
| uint32_t>&)>>( |
| std::forward<decltype(callback)>(callback)); |
| managedStore::GetManagedObjectStore()->GetIoContext().post( |
| [callback_in, msg]() mutable { |
| std::move (*callback_in)(boost::system::errc::make_error_code( |
| boost::system::errc::not_connected), |
| msg, {}); |
| }); |
| }); |
| |
| crow::Request req = CreateRequest(R"({"Prohibit": false})"); |
| |
| handleGoogleNvmeControllerLockdownInband(app_, req, share_async_resp_, |
| std::string(kNvmeId), |
| std::string(kControllerId)); |
| |
| RunIoUntilDone(); |
| |
| EXPECT_EQ(share_async_resp_->res.result(), |
| boost::beast::http::status::internal_server_error); |
| } |
| |
| } // namespace |
| } // namespace crow::google_api |