Improve error handling for Redfish Chassis Drive endpoints

Google-Bug-Id:552840290
PiperOrigin-RevId: 980889855
Change-Id: Ie01fedebcf093881a859f6ed5723f0dbe911eeaa
diff --git a/redfish-core/lib/storage.hpp b/redfish-core/lib/storage.hpp
index 1a25c2e..67c31e6 100644
--- a/redfish-core/lib/storage.hpp
+++ b/redfish-core/lib/storage.hpp
@@ -30,6 +30,7 @@
 
 #include <algorithm>
 #include <array>
+#include <cerrno>
 #include <charconv>
 #include <chrono>  // NOLINT
 #include <cstdint>
@@ -1137,6 +1138,7 @@
   }
 
   // Iterate over all retrieved ObjectPaths.
+  bool found = false;
   for (const auto& [path, connectionNames] : subtree) {
     sdbusplus::message::object_path objPath(path);
     if (objPath.filename() != driveName) {
@@ -1148,6 +1150,8 @@
       continue;
     }
 
+    found = true;
+
     asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
         "redfish", "v1", "Chassis", chassisId, "Drives", driveName);
 
@@ -1210,6 +1214,10 @@
     addAllDriveInfo(asyncResp, driveName, connectionNames[0].first, path,
                     connectionNames[0].second, chassisId);
   }
+  if (!found) {
+    messages::resourceNotFound(asyncResp->res, "#Drive.v1_7_0.Drive",
+                               driveName);
+  }
 }
 
 inline void matchAndFillDrive(
@@ -1577,8 +1585,19 @@
                                      chassisId);
           return;
         }
-        dbus_utils::getAssociationEndPoints(chassisPath + "/drive",
-                                            requestContext, cb);
+        dbus_utils::getAssociationEndPoints(
+            chassisPath + "/drive", requestContext,
+            [cb{std::move(cb)}](const boost::system::error_code& ec2,
+                                const dbus::utility::MapperEndPoints& resp) {
+              if (ec2) {
+                if (ec2.value() == EBADR || ec2.value() == ENOENT) {
+                  // No drive association endpoints found on this chassis.
+                  cb(boost::system::error_code{}, {});
+                  return;
+                }
+              }
+              cb(ec2, resp);
+            });
       });
 }
 
@@ -1594,7 +1613,9 @@
       [asyncResp, chassisId, driveName](const boost::system::error_code ec,
                                         const std::vector<std::string>& resp) {
         if (ec) {
-          return;  // no drives = no failures
+          BMCWEB_LOG_ERROR << "DBUS response error " << ec;
+          messages::internalError(asyncResp->res);
+          return;
         }
         matchAndFillDrive(asyncResp, chassisId, driveName, resp);
       });
@@ -1829,9 +1850,9 @@
       [asyncResp, driveId, resetType](const boost::system::error_code ec,
                                       const std::vector<std::string>& drives) {
         if (ec) {
-          BMCWEB_LOG_ERROR << "failed to find drives";
+          BMCWEB_LOG_ERROR << "failed to find drives: " << ec;
           messages::internalError(asyncResp->res);
-          return;  // no drives = no failures
+          return;
         }
         handleChassisDriveReset(asyncResp, driveId, resetType, drives);
       });
@@ -1965,9 +1986,9 @@
       [asyncResp, chassisId, driveId](const boost::system::error_code ec,
                                       const std::vector<std::string>& drives) {
         if (ec) {
-          BMCWEB_LOG_ERROR << "failed to find drives";
+          BMCWEB_LOG_ERROR << "failed to find drives: " << ec;
           messages::internalError(asyncResp->res);
-          return;  // no drives = no failures
+          return;
         }
         handleChassisDriveResetActionInfo(asyncResp, chassisId, driveId,
                                           drives);
diff --git a/test/redfish-core/lib/storage_test.cpp b/test/redfish-core/lib/storage_test.cpp
index e9c5f2c..bcd042b 100644
--- a/test/redfish-core/lib/storage_test.cpp
+++ b/test/redfish-core/lib/storage_test.cpp
@@ -2,6 +2,7 @@
 
 #include <systemd/sd-bus.h>
 
+#include <cerrno>
 #include <filesystem>
 #include <memory>
 #include <string>
@@ -898,5 +899,306 @@
             "Google/Metrics/FirstMetric");
   EXPECT_EQ(json["Name"], "FirstMetric");
 }
+
+TEST_F(StorageSnapshotFixture, HandleChassisDriveGetInvalidDriveNotFound) {
+  handleChassisDriveGet(app_, CreateRequest(), share_async_resp_, "platform1",
+                        "invalid_drive");
+  RunIoUntilDone();
+
+  EXPECT_EQ(share_async_resp_->res.result(),
+            boost::beast::http::status::not_found);
+}
+
+TEST_F(StorageSnapshotFixture, HandleChassisDriveGetInvalidChassisNotFound) {
+  handleChassisDriveGet(app_, CreateRequest(), share_async_resp_,
+                        "invalid_chassis", "invalid_drive");
+  RunIoUntilDone();
+
+  EXPECT_EQ(share_async_resp_->res.result(),
+            boost::beast::http::status::not_found);
+}
+
+TEST_F(StorageSnapshotFixture, HandleChassisDriveGetEbadr) {
+  managedStore::KeyType driveAssocKey(
+      managedStore::ManagedType::kManagedProperty,
+      "xyz.openbmc_project.ObjectMapper",
+      sdbusplus::message::object_path(
+          "/xyz/openbmc_project/inventory/system/board/platform1/drive"),
+      "xyz.openbmc_project.Association", "endpoints");
+  ASSERT_TRUE(
+      dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
+          managedStore::GetManagedObjectStore())
+          ->upsertMockObjectIntoManagedStore(
+              driveAssocKey, CreateErrorValueType(
+                                 dbus::utility::DbusVariantType{},
+                                 boost::system::error_code(
+                                     EBADR, boost::system::generic_category())))
+          .ok());
+
+  handleChassisDriveGet(app_, CreateRequest(), share_async_resp_, "platform1",
+                        "invalid_drive");
+  RunIoUntilDone();
+
+  EXPECT_EQ(share_async_resp_->res.result(),
+            boost::beast::http::status::not_found);
+}
+
+TEST_F(StorageSnapshotFixture, HandleChassisDriveGetEnoent) {
+  managedStore::KeyType driveAssocKey(
+      managedStore::ManagedType::kManagedProperty,
+      "xyz.openbmc_project.ObjectMapper",
+      sdbusplus::message::object_path(
+          "/xyz/openbmc_project/inventory/system/board/platform1/drive"),
+      "xyz.openbmc_project.Association", "endpoints");
+  ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
+                  managedStore::GetManagedObjectStore())
+                  ->upsertMockObjectIntoManagedStore(
+                      driveAssocKey,
+                      CreateErrorValueType(
+                          dbus::utility::DbusVariantType{},
+                          boost::system::error_code(
+                              ENOENT, boost::system::generic_category())))
+                  .ok());
+
+  handleChassisDriveGet(app_, CreateRequest(), share_async_resp_, "platform1",
+                        "invalid_drive");
+  RunIoUntilDone();
+
+  EXPECT_EQ(share_async_resp_->res.result(),
+            boost::beast::http::status::not_found);
+}
+
+TEST_F(StorageSnapshotFixture, HandleChassisDriveGetDbusError) {
+  managedStore::KeyType driveAssocKey(
+      managedStore::ManagedType::kManagedProperty,
+      "xyz.openbmc_project.ObjectMapper",
+      sdbusplus::message::object_path(
+          "/xyz/openbmc_project/inventory/system/board/platform1/drive"),
+      "xyz.openbmc_project.Association", "endpoints");
+  ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
+                  managedStore::GetManagedObjectStore())
+                  ->upsertMockObjectIntoManagedStore(
+                      driveAssocKey,
+                      CreateErrorValueType(dbus::utility::DbusVariantType{},
+                                           boost::system::errc::make_error_code(
+                                               boost::system::errc::io_error)))
+                  .ok());
+
+  handleChassisDriveGet(app_, CreateRequest(), share_async_resp_, "platform1",
+                        "invalid_drive");
+  RunIoUntilDone();
+
+  EXPECT_EQ(share_async_resp_->res.result(),
+            boost::beast::http::status::internal_server_error);
+}
+
+TEST_F(StorageSnapshotFixture, BuildDriveFound) {
+  KeyType storageSubtreeKey(ManagedType::kManagedSubtree,
+                            "/xyz/openbmc_project/inventory", 0,
+                            {"xyz.openbmc_project.Inventory.Item.Storage"});
+  ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
+                  managedStore::GetManagedObjectStore())
+                  ->upsertMockObjectIntoManagedStore(
+                      storageSubtreeKey,
+                      managedStore::MockManagedStoreTest::CreateValueType(
+                          dbus::utility::MapperGetSubTreeResponse{}))
+                  .ok());
+
+  std::string driveName = "drive_0";
+  dbus::utility::MapperGetSubTreeResponse mockSubtree = {
+      {"/xyz/openbmc_project/inventory/drive_0",
+       {{"xyz.openbmc_project.Drive", {}}}},
+  };
+
+  buildDrive(share_async_resp_, "platform1", driveName,
+             boost::system::error_code{}, mockSubtree);
+  RunIoUntilDone();
+
+  EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok);
+  EXPECT_EQ(share_async_resp_->res.jsonValue["@odata.id"],
+            "/redfish/v1/Chassis/platform1/Drives/drive_0");
+  EXPECT_EQ(share_async_resp_->res.jsonValue["Name"], "drive_0");
+  EXPECT_EQ(share_async_resp_->res.jsonValue["Id"], "drive_0");
+  EXPECT_EQ(share_async_resp_->res.jsonValue["Status"]["State"], "Enabled");
+}
+
+TEST_F(StorageSnapshotFixture, BuildDriveNotFound) {
+  dbus::utility::MapperGetSubTreeResponse mockSubtree = {
+      {"/xyz/openbmc_project/inventory/other_drive",
+       {{"xyz.openbmc_project.Drive", {}}}},
+  };
+
+  buildDrive(share_async_resp_, "platform1", "drive_0",
+             boost::system::error_code{}, mockSubtree);
+  RunIoUntilDone();
+
+  EXPECT_EQ(share_async_resp_->res.result(),
+            boost::beast::http::status::not_found);
+}
+
+TEST_F(StorageSnapshotFixture, BuildDriveEmptyConnectionNames) {
+  dbus::utility::MapperGetSubTreeResponse mockSubtree = {
+      {"/xyz/openbmc_project/inventory/drive_0", {}},
+  };
+
+  buildDrive(share_async_resp_, "platform1", "drive_0",
+             boost::system::error_code{}, mockSubtree);
+  RunIoUntilDone();
+
+  EXPECT_EQ(share_async_resp_->res.result(),
+            boost::beast::http::status::not_found);
+}
+
+TEST_F(StorageSnapshotFixture, BuildDriveDbusError) {
+  buildDrive(
+      share_async_resp_, "platform1", "drive_0",
+      boost::system::errc::make_error_code(boost::system::errc::io_error), {});
+  RunIoUntilDone();
+
+  EXPECT_EQ(share_async_resp_->res.result(),
+            boost::beast::http::status::internal_server_error);
+}
+
+TEST_F(StorageSnapshotFixture, HandleChassisDriveGetValidDrive) {
+  KeyType storageSubtreeKey(ManagedType::kManagedSubtree,
+                            "/xyz/openbmc_project/inventory", 0,
+                            {"xyz.openbmc_project.Inventory.Item.Storage"});
+  ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
+                  managedStore::GetManagedObjectStore())
+                  ->upsertMockObjectIntoManagedStore(
+                      storageSubtreeKey,
+                      managedStore::MockManagedStoreTest::CreateValueType(
+                          dbus::utility::MapperGetSubTreeResponse{}))
+                  .ok());
+
+  managedStore::KeyType driveAssocKey(
+      managedStore::ManagedType::kManagedProperty,
+      "xyz.openbmc_project.ObjectMapper",
+      sdbusplus::message::object_path(
+          "/xyz/openbmc_project/inventory/system/board/platform1/drive"),
+      "xyz.openbmc_project.Association", "endpoints");
+  dbus::utility::MapperEndPoints driveEndpoints = {
+      "/xyz/openbmc_project/inventory/system/chassis/drive0"};
+  ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
+                  managedStore::GetManagedObjectStore())
+                  ->upsertMockObjectIntoManagedStore(
+                      driveAssocKey,
+                      managedStore::MockManagedStoreTest::CreateValueType(
+                          dbus::utility::DbusVariantType(driveEndpoints)))
+                  .ok());
+
+  managedStore::KeyType driveSubtreeKey(
+      managedStore::ManagedType::kManagedSubtree,
+      "/xyz/openbmc_project/inventory", 0,
+      {"xyz.openbmc_project.Inventory.Item.Drive"});
+  dbus::utility::MapperGetSubTreeResponse mockDriveSubtree = {
+      {"/xyz/openbmc_project/inventory/system/chassis/drive0",
+       {{"xyz.openbmc_project.DriveService", {}}}},
+  };
+  ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
+                  managedStore::GetManagedObjectStore())
+                  ->upsertMockObjectIntoManagedStore(
+                      driveSubtreeKey,
+                      managedStore::MockManagedStoreTest::CreateValueType(
+                          std::move(mockDriveSubtree)))
+                  .ok());
+
+  handleChassisDriveGet(app_, CreateRequest(), share_async_resp_, "platform1",
+                        "drive0");
+  RunIoUntilDone();
+
+  EXPECT_EQ(share_async_resp_->res.result(), boost::beast::http::status::ok);
+  EXPECT_EQ(share_async_resp_->res.jsonValue["@odata.id"],
+            "/redfish/v1/Chassis/platform1/Drives/drive0");
+  EXPECT_EQ(share_async_resp_->res.jsonValue["Name"], "drive0");
+  EXPECT_EQ(share_async_resp_->res.jsonValue["Id"], "drive0");
+}
+
+TEST_F(StorageSnapshotFixture, HandleChassisDriveGetDriveNotInSubtree) {
+  managedStore::KeyType driveAssocKey(
+      managedStore::ManagedType::kManagedProperty,
+      "xyz.openbmc_project.ObjectMapper",
+      sdbusplus::message::object_path(
+          "/xyz/openbmc_project/inventory/system/board/platform1/drive"),
+      "xyz.openbmc_project.Association", "endpoints");
+  dbus::utility::MapperEndPoints driveEndpoints = {
+      "/xyz/openbmc_project/inventory/system/chassis/drive0"};
+  ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
+                  managedStore::GetManagedObjectStore())
+                  ->upsertMockObjectIntoManagedStore(
+                      driveAssocKey,
+                      managedStore::MockManagedStoreTest::CreateValueType(
+                          dbus::utility::DbusVariantType(driveEndpoints)))
+                  .ok());
+
+  managedStore::KeyType driveSubtreeKey(
+      managedStore::ManagedType::kManagedSubtree,
+      "/xyz/openbmc_project/inventory", 0,
+      {"xyz.openbmc_project.Inventory.Item.Drive"});
+  dbus::utility::MapperGetSubTreeResponse mockDriveSubtree = {};
+  ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
+                  managedStore::GetManagedObjectStore())
+                  ->upsertMockObjectIntoManagedStore(
+                      driveSubtreeKey,
+                      managedStore::MockManagedStoreTest::CreateValueType(
+                          std::move(mockDriveSubtree)))
+                  .ok());
+
+  handleChassisDriveGet(app_, CreateRequest(), share_async_resp_, "platform1",
+                        "drive0");
+  RunIoUntilDone();
+
+  EXPECT_EQ(share_async_resp_->res.result(),
+            boost::beast::http::status::not_found);
+}
+
+TEST_F(StorageSnapshotFixture, HandlePostDriveResetActionDbusError) {
+  managedStore::KeyType driveAssocKey(
+      managedStore::ManagedType::kManagedProperty,
+      "xyz.openbmc_project.ObjectMapper",
+      sdbusplus::message::object_path(
+          "/xyz/openbmc_project/inventory/system/board/platform1/drive"),
+      "xyz.openbmc_project.Association", "endpoints");
+  ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
+                  managedStore::GetManagedObjectStore())
+                  ->upsertMockObjectIntoManagedStore(
+                      driveAssocKey,
+                      CreateErrorValueType(dbus::utility::DbusVariantType{},
+                                           boost::system::errc::make_error_code(
+                                               boost::system::errc::io_error)))
+                  .ok());
+
+  handlePostDriveResetAction(app_, CreateRequest(), share_async_resp_,
+                             "platform1", "drive_0");
+  RunIoUntilDone();
+
+  EXPECT_EQ(share_async_resp_->res.result(),
+            boost::beast::http::status::internal_server_error);
+}
+
+TEST_F(StorageSnapshotFixture, HandleGetDriveResetActionInfoDbusError) {
+  managedStore::KeyType driveAssocKey(
+      managedStore::ManagedType::kManagedProperty,
+      "xyz.openbmc_project.ObjectMapper",
+      sdbusplus::message::object_path(
+          "/xyz/openbmc_project/inventory/system/board/platform1/drive"),
+      "xyz.openbmc_project.Association", "endpoints");
+  ASSERT_TRUE(dynamic_cast<managedStore::MockSerializedManagedObjectStore*>(
+                  managedStore::GetManagedObjectStore())
+                  ->upsertMockObjectIntoManagedStore(
+                      driveAssocKey,
+                      CreateErrorValueType(dbus::utility::DbusVariantType{},
+                                           boost::system::errc::make_error_code(
+                                               boost::system::errc::io_error)))
+                  .ok());
+
+  handleGetDriveResetActionInfo(app_, CreateRequest(), share_async_resp_,
+                                "platform1", "drive_0");
+  RunIoUntilDone();
+
+  EXPECT_EQ(share_async_resp_->res.result(),
+            boost::beast::http::status::internal_server_error);
+}
+
 }  // namespace
 }  // namespace redfish