blob: cc449b95e1012ceb8c1f00642bf1f26c4b1c27ed [file]
#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