| #include "NVMeMiFake.hpp" |
| #include "NVMeSubsys.hpp" |
| |
| #include <dlfcn.h> |
| #include <valgrind/valgrind.h> |
| |
| #include <nlohmann/json.hpp> |
| #include <sdbusplus/asio/connection.hpp> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #define xstr(s) str(s) // NOLINT |
| #define str(s) #s // NOLINT |
| |
| std::unordered_map<std::string, void*> pluginLibMap = {}; |
| class NVMeMiMock : |
| public NVMeMiIntf, |
| public std::enable_shared_from_this<NVMeMiMock> |
| { |
| public: |
| NVMeMiMock(boost::asio::io_context& io, std::chrono::milliseconds delay) : |
| fake(std::make_shared<NVMeMiFake>(io, delay)) |
| { |
| ON_CALL(*this, getNID).WillByDefault([]() { return 0; }); |
| ON_CALL(*this, getEID).WillByDefault([]() { return 0; }); |
| ON_CALL(*this, miSubsystemHealthStatusPoll) |
| .WillByDefault( |
| [this]( |
| std::function<void(const std::error_code&, |
| nvme_mi_nvm_ss_health_status*)>&& cb) { |
| return fake->miSubsystemHealthStatusPoll(std::move(cb)); |
| }); |
| ON_CALL(*this, miScanCtrl) |
| .WillByDefault( |
| [this]( |
| std::function<void(const std::error_code& ec, |
| const std::vector<nvme_mi_ctrl_t>& list)> |
| cb) { return fake->miScanCtrl(std::move(cb)); }); |
| ON_CALL(*this, flushOperations) |
| .WillByDefault([this](std::function<void()>&& cb) { |
| return fake->flushOperations(std::move(cb)); |
| }); |
| ON_CALL(*this, adminIdentify) |
| .WillByDefault( |
| [this]( |
| nvme_mi_ctrl_t ctrl, nvme_identify_cns cns, uint32_t nsid, |
| uint16_t cntid, |
| std::function<void(nvme_ex_ptr, std::span<uint8_t>)>&& cb) { |
| return fake->adminIdentify(ctrl, cns, nsid, cntid, std::move(cb)); |
| }); |
| ON_CALL(*this, adminGetLogPage) |
| .WillByDefault([this](nvme_mi_ctrl_t ctrl, nvme_cmd_get_log_lid lid, |
| uint32_t nsid, uint8_t lsp, uint16_t lsi, |
| std::function<void(const std::error_code&, |
| std::span<uint8_t>)>&& cb, |
| uint8_t dataArea) { |
| return fake->adminGetLogPage(ctrl, lid, nsid, lsp, lsi, |
| std::move(cb), dataArea); |
| }); |
| ON_CALL(*this, adminFwCommit) |
| .WillByDefault([this](nvme_mi_ctrl_t ctrl, nvme_fw_commit_ca action, |
| uint8_t slot, bool bpid, |
| std::function<void(const std::error_code&, |
| nvme_status_field)>&& cb) { |
| return fake->adminFwCommit(ctrl, action, slot, bpid, std::move(cb)); |
| }); |
| ON_CALL(*this, adminXfer) |
| .WillByDefault( |
| [this]( |
| nvme_mi_ctrl_t ctrl, const nvme_mi_admin_req_hdr& adminReq, |
| std::span<uint8_t> data, unsigned int timeoutMs, |
| std::function<void(const std::error_code& ec, |
| const nvme_mi_admin_resp_hdr& adminResp, |
| std::span<uint8_t> respData)>&& cb) { |
| return fake->adminXfer(ctrl, adminReq, data, timeoutMs, |
| std::move(cb)); |
| }); |
| ON_CALL(*this, adminSecuritySend).WillByDefault([]() { return; }); |
| ON_CALL(*this, adminSecurityReceive).WillByDefault([]() { return; }); |
| ON_CALL(*this, adminListNamespaces) |
| .WillByDefault( |
| [this](nvme_mi_ctrl_t ctrl, |
| std::function<void(nvme_ex_ptr ex, |
| std::vector<uint32_t> ns)>&& cb) { |
| // return empty NS list |
| return fake->adminListNamespaces(ctrl, std::move(cb)); |
| }); |
| ON_CALL(*this, adminFormat).WillByDefault([this](auto&&... args) { |
| return fake->adminFormat(std::forward<decltype(args)>(args)...); |
| }); |
| } |
| |
| MOCK_METHOD(void, start, (const std::shared_ptr<MctpEndpoint>&), |
| (override)); |
| MOCK_METHOD(void, stop, (), (override)); |
| MOCK_METHOD(void, recover, (), (override)); |
| MOCK_METHOD(int, getNID, (), (const override)); |
| MOCK_METHOD(int, getEID, (), (const override)); |
| MOCK_METHOD(void, miSubsystemHealthStatusPoll, |
| (std::function<void(const std::error_code&, |
| nvme_mi_nvm_ss_health_status*)>&&), |
| (override)); |
| MOCK_METHOD(void, miScanCtrl, |
| (std::function<void(const std::error_code&, |
| const std::vector<nvme_mi_ctrl_t>&)>), |
| (override)); |
| MOCK_METHOD(bool, flushOperations, (std::function<void()>&&)); |
| MOCK_METHOD(void, adminIdentify, |
| (nvme_mi_ctrl_t ctrl, nvme_identify_cns cns, uint32_t nsid, |
| uint16_t cntid, |
| std::function<void(nvme_ex_ptr, std::span<uint8_t>)>&& cb), |
| (override)); |
| MOCK_METHOD(void, adminGetFeatures, |
| (nvme_mi_ctrl_t ctrl, const GetFeaturesRequest& req, |
| std::function<void(nvme_ex_ptr, uint32_t response, |
| std::span<uint8_t>)>&& cb), |
| (override)); |
| MOCK_METHOD(void, adminSetFeatures, |
| (nvme_mi_ctrl_t ctrl, const SetFeaturesRequest& req, |
| std::function<void(nvme_ex_ptr, uint32_t response)>&& cb), |
| (override)); |
| MOCK_METHOD( |
| void, adminGetLogPage, |
| (nvme_mi_ctrl_t ctrl, nvme_cmd_get_log_lid lid, uint32_t nsid, |
| uint8_t lsp, uint16_t lsi, |
| std::function<void(const std::error_code&, std::span<uint8_t>)>&& cb, |
| uint8_t dataArea), |
| (override)); |
| MOCK_METHOD( |
| void, adminFwCommit, |
| (nvme_mi_ctrl_t ctrl, nvme_fw_commit_ca action, uint8_t slot, bool bpid, |
| std::function<void(const std::error_code&, nvme_status_field)>&& cb), |
| (override)); |
| MOCK_METHOD(void, adminXfer, |
| (nvme_mi_ctrl_t ctrl, const nvme_mi_admin_req_hdr& adminReq, |
| std::span<uint8_t> data, unsigned int timeoutMs, |
| std::function<void(const std::error_code& ec, |
| const nvme_mi_admin_resp_hdr& adminResp, |
| std::span<uint8_t> respData)>&& cb), |
| (override)); |
| MOCK_METHOD( |
| void, adminLockdownInband, |
| (nvme_mi_ctrl_t ctrl, uint8_t prohibit, |
| const std::vector<uint8_t>& adminCmds, |
| const std::vector<uint8_t>& features, |
| const std::vector<uint8_t>& logPages, |
| std::function< |
| void(const std::error_code&, int nvmeStatus, uint32_t comptionDw0, |
| const std::string& failureScope, uint32_t failureId)>&& cb), |
| (override)); |
| MOCK_METHOD( |
| void, adminSecuritySend, |
| (nvme_mi_ctrl_t ctrl, uint8_t proto, uint16_t protoSpecific, |
| std::span<uint8_t> data, |
| std::function<void(const std::error_code&, int nvmeStatus)>&& cb), |
| (override)); |
| MOCK_METHOD(void, adminSecurityReceive, |
| (nvme_mi_ctrl_t ctrl, uint8_t proto, uint16_t protoSpecific, |
| uint32_t transferLength, |
| std::function<void(const std::error_code&, int nvmeStatus, |
| const std::span<uint8_t> data)>&& cb), |
| (override)); |
| |
| MOCK_METHOD( |
| void, adminFwDownload, |
| (nvme_mi_ctrl_t ctrl, std::string firmwarefile, |
| std::function<void(const std::error_code&, nvme_status_field)>&& cb), |
| (override)); |
| |
| MOCK_METHOD(void, adminNonDataCmd, |
| (nvme_mi_ctrl_t ctrl, uint8_t opcode, uint32_t cdw1, |
| uint32_t cdw2, uint32_t cdw3, uint32_t cdw10, uint32_t cdw11, |
| uint32_t cdw12, uint32_t cdw13, uint32_t cdw14, uint32_t cdw15, |
| std::function<void(const std::error_code&, int nvmeStatus, |
| uint32_t comptionDw0)>&& cb), |
| (override)); |
| MOCK_METHOD(void, createNamespace, |
| (nvme_mi_ctrl_t ctrl, uint64_t size, size_t lbaFormat, |
| bool metadataAtEnd, |
| std::function<void(nvme_ex_ptr ex)>&& submittedCB, |
| std::function<void(nvme_ex_ptr ex, NVMeNSIdentify newid)>&& |
| finishedCB), |
| (override)); |
| |
| MOCK_METHOD( |
| void, adminDeleteNamespace, |
| (nvme_mi_ctrl_t ctrl, uint32_t nsid, |
| std::function<void(const std::error_code&, int nvmeStatus)>&& cb), |
| (override)); |
| |
| MOCK_METHOD( |
| void, adminAttachDetachNamespace, |
| (nvme_mi_ctrl_t ctrl, uint16_t ctrlid, uint32_t nsid, bool attach, |
| std::function<void(const std::error_code&, int nvmeStatus)>&& cb), |
| (override)); |
| |
| MOCK_METHOD( |
| void, adminListNamespaces, |
| (nvme_mi_ctrl_t ctrl, |
| std::function<void(nvme_ex_ptr ex, std::vector<uint32_t> ns)>&& cb), |
| (override)); |
| |
| MOCK_METHOD(void, adminSanitize, |
| (nvme_mi_ctrl_t ctrl, enum nvme_sanitize_sanact sanact, |
| uint8_t passes, uint32_t pattern, bool invertPattern, |
| std::function<void(nvme_ex_ptr ex)>&& cb), |
| (override)); |
| |
| MOCK_METHOD( |
| void, adminFormat, |
| (nvme_mi_ctrl_t ctrl, uint32_t nsid, uint8_t lbaFormat, |
| uint8_t secureEraseSetting, uint8_t metadataSettings, |
| uint8_t dataProtectionType, uint8_t protectionInformationLocation, |
| std::function<void(const std::error_code&, int nvmeStatus)>&& cb), |
| (override)); |
| |
| std::shared_ptr<NVMeMiFake> fake; |
| }; |
| |
| class NVMeTest : public ::testing::Test |
| { |
| protected: |
| NVMeTest() : |
| systemBus(std::make_shared<sdbusplus::asio::connection>(io)), |
| object_server(systemBus), |
| nvme_intf(NVMeIntf::create<::testing::NiceMock<NVMeMiMock>>( |
| io, subsysPollTime / 10)), |
| mock(*std::dynamic_pointer_cast<NVMeMiMock>( |
| std::get<std::shared_ptr<NVMeMiIntf>>(nvme_intf.getInferface()))), |
| subsys(std::make_shared<NVMeSubsystem>( |
| io, object_server, systemBus, std::string(subsysPath), "NVMe_1", |
| SensorData{}, nvme_intf, false)) |
| { |
| systemBus->request_name("xyz.openbmc_project.NVMeTest"); |
| subsys->unavailableMaxCount = 1; |
| subsys->pollingInterval = subsysPollTime; |
| } |
| |
| static void SetUpTestSuite() |
| { |
| // Load plugin shared libraries |
| try |
| { |
| for (const auto& entry : |
| std::filesystem::directory_iterator(xstr(BUILDDIR))) |
| { |
| void* lib = dlopen(entry.path().c_str(), RTLD_NOW); |
| if (lib != nullptr) |
| { |
| pluginLibMap.emplace(entry.path().filename().string(), lib); |
| } |
| else |
| { |
| std::cerr << "could not load the plugin: " << dlerror() |
| << '\n'; |
| } |
| } |
| } |
| catch (const std::filesystem::filesystem_error& e) |
| { |
| std::cerr << "failed to open plugin folder: " << e.what() << '\n'; |
| } |
| } |
| |
| void SetUp() override |
| { |
| subsys->init(); |
| subsys->start(); |
| } |
| |
| void TearDown() override |
| { |
| if (subsys) |
| { |
| subsys->stop(); |
| } |
| io.poll(); |
| io.restart(); |
| } |
| |
| static constexpr std::string_view subsysPath = // NOLINT |
| "/xyz/openbmc_project/inventory/Test_Chassis/Test_NVMe"; |
| |
| boost::asio::io_context io; |
| std::shared_ptr<sdbusplus::asio::connection> systemBus; |
| sdbusplus::asio::object_server object_server; |
| |
| NVMeIntf nvme_intf; |
| NVMeMiMock& mock; |
| std::shared_ptr<NVMeSubsystem> subsys; |
| |
| const static std::chrono::milliseconds subsysPollTime; |
| }; |
| |
| const std::chrono::milliseconds NVMeTest::subsysPollTime = []() { |
| return (RUNNING_ON_VALGRIND != 0U) ? std::chrono::milliseconds(1000) |
| : std::chrono::milliseconds(100); |
| }(); |
| |
| /** |
| * @brief Test start and stop function of NVMeSubsystem |
| * |
| */ |
| TEST_F(NVMeTest, TestSubsystemStartStop) |
| { |
| using ::testing::AtLeast; |
| boost::asio::steady_timer timer(io); |
| |
| EXPECT_CALL(mock, miSubsystemHealthStatusPoll).Times(AtLeast(1)); |
| EXPECT_CALL(mock, adminIdentify).Times(AtLeast(1)); |
| EXPECT_CALL(mock, miScanCtrl).Times(AtLeast(1)); |
| |
| // wait for subsystem initialization |
| timer.expires_after(subsysPollTime * 2); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&, this](boost::system::error_code, const GetSubTreeType& result) { |
| // Only PF and the enabled VF should be listed |
| EXPECT_EQ(result.size(), 2); |
| subsys->stop(); |
| |
| // wait for storage controller destruction. |
| timer.expires_after(subsysPollTime * 1); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&](boost::system::error_code, |
| const GetSubTreeType& result) { |
| // not storage controller should be listed. |
| nlohmann::json j(result); |
| EXPECT_EQ(result.size(), 0) |
| << "The following interfaces remain after STOP: \n" |
| << j.dump(2) << '\n'; |
| // restart the subsystem |
| subsys->start(); |
| timer.expires_after(subsysPollTime * 2); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&](boost::system::error_code, |
| const GetSubTreeType& result) { |
| EXPECT_EQ(result.size(), 2); |
| |
| subsys->stop(); |
| // subsys.reset(); |
| |
| // wait for storage controller destruction. |
| timer.expires_after(subsysPollTime * 1); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&](boost::system::error_code, |
| const GetSubTreeType& result) { |
| // not storage controller should be listed. |
| nlohmann::json j(result); |
| EXPECT_EQ(result.size(), 0) |
| << "The following interfaces remain after STOP: \n" |
| << j.dump(2) << '\n'; |
| io.stop(); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", |
| "GetSubTree", subsysPath, 0, |
| std::vector<std::string>{ |
| "xyz.openbmc_project.Inventory." |
| "Item.StorageController"}); |
| }); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetSubTree", |
| subsysPath, 0, |
| std::vector<std::string>{ |
| "xyz.openbmc_project.Inventory.Item.StorageController"}); |
| }); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetSubTree", |
| subsysPath, 0, |
| std::vector<std::string>{"xyz.openbmc_project.Inventory." |
| "Item.StorageController"}); |
| }); |
| }, "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetSubTree", subsysPath, 0, |
| std::vector<std::string>{ |
| "xyz.openbmc_project.Inventory.Item.StorageController"}); |
| }); |
| io.run(); |
| } |
| |
| /** |
| * @brief Test NVMeMi return DriveFunctional(NSHDS.NSS.DF) = 0 |
| * |
| */ |
| TEST_F(NVMeTest, TestDriveFunctional) |
| { |
| using ::testing::AtLeast; |
| boost::asio::steady_timer timer(io); |
| |
| EXPECT_CALL(mock, miSubsystemHealthStatusPoll).Times(AtLeast(1)); |
| EXPECT_CALL(mock, adminIdentify).Times(AtLeast(1)); |
| EXPECT_CALL(mock, miScanCtrl).Times(AtLeast(1)); |
| |
| // wait for subsystem initialization |
| timer.expires_after(subsysPollTime * 2); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&](boost::system::error_code, const GetSubTreeType& result) { |
| // Only PF and the enabled VF should be listed |
| EXPECT_EQ(result.size(), 2); |
| |
| // mimik communication error of NVMeMI request |
| ON_CALL(mock, miSubsystemHealthStatusPoll) |
| .WillByDefault( |
| [&](std::function<void(const std::error_code&, |
| nvme_mi_nvm_ss_health_status*)>&& |
| cb) { |
| std::cerr << "mock device not functional health poll" << '\n'; |
| // return status.nss.df = 0 |
| return boost::asio::post(io, [cb = std::move(cb)]() { |
| nvme_mi_nvm_ss_health_status status{}; |
| status.nss = 0; |
| cb({}, &status); |
| }); |
| }); |
| |
| // wait for storage controller destruction. |
| timer.expires_after(subsysPollTime * 2); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&](boost::system::error_code, |
| const GetSubTreeType& result) { |
| // no storage controller should be listed. |
| nlohmann::json j(result); |
| EXPECT_EQ(result.size(), 0) |
| << "The following interfaces remain after unfunctional: \n" |
| << j.dump(2) << '\n'; |
| |
| // restart sending DF = 1 |
| ON_CALL(mock, miSubsystemHealthStatusPoll) |
| .WillByDefault( |
| [&](std::function<void( |
| const std::error_code&, |
| nvme_mi_nvm_ss_health_status*)>&& cb) { |
| return mock.fake->miSubsystemHealthStatusPoll( |
| std::move(cb)); |
| }); |
| timer.expires_after(subsysPollTime * 2); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&](boost::system::error_code, |
| const GetSubTreeType& result) { |
| // storage controller should be restored. |
| EXPECT_EQ(result.size(), 2); |
| |
| subsys->stop(); |
| // subsys.reset(); |
| |
| // wait for storage controller destruction. |
| timer.expires_after(subsysPollTime * 1); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&](boost::system::error_code, |
| const GetSubTreeType& result) { |
| // not storage controller should be listed. |
| nlohmann::json j(result); |
| EXPECT_EQ(result.size(), 0) |
| << "The following interfaces remain after STOP: \n" |
| << j.dump(2) << '\n'; |
| io.stop(); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", |
| "GetSubTree", subsysPath, 0, |
| std::vector<std::string>{ |
| "xyz.openbmc_project.Inventory." |
| "Item.StorageController"}); |
| }); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetSubTree", |
| subsysPath, 0, |
| std::vector<std::string>{ |
| "xyz.openbmc_project.Inventory." |
| "Item.StorageController"}); |
| }); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetSubTree", |
| subsysPath, 0, |
| std::vector<std::string>{"xyz.openbmc_project.Inventory." |
| "Item.StorageController"}); |
| }); |
| }, "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetSubTree", subsysPath, 0, |
| std::vector<std::string>{ |
| "xyz.openbmc_project.Inventory.Item.StorageController"}); |
| }); |
| io.run(); |
| } |
| |
| /** |
| * @brief Test NVMeMi returns Drive is absent (ec = no_such_device) |
| * |
| */ |
| TEST_F(NVMeTest, TestDriveAbsent) |
| { |
| using ::testing::AtLeast; |
| boost::asio::steady_timer timer(io); |
| |
| EXPECT_CALL(mock, miSubsystemHealthStatusPoll).Times(AtLeast(1)); |
| EXPECT_CALL(mock, adminIdentify).Times(AtLeast(1)); |
| EXPECT_CALL(mock, miScanCtrl).Times(AtLeast(1)); |
| |
| // wait for subsystem initialization |
| timer.expires_after(subsysPollTime * 2); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&](boost::system::error_code, const GetSubTreeType& result) { |
| // Only PF and the enabled VF should be listed |
| EXPECT_EQ(result.size(), 2); |
| |
| // mimik communication error of NVMeMI request |
| ON_CALL(mock, miSubsystemHealthStatusPoll) |
| .WillByDefault( |
| [&](std::function<void(const std::error_code&, |
| nvme_mi_nvm_ss_health_status*)>&& |
| cb) { |
| std::cerr << "mock device absent health poll" << '\n'; |
| // return no_such_device |
| return boost::asio::post(io, [cb = std::move(cb)]() { |
| cb(std::make_error_code(std::errc::no_such_device), |
| nullptr); |
| }); |
| }); |
| |
| // wait for storage controller destruction. |
| timer.expires_after(subsysPollTime * 2); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&](boost::system::error_code, |
| const GetSubTreeType& result) { |
| // no storage controller should be listed. |
| nlohmann::json j(result); |
| EXPECT_EQ(result.size(), 0) |
| << "The following interfaces remain after absent: \n" |
| << j.dump(2) << '\n'; |
| |
| // restart sending normal polling result |
| ON_CALL(mock, miSubsystemHealthStatusPoll) |
| .WillByDefault( |
| [&](std::function<void( |
| const std::error_code&, |
| nvme_mi_nvm_ss_health_status*)>&& cb) { |
| return mock.fake->miSubsystemHealthStatusPoll( |
| std::move(cb)); |
| }); |
| timer.expires_after(subsysPollTime * 5); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&](boost::system::error_code, |
| const GetSubTreeType& result) { |
| // storage controller should be restored. |
| EXPECT_EQ(result.size(), 2); |
| |
| subsys->stop(); |
| |
| // wait for storage controller destruction. |
| timer.expires_after(subsysPollTime * 1); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&](boost::system::error_code, |
| const GetSubTreeType& result) { |
| // not storage controller should be listed. |
| nlohmann::json j(result); |
| EXPECT_EQ(result.size(), 0) |
| << "The following interfaces remain after STOP: \n" |
| << j.dump(2) << '\n'; |
| io.stop(); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", |
| "GetSubTree", subsysPath, 0, |
| std::vector<std::string>{ |
| "xyz.openbmc_project.Inventory." |
| "Item.StorageController"}); |
| }); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetSubTree", |
| subsysPath, 0, |
| std::vector<std::string>{ |
| "xyz.openbmc_project.Inventory." |
| "Item.StorageController"}); |
| }); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetSubTree", |
| subsysPath, 0, |
| std::vector<std::string>{"xyz.openbmc_project.Inventory." |
| "Item.StorageController"}); |
| }); |
| }, "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetSubTree", subsysPath, 0, |
| std::vector<std::string>{ |
| "xyz.openbmc_project.Inventory.Item.StorageController"}); |
| }); |
| io.run(); |
| } |
| |
| /** |
| * @brief Inject error during subsystem initialization process. The subsystem is |
| * expected to recover after the error |
| */ |
| TEST_F(NVMeTest, InitErrorInjection) |
| { |
| using ::testing::_; |
| using ::testing::AnyNumber; |
| using ::testing::AtLeast; |
| using ::testing::Eq; |
| boost::asio::steady_timer timer(io); |
| |
| EXPECT_CALL(mock, miSubsystemHealthStatusPoll).Times(AtLeast(1)); |
| EXPECT_CALL(mock, miScanCtrl) |
| .WillOnce( |
| [](const std::function<void(const std::error_code&, |
| const std::vector<nvme_mi_ctrl_t>&)>& |
| cb) { |
| cb(std::make_error_code(std::errc::no_such_device), {}); |
| }).WillRepeatedly([&](auto&& cb) { |
| return mock.fake->miScanCtrl(std::forward<decltype(cb)>(cb)); |
| }); |
| |
| EXPECT_CALL(mock, adminIdentify).Times(::testing::AnyNumber()); |
| |
| // Failed on the first query on id_sec_cntrl_list |
| EXPECT_CALL( |
| mock, |
| adminIdentify( |
| _, Eq(nvme_identify_cns::NVME_IDENTIFY_CNS_SECONDARY_CTRL_LIST), _, |
| _, _)) |
| .WillOnce([]<class... Args>(Args... args) -> void { |
| auto&& cb = std::get<sizeof...(Args) - 1>(std::tie(args...)); |
| return cb( |
| makeLibNVMeError(0, NVME_MI_RESP_INVALID_PARAM, "adminIdentify"), |
| {}); |
| }).WillRepeatedly([&]<class... Args>(Args&&... args) { |
| return mock.fake->adminIdentify(std::forward<Args>(args)...); |
| }); |
| |
| // failed on first id_allocated_ns |
| EXPECT_CALL( |
| mock, |
| adminIdentify(_, Eq(nvme_identify_cns::NVME_IDENTIFY_CNS_ALLOCATED_NS), |
| _, _, _)) |
| .Times(AnyNumber()) // allow to run at 0 times based on given NS number |
| .WillOnce([]<class... Args>(Args... args) -> void { |
| auto&& cb = std::get<sizeof...(Args) - 1>(std::tie(args...)); |
| return cb( |
| makeLibNVMeError(0, NVME_MI_RESP_INVALID_PARAM, "adminIdentify"), |
| {}); |
| }).WillRepeatedly([&]<class... Args>(Args&&... args) { |
| return mock.fake->adminIdentify(std::forward<Args>(args)...); |
| }); |
| |
| // failed on first id_ns_cntrl |
| EXPECT_CALL( |
| mock, |
| adminIdentify(_, Eq(nvme_identify_cns::NVME_IDENTIFY_CNS_NS_CTRL_LIST), |
| _, _, _)) |
| .Times(AnyNumber()) // allow to run at 0 times based on given NS number |
| .WillOnce([]<class... Args>(Args... args) -> void { |
| auto&& cb = std::get<sizeof...(Args) - 1>(std::tie(args...)); |
| return cb( |
| makeLibNVMeError(0, NVME_MI_RESP_INVALID_PARAM, "adminIdentify"), |
| {}); |
| }).WillRepeatedly([&]<class... Args>(Args&&... args) { |
| return mock.fake->adminIdentify(std::forward<Args>(args)...); |
| }); |
| |
| // failed on first id_cntrl |
| EXPECT_CALL(mock, |
| adminIdentify(_, Eq(nvme_identify_cns::NVME_IDENTIFY_CNS_CTRL), |
| _, _, _)) |
| .WillOnce([]<class... Args>(Args... args) -> void { |
| auto&& cb = std::get<sizeof...(Args) - 1>(std::tie(args...)); |
| return cb( |
| makeLibNVMeError(0, NVME_MI_RESP_INVALID_PARAM, "adminIdentify"), |
| {}); |
| }).WillRepeatedly([&]<class... Args>(Args&&... args) { |
| return mock.fake->adminIdentify(std::forward<Args>(args)...); |
| }); |
| |
| // failed on first id_ns |
| EXPECT_CALL( |
| mock, |
| adminIdentify(_, Eq(nvme_identify_cns::NVME_IDENTIFY_CNS_NS), _, _, _)) |
| .Times(AnyNumber()) // allow to run at 0 times based on given NS number |
| .WillOnce([]<class... Args>(Args... args) -> void { |
| auto&& cb = std::get<sizeof...(Args) - 1>(std::tie(args...)); |
| return cb( |
| makeLibNVMeError(0, NVME_MI_RESP_INVALID_PARAM, "adminIdentify"), |
| {}); |
| }).WillRepeatedly([&]<class... Args>(Args&&... args) { |
| return mock.fake->adminIdentify(std::forward<Args>(args)...); |
| }); |
| |
| // Failed on list namespace |
| EXPECT_CALL(mock, adminListNamespaces) |
| .WillOnce([]<class... Args>(Args... args) -> void { |
| auto&& cb = std::get<sizeof...(Args) - 1>(std::tie(args...)); |
| return cb( |
| makeLibNVMeError(0, NVME_MI_RESP_INVALID_PARAM, "adminIdentify"), |
| {}); |
| }).WillRepeatedly([&]<class... Args>(Args&&... args) { |
| return mock.fake->adminListNamespaces(std::forward<Args>(args)...); |
| }); |
| |
| // wait for subsystem initialization, each failure will introduce 1 second |
| // delay for retry |
| timer.expires_after(subsysPollTime * (2 + 10 + 4)); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&, this](boost::system::error_code, const GetSubTreeType& result) { |
| // Only PF and the enabled VF should be listed |
| EXPECT_EQ(result.size(), 2); |
| subsys->stop(); |
| |
| // wait for storage controller destruction. |
| timer.expires_after(subsysPollTime * 1); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&](boost::system::error_code, |
| const GetSubTreeType& result) { |
| // not storage controller should be listed. |
| nlohmann::json j(result); |
| EXPECT_EQ(result.size(), 0) |
| << "The following interfaces remain after STOP: \n" |
| << j.dump(2) << '\n'; |
| io.stop(); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetSubTree", |
| subsysPath, 0, |
| std::vector<std::string>{"xyz.openbmc_project.Inventory." |
| "Item.StorageController"}); |
| }); |
| }, "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetSubTree", subsysPath, 0, |
| std::vector<std::string>{ |
| "xyz.openbmc_project.Inventory.Item.StorageController"}); |
| }); |
| io.run(); |
| } |
| /** |
| * @brief Test NVMe Format D-Bus method |
| * |
| */ |
| TEST_F(NVMeTest, TestFormat) |
| { |
| using ::testing::_; |
| using ::testing::AtLeast; |
| boost::asio::steady_timer timer(io); |
| |
| EXPECT_CALL(mock, miSubsystemHealthStatusPoll).Times(AtLeast(1)); |
| EXPECT_CALL(mock, adminIdentify).Times(AtLeast(1)); |
| EXPECT_CALL(mock, miScanCtrl).Times(AtLeast(1)); |
| |
| constexpr uint8_t lbaf1 = 1; |
| constexpr uint8_t sesNone = 0; |
| |
| mock.fake->fakeMetadataCapabilities = |
| 1; // MTELBAS = 1, MTSBS = 0 -> Hardware MSET = 1 |
| |
| EXPECT_CALL(mock, adminFormat) |
| .WillOnce( |
| [this, lbaf1, sesNone]( |
| nvme_mi_ctrl_t /*ctrl*/, uint32_t nsid, uint8_t lbaFormat, |
| uint8_t secureEraseSetting, uint8_t metadataSettings, |
| uint8_t dataProtectionType, |
| uint8_t protectionInformationLocation, |
| std::function<void(const std::error_code&, int nvmeStatus)>&& |
| cb) { |
| EXPECT_EQ(nsid, 1); |
| EXPECT_EQ(lbaFormat, lbaf1); |
| EXPECT_EQ(secureEraseSetting, sesNone); |
| EXPECT_EQ(metadataSettings, 1); |
| EXPECT_EQ(dataProtectionType, 0); |
| EXPECT_EQ(protectionInformationLocation, 0); |
| boost::asio::post(io, [cb = std::move(cb)]() mutable { cb({}, 0); }); |
| }).WillRepeatedly([&](auto&&... args) { |
| return mock.fake->adminFormat(std::forward<decltype(args)>(args)...); |
| }); |
| |
| // wait for subsystem initialization |
| timer.expires_after(subsysPollTime * 2); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&, this](boost::system::error_code ec) { |
| EXPECT_FALSE(ec) << "D-Bus error: " << ec.message(); |
| |
| subsys->stop(); |
| // wait for storage controller destruction. |
| timer.expires_after(subsysPollTime * 1); |
| timer.async_wait([&](boost::system::error_code) { io.stop(); }); |
| }, "xyz.openbmc_project.NVMeTest", |
| std::string(subsysPath) + "/controllers/0", |
| "xyz.openbmc_project.Nvme.Controller", "Format", lbaf1, |
| std::string( |
| "xyz.openbmc_project.NVMe.Format.SecureEraseSetting.None"), |
| std::string( |
| "xyz.openbmc_project.NVMe.Format.DataProtectionType.None")); |
| }); |
| io.run(); |
| } |
| |
| /** |
| * @brief Test NVMe Format D-Bus method with Separate Buffer Metadata (MSET=1) |
| * |
| */ |
| TEST_F(NVMeTest, TestFormatSeparateBuffer) |
| { |
| using ::testing::_; |
| using ::testing::AtLeast; |
| boost::asio::steady_timer timer(io); |
| |
| EXPECT_CALL(mock, miSubsystemHealthStatusPoll).Times(AtLeast(1)); |
| EXPECT_CALL(mock, adminIdentify).Times(AtLeast(1)); |
| EXPECT_CALL(mock, miScanCtrl).Times(AtLeast(1)); |
| |
| constexpr uint8_t lbaf1 = 1; |
| constexpr uint8_t sesNone = 0; |
| |
| mock.fake->fakeMetadataCapabilities = |
| 2; // MTELBAS = 0, MTSBS = 1 -> Hardware MSET = 0 |
| |
| EXPECT_CALL(mock, adminFormat) |
| .WillOnce( |
| [this, lbaf1, sesNone]( |
| nvme_mi_ctrl_t /*ctrl*/, uint32_t nsid, uint8_t lbaFormat, |
| uint8_t secureEraseSetting, uint8_t metadataSettings, |
| uint8_t dataProtectionType, |
| uint8_t protectionInformationLocation, |
| std::function<void(const std::error_code&, int nvmeStatus)>&& |
| cb) { |
| EXPECT_EQ(nsid, 1); |
| EXPECT_EQ(lbaFormat, lbaf1); |
| EXPECT_EQ(secureEraseSetting, sesNone); |
| EXPECT_EQ(metadataSettings, 0); |
| EXPECT_EQ(dataProtectionType, 0); |
| EXPECT_EQ(protectionInformationLocation, 0); |
| boost::asio::post(io, [cb = std::move(cb)]() mutable { cb({}, 0); }); |
| }).WillRepeatedly([&](auto&&... args) { |
| return mock.fake->adminFormat(std::forward<decltype(args)>(args)...); |
| }); |
| |
| // wait for subsystem initialization |
| timer.expires_after(subsysPollTime * 2); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&, this](boost::system::error_code ec) { |
| EXPECT_FALSE(ec); |
| |
| subsys->stop(); |
| // wait for storage controller destruction. |
| timer.expires_after(subsysPollTime * 1); |
| timer.async_wait([&](boost::system::error_code) { io.stop(); }); |
| }, "xyz.openbmc_project.NVMeTest", |
| std::string(subsysPath) + "/controllers/0", |
| "xyz.openbmc_project.Nvme.Controller", "Format", lbaf1, |
| std::string( |
| "xyz.openbmc_project.NVMe.Format.SecureEraseSetting.None"), |
| std::string( |
| "xyz.openbmc_project.NVMe.Format.DataProtectionType.None")); |
| }); |
| io.run(); |
| } |
| |
| /** |
| * @brief Test NVMe Format D-Bus method with Crypto Erase (Success) |
| * |
| */ |
| TEST_F(NVMeTest, TestFormatCryptoEraseSuccess) |
| { |
| using ::testing::_; |
| using ::testing::AtLeast; |
| boost::asio::steady_timer timer(io); |
| |
| EXPECT_CALL(mock, miSubsystemHealthStatusPoll).Times(AtLeast(1)); |
| EXPECT_CALL(mock, adminIdentify).Times(AtLeast(1)); |
| EXPECT_CALL(mock, miScanCtrl).Times(AtLeast(1)); |
| |
| constexpr uint8_t lbaf1 = 1; |
| constexpr uint8_t sesCryptoErase = 2; |
| |
| mock.fake->fakeMetadataCapabilities = |
| 1; // MTELBAS = 1, MTSBS = 0 -> Hardware MSET = 1 |
| |
| EXPECT_CALL(mock, adminFormat) |
| .WillOnce( |
| [this, lbaf1, sesCryptoErase]( |
| nvme_mi_ctrl_t /*ctrl*/, uint32_t nsid, uint8_t lbaFormat, |
| uint8_t secureEraseSetting, uint8_t metadataSettings, |
| uint8_t dataProtectionType, |
| uint8_t protectionInformationLocation, |
| std::function<void(const std::error_code&, int nvmeStatus)>&& |
| cb) { |
| EXPECT_EQ(nsid, 1); |
| EXPECT_EQ(lbaFormat, lbaf1); |
| EXPECT_EQ(secureEraseSetting, sesCryptoErase); |
| EXPECT_EQ(metadataSettings, 1); |
| EXPECT_EQ(dataProtectionType, 0); |
| EXPECT_EQ(protectionInformationLocation, 0); |
| boost::asio::post(io, [cb = std::move(cb)]() mutable { cb({}, 0); }); |
| }).WillRepeatedly([&](auto&&... args) { |
| return mock.fake->adminFormat(std::forward<decltype(args)>(args)...); |
| }); |
| |
| // wait for subsystem initialization |
| timer.expires_after(subsysPollTime * 2); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&, this](boost::system::error_code ec) { |
| EXPECT_FALSE(ec); |
| |
| subsys->stop(); |
| // wait for storage controller destruction. |
| timer.expires_after(subsysPollTime * 1); |
| timer.async_wait([&](boost::system::error_code) { io.stop(); }); |
| }, "xyz.openbmc_project.NVMeTest", |
| std::string(subsysPath) + "/controllers/0", |
| "xyz.openbmc_project.Nvme.Controller", "Format", lbaf1, |
| std::string( |
| "xyz.openbmc_project.NVMe.Format.SecureEraseSetting.CryptoErase"), |
| std::string( |
| "xyz.openbmc_project.NVMe.Format.DataProtectionType.None")); |
| }); |
| io.run(); |
| } |
| |
| /** |
| * @brief Test NVMe Subsystem Format D-Bus method with Broadcast (Success) |
| * |
| */ |
| TEST_F(NVMeTest, TestSubsystemFormatBroadcastSuccess) |
| { |
| using ::testing::_; |
| using ::testing::AtLeast; |
| boost::asio::steady_timer timer(io); |
| |
| EXPECT_CALL(mock, miSubsystemHealthStatusPoll).Times(AtLeast(1)); |
| EXPECT_CALL(mock, adminIdentify).Times(AtLeast(1)); |
| EXPECT_CALL(mock, miScanCtrl).Times(AtLeast(1)); |
| |
| constexpr uint8_t lbaf1 = 1; |
| constexpr uint8_t sesNone = 0; |
| |
| mock.fake->fakeMetadataCapabilities = |
| 1; // MTELBAS = 1, MTSBS = 0 -> Hardware MSET = 1 |
| |
| EXPECT_CALL(mock, adminFormat) |
| .WillOnce( |
| [this, lbaf1, sesNone]( |
| nvme_mi_ctrl_t /*ctrl*/, uint32_t nsid, uint8_t lbaFormat, |
| uint8_t secureEraseSetting, uint8_t metadataSettings, |
| uint8_t dataProtectionType, |
| uint8_t protectionInformationLocation, |
| std::function<void(const std::error_code&, int nvmeStatus)>&& |
| cb) { |
| EXPECT_EQ(nsid, NVME_NSID_ALL); |
| EXPECT_EQ(lbaFormat, lbaf1); |
| EXPECT_EQ(secureEraseSetting, sesNone); |
| EXPECT_EQ(metadataSettings, 1); |
| EXPECT_EQ(dataProtectionType, 0); |
| EXPECT_EQ(protectionInformationLocation, 0); |
| boost::asio::post(io, [cb = std::move(cb)]() mutable { cb({}, 0); }); |
| }).WillRepeatedly([&](auto&&... args) { |
| return mock.fake->adminFormat(std::forward<decltype(args)>(args)...); |
| }); |
| |
| // wait for subsystem initialization |
| timer.expires_after(subsysPollTime * 2); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&, this](boost::system::error_code ec) { |
| EXPECT_FALSE(ec); |
| |
| subsys->stop(); |
| // wait for storage controller destruction. |
| timer.expires_after(subsysPollTime * 1); |
| timer.async_wait([&](boost::system::error_code) { io.stop(); }); |
| }, "xyz.openbmc_project.NVMeTest", std::string(subsysPath), |
| "xyz.openbmc_project.Nvme.Storage", "Format", lbaf1, |
| std::string( |
| "xyz.openbmc_project.NVMe.Format.SecureEraseSetting.None"), |
| std::string( |
| "xyz.openbmc_project.NVMe.Format.DataProtectionType.None")); |
| }); |
| io.run(); |
| } |
| |
| /** |
| * @brief Test NVMe Format D-Bus method with Crypto Erase (Failure due to FNA) |
| * |
| */ |
| TEST_F(NVMeTest, TestFormatCryptoEraseFailure) |
| { |
| using ::testing::_; |
| using ::testing::AtLeast; |
| boost::asio::steady_timer timer(io); |
| |
| EXPECT_CALL(mock, miSubsystemHealthStatusPoll).Times(AtLeast(1)); |
| EXPECT_CALL(mock, adminIdentify).Times(AtLeast(1)); |
| EXPECT_CALL(mock, miScanCtrl).Times(AtLeast(1)); |
| |
| constexpr uint8_t lbaf1 = 1; |
| |
| mock.fake->fakeMetadataCapabilities = |
| 1; // MTELBAS = 1, MTSBS = 0 -> Hardware MSET = 1 |
| |
| // When ses=2, NVMeMi will call adminIdentify (CNS_CTRL) to check FNA bit 2. |
| // NVMeMi::adminFormat handles the identify and returns error if bit 2 is 0. |
| // In our mock, adminFormat will be called by NVMeSubsystem. |
| // The error will be caught by checkLibNVMeError and thrown as an exception. |
| |
| EXPECT_CALL(mock, adminFormat) |
| .WillOnce( |
| [this](nvme_mi_ctrl_t /*ctrl*/, uint32_t /*nsid*/, |
| uint8_t /*lbaFormat*/, uint8_t /*secureEraseSetting*/, |
| uint8_t /*metadataSettings*/, uint8_t /*dataProtectionType*/, |
| uint8_t /*protectionInformationLocation*/, |
| std::function<void(const std::error_code&, int nvmeStatus)>&& |
| cb) { |
| // Simulate FNA bit 2 is 0 error |
| boost::asio::post(io, [cb = std::move(cb)]() mutable { |
| cb(std::make_error_code(std::errc::invalid_argument), |
| NVME_SC_INVALID_FIELD); |
| }); |
| }).WillRepeatedly([&](auto&&... args) { |
| return mock.fake->adminFormat(std::forward<decltype(args)>(args)...); |
| }); |
| |
| // wait for subsystem initialization |
| timer.expires_after(subsysPollTime * 2); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&, this](boost::system::error_code ec) { |
| // Should fail with D-Bus error |
| EXPECT_TRUE(ec); |
| |
| subsys->stop(); |
| // wait for storage controller destruction. |
| timer.expires_after(subsysPollTime * 1); |
| timer.async_wait([&](boost::system::error_code) { io.stop(); }); |
| }, "xyz.openbmc_project.NVMeTest", |
| std::string(subsysPath) + "/controllers/0", |
| "xyz.openbmc_project.Nvme.Controller", "Format", lbaf1, |
| std::string( |
| "xyz.openbmc_project.NVMe.Format.SecureEraseSetting.CryptoErase"), |
| std::string( |
| "xyz.openbmc_project.NVMe.Format.DataProtectionType.None")); |
| }); |
| io.run(); |
| } |
| |
| /** |
| * @brief Test NVMe Format D-Bus method with UserDataErase (Failure as |
| * unsupported) |
| * |
| */ |
| TEST_F(NVMeTest, TestFormatUserDataEraseFailure) |
| { |
| using ::testing::_; |
| using ::testing::AtLeast; |
| boost::asio::steady_timer timer(io); |
| |
| EXPECT_CALL(mock, miSubsystemHealthStatusPoll).Times(AtLeast(1)); |
| EXPECT_CALL(mock, adminIdentify).Times(AtLeast(1)); |
| EXPECT_CALL(mock, miScanCtrl).Times(AtLeast(1)); |
| |
| constexpr uint8_t lbaf1 = 1; |
| |
| mock.fake->fakeMetadataCapabilities = |
| 1; // MTELBAS = 1, MTSBS = 0 -> Hardware MSET = 1 |
| |
| // NVMeSubsystem::format should reject UserDataErase directly and throw |
| // InvalidArgument. So adminFormat must NEVER be called. |
| EXPECT_CALL(mock, adminFormat).Times(0); |
| |
| // wait for subsystem initialization |
| timer.expires_after(subsysPollTime * 2); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&, this](boost::system::error_code ec) { |
| // Should fail with D-Bus error (InvalidArgument) |
| EXPECT_TRUE(ec); |
| |
| subsys->stop(); |
| // wait for storage controller destruction. |
| timer.expires_after(subsysPollTime * 1); |
| timer.async_wait([&](boost::system::error_code) { io.stop(); }); |
| }, "xyz.openbmc_project.NVMeTest", |
| std::string(subsysPath) + "/controllers/0", |
| "xyz.openbmc_project.Nvme.Controller", "Format", lbaf1, |
| std::string( |
| "xyz.openbmc_project.NVMe.Format.SecureEraseSetting.UserDataErase"), |
| std::string( |
| "xyz.openbmc_project.NVMe.Format.DataProtectionType.None")); |
| }); |
| io.run(); |
| } |
| |
| /** |
| * @brief Test NVMe Volume Format D-Bus method with Crypto Erase (Success) |
| * |
| */ |
| TEST_F(NVMeTest, TestVolumeFormatCryptoEraseSuccess) |
| { |
| using ::testing::_; |
| using ::testing::AtLeast; |
| boost::asio::steady_timer timer(io); |
| |
| EXPECT_CALL(mock, miSubsystemHealthStatusPoll).Times(AtLeast(1)); |
| EXPECT_CALL(mock, adminIdentify).Times(AtLeast(1)); |
| EXPECT_CALL(mock, miScanCtrl).Times(AtLeast(1)); |
| |
| constexpr uint8_t lbaf1 = 1; |
| constexpr uint8_t sesCryptoErase = 2; |
| |
| mock.fake->fakeMetadataCapabilities = |
| 1; // MTELBAS = 1, MTSBS = 0 -> Hardware MSET = 1 |
| |
| EXPECT_CALL(mock, adminFormat) |
| .WillOnce( |
| [this, lbaf1, sesCryptoErase]( |
| nvme_mi_ctrl_t /*ctrl*/, uint32_t nsid, uint8_t lbaFormat, |
| uint8_t secureEraseSetting, uint8_t metadataSettings, |
| uint8_t dataProtectionType, |
| uint8_t protectionInformationLocation, |
| std::function<void(const std::error_code&, int nvmeStatus)>&& |
| cb) { |
| EXPECT_EQ(nsid, 1); |
| EXPECT_EQ(lbaFormat, lbaf1); |
| EXPECT_EQ(secureEraseSetting, sesCryptoErase); |
| EXPECT_EQ(metadataSettings, 1); |
| EXPECT_EQ(dataProtectionType, 0); |
| EXPECT_EQ(protectionInformationLocation, 0); |
| boost::asio::post(io, [cb = std::move(cb)]() mutable { cb({}, 0); }); |
| }).WillRepeatedly([&](auto&&... args) { |
| return mock.fake->adminFormat(std::forward<decltype(args)>(args)...); |
| }); |
| |
| // Mock the post-format re-identify call to return LBAF 1 (4KB) and size |
| // 2000 blocks |
| EXPECT_CALL(mock, adminIdentify( |
| _, nvme_identify_cns::NVME_IDENTIFY_CNS_ALLOCATED_NS, |
| 1, _, _)) |
| .WillOnce([&](auto&&... args) { |
| mock.fake->adminIdentify(std::forward<decltype(args)>(args)...); |
| }) |
| .WillOnce( |
| [this](nvme_mi_ctrl_t /*ctrl*/, nvme_identify_cns /*cns*/, |
| uint32_t /*nsid*/, uint16_t /*cntid*/, |
| std::function<void(nvme_ex_ptr, std::span<uint8_t>)>&& cb) { |
| std::vector<uint8_t> data(NVME_IDENTIFY_DATA_SIZE); |
| nvme_id_ns idNs{}; |
| idNs.flbas = 1; // LBAF 1 |
| idNs.lbaf[0].ds = 9; // 512B |
| idNs.lbaf[1].ds = 12; // 4KB |
| idNs.nsze = htole64(2000); // 2000 blocks |
| idNs.ncap = htole64(2000); |
| std::memcpy(data.data(), &idNs, sizeof(idNs)); |
| boost::asio::post(io, |
| [cb = std::move(cb), |
| data = std::move(data)]() mutable { cb({}, data); }); |
| }); |
| |
| // wait for subsystem initialization |
| timer.expires_after(subsysPollTime * 2); |
| timer.async_wait([&](boost::system::error_code) { |
| systemBus->async_method_call( |
| [&, this](boost::system::error_code ec) { |
| EXPECT_FALSE(ec); |
| |
| // Verify Volume properties after format |
| systemBus->async_method_call( |
| [&, |
| this](boost::system::error_code ec, |
| const std::map<std::string, |
| std::variant<uint64_t, uint32_t, bool>>& |
| properties) { |
| EXPECT_FALSE(ec); |
| auto sizeIt = properties.find("Size"); |
| ASSERT_NE(sizeIt, properties.end()); |
| EXPECT_EQ(std::get<uint64_t>(sizeIt->second), 2000 * 4096); |
| |
| auto blockSizeIt = properties.find("BlockSize"); |
| ASSERT_NE(blockSizeIt, properties.end()); |
| EXPECT_EQ(std::get<uint64_t>(blockSizeIt->second), 4096); |
| |
| // Now query Nvme.Volume interface |
| systemBus->async_method_call( |
| [&, this]( |
| boost::system::error_code ec, |
| const std::map<std::string, |
| std::variant<uint64_t, uint32_t, bool>>& |
| nvmeProperties) { |
| EXPECT_FALSE(ec); |
| auto lbafIt = nvmeProperties.find("LBAFormat"); |
| ASSERT_NE(lbafIt, nvmeProperties.end()); |
| EXPECT_EQ(std::get<uint64_t>(lbafIt->second), 1); |
| |
| subsys->stop(); |
| // wait for storage controller destruction. |
| timer.expires_after(subsysPollTime * 1); |
| timer.async_wait( |
| [&](boost::system::error_code) { io.stop(); }); |
| }, |
| "xyz.openbmc_project.NVMeTest", |
| std::string(subsysPath) + "/volumes/1", |
| "org.freedesktop.DBus.Properties", "GetAll", |
| "xyz.openbmc_project.Nvme.Volume"); |
| }, |
| "xyz.openbmc_project.NVMeTest", |
| std::string(subsysPath) + "/volumes/1", |
| "org.freedesktop.DBus.Properties", "GetAll", |
| "xyz.openbmc_project.Inventory.Item.Volume"); |
| }, "xyz.openbmc_project.NVMeTest", |
| std::string(subsysPath) + "/volumes/1", |
| "xyz.openbmc_project.Nvme.Volume", "Format", lbaf1, |
| std::string( |
| "xyz.openbmc_project.NVMe.Format.SecureEraseSetting.CryptoErase"), |
| std::string( |
| "xyz.openbmc_project.NVMe.Format.DataProtectionType.None")); |
| }); |
| io.run(); |
| } |
| |
| int main(int argc, char** argv) |
| { |
| ::testing::InitGoogleTest(&argc, argv); |
| return RUN_ALL_TESTS(); |
| } |