blob: 528da09aa8cfa8ba3ec41c359e45b54b0c411bb0 [file]
#include <systemd/sd-bus.h>
#include <cstring>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/container/flat_hash_set.h"
#include "absl/functional/any_invocable.h"
#include "http_request.hpp"
#include "http_response.hpp"
#include "async_resp.hpp"
#include "dbus_utility.hpp"
#include "nvme_metric_utils.hpp"
#include "storage.hpp"
#include "test/redfish-core/lib/snapshot_fixture.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 redfish {
namespace {
using ::managedStore::KeyType;
using ::managedStore::ManagedType;
using ::managedStore::ValueType;
using ::testing::_;
using ::testing::An;
class NVMeMetricTest : public SnapshotFixture {
protected:
std::string storageName = "storage_nvme";
std::string controllerId = "0";
std::string storagePath =
"/xyz/openbmc_project/inventory/system/board/ParentStorage/storage_nvme";
std::string controllerPath = storagePath + "/controllers/0";
std::string metricId = "SMARTMetric";
void mockController() {
// Mock Subtree for findStorageAndController
KeyType key(ManagedType::kManagedSubtree, "/xyz/openbmc_project/inventory",
0, {"xyz.openbmc_project.Inventory.Item.StorageController"});
dbus::utility::MapperGetSubTreeResponse mockSubtreeResponse{
{controllerPath,
{{"xyz.openbmc_project.NVMe",
std::vector<std::string>{
"xyz.openbmc_project.Inventory.Item.StorageController",
"xyz.openbmc_project.NVMe.NVMeAdmin",
"xyz.openbmc_project.NVMe.MetricStore"}}}}};
ASSERT_TRUE(
dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
managedStore::GetManagedObjectStore())
->upsertMockObjectIntoManagedStore(
key, managedStore::MockManagedStoreTest::CreateValueType(
std::move(mockSubtreeResponse)))
.ok());
// Mock MetricCollection property
KeyType propKey(ManagedType::kManagedProperty, "xyz.openbmc_project.NVMe",
controllerPath, "xyz.openbmc_project.NVMe.MetricStore",
"MetricCollection");
std::shared_ptr<ValueType> metricCollection =
managedStore::MockManagedStoreTest::CreateValueType<
dbus::utility::DbusVariantType>(std::vector<std::string>{metricId});
ASSERT_TRUE(
dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
managedStore::GetManagedObjectStore())
->upsertMockObjectIntoManagedStore(propKey, metricCollection)
.ok());
}
};
TEST_F(NVMeMetricTest, RefreshMetricActionTargetIncludedInGet) {
mockController();
// Mock GET request
crow::Request req = CreateRequest();
requestRoutesStorageControllerMetric(app_);
// We can't easily call the lambda directly from
// requestRoutesStorageControllerMetric but we can call
// nvmeControllerMetricFetcher which it uses. However, the lambda also sets
// @odata.id and Name.
std::shared_ptr<bmcweb::AsyncResp> asyncResp =
std::make_shared<bmcweb::AsyncResp>();
// Simulate the route handler logic
asyncResp->res.jsonValue["@odata.id"] =
"/redfish/v1/Systems/system/Storage/storage_nvme/Controllers/0/Oem/"
"Google/Metrics/SMARTMetric";
asyncResp->res.jsonValue["Name"] = metricId;
asyncResp->res
.jsonValue["Actions"]["#GoogleNvmeMetric.RefreshMetric"]["target"] =
"/redfish/v1/Systems/system/Storage/storage_nvme/Controllers/0/Oem/"
"Google/Metrics/SMARTMetric/Actions/GoogleNvmeMetric.RefreshMetric";
// Since we don't want to mock the FD reading (too complex), we just verify
// the Actions field is present.
EXPECT_EQ(
asyncResp->res
.jsonValue["Actions"]["#GoogleNvmeMetric.RefreshMetric"]["target"],
"/redfish/v1/Systems/system/Storage/storage_nvme/Controllers/0/Oem/"
"Google/Metrics/SMARTMetric/Actions/GoogleNvmeMetric.RefreshMetric");
}
TEST_F(NVMeMetricTest, RefreshMetricPostTriggersDbusCall) {
mockController();
testing::StrictMock<sdbusplus::SdBusMock> sdbus;
EXPECT_CALL(sdbus,
sd_bus_message_new_method_call(testing::_, testing::_, nullptr,
nullptr, nullptr, nullptr))
.WillRepeatedly(testing::Return(0));
sd_bus_error err = SD_BUS_ERROR_NULL;
EXPECT_CALL(sdbus, sd_bus_message_get_error(testing::_))
.WillRepeatedly(testing::Return(&err));
sdbusplus::message_t msg = sdbusplus::get_mocked_new(&sdbus).new_method_call(
nullptr, nullptr, nullptr, nullptr);
// Expect RefreshMetric DBus call
EXPECT_CALL(
*dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
managedStore::GetManagedObjectStore()),
PostDbusCallWithStringRetMsgAndFd(
_,
An<absl::AnyInvocable<void(const boost::system::error_code&,
const sdbusplus::message_t&,
const sdbusplus::message::unix_fd&)>&&>(),
"xyz.openbmc_project.NVMe", controllerPath,
"xyz.openbmc_project.NVMe.MetricStore", "RefreshMetric", metricId))
.Times(1);
nvmeControllerMetricRefresher(share_async_resp_, storageName, controllerId,
metricId);
RunIoUntilDone();
}
TEST_F(NVMeMetricTest, DefaultMetricCallbackPopulatesTimeStamps) {
std::shared_ptr<bmcweb::AsyncResp> asyncResp =
std::make_shared<bmcweb::AsyncResp>();
NVMeMetricHeader header;
header.startTime = 1000;
header.finishTime = 2000;
std::string data(sizeof(NVMeMetricHeader) + 4, 'A');
std::memcpy(data.data(), &header, sizeof(NVMeMetricHeader));
defaultMetricCallback(asyncResp, data);
EXPECT_EQ(asyncResp->res.jsonValue["StartTime"], 1000);
EXPECT_EQ(asyncResp->res.jsonValue["FinishTime"], 2000);
EXPECT_TRUE(asyncResp->res.jsonValue.contains("StartTimeUTC"));
EXPECT_TRUE(asyncResp->res.jsonValue.contains("FinishTimeUTC"));
EXPECT_TRUE(asyncResp->res.jsonValue["StartTimeUTC"].is_string());
EXPECT_TRUE(asyncResp->res.jsonValue["FinishTimeUTC"].is_string());
}
TEST(NVMeMetricUtilsTest, ExtractHeaderCorrectlyParsesValidBuffer) {
NVMeMetricHeader input;
input.lens = 24;
input.version = 1;
input.dataFormat = 0;
input.startTime = 0x1234567890ABCDEF;
input.finishTime = 0xFEDCBA0987654321;
std::string buffer(sizeof(NVMeMetricHeader), '\0');
std::memcpy(buffer.data(), &input, sizeof(NVMeMetricHeader));
std::optional<NVMeMetricHeader> result = extractHeader(buffer);
ASSERT_TRUE(result.has_value());
EXPECT_EQ(result->lens, 24);
EXPECT_EQ(result->version, 1);
EXPECT_EQ(result->dataFormat, 0);
EXPECT_EQ(result->startTime, 0x1234567890ABCDEF);
EXPECT_EQ(result->finishTime, 0xFEDCBA0987654321);
}
TEST(NVMeMetricUtilsTest, ExtractHeaderReturnsNulloptForShortBuffer) {
std::string buffer(sizeof(NVMeMetricHeader) - 1, 'A');
std::optional<NVMeMetricHeader> result = extractHeader(buffer);
EXPECT_FALSE(result.has_value());
}
TEST_F(NVMeMetricTest, DefaultMetricCallbackHandlesInvalidCache) {
std::shared_ptr<bmcweb::AsyncResp> asyncResp =
std::make_shared<bmcweb::AsyncResp>();
defaultMetricCallback(asyncResp, std::nullopt);
EXPECT_EQ(asyncResp->res.result(),
boost::beast::http::status::internal_server_error);
}
TEST_F(NVMeMetricTest, CheckRequiredInterfacesReturnsTrueWhenAllIfacesPresent) {
sdbusplus::message::object_path path("/test/path");
std::vector<std::string> ifaceNames = {"iface1", "iface2", nvmeMetricInfc};
absl::flat_hash_set<std::string> requiredIfaces = {"iface1", "iface2"};
EXPECT_TRUE(checkRequiredInterfaces(share_async_resp_, path, ifaceNames,
requiredIfaces, nvmeMetricInfc));
EXPECT_NE(share_async_resp_->res.result(),
boost::beast::http::status::not_found);
}
TEST_F(NVMeMetricTest,
CheckRequiredInterfacesReturnsFalseWhenMetricIfaceMissing) {
sdbusplus::message::object_path path("/test/path");
std::vector<std::string> ifaceNames = {"iface1", "iface2"};
absl::flat_hash_set<std::string> requiredIfaces = {"iface1", "iface2"};
EXPECT_FALSE(checkRequiredInterfaces(share_async_resp_, path, ifaceNames,
requiredIfaces, nvmeMetricInfc));
EXPECT_EQ(share_async_resp_->res.result(),
boost::beast::http::status::not_found);
}
TEST_F(NVMeMetricTest,
CheckRequiredInterfacesReturnsFalseWhenRequiredIfaceMissing) {
sdbusplus::message::object_path path("/test/path");
std::vector<std::string> ifaceNames = {"iface1", nvmeMetricInfc};
absl::flat_hash_set<std::string> requiredIfaces = {"iface1", "iface2"};
EXPECT_FALSE(checkRequiredInterfaces(share_async_resp_, path, ifaceNames,
requiredIfaces, nvmeMetricInfc));
EXPECT_EQ(share_async_resp_->res.result(),
boost::beast::http::status::not_found);
}
TEST_F(NVMeMetricTest, DefaultMetricCallbackHandlesShortData) {
std::shared_ptr<bmcweb::AsyncResp> asyncResp =
std::make_shared<bmcweb::AsyncResp>();
std::string data = "short";
defaultMetricCallback(asyncResp, data);
EXPECT_EQ(asyncResp->res.result(),
boost::beast::http::status::internal_server_error);
}
TEST_F(NVMeMetricTest, NvmeMetricRefresherHandlesMissingNvmeInterface) {
sdbusplus::message::object_path path(
"/xyz/openbmc_project/inventory/system/board/ParentStorage/storage_nvme/"
"controllers/0");
dbus::utility::MapperServiceMap ifaces = {
{"other.service", {"other.interface"}}};
nvmeMetricRefresher(share_async_resp_, path, ifaces, metricId, {});
EXPECT_EQ(share_async_resp_->res.result(),
boost::beast::http::status::not_found);
}
TEST_F(NVMeMetricTest, NvmeMetricRefresherHandlesMissingRequiredInterfaces) {
sdbusplus::message::object_path path(
"/xyz/openbmc_project/inventory/system/board/ParentStorage/storage_nvme/"
"controllers/0");
dbus::utility::MapperServiceMap ifaces = {
{"xyz.openbmc_project.NVMe", {"xyz.openbmc_project.NVMe.NVMeAdmin"}}};
// requiredIfaces = {nvmeMetricInfc} which is missing in ifaces
nvmeMetricRefresher(share_async_resp_, path, ifaces, metricId, {});
EXPECT_EQ(share_async_resp_->res.result(),
boost::beast::http::status::not_found);
}
TEST_F(NVMeMetricTest,
NvmeMetricRefresherHandlesMetricCollectionPropertyError) {
sdbusplus::message::object_path path(
"/xyz/openbmc_project/inventory/system/board/ParentStorage/storage_nvme/"
"controllers/0");
dbus::utility::MapperServiceMap ifaces = {
{"xyz.openbmc_project.NVMe",
{"xyz.openbmc_project.NVMe.NVMeAdmin",
"xyz.openbmc_project.NVMe.MetricStore"}}};
// Mock getProperty failure
KeyType key(ManagedType::kManagedProperty, "xyz.openbmc_project.NVMe",
path.str, "xyz.openbmc_project.NVMe.MetricStore",
"MetricCollection");
// We can't easily force a failure in getProperty if the key is not in store,
// it will try to call D-Bus. In MockManagedStore, we can use
// SimulateFailedAsyncPostDbusCallThreadSafeWithMsgAndEmptyValueAction if it
// was a PostDbusCall. getProperty uses getManagedObjects or getProperty
// depending on implementation. In bmcweb it usually calls
// managedStore::getProperty.
// Actually, nvmeMetricRefresher calls redfish::dbus_utils::getProperty which
// calls managedStore->getProperty.
// Let's mock a D-Bus error for the property call.
std::shared_ptr<ValueType> metricCollectionErr =
managedStore::MockManagedStoreTest::CreateErrorValueType(
dbus::utility::DbusVariantType(std::vector<std::string>{metricId}),
boost::system::errc::make_error_code(boost::system::errc::io_error));
ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
managedStore::GetManagedObjectStore())
->upsertMockObjectIntoManagedStore(key, metricCollectionErr)
.ok());
nvmeMetricRefresher(share_async_resp_, path, ifaces, metricId, {});
RunIoUntilDone();
EXPECT_EQ(share_async_resp_->res.result(),
boost::beast::http::status::internal_server_error);
}
TEST_F(NVMeMetricTest, NvmeMetricRefresherHandlesMetricIdNotFoundInCollection) {
sdbusplus::message::object_path path(
"/xyz/openbmc_project/inventory/system/board/ParentStorage/storage_nvme/"
"controllers/0");
dbus::utility::MapperServiceMap ifaces = {
{"xyz.openbmc_project.NVMe",
{"xyz.openbmc_project.NVMe.NVMeAdmin",
"xyz.openbmc_project.NVMe.MetricStore"}}};
// Mock MetricCollection property with different ID
KeyType propKey(ManagedType::kManagedProperty, "xyz.openbmc_project.NVMe",
path.str, "xyz.openbmc_project.NVMe.MetricStore",
"MetricCollection");
std::shared_ptr<ValueType> metricCollection =
managedStore::MockManagedStoreTest::CreateValueType<
dbus::utility::DbusVariantType>(
std::vector<std::string>{"OtherMetric"});
ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
managedStore::GetManagedObjectStore())
->upsertMockObjectIntoManagedStore(propKey, metricCollection)
.ok());
nvmeMetricRefresher(share_async_resp_, path, ifaces, metricId, {});
RunIoUntilDone();
EXPECT_EQ(share_async_resp_->res.result(),
boost::beast::http::status::not_found);
}
TEST_F(NVMeMetricTest, NvmeMetricRefresherHandlesUnavailableError) {
sdbusplus::message::object_path path(
"/xyz/openbmc_project/inventory/system/board/ParentStorage/storage_nvme/"
"controllers/0");
dbus::utility::MapperServiceMap ifaces = {
{"xyz.openbmc_project.NVMe",
{"xyz.openbmc_project.NVMe.NVMeAdmin",
"xyz.openbmc_project.NVMe.MetricStore"}}};
// Mock MetricCollection property
KeyType propKey(ManagedType::kManagedProperty, "xyz.openbmc_project.NVMe",
path.str, "xyz.openbmc_project.NVMe.MetricStore",
"MetricCollection");
std::shared_ptr<ValueType> metricCollection =
managedStore::MockManagedStoreTest::CreateValueType<
dbus::utility::DbusVariantType>(std::vector<std::string>{metricId});
ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
managedStore::GetManagedObjectStore())
->upsertMockObjectIntoManagedStore(propKey, metricCollection)
.ok());
// Mock RefreshMetric DBus call returning Unavailable
testing::NiceMock<sdbusplus::SdBusMock> sdbus;
sd_bus_error err = SD_BUS_ERROR_NULL;
sd_bus_error_set(&err, "xyz.openbmc_project.Common.Error.Unavailable",
"Service Unavailable");
EXPECT_CALL(sdbus, sd_bus_message_get_error(testing::_))
.WillRepeatedly(testing::Return(&err));
sdbusplus::message_t msg = sdbusplus::get_mocked_new(&sdbus).new_method_call(
nullptr, nullptr, nullptr, nullptr);
EXPECT_CALL(
*dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
managedStore::GetManagedObjectStore()),
PostDbusCallWithStringRetMsgAndFd(
_,
An<absl::AnyInvocable<void(const boost::system::error_code&,
const sdbusplus::message_t&,
const sdbusplus::message::unix_fd&)>&&>(),
"xyz.openbmc_project.NVMe", path.str,
"xyz.openbmc_project.NVMe.MetricStore", "RefreshMetric", metricId))
.WillOnce(
[&msg](
const std::shared_ptr<boost::asio::io_context::strand>&,
absl::AnyInvocable<void(
const boost::system::error_code&, const sdbusplus::message_t&,
const sdbusplus::message::unix_fd&)>&& callback,
const std::string&, const std::string&, const std::string&,
const std::string&, const std::string&) {
std::move(callback)(boost::system::errc::make_error_code(
boost::system::errc::io_error),
msg, sdbusplus::message::unix_fd{-1});
});
nvmeMetricRefresher(share_async_resp_, path, ifaces, metricId, {});
RunIoUntilDone();
// Callback should be called with nullopt, which sets internalError
EXPECT_EQ(share_async_resp_->res.result(),
boost::beast::http::status::internal_server_error);
sd_bus_error_free(&err);
}
TEST_F(NVMeMetricTest, NvmeMetricRefresherHandlesGeneralDbusError) {
sdbusplus::message::object_path path(
"/xyz/openbmc_project/inventory/system/board/ParentStorage/storage_nvme/"
"controllers/0");
dbus::utility::MapperServiceMap ifaces = {
{"xyz.openbmc_project.NVMe",
{"xyz.openbmc_project.NVMe.NVMeAdmin",
"xyz.openbmc_project.NVMe.MetricStore"}}};
// Mock MetricCollection property
KeyType propKey(ManagedType::kManagedProperty, "xyz.openbmc_project.NVMe",
path.str, "xyz.openbmc_project.NVMe.MetricStore",
"MetricCollection");
std::shared_ptr<ValueType> metricCollection =
managedStore::MockManagedStoreTest::CreateValueType<
dbus::utility::DbusVariantType>(std::vector<std::string>{metricId});
ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
managedStore::GetManagedObjectStore())
->upsertMockObjectIntoManagedStore(propKey, metricCollection)
.ok());
// Mock RefreshMetric DBus call returning General Error
testing::NiceMock<sdbusplus::SdBusMock> sdbus;
sd_bus_error err = SD_BUS_ERROR_NULL;
sd_bus_error_set(&err, "org.freedesktop.DBus.Error.Failed", "General Error");
EXPECT_CALL(sdbus, sd_bus_message_get_error(testing::_))
.WillRepeatedly(testing::Return(&err));
sdbusplus::message_t msg = sdbusplus::get_mocked_new(&sdbus).new_method_call(
nullptr, nullptr, nullptr, nullptr);
EXPECT_CALL(
*dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
managedStore::GetManagedObjectStore()),
PostDbusCallWithStringRetMsgAndFd(
_,
An<absl::AnyInvocable<void(const boost::system::error_code&,
const sdbusplus::message_t&,
const sdbusplus::message::unix_fd&)>&&>(),
"xyz.openbmc_project.NVMe", path.str,
"xyz.openbmc_project.NVMe.MetricStore", "RefreshMetric", metricId))
.WillOnce(
[&msg](
const std::shared_ptr<boost::asio::io_context::strand>&,
absl::AnyInvocable<void(
const boost::system::error_code&, const sdbusplus::message_t&,
const sdbusplus::message::unix_fd&)>&& callback,
const std::string&, const std::string&, const std::string&,
const std::string&, const std::string&) {
std::move(callback)(boost::system::errc::make_error_code(
boost::system::errc::io_error),
msg, sdbusplus::message::unix_fd{-1});
});
nvmeMetricRefresher(share_async_resp_, path, ifaces, metricId, {});
RunIoUntilDone();
EXPECT_EQ(share_async_resp_->res.result(),
boost::beast::http::status::internal_server_error);
sd_bus_error_free(&err);
}
} // namespace
} // namespace redfish