| #include "NVMeMiFake.hpp" |
| #include "NVMeSubsys.hpp" |
| |
| #include <dlfcn.h> |
| |
| #include <nlohmann/json.hpp> |
| #include <sdbusplus/asio/connection.hpp> |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #define xstr(s) str(s) |
| #define str(s) #s |
| |
| 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) : |
| fake(std::move(std::make_shared<NVMeMiFake>(io))) |
| { |
| 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) { |
| return fake->adminGetLogPage(ctrl, lid, nsid, lsp, lsi, |
| std::move(cb)); |
| }); |
| 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& admin_req, |
| std::span<uint8_t> data, unsigned int timeout_ms, |
| std::function<void(const std::error_code& ec, |
| const nvme_mi_admin_resp_hdr& admin_resp, |
| std::span<uint8_t> resp_data)>&& cb) { |
| return fake->adminXfer(ctrl, admin_req, data, timeout_ms, |
| std::move(cb)); |
| }); |
| ON_CALL(*this, adminSecuritySend).WillByDefault([]() { return; }); |
| ON_CALL(*this, adminSecurityReceive).WillByDefault([]() { return; }); |
| } |
| |
| MOCK_METHOD(void, start, (), (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, 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), |
| (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& admin_req, |
| std::span<uint8_t> data, unsigned int timeout_ms, |
| std::function<void(const std::error_code& ec, |
| const nvme_mi_admin_resp_hdr& admin_resp, |
| std::span<uint8_t> resp_data)>&& cb), |
| (override)); |
| |
| MOCK_METHOD( |
| void, adminSecuritySend, |
| (nvme_mi_ctrl_t ctrl, uint8_t proto, uint16_t proto_specific, |
| std::span<uint8_t> data, |
| std::function<void(const std::error_code&, int nvme_status)>&& cb), |
| (override)); |
| MOCK_METHOD(void, adminSecurityReceive, |
| (nvme_mi_ctrl_t ctrl, uint8_t proto, uint16_t proto_specific, |
| uint32_t transfer_length, |
| std::function<void(const std::error_code&, int nvme_status, |
| 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 nvme_status, |
| uint32_t comption_dw0)>&& cb), |
| (override)); |
| MOCK_METHOD(void, createNamespace, |
| (nvme_mi_ctrl_t ctrl, uint64_t size, size_t lba_format, |
| bool metadata_at_end, |
| std::function<void(nvme_ex_ptr ex)>&& submitted_cb, |
| std::function<void(nvme_ex_ptr ex, NVMeNSIdentify newid)>&& |
| finished_cb), |
| (override)); |
| |
| MOCK_METHOD( |
| void, adminDeleteNamespace, |
| (nvme_mi_ctrl_t ctrl, uint32_t nsid, |
| std::function<void(const std::error_code&, int nvme_status)>&& 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 nvme_status)>&& 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 invert_pattern, |
| std::function<void(nvme_ex_ptr ex)>&& cb), |
| (override)); |
| |
| std::shared_ptr<NVMeMiFake> fake; |
| }; |
| |
| class NVMeTest : public ::testing::Test |
| { |
| protected: |
| NVMeTest() : |
| object_server(system_bus), nvme_intf(NVMeIntf::create<NVMeMiMock>(io)), |
| mock(*std::dynamic_pointer_cast<NVMeMiMock>( |
| std::get<std::shared_ptr<NVMeMiIntf>>( |
| nvme_intf.getInferface())) |
| .get()), |
| subsys(std::make_shared<NVMeSubsystem>(io, object_server, system_bus, |
| subsys_path, "NVMe_1", |
| SensorData{}, nvme_intf)) |
| { |
| subsys->unavailableMaxCount = 1; |
| } |
| |
| static void SetUpTestSuite() |
| { |
| system_bus = |
| std::make_shared<sdbusplus::asio::connection>(NVMeTest::io); |
| system_bus->request_name("xyz.openbmc_project.NVMeTest"); |
| |
| // 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() |
| << std::endl; |
| } |
| } |
| } |
| catch (const std::filesystem::filesystem_error& e) |
| { |
| std::cerr << "failed to open plugin folder: " << e.what() |
| << std::endl; |
| } |
| } |
| |
| void SetUp() override |
| { |
| subsys->init(); |
| subsys->start(); |
| } |
| |
| void TearDown() override |
| { |
| io.restart(); |
| } |
| |
| static constexpr char subsys_path[] = |
| "/xyz/openbmc_project/inventory/Test_Chassis/Test_NVMe"; |
| |
| static boost::asio::io_context io; |
| static std::shared_ptr<sdbusplus::asio::connection> system_bus; |
| sdbusplus::asio::object_server object_server; |
| |
| NVMeIntf nvme_intf; |
| NVMeMiMock& mock; |
| std::shared_ptr<NVMeSubsystem> subsys; |
| }; |
| |
| boost::asio::io_context NVMeTest::io; |
| std::shared_ptr<sdbusplus::asio::connection> NVMeTest::system_bus; |
| |
| /** |
| * @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(std::chrono::seconds(2)); |
| timer.async_wait([&](boost::system::error_code) { |
| system_bus->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(std::chrono::seconds(1)); |
| timer.async_wait([&](boost::system::error_code) { |
| system_bus->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) << std::endl; |
| io.stop(); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", "GetSubTree", |
| subsys_path, 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", subsys_path, 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(std::chrono::seconds(2)); |
| timer.async_wait([&](boost::system::error_code) { |
| system_bus->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" |
| << std::endl; |
| // return status.nss.df = 0 |
| return io.post([cb = std::move(cb)]() { |
| nvme_mi_nvm_ss_health_status status; |
| status.nss = 0; |
| cb({}, &status); |
| }); |
| }); |
| |
| // wait for storage controller destruction. |
| timer.expires_after(std::chrono::seconds(2)); |
| timer.async_wait([&](boost::system::error_code) { |
| system_bus->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) << std::endl; |
| |
| // 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(std::chrono::seconds(2)); |
| timer.async_wait([&](boost::system::error_code) { |
| system_bus->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(std::chrono::seconds(1)); |
| timer.async_wait([&](boost::system::error_code) { |
| system_bus->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) << std::endl; |
| io.stop(); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", |
| "GetSubTree", subsys_path, 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", |
| subsys_path, 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", |
| subsys_path, 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", subsys_path, 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(std::chrono::seconds(2)); |
| timer.async_wait([&](boost::system::error_code) { |
| system_bus->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" << std::endl; |
| // return no_such_device |
| return io.post([cb = std::move(cb)]() { |
| cb(std::make_error_code(std::errc::no_such_device), |
| nullptr); |
| }); |
| }); |
| |
| // wait for storage controller destruction. |
| timer.expires_after(std::chrono::seconds(2)); |
| timer.async_wait([&](boost::system::error_code) { |
| system_bus->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) << std::endl; |
| |
| // 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(std::chrono::seconds(2)); |
| timer.async_wait([&](boost::system::error_code) { |
| system_bus->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(std::chrono::seconds(1)); |
| timer.async_wait([&](boost::system::error_code) { |
| system_bus->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) << std::endl; |
| io.stop(); |
| }, |
| "xyz.openbmc_project.ObjectMapper", |
| "/xyz/openbmc_project/object_mapper", |
| "xyz.openbmc_project.ObjectMapper", |
| "GetSubTree", subsys_path, 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", |
| subsys_path, 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", |
| subsys_path, 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", subsys_path, 0, |
| std::vector<std::string>{ |
| "xyz.openbmc_project.Inventory.Item.StorageController"}); |
| }); |
| io.run(); |
| } |
| |
| int main(int argc, char** argv) |
| { |
| ::testing::InitGoogleTest(&argc, argv); |
| return RUN_ALL_TESTS(); |
| } |