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