| #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 "boost/asio/post.hpp" // NOLINT | |
| #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)))); | |
| boost::asio::post( | |
| managedStore::GetManagedObjectStore()->GetIoContext(), | |
| [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)); | |
| boost::asio::post(managedStore::GetManagedObjectStore()->GetIoContext(), | |
| [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)); | |
| boost::asio::post( | |
| managedStore::GetManagedObjectStore()->GetIoContext(), | |
| [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 |