Support injecting PhysicalLocation instead of Location in Redfish devpath injector

This change adds a use_physical_location option to the Redfish expose configuration. When enabled, the injector writes location properties to the "PhysicalLocation" field instead of "Location".

Google-Bug-Id:511148872
PiperOrigin-RevId: 978192791
Change-Id: I13cba09751e4b43518230eb1e9750e3b81454788
diff --git a/devpath_plugin/redfish_devpath_injector.cpp b/devpath_plugin/redfish_devpath_injector.cpp
index 97e6673..fbd287c 100644
--- a/devpath_plugin/redfish_devpath_injector.cpp
+++ b/devpath_plugin/redfish_devpath_injector.cpp
@@ -99,24 +99,29 @@
   milotic_tlbmc::StableId stable_id = milotic_tlbmc::GetStableId(
       location_context, /*root_chassis_location_code=*/"");
 
-  nlohmann::json& json = res.jsonValue;
+  std::string_view location_key =
+      value->redfish_expose_config.use_physical_location() ? "PhysicalLocation"
+                                                           : "Location";
+  nlohmann::json& location_json = res.jsonValue[location_key];
 
-  json["Location"]["PartLocation"]["ServiceLabel"] = stable_id.service_label();
+  location_json["PartLocation"]["ServiceLabel"] = stable_id.service_label();
 
   if (value->redfish_expose_config.set_plc_as_placeholder()) {
-    json["Location"]["PartLocationContext"] = "PlaceholderAndShouldNotBeUsed";
+    location_json["PartLocationContext"] = "PlaceholderAndShouldNotBeUsed";
   } else if (stable_id.has_part_location_context() &&
              !stable_id.part_location_context().empty()) {
-    json["Location"]["PartLocationContext"] = stable_id.part_location_context();
+    location_json["PartLocationContext"] = stable_id.part_location_context();
+  } else {
+    location_json.erase("PartLocationContext");
   }
 
   if (value->redfish_expose_config.expose_oem_google_devpath()) {
-    json["Location"]["Oem"]["Google"]["Devpath"] = barepath;
+    location_json["Oem"]["Google"]["Devpath"] = barepath;
   }
 
   if (stable_id.has_embedded_location_context() &&
       !stable_id.embedded_location_context().empty()) {
-    json["Location"]["Oem"]["Google"]["EmbeddedLocationContext"] =
+    location_json["Oem"]["Google"]["EmbeddedLocationContext"] =
         stable_id.embedded_location_context();
   }
 }
diff --git a/devpath_plugin/redfish_to_uhm_mapping.proto b/devpath_plugin/redfish_to_uhm_mapping.proto
index 45c6feb..0fec16e 100644
--- a/devpath_plugin/redfish_to_uhm_mapping.proto
+++ b/devpath_plugin/redfish_to_uhm_mapping.proto
@@ -29,6 +29,7 @@
   bool expose_oem_google_devpath = 1;
   bool set_plc_as_placeholder = 2;
   string embedded_location_context = 3;
+  bool use_physical_location = 4;
 }
 
 // Individual Redfish to UHM entry.
diff --git a/test/devpath_plugin/redfish_devpath_injector_test.cpp b/test/devpath_plugin/redfish_devpath_injector_test.cpp
index b58c17c..cc449b9 100644
--- a/test/devpath_plugin/redfish_devpath_injector_test.cpp
+++ b/test/devpath_plugin/redfish_devpath_injector_test.cpp
@@ -98,6 +98,15 @@
         set_plc_as_placeholder: false
       }
     }
+    redfish_to_uhm_mapping {
+      key { redfish_url_pattern: "/redfish/v1/Chassis/ChassisA/Drives/Drive0" }
+      uhm_monitored_component_key { barepath: "/phys/BoardA/SLOT0" }
+      redfish_expose_config {
+        expose_oem_google_devpath: true
+        set_plc_as_placeholder: false
+        use_physical_location: true
+      }
+    }
   )pb";
   std::string config_path =
       WriteTmpFile("injectable_config.textproto", config_content);
@@ -153,6 +162,129 @@
     EXPECT_EQ(json["Location"]["PartLocation"]["ServiceLabel"], "BoardA");
     EXPECT_FALSE(json["Location"].contains("PartLocationContext"));
   }
+
+  // Test Component 4 with use_physical_location enabled
+  {
+    crow::Request req =
+        CreateRequest("/redfish/v1/Chassis/ChassisA/Drives/Drive0");
+    crow::Response res;
+    res.result(boost::beast::http::status::ok);
+
+    (*injector)->Inject(req, res);
+
+    EXPECT_EQ(res.jsonValue, nlohmann::json::parse(R"json(
+      {
+        "PhysicalLocation": {
+          "Oem": {
+            "Google": {
+              "Devpath": "/phys/BoardA/SLOT0"
+            }
+          },
+          "PartLocation": {
+            "ServiceLabel": "SLOT0"
+          },
+          "PartLocationContext": "BoardA"
+        }
+      }
+    )json"));
+  }
+}
+
+TEST(RedfishDevpathInjectorTest,
+     InjectPhysicalLocationOverridesPreexistingFields) {
+  std::string config_content = R"pb(
+    redfish_to_uhm_mapping {
+      key { redfish_url_pattern: "/redfish/v1/Chassis/ChassisA/Drives/Drive1" }
+      uhm_monitored_component_key { barepath: "/phys/HPM0/DOWNLINK/PE1A/SLOT2" }
+      redfish_expose_config {
+        expose_oem_google_devpath: true
+        set_plc_as_placeholder: false
+        use_physical_location: true
+      }
+    }
+    redfish_to_uhm_mapping {
+      key { redfish_url_pattern: "/redfish/v1/Chassis/ChassisA/Drives/Drive2" }
+      uhm_monitored_component_key { barepath: "/phys/SLOT0" }
+      redfish_expose_config {
+        expose_oem_google_devpath: false
+        set_plc_as_placeholder: false
+        use_physical_location: true
+      }
+    }
+  )pb";
+  std::string config_path =
+      WriteTmpFile("override_config.textproto", config_content);
+
+  absl::StatusOr<std::unique_ptr<RedfishDevpathInjector>> injector =
+      RedfishDevpathInjector::Create(config_path);
+  ASSERT_OK(injector.status());
+
+  // Case 1: Pre-existing wrong Devpath and placeholder PLC from handler are
+  // overwritten when expose_oem_google_devpath is true.
+  {
+    crow::Request req =
+        CreateRequest("/redfish/v1/Chassis/ChassisA/Drives/Drive1");
+    crow::Response res;
+    res.result(boost::beast::http::status::ok);
+    res.jsonValue = nlohmann::json::parse(R"json(
+      {
+        "PhysicalLocation": {
+          "Oem": {
+            "Google": {
+              "Devpath": "/phys/HPM0/DOWNLINK/PE1A/DOWNLINK"
+            }
+          },
+          "PartLocationContext": "PlaceHolderAndShouldNotBeUsed"
+        }
+      }
+    )json");
+
+    (*injector)->Inject(req, res);
+
+    EXPECT_EQ(res.jsonValue, nlohmann::json::parse(R"json(
+      {
+        "PhysicalLocation": {
+          "Oem": {
+            "Google": {
+              "Devpath": "/phys/HPM0/DOWNLINK/PE1A/SLOT2"
+            }
+          },
+          "PartLocation": {
+            "ServiceLabel": "SLOT2"
+          },
+          "PartLocationContext": "HPM0/DOWNLINK/PE1A"
+        }
+      }
+    )json"));
+  }
+
+  // Case 2: Pre-existing placeholder PLC is erased when part_location_context
+  // is empty and set_plc_as_placeholder is false.
+  {
+    crow::Request req =
+        CreateRequest("/redfish/v1/Chassis/ChassisA/Drives/Drive2");
+    crow::Response res;
+    res.result(boost::beast::http::status::ok);
+    res.jsonValue = nlohmann::json::parse(R"json(
+      {
+        "PhysicalLocation": {
+          "PartLocationContext": "PlaceHolderAndShouldNotBeUsed"
+        }
+      }
+    )json");
+
+    (*injector)->Inject(req, res);
+
+    EXPECT_EQ(res.jsonValue, nlohmann::json::parse(R"json(
+      {
+        "PhysicalLocation": {
+          "PartLocation": {
+            "ServiceLabel": "SLOT0"
+          }
+        }
+      }
+    )json"));
+  }
 }
 
 TEST(RedfishDevpathInjectorTest, InjectNonGetRequestReturnsNoOp) {