Skip to content

Commit 600a6ec

Browse files
committed
impl(bigtable): add DirectPath diagnostics
1 parent f44759f commit 600a6ec

7 files changed

Lines changed: 767 additions & 0 deletions

File tree

google/cloud/bigtable/CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,8 @@ add_library(
188188
internal/default_row_reader.h
189189
internal/defaults.cc
190190
internal/defaults.h
191+
internal/directpath_diagnostics.cc
192+
internal/directpath_diagnostics.h
191193
internal/directpath_prober.cc
192194
internal/directpath_prober.h
193195
internal/dynamic_channel_pool.h
@@ -477,6 +479,7 @@ if (BUILD_TESTING)
477479
internal/data_tracing_connection_test.cc
478480
internal/default_row_reader_test.cc
479481
internal/defaults_test.cc
482+
internal/directpath_diagnostics_test.cc
480483
internal/directpath_prober_test.cc
481484
internal/dynamic_channel_pool_test.cc
482485
internal/google_bytes_traits_test.cc

google/cloud/bigtable/bigtable_client_unit_tests.bzl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ bigtable_client_unit_tests = [
5454
"internal/data_tracing_connection_test.cc",
5555
"internal/default_row_reader_test.cc",
5656
"internal/defaults_test.cc",
57+
"internal/directpath_diagnostics_test.cc",
5758
"internal/directpath_prober_test.cc",
5859
"internal/dynamic_channel_pool_test.cc",
5960
"internal/google_bytes_traits_test.cc",

google/cloud/bigtable/google_cloud_cpp_bigtable.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ google_cloud_cpp_bigtable_hdrs = [
9292
"internal/data_tracing_connection.h",
9393
"internal/default_row_reader.h",
9494
"internal/defaults.h",
95+
"internal/directpath_diagnostics.h",
9596
"internal/directpath_prober.h",
9697
"internal/dynamic_channel_pool.h",
9798
"internal/endpoint_options.h",
@@ -210,6 +211,7 @@ google_cloud_cpp_bigtable_srcs = [
210211
"internal/data_tracing_connection.cc",
211212
"internal/default_row_reader.cc",
212213
"internal/defaults.cc",
214+
"internal/directpath_diagnostics.cc",
213215
"internal/directpath_prober.cc",
214216
"internal/google_bytes_traits.cc",
215217
"internal/grpc_metrics_exporter.cc",
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "google/cloud/bigtable/internal/directpath_diagnostics.h"
16+
#include "google/cloud/bigtable/options.h"
17+
#include "google/cloud/internal/detect_gcp.h"
18+
#include "google/cloud/internal/make_status.h"
19+
#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS
20+
#include <opentelemetry/context/runtime_context.h>
21+
#endif
22+
#include <chrono>
23+
#include <cstdint>
24+
#include <cstring>
25+
#include <memory>
26+
#include <string>
27+
28+
#ifndef _WIN32
29+
#include <arpa/inet.h>
30+
#include <netinet/in.h>
31+
#include <ifaddrs.h>
32+
#include <netdb.h>
33+
#include <sys/socket.h>
34+
#include <sys/types.h>
35+
#include <unistd.h>
36+
#endif
37+
38+
namespace google {
39+
namespace cloud {
40+
namespace bigtable_internal {
41+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
42+
43+
std::string ToString(DiagnosticFailureReason reason) {
44+
switch (reason) {
45+
case DiagnosticFailureReason::kNotInGcp:
46+
return "not_in_gcp";
47+
case DiagnosticFailureReason::kMetadataUnreachable:
48+
return "metadata_unreachable";
49+
case DiagnosticFailureReason::kNoIpAssigned:
50+
return "no_ip_assigned";
51+
case DiagnosticFailureReason::kLoopbackMisconfigured:
52+
return "loopback_misconfigured";
53+
case DiagnosticFailureReason::kLoopbackMisconfiguredIpv4:
54+
return "loopback_misconfigured_ipv4";
55+
case DiagnosticFailureReason::kLoopbackMisconfiguredIpv6:
56+
return "loopback_misconfigured_ipv6";
57+
case DiagnosticFailureReason::kMetadataMissing:
58+
return "metadata_missing";
59+
case DiagnosticFailureReason::kXdsReachabilityFailed:
60+
return "xds_reachability_failed";
61+
case DiagnosticFailureReason::kXdsEdsFailed:
62+
return "xds_eds_failed";
63+
case DiagnosticFailureReason::kXdsMalformedEndpoint:
64+
return "xds_malformed_endpoint";
65+
case DiagnosticFailureReason::kRouteUnreachable:
66+
return "route_unreachable";
67+
case DiagnosticFailureReason::kAltsHandshakeFailed:
68+
return "alts_handshake_failed";
69+
case DiagnosticFailureReason::kTimeout:
70+
return "timeout";
71+
case DiagnosticFailureReason::kUnknown:
72+
return "unknown";
73+
}
74+
return "unknown";
75+
}
76+
77+
#ifndef _WIN32
78+
namespace {
79+
80+
bool CanConnectTcp(std::string const& host, std::uint16_t port,
81+
std::chrono::milliseconds timeout) {
82+
int const sock = socket(AF_INET, SOCK_STREAM, 0);
83+
if (sock < 0) return false;
84+
85+
std::int64_t const total_usec =
86+
std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
87+
struct timeval tv;
88+
tv.tv_sec = static_cast<time_t>(total_usec / 1000000);
89+
tv.tv_usec = static_cast<suseconds_t>(total_usec % 1000000);
90+
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
91+
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
92+
93+
struct sockaddr_in addr;
94+
std::memset(&addr, 0, sizeof(addr));
95+
addr.sin_family = AF_INET;
96+
addr.sin_port = htons(port);
97+
inet_pton(AF_INET, host.c_str(), &addr.sin_addr);
98+
99+
int const res =
100+
connect(sock, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr));
101+
close(sock);
102+
return res == 0;
103+
}
104+
105+
DiagnosticFailureReason CheckLoopbackConfiguration() {
106+
struct ifaddrs* ifaddr = nullptr;
107+
if (getifaddrs(&ifaddr) == -1) return DiagnosticFailureReason::kUnknown;
108+
109+
bool has_ipv4_lo = false;
110+
bool has_ipv6_lo = false;
111+
112+
for (struct ifaddrs* ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
113+
if (ifa->ifa_addr == nullptr) continue;
114+
if (std::string(ifa->ifa_name) == "lo") {
115+
if (ifa->ifa_addr->sa_family == AF_INET) {
116+
has_ipv4_lo = true;
117+
} else if (ifa->ifa_addr->sa_family == AF_INET6) {
118+
has_ipv6_lo = true;
119+
}
120+
}
121+
}
122+
freeifaddrs(ifaddr);
123+
124+
if (!has_ipv4_lo && !has_ipv6_lo) {
125+
return DiagnosticFailureReason::kLoopbackMisconfigured;
126+
}
127+
if (!has_ipv4_lo) {
128+
return DiagnosticFailureReason::kLoopbackMisconfiguredIpv4;
129+
}
130+
if (!has_ipv6_lo) {
131+
return DiagnosticFailureReason::kLoopbackMisconfiguredIpv6;
132+
}
133+
return DiagnosticFailureReason::kUnknown;
134+
}
135+
136+
} // namespace
137+
#endif
138+
139+
DiagnosticFailureReason DirectPathDiagnostics::RunDiagnostics(
140+
Options const& options) {
141+
return RunDiagnostics(options, internal::MakeGcpDetector());
142+
}
143+
144+
DiagnosticFailureReason DirectPathDiagnostics::RunDiagnostics(
145+
Options const& options, std::shared_ptr<internal::GcpDetector> detector,
146+
std::string const& metadata_host, std::uint16_t metadata_port) {
147+
// Step 1: Check platform (GCP VM)
148+
if (detector == nullptr) {
149+
detector = internal::MakeGcpDetector();
150+
}
151+
if (!detector->IsGoogleCloudBios()) {
152+
return DiagnosticFailureReason::kNotInGcp;
153+
}
154+
155+
#ifdef _WIN32
156+
return DiagnosticFailureReason::kUnknown;
157+
#else
158+
// Step 2: Metadata server reachability
159+
std::chrono::milliseconds timeout =
160+
options.get<bigtable::experimental::DirectPathDiagnosticsTimeoutOption>();
161+
if (timeout <= std::chrono::milliseconds::zero()) {
162+
timeout = std::chrono::milliseconds(500);
163+
}
164+
165+
if (!CanConnectTcp(metadata_host, metadata_port, timeout)) {
166+
return DiagnosticFailureReason::kMetadataUnreachable;
167+
}
168+
169+
// Step 3 & 4: Loopback configuration check
170+
DiagnosticFailureReason const lo_result = CheckLoopbackConfiguration();
171+
if (lo_result != DiagnosticFailureReason::kUnknown) {
172+
return lo_result;
173+
}
174+
175+
// Step 5: Route resolution or fallback
176+
return DiagnosticFailureReason::kUnknown;
177+
#endif
178+
}
179+
180+
#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS
181+
void DirectPathDiagnostics::RunAsync(
182+
CompletionQueue cq, Options const& options,
183+
std::shared_ptr<DirectAccessCompatibility> direct_access_compatibility) {
184+
RunAsync(std::move(cq), options, std::move(direct_access_compatibility),
185+
internal::MakeGcpDetector());
186+
}
187+
188+
void DirectPathDiagnostics::RunAsync(
189+
CompletionQueue cq, Options const& options,
190+
std::shared_ptr<DirectAccessCompatibility> direct_access_compatibility,
191+
std::shared_ptr<internal::GcpDetector> detector,
192+
std::string const& metadata_host, std::uint16_t metadata_port) {
193+
std::chrono::milliseconds timeout =
194+
options.get<bigtable::experimental::DirectPathDiagnosticsTimeoutOption>();
195+
if (timeout <= std::chrono::milliseconds::zero()) {
196+
timeout = std::chrono::milliseconds(5000);
197+
}
198+
199+
cq.RunAsync([options,
200+
direct_access_compatibility =
201+
std::move(direct_access_compatibility),
202+
detector = std::move(detector), metadata_host, metadata_port]() {
203+
DiagnosticFailureReason const reason =
204+
DirectPathDiagnostics::RunDiagnostics(options, detector, metadata_host,
205+
metadata_port);
206+
if (direct_access_compatibility != nullptr) {
207+
direct_access_compatibility->Record(
208+
opentelemetry::context::RuntimeContext::GetCurrent(), 0,
209+
DirectAccessCompatibilityLabels{"", ToString(reason)});
210+
}
211+
});
212+
}
213+
#endif
214+
215+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
216+
} // namespace bigtable_internal
217+
} // namespace cloud
218+
} // namespace google
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#ifndef GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_INTERNAL_DIRECTPATH_DIAGNOSTICS_H
16+
#define GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_INTERNAL_DIRECTPATH_DIAGNOSTICS_H
17+
18+
#include "google/cloud/bigtable/options.h"
19+
#include "google/cloud/bigtable/version.h"
20+
#include "google/cloud/completion_queue.h"
21+
#include "google/cloud/future.h"
22+
#include "google/cloud/options.h"
23+
#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS
24+
#include "google/cloud/bigtable/internal/client_schema_metrics.h"
25+
#endif
26+
#include "google/cloud/internal/detect_gcp.h"
27+
#include <cstdint>
28+
#include <memory>
29+
#include <string>
30+
#include <utility>
31+
32+
namespace google {
33+
namespace cloud {
34+
namespace bigtable_internal {
35+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_BEGIN
36+
37+
enum class DiagnosticFailureReason {
38+
kNotInGcp,
39+
kMetadataUnreachable,
40+
kNoIpAssigned,
41+
kLoopbackMisconfigured,
42+
kLoopbackMisconfiguredIpv4,
43+
kLoopbackMisconfiguredIpv6,
44+
kMetadataMissing,
45+
kXdsReachabilityFailed,
46+
kXdsEdsFailed,
47+
kXdsMalformedEndpoint,
48+
kRouteUnreachable,
49+
kAltsHandshakeFailed,
50+
kTimeout,
51+
kUnknown,
52+
};
53+
54+
std::string ToString(DiagnosticFailureReason reason);
55+
56+
class DirectPathDiagnostics {
57+
public:
58+
static DiagnosticFailureReason RunDiagnostics(Options const& options);
59+
60+
/// For testing only.
61+
static DiagnosticFailureReason RunDiagnostics(
62+
Options const& options, std::shared_ptr<internal::GcpDetector> detector,
63+
std::string const& metadata_host = "169.254.169.254",
64+
std::uint16_t metadata_port = 80);
65+
66+
#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS
67+
static void RunAsync(CompletionQueue cq, Options const& options,
68+
std::shared_ptr<DirectAccessCompatibility>
69+
direct_access_compatibility = nullptr);
70+
71+
/// For testing only.
72+
static void RunAsync(CompletionQueue cq, Options const& options,
73+
std::shared_ptr<DirectAccessCompatibility>
74+
direct_access_compatibility,
75+
std::shared_ptr<internal::GcpDetector> detector,
76+
std::string const& metadata_host = "169.254.169.254",
77+
std::uint16_t metadata_port = 80);
78+
#endif
79+
};
80+
81+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
82+
} // namespace bigtable_internal
83+
} // namespace cloud
84+
} // namespace google
85+
86+
#endif // GOOGLE_CLOUD_CPP_GOOGLE_CLOUD_BIGTABLE_INTERNAL_DIRECTPATH_DIAGNOSTICS_H

0 commit comments

Comments
 (0)