gbmcweb: storage: add metric store for volume
Tested: https://paste.googleplex.com/6324674117763072
Google-Bug-Id: 366274716
Change-Id: I6cdd18e324f044a7243448084d27f538dbb016e6
Signed-off-by: Munawar Hussain <munawarhussain@google.com>
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index d3dca3b..8ba9325 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -151,6 +151,7 @@
requestDriveResetAction(app);
requestRoutesDriveResetActionInfo(app);
requestRoutesStorageControllerMetric(app);
+ requestRoutesStorageVolumeMetric(app);
#ifdef BMCWEB_INSECURE_ENABLE_REDFISH_FW_TFTP_UPDATE
requestRoutesUpdateServiceActionsSimpleUpdate(app);
#endif
diff --git a/redfish-core/include/utils/nvme_metric_utils.hpp b/redfish-core/include/utils/nvme_metric_utils.hpp
index 0020b94..984daf2 100644
--- a/redfish-core/include/utils/nvme_metric_utils.hpp
+++ b/redfish-core/include/utils/nvme_metric_utils.hpp
@@ -17,6 +17,10 @@
namespace redfish
{
+
+using MetricStoreCb =
+ std::function<void(const std::shared_ptr<bmcweb::AsyncResp>& resp,
+ const std::optional<std::string>& data)>;
// Define the header struct
struct NVMeMetricHeader
@@ -73,8 +77,7 @@
inline void fetchFileUtil(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::shared_ptr<boost::asio::posix::stream_descriptor>& fileConn,
- std::function<void(const std::shared_ptr<bmcweb::AsyncResp>& resp,
- const std::string& data)>&& cb,
+ MetricStoreCb&& cb,
const std::shared_ptr<std::array<char, 1024>>& readBuffer =
std::make_shared<std::array<char, 1024>>(),
const std::shared_ptr<std::string>& output =
@@ -117,8 +120,7 @@
inline void tryPopulateMetricCollection(
const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
const std::string& path, const dbus::utility::MapperServiceMap& ifaces,
- const std::string& systemName, const std::string& storageId,
- const std::string& controllerId)
+ const boost::urls::url& parentUrl)
{
managedStore::ManagedObjectStoreContext context(asyncResp);
auto service = storage_utils::matchServiceName(ifaces, INFC);
@@ -128,8 +130,8 @@
}
redfish::storage_utils::getProperty<std::vector<std::string>>(
*service, path, INFC, "MetricCollection", context,
- [asyncResp, systemName, storageId,
- controllerId](const boost::system::error_code& ec,
+ [asyncResp,
+ parentUrl](const boost::system::error_code& ec,
const std::vector<std::string>& metricCollection) {
if (ec)
{
@@ -138,11 +140,10 @@
for (const std::string& metric : metricCollection)
{
- asyncResp->res.jsonValue["Oem"]["Google"][metric]["DataUri"] =
- crow::utility::urlFromPieces("redfish", "v1", "Systems",
- systemName, "Storage", storageId,
- "Controllers", controllerId, "Oem",
- "Google", "Metrics", metric);
+ boost::urls::url dataUri(parentUrl);
+ crow::utility::appendUrlPieces(dataUri, "Oem",
+ "Google", "Metrics", metric);
+ asyncResp->res.jsonValue["Oem"]["Google"][metric]["DataUri"] = dataUri;
}
});
}
@@ -153,9 +154,17 @@
inline void
defaultMetricCallback(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
- const std::string& output)
+ const std::optional<std::string>& output)
{
- std::optional<NVMeMetricHeader> header = extractHeader(output);
+ // if the output is nullopt then the cache is invalid
+ if (!output.has_value())
+ {
+ BMCWEB_LOG_ERROR << "Failed to read metric: Cache invalid";
+ redfish::messages::internalError(asyncResp->res);
+ return;
+ }
+ const std::string& data = output.value();
+ std::optional<NVMeMetricHeader> header = extractHeader(data);
if (!header.has_value())
{
BMCWEB_LOG_ERROR << "Failed to read metric header";
@@ -168,117 +177,152 @@
std::to_string(header.value().finishTime);
asyncResp->res.jsonValue["Metric"] = std::move(
- ::crow::utility::base64encode(output.substr(sizeof(NVMeMetricHeader))));
+ ::crow::utility::base64encode(data.substr(sizeof(NVMeMetricHeader))));
return;
}
+/**
+ * @details fetch the metric in the given resource dbus path from metric store
+ * and trigger the cb with the data if succeed.
+ *
+ * Exception Handling:
+ * If the expected service or the required interfaces or the metric itself is
+ * not available, it will set resource not found in the provided asyncResp and
+ * will not trigger cb. If while fetching metric, the service throws dbus
+ * exception Unavailable, then that means the service could not cache the metric
+ * yet. In that case the callback cb will be called with data as nullopt. The
+ * consumer can either set internal error or treat that as success case. For all
+ * other exceptions, it will set internal error in asyncResp and will not
+ * trigger cb.
+ */
template <const char* INFC = nvmeMetricInfc>
-inline void nvmeMetricFetcher(
- const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
- const std::string& storageId, const std::string& controllerId,
- const std::string& metricId,
- std::function<void(const std::shared_ptr<bmcweb::AsyncResp>& resp,
- const std::string& data)>&& cb = defaultMetricCallback)
+inline void
+ nvmeMetricFetcher(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const sdbusplus::message::object_path& resourcePath,
+ const dbus::utility::MapperServiceMap& resourceIfaces,
+ const std::string& metricId,
+ std::unordered_set<std::string> requiredIfaces,
+ MetricStoreCb&& cb = defaultMetricCallback)
{
// Find the controller and then create a dbus call to the metric name
// Before you do check for the presence of the interface using association
// get sub tree
+ auto foundService = std::find_if(
+ resourceIfaces.begin(), resourceIfaces.end(),
+ [](const std::pair<std::string,
+ dbus::utility::MapperGetSubTreePathsResponse>&
+ pair) { return pair.first == "xyz.openbmc_project.NVMe"; });
- redfish::storage_utils::findStorageAndController(
- asyncResp, storageId, controllerId,
- [asyncResp, storageId, controllerId, metricId, cb{std::move(cb)}](
- const sdbusplus::message::object_path& path,
- const dbus::utility::MapperServiceMap& ifaces) mutable {
- auto foundService = std::find_if(
- ifaces.begin(), ifaces.end(),
- [](const std::pair<std::string,
- dbus::utility::MapperGetSubTreePathsResponse>&
- pair) { return pair.first == "xyz.openbmc_project.NVMe"; });
+ if (foundService == resourceIfaces.end())
+ {
+ redfish::messages::resourceNotFound(
+ asyncResp->res, "xyz.openbmc_project.NVMe", resourcePath.str);
+ return;
+ }
+ const auto& service = foundService->first;
+ auto ifaceNames = foundService->second;
- if (foundService == ifaces.end())
+ // examine if the controller resource has all required interfaces for
+ // metric
+
+ std::unordered_set<std::string> checklist = { {INFC}};
+ checklist.insert(requiredIfaces.begin(), requiredIfaces.end());
+
+ for (const auto& iface : ifaceNames)
+ {
+ auto find = checklist.find(iface);
+ if (find != checklist.end())
{
- redfish::messages::resourceNotFound(
- asyncResp->res, "xyz.openbmc_project.NVMe", storageId);
+ checklist.erase(*find);
+ }
+ if (checklist.empty())
+ {
+ break;
+ }
+ }
+ if (!checklist.empty())
+ {
+ redfish::messages::resourceNotFound(
+ asyncResp->res, *(checklist.begin()), resourcePath.str);
+ return;
+ }
+
+ // query the metric from MetricStore
+ managedStore::ManagedObjectStoreContext requestContext(asyncResp);
+
+ redfish::dbus_utils::getProperty<std::vector<std::string>>(
+ service, resourcePath, INFC, "MetricCollection", requestContext,
+ [asyncResp, service, resourcePath, metricId, cb{std::move(cb)}](
+ const boost::system::error_code ec,
+ const std::vector<std::string>& metricCollection) mutable {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << std::format(
+ "[{}, {}]Failed to enumerate Metric", resourcePath.str, metricId);
+ redfish::messages::internalError(asyncResp->res);
return;
}
- const auto& service = foundService->first;
- auto ifaceNames = foundService->second;
- // exame if the controller resource has all required interfaces for
- // metric
-
- std::unordered_set<std::string> checklist = {
- {"xyz.openbmc_project.NVMe.NVMeAdmin"}, {INFC}};
-
- for (const auto& iface : ifaceNames)
- {
- auto find = checklist.find(iface);
- if (find != checklist.end())
- {
- checklist.erase(*find);
- }
- if (checklist.empty())
- {
- break;
- }
- }
- if (!checklist.empty())
+ auto res = std::find(metricCollection.begin(),
+ metricCollection.end(), metricId);
+ if (res == metricCollection.end())
{
redfish::messages::resourceNotFound(asyncResp->res, INFC,
- controllerId);
+ metricId);
return;
}
- // query the metric from MetricStore
- managedStore::ManagedObjectStoreContext requestContext(asyncResp);
-
- redfish::dbus_utils::getProperty<std::vector<std::string>>(
- service, path, INFC, "MetricCollection", requestContext,
- [asyncResp, service, path, metricId, cb{std::move(cb)}](
- const boost::system::error_code ec,
- const std::vector<std::string>& metricCollection) mutable {
+ // read metric
+ managedStore::GetManagedObjectStore()
+ ->PostDbusCallToIoContextThreadSafe(
+ asyncResp->strand_,
+ [asyncResp, service, resourcePath, metricId, cb{std::move(cb)}](
+ const boost::system::error_code ec,
+ const sdbusplus::message_t& msg,
+ const sdbusplus::message::unix_fd& fd) mutable {
if (ec)
{
+ const sd_bus_error* dbusError = msg.get_error();
+ if (std::string_view(
+ "xyz.openbmc_project.Common.Error.Unavailable") ==
+ dbusError->name)
+ {
+ // if cache is invalid then the service throws unavailble
+ cb(asyncResp, std::nullopt);
+ return;
+ }
BMCWEB_LOG_ERROR << std::format(
- "[{}, {}]Failed to enumerate Metric", path.str, metricId);
+ "[{}, {}]Failed to read Metric", resourcePath.str, metricId);
redfish::messages::internalError(asyncResp->res);
return;
}
- auto res = std::find(metricCollection.begin(),
- metricCollection.end(), metricId);
- if (res == metricCollection.end())
- {
- redfish::messages::resourceNotFound(asyncResp->res, INFC,
- metricId);
- return;
- }
+ int dupFd = dup(fd.fd);
+ fetchFileUtil(
+ asyncResp,
+ std::make_shared<boost::asio::posix::stream_descriptor>(
+ managedStore::GetManagedObjectStore()->GetIoContext(),
+ dupFd),
+ std::move(cb));
+ },
+ service, resourcePath, INFC, "GetMetric", metricId);
+ });
+}
- // read metric
- managedStore::GetManagedObjectStore()
- ->PostDbusCallToIoContextThreadSafe(
- asyncResp->strand_,
- [asyncResp, service, path, metricId, cb{std::move(cb)}](
- const boost::system::error_code ec,
- const sdbusplus::message::unix_fd& fd) mutable {
- if (ec)
- {
- BMCWEB_LOG_ERROR << std::format(
- "[{}, {}]Failed to read Metric", path.str, metricId);
- redfish::messages::internalError(asyncResp->res);
- return;
- }
-
- int dupFd = dup(fd.fd);
- fetchFileUtil(
- asyncResp,
- std::make_shared<boost::asio::posix::stream_descriptor>(
- managedStore::GetManagedObjectStore()->GetIoContext(),
- dupFd),
- std::move(cb));
- },
- service, path, INFC, "GetMetric", metricId);
- });
+template <const char* INFC = nvmeMetricInfc>
+inline void nvmeControllerMetricFetcher(
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const std::string& storageId, const std::string& controllerId,
+ const std::string& metricId, MetricStoreCb&& cb = defaultMetricCallback)
+{
+ redfish::storage_utils::findStorageAndController(
+ asyncResp, storageId, controllerId,
+ [asyncResp, metricId, cb{std::move(cb)}](
+ const sdbusplus::message::object_path& path,
+ const dbus::utility::MapperServiceMap& ifaces) mutable {
+ nvmeMetricFetcher<INFC>(asyncResp, path, ifaces, metricId,
+ {"xyz.openbmc_project.NVMe.NVMeAdmin"},
+ std::move(cb));
});
}
} // namespace redfish
diff --git a/redfish-core/lib/storage.hpp b/redfish-core/lib/storage.hpp
index 85ba87a..1edad9c 100644
--- a/redfish-core/lib/storage.hpp
+++ b/redfish-core/lib/storage.hpp
@@ -2406,8 +2406,7 @@
interfaces);
populateStorageControllerAttached(asyncResp, systemName, storageId, path);
tryPopulateControllerNvme(asyncResp, path, ifaces);
- tryPopulateMetricCollection(asyncResp, path, ifaces, systemName, storageId,
- controllerId);
+ tryPopulateMetricCollection(asyncResp, path, ifaces, url);
tryPopulateControllerSecurity(asyncResp, url, ifaces);
if (enableCustomSSD)
{
@@ -3264,6 +3263,8 @@
}
});
+ tryPopulateMetricCollection(asyncResp, path, ifaces, url);
+
auto findIfaces = std::find_if(
ifaces.begin(), ifaces.end(),
[connectionName](
@@ -4012,7 +4013,50 @@
// TODO: verify system and storage relation
- nvmeMetricFetcher(asyncResp, storage, cntrl, metric);
+ nvmeControllerMetricFetcher(asyncResp, storage, cntrl, metric);
+ });
+}
+
+void handleStorageVolumeMetricGet(
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const std::string& system, const std::string& storage,
+ const std::string& volume, const std::string& metric)
+{
+ asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
+ "redfish", "v1", "Systems", system, "Storage", storage,
+ "Volumes", volume, "Oem", "Google", "Metrics", metric);
+
+ asyncResp->res.jsonValue["Name"] = metric;
+
+ checkSystemAndStorage(asyncResp, system, storage);
+
+ findStorageVolume(
+ asyncResp, storage, volume,
+ [asyncResp, metric](const std::string& /* sPath */,
+ const std::string& vPath,
+ const std::string& /* connectionName */,
+ const dbus::utility::MapperServiceMap& ifaces) {
+ nvmeMetricFetcher(asyncResp, vPath, ifaces, metric, {});
+ });
+}
+
+inline void requestRoutesStorageVolumeMetric(App& app)
+{
+ BMCWEB_ROUTE(
+ app,
+ "/redfish/v1/Systems/<str>/Storage/<str>/Volumes/<str>/Oem/Google/Metrics/<str>")
+ .privileges(redfish::privileges::privilegeSetLogin)
+ .methods(boost::beast::http::verb::get)(
+ [&app](const crow::Request& req,
+ const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+ const std::string& system, const std::string& storage,
+ const std::string& volume, const std::string& metric) {
+ if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+ {
+ return;
+ }
+ handleStorageVolumeMetricGet(asyncResp, system, storage, volume,
+ metric);
});
}
diff --git a/test/redfish-core/lib/storage_test.cpp b/test/redfish-core/lib/storage_test.cpp
index d43db09..0f85747 100644
--- a/test/redfish-core/lib/storage_test.cpp
+++ b/test/redfish-core/lib/storage_test.cpp
@@ -9,6 +9,7 @@
#include <boost/beast/core/string_type.hpp>
#include <boost/beast/http/message.hpp>
#include <nlohmann/json.hpp>
+#include <sdbusplus/test/sdbus_mock.hpp>
#include <system_error>
#include <unordered_set>
@@ -23,6 +24,10 @@
using ::managedStore::KeyType;
using ::managedStore::ManagedType;
+using ::managedStore::
+ SimulateFailedAsyncPostDbusCallThreadSafeWithEmptyValueAction;
+using ::managedStore::
+ SimulateFailedAsyncPostDbusCallThreadSafeWithMsgAndEmptyValueAction;
using ::managedStore::SimulateFailedAsyncSetPropertyDbusCallAction;
using ::managedStore::SimulateSuccessfulAsyncSetPropertyDbusCallAction;
using ::managedStore::ValueType;
@@ -220,11 +225,14 @@
/**
* @brief: Mock a parent object that containing the target object
* @param[in] objectPath the current object path
- * @param[in] objectType the current object type presented as dbus interface
+ * @param[in] objectType the current object type presented as dbus
+ interface
* @param[in] parentConnection service name for the parent object
- * @param[in] parentPath parent object path; return empty list on empty string
+ * @param[in] parentPath parent object path; return empty list on empty
+ string
* @param[in] parentType parent type present as dbus interace
- * @param[in] serviceLabel parent service_label/location_code property; return
+ * @param[in] serviceLabel parent service_label/location_code property;
+ return
* ec on dbus read when empty string
*/
void mockParent(const std::string& objectPath, const std::string& objectType,
@@ -586,18 +594,23 @@
}
void addVolumeToMockStorage(std::string storagePath, std::string volumeName,
- std::string& volumePath)
+ std::string& volumePath,
+ bool includeMetricStore = true)
{
+ std::vector<std::string> volIfaces(
+ {"xyz.openbmc_project.Inventory.Item.Volume",
+ "xyz.openbmc_project.Nvme.Volume"});
+ if (includeMetricStore)
+ {
+ volIfaces.push_back("xyz.openbmc_project.NVMe.MetricStore");
+ }
volumePath = storagePath + "/volumes/" + volumeName;
KeyType getAssociatedSubTreeKey(
ManagedType::kManagedAssociatedSubtree, storagePath + "/containing",
"/xyz/openbmc_project/inventory", 0,
{"xyz.openbmc_project.Inventory.Item.Volume"});
dbus::utility::MapperGetSubTreeResponse mockSubtreeResponse{
- {volumePath,
- {{"xyz.openbmc_project.NVMe",
- std::vector<std::string>{"xyz.openbmc_project.Inventory.Item.Volume",
- "xyz.openbmc_project.Nvme.Volume"}}}}};
+ {volumePath, {{"xyz.openbmc_project.NVMe", volIfaces}}}};
std::shared_ptr<ValueType> subtree =
managedStore::MockManagedStoreTest::CreateValueType(
std::move(mockSubtreeResponse));
@@ -666,29 +679,32 @@
.ok());
}
-TEST_F(StorgeSnapshotFixture, StorageVolumeHandlerCheck)
+void mockMetricStore(std::string volumePath, bool failed = false)
{
- std::string storageName = "storage_nvme";
- std::string volumeName = "1";
- std::string storagePath;
- mockStorageObject(storageName, storagePath);
- std::string volumePath;
- addVolumeToMockStorage(storagePath, volumeName, volumePath);
- mockItemVolumeData(volumePath);
- mockNvmeVolumeData(volumePath);
+ KeyType key(ManagedType::kManagedProperty, "xyz.openbmc_project.NVMe",
+ volumePath, "xyz.openbmc_project.NVMe.MetricStore",
+ "MetricCollection");
+ if (failed)
+ {
+ ASSERT_TRUE(managedStore::GetManagedObjectStore()
+ ->evictMockObjectFromManagedStore(key)
+ .ok());
+ }
+ else
+ {
+ std::shared_ptr<ValueType> metricCollection =
+ managedStore::MockManagedStoreTest::CreateValueType<
+ dbus::utility::DbusVariantType>(std::move(
+ std::vector<std::string>({"FirstMetric", "SecondMetric"})));
+ ASSERT_TRUE(
+ managedStore::GetManagedObjectStore()
+ ->upsertMockObjectIntoManagedStore(key, metricCollection)
+ .ok());
+ }
+}
- // Skip location check due to it's complexity and
- // since this test is targeting Metrics
- mockAvoidLocationCheckForVolume(volumePath);
-
- storageVolumeHandler(app_, CreateRequest(), share_async_resp_, "system",
- storageName, volumeName);
- RunIoUntilDone();
-
- EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok);
-
- nlohmann::json& json = share_async_resp_->res.jsonValue;
-
+void checkGenericVolumeMockData(nlohmann::json& json)
+{
EXPECT_EQ(json["@odata.id"],
"/redfish/v1/Systems/system/Storage/storage_nvme/Volumes/1");
EXPECT_EQ(json["@odata.type"], "#Volume.v1_9_0.Volume");
@@ -703,6 +719,39 @@
EXPECT_EQ(json["Name"], "Namespace 1");
}
+TEST_F(StorgeSnapshotFixture, StorageVolumeHandlerCheck)
+{
+ std::string storageName = "storage_nvme";
+ std::string volumeName = "1";
+ std::string storagePath;
+ mockStorageObject(storageName, storagePath);
+ std::string volumePath;
+ addVolumeToMockStorage(storagePath, volumeName, volumePath);
+ mockItemVolumeData(volumePath);
+ mockNvmeVolumeData(volumePath);
+ mockMetricStore(volumePath);
+
+ // Skip location check due to it's complexity and
+ // since this test is targeting Metrics
+ mockAvoidLocationCheckForVolume(volumePath);
+
+ storageVolumeHandler(app_, CreateRequest(), share_async_resp_, "system",
+ storageName, volumeName);
+ RunIoUntilDone();
+
+ EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok);
+
+ nlohmann::json& json = share_async_resp_->res.jsonValue;
+
+ checkGenericVolumeMockData(json);
+ EXPECT_EQ(
+ json["Oem"]["Google"]["FirstMetric"]["DataUri"],
+ "/redfish/v1/Systems/system/Storage/storage_nvme/Volumes/1/Oem/Google/Metrics/FirstMetric");
+ EXPECT_EQ(
+ json["Oem"]["Google"]["SecondMetric"]["DataUri"],
+ "/redfish/v1/Systems/system/Storage/storage_nvme/Volumes/1/Oem/Google/Metrics/SecondMetric");
+}
+
TEST_F(StorgeSnapshotFixture, StorageVolumeHandlerCheckWithoutItemVolume)
{
std::string storageName = "storage_nvme";
@@ -712,6 +761,7 @@
std::string volumePath;
addVolumeToMockStorage(storagePath, volumeName, volumePath);
mockNvmeVolumeData(volumePath);
+ mockMetricStore(volumePath);
// remove item volume from store
// previous testcases can affect this, hence removing
@@ -741,7 +791,120 @@
EXPECT_FALSE(json.contains("NVMeNamespaceProperties"));
EXPECT_EQ(json["Id"], "1");
EXPECT_EQ(json["Name"], std::string("Volume ") + volumeName);
+ EXPECT_EQ(
+ json["Oem"]["Google"]["FirstMetric"]["DataUri"],
+ "/redfish/v1/Systems/system/Storage/storage_nvme/Volumes/1/Oem/Google/Metrics/FirstMetric");
+ EXPECT_EQ(
+ json["Oem"]["Google"]["SecondMetric"]["DataUri"],
+ "/redfish/v1/Systems/system/Storage/storage_nvme/Volumes/1/Oem/Google/Metrics/SecondMetric");
}
+TEST_F(StorgeSnapshotFixture, StorageVolumeHandlerCheckWithoutMetricStore)
+{
+ std::string storageName = "storage_nvme";
+ std::string volumeName = "1";
+ std::string storagePath;
+ mockStorageObject(storageName, storagePath);
+ std::string volumePath;
+ addVolumeToMockStorage(storagePath, volumeName, volumePath, false);
+ mockItemVolumeData(volumePath);
+ mockNvmeVolumeData(volumePath);
+
+ // Skip location check due to it's complexity and
+ // since this test is targeting Metrics
+ mockAvoidLocationCheckForVolume(volumePath);
+
+ storageVolumeHandler(app_, CreateRequest(), share_async_resp_, "system",
+ storageName, volumeName);
+ RunIoUntilDone();
+
+ EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok);
+
+ nlohmann::json& json = share_async_resp_->res.jsonValue;
+
+ checkGenericVolumeMockData(json);
+ EXPECT_FALSE(json["Oem"]["Google"].contains("FirstMetric"));
+}
+
+TEST_F(StorgeSnapshotFixture, StorageVolumeHandlerCheckWithMetricStoreError)
+{
+ std::string storageName = "storage_nvme";
+ std::string volumeName = "1";
+ std::string storagePath;
+ mockStorageObject(storageName, storagePath);
+ std::string volumePath;
+ addVolumeToMockStorage(storagePath, volumeName, volumePath);
+ mockItemVolumeData(volumePath);
+ mockNvmeVolumeData(volumePath);
+ mockMetricStore(volumePath, true);
+
+ // Skip location check due to it's complexity and
+ // since this test is targeting Metrics
+ mockAvoidLocationCheckForVolume(volumePath);
+
+ storageVolumeHandler(app_, CreateRequest(), share_async_resp_, "system",
+ storageName, volumeName);
+ RunIoUntilDone();
+
+ EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok);
+
+ nlohmann::json& json = share_async_resp_->res.jsonValue;
+
+ checkGenericVolumeMockData(json);
+ EXPECT_FALSE(json["Oem"]["Google"].contains("FirstMetric"));
+}
+
+TEST_F(StorgeSnapshotFixture, StorageVolumeGetMetricError)
+{
+ std::string storageName = "storage_nvme";
+ std::string volumeName = "1";
+ std::string storagePath;
+ mockStorageObject(storageName, storagePath);
+ std::string volumePath;
+ addVolumeToMockStorage(storagePath, volumeName, volumePath);
+ mockMetricStore(volumePath);
+ std::string metricId = "FirstMetric";
+
+ 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;
+ err.name = "xyz.openbmc_project.Common.Error.Unavailable";
+
+ EXPECT_CALL(sdbus, sd_bus_message_get_error(testing::_))
+ .WillRepeatedly(testing::Return(&err));
+ // intentionally failing
+ // want to test only properties not the metric data since there is no
+ // existing model to mock fd
+ sdbusplus::message_t msg =
+ sdbusplus::get_mocked_new(&sdbus).new_method_call(nullptr, nullptr,
+ nullptr, nullptr);
+ EXPECT_CALL(
+ *managedStore::GetManagedObjectStore(),
+ PostDbusCallToIoContextThreadSafe(
+ _,
+ An<absl::AnyInvocable<void(
+ const boost::system::error_code&, const sdbusplus::message_t&,
+ const sdbusplus::message::unix_fd&)>&&>(),
+ "xyz.openbmc_project.NVMe", volumePath,
+ "xyz.openbmc_project.NVMe.MetricStore", "GetMetric", metricId))
+ .Times(testing::AtMost(1))
+ .WillOnce(
+ SimulateFailedAsyncPostDbusCallThreadSafeWithMsgAndEmptyValueAction::
+ SimulateFailedAsyncPostDbusCallWithMsgAndEmptyValue(msg));
+ handleStorageVolumeMetricGet(share_async_resp_, "system", storageName,
+ volumeName, metricId);
+ RunIoUntilDone();
+
+ EXPECT_EQ(share_async_resp_->res.result(),
+ boost::beast::http::status::internal_server_error);
+ nlohmann::json& json = share_async_resp_->res.jsonValue;
+ EXPECT_EQ(
+ json["@odata.id"],
+ "/redfish/v1/Systems/system/Storage/storage_nvme/Volumes/1/Oem/Google/Metrics/FirstMetric");
+ EXPECT_EQ(json["Name"], "FirstMetric");
+}
} // namespace
} // namespace redfish