| #include "redfish_devpath_injector.hpp" |
| |
| #include <fstream> |
| #include <memory> |
| #include <string> |
| #include <string_view> |
| #include <system_error> // NOLINT |
| |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/str_cat.h" |
| #include "boost/beast/http/status.hpp" // NOLINT |
| #include "boost/beast/http/verb.hpp" // NOLINT |
| #include "http_request.hpp" |
| #include "http_response.hpp" |
| #include <nlohmann/json.hpp> |
| |
| using ::testing::status::StatusIs; |
| |
| namespace devpath_plugin { |
| namespace { |
| |
| std::string WriteTmpFile(std::string_view name, std::string_view content) { |
| std::string path = absl::StrCat(testing::TempDir(), "/", name); |
| std::ofstream out(path); |
| out << content; |
| out.close(); |
| return path; |
| } |
| |
| crow::Request CreateRequest( |
| std::string_view path, |
| boost::beast::http::verb method = boost::beast::http::verb::get) { |
| std::error_code ec; |
| crow::Request req(std::string_view(""), ec); |
| req.set_method(method); |
| req.target(path); |
| return req; |
| } |
| |
| TEST(RedfishDevpathInjectorTest, LoadValidConfigSuccess) { |
| std::string config_content = R"pb( |
| redfish_to_uhm_mapping { |
| key { redfish_url_pattern: "/redfish/v1/RootOfTrustCollection/RotA" } |
| uhm_monitored_component_key { barepath: "/phys/BoardA:device:rot_a" } |
| redfish_expose_config { |
| expose_oem_google_devpath: true |
| set_plc_as_placeholder: false |
| embedded_location_context: "rot_context_a" |
| } |
| } |
| )pb"; |
| std::string config_path = |
| WriteTmpFile("valid_config.textproto", config_content); |
| |
| ASSERT_OK(RedfishDevpathInjector::Create(config_path).status()); |
| } |
| |
| TEST(RedfishDevpathInjectorTest, LoadInvalidConfigReturnsError) { |
| std::string invalid_config_path = |
| WriteTmpFile("invalid_config.textproto", "invalid_content"); |
| EXPECT_THAT(RedfishDevpathInjector::Create(invalid_config_path), |
| StatusIs(absl::StatusCode::kInvalidArgument)); |
| } |
| |
| TEST(RedfishDevpathInjectorTest, NonExistentConfigReturnsError) { |
| EXPECT_THAT( |
| RedfishDevpathInjector::Create("/path/to/non_existent_file.textproto"), |
| StatusIs(absl::StatusCode::kNotFound)); |
| } |
| |
| TEST(RedfishDevpathInjectorTest, InjectValidMappingSuccess) { |
| std::string config_content = R"pb( |
| redfish_to_uhm_mapping { |
| key { redfish_url_pattern: "/redfish/v1/RootOfTrustCollection/RotA" } |
| uhm_monitored_component_key { barepath: "/phys/BoardA:device:rot_a" } |
| redfish_expose_config { |
| expose_oem_google_devpath: true |
| set_plc_as_placeholder: false |
| embedded_location_context: "rot_context_a" |
| } |
| } |
| redfish_to_uhm_mapping { |
| key { redfish_url_pattern: "/redfish/v1/Chassis/ChassisA" } |
| uhm_monitored_component_key { barepath: "/phys/BoardA" } |
| redfish_expose_config { |
| expose_oem_google_devpath: false |
| set_plc_as_placeholder: true |
| } |
| } |
| redfish_to_uhm_mapping { |
| key { redfish_url_pattern: "/redfish/v1/Chassis/ChassisB" } |
| uhm_monitored_component_key { barepath: "/phys/BoardA" } |
| redfish_expose_config { |
| expose_oem_google_devpath: false |
| 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); |
| |
| absl::StatusOr<std::unique_ptr<RedfishDevpathInjector>> injector = |
| RedfishDevpathInjector::Create(config_path); |
| ASSERT_OK(injector.status()); |
| |
| // Test Component 1 with :device: |
| { |
| crow::Request req = CreateRequest("/redfish/v1/RootOfTrustCollection/RotA"); |
| crow::Response res; |
| res.result(boost::beast::http::status::ok); |
| |
| (*injector)->Inject(req, res); |
| |
| const nlohmann::json& json = res.jsonValue; |
| EXPECT_EQ(json["Location"]["PartLocation"]["ServiceLabel"], "rot_a"); |
| EXPECT_EQ(json["Location"]["PartLocationContext"], "BoardA"); |
| EXPECT_EQ(json["Location"]["Oem"]["Google"]["Devpath"], |
| "/phys/BoardA:device:rot_a"); |
| EXPECT_EQ(json["Location"]["Oem"]["Google"]["EmbeddedLocationContext"], |
| "rot_context_a"); |
| } |
| |
| // Test Component 2 without :device: and with placeholder plc |
| { |
| crow::Request req = CreateRequest("/redfish/v1/Chassis/ChassisA"); |
| crow::Response res; |
| res.result(boost::beast::http::status::ok); |
| |
| (*injector)->Inject(req, res); |
| |
| nlohmann::json json = res.jsonValue; |
| EXPECT_EQ(json["Location"]["PartLocation"]["ServiceLabel"], "BoardA"); |
| EXPECT_EQ(json["Location"]["PartLocationContext"], |
| "PlaceholderAndShouldNotBeUsed"); |
| |
| EXPECT_FALSE(json["Location"]["Oem"]["Google"].contains("Devpath")); |
| EXPECT_FALSE( |
| json["Location"]["Oem"]["Google"].contains("EmbeddedLocationContext")); |
| } |
| |
| // Test Component 3 where PLC is empty and shouldn't appear |
| { |
| crow::Request req = CreateRequest("/redfish/v1/Chassis/ChassisB"); |
| crow::Response res; |
| res.result(boost::beast::http::status::ok); |
| |
| (*injector)->Inject(req, res); |
| |
| nlohmann::json json = res.jsonValue; |
| 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) { |
| std::string config_content = R"pb( |
| redfish_to_uhm_mapping { |
| key { redfish_url_pattern: "/redfish/v1/RootOfTrustCollection/RotA" } |
| uhm_monitored_component_key { barepath: "/phys/BoardA:device:rot_a" } |
| } |
| )pb"; |
| std::string config_path = |
| WriteTmpFile("nonget_config.textproto", config_content); |
| |
| absl::StatusOr<std::unique_ptr<RedfishDevpathInjector>> injector = |
| RedfishDevpathInjector::Create(config_path); |
| ASSERT_OK(injector.status()); |
| |
| crow::Request req = CreateRequest("/redfish/v1/RootOfTrustCollection/RotA", |
| boost::beast::http::verb::post); |
| crow::Response res; |
| res.result(boost::beast::http::status::ok); |
| |
| (*injector)->Inject(req, res); |
| |
| EXPECT_TRUE(res.jsonValue.empty()); |
| } |
| |
| TEST(RedfishDevpathInjectorTest, InjectNonOkStatusReturnsNoOp) { |
| std::string config_content = R"pb( |
| redfish_to_uhm_mapping { |
| key { redfish_url_pattern: "/redfish/v1/RootOfTrustCollection/RotA" } |
| uhm_monitored_component_key { barepath: "/phys/BoardA:device:rot_a" } |
| } |
| )pb"; |
| std::string config_path = |
| WriteTmpFile("nonok_config.textproto", config_content); |
| |
| absl::StatusOr<std::unique_ptr<RedfishDevpathInjector>> injector = |
| RedfishDevpathInjector::Create(config_path); |
| ASSERT_OK(injector.status()); |
| |
| crow::Request req = CreateRequest("/redfish/v1/RootOfTrustCollection/RotA"); |
| crow::Response res; |
| res.result(boost::beast::http::status::not_found); |
| |
| (*injector)->Inject(req, res); |
| |
| EXPECT_TRUE(res.jsonValue.empty()); |
| } |
| |
| TEST(RedfishDevpathInjectorTest, InjectNonExistentUrlReturnsNoOp) { |
| std::string config_content = R"pb( |
| redfish_to_uhm_mapping { |
| key { redfish_url_pattern: "/redfish/v1/RootOfTrustCollection/RotA" } |
| uhm_monitored_component_key { barepath: "/phys/BoardA:device:rot_a" } |
| } |
| )pb"; |
| std::string config_path = |
| WriteTmpFile("nonexistent_config.textproto", config_content); |
| |
| absl::StatusOr<std::unique_ptr<RedfishDevpathInjector>> injector = |
| RedfishDevpathInjector::Create(config_path); |
| ASSERT_OK(injector.status()); |
| |
| crow::Request req = |
| CreateRequest("/redfish/v1/RootOfTrustCollection/DeviceB"); |
| crow::Response res; |
| res.result(boost::beast::http::status::ok); |
| |
| (*injector)->Inject(req, res); |
| |
| EXPECT_TRUE(res.jsonValue.empty()); |
| } |
| |
| TEST(RedfishDevpathInjectorTest, InjectRegexMappingSuccess) { |
| std::string config_content = R"pb( |
| redfish_to_uhm_mapping { |
| key { |
| redfish_url_pattern: "/redfish/v1/Systems/system/Storage/StorageModuleA/Controllers/[0-9]+" |
| } |
| uhm_monitored_component_key { |
| barepath: "/phys/SlotA:device:controller_a" |
| } |
| redfish_expose_config { |
| expose_oem_google_devpath: true |
| set_plc_as_placeholder: false |
| embedded_location_context: "controller:0" |
| } |
| } |
| )pb"; |
| std::string config_path = |
| WriteTmpFile("regex_config.textproto", config_content); |
| |
| absl::StatusOr<std::unique_ptr<RedfishDevpathInjector>> injector = |
| RedfishDevpathInjector::Create(config_path); |
| ASSERT_OK(injector.status()); |
| |
| // Test correct match |
| { |
| crow::Request req = CreateRequest( |
| "/redfish/v1/Systems/system/Storage/StorageModuleA/Controllers/0"); |
| crow::Response res; |
| res.result(boost::beast::http::status::ok); |
| |
| (*injector)->Inject(req, res); |
| |
| const nlohmann::json& json = res.jsonValue; |
| EXPECT_EQ(json["Location"]["PartLocation"]["ServiceLabel"], "controller_a"); |
| EXPECT_EQ(json["Location"]["PartLocationContext"], "SlotA"); |
| EXPECT_EQ(json["Location"]["Oem"]["Google"]["Devpath"], |
| "/phys/SlotA:device:controller_a"); |
| EXPECT_EQ(json["Location"]["Oem"]["Google"]["EmbeddedLocationContext"], |
| "controller:0"); |
| } |
| |
| // Test path not fitting the regex |
| { |
| crow::Request req = CreateRequest( |
| "/redfish/v1/Systems/system/Storage/StorageModuleA/Controllers/abc"); |
| crow::Response res; |
| res.result(boost::beast::http::status::ok); |
| |
| (*injector)->Inject(req, res); |
| |
| EXPECT_TRUE(res.jsonValue.empty()); |
| } |
| } |
| |
| } // namespace |
| } // namespace devpath_plugin |