Skip to content

Commit 0ba8e5f

Browse files
authored
ci(bigtable): add fake monitoring server and metric test that uses it (googleapis#16266)
1 parent f5f12f3 commit 0ba8e5f

21 files changed

Lines changed: 987 additions & 5 deletions

ci/cloudbuild/builds/lib/integration.sh

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,11 @@ source module ci/lib/io.sh
2929

3030
# To run the integration tests we need to install the dependencies for the storage emulator
3131
export PATH="${HOME}/.local/bin:${PATH}"
32-
python3 -m pip uninstall -y --quiet googleapis-storage-testbench
33-
python3 -m pip install --upgrade --user --quiet --disable-pip-version-check \
34-
"git+https://github.com/googleapis/storage-testbench@v0.61.0"
32+
if python3 -m pip --version >/dev/null 2>&1; then
33+
python3 -m pip uninstall -y --quiet googleapis-storage-testbench || true
34+
python3 -m pip install --upgrade --user --quiet --disable-pip-version-check \
35+
"git+https://github.com/googleapis/storage-testbench@v0.61.0" || true
36+
fi
3537

3638
# Some of the tests will need a valid roots.pem file.
3739
rm -f /dev/shm/roots.pem
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/bin/bash
2+
#
3+
# Copyright 2026 Google LLC
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# https://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
17+
set -euo pipefail
18+
19+
source "$(dirname "$0")/../../lib/init.sh"
20+
source module ci/cloudbuild/builds/lib/bazel.sh
21+
source module ci/cloudbuild/builds/lib/cloudcxxrc.sh
22+
source module ci/cloudbuild/builds/lib/integration.sh
23+
source module ci/lib/io.sh
24+
25+
export CC=clang
26+
export CXX=clang++
27+
28+
mapfile -t args < <(bazel::common_args)
29+
args+=("--//google/cloud/bigtable:enable_metrics=true")
30+
mapfile -t integration_args < <(integration::bazel_args)
31+
32+
io::log_h2 "Building OtelCollector targets and Bigtable Observability integration tests"
33+
io::run bazel build "${args[@]}" \
34+
//ci/otel_collector:otel_collector_main \
35+
//google/cloud/bigtable/tests:observability_integration_test-default \
36+
//google/cloud/bigtable/tests:observability_integration_test-dynamic-pool
37+
38+
io::log_h2 "Running Bigtable Observability Integration Tests against production endpoint"
39+
io::run bazel test "${args[@]}" "${integration_args[@]}" \
40+
--cache_test_results="auto" \
41+
-- //google/cloud/bigtable/tests:observability_integration_test-default \
42+
//google/cloud/bigtable/tests:observability_integration_test-dynamic-pool
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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+
filename: ci/cloudbuild/cloudbuild.yaml
16+
github:
17+
name: google-cloud-cpp
18+
owner: googleapis
19+
push:
20+
branch: main
21+
includeBuildLogs: INCLUDE_BUILD_LOGS_WITH_STATUS
22+
name: observability-ci
23+
substitutions:
24+
_BUILD_NAME: observability
25+
_DISTRO: fedora-latest-bazel
26+
_TRIGGER_TYPE: ci
27+
tags:
28+
- ci
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
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+
filename: ci/cloudbuild/cloudbuild.yaml
16+
github:
17+
name: google-cloud-cpp
18+
owner: googleapis
19+
pullRequest:
20+
branch: main
21+
commentControl: COMMENTS_ENABLED_FOR_EXTERNAL_CONTRIBUTORS_ONLY
22+
includeBuildLogs: INCLUDE_BUILD_LOGS_WITH_STATUS
23+
name: observability-pr
24+
substitutions:
25+
_BUILD_NAME: observability
26+
_DISTRO: fedora-latest-bazel
27+
_TRIGGER_TYPE: pr
28+
tags:
29+
- pr

ci/lib/io.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ fi # include guard
3030
# Callers may use these IO_* variables directly, but should prefer to use the
3131
# logging functions below instead. For example, prefer `io::log_green "..."`
3232
# over `echo "${IO_COLOR_GREEN}...${IO_RESET}"`.
33-
if [ -t 0 ] && command -v tput >/dev/null; then
33+
if [ -t 0 ] && command -v tput >/dev/null && tput bold >/dev/null 2>&1; then
3434
IO_BOLD="$(tput bold)"
3535
IO_COLOR_RED="$(tput setaf 1)"
3636
IO_COLOR_GREEN="$(tput setaf 2)"

ci/otel_collector/ARCHITECTURE.md

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
# Architectural Design: Observability Collector Server
2+
3+
## 1. Objective
4+
5+
Provide a generalized design for an in-process and out-of-process fake gRPC
6+
server to intercept, store, and verify OpenTelemetry / Cloud Monitoring and
7+
Cloud Trace RPCs sent by any `google-cloud-cpp` client library (e.g., Bigtable,
8+
Storage, Spanner, PubSub) during integration and system testing.
9+
10+
______________________________________________________________________
11+
12+
## 2. Background & Rationale
13+
14+
Multiple `google-cloud-cpp` client libraries export low-level transport and
15+
client-side performance telemetry (metrics and traces) to Google Cloud
16+
Observability backends via OpenTelemetry. While unit testing with in-process
17+
mocks validates component logic, integration tests require verifying the
18+
end-to-end telemetry pipeline across gRPC transports.
19+
20+
By establishing a shared **Observability Collector Server** under internal CI
21+
tooling infrastructure (`ci/otel_collector`):
22+
23+
- **Separation of Customer vs Testing Code**: Keeps internal mock servers, test
24+
binaries, and CI helper code inside `ci/` and out of customer-facing
25+
header/library directories (`google/cloud/`).
26+
- **Reusability Across All Libraries**: A single utility under `ci/` serves
27+
Bigtable, Storage, Spanner, PubSub, and future SDK components exporting
28+
telemetry.
29+
- **Complete End-to-End Transport Coverage**: Verifies gRPC OpenTelemetry plugin
30+
registration, batching readers/processors, protobuf serialization, and network
31+
dispatch.
32+
- **Process & Container Decoupled Testing**: Can be embedded into integration
33+
test binaries or built as a standalone binary/container
34+
(`ci/otel_collector_main`) for multi-language system testing.
35+
- **Hermetic Test Execution**: Standardized verification RPCs enable resetting
36+
server state between test cases to prevent telemetry pollution across tests.
37+
- **Unified Observability Architecture**: Supports metric verification
38+
(`GetRecordedMetrics`) and is architected to seamlessly accommodate trace
39+
verification (`GetRecordedTraces`) in a single server framework.
40+
41+
______________________________________________________________________
42+
43+
## 3. System Architecture
44+
45+
The server hosts multiple gRPC service interfaces on a single listening port:
46+
47+
1. **`google.monitoring.v3.MetricService`**: Standard Cloud Monitoring API
48+
endpoint that receives `CreateTimeSeries` requests.
49+
1. **`google.devtools.cloudtrace.v2.TraceService`**: Standard Cloud Trace API
50+
endpoint receiving trace spans (future extension).
51+
1. **`google.cloud.opentelemetry.testing.ObservabilityVerificationService`**:
52+
Shared control-plane interface used by test suites to inspect and manage
53+
captured metric and trace protobufs.
54+
55+
### Architecture Overview
56+
57+
```mermaid
58+
graph TD
59+
subgraph Test_Process ["Test Process Boundary"]
60+
TestHarness["Integration Test Suite (Bigtable, Storage, Spanner, etc.)"]
61+
SDKClient["Google Cloud C++ Client Library (Exporting telemetry via OpenTelemetry)"]
62+
end
63+
64+
subgraph Server_Process ["Observability Collector Server Process (ci/otel_collector)"]
65+
MetricSvc["google.monitoring.v3.MetricService - CreateTimeSeries"]
66+
VerifySvc["google.cloud.opentelemetry.testing.ObservabilityVerificationService"]
67+
Storage["In-Memory Telemetry Request Store"]
68+
end
69+
70+
SDKClient -->|"1. Export telemetry over gRPC (GOOGLE_CLOUD_CPP_METRIC_SERVICE_ENDPOINT)"| MetricSvc
71+
MetricSvc -->|"2. Record requests"| Storage
72+
73+
TestHarness -->|"3. Clear state / Query recorded telemetry"| VerifySvc
74+
VerifySvc -->|"4. Read recorded protobufs"| Storage
75+
VerifySvc -->|"5. Return GetRecordedMetricsResponse"| TestHarness
76+
```
77+
78+
______________________________________________________________________
79+
80+
## 4. Service Contracts & Protobuf Specification
81+
82+
### 4.1 Imported Services
83+
84+
Standard Google Cloud Observability proto definitions:
85+
86+
- `google/monitoring/v3/metric_service.proto`
87+
- `google/devtools/cloudtrace/v2/tracing.proto` (optional/future)
88+
89+
### 4.2 Generic Control-Plane Proto Schema (`protos/google/cloud/opentelemetry/testing/observability_verification.proto`)
90+
91+
```protobuf
92+
syntax = "proto3";
93+
94+
package google.cloud.opentelemetry.testing;
95+
96+
import "google/monitoring/v3/metric_service.proto";
97+
import "google/protobuf/empty.proto";
98+
99+
// Response containing all captured CreateTimeSeriesRequest protobuf messages.
100+
message GetRecordedMetricsResponse {
101+
repeated google.monitoring.v3.CreateTimeSeriesRequest requests = 1;
102+
}
103+
104+
// Control-plane service for test harnesses to query and manage captured observability telemetry.
105+
service ObservabilityVerificationService {
106+
// Returns all CreateTimeSeriesRequests captured since the last reset.
107+
rpc GetRecordedMetrics(google.protobuf.Empty)
108+
returns (GetRecordedMetricsResponse);
109+
110+
// Resets the server metric state by clearing all recorded metric requests.
111+
rpc ClearRecordedMetrics(google.protobuf.Empty)
112+
returns (google.protobuf.Empty);
113+
}
114+
```
115+
116+
______________________________________________________________________
117+
118+
## 5. Sequence Diagram: Generic Integration Test Lifecycle
119+
120+
```mermaid
121+
sequenceDiagram
122+
autonumber
123+
participant Server as Observability Collector (ci/otel_collector)
124+
participant Client as Google Cloud Client SDK
125+
participant Test as Test Suite Harness
126+
127+
Test->>Server: ClearRecordedMetrics()
128+
Server-->>Test: OK (Empty)
129+
130+
Test->>Client: Perform SDK Operations (API calls)
131+
Note over Client: OpenTelemetry exporter gathers metrics and triggers periodic flush
132+
Client->>Server: CreateTimeSeries(CreateTimeSeriesRequest)
133+
Note over Server: Store request in thread-safe memory
134+
Server-->>Client: OK (Empty)
135+
136+
Note over Test: Wait for flush period (or force flush)
137+
Test->>Server: GetRecordedMetrics()
138+
Server-->>Test: GetRecordedMetricsResponse [requests...]
139+
Note over Test: Inspect and validate metric.type, MonitoredResource, project_id, and point values
140+
```
141+
142+
______________________________________________________________________
143+
144+
## 6. Implementation & Directory Layout
145+
146+
### 6.1 Repository Directory Layout
147+
148+
To keep internal test infrastructure strictly separate from customer-facing
149+
headers and libraries:
150+
151+
- **Protobuf Schema**:
152+
`protos/google/cloud/opentelemetry/testing/observability_verification.proto`
153+
- **C++ Implementation Header**: `ci/otel_collector/otel_collector.h`
154+
- **C++ Implementation Source**: `ci/otel_collector/otel_collector.cc`
155+
- **Standalone Server Binary Main**: `ci/otel_collector/otel_collector_main.cc`
156+
- **Build Targets (CMake / Bazel)**: Internal target defined under
157+
`ci/otel_collector/CMakeLists.txt` (not installed or exposed in public SDK
158+
distribution packages).
159+
160+
### 6.2 C++ Fake Server Implementation (`google::cloud::testing_util`)
161+
162+
```cpp
163+
#include "protos/google/cloud/opentelemetry/testing/observability_verification.grpc.pb.h"
164+
#include <google/monitoring/v3/metric_service.grpc.pb.h>
165+
#include <grpcpp/grpcpp.h>
166+
#include <mutex>
167+
#include <vector>
168+
169+
namespace google {
170+
namespace cloud {
171+
namespace testing_util {
172+
173+
class OtelCollectorServer final
174+
: public google::monitoring::v3::MetricService::Service,
175+
public google::cloud::opentelemetry::testing::ObservabilityVerificationService::Service {
176+
public:
177+
// --- MetricService Interface ---
178+
grpc::Status CreateTimeSeries(
179+
grpc::ServerContext* /*context*/,
180+
google::monitoring::v3::CreateTimeSeriesRequest const* request,
181+
google::protobuf::Empty* /*response*/) override {
182+
std::lock_guard<std::mutex> lock(mu_);
183+
metric_requests_.push_back(*request);
184+
return grpc::Status::OK;
185+
}
186+
187+
// --- ObservabilityVerificationService Interface ---
188+
grpc::Status GetRecordedMetrics(
189+
grpc::ServerContext* /*context*/,
190+
google::protobuf::Empty const* /*request*/,
191+
google::cloud::opentelemetry::testing::GetRecordedMetricsResponse* response) override {
192+
std::lock_guard<std::mutex> lock(mu_);
193+
for (auto const& req : metric_requests_) {
194+
*response->add_requests() = req;
195+
}
196+
return grpc::Status::OK;
197+
}
198+
199+
grpc::Status ClearRecordedMetrics(
200+
grpc::ServerContext* /*context*/,
201+
google::protobuf::Empty const* /*request*/,
202+
google::protobuf::Empty* /*response*/) override {
203+
std::lock_guard<std::mutex> lock(mu_);
204+
metric_requests_.clear();
205+
return grpc::Status::OK;
206+
}
207+
208+
private:
209+
std::mutex mu_;
210+
std::vector<google::monitoring::v3::CreateTimeSeriesRequest> metric_requests_;
211+
};
212+
213+
} // namespace testing_util
214+
} // namespace cloud
215+
} // namespace google
216+
```
217+
218+
______________________________________________________________________
219+
220+
## 7. Generic Client Verification Matrix
221+
222+
Integration tests across different libraries can reuse
223+
`ObservabilityVerificationService` to validate service-specific observability
224+
contracts:
225+
226+
| Library | Sample Target Metric Types | Key MonitoredResource Labels to Assert |
227+
| :----------- | :------------------------------------------------------------------------- | :------------------------------------------------------------- |
228+
| **Bigtable** | `grpc.client.attempt.duration`, `grpc.subchannel.open_connections` | `project_id`, `instance`, `app_profile`, `client_name`, `uuid` |
229+
| **Storage** | `storage.googleapis.com/client/throughput`, `grpc.client.attempt.duration` | `project_id`, `bucket_name`, `client_name` |
230+
| **Spanner** | `spanner.googleapis.com/client/session_pool/active_count` | `project_id`, `instance_id`, `database_id` |
231+
| **PubSub** | `pubsub.googleapis.com/client/published_message_count` | `project_id`, `topic_id` |
232+
233+
______________________________________________________________________
234+
235+
## 8. Extensibility & Future Expansion
236+
237+
1. **Trace Verification Integration**: The `ObservabilityVerificationService`
238+
control plane can be extended with
239+
`google.devtools.cloudtrace.v2.TraceService` implementation and corresponding
240+
methods:
241+
- `rpc GetRecordedTraces(google.protobuf.Empty) returns (GetRecordedTracesResponse)`
242+
- `rpc ClearRecordedTraces(google.protobuf.Empty) returns (google.protobuf.Empty)`
243+
1. **Error Injection API**: Add `SetCreateTimeSeriesStatus(grpc::Status status)`
244+
to simulate transient backend failures and test SDK fallback/retry behavior.
245+
1. **Reactive Verification**: Support streaming RPCs or server push
246+
notifications when expected telemetry points are recorded to accelerate
247+
integration test execution without arbitrary `sleep` timeouts.

0 commit comments

Comments
 (0)