blob: 414fd07e0f55d15c38cc72e94e22e1c40fa279b1 [file] [log] [blame]
#include "proxy_builder.h"
#include <cstdlib>
#include <functional>
#include <memory>
#include <string>
#include <utility>
#include "file/base/filesystem.h"
#include "file/base/options.h"
#include "file/base/options.proto.h"
#include "net/grpc/public/include/grpcpp/impl/channel_credentials_google_impl.h"
#include "net/grpc/public/include/grpcpp/loas2_credentials_options.h"
#include "net/grpc/public/include/grpcpp/server_credentials_google.h"
#include "net/grpc/public/include/grpcpp/support/time_google.h"
#include "gmock.h"
#include "gunit.h"
#include "mock-log.h"
#include "absl/base/log_severity.h"
#include "absl/log/log.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/str_cat.h"
#include "absl/strings/str_format.h"
#include "absl/strings/string_view.h"
#include "absl/time/clock.h"
#include "absl/time/time.h"
#include "redfish_query_engine/file/test_filesystem.h"
#include "redfish_query_engine/protobuf/parse.h"
#include "redfish_query_engine/redfish_v1.grpc.pb.h"
#include "redfish_query_engine/redfish_v1.pb.h"
#include "redfish_query_engine/redfish/test_mockup.h"
#include "grpc/grpc_security_constants.h"
#include "grpcpp/channel.h"
#include "grpcpp/client_context.h"
#include "grpcpp/create_channel.h"
#include "grpcpp/security/credentials.h"
#include "grpcpp/security/server_credentials.h"
#include "grpcpp/support/client_callback.h"
#include "nlohmann/json.hpp"
#include "nlohmann/json_fwd.hpp"
#include "deferred_status.h"
#include "mock_ssh_client.h"
#include "proxy_config.pb.h"
#include "ssh_client.h"
#include "voyager/mock_executor.hpp"
#include "voyager/voyager_telemetry.grpc.pb.h"
#include "voyager/voyager_telemetry.pb.h"
#include "thread/thread.h"
#include "thread/thread_options.h"
#include "util/task/status_macros.h"
namespace milotic::internal {
extern absl::StatusOr<milotic_grpc_proxy::Configuration> LoadConfig(
const std::string& config_file_path);
} // namespace milotic::internal
namespace {
using ::base_logging::INFO;
using ::testing::_;
using ::testing::AnyNumber;
using ::testing::DoAll;
using ::testing::ElementsAre;
using ::testing::EqualsProto;
using ::testing::HasSubstr;
using ::testing::kDoNotCaptureLogsYet;
using ::testing::MockFunction;
using ::testing::Not;
using ::testing::Return;
using ::testing::SaveArg;
using ::testing::ScopedMockLog;
using ::testing::SizeIs;
using ::testing::StartsWith;
using ::testing::proto::Partially;
using ::testing::status::IsOk;
using ::testing::status::IsOkAndHolds;
using ::testing::status::StatusIs;
using ::ecclesia::TestFilesystem;
using ::ecclesia::TestingMockupServer;
using ::milotic::MockSshClient;
using ::voyager::MockExecutor;
constexpr absl::string_view kTestDataPath =
"/google3/testdata/";
constexpr absl::Duration kRequestTimeout = absl::Seconds(30);
std::string GetTestDataPath(absl::string_view filename) {
return absl::StrCat(std::getenv("TEST_SRCDIR"), kTestDataPath,
"config.pb.txt");
}
TEST(TestLoadConfig, LoadConfigWorks) {
auto expected =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
R"pb(proxy_configuration {
target {
scheme: TARGET_SCHEME_HTTP
hostname: "test target host"
port: 123
}
grpc_configuration {
net_endpoint: { host: "localhost" port: 9998 }
}
redfish_v1_options: {}
voyager_telemetry_options: {}
}
)pb");
EXPECT_THAT(milotic::internal::LoadConfig(GetTestDataPath("config.pb.txt")),
IsOkAndHolds(EqualsProto(expected)));
}
class BackgroundThread : public Thread {
public:
explicit BackgroundThread(milotic::ProxyBuilder& proxies)
: Thread(thread::Options().set_joinable(true), "background"),
proxies_(proxies) {}
void Run() override { result_ = proxies_.Wait(); }
absl::Status Finish() {
proxies_.Shutdown();
Join();
return result_;
}
private:
absl::Status result_;
milotic::ProxyBuilder& proxies_;
};
absl::StatusOr<std::string> GetName(absl::string_view json_str) {
nlohmann::json json = nlohmann::json::parse(json_str, nullptr, false);
if (json.is_discarded()) {
return absl::InvalidArgumentError(
absl::StrCat("Bad json: \"", json_str, "\""));
}
if (!json.contains("Name")) {
return absl::InvalidArgumentError("Missing 'Name'");
}
return json.at("Name");
}
absl::StatusOr<std::string> GetName(const third_party_voyager::Update& resp) {
if (resp.code() != 200) {
return absl::InternalError(absl::StrCat("HTTP error: ", resp.code()));
}
if (resp.data_points().empty()) {
return absl::NotFoundError("No data points");
}
return GetName(resp.data_points(0).json());
}
third_party_voyager::Request GetRequest(absl::string_view id) {
third_party_voyager::Request req;
req.set_req_id("test_get");
third_party_voyager::RequestFqp* req_fqp = req.add_req_fqp();
req_fqp->mutable_fqp()->set_specifier(id);
return req;
}
absl::StatusOr<std::string> GetName(
third_party_voyager::MachineTelemetry::Stub& stub, absl::string_view id) {
third_party_voyager::Request req = GetRequest(id);
grpc::ClientContext context;
context.set_deadline(absl::Now() + kRequestTimeout);
third_party_voyager::Update resp;
RETURN_IF_ERROR(stub.Get(&context, req, &resp));
return GetName(resp);
}
absl::StatusOr<std::string> NameFromVoyagerTelemetry(
const std::shared_ptr<grpc::Channel>& channel, absl::string_view id) {
std::unique_ptr<third_party_voyager::MachineTelemetry::Stub> stub(
third_party_voyager::MachineTelemetry::NewStub(channel));
return GetName(*stub, id);
}
class NameFromVoyagerTelemetryAsync {
public:
explicit NameFromVoyagerTelemetryAsync(
third_party_voyager::MachineTelemetry::Stub& stub, absl::string_view id)
: req_(GetRequest(id)) {
stub.async()->Get(&context_, &req_, &resp_, status_.Setter());
}
absl::StatusOr<std::string> GetName(
absl::Duration timeout = absl::Seconds(1)) {
RETURN_IF_ERROR(status_.AwaitStatus(timeout));
return ::GetName(resp_);
}
private:
milotic::DeferredStatus status_;
grpc::ClientContext context_;
const third_party_voyager::Request req_;
third_party_voyager::Update resp_;
};
absl::StatusOr<std::string> NameFromRedfishV1(
const std::shared_ptr<grpc::Channel>& channel, absl::string_view id) {
std::unique_ptr<redfish::v1::grpc_gen::RedfishV1::Stub> stub(
redfish::v1::grpc_gen::RedfishV1::NewStub(channel));
redfish::v1::Request req;
req.set_url(id);
grpc::ClientContext context;
context.set_deadline(absl::Now() + kRequestTimeout);
redfish::v1::Response resp;
RETURN_IF_ERROR(stub->Get(&context, req, &resp));
if (resp.code() != 200) {
return absl::InternalError(absl::StrCat("HTTP error: ", resp.code()));
}
return GetName(resp.json_str());
}
milotic::ProxyBuilder::Options GetOptions(const TestFilesystem* fs = nullptr,
bool insecure = true) {
milotic::ProxyBuilder::Options options;
if (fs != nullptr) {
options.SafeUdsRoot(
[fs](const std::string& root) { return root == fs->GetTruePath("/"); });
}
if (insecure) {
options.AddCredentials({.net_creds = grpc::InsecureServerCredentials(),
.uds_creds = grpc::InsecureServerCredentials()});
}
return options;
}
TEST(TestProxyBuilder, EmptyConfigFails) {
milotic_grpc_proxy::Configuration config;
TestFilesystem fs(ecclesia::GetTestTempUdsDirectory());
EXPECT_THAT(milotic::ProxyBuilder::CreateAndStart(config, GetOptions(&fs)),
StatusIs(absl::StatusCode::kInvalidArgument));
}
TEST(TestProxyBuilder, AllServicesSingleProxyOk) {
TestingMockupServer mockup_server("barebones_session_auth/mockup.shar");
TestFilesystem fs(ecclesia::GetTestTempUdsDirectory());
const auto [host, port] =
std::get<TestingMockupServer::ConfigNetwork>(mockup_server.GetConfig());
std::string uds_path = fs.GetTruePath("/test/proxy.socket");
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
absl::StrFormat(
R"pb(
proxy_configuration {
target { scheme: TARGET_SCHEME_HTTP hostname: "%s" port: %d }
grpc_configuration {
uds_endpoint { path: "%s" }
net_endpoint { host: "localhost" port: 9999 }
}
plugins: {
redfish_passthrough: {
request_timeout_msec: 30000
connect_timeout_msec: 5000
dns_timeout_sec: 60
max_recv_speed: -1
sse_low_speed_limit_bytes_per_sec: 100
sse_low_speed_time_sec: 30
}
}
redfish_v1_options {}
voyager_telemetry_options {}
resource_authorization_policy: {
mappings: {
name: "match_all"
allow: { key: "all" }
resource_path: ""
with_subtree: true
}
}
}
)pb",
host, port, uds_path));
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(config, GetOptions(&fs));
ASSERT_OK(proxies);
BackgroundThread bg(*proxies);
bg.Start();
std::shared_ptr<grpc::Channel> uds_channel = grpc::CreateChannel(
"unix://" + uds_path, grpc::InsecureChannelCredentials());
EXPECT_THAT(NameFromVoyagerTelemetry(uds_channel, "/redfish/v1"),
IsOkAndHolds("Root Service"));
EXPECT_THAT(NameFromRedfishV1(uds_channel, "/redfish/v1"),
IsOkAndHolds("Root Service"));
std::shared_ptr<grpc::Channel> net_channel =
grpc::CreateChannel("localhost:9999", grpc::InsecureChannelCredentials());
EXPECT_THAT(NameFromVoyagerTelemetry(net_channel, "/redfish/v1"),
IsOkAndHolds("Root Service"));
EXPECT_THAT(NameFromRedfishV1(net_channel, "/redfish/v1"),
IsOkAndHolds("Root Service"));
EXPECT_OK(bg.Finish());
}
TEST(TestProxyBuilder, RedfishV1OnlySingleProxyOk) {
TestingMockupServer mockup_server("barebones_session_auth/mockup.shar");
TestFilesystem fs(ecclesia::GetTestTempUdsDirectory());
const auto [host, port] =
std::get<TestingMockupServer::ConfigNetwork>(mockup_server.GetConfig());
std::string uds_path = fs.GetTruePath("/test/proxy.socket");
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
absl::StrFormat(
R"pb(
proxy_configuration {
target { scheme: TARGET_SCHEME_HTTP hostname: "%s" port: %d }
grpc_configuration {
uds_endpoint: { path: "%s" }
net_endpoint: { host: "localhost", port: 9999 }
}
plugins: {
redfish_passthrough: {
request_timeout_msec: 30000
connect_timeout_msec: 5000
dns_timeout_sec: 60
max_recv_speed: -1
sse_low_speed_limit_bytes_per_sec: 100
sse_low_speed_time_sec: 30
}
}
redfish_v1_options {}
resource_authorization_policy: {
mappings: {
name: "match_all"
allow: { key: "all" }
resource_path: ""
with_subtree: true
}
}
}
)pb",
host, port, uds_path));
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(config, GetOptions(&fs));
ASSERT_OK(proxies);
BackgroundThread bg(*proxies);
bg.Start();
std::shared_ptr<grpc::Channel> uds_channel = grpc::CreateChannel(
"unix://" + uds_path, grpc::InsecureChannelCredentials());
EXPECT_THAT(NameFromVoyagerTelemetry(uds_channel, "/redfish/v1"),
Not(IsOk()));
EXPECT_THAT(NameFromRedfishV1(uds_channel, "/redfish/v1"),
IsOkAndHolds("Root Service"));
std::shared_ptr<grpc::Channel> net_channel =
grpc::CreateChannel("localhost:9999", grpc::InsecureChannelCredentials());
EXPECT_THAT(NameFromVoyagerTelemetry(net_channel, "/redfish/v1"),
Not(IsOk()));
EXPECT_THAT(NameFromRedfishV1(net_channel, "/redfish/v1"),
IsOkAndHolds("Root Service"));
EXPECT_OK(bg.Finish());
}
TEST(TestProxyBuilder, VoyagerTelemetryOnlySingleProxyOk) {
TestingMockupServer mockup_server("barebones_session_auth/mockup.shar");
TestFilesystem fs(ecclesia::GetTestTempUdsDirectory());
const auto [host, port] =
std::get<TestingMockupServer::ConfigNetwork>(mockup_server.GetConfig());
std::string uds_path = fs.GetTruePath("/test/proxy.socket");
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
absl::StrFormat(
R"pb(
proxy_configuration {
target { scheme: TARGET_SCHEME_HTTP hostname: "%s" port: %d }
grpc_configuration {
uds_endpoint: { path: "%s" }
net_endpoint: { host: "localhost", port: 9999 }
}
plugins: {
redfish_passthrough: {
request_timeout_msec: 30000
connect_timeout_msec: 5000
dns_timeout_sec: 60
max_recv_speed: -1
sse_low_speed_limit_bytes_per_sec: 100
sse_low_speed_time_sec: 30
}
}
voyager_telemetry_options {}
resource_authorization_policy: {
mappings: {
name: "match_all"
allow: { key: "all" }
resource_path: ""
with_subtree: true
}
}
}
)pb",
host, port, uds_path));
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(config, GetOptions(&fs));
ASSERT_OK(proxies);
BackgroundThread bg(*proxies);
bg.Start();
std::shared_ptr<grpc::Channel> uds_channel = grpc::CreateChannel(
"unix://" + uds_path, grpc::InsecureChannelCredentials());
EXPECT_THAT(NameFromVoyagerTelemetry(uds_channel, "/redfish/v1"),
IsOkAndHolds("Root Service"));
EXPECT_THAT(NameFromRedfishV1(uds_channel, "/redfish/v1"), Not(IsOk()));
std::shared_ptr<grpc::Channel> net_channel =
grpc::CreateChannel("localhost:9999", grpc::InsecureChannelCredentials());
EXPECT_THAT(NameFromVoyagerTelemetry(net_channel, "/redfish/v1"),
IsOkAndHolds("Root Service"));
EXPECT_THAT(NameFromRedfishV1(net_channel, "/redfish/v1"), Not(IsOk()));
EXPECT_OK(bg.Finish());
}
TEST(TestProxyBuilder, AllServicesUdsOnlySingleProxyOk) {
TestingMockupServer mockup_server("barebones_session_auth/mockup.shar");
TestFilesystem fs(ecclesia::GetTestTempUdsDirectory());
const auto [host, port] =
std::get<TestingMockupServer::ConfigNetwork>(mockup_server.GetConfig());
std::string uds_path = fs.GetTruePath("/test/proxy.socket");
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
absl::StrFormat(
R"pb(
proxy_configuration {
target { scheme: TARGET_SCHEME_HTTP hostname: "%s" port: %d }
grpc_configuration { uds_endpoint { path: "%s" } }
plugins: {
redfish_passthrough: {
request_timeout_msec: 30000
connect_timeout_msec: 5000
dns_timeout_sec: 60
max_recv_speed: -1
sse_low_speed_limit_bytes_per_sec: 100
sse_low_speed_time_sec: 30
}
}
redfish_v1_options {}
voyager_telemetry_options {}
resource_authorization_policy: {
mappings: {
name: "match_all"
allow: { key: "all" }
resource_path: ""
with_subtree: true
}
}
}
)pb",
host, port, uds_path));
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(config, GetOptions(&fs));
ASSERT_OK(proxies);
BackgroundThread bg(*proxies);
bg.Start();
std::shared_ptr<grpc::Channel> uds_channel = grpc::CreateChannel(
"unix://" + uds_path, grpc::InsecureChannelCredentials());
EXPECT_THAT(NameFromVoyagerTelemetry(uds_channel, "/redfish/v1"),
IsOkAndHolds("Root Service"));
EXPECT_THAT(NameFromRedfishV1(uds_channel, "/redfish/v1"),
IsOkAndHolds("Root Service"));
std::shared_ptr<grpc::Channel> net_channel =
grpc::CreateChannel("localhost:9999", grpc::InsecureChannelCredentials());
EXPECT_THAT(NameFromVoyagerTelemetry(net_channel, "/redfish/v1"),
Not(IsOk()));
EXPECT_THAT(NameFromRedfishV1(net_channel, "/redfish/v1"), Not(IsOk()));
EXPECT_OK(bg.Finish());
}
TEST(TestProxyBuilder, AllServicesNetOnlySingleProxyOk) {
TestingMockupServer mockup_server("barebones_session_auth/mockup.shar");
TestFilesystem fs(ecclesia::GetTestTempUdsDirectory());
const auto [host, port] =
std::get<TestingMockupServer::ConfigNetwork>(mockup_server.GetConfig());
std::string uds_path = fs.GetTruePath("/test/proxy.socket");
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
absl::StrFormat(
R"pb(
proxy_configuration {
target { scheme: TARGET_SCHEME_HTTP hostname: "%s" port: %d }
grpc_configuration {
net_endpoint { host: "localhost" port: 9999 }
}
plugins: {
redfish_passthrough: {
request_timeout_msec: 30000
connect_timeout_msec: 5000
dns_timeout_sec: 60
max_recv_speed: -1
sse_low_speed_limit_bytes_per_sec: 100
sse_low_speed_time_sec: 30
}
}
redfish_v1_options {}
voyager_telemetry_options {}
resource_authorization_policy: {
mappings: {
name: "match_all"
allow: { key: "all" }
resource_path: ""
with_subtree: true
}
}
}
)pb",
host, port));
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(config, GetOptions(&fs));
ASSERT_OK(proxies);
BackgroundThread bg(*proxies);
bg.Start();
std::shared_ptr<grpc::Channel> uds_channel = grpc::CreateChannel(
"unix://" + uds_path, grpc::InsecureChannelCredentials());
EXPECT_THAT(NameFromVoyagerTelemetry(uds_channel, "/redfish/v1"),
Not(IsOk()));
EXPECT_THAT(NameFromRedfishV1(uds_channel, "/redfish/v1"), Not(IsOk()));
std::shared_ptr<grpc::Channel> net_channel =
grpc::CreateChannel("localhost:9999", grpc::InsecureChannelCredentials());
EXPECT_THAT(NameFromVoyagerTelemetry(net_channel, "/redfish/v1"),
IsOkAndHolds("Root Service"));
EXPECT_THAT(NameFromRedfishV1(net_channel, "/redfish/v1"),
IsOkAndHolds("Root Service"));
EXPECT_OK(bg.Finish());
}
TEST(TestProxyBuilder, CreateAndStartFromFileWorks) {
EXPECT_OK(milotic::ProxyBuilder::CreateAndStart(
GetTestDataPath("config.pb.txt"), GetOptions()));
}
TEST(TestProxyBuilder, CreateAndStartFromMissingFileFails) {
EXPECT_THAT(milotic::ProxyBuilder::CreateAndStart("nonexistent path"),
StatusIs(absl::StatusCode::kNotFound));
}
TEST(TestProxyBuilder, PluginConfigWorks) {
TestingMockupServer mockup_server("barebones_session_auth/mockup.shar");
ScopedMockLog log(kDoNotCaptureLogsYet);
EXPECT_CALL(log, Log).Times(AnyNumber());
EXPECT_CALL(log, Log(INFO, _, "Handler init succeeded: redfish_passthrough"));
const auto [host, port] =
std::get<TestingMockupServer::ConfigNetwork>(mockup_server.GetConfig());
auto config = ecclesia::ParseTextAsProtoOrDie<
milotic_grpc_proxy::Configuration>(absl::StrFormat(
R"pb(
proxy_configuration {
target { scheme: TARGET_SCHEME_HTTP hostname: "%s" port: %d }
grpc_configuration { net_endpoint { host: "localhost", port: 9999 } }
redfish_v1_options {}
voyager_telemetry_options {}
plugins { test { fixed_redfish_id: "/redfish/v1/Chassis/chassis" } }
plugins: {
redfish_passthrough: {
request_timeout_msec: 30000
connect_timeout_msec: 5000
dns_timeout_sec: 60
max_recv_speed: -1
sse_low_speed_limit_bytes_per_sec: 100
sse_low_speed_time_sec: 30
}
name: "redfish_passthrough"
}
resource_authorization_policy: {
mappings: {
name: "match_all"
allow: { key: "all" }
resource_path: ""
with_subtree: true
}
}
}
)pb",
host, port));
log.StartCapturingLogs();
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(config, GetOptions());
ASSERT_OK(proxies);
BackgroundThread bg(*proxies);
bg.Start();
std::shared_ptr<grpc::Channel> net_channel =
grpc::CreateChannel("localhost:9999", grpc::InsecureChannelCredentials());
// Test plugin replaces URI.
EXPECT_THAT(NameFromRedfishV1(net_channel, "/bad_id"),
IsOkAndHolds("chassis"));
EXPECT_OK(bg.Finish());
}
TEST(TestProxyBuilder, PluginOrderIsPreserved) {
TestingMockupServer mockup_server("barebones_session_auth/mockup.shar");
ScopedMockLog log(kDoNotCaptureLogsYet);
EXPECT_CALL(log, Log).Times(AnyNumber());
{
testing::InSequence s;
EXPECT_CALL(log, Log(INFO, _, "Handler init succeeded: test1"));
EXPECT_CALL(log, Log(INFO, _, "Handler init succeeded: test2"));
EXPECT_CALL(log,
Log(INFO, _, "Handler init succeeded: redfish_passthrough"));
}
const auto [host, port] =
std::get<TestingMockupServer::ConfigNetwork>(mockup_server.GetConfig());
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
absl::StrFormat(
R"pb(
proxy_configuration {
target { scheme: TARGET_SCHEME_HTTP hostname: "%s" port: %d }
grpc_configuration {
net_endpoint { host: "localhost", port: 9999 }
}
redfish_v1_options {}
voyager_telemetry_options {}
plugins {
test { fixed_redfish_id: "/value/is/overridden" }
name: "test1"
}
plugins {
test { fixed_redfish_id: "/redfish/v1/Chassis/chassis" }
name: "test2"
}
plugins {
redfish_passthrough: {
request_timeout_msec: 30000
connect_timeout_msec: 5000
dns_timeout_sec: 60
max_recv_speed: -1
sse_low_speed_limit_bytes_per_sec: 100
sse_low_speed_time_sec: 30
}
name: "redfish_passthrough"
}
resource_authorization_policy: {
mappings: {
name: "match_all"
allow: { key: "all" }
resource_path: ""
with_subtree: true
}
}
}
)pb",
host, port));
log.StartCapturingLogs();
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(config, GetOptions());
ASSERT_OK(proxies);
BackgroundThread bg(*proxies);
bg.Start();
std::shared_ptr<grpc::Channel> net_channel =
grpc::CreateChannel("localhost:9999", grpc::InsecureChannelCredentials());
// Test plugin replaces URI.
EXPECT_THAT(NameFromRedfishV1(net_channel, "/bad_id"),
IsOkAndHolds("chassis"));
EXPECT_OK(bg.Finish());
}
TEST(TestProxyBuilder, UdsUserAndGroupAreSet) {
TestFilesystem fs(ecclesia::GetTestTempUdsDirectory());
std::string uds_path = fs.GetTruePath("/test/proxy.socket");
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
absl::StrFormat(R"pb(
proxy_configuration {
target {
scheme: TARGET_SCHEME_HTTP
hostname: "not_used"
port: 123
}
grpc_configuration {
uds_endpoint: {
path: "%s"
group { id: 5000 }
permission: PERM_USER_AND_GROUP
}
}
voyager_telemetry_options {}
}
)pb",
uds_path));
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(config, GetOptions(&fs));
EXPECT_OK(proxies);
EXPECT_THAT(file::Stat(fs.GetTruePath("/test"), file::Defaults()),
IsOkAndHolds(Partially(EqualsProto(R"pb(
file_type: DIRECTORY
group: "eng"
mode: 0750
)pb"))));
}
TEST(TestProxyBuilder, UdsUserOnlySet) {
TestFilesystem fs(ecclesia::GetTestTempUdsDirectory());
std::string uds_path = fs.GetTruePath("/test/proxy.socket");
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
absl::StrFormat(
R"pb(
proxy_configuration {
target {
scheme: TARGET_SCHEME_HTTP
hostname: "not_used"
port: 123
}
grpc_configuration {
uds_endpoint: { path: "%s" permission: PERM_USER_ONLY }
}
voyager_telemetry_options {}
}
)pb",
uds_path));
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(config, GetOptions(&fs));
EXPECT_OK(proxies);
EXPECT_THAT(file::Stat(fs.GetTruePath("/test"), file::Defaults()),
IsOkAndHolds(Partially(EqualsProto(R"pb(
file_type: DIRECTORY
mode: 0700
)pb"))));
}
TEST(TestProxyBuilder, UdsInvalidPathFails) {
TestFilesystem fs(ecclesia::GetTestTempUdsDirectory());
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
R"pb(
proxy_configuration {
target {
scheme: TARGET_SCHEME_HTTP
hostname: "not_used"
port: 123
}
grpc_configuration {
uds_endpoint: {
path: "/tmp/invalid.socket"
permission: PERM_USER_ONLY
}
}
voyager_telemetry_options {}
}
)pb");
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(config, GetOptions(&fs));
EXPECT_THAT(proxies, StatusIs(absl::StatusCode::kUnavailable));
}
TEST(TestProxyBuilder, NetCredentialsWork) {
TestingMockupServer mockup_server("barebones_session_auth/mockup.shar");
const auto [host, port] =
std::get<TestingMockupServer::ConfigNetwork>(mockup_server.GetConfig());
TestFilesystem fs(ecclesia::GetTestTempUdsDirectory());
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
absl::StrFormat(
R"pb(
proxy_configuration {
target { scheme: TARGET_SCHEME_HTTP hostname: "%s" port: %d }
grpc_configuration {
net_endpoint: { host: "localhost" port: 9999 }
}
plugins: {
redfish_passthrough: {
request_timeout_msec: 30000
connect_timeout_msec: 5000
dns_timeout_sec: 60
max_recv_speed: -1
sse_low_speed_limit_bytes_per_sec: 100
sse_low_speed_time_sec: 30
}
}
voyager_telemetry_options {}
resource_authorization_policy: {
mappings: {
name: "match_all"
allow: { key: "all" }
resource_path: ""
with_subtree: true
}
}
}
)pb",
host, port));
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(
config,
GetOptions(&fs, false)
.AddCredentials({.net_creds = grpc::Loas2ServerCredentials(
grpc::Loas2ServerCredentialsOptions())}));
ASSERT_OK(proxies);
BackgroundThread bg(*proxies);
bg.Start();
std::shared_ptr<grpc::Channel> net_channel = grpc::CreateChannel(
"localhost:9999",
grpc::Loas2Credentials(grpc::Loas2CredentialsOptions()));
EXPECT_THAT(NameFromVoyagerTelemetry(net_channel, "/redfish/v1"),
IsOkAndHolds("Root Service"));
std::shared_ptr<grpc::Channel> insecure_net_channel =
grpc::CreateChannel("localhost:9999", grpc::InsecureChannelCredentials());
EXPECT_THAT(NameFromVoyagerTelemetry(insecure_net_channel, "/redfish/v1"),
Not(IsOk()));
EXPECT_OK(bg.Finish());
}
TEST(TestProxyBuilder, NetCredentialsAreRequired) {
TestFilesystem fs(ecclesia::GetTestTempUdsDirectory());
auto config = ecclesia::ParseTextAsProtoOrDie<
milotic_grpc_proxy::Configuration>(
R"pb(
proxy_configuration {
target { scheme: TARGET_SCHEME_HTTP hostname: "not used" port: 123 }
grpc_configuration { net_endpoint: { host: "localhost" port: 9999 } }
plugins: {
redfish_passthrough: {
request_timeout_msec: 30000
connect_timeout_msec: 5000
dns_timeout_sec: 60
max_recv_speed: -1
sse_low_speed_limit_bytes_per_sec: 100
sse_low_speed_time_sec: 30
}
}
voyager_telemetry_options {}
}
)pb");
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(config, GetOptions(&fs, false));
EXPECT_THAT(proxies, StatusIs(absl::StatusCode::kFailedPrecondition));
}
TEST(TestProxyBuilder, UdsCredentialsAreRequired) {
TestFilesystem fs(ecclesia::GetTestTempUdsDirectory());
std::string uds_path = fs.GetTruePath("/test/proxy.socket");
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
absl::StrFormat(
R"pb(
proxy_configuration {
target {
scheme: TARGET_SCHEME_HTTP
hostname: "not used"
port: 123
}
grpc_configuration { uds_endpoint: { path: "%s" } }
voyager_telemetry_options {}
}
)pb",
uds_path));
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(config, GetOptions(&fs, false));
EXPECT_THAT(proxies, StatusIs(absl::StatusCode::kFailedPrecondition));
}
TEST(TestProxyBuilder, UdsCredentialsWork) {
TestingMockupServer mockup_server("barebones_session_auth/mockup.shar");
const auto [host, port] =
std::get<TestingMockupServer::ConfigNetwork>(mockup_server.GetConfig());
TestFilesystem fs(ecclesia::GetTestTempUdsDirectory());
std::string uds_path = fs.GetTruePath("/test/proxy.socket");
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
absl::StrFormat(
R"pb(
proxy_configuration {
target { scheme: TARGET_SCHEME_HTTP hostname: "%s" port: %d }
grpc_configuration { uds_endpoint: { path: "%s" } }
plugins: {
redfish_passthrough: {
request_timeout_msec: 30000
connect_timeout_msec: 5000
dns_timeout_sec: 60
max_recv_speed: -1
sse_low_speed_limit_bytes_per_sec: 100
sse_low_speed_time_sec: 30
}
}
voyager_telemetry_options {}
resource_authorization_policy: {
mappings: {
name: "match_all"
allow: { key: "all" }
resource_path: ""
with_subtree: true
}
}
}
)pb",
host, port, uds_path));
// Local credentials don't do much right now, so there is really no effect to
// doing this, but the test should continue to pass if the underlying
// credentials implementation is updated.
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(
config,
GetOptions(&fs, false)
.AddCredentials(
{.uds_creds =
grpc::experimental::LocalServerCredentials(UDS)}));
ASSERT_OK(proxies);
BackgroundThread bg(*proxies);
bg.Start();
std::shared_ptr<grpc::Channel> uds_channel =
grpc::CreateChannel(absl::StrCat("unix://", uds_path),
grpc::experimental::LocalCredentials(UDS));
EXPECT_THAT(NameFromVoyagerTelemetry(uds_channel, "/redfish/v1"),
IsOkAndHolds("Root Service"));
EXPECT_OK(bg.Finish());
}
TEST(TestProxyBuilder, QueueSizeIsConfigurable) {
TestingMockupServer mockup_server("barebones_session_auth/mockup.shar");
const auto [host, port] =
std::get<TestingMockupServer::ConfigNetwork>(mockup_server.GetConfig());
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
absl::StrFormat(
R"pb(
proxy_configuration {
target { scheme: TARGET_SCHEME_HTTP hostname: "%s" port: %d }
grpc_configuration {
net_endpoint { host: "localhost", port: 9999 }
}
redfish_v1_options {}
voyager_telemetry_options {}
}
queue_options { size: 1 }
)pb",
host, port));
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(
config, GetOptions(/*fs=*/nullptr, /*insecure=*/true));
EXPECT_OK(proxies);
// While we can confirm that the config is loaded correctly, there's no way to
// test it has any effect, since there are only performance tradeoffs if this
// value is changed.
}
TEST(TestProxyBuilder, TargetConfigUsingProtoMessageWorks) {
TestingMockupServer mockup_server("barebones_session_auth/mockup.shar");
const auto [host, port] =
std::get<TestingMockupServer::ConfigNetwork>(mockup_server.GetConfig());
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
absl::StrFormat(
R"pb(
proxy_configuration {
target { scheme: TARGET_SCHEME_HTTP hostname: "%s" port: %d }
grpc_configuration {
net_endpoint: { host: "localhost" port: 9999 }
}
plugins: {
redfish_passthrough: {
request_timeout_msec: 30000
connect_timeout_msec: 5000
dns_timeout_sec: 60
max_recv_speed: -1
sse_low_speed_limit_bytes_per_sec: 100
sse_low_speed_time_sec: 30
}
}
voyager_telemetry_options {}
resource_authorization_policy: {
mappings: {
name: "match_all"
allow: { key: "all" }
resource_path: ""
with_subtree: true
}
}
}
)pb",
host, port));
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(config, GetOptions());
ASSERT_OK(proxies);
BackgroundThread bg(*proxies);
bg.Start();
std::shared_ptr<grpc::Channel> net_channel =
grpc::CreateChannel("localhost:9999", grpc::InsecureChannelCredentials());
EXPECT_THAT(NameFromVoyagerTelemetry(net_channel, "/redfish/v1"),
IsOkAndHolds("Root Service"));
EXPECT_OK(bg.Finish());
}
TEST(TestProxyBuilder, TargetConfigWithBadPortNumberFails) {
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
R"pb(
proxy_configuration {
target {
scheme: TARGET_SCHEME_HTTP
hostname: "host"
port: 0x10000
}
}
)pb");
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(config, GetOptions());
ASSERT_THAT(proxies, StatusIs(absl::StatusCode::kInvalidArgument));
}
TEST(TestProxyBuilder, TargetConfigUsingMismatchedSchemeFailsHttp) {
TestingMockupServer mockup_server("barebones_session_auth/mockup.shar");
const auto [host, port] =
std::get<TestingMockupServer::ConfigNetwork>(mockup_server.GetConfig());
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
absl::StrFormat(
R"pb(
proxy_configuration {
target { scheme: TARGET_SCHEME_HTTPS hostname: "%s" port: %d }
grpc_configuration {
net_endpoint: { host: "localhost" port: 9999 }
}
plugins: {
redfish_passthrough: {
request_timeout_msec: 30000
connect_timeout_msec: 5000
dns_timeout_sec: 60
max_recv_speed: -1
sse_low_speed_limit_bytes_per_sec: 100
sse_low_speed_time_sec: 30
}
}
voyager_telemetry_options {}
resource_authorization_policy: {
mappings: {
name: "match_all"
allow: { key: "all" }
resource_path: ""
with_subtree: true
}
}
}
)pb",
host, port));
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(config, GetOptions());
ASSERT_OK(proxies);
BackgroundThread bg(*proxies);
bg.Start();
std::shared_ptr<grpc::Channel> net_channel =
grpc::CreateChannel("localhost:9999", grpc::InsecureChannelCredentials());
EXPECT_THAT(NameFromVoyagerTelemetry(net_channel, "/redfish/v1"),
StatusIs(absl::StatusCode::kInternal));
EXPECT_OK(bg.Finish());
}
TEST(TestProxyBuilder, SshConfigWorks) {
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
R"pb(
proxy_configuration {
target {
scheme: TARGET_SCHEME_HTTPS
hostname: "test_hostname"
port: 1234
}
ssh_options {
credentials_file: {
path: [ "test/credentials/path1", "test/credentials/path2" ]
private_cache_path: "test/credentials/cache"
}
port: 9876
max_connections: 2
connect_timeout_sec: 100
connect_retry_interval_ms: 200
}
}
)pb");
MockFunction<milotic::SshClient::CreateFn> mock_create;
MockSshClient::MockCreate() = mock_create.AsStdFunction();
milotic::SshClient::CreationOptions passed_options;
auto mock_client = std::make_unique<MockSshClient>();
EXPECT_CALL(mock_create, Call)
.WillOnce(
DoAll(SaveArg<0>(&passed_options), Return(std::move(mock_client))));
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(
config, GetOptions().SetSshClientType<MockSshClient>());
EXPECT_THAT(passed_options.credentials_filename,
ElementsAre("test/credentials/path1", "test/credentials/path2"));
EXPECT_EQ(passed_options.private_cache_path, "test/credentials/cache");
EXPECT_EQ(passed_options.port, 9876);
EXPECT_EQ(passed_options.max_connections, 2);
EXPECT_EQ(passed_options.connect_timeout, absl::Seconds(100));
EXPECT_EQ(passed_options.connect_retry_interval, absl::Milliseconds(200));
EXPECT_EQ(passed_options.host, "test_hostname");
}
TEST(TestProxyBuilder, SshConfigWithoutClientTypeOptionFails) {
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
R"pb(
proxy_configuration {
target {
scheme: TARGET_SCHEME_HTTPS
hostname: "test_hostname"
port: 1234
}
ssh_options {}
}
)pb");
EXPECT_THAT(milotic::ProxyBuilder::CreateAndStart(config, GetOptions()),
StatusIs(absl::StatusCode::kInvalidArgument));
}
TEST(TestProxyBuilder, SshConfigWithMisconfiguredTargetFails) {
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
R"pb(
proxy_configuration { ssh_options {} }
)pb");
MockFunction<milotic::SshClient::CreateFn> mock_create;
MockSshClient::MockCreate() = mock_create.AsStdFunction();
EXPECT_CALL(mock_create, Call).Times(0);
EXPECT_THAT(milotic::ProxyBuilder::CreateAndStart(
config, GetOptions().SetSshClientType<MockSshClient>()),
StatusIs(absl::StatusCode::kInvalidArgument));
}
TEST(TestProxyBuilder, SshConfigBadPortNumberFails) {
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
R"pb(
proxy_configuration {
target {
scheme: TARGET_SCHEME_HTTPS
hostname: "target_host"
port: 1234
}
ssh_options { port: 0x10000 }
}
)pb");
MockFunction<milotic::SshClient::CreateFn> mock_create;
MockSshClient::MockCreate() = mock_create.AsStdFunction();
EXPECT_CALL(mock_create, Call).Times(0);
EXPECT_THAT(milotic::ProxyBuilder::CreateAndStart(
config, GetOptions().SetSshClientType<MockSshClient>()),
StatusIs(absl::StatusCode::kInvalidArgument));
}
TEST(TestProxyBuilder, SshClientCreationErrorCausesFailure) {
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
R"pb(
proxy_configuration {
target {
scheme: TARGET_SCHEME_HTTPS
hostname: "test_hostname"
port: 1234
}
ssh_options {
credentials_file: { path: "test/credentials/path" }
port: 9876
max_connections: 2
connect_timeout_sec: 100
connect_retry_interval_ms: 200
}
}
)pb");
MockFunction<milotic::SshClient::CreateFn> mock_create;
MockSshClient::MockCreate() = mock_create.AsStdFunction();
auto mock_client = std::make_unique<MockSshClient>();
EXPECT_CALL(mock_create, Call)
.WillOnce(Return(absl::UnknownError("test error")));
EXPECT_THAT(milotic::ProxyBuilder::CreateAndStart(
config, GetOptions().SetSshClientType<MockSshClient>()),
StatusIs(absl::StatusCode::kUnknown, "test error"));
}
TEST(TestProxyBuilder, InvalidQuantumReturnsError) {
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
R"pb(
queue_options { size: 10 quantum: 20 }
)pb");
EXPECT_THAT(milotic::ProxyBuilder::CreateAndStart(config, GetOptions()),
StatusIs(absl::StatusCode::kInvalidArgument));
}
TEST(TestProxyBuilder, ExecutorIsUsed) {
TestingMockupServer mockup_server("barebones_session_auth/mockup.shar");
const auto [host, port] =
std::get<TestingMockupServer::ConfigNetwork>(mockup_server.GetConfig());
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
absl::StrFormat(
R"pb(
proxy_configuration {
target { scheme: TARGET_SCHEME_HTTP hostname: "%s" port: %d }
grpc_configuration {
net_endpoint: { host: "localhost" port: 9999 }
}
plugins: {
redfish_passthrough: {
request_timeout_msec: 30000
connect_timeout_msec: 5000
dns_timeout_sec: 60
max_recv_speed: -1
sse_low_speed_limit_bytes_per_sec: 100
sse_low_speed_time_sec: 30
}
}
voyager_telemetry_options {}
resource_authorization_policy: {
mappings: {
name: "match_all"
allow: { key: "all" }
resource_path: ""
with_subtree: true
}
}
}
queue_options { size: 13 quantum: 7 }
)pb",
host, port));
MockExecutor* mock_executor = nullptr;
absl::StatusOr<milotic::ProxyBuilder> proxies =
milotic::ProxyBuilder::CreateAndStart(
config, GetOptions().SetExecutorFactory(
[&mock_executor](
const milotic_grpc_proxy::QueueOptions& options) {
EXPECT_THAT(options, EqualsProto(R"pb(size: 13
quantum: 7)pb"));
auto mock = std::make_unique<MockExecutor>();
mock_executor = mock.get();
return mock;
}));
ASSERT_OK(proxies);
std::shared_ptr<grpc::Channel> net_channel =
grpc::CreateChannel("localhost:9999", grpc::InsecureChannelCredentials());
std::unique_ptr<third_party_voyager::MachineTelemetry::Stub> stub(
third_party_voyager::MachineTelemetry::NewStub(net_channel));
NameFromVoyagerTelemetryAsync request1(*stub, "/redfish/v1");
NameFromVoyagerTelemetryAsync request2(*stub, "/redfish/v1/SessionService");
while (proxies->queue().Size() < 2) {
absl::SleepFor(absl::Milliseconds(100));
}
EXPECT_CALL(*mock_executor, Execute(SizeIs(2))).Times(1);
BackgroundThread bg(*proxies);
bg.Start();
EXPECT_THAT(request1.GetName(), IsOkAndHolds("Root Service"));
EXPECT_THAT(request2.GetName(), IsOkAndHolds("Session Service"));
ASSERT_OK(bg.Finish());
}
void CreateChassis(TestingMockupServer& server, absl::string_view chassis_id,
absl::string_view chassis_name) {
auto interface = server.RedfishClientInterface();
auto resp = interface->PostUri(
"/redfish/v1/Chassis",
nlohmann::json({{"Id", chassis_id}, {"Name", chassis_name}}).dump());
ASSERT_OK(resp.status());
}
milotic_grpc_proxy::ProxyConfiguration ConfigureProxy(
const TestingMockupServer& server, absl::string_view uds_path) {
milotic_grpc_proxy::ProxyConfiguration proxy_config;
const auto [host, port] =
std::get<TestingMockupServer::ConfigNetwork>(server.GetConfig());
proxy_config.mutable_target()->set_scheme(
milotic_grpc_proxy::TARGET_SCHEME_HTTP);
proxy_config.mutable_target()->set_hostname(host);
proxy_config.mutable_target()->set_port(port);
proxy_config.mutable_grpc_configuration()->mutable_uds_endpoint()->set_path(
uds_path);
return proxy_config;
}
TEST(TestProxyBuilder, MultipleProxiesRunIndependently) {
TestingMockupServer mockup_server1("barebones_session_auth/mockup.shar");
CreateChassis(mockup_server1, "chassis1", "chassis1");
TestingMockupServer mockup_server2("barebones_session_auth/mockup.shar");
CreateChassis(mockup_server2, "chassis2", "chassis2");
TestFilesystem fs(ecclesia::GetTestTempUdsDirectory());
auto proxy_config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::ProxyConfiguration>(
R"pb(
target { scheme: TARGET_SCHEME_HTTP }
plugins: {
redfish_passthrough: {
request_timeout_msec: 30000
connect_timeout_msec: 5000
dns_timeout_sec: 60
max_recv_speed: -1
sse_low_speed_limit_bytes_per_sec: 100
sse_low_speed_time_sec: 30
}
}
redfish_v1_options {}
voyager_telemetry_options {}
resource_authorization_policy: {
mappings: {
name: "chassis"
resource_path: "/redfish/v1/Chassis"
allow: {
key: "uds_only"
value: { local: true }
}
with_subtree: true
request_verb: REQUEST_VERB_GET
}
}
)pb");
std::string uds_path1 = fs.GetTruePath("/test/proxy1.socket");
std::string uds_path2 = fs.GetTruePath("/test/proxy2.socket");
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
R"pb(
queue_options { size: 10 quantum: 7 }
)pb");
milotic_grpc_proxy::ProxyConfiguration* new_config =
config.add_proxy_configuration();
*new_config = proxy_config;
new_config->MergeFrom(ConfigureProxy(mockup_server1, uds_path1));
new_config = config.add_proxy_configuration();
*new_config = proxy_config;
new_config->MergeFrom(ConfigureProxy(mockup_server2, uds_path2));
ASSERT_OK_AND_ASSIGN(
milotic::ProxyBuilder proxies,
milotic::ProxyBuilder::CreateAndStart(
config,
GetOptions(&fs, false)
.AddCredentials(
{.uds_creds =
grpc::experimental::LocalServerCredentials(UDS)})
.AddCredentials(
{.uds_creds =
grpc::experimental::LocalServerCredentials(UDS)})));
std::unique_ptr<third_party_voyager::MachineTelemetry::Stub> stub1 =
third_party_voyager::MachineTelemetry::NewStub(
grpc::CreateChannel(absl::StrCat("unix://", uds_path1),
grpc::experimental::LocalCredentials(UDS)));
std::unique_ptr<third_party_voyager::MachineTelemetry::Stub> stub2 =
third_party_voyager::MachineTelemetry::NewStub(
grpc::CreateChannel(absl::StrCat("unix://", uds_path2),
grpc::experimental::LocalCredentials(UDS)));
BackgroundThread bg(proxies);
bg.Start();
EXPECT_THAT(GetName(*stub1, "/redfish/v1/Chassis/chassis1"),
IsOkAndHolds("chassis1"));
EXPECT_THAT(GetName(*stub2, "/redfish/v1/Chassis/chassis2"),
IsOkAndHolds("chassis2"));
EXPECT_THAT(
GetName(*stub1, "/redfish/v1/Chassis/chassis2"),
StatusIs(absl::StatusCode::kInternal, StartsWith("HTTP error: 404")));
EXPECT_THAT(
GetName(*stub2, "/redfish/v1/Chassis/chassis1"),
StatusIs(absl::StatusCode::kInternal, StartsWith("HTTP error: 404")));
ASSERT_OK(bg.Finish());
}
TEST(TestProxyBuilder, MultipleProxiesHaveIndependentAuthz) {
TestingMockupServer mockup_server("barebones_session_auth/mockup.shar");
CreateChassis(mockup_server, "chassis1", "chassis1");
CreateChassis(mockup_server, "chassis2", "chassis2");
TestFilesystem fs(ecclesia::GetTestTempUdsDirectory());
auto proxy_config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::ProxyConfiguration>(
R"pb(
target { scheme: TARGET_SCHEME_HTTP }
plugins: {
redfish_passthrough: {
request_timeout_msec: 30000
connect_timeout_msec: 5000
dns_timeout_sec: 60
max_recv_speed: -1
sse_low_speed_limit_bytes_per_sec: 100
sse_low_speed_time_sec: 30
}
}
redfish_v1_options {}
voyager_telemetry_options {}
resource_authorization_policy: {
mappings: {
name: "chassis"
allow: {
key: "uds_only"
value: { local: true }
}
with_subtree: true
request_verb: REQUEST_VERB_GET
}
}
)pb");
std::string uds_path1 = fs.GetTruePath("/test/proxy1.socket");
std::string uds_path2 = fs.GetTruePath("/test/proxy2.socket");
auto config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
R"pb(
queue_options { size: 10 quantum: 7 }
)pb");
milotic_grpc_proxy::ProxyConfiguration* new_config =
config.add_proxy_configuration();
*new_config = proxy_config;
new_config->MergeFrom(ConfigureProxy(mockup_server, uds_path1));
new_config->mutable_resource_authorization_policy()
->mutable_mappings(0)
->add_resource_path("/redfish/v1/Chassis/chassis1");
new_config = config.add_proxy_configuration();
*new_config = proxy_config;
new_config->MergeFrom(ConfigureProxy(mockup_server, uds_path2));
new_config->mutable_resource_authorization_policy()
->mutable_mappings(0)
->add_resource_path("/redfish/v1/Chassis/chassis2");
ASSERT_OK_AND_ASSIGN(
milotic::ProxyBuilder proxies,
milotic::ProxyBuilder::CreateAndStart(
config,
GetOptions(&fs, false)
.AddCredentials(
{.uds_creds =
grpc::experimental::LocalServerCredentials(UDS)})
.AddCredentials(
{.uds_creds =
grpc::experimental::LocalServerCredentials(UDS)})));
std::unique_ptr<third_party_voyager::MachineTelemetry::Stub> stub1 =
third_party_voyager::MachineTelemetry::NewStub(
grpc::CreateChannel(absl::StrCat("unix://", uds_path1),
grpc::experimental::LocalCredentials(UDS)));
std::unique_ptr<third_party_voyager::MachineTelemetry::Stub> stub2 =
third_party_voyager::MachineTelemetry::NewStub(
grpc::CreateChannel(absl::StrCat("unix://", uds_path2),
grpc::experimental::LocalCredentials(UDS)));
BackgroundThread bg(proxies);
bg.Start();
EXPECT_THAT(GetName(*stub1, "/redfish/v1/Chassis/chassis1"),
IsOkAndHolds("chassis1"));
EXPECT_THAT(GetName(*stub2, "/redfish/v1/Chassis/chassis2"),
IsOkAndHolds("chassis2"));
EXPECT_THAT(GetName(*stub1, "/redfish/v1/Chassis/chassis2"),
StatusIs(absl::StatusCode::kPermissionDenied));
EXPECT_THAT(GetName(*stub2, "/redfish/v1/Chassis/chassis1"),
StatusIs(absl::StatusCode::kPermissionDenied));
ASSERT_OK(bg.Finish());
}
TEST(TestProxyBuilder, AdditionalServicesAreAdded) {
TestingMockupServer mockup_server("barebones_session_auth/mockup.shar");
CreateChassis(mockup_server, "chassis1", "chassis1");
CreateChassis(mockup_server, "chassis2", "chassis2");
TestFilesystem fs(ecclesia::GetTestTempUdsDirectory());
auto proxy_config =
ecclesia::ParseTextAsProtoOrDie<milotic_grpc_proxy::Configuration>(
R"pb(
proxy_configuration {
plugins: {
redfish_passthrough: {
request_timeout_msec: 30000
connect_timeout_msec: 5000
dns_timeout_sec: 60
max_recv_speed: -1
sse_low_speed_limit_bytes_per_sec: 100
sse_low_speed_time_sec: 30
}
}
additional_services: {
[type.googleapis.com/
milotic_grpc_proxy.VoyagerTelemetryOptions] {}
}
resource_authorization_policy: {
mappings: {
name: "match_all"
allow: { key: "all" }
resource_path: ""
with_subtree: true
}
}
}
queue_options { size: 13 quantum: 7 }
)pb");
std::string uds_path = fs.GetTruePath("/test/proxy1.socket");
proxy_config.mutable_proxy_configuration(0)->MergeFrom(
ConfigureProxy(mockup_server, uds_path));
ASSERT_OK_AND_ASSIGN(
milotic::ProxyBuilder proxies,
milotic::ProxyBuilder::CreateAndStart(
proxy_config,
GetOptions(&fs, false)
.AddCredentials(
{.uds_creds =
grpc::experimental::LocalServerCredentials(UDS)})));
BackgroundThread bg(proxies);
bg.Start();
EXPECT_THAT(
NameFromVoyagerTelemetry(
grpc::CreateChannel(absl::StrCat("unix://", uds_path),
grpc::experimental::LocalCredentials(UDS)),
"/redfish/v1"),
IsOkAndHolds("Root Service"));
ASSERT_OK(bg.Finish());
}
} // namespace