Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion google/cloud/storage/doc/environment-variables.dox
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,21 @@ protocol, but currently unused.

`GOOGLE_CLOUD_CPP_STORAGE_GRPC_CONFIG=...`: this is deprecated.

[project-definition-link]: https://cloud.google.com/storage/docs/projects 'Project Definition in GCS'
`GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS_OVER_INTERCONNECT=true`: attempt DirectPath
over [Cloud Interconnect][interconnect-link]. This bypasses the usual GCE
environment detection, making DirectPath usable from on-premise hosts. Set to
`false` to disable the feature even when the corresponding option is enabled in
code. Only these exact values are recognized, and the comparison is

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit:

  1. We should also mention that true is used to enable the feature.
  2. The statement seems incomplete. Only these exact values are recognized but we have mentioned only false in the above statement as acceptable value.

case-sensitive; any other value, such as `1` or `TRUE`, is treated as if the
variable were not set. This environment variable takes precedence over the
option.

@see google::cloud::storage_experimental::DirectPathXdsOverInterconnectOption

[project-definition-link]: https://cloud.google.com/storage/docs/projects
'Project Definition in GCS' [interconnect-link]:
https://cloud.google.com/network-connectivity/docs/interconnect 'Cloud
Interconnect'

@section storage-env-logging Logging

Expand Down
34 changes: 34 additions & 0 deletions google/cloud/storage/grpc_plugin.h
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,40 @@ struct GrpcMetricsExcludedLabelsOption {
using Type = std::set<std::string>;
};

/**
* Option to attempt DirectPath over Interconnect.
*
* When this option is enabled, the client bypasses GCE VM environment/BIOS
* checks and configures the gRPC channel to target
* `google-c2p:///storage-direct.googleapis.com?force-xds` with standard TLS.
* This makes DirectPath usable from on-premise hosts reaching Google Cloud over
* [Cloud Interconnect], where the usual GCE environment detection would
* otherwise disable it.
*
* The default is `false`. The
* `GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS_OVER_INTERCONNECT` environment variable
* overrides this option: set it to `"true"` to enable the feature, or to
* `"false"` to disable it even when this option is set. Only these exact
* values are recognized, and the comparison is case-sensitive; any other
* value, for example `"1"` or `"TRUE"`, is treated as if the variable were not
* set. This lets a deployment turn the feature on or off without rebuilding
* the application.
*
* @par Example: Enable DirectPath over Interconnect
* @code
* namespace gcs_ex = google::cloud::storage_experimental;
* auto client = google::cloud::storage::MakeGrpcClient(
* google::cloud::Options{}
* .set<gcs_ex::DirectPathXdsOverInterconnectOption>(true));
* @endcode
*
* [Cloud Interconnect]:
* https://cloud.google.com/network-connectivity/docs/interconnect
*/
struct DirectPathXdsOverInterconnectOption {
using Type = bool;
};

GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
} // namespace storage_experimental
} // namespace cloud
Expand Down
47 changes: 40 additions & 7 deletions google/cloud/storage/internal/grpc/default_options.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ namespace {
auto constexpr kMinMetricsPeriod = std::chrono::seconds(5);
auto constexpr kDefaultMetricsPeriod = std::chrono::seconds(60);
auto constexpr kDefaultMetricsExportTimeout = std::chrono::seconds(30);
bool constexpr kDefaultDirectPathXdsOverInterconnect = false;

int DefaultGrpcNumChannels(std::string const& endpoint) {
// When using Direct Connectivity the gRPC library already does load balancing
Expand Down Expand Up @@ -100,13 +101,45 @@ Options DefaultOptionsGrpc(
auto const ep = google::cloud::internal::UniverseDomainEndpoint(
"storage.googleapis.com", options);

// Set default to direct connectivity if we can detect we are running in GCP
// and there is not already a set endpoint or unviverse domain endpoint.
if ((!options.has<EndpointOption>() &&
!options.has<internal::UniverseDomainOption>()) &&
(gcp_detector->IsGoogleCloudBios() ||
gcp_detector->IsGoogleCloudServerless())) {
options.set<EndpointOption>("google-c2p:///storage.googleapis.com");
if (!options
.has<storage_experimental::DirectPathXdsOverInterconnectOption>()) {
options.set<storage_experimental::DirectPathXdsOverInterconnectOption>(
kDefaultDirectPathXdsOverInterconnect);
}

// The environment variable takes precedence over the option, consistent with.
// An explicit "false" disables the feature even when the option is set, so
// deployments can opt out without rebuilding the application. Any other value
// is treated as if the variable were not set.
auto const direct_path_interconnect_env =
GetEnv("GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS_OVER_INTERCONNECT");
if (direct_path_interconnect_env.has_value()) {
if (*direct_path_interconnect_env == "true") {
options.set<storage_experimental::DirectPathXdsOverInterconnectOption>(
true);
} else if (*direct_path_interconnect_env == "false") {
options.set<storage_experimental::DirectPathXdsOverInterconnectOption>(
false);
}
}
bool const direct_path_interconnect =
options.get<storage_experimental::DirectPathXdsOverInterconnectOption>();
Comment thread
kalragauri marked this conversation as resolved.

// Unless the application configured an endpoint or universe domain, default
// to direct connectivity: the Interconnect target when that feature is
// enabled, otherwise the standard target when running in GCP.
if (!options.has<EndpointOption>() &&
!options.has<internal::UniverseDomainOption>()) {
if (direct_path_interconnect) {
options.set<EndpointOption>(
"google-c2p:///storage-direct.googleapis.com?force-xds");
if (!options.has<AuthorityOption>()) {
options.set<AuthorityOption>("storage.googleapis.com");
}
} else if (gcp_detector->IsGoogleCloudBios() ||
gcp_detector->IsGoogleCloudServerless()) {
options.set<EndpointOption>("google-c2p:///storage.googleapis.com");
}
}

options = google::cloud::internal::MergeOptions(
Expand Down
237 changes: 237 additions & 0 deletions google/cloud/storage/internal/grpc/default_options_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ TEST(DefaultOptionsGrpc, DefaultOptionsGrpcChannelCount) {
{"storage.googleapis.com", 4, std::numeric_limits<int>::max()},
{"google-c2p:///storage.googleapis.com", 1, 1},
{"google-c2p-experimental:///storage.googleapis.com", 1, 1},
{"google-c2p:///storage-direct.googleapis.com?force-xds", 1, 1},
};

for (auto const& test : cases) {
Expand Down Expand Up @@ -95,6 +96,242 @@ TEST(DefaultOptionsGrpc, DefaultEndpointsDirectPath) {
EXPECT_EQ(options.get<AuthorityOption>(), "storage.googleapis.com");
}

/// @test Verify the option defaults to disabled, and that the resolved default
/// is reported when neither the option nor the environment variable is set.
TEST(DefaultOptionsGrpc, DirectPathOverInterconnectDefault) {
ScopedEnvironment env("GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS_OVER_INTERCONNECT",
{});
auto mock_detector = std::make_shared<MockGcpDetector>();
EXPECT_CALL(*mock_detector, IsGoogleCloudBios())
.WillRepeatedly(Return(false));
EXPECT_CALL(*mock_detector, IsGoogleCloudServerless())
.WillRepeatedly(Return(false));

auto options = DefaultOptionsGrpc(Options{}, mock_detector);
EXPECT_TRUE(
options.has<storage_experimental::DirectPathXdsOverInterconnectOption>());
EXPECT_FALSE(
options.get<storage_experimental::DirectPathXdsOverInterconnectOption>());
EXPECT_EQ(options.get<EndpointOption>(), "storage.googleapis.com");
EXPECT_EQ(options.get<AuthorityOption>(), "storage.googleapis.com");
}

/// @test Verify enabling the option off-GCE targets the DirectPath resolver,
/// overrides the authority so TLS/SNI matches, and uses a single channel.
TEST(DefaultOptionsGrpc, DirectPathOverInterconnectOption) {
ScopedEnvironment env("GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS_OVER_INTERCONNECT",
{});
auto mock_detector = std::make_shared<MockGcpDetector>();
EXPECT_CALL(*mock_detector, IsGoogleCloudBios())
.WillRepeatedly(Return(false));
EXPECT_CALL(*mock_detector, IsGoogleCloudServerless())
.WillRepeatedly(Return(false));

auto options = DefaultOptionsGrpc(
Options{}.set<storage_experimental::DirectPathXdsOverInterconnectOption>(
true),
mock_detector);
EXPECT_EQ(options.get<EndpointOption>(),
"google-c2p:///storage-direct.googleapis.com?force-xds");
EXPECT_EQ(options.get<AuthorityOption>(), "storage.googleapis.com");
EXPECT_EQ(options.get<GrpcNumChannelsOption>(), 1);
}

/// @test Verify the environment variable enables the feature when the option is
/// unset, i.e. the zero-code rollout path for operators.
TEST(DefaultOptionsGrpc, DirectPathOverInterconnectEnvVar) {
ScopedEnvironment env("GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS_OVER_INTERCONNECT",
"true");
auto mock_detector = std::make_shared<MockGcpDetector>();
EXPECT_CALL(*mock_detector, IsGoogleCloudBios())
.WillRepeatedly(Return(false));
EXPECT_CALL(*mock_detector, IsGoogleCloudServerless())
.WillRepeatedly(Return(false));

auto options = DefaultOptionsGrpc(Options{}, mock_detector);
EXPECT_EQ(options.get<EndpointOption>(),
"google-c2p:///storage-direct.googleapis.com?force-xds");
EXPECT_EQ(options.get<AuthorityOption>(), "storage.googleapis.com");
EXPECT_EQ(options.get<GrpcNumChannelsOption>(), 1);
}

/// @test Verify explicitly disabling the option off-GCE keeps the client on
/// CloudPath, including the CloudPath multi-channel defaults.
TEST(DefaultOptionsGrpc, DirectPathOverInterconnectDisabled) {
ScopedEnvironment env("GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS_OVER_INTERCONNECT",
{});
auto mock_detector = std::make_shared<MockGcpDetector>();
EXPECT_CALL(*mock_detector, IsGoogleCloudBios())
.WillRepeatedly(Return(false));
EXPECT_CALL(*mock_detector, IsGoogleCloudServerless())
.WillRepeatedly(Return(false));

auto options = DefaultOptionsGrpc(
Options{}.set<storage_experimental::DirectPathXdsOverInterconnectOption>(
false),
mock_detector);
EXPECT_EQ(options.get<EndpointOption>(), "storage.googleapis.com");
EXPECT_EQ(options.get<AuthorityOption>(), "storage.googleapis.com");
EXPECT_GE(options.get<GrpcNumChannelsOption>(), 4);
}

/// @test Verify a caller-supplied endpoint wins over the DirectPath target, so
/// testbench and private-endpoint setups keep working.
TEST(DefaultOptionsGrpc, DirectPathOverInterconnectUserEndpointOverride) {
auto mock_detector = std::make_shared<MockGcpDetector>();
EXPECT_CALL(*mock_detector, IsGoogleCloudBios())
.WillRepeatedly(Return(false));
EXPECT_CALL(*mock_detector, IsGoogleCloudServerless())
.WillRepeatedly(Return(false));

auto options = DefaultOptionsGrpc(
Options{}
.set<storage_experimental::DirectPathXdsOverInterconnectOption>(true)
.set<EndpointOption>("custom-endpoint")
.set<AuthorityOption>("custom-authority"),
mock_detector);
EXPECT_EQ(options.get<EndpointOption>(), "custom-endpoint");
EXPECT_EQ(options.get<AuthorityOption>(), "custom-authority");
}

/// @test Verify a caller-supplied universe domain wins over the DirectPath
/// target, as DirectPath is only available in the default universe.
TEST(DefaultOptionsGrpc, DirectPathOverInterconnectUniverseDomainOverride) {
auto mock_detector = std::make_shared<MockGcpDetector>();
EXPECT_CALL(*mock_detector, IsGoogleCloudBios())
.WillRepeatedly(Return(false));
EXPECT_CALL(*mock_detector, IsGoogleCloudServerless())
.WillRepeatedly(Return(false));

auto options = DefaultOptionsGrpc(
Options{}
.set<storage_experimental::DirectPathXdsOverInterconnectOption>(true)
.set<internal::UniverseDomainOption>("my-ud.net"),
mock_detector);
EXPECT_EQ(options.get<EndpointOption>(), "storage.my-ud.net");
EXPECT_EQ(options.get<AuthorityOption>(), "storage.my-ud.net");
}

/// @test Verify `GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS_OVER_INTERCONNECT=true`
/// overrides the option, consistent with other environment variables.
TEST(DefaultOptionsGrpc, DirectPathOverInterconnectEnvVarOverridesOption) {
ScopedEnvironment env("GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS_OVER_INTERCONNECT",
"true");
auto mock_detector = std::make_shared<MockGcpDetector>();
EXPECT_CALL(*mock_detector, IsGoogleCloudBios())
.WillRepeatedly(Return(false));
EXPECT_CALL(*mock_detector, IsGoogleCloudServerless())
.WillRepeatedly(Return(false));

auto options = DefaultOptionsGrpc(
Options{}.set<storage_experimental::DirectPathXdsOverInterconnectOption>(
false),
mock_detector);
EXPECT_TRUE(
options.get<storage_experimental::DirectPathXdsOverInterconnectOption>());
EXPECT_EQ(options.get<EndpointOption>(),
"google-c2p:///storage-direct.googleapis.com?force-xds");
EXPECT_EQ(options.get<AuthorityOption>(), "storage.googleapis.com");
EXPECT_EQ(options.get<GrpcNumChannelsOption>(), 1);
}

/// @test Verify `GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS_OVER_INTERCONNECT=false`
/// disables the feature even when the option is set, providing an opt-out that
/// does not require rebuilding the application.
TEST(DefaultOptionsGrpc, DirectPathOverInterconnectEnvVarFalseOverridesOption) {
ScopedEnvironment env("GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS_OVER_INTERCONNECT",
"false");
auto mock_detector = std::make_shared<MockGcpDetector>();
EXPECT_CALL(*mock_detector, IsGoogleCloudBios())
.WillRepeatedly(Return(false));
EXPECT_CALL(*mock_detector, IsGoogleCloudServerless())
.WillRepeatedly(Return(false));

auto options = DefaultOptionsGrpc(
Options{}.set<storage_experimental::DirectPathXdsOverInterconnectOption>(
true),
mock_detector);
EXPECT_FALSE(
options.get<storage_experimental::DirectPathXdsOverInterconnectOption>());
EXPECT_EQ(options.get<EndpointOption>(), "storage.googleapis.com");
EXPECT_EQ(options.get<AuthorityOption>(), "storage.googleapis.com");
EXPECT_GE(options.get<GrpcNumChannelsOption>(), 4);
}

/// @test Verify unrecognized environment variable values are ignored and leave
/// the option as the application configured it. Each value is tried against
/// both an enabled and a disabled option, so that a case-insensitive match
/// (for example treating `"TRUE"` as `"true"`) would be detected.
TEST(DefaultOptionsGrpc, DirectPathOverInterconnectEnvVarInvalidValueIgnored) {
for (auto const* value : {"1", "0", "TRUE", "False", "yes", ""}) {
for (auto const configured : {false, true}) {
SCOPED_TRACE("Testing with value " + std::string(value) +
", option configured as " + (configured ? "true" : "false"));
ScopedEnvironment env(
"GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS_OVER_INTERCONNECT", value);
auto mock_detector = std::make_shared<MockGcpDetector>();
EXPECT_CALL(*mock_detector, IsGoogleCloudBios())
.WillRepeatedly(Return(false));
EXPECT_CALL(*mock_detector, IsGoogleCloudServerless())
.WillRepeatedly(Return(false));

auto options = DefaultOptionsGrpc(
Options{}
.set<storage_experimental::DirectPathXdsOverInterconnectOption>(
configured),
mock_detector);
EXPECT_EQ(
options
.get<storage_experimental::DirectPathXdsOverInterconnectOption>(),
configured);
EXPECT_EQ(options.get<EndpointOption>(),
configured
? "google-c2p:///storage-direct.googleapis.com?force-xds"
: "storage.googleapis.com");
}
}
}

/// @test Verify a caller-supplied authority is preserved when the DirectPath
/// target is selected; `storage.googleapis.com` is only the default.
TEST(DefaultOptionsGrpc, DirectPathOverInterconnectPreservesCustomAuthority) {
ScopedEnvironment env("GOOGLE_CLOUD_ENABLE_DIRECT_PATH_XDS_OVER_INTERCONNECT",
{});
auto mock_detector = std::make_shared<MockGcpDetector>();
EXPECT_CALL(*mock_detector, IsGoogleCloudBios())
.WillRepeatedly(Return(false));
EXPECT_CALL(*mock_detector, IsGoogleCloudServerless())
.WillRepeatedly(Return(false));

auto options = DefaultOptionsGrpc(
Options{}
.set<storage_experimental::DirectPathXdsOverInterconnectOption>(true)
.set<AuthorityOption>("custom-authority"),
mock_detector);
EXPECT_EQ(options.get<EndpointOption>(),
"google-c2p:///storage-direct.googleapis.com?force-xds");
EXPECT_EQ(options.get<AuthorityOption>(), "custom-authority");
EXPECT_EQ(options.get<GrpcNumChannelsOption>(), 1);
}

/// @test Verify universe domain precedence also holds when the domain comes
/// from `GOOGLE_CLOUD_UNIVERSE_DOMAIN` rather than an option.
TEST(DefaultOptionsGrpc, DirectPathOverInterconnectUniverseDomainEnvVar) {
ScopedEnvironment ud("GOOGLE_CLOUD_UNIVERSE_DOMAIN", "my-ud.net");
auto mock_detector = std::make_shared<MockGcpDetector>();
EXPECT_CALL(*mock_detector, IsGoogleCloudBios())
.WillRepeatedly(Return(false));
EXPECT_CALL(*mock_detector, IsGoogleCloudServerless())
.WillRepeatedly(Return(false));

auto options = DefaultOptionsGrpc(
Options{}.set<storage_experimental::DirectPathXdsOverInterconnectOption>(
true),
mock_detector);
EXPECT_EQ(options.get<EndpointOption>(), "storage.my-ud.net");
EXPECT_EQ(options.get<AuthorityOption>(), "storage.my-ud.net");
}

TEST(DefaultOptionsGrpc, EndpointOptionsOverrideDefaults) {
ScopedEnvironment ud("GOOGLE_CLOUD_UNIVERSE_DOMAIN", "ud-env-var.net");

Expand Down
Loading