| #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 |