Skip to content

Commit fdfdbea

Browse files
authored
impl(bigtable): add DirectPath diagnostics (#16378)
1 parent 61a04eb commit fdfdbea

7 files changed

Lines changed: 1036 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: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,269 @@
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+
class DefaultDirectPathNetworkSystem : public DirectPathNetworkSystem {
81+
public:
82+
bool CanConnectTcp(std::string const& host, std::uint16_t port,
83+
std::chrono::milliseconds timeout) override {
84+
int const sock = socket(AF_INET, SOCK_STREAM, 0);
85+
if (sock < 0) return false;
86+
87+
std::int64_t const total_usec =
88+
std::chrono::duration_cast<std::chrono::microseconds>(timeout).count();
89+
struct timeval tv;
90+
tv.tv_sec = static_cast<time_t>(total_usec / 1000000);
91+
tv.tv_usec = static_cast<suseconds_t>(total_usec % 1000000);
92+
setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
93+
setsockopt(sock, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
94+
95+
struct sockaddr_in addr;
96+
std::memset(&addr, 0, sizeof(addr));
97+
addr.sin_family = AF_INET;
98+
addr.sin_port = htons(port);
99+
if (inet_pton(AF_INET, host.c_str(), &addr.sin_addr) <= 0) {
100+
close(sock);
101+
return false;
102+
}
103+
104+
int const res =
105+
connect(sock, reinterpret_cast<struct sockaddr*>(&addr), sizeof(addr));
106+
close(sock);
107+
return res == 0;
108+
}
109+
110+
DiagnosticFailureReason CheckLoopbackConfiguration() override {
111+
struct ifaddrs* ifaddr = nullptr;
112+
if (getifaddrs(&ifaddr) == -1) return DiagnosticFailureReason::kUnknown;
113+
114+
bool has_ipv4_lo = false;
115+
bool has_ipv6_lo = false;
116+
117+
for (struct ifaddrs* ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) {
118+
if (ifa->ifa_addr == nullptr) continue;
119+
if (ifa->ifa_name != nullptr && std::strcmp(ifa->ifa_name, "lo") == 0) {
120+
if (ifa->ifa_addr->sa_family == AF_INET) {
121+
has_ipv4_lo = true;
122+
} else if (ifa->ifa_addr->sa_family == AF_INET6) {
123+
has_ipv6_lo = true;
124+
}
125+
}
126+
}
127+
freeifaddrs(ifaddr);
128+
129+
if (!has_ipv4_lo && !has_ipv6_lo) {
130+
return DiagnosticFailureReason::kLoopbackMisconfigured;
131+
}
132+
if (!has_ipv4_lo) {
133+
return DiagnosticFailureReason::kLoopbackMisconfiguredIpv4;
134+
}
135+
if (!has_ipv6_lo) {
136+
return DiagnosticFailureReason::kLoopbackMisconfiguredIpv6;
137+
}
138+
return DiagnosticFailureReason::kUnknown;
139+
}
140+
};
141+
142+
} // namespace
143+
144+
std::shared_ptr<DirectPathNetworkSystem> MakeDefaultDirectPathNetworkSystem() {
145+
return std::make_shared<DefaultDirectPathNetworkSystem>();
146+
}
147+
148+
#else
149+
150+
namespace {
151+
152+
class DefaultDirectPathNetworkSystem : public DirectPathNetworkSystem {
153+
public:
154+
bool CanConnectTcp(std::string const&, std::uint16_t,
155+
std::chrono::milliseconds) override {
156+
return false;
157+
}
158+
DiagnosticFailureReason CheckLoopbackConfiguration() override {
159+
return DiagnosticFailureReason::kUnknown;
160+
}
161+
};
162+
163+
} // namespace
164+
165+
std::shared_ptr<DirectPathNetworkSystem> MakeDefaultDirectPathNetworkSystem() {
166+
return std::make_shared<DefaultDirectPathNetworkSystem>();
167+
}
168+
#endif
169+
170+
DiagnosticFailureReason DirectPathDiagnostics::RunDiagnostics(
171+
Options const& options) {
172+
return RunDiagnostics(options, internal::MakeGcpDetector(),
173+
MakeDefaultDirectPathNetworkSystem(), "169.254.169.254",
174+
80);
175+
}
176+
177+
DiagnosticFailureReason DirectPathDiagnostics::RunDiagnostics(
178+
Options const& options,
179+
std::shared_ptr<internal::GcpDetector> const& detector,
180+
std::shared_ptr<DirectPathNetworkSystem> const& network_system,
181+
std::string const& metadata_host, std::uint16_t metadata_port) {
182+
// Step 1: Check platform (GCP VM)
183+
auto effective_detector = detector;
184+
if (effective_detector == nullptr) {
185+
effective_detector = internal::MakeGcpDetector();
186+
}
187+
if (!effective_detector->IsGoogleCloudBios()) {
188+
return DiagnosticFailureReason::kNotInGcp;
189+
}
190+
191+
auto effective_network_system = network_system;
192+
if (effective_network_system == nullptr) {
193+
effective_network_system = MakeDefaultDirectPathNetworkSystem();
194+
}
195+
196+
// Step 2: Metadata server reachability
197+
std::chrono::milliseconds timeout =
198+
options.get<bigtable::experimental::DirectPathDiagnosticsTimeoutOption>();
199+
if (timeout <= std::chrono::milliseconds::zero()) {
200+
timeout = std::chrono::milliseconds(500);
201+
}
202+
203+
if (!effective_network_system->CanConnectTcp(metadata_host, metadata_port,
204+
timeout)) {
205+
return DiagnosticFailureReason::kMetadataUnreachable;
206+
}
207+
208+
// Step 3 & 4: Loopback configuration check
209+
DiagnosticFailureReason const lo_result =
210+
effective_network_system->CheckLoopbackConfiguration();
211+
if (lo_result != DiagnosticFailureReason::kUnknown) {
212+
return lo_result;
213+
}
214+
215+
// Step 5: Route resolution or fallback
216+
return DiagnosticFailureReason::kUnknown;
217+
}
218+
219+
#ifdef GOOGLE_CLOUD_CPP_BIGTABLE_WITH_OTEL_METRICS
220+
void DirectPathDiagnostics::RunAsync(CompletionQueue cq,
221+
Options const& options) {
222+
RunAsync(std::move(cq), options, nullptr);
223+
}
224+
225+
void DirectPathDiagnostics::RunAsync(
226+
CompletionQueue cq, Options const& options,
227+
std::shared_ptr<DirectAccessCompatibility> direct_access_compatibility) {
228+
RunAsync(std::move(cq), options, std::move(direct_access_compatibility),
229+
internal::MakeGcpDetector(), MakeDefaultDirectPathNetworkSystem(),
230+
"169.254.169.254", 80);
231+
}
232+
233+
void DirectPathDiagnostics::RunAsync(
234+
CompletionQueue cq, Options const& options,
235+
std::shared_ptr<DirectAccessCompatibility> direct_access_compatibility,
236+
std::shared_ptr<internal::GcpDetector> detector,
237+
std::shared_ptr<DirectPathNetworkSystem> network_system,
238+
std::string const& metadata_host, std::uint16_t metadata_port) {
239+
auto run_options = options;
240+
if (run_options
241+
.get<bigtable::experimental::DirectPathDiagnosticsTimeoutOption>() <=
242+
std::chrono::milliseconds::zero()) {
243+
run_options.set<bigtable::experimental::DirectPathDiagnosticsTimeoutOption>(
244+
std::chrono::milliseconds(5000));
245+
}
246+
247+
cq.RunAsync(
248+
[run_options,
249+
direct_access_compatibility = std::move(direct_access_compatibility),
250+
detector = std::move(detector),
251+
network_system = std::move(network_system), metadata_host,
252+
metadata_port]() {
253+
DiagnosticFailureReason const reason =
254+
DirectPathDiagnostics::RunDiagnostics(run_options, detector,
255+
network_system, metadata_host,
256+
metadata_port);
257+
if (direct_access_compatibility != nullptr) {
258+
direct_access_compatibility->Record(
259+
opentelemetry::context::RuntimeContext::GetCurrent(), 0,
260+
DirectAccessCompatibilityLabels{"", ToString(reason)});
261+
}
262+
});
263+
}
264+
#endif
265+
266+
GOOGLE_CLOUD_CPP_INLINE_NAMESPACE_END
267+
} // namespace bigtable_internal
268+
} // namespace cloud
269+
} // namespace google

0 commit comments

Comments
 (0)