blob: 7c101bfb5066c5cc9462ed785e908bb97b162683 [file]
#include "nvme_features.pb.h"
#include "NVMeError.hpp"
#include "NVMeFeatureStore.hpp"
#include "NVMeMiFake.hpp"
#include <boost/asio/spawn.hpp>
#include <sdbusplus/asio/connection.hpp>
#include <sdbusplus/asio/object_server.hpp>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
class NVMeMiMock :
public NVMeMiIntf,
public std::enable_shared_from_this<NVMeMiMock>
{
public:
~NVMeMiMock() override = default;
MOCK_METHOD(void, start, (const std::shared_ptr<MctpEndpoint>&),
(override));
MOCK_METHOD(void, stop, (), (override));
MOCK_METHOD(void, recover, (), (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()>&&), (override));
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 NVMeMiIntf::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 NVMeMiIntf::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),
(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, adminFwDownload,
(nvme_mi_ctrl_t ctrl, std::string firmwarefile,
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, adminSecuritySend,
(nvme_mi_ctrl_t ctrl, uint8_t proto, uint16_t specific,
std::span<uint8_t> data,
std::function<void(const std::error_code&, int)>&& cb),
(override));
MOCK_METHOD(void, adminSecurityReceive,
(nvme_mi_ctrl_t ctrl, uint8_t proto, uint16_t specific,
uint32_t al,
std::function<void(const std::error_code&, int,
const std::span<uint8_t>)>&& 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, uint32_t)>&& 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)>&& submittedCb,
std::function<void(nvme_ex_ptr, NVMeNSIdentify)>&& finishedCb),
(override));
MOCK_METHOD(void, adminDeleteNamespace,
(nvme_mi_ctrl_t ctrl, uint32_t nsid,
std::function<void(const std::error_code&, int)>&& cb),
(override));
MOCK_METHOD(void, adminListNamespaces,
(nvme_mi_ctrl_t ctrl,
std::function<void(nvme_ex_ptr, std::vector<uint32_t>)>&& 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)>&& 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)>&& 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));
};
class FeatureStoreTest : public ::testing::Test
{
public:
static std::vector<uint8_t>
callGetFeature(const std::shared_ptr<FeatureStore>& fs,
boost::asio::yield_context yield,
const std::string& featureName)
{
return fs->getFeature(std::move(yield), featureName);
}
static void callSetFeature(const std::shared_ptr<FeatureStore>& fs,
boost::asio::yield_context yield,
const std::string& featureName,
const std::vector<uint8_t>& data)
{
fs->setFeature(std::move(yield), featureName, data);
}
static std::map<uint8_t, uint32_t>&
getFeatureCaps(const std::shared_ptr<FeatureStore>& fs)
{
return fs->featureCaps;
}
};
TEST_F(FeatureStoreTest, GetFeatureArbitration)
{
boost::asio::io_context io;
auto conn = std::make_shared<sdbusplus::asio::connection>(io);
sdbusplus::asio::object_server objServer(conn);
std::string objectPath = "/xyz/openbmc_project/nvme/ctrl0";
auto mockNvme = std::make_shared<NVMeMiMock>();
nvme_mi_ctrl_t ctrl = nullptr;
auto featureStore = std::make_shared<FeatureStore>(
io, objServer, conn, objectPath, mockNvme, ctrl, 0,
FeatureScope::Controller);
EXPECT_CALL(*mockNvme,
adminGetFeatures(::testing::_, ::testing::_, ::testing::_))
.WillOnce(
[](nvme_mi_ctrl_t, const NVMeMiIntf::GetFeaturesRequest& req,
std::function<void(nvme_ex_ptr, uint32_t, std::span<uint8_t>)>&&
cb) {
EXPECT_EQ(req.fid, 0x01); // Arbitration
cb(nullptr, 0x01020304, {}); // burst=4, low=3, med=2, high=1
});
std::vector<uint8_t> result;
bool done = false;
boost::asio::spawn(io, [&](boost::asio::yield_context yield) {
result = FeatureStoreTest::callGetFeature(
featureStore, std::move(yield), "Arbitration_Current");
done = true;
});
while (!done)
{
io.run_one();
}
EXPECT_TRUE(done);
google::gbmc::nvme::base::Arbitration msg;
ASSERT_TRUE(
msg.ParseFromArray(result.data(), static_cast<int>(result.size())));
EXPECT_EQ(msg.arbitration_burst(), 0x04);
EXPECT_EQ(msg.low_priority_weight(), 0x03);
EXPECT_EQ(msg.medium_priority_weight(), 0x02);
EXPECT_EQ(msg.high_priority_weight(), 0x01);
}
TEST_F(FeatureStoreTest, SetFeatureArbitration)
{
boost::asio::io_context io;
auto conn = std::make_shared<sdbusplus::asio::connection>(io);
sdbusplus::asio::object_server objServer(conn);
std::string objectPath = "/xyz/openbmc_project/nvme/ctrl0";
auto mockNvme = std::make_shared<NVMeMiMock>();
nvme_mi_ctrl_t ctrl = nullptr;
auto featureStore = std::make_shared<FeatureStore>(
io, objServer, conn, objectPath, mockNvme, ctrl, 0,
FeatureScope::Controller);
google::gbmc::nvme::base::Arbitration msg;
msg.set_arbitration_burst(0x08);
msg.set_low_priority_weight(0x07);
msg.set_medium_priority_weight(0x06);
msg.set_high_priority_weight(0x05);
std::vector<uint8_t> data(msg.ByteSizeLong());
ASSERT_TRUE(
msg.SerializeToArray(data.data(), static_cast<int>(data.size())));
EXPECT_CALL(*mockNvme,
adminSetFeatures(::testing::_, ::testing::_, ::testing::_))
.WillOnce([](nvme_mi_ctrl_t, const NVMeMiIntf::SetFeaturesRequest& req,
std::function<void(nvme_ex_ptr, uint32_t)>&& cb) {
EXPECT_EQ(req.fid, 0x01); // Arbitration
EXPECT_EQ(req.cdw11, 0x05060708);
cb(nullptr, 0);
});
bool done = false;
boost::asio::spawn(io, [&](boost::asio::yield_context yield) {
FeatureStoreTest::callSetFeature(featureStore, std::move(yield),
"Arbitration_Current", data);
done = true;
});
while (!done)
{
io.run_one();
}
EXPECT_TRUE(done);
}
TEST_F(FeatureStoreTest, DiscoverFeaturesONCS)
{
boost::asio::io_context io;
auto conn = std::make_shared<sdbusplus::asio::connection>(io);
sdbusplus::asio::object_server objServer(conn);
std::string objectPath = "/xyz/openbmc_project/nvme/ctrl0";
auto mockNvme = std::make_shared<NVMeMiMock>();
nvme_mi_ctrl_t ctrl = nullptr;
auto featureStore = std::make_shared<FeatureStore>(
io, objServer, conn, objectPath, mockNvme, ctrl, 0,
FeatureScope::Controller);
// Mock Identify Controller to return ONCS with bit 4 set
EXPECT_CALL(*mockNvme,
adminIdentify(::testing::_, NVME_IDENTIFY_CNS_CTRL,
::testing::_, ::testing::_, ::testing::_))
.WillOnce(
[](nvme_mi_ctrl_t, nvme_identify_cns, uint32_t, uint16_t,
std::function<void(nvme_ex_ptr, std::span<uint8_t>)>&& cb) {
nvme_id_ctrl id{};
id.vwc = 1; // VWC supported
id.oncs = htole16(1 << 4); // Save and Select supported
std::vector<uint8_t> data(sizeof(id));
memcpy(data.data(), &id, sizeof(id));
cb(nullptr, data);
});
// Mock Get Features for capabilities of Arbitration (FID 0x01)
// Return Saveable (bit 2) and Changeable (bit 0)
EXPECT_CALL(
*mockNvme,
adminGetFeatures(::testing::_,
::testing::Field(&NVMeMiIntf::GetFeaturesRequest::sel,
NVME_GET_FEATURES_SEL_SUPPORTED),
::testing::_))
.WillRepeatedly(
[](nvme_mi_ctrl_t, const NVMeMiIntf::GetFeaturesRequest& req,
std::function<void(nvme_ex_ptr, uint32_t, std::span<uint8_t>)>&&
cb) {
if (req.fid == 0x01)
{
cb(nullptr, 0x5, {}); // Changeable | Saveable
}
else
{
cb(nullptr, 0x4, {}); // Just Changeable
}
});
bool done = false;
featureStore->init([&done](const std::error_code& ec) {
EXPECT_FALSE(ec);
done = true;
});
while (!done)
{
io.run_one();
}
auto caps = FeatureStoreTest::getFeatureCaps(featureStore);
// Check for Arbitration states (FID 0x01)
auto it = caps.find(0x01);
ASSERT_TRUE(it != caps.end());
uint32_t cap = it->second;
EXPECT_TRUE(cap & 0x1); // Saveable
EXPECT_TRUE(cap & 0x4); // Changeable
}
TEST_F(FeatureStoreTest, GetFeatureInvalidName)
{
boost::asio::io_context io;
auto conn = std::make_shared<sdbusplus::asio::connection>(io);
sdbusplus::asio::object_server objServer(conn);
std::string objectPath = "/xyz/openbmc_project/nvme/ctrl0";
auto mockNvme = std::make_shared<NVMeMiMock>();
nvme_mi_ctrl_t ctrl = nullptr;
auto featureStore = std::make_shared<FeatureStore>(
io, objServer, conn, objectPath, mockNvme, ctrl, 0,
FeatureScope::Controller);
bool threw = false;
bool done = false;
boost::asio::spawn(io, [&](boost::asio::yield_context yield) {
try
{
FeatureStoreTest::callGetFeature(featureStore, std::move(yield),
"InvalidFeature_Current");
}
catch (const sdbusplus::xyz::openbmc_project::Common::Error::
InvalidArgument&)
{
threw = true;
}
done = true;
});
while (!done)
{
io.run_one();
}
EXPECT_TRUE(threw);
}
TEST_F(FeatureStoreTest, GetFeatureDbusError)
{
boost::asio::io_context io;
auto conn = std::make_shared<sdbusplus::asio::connection>(io);
sdbusplus::asio::object_server objServer(conn);
std::string objectPath = "/xyz/openbmc_project/nvme/ctrl0";
auto mockNvme = std::make_shared<NVMeMiMock>();
nvme_mi_ctrl_t ctrl = nullptr;
auto featureStore = std::make_shared<FeatureStore>(
io, objServer, conn, objectPath, mockNvme, ctrl, 0,
FeatureScope::Controller);
EXPECT_CALL(*mockNvme,
adminGetFeatures(::testing::_, ::testing::_, ::testing::_))
.WillOnce(
[](nvme_mi_ctrl_t, const NVMeMiIntf::GetFeaturesRequest&,
std::function<void(nvme_ex_ptr, uint32_t, std::span<uint8_t>)>&&
cb) {
cb(makeLibNVMeError(std::make_error_code(std::errc::connection_aborted),
-1, "mock"),
0, {});
});
bool threw = false;
bool done = false;
boost::asio::spawn(io, [&](boost::asio::yield_context yield) {
try
{
FeatureStoreTest::callGetFeature(featureStore, std::move(yield),
"Arbitration_Current");
}
catch (
const sdbusplus::xyz::openbmc_project::Common::Error::Unavailable&)
{
threw = true;
}
done = true;
});
while (!done)
{
io.run_one();
}
EXPECT_TRUE(threw);
}
TEST_F(FeatureStoreTest, SetFeatureInvalidName)
{
boost::asio::io_context io;
auto conn = std::make_shared<sdbusplus::asio::connection>(io);
sdbusplus::asio::object_server objServer(conn);
std::string objectPath = "/xyz/openbmc_project/nvme/ctrl0";
auto mockNvme = std::make_shared<NVMeMiMock>();
nvme_mi_ctrl_t ctrl = nullptr;
auto featureStore = std::make_shared<FeatureStore>(
io, objServer, conn, objectPath, mockNvme, ctrl, 0,
FeatureScope::Controller);
bool threw = false;
bool done = false;
boost::asio::spawn(io, [&](boost::asio::yield_context yield) {
try
{
FeatureStoreTest::callSetFeature(featureStore, std::move(yield),
"Arbitration_InvalidSelect", {});
}
catch (const sdbusplus::xyz::openbmc_project::Common::Error::
InvalidArgument&)
{
threw = true;
}
done = true;
});
while (!done)
{
io.run_one();
}
EXPECT_TRUE(threw);
}
TEST_F(FeatureStoreTest, SetFeatureProtoDeserializationFailure)
{
boost::asio::io_context io;
auto conn = std::make_shared<sdbusplus::asio::connection>(io);
sdbusplus::asio::object_server objServer(conn);
std::string objectPath = "/xyz/openbmc_project/nvme/ctrl0";
auto mockNvme = std::make_shared<NVMeMiMock>();
nvme_mi_ctrl_t ctrl = nullptr;
auto featureStore = std::make_shared<FeatureStore>(
io, objServer, conn, objectPath, mockNvme, ctrl, 0,
FeatureScope::Controller);
std::vector<uint8_t> malformedData = {0xFF, 0xFF, 0xFF};
bool threw = false;
bool done = false;
boost::asio::spawn(io, [&](boost::asio::yield_context yield) {
try
{
FeatureStoreTest::callSetFeature(featureStore, std::move(yield),
"Arbitration_Current",
malformedData);
}
catch (const sdbusplus::xyz::openbmc_project::Common::Error::
InvalidArgument&)
{
threw = true;
}
done = true;
});
while (!done)
{
io.run_one();
}
EXPECT_TRUE(threw);
}
TEST_F(FeatureStoreTest, SetFeatureInvalidArgumentBounds)
{
boost::asio::io_context io;
auto conn = std::make_shared<sdbusplus::asio::connection>(io);
sdbusplus::asio::object_server objServer(conn);
std::string objectPath = "/xyz/openbmc_project/nvme/ctrl0";
auto mockNvme = std::make_shared<NVMeMiMock>();
nvme_mi_ctrl_t ctrl = nullptr;
auto featureStore = std::make_shared<FeatureStore>(
io, objServer, conn, objectPath, mockNvme, ctrl, 0,
FeatureScope::Controller);
google::gbmc::nvme::base::Arbitration msg;
msg.set_arbitration_burst(256); // Exceeds 8-bit bound (0-255)
std::vector<uint8_t> data(msg.ByteSizeLong());
ASSERT_TRUE(
msg.SerializeToArray(data.data(), static_cast<int>(data.size())));
bool threw = false;
bool done = false;
boost::asio::spawn(io, [&](boost::asio::yield_context yield) {
try
{
FeatureStoreTest::callSetFeature(featureStore, std::move(yield),
"Arbitration_Current", data);
}
catch (const sdbusplus::xyz::openbmc_project::Common::Error::
InvalidArgument&)
{
threw = true;
}
done = true;
});
while (!done)
{
io.run_one();
}
EXPECT_TRUE(threw);
}
TEST_F(FeatureStoreTest, SetFeatureNotChangeableOrSaveable)
{
boost::asio::io_context io;
auto conn = std::make_shared<sdbusplus::asio::connection>(io);
sdbusplus::asio::object_server objServer(conn);
std::string objectPath = "/xyz/openbmc_project/nvme/ctrl0";
auto mockNvme = std::make_shared<NVMeMiMock>();
nvme_mi_ctrl_t ctrl = nullptr;
auto featureStore = std::make_shared<FeatureStore>(
io, objServer, conn, objectPath, mockNvme, ctrl, 0,
FeatureScope::Controller);
// Seed featureCaps for Arbitration (FID 0x01) with 0 (neither changeable
// nor saveable)
FeatureStoreTest::getFeatureCaps(featureStore)[0x01] = 0;
google::gbmc::nvme::base::Arbitration msg;
msg.set_arbitration_burst(4);
std::vector<uint8_t> data(msg.ByteSizeLong());
ASSERT_TRUE(
msg.SerializeToArray(data.data(), static_cast<int>(data.size())));
// 1. Try to set Current (changeable=false) -> should throw NotAllowed
bool threwNotAllowedCurrent = false;
bool doneCurrent = false;
boost::asio::spawn(io, [&](boost::asio::yield_context yield) {
try
{
FeatureStoreTest::callSetFeature(featureStore, std::move(yield),
"Arbitration_Current", data);
}
catch (
const sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed&)
{
threwNotAllowedCurrent = true;
}
doneCurrent = true;
});
while (!doneCurrent)
{
io.run_one();
}
EXPECT_TRUE(threwNotAllowedCurrent);
// 2. Try to set Saved (saveable=false) -> should throw NotAllowed
io.restart();
bool threwNotAllowedSaved = false;
bool doneSaved = false;
boost::asio::spawn(io, [&](boost::asio::yield_context yield) {
try
{
FeatureStoreTest::callSetFeature(featureStore, std::move(yield),
"Arbitration_Saved", data);
}
catch (
const sdbusplus::xyz::openbmc_project::Common::Error::NotAllowed&)
{
threwNotAllowedSaved = true;
}
doneSaved = true;
});
while (!doneSaved)
{
io.run_one();
}
EXPECT_TRUE(threwNotAllowedSaved);
}
int main(int argc, char** argv)
{
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}