Skip to content

Commit a14deab

Browse files
committed
impl(bigtable): enable directpath probe and cloudpath fallback
1 parent 5c916cf commit a14deab

7 files changed

Lines changed: 347 additions & 22 deletions

File tree

ci/cloudbuild/builds/observability.sh

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ mapfile -t integration_args < <(integration::bazel_args)
3636
observability_key_base="observability-key-$(date +"%Y-%m")"
3737
readonly KEY_DIR="/dev/shm"
3838
readonly SECRETS_BUCKET="gs://cloud-cpp-testing-resources-secrets"
39-
gcloud storage cp --quiet "${SECRETS_BUCKET}/${observability_key_base}.json" "${KEY_DIR}/${observability_key_base}.json" >/dev/null 2>&1 || true
39+
gcloud storage cp --quiet \
40+
"${SECRETS_BUCKET}/${observability_key_base}.json" \
41+
"${KEY_DIR}/${observability_key_base}.json" >/dev/null 2>&1 || true
4042
if [[ -r "${KEY_DIR}/${observability_key_base}.json" ]]; then
4143
GOOGLE_CLOUD_CPP_TEST_OBSERVABILITY_KEY_FILE_JSON="${KEY_DIR}/${observability_key_base}.json"
4244
fi
@@ -175,6 +177,18 @@ gcloud storage cp --quiet /tmp/startup.log /tmp/*.log /tmp/*.xml /tmp/exit_code.
175177
EOF
176178
)
177179

180+
io::log_h2 "Running DirectPath fallback and diagnostics test on the build machine"
181+
(
182+
export GOOGLE_CLOUD_PROJECT="${PROJECT_ID}"
183+
export GOOGLE_CLOUD_CPP_BIGTABLE_TEST_INSTANCE_ID="${BIGTABLE_INSTANCE_ID}"
184+
export GOOGLE_CLOUD_CPP_BIGTABLE_TEST_CLUSTER_ID="${BIGTABLE_CLUSTER_ID}"
185+
export GOOGLE_CLOUD_CPP_BIGTABLE_TEST_ZONE_A="${BIGTABLE_ZONE_A}"
186+
export GOOGLE_CLOUD_CPP_BIGTABLE_TEST_ZONE_B="${BIGTABLE_ZONE_B}"
187+
export GOOGLE_CLOUD_CPP_BIGTABLE_TESTING_CHANNEL_POOL=dynamic
188+
io::run "${TEST_DYNAMIC_BIN}" \
189+
--gtest_filter="*VerifyDirectAccessFallbackAndDiagnosticsMetric*"
190+
)
191+
178192
for candidate_zone in "${ZONES[@]}"; do
179193
ZONE="${candidate_zone}"
180194
io::log_h2 "Creating ephemeral DirectPath VM instance: ${VM_NAME} in zone ${ZONE}"
@@ -205,7 +219,7 @@ fi
205219

206220
io::log_h2 "Waiting for test execution to complete on VM ${VM_NAME}..."
207221
TEST_COMPLETED="false"
208-
for i in {1..90}; do
222+
for i in {1..180}; do
209223
if gcloud storage cp --quiet "${GCS_PATH}/results/exit_code.txt" /tmp/exit_code.txt >/dev/null 2>&1; then
210224
TEST_COMPLETED="true"
211225
io::log_yellow "Test execution on VM ${VM_NAME} finished."

google/cloud/bigtable/data_connection.cc

Lines changed: 148 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,10 @@
1818
#include "google/cloud/bigtable/internal/data_connection_impl.h"
1919
#include "google/cloud/bigtable/internal/data_tracing_connection.h"
2020
#include "google/cloud/bigtable/internal/defaults.h"
21+
#include "google/cloud/bigtable/internal/directpath_diagnostics.h"
22+
#include "google/cloud/bigtable/internal/directpath_prober.h"
2123
#include "google/cloud/bigtable/internal/grpc_metrics_exporter.h"
24+
#include "google/cloud/bigtable/internal/metrics.h"
2225
#include "google/cloud/bigtable/internal/mutate_rows_limiter.h"
2326
#include "google/cloud/bigtable/internal/partial_result_set_source.h"
2427
#include "google/cloud/bigtable/internal/row_reader_impl.h"
@@ -27,12 +30,15 @@
2730
#include "google/cloud/background_threads.h"
2831
#include "google/cloud/common_options.h"
2932
#include "google/cloud/credentials.h"
33+
#include "google/cloud/future.h"
3034
#include "google/cloud/grpc_options.h"
3135
#include "google/cloud/internal/opentelemetry.h"
3236
#include "google/cloud/internal/unified_grpc_credentials.h"
3337
#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS
38+
#include "google/cloud/bigtable/internal/client_schema_metrics.h"
3439
#include "google/cloud/monitoring/v3/metric_connection.h"
3540
#include "google/cloud/internal/random.h"
41+
#include <opentelemetry/context/runtime_context.h>
3642
#endif // GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS
3743
#include <memory>
3844
#include <mutex>
@@ -86,13 +92,30 @@ MakeOperationContextFactory(
8692
return std::make_unique<bigtable_internal::SimpleOperationContextFactory>();
8793
}
8894

95+
#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS
96+
std::shared_ptr<bigtable_internal::DirectAccessCompatibility>
97+
GetDirectAccessCompatibility(
98+
bigtable_internal::OperationContextFactory const* factory) {
99+
auto const* metrics_factory =
100+
dynamic_cast<bigtable_internal::MetricsOperationContextFactory const*>(
101+
factory);
102+
return metrics_factory != nullptr
103+
? metrics_factory->direct_access_compatibility()
104+
: nullptr;
105+
}
106+
#endif // GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS
107+
89108
struct DataConnectionComponents {
90109
Options options;
91110
std::unique_ptr<BackgroundThreads> background;
92111
std::shared_ptr<google::cloud::internal::GrpcAuthenticationStrategy> auth;
93112
std::shared_ptr<bigtable_internal::MutateRowsLimiter> limiter;
94113
std::unique_ptr<bigtable_internal::OperationContextFactory>
95114
operation_context_factory;
115+
#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS
116+
std::shared_ptr<bigtable_internal::DirectAccessCompatibility>
117+
direct_access_compatibility;
118+
#endif
96119
};
97120

98121
DataConnectionComponents MakeDataConnectionComponents(
@@ -116,12 +139,32 @@ DataConnectionComponents MakeDataConnectionComponents(
116139
std::unique_ptr<bigtable_internal::OperationContextFactory>
117140
operation_context_factory =
118141
MakeOperationContextFactory(options, instances);
142+
#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS
143+
std::shared_ptr<bigtable_internal::DirectAccessCompatibility>
144+
direct_access_compatibility =
145+
GetDirectAccessCompatibility(operation_context_factory.get());
146+
return {std::move(options),
147+
std::move(background),
148+
std::move(auth),
149+
std::move(limiter),
150+
std::move(operation_context_factory),
151+
std::move(direct_access_compatibility)};
152+
#else
119153
return {std::move(options), std::move(background), std::move(auth),
120154
std::move(limiter), std::move(operation_context_factory)};
155+
#endif
121156
}
122157

123158
std::shared_ptr<bigtable::DataConnection> MakeSingleStubDataConnection(
124159
DataConnectionComponents components) {
160+
#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS
161+
if (components.direct_access_compatibility != nullptr) {
162+
components.direct_access_compatibility->Record(
163+
opentelemetry::context::RuntimeContext::GetCurrent(), 0,
164+
bigtable_internal::DirectAccessCompatibilityLabels{
165+
"", "manually_disabled"});
166+
}
167+
#endif
125168
std::shared_ptr<bigtable_internal::BigtableStub> stub =
126169
bigtable_internal::CreateBigtableStub(std::move(components.auth),
127170
components.background->cq(),
@@ -150,6 +193,14 @@ std::shared_ptr<bigtable::DataConnection> MakeInstanceAffinityDataConnection(
150193
std::shared_ptr<bigtable_internal::BigtableStub>>
151194
affinity_stubs = bigtable_internal::CreateBigtableAffinityStubs(
152195
instances, stub_creation_fn);
196+
#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS
197+
if (components.direct_access_compatibility != nullptr) {
198+
components.direct_access_compatibility->Record(
199+
opentelemetry::context::RuntimeContext::GetCurrent(), 0,
200+
bigtable_internal::DirectAccessCompatibilityLabels{
201+
"", "manually_disabled"});
202+
}
203+
#endif
153204
return std::make_shared<bigtable_internal::DataConnectionImpl>(
154205
std::move(components.background),
155206
std::make_unique<bigtable_internal::StubManager>(
@@ -161,6 +212,7 @@ std::shared_ptr<bigtable::DataConnection> MakeInstanceAffinityDataConnection(
161212
std::shared_ptr<bigtable::DataConnection> MakeDirectPathDataConnection(
162213
DataConnectionComponents components,
163214
std::vector<bigtable::InstanceResource> const& instances) {
215+
// Step 1: Configure separate Options for DirectPath and CloudPath (fallback).
164216
Options directpath_options = components.options;
165217
directpath_options
166218
.set<::google::cloud::bigtable_internal::DataEndpointOption>(
@@ -172,25 +224,106 @@ std::shared_ptr<bigtable::DataConnection> MakeDirectPathDataConnection(
172224
directpath_options.set<bigtable::experimental::DirectPathModeOption>(
173225
bigtable::experimental::DirectPathMode::kEnabled);
174226

175-
auto stub_creation_fn =
176-
[auth = components.auth, cq = components.background->cq(),
177-
directpath_options](std::string_view instance_name,
178-
bigtable_internal::StubManager::Priming priming) {
179-
return bigtable_internal::CreateBigtableStub(
180-
auth, cq, instance_name, priming, directpath_options);
181-
};
182-
absl::flat_hash_map<std::string,
183-
std::shared_ptr<bigtable_internal::BigtableStub>>
184-
affinity_stubs = bigtable_internal::CreateBigtableAffinityStubs(
185-
instances, stub_creation_fn);
186-
std::unique_ptr<bigtable_internal::StubManager> stub_manager =
187-
std::make_unique<bigtable_internal::StubManager>(
188-
std::move(affinity_stubs), stub_creation_fn);
227+
Options cloudpath_options = components.options;
228+
cloudpath_options.set<::google::cloud::bigtable_internal::DataEndpointOption>(
229+
"bigtable.googleapis.com");
230+
cloudpath_options.set<EndpointOption>("bigtable.googleapis.com");
231+
cloudpath_options.set<AuthorityOption>("bigtable.googleapis.com");
232+
cloudpath_options.set<bigtable::experimental::DirectPathModeOption>(
233+
bigtable::experimental::DirectPathMode::kDisabled);
234+
235+
// Step 2: Concurrently create StubManagers for both DirectPath and CloudPath
236+
// on background threads so that stubs are warming up while probing proceeds.
237+
promise<std::unique_ptr<bigtable_internal::StubManager>> directpath_promise;
238+
future<std::unique_ptr<bigtable_internal::StubManager>> directpath_future =
239+
directpath_promise.get_future();
240+
promise<std::unique_ptr<bigtable_internal::StubManager>> cloudpath_promise;
241+
future<std::unique_ptr<bigtable_internal::StubManager>> cloudpath_future =
242+
cloudpath_promise.get_future();
243+
244+
components.background->cq().RunAsync(
245+
[p = std::move(directpath_promise), auth = components.auth,
246+
cq = components.background->cq(), directpath_options,
247+
instances]() mutable {
248+
auto stub_creation_fn =
249+
[auth, cq, directpath_options](
250+
std::string_view instance_name,
251+
bigtable_internal::StubManager::Priming priming) {
252+
return bigtable_internal::CreateBigtableStub(
253+
auth, cq, instance_name, priming, directpath_options);
254+
};
255+
absl::flat_hash_map<std::string,
256+
std::shared_ptr<bigtable_internal::BigtableStub>>
257+
affinity_stubs = bigtable_internal::CreateBigtableAffinityStubs(
258+
instances, stub_creation_fn);
259+
p.set_value(std::make_unique<bigtable_internal::StubManager>(
260+
std::move(affinity_stubs), stub_creation_fn));
261+
});
262+
263+
components.background->cq().RunAsync(
264+
[p = std::move(cloudpath_promise), auth = components.auth,
265+
cq = components.background->cq(), cloudpath_options,
266+
instances]() mutable {
267+
auto stub_creation_fn =
268+
[auth, cq, cloudpath_options](
269+
std::string_view instance_name,
270+
bigtable_internal::StubManager::Priming priming) {
271+
return bigtable_internal::CreateBigtableStub(
272+
auth, cq, instance_name, priming, cloudpath_options);
273+
};
274+
absl::flat_hash_map<std::string,
275+
std::shared_ptr<bigtable_internal::BigtableStub>>
276+
affinity_stubs = bigtable_internal::CreateBigtableAffinityStubs(
277+
instances, stub_creation_fn);
278+
p.set_value(std::make_unique<bigtable_internal::StubManager>(
279+
std::move(affinity_stubs), stub_creation_fn));
280+
});
281+
282+
// Step 3: Probe DirectPath connectivity and ALTS negotiation on the primary
283+
// instance.
284+
StatusOr<bigtable_internal::DirectPathProbeResult> const probe_result =
285+
bigtable_internal::DirectPathProber::Probe(
286+
components.auth, instances.front(), directpath_options,
287+
components.background->cq());
288+
289+
// Step 4a: If DirectPath probing succeeds, use DirectPath stubs, discard the
290+
// fallback CloudPath future asynchronously, and record successful metrics.
291+
if (probe_result.ok() && probe_result->success) {
292+
std::unique_ptr<bigtable_internal::StubManager> stub_manager =
293+
directpath_future.get();
294+
components.background->cq().RunAsync(
295+
[f = std::move(cloudpath_future)]() mutable { (void)f.get(); });
296+
#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS
297+
if (components.direct_access_compatibility != nullptr) {
298+
components.direct_access_compatibility->Record(
299+
opentelemetry::context::RuntimeContext::GetCurrent(), 1,
300+
bigtable_internal::DirectAccessCompatibilityLabels{
301+
bigtable_internal::ToString(probe_result->ip_preference), ""});
302+
}
303+
#endif
304+
return std::make_shared<bigtable_internal::DataConnectionImpl>(
305+
std::move(components.background), std::move(stub_manager),
306+
std::move(components.operation_context_factory),
307+
std::move(components.limiter), std::move(directpath_options));
308+
}
189309

310+
// Step 4b: If DirectPath probing fails, fall back to the CloudPath
311+
// StubManager, discard the unused DirectPath future asynchronously, and run
312+
// environmental diagnostics asynchronously to diagnose and record metrics for
313+
// the failure reason.
314+
std::unique_ptr<bigtable_internal::StubManager> stub_manager =
315+
cloudpath_future.get();
316+
components.background->cq().RunAsync(
317+
[f = std::move(directpath_future)]() mutable { (void)f.get(); });
318+
#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS
319+
bigtable_internal::DirectPathDiagnostics::RunAsync(
320+
components.background->cq(), directpath_options,
321+
components.direct_access_compatibility);
322+
#endif
190323
return std::make_shared<bigtable_internal::DataConnectionImpl>(
191324
std::move(components.background), std::move(stub_manager),
192325
std::move(components.operation_context_factory),
193-
std::move(components.limiter), std::move(directpath_options));
326+
std::move(components.limiter), std::move(cloudpath_options));
194327
}
195328

196329
std::shared_ptr<bigtable::DataConnection> WrapDataTracing(

google/cloud/bigtable/internal/defaults.cc

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,13 @@ bool IsDirectPath(Options const& options) {
132132
// Bigtable specific env var for Direct Path support used by all clients.
133133
std::optional<std::string> const cbt_direct_path =
134134
google::cloud::internal::GetEnv("CBT_ENABLE_DIRECTPATH");
135-
if (direct_path.has_value() || cbt_direct_path.has_value()) {
136-
return absl::c_any_of(
137-
absl::StrSplit(direct_path.value_or(""), ','),
138-
[](absl::string_view v) { return v == "bigtable"; }) ||
139-
cbt_direct_path.value_or("") == "true";
135+
if (cbt_direct_path.has_value()) {
136+
if (*cbt_direct_path == "false") return false;
137+
if (*cbt_direct_path == "true") return true;
138+
}
139+
if (direct_path.has_value()) {
140+
return absl::c_any_of(absl::StrSplit(*direct_path, ','),
141+
[](absl::string_view v) { return v == "bigtable"; });
140142
}
141143
return options.get<experimental::DirectPathModeOption>() ==
142144
experimental::DirectPathMode::kEnabled;

google/cloud/bigtable/internal/defaults_test.cc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,16 @@ TEST(EndpointEnvTest, DirectPathEnvVarOverridesDirectPathModeOption) {
619619
EXPECT_FALSE(IsDirectPath(opts));
620620
}
621621

622+
TEST(EndpointEnvTest, CbtDirectPathFalseOverridesCloudDirectPath) {
623+
ScopedEnvironment emulator("BIGTABLE_EMULATOR_HOST", std::nullopt);
624+
ScopedEnvironment direct_path("GOOGLE_CLOUD_ENABLE_DIRECT_PATH", "bigtable");
625+
ScopedEnvironment cbt_direct_path("CBT_ENABLE_DIRECTPATH", "false");
626+
627+
Options const opts = Options{}.set<experimental::DirectPathModeOption>(
628+
experimental::DirectPathMode::kEnabled);
629+
EXPECT_FALSE(IsDirectPath(opts));
630+
}
631+
622632
TEST(EndpointEnvTest, DirectPathTimeoutDefaults) {
623633
ScopedEnvironment emulator("BIGTABLE_EMULATOR_HOST", std::nullopt);
624634
Options const opts = DefaultOptions();

google/cloud/bigtable/internal/operation_context_factory.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -355,6 +355,10 @@ void MetricsOperationContextFactory::InitializeProvider(
355355
context->AddMetricReader(std::move(reader));
356356
provider_ = opentelemetry::sdk::metrics::MeterProviderFactory::Create(
357357
std::move(context));
358+
DirectAccessCompatibility metric(kBigtableMetricNamePath, provider_);
359+
direct_access_compatibility_ = std::shared_ptr<DirectAccessCompatibility>(
360+
static_cast<DirectAccessCompatibility*>(
361+
metric.clone(client_resource_labels_).release()));
358362
}
359363

360364
std::shared_ptr<OperationContext> MetricsOperationContextFactory::ReadRow(

google/cloud/bigtable/internal/operation_context_factory.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,11 @@ class MetricsOperationContextFactory : public OperationContextFactory {
135135
std::string const& instance_name,
136136
std::string const& app_profile) override;
137137

138+
std::shared_ptr<DirectAccessCompatibility> direct_access_compatibility()
139+
const {
140+
return direct_access_compatibility_;
141+
}
142+
138143
private:
139144
void InitializeProvider(
140145
std::shared_ptr<monitoring_v3::MetricServiceConnection> conn,
@@ -144,6 +149,7 @@ class MetricsOperationContextFactory : public OperationContextFactory {
144149
std::shared_ptr<OperationContext::Clock> clock_;
145150
std::shared_ptr<opentelemetry::metrics::MeterProvider> provider_;
146151
ClientResourceLabels client_resource_labels_;
152+
std::shared_ptr<DirectAccessCompatibility> direct_access_compatibility_;
147153

148154
// These vectors are initialized exactly once and the initialization is
149155
// delayed until the first time the corresponding method is called.

0 commit comments

Comments
 (0)