From c3f0ca3fc70657244b660da8f3e5b8550debc1f4 Mon Sep 17 00:00:00 2001 From: Diego Date: Thu, 9 Apr 2026 10:22:01 -0400 Subject: [PATCH 01/15] test(o11y): confirm behavior of golden signals in compute --- java-compute/google-cloud-compute/pom.xml | 39 ++ .../integration/ITComputeGoldenSignals.java | 347 ++++++++++++++++++ 2 files changed, 386 insertions(+) create mode 100644 java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java diff --git a/java-compute/google-cloud-compute/pom.xml b/java-compute/google-cloud-compute/pom.xml index 646c48de3a77..0dc55727b19a 100644 --- a/java-compute/google-cloud-compute/pom.xml +++ b/java-compute/google-cloud-compute/pom.xml @@ -92,6 +92,45 @@ google-cloud-core test + + io.opentelemetry + opentelemetry-sdk + test + + + io.opentelemetry + opentelemetry-exporter-otlp + test + + + io.opentelemetry + opentelemetry-sdk-testing + test + + + + ch.qos.logback + logback-classic + 1.5.25 + test + + + ch.qos.logback + logback-core + 1.5.25 + test + + + com.google.cloud + google-cloud-trace + 2.89.0-SNAPSHOT + test + + + com.google.truth + truth + test + diff --git a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java new file mode 100644 index 000000000000..5938cbfb558f --- /dev/null +++ b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java @@ -0,0 +1,347 @@ +package com.google.cloud.compute.v1.integration; + +import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.fail; + +import com.google.api.gax.tracing.CompositeTracerFactory; +import com.google.api.gax.tracing.OpenTelemetryTracingFactory; +import com.google.api.gax.tracing.ObservabilityAttributes; +import com.google.api.gax.tracing.OpenTelemetryMetricsFactory; +import com.google.api.gax.tracing.LoggingTracerFactory; +import com.google.api.gax.tracing.ApiTracerFactory; +import com.google.cloud.compute.v1.InstancesClient; +import com.google.cloud.compute.v1.InstancesSettings; +import com.google.cloud.trace.v1.TraceServiceClient; +import com.google.cloud.trace.v1.TraceServiceSettings; +import com.google.devtools.cloudtrace.v1.GetTraceRequest; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.StatusCode; +import java.time.Duration; +import com.google.devtools.cloudtrace.v1.Trace; +import com.google.devtools.cloudtrace.v1.TraceSpan; +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.api.trace.Tracer; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.Scope; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import io.opentelemetry.sdk.metrics.SdkMeterProvider; +import io.opentelemetry.sdk.metrics.data.MetricData; +import java.util.Collection; +import com.google.auth.oauth2.GoogleCredentials; +import org.slf4j.LoggerFactory; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import java.util.ArrayList; +import java.util.HashMap; +import org.slf4j.event.KeyValuePair; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.sdk.resources.Resource; +import java.util.Arrays; +import java.util.List; +import java.util.UUID; +import java.util.Map; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Integration tests for Compute observability "golden signals". + * Validates that traces, metrics, and actionable error logs are correctly recorded and exported. + */ +public class ITComputeGoldenSignals extends BaseTest { + private static final Logger logger = (Logger) LoggerFactory.getLogger(ITComputeGoldenSignals.class); + private static final String TELEMETRY_ENDPOINT = "https://telemetry.googleapis.com"; + + private OpenTelemetrySdk openTelemetrySdk; + private TraceServiceClient traceClient; + private String traceId; + private String rootSpanName; + private Tracer tracer; + private CompositeTracerFactory compositeFactory; + private InMemoryMetricReader metricReader; + private TestAppender testAppender; + + @Before + public void setUp() throws Exception { + traceId = generateRandomHexString(32); + rootSpanName = "ComputeRootSpan-" + generateRandomHexString(8); + + GoogleCredentials credentials = GoogleCredentials.getApplicationDefault(); + credentials.refreshIfExpired(); + String token = credentials.getAccessToken().getTokenValue(); + + OtlpGrpcSpanExporter spanExporter = OtlpGrpcSpanExporter.builder() + .setEndpoint(TELEMETRY_ENDPOINT) + .addHeader("Authorization", "Bearer " + token) + .addHeader("x-goog-user-project", DEFAULT_PROJECT) + .build(); + + BatchSpanProcessor spanProcessor = BatchSpanProcessor.builder(spanExporter).build(); + + Resource resource = Resource.getDefault() + .merge(Resource.create(Attributes.of(AttributeKey.stringKey("gcp.project_id"), DEFAULT_PROJECT))); + + metricReader = InMemoryMetricReader.create(); + openTelemetrySdk = OpenTelemetrySdk.builder() + .setTracerProvider( + SdkTracerProvider.builder() + .addSpanProcessor(spanProcessor) + .setResource(resource) + .build()) + .setMeterProvider( + SdkMeterProvider.builder() + .registerMetricReader(metricReader) + .setResource(resource) + .build()) + .build(); + + tracer = openTelemetrySdk.getTracer("testing-compute"); + + // Configure TraceServiceClient with retry settings + TraceServiceSettings.Builder settingsBuilder = TraceServiceSettings.newBuilder(); + settingsBuilder.getTraceSettings() + .setRetrySettings( + RetrySettings.newBuilder() + .setTotalTimeoutDuration(Duration.ofSeconds(60)) + .setInitialRpcTimeoutDuration(Duration.ofSeconds(5)) + .setMaxRpcTimeoutDuration(Duration.ofSeconds(10)) + .build()) + .setRetryableCodes(StatusCode.Code.NOT_FOUND); + + settingsBuilder.getStubSettingsBuilder().setTracerFactory(com.google.api.gax.tracing.BaseApiTracerFactory.getInstance()); + + traceClient = TraceServiceClient.create(settingsBuilder.build()); + + // Combine tracers using CompositeTracerFactory + List factories = Arrays.asList( + new OpenTelemetryTracingFactory(openTelemetrySdk), + new OpenTelemetryMetricsFactory(openTelemetrySdk), + new LoggingTracerFactory() + ); + compositeFactory = new CompositeTracerFactory(factories); + + // Initialize and attach TestAppender + testAppender = new TestAppender(); + testAppender.start(); + Logger loggingTracerLogger = + (Logger) LoggerFactory.getLogger("com.google.api.gax.tracing.LoggingTracer"); + loggingTracerLogger.addAppender(testAppender); + loggingTracerLogger.setLevel(ch.qos.logback.classic.Level.DEBUG); + } + + @After + public void tearDown() throws Exception { + if (traceClient != null) { + traceClient.close(); + } + if (openTelemetrySdk != null) { + openTelemetrySdk.close(); + } + if (testAppender != null) { + ((Logger) LoggerFactory.getLogger("ROOT")) + .detachAppender(testAppender); + } + } + + /** + * Creates a root span with a specific trace ID to simulate an external parent context. + * This helps verify that the library correctly creates child spans that inherit the parent's trace ID. + * + * @param traceId The trace ID to use for the root span. + * @return The created root span. + */ + private Span createRootSpan(String traceId) { + SpanContext customSpanContext = SpanContext.create(traceId, generateRandomHexString(16), TraceFlags.getSampled(), TraceState.getDefault()); + return tracer.spanBuilder(rootSpanName) + .setParent(Context.root().with(Span.wrap(customSpanContext))) + .startSpan(); + } + + /** + * Tests that a successful compute operation generates traces that are correctly exported to Cloud Trace. + */ + @Test + public void testComputeOperationTracing() throws Exception { + String localTraceId = generateRandomHexString(32); + Span rootSpan = createRootSpan(localTraceId); + + try (Scope scope = rootSpan.makeCurrent()) { + InstancesSettings.Builder settingsBuilder = InstancesSettings.newBuilder(); + settingsBuilder.getStubSettingsBuilder().setTracerFactory(compositeFactory); + + try (InstancesClient client = InstancesClient.create(settingsBuilder.build())) { + logger.info("Listing instances in project: " + DEFAULT_PROJECT + " zone: " + DEFAULT_ZONE); + client.list(DEFAULT_PROJECT, DEFAULT_ZONE); + } + } finally { + rootSpan.end(); + } + + openTelemetrySdk.getSdkTracerProvider().forceFlush(); + fetchAndValidateTrace(localTraceId, false); + validateMetrics(); + validateLogging(false); + } + + /** + * Tests that a failed compute operation generates traces with error attributes. + */ + @Test + public void testComputeOperationTracing_Error() throws Exception { + String localTraceId = generateRandomHexString(32); + Span rootSpan = createRootSpan(localTraceId); + + try (Scope scope = rootSpan.makeCurrent()) { + InstancesSettings.Builder settingsBuilder = InstancesSettings.newBuilder(); + settingsBuilder.getStubSettingsBuilder().setTracerFactory(compositeFactory); + + try (InstancesClient client = InstancesClient.create(settingsBuilder.build())) { + logger.info("Triggering error by listing instances in invalid project..."); + client.list("invalid-project-" + UUID.randomUUID().toString(), DEFAULT_ZONE); + fail("Expected exception not thrown"); + } catch (Exception e) { + logger.info("Caught expected exception: " + e.getMessage()); + } + } finally { + rootSpan.end(); + } + + openTelemetrySdk.getSdkTracerProvider().forceFlush(); + fetchAndValidateTrace(localTraceId, true); + validateMetrics(); + validateLogging(true); + } + + private void fetchAndValidateTrace(String traceId, boolean expectError) throws Exception { + Trace trace = traceClient.getTrace(DEFAULT_PROJECT, traceId); + assertThat(trace).isNotNull(); + + for (TraceSpan span : trace.getSpansList()) { + logger.info("Verifying attributes for span: " + span.getName()); + + // Skip root span as it's manually created and doesn't have RPC attributes. + if (span.getName().contains("ComputeRootSpan")) { + continue; + } + + // Assert RPC span name pattern {method} {url template} + assertThat(span.getName()).isEqualTo("GET compute/v1/projects/{project=*}/zones/{zone=*}/instances"); + + // Compute uses HTTP/REST, so we check for rpc.system.name and other HTTP attributes + assertThat(span.getLabelsMap().get(ObservabilityAttributes.RPC_SYSTEM_NAME_ATTRIBUTE)).isEqualTo("http"); + assertThat(span.getLabelsMap().get(ObservabilityAttributes.URL_DOMAIN_ATTRIBUTE)).isEqualTo("compute.googleapis.com"); + assertThat(span.getLabelsMap().get(ObservabilityAttributes.HTTP_METHOD_ATTRIBUTE)).isEqualTo("GET"); + assertThat(span.getLabelsMap().get(ObservabilityAttributes.GCP_CLIENT_SERVICE_ATTRIBUTE)).isEqualTo("compute"); + assertThat(span.getLabelsMap().get(ObservabilityAttributes.REPO_ATTRIBUTE)).isEqualTo("googleapis/google-cloud-java"); + + if (expectError) { + assertThat(span.getLabelsMap().get(ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE)).isEqualTo("404"); + } else { + assertThat(span.getLabelsMap().get(ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE)).isEqualTo("200"); + } + + if (expectError) { + // Verify error attributes + assertThat(span.getLabelsMap()).containsKey(ObservabilityAttributes.ERROR_TYPE_ATTRIBUTE); + assertThat(span.getLabelsMap()).containsKey(ObservabilityAttributes.EXCEPTION_TYPE_ATTRIBUTE); + assertThat(span.getLabelsMap()).containsKey(ObservabilityAttributes.STATUS_MESSAGE_ATTRIBUTE); + } + } + } + + private void validateMetrics() { + Collection metrics = metricReader.collectAllMetrics(); + logger.info("Collected " + metrics.size() + " metrics"); + + // GoldenSignalsMetricsRecorder.CLIENT_REQUEST_DURATION_METRIC_NAME is package-private + String expectedMetricName = "gcp.client.request.duration"; + + MetricData durationMetric = + metrics.stream() + .filter(m -> m.getName().equals(expectedMetricName)) + .findFirst() + .orElseThrow(() -> new AssertionError("Duration metric not found: " + expectedMetricName)); + + logger.info("Found duration metric: " + durationMetric.getName()); + + // Assert that we have at least one point + assertThat(durationMetric.getHistogramData().getPoints()).isNotEmpty(); + + io.opentelemetry.api.common.Attributes attributes = durationMetric.getHistogramData().getPoints().iterator().next().getAttributes(); + assertThat(attributes.get(AttributeKey.stringKey(ObservabilityAttributes.RPC_SYSTEM_NAME_ATTRIBUTE))).isEqualTo("http"); + assertThat(attributes.get(AttributeKey.stringKey(ObservabilityAttributes.GCP_CLIENT_SERVICE_ATTRIBUTE))).isEqualTo("compute"); + assertThat(attributes.get(AttributeKey.stringKey(ObservabilityAttributes.URL_TEMPLATE_ATTRIBUTE))).isEqualTo("compute/v1/projects/{project=*}/zones/{zone=*}/instances"); + } + + private void validateLogging(boolean expectError) { + List computeEvents = new ArrayList<>(); + for (ILoggingEvent event : testAppender.events) { + if (event.getKeyValuePairs() == null) { + continue; + } + Map mdc = new HashMap<>(); + for (KeyValuePair kvp : event.getKeyValuePairs()) { + mdc.put(kvp.key, String.valueOf(kvp.value)); + } + if (!"compute".equals(mdc.get("gcp.client.service"))) { + continue; + } + computeEvents.add(event); + } + + if (expectError) { + assertThat(computeEvents).isNotEmpty(); + ILoggingEvent event = computeEvents.get(computeEvents.size() - 1); + if (event.getKeyValuePairs() == null) { + fail("Expected log event to have key value pairs"); + } + Map mdc = new HashMap<>(); + for (KeyValuePair kvp : event.getKeyValuePairs()) { + mdc.put(kvp.key, String.valueOf(kvp.value)); + } + + assertThat(mdc).containsEntry(ObservabilityAttributes.RPC_SYSTEM_NAME_ATTRIBUTE, "http"); + assertThat(mdc).containsEntry(ObservabilityAttributes.GCP_CLIENT_SERVICE_ATTRIBUTE, "compute"); + assertThat(mdc).containsEntry(ObservabilityAttributes.REPO_ATTRIBUTE, "googleapis/google-cloud-java"); + assertThat(mdc).containsEntry(ObservabilityAttributes.HTTP_METHOD_ATTRIBUTE, "GET"); + assertThat(mdc).containsKey("url.template"); + assertThat(mdc).containsKey(ObservabilityAttributes.EXCEPTION_MESSAGE_ATTRIBUTE); + } else { + if (!computeEvents.isEmpty()) { + logger.info("Captured " + computeEvents.size() + " unexpected compute log events:"); + for (ILoggingEvent event : computeEvents) { + logger.info("Event: " + event.getMessage() + ", Extracted: " + event.getKeyValuePairs()); + } + } + assertThat(computeEvents).isEmpty(); + } + } + + public static class TestAppender extends AppenderBase { + public List events = new ArrayList<>(); + + @Override + protected void append(ILoggingEvent eventObject) { + eventObject.getMDCPropertyMap(); + events.add(eventObject); + } + + public void clearEvents() { + events.clear(); + } + } + + private String generateRandomHexString(int length) { + return UUID.randomUUID().toString().replace("-", "").substring(0, length); + } +} From 4ec963c67ab31893541f332d160a2edf2dc555e8 Mon Sep 17 00:00:00 2001 From: Diego Date: Thu, 9 Apr 2026 10:28:33 -0400 Subject: [PATCH 02/15] chore: fomrat --- .../integration/ITComputeGoldenSignals.java | 240 ++++++++++-------- 1 file changed, 138 insertions(+), 102 deletions(-) diff --git a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java index 5938cbfb558f..5178ebcf237a 100644 --- a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java +++ b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java @@ -3,23 +3,26 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.fail; +import ch.qos.logback.classic.Logger; +import ch.qos.logback.classic.spi.ILoggingEvent; +import ch.qos.logback.core.AppenderBase; +import com.google.api.gax.retrying.RetrySettings; +import com.google.api.gax.rpc.StatusCode; +import com.google.api.gax.tracing.ApiTracerFactory; import com.google.api.gax.tracing.CompositeTracerFactory; -import com.google.api.gax.tracing.OpenTelemetryTracingFactory; +import com.google.api.gax.tracing.LoggingTracerFactory; import com.google.api.gax.tracing.ObservabilityAttributes; import com.google.api.gax.tracing.OpenTelemetryMetricsFactory; -import com.google.api.gax.tracing.LoggingTracerFactory; -import com.google.api.gax.tracing.ApiTracerFactory; +import com.google.api.gax.tracing.OpenTelemetryTracingFactory; +import com.google.auth.oauth2.GoogleCredentials; import com.google.cloud.compute.v1.InstancesClient; import com.google.cloud.compute.v1.InstancesSettings; import com.google.cloud.trace.v1.TraceServiceClient; import com.google.cloud.trace.v1.TraceServiceSettings; -import com.google.devtools.cloudtrace.v1.GetTraceRequest; -import com.google.api.gax.retrying.RetrySettings; -import com.google.api.gax.rpc.StatusCode; -import java.time.Duration; import com.google.devtools.cloudtrace.v1.Trace; import com.google.devtools.cloudtrace.v1.TraceSpan; -import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.TraceFlags; @@ -27,44 +30,39 @@ import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; -import io.opentelemetry.sdk.OpenTelemetrySdk; -import io.opentelemetry.sdk.trace.SdkTracerProvider; -import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; -import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.data.MetricData; -import java.util.Collection; -import com.google.auth.oauth2.GoogleCredentials; -import org.slf4j.LoggerFactory; -import ch.qos.logback.classic.Logger; -import ch.qos.logback.classic.spi.ILoggingEvent; -import ch.qos.logback.core.AppenderBase; -import java.util.ArrayList; -import java.util.HashMap; -import org.slf4j.event.KeyValuePair; -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; +import io.opentelemetry.sdk.trace.SdkTracerProvider; +import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; +import java.time.Duration; +import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; import java.util.List; -import java.util.UUID; import java.util.Map; +import java.util.UUID; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.slf4j.LoggerFactory; +import org.slf4j.event.KeyValuePair; /** - * Integration tests for Compute observability "golden signals". - * Validates that traces, metrics, and actionable error logs are correctly recorded and exported. + * Integration tests for Compute observability "golden signals". Validates that traces, metrics, and + * actionable error logs are correctly recorded and exported. */ public class ITComputeGoldenSignals extends BaseTest { - private static final Logger logger = (Logger) LoggerFactory.getLogger(ITComputeGoldenSignals.class); + private static final Logger logger = + (Logger) LoggerFactory.getLogger(ITComputeGoldenSignals.class); private static final String TELEMETRY_ENDPOINT = "https://telemetry.googleapis.com"; - + private OpenTelemetrySdk openTelemetrySdk; private TraceServiceClient traceClient; - private String traceId; private String rootSpanName; private Tracer tracer; private CompositeTracerFactory compositeFactory; @@ -73,43 +71,48 @@ public class ITComputeGoldenSignals extends BaseTest { @Before public void setUp() throws Exception { - traceId = generateRandomHexString(32); rootSpanName = "ComputeRootSpan-" + generateRandomHexString(8); - + GoogleCredentials credentials = GoogleCredentials.getApplicationDefault(); credentials.refreshIfExpired(); String token = credentials.getAccessToken().getTokenValue(); - OtlpGrpcSpanExporter spanExporter = OtlpGrpcSpanExporter.builder() - .setEndpoint(TELEMETRY_ENDPOINT) - .addHeader("Authorization", "Bearer " + token) - .addHeader("x-goog-user-project", DEFAULT_PROJECT) - .build(); + OtlpGrpcSpanExporter spanExporter = + OtlpGrpcSpanExporter.builder() + .setEndpoint(TELEMETRY_ENDPOINT) + .addHeader("Authorization", "Bearer " + token) + .addHeader("x-goog-user-project", DEFAULT_PROJECT) + .build(); BatchSpanProcessor spanProcessor = BatchSpanProcessor.builder(spanExporter).build(); - Resource resource = Resource.getDefault() - .merge(Resource.create(Attributes.of(AttributeKey.stringKey("gcp.project_id"), DEFAULT_PROJECT))); + Resource resource = + Resource.getDefault() + .merge( + Resource.create( + Attributes.of(AttributeKey.stringKey("gcp.project_id"), DEFAULT_PROJECT))); metricReader = InMemoryMetricReader.create(); - openTelemetrySdk = OpenTelemetrySdk.builder() - .setTracerProvider( - SdkTracerProvider.builder() - .addSpanProcessor(spanProcessor) - .setResource(resource) - .build()) - .setMeterProvider( - SdkMeterProvider.builder() - .registerMetricReader(metricReader) - .setResource(resource) - .build()) - .build(); + openTelemetrySdk = + OpenTelemetrySdk.builder() + .setTracerProvider( + SdkTracerProvider.builder() + .addSpanProcessor(spanProcessor) + .setResource(resource) + .build()) + .setMeterProvider( + SdkMeterProvider.builder() + .registerMetricReader(metricReader) + .setResource(resource) + .build()) + .build(); tracer = openTelemetrySdk.getTracer("testing-compute"); - + // Configure TraceServiceClient with retry settings TraceServiceSettings.Builder settingsBuilder = TraceServiceSettings.newBuilder(); - settingsBuilder.getTraceSettings() + settingsBuilder + .getTraceSettings() .setRetrySettings( RetrySettings.newBuilder() .setTotalTimeoutDuration(Duration.ofSeconds(60)) @@ -117,23 +120,25 @@ public void setUp() throws Exception { .setMaxRpcTimeoutDuration(Duration.ofSeconds(10)) .build()) .setRetryableCodes(StatusCode.Code.NOT_FOUND); - - settingsBuilder.getStubSettingsBuilder().setTracerFactory(com.google.api.gax.tracing.BaseApiTracerFactory.getInstance()); - + + settingsBuilder + .getStubSettingsBuilder() + .setTracerFactory(com.google.api.gax.tracing.BaseApiTracerFactory.getInstance()); + traceClient = TraceServiceClient.create(settingsBuilder.build()); // Combine tracers using CompositeTracerFactory - List factories = Arrays.asList( - new OpenTelemetryTracingFactory(openTelemetrySdk), - new OpenTelemetryMetricsFactory(openTelemetrySdk), - new LoggingTracerFactory() - ); + List factories = + Arrays.asList( + new OpenTelemetryTracingFactory(openTelemetrySdk), + new OpenTelemetryMetricsFactory(openTelemetrySdk), + new LoggingTracerFactory()); compositeFactory = new CompositeTracerFactory(factories); - + // Initialize and attach TestAppender testAppender = new TestAppender(); testAppender.start(); - Logger loggingTracerLogger = + Logger loggingTracerLogger = (Logger) LoggerFactory.getLogger("com.google.api.gax.tracing.LoggingTracer"); loggingTracerLogger.addAppender(testAppender); loggingTracerLogger.setLevel(ch.qos.logback.classic.Level.DEBUG); @@ -148,27 +153,30 @@ public void tearDown() throws Exception { openTelemetrySdk.close(); } if (testAppender != null) { - ((Logger) LoggerFactory.getLogger("ROOT")) - .detachAppender(testAppender); + ((Logger) LoggerFactory.getLogger("ROOT")).detachAppender(testAppender); } } /** - * Creates a root span with a specific trace ID to simulate an external parent context. - * This helps verify that the library correctly creates child spans that inherit the parent's trace ID. + * Creates a root span with a specific trace ID to simulate an external parent context. This helps + * verify that the library correctly creates child spans that inherit the parent's trace ID. * * @param traceId The trace ID to use for the root span. * @return The created root span. */ private Span createRootSpan(String traceId) { - SpanContext customSpanContext = SpanContext.create(traceId, generateRandomHexString(16), TraceFlags.getSampled(), TraceState.getDefault()); - return tracer.spanBuilder(rootSpanName) + SpanContext customSpanContext = + SpanContext.create( + traceId, generateRandomHexString(16), TraceFlags.getSampled(), TraceState.getDefault()); + return tracer + .spanBuilder(rootSpanName) .setParent(Context.root().with(Span.wrap(customSpanContext))) .startSpan(); } /** - * Tests that a successful compute operation generates traces that are correctly exported to Cloud Trace. + * Tests that a successful compute operation generates traces that are correctly exported to Cloud + * Trace. */ @Test public void testComputeOperationTracing() throws Exception { @@ -178,7 +186,7 @@ public void testComputeOperationTracing() throws Exception { try (Scope scope = rootSpan.makeCurrent()) { InstancesSettings.Builder settingsBuilder = InstancesSettings.newBuilder(); settingsBuilder.getStubSettingsBuilder().setTracerFactory(compositeFactory); - + try (InstancesClient client = InstancesClient.create(settingsBuilder.build())) { logger.info("Listing instances in project: " + DEFAULT_PROJECT + " zone: " + DEFAULT_ZONE); client.list(DEFAULT_PROJECT, DEFAULT_ZONE); @@ -193,9 +201,7 @@ public void testComputeOperationTracing() throws Exception { validateLogging(false); } - /** - * Tests that a failed compute operation generates traces with error attributes. - */ + /** Tests that a failed compute operation generates traces with error attributes. */ @Test public void testComputeOperationTracing_Error() throws Exception { String localTraceId = generateRandomHexString(32); @@ -204,7 +210,7 @@ public void testComputeOperationTracing_Error() throws Exception { try (Scope scope = rootSpan.makeCurrent()) { InstancesSettings.Builder settingsBuilder = InstancesSettings.newBuilder(); settingsBuilder.getStubSettingsBuilder().setTracerFactory(compositeFactory); - + try (InstancesClient client = InstancesClient.create(settingsBuilder.build())) { logger.info("Triggering error by listing instances in invalid project..."); client.list("invalid-project-" + UUID.randomUUID().toString(), DEFAULT_ZONE); @@ -225,36 +231,54 @@ public void testComputeOperationTracing_Error() throws Exception { private void fetchAndValidateTrace(String traceId, boolean expectError) throws Exception { Trace trace = traceClient.getTrace(DEFAULT_PROJECT, traceId); assertThat(trace).isNotNull(); - + for (TraceSpan span : trace.getSpansList()) { logger.info("Verifying attributes for span: " + span.getName()); - + // Skip root span as it's manually created and doesn't have RPC attributes. if (span.getName().contains("ComputeRootSpan")) { continue; } - + // Assert RPC span name pattern {method} {url template} - assertThat(span.getName()).isEqualTo("GET compute/v1/projects/{project=*}/zones/{zone=*}/instances"); - + assertThat(span.getName()) + .isEqualTo("GET compute/v1/projects/{project=*}/zones/{zone=*}/instances"); + // Compute uses HTTP/REST, so we check for rpc.system.name and other HTTP attributes - assertThat(span.getLabelsMap().get(ObservabilityAttributes.RPC_SYSTEM_NAME_ATTRIBUTE)).isEqualTo("http"); - assertThat(span.getLabelsMap().get(ObservabilityAttributes.URL_DOMAIN_ATTRIBUTE)).isEqualTo("compute.googleapis.com"); - assertThat(span.getLabelsMap().get(ObservabilityAttributes.HTTP_METHOD_ATTRIBUTE)).isEqualTo("GET"); - assertThat(span.getLabelsMap().get(ObservabilityAttributes.GCP_CLIENT_SERVICE_ATTRIBUTE)).isEqualTo("compute"); - assertThat(span.getLabelsMap().get(ObservabilityAttributes.REPO_ATTRIBUTE)).isEqualTo("googleapis/google-cloud-java"); + assertThat(span.getLabelsMap().get(ObservabilityAttributes.RPC_SYSTEM_NAME_ATTRIBUTE)) + .isEqualTo("http"); + assertThat(span.getLabelsMap().get(ObservabilityAttributes.URL_DOMAIN_ATTRIBUTE)) + .isEqualTo("compute.googleapis.com"); + assertThat(span.getLabelsMap().get(ObservabilityAttributes.HTTP_METHOD_ATTRIBUTE)) + .isEqualTo("GET"); + assertThat(span.getLabelsMap().get(ObservabilityAttributes.GCP_CLIENT_SERVICE_ATTRIBUTE)) + .isEqualTo("compute"); + assertThat(span.getLabelsMap().get(ObservabilityAttributes.REPO_ATTRIBUTE)) + .isEqualTo("googleapis/google-cloud-java"); + assertThat(span.getLabelsMap().get(ObservabilityAttributes.HTTP_URL_TEMPLATE_ATTRIBUTE)) + .isEqualTo("compute/v1/projects/{project=*}/zones/{zone=*}/instances"); + assertThat(span.getLabelsMap().get(ObservabilityAttributes.DESTINATION_RESOURCE_ID_ATTRIBUTE)) + .isEqualTo("//compute.googleapis.com/projects/" + DEFAULT_PROJECT + "/zones/us-central1-a"); + // These might fail if not supported in HTTP/REST yet + assertThat(span.getLabelsMap()).containsKey(ObservabilityAttributes.HTTP_URL_FULL_ATTRIBUTE); + assertThat(span.getLabelsMap()).containsKey(ObservabilityAttributes.HTTP_RESPONSE_BODY_SIZE); + if (expectError) { - assertThat(span.getLabelsMap().get(ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE)).isEqualTo("404"); + assertThat(span.getLabelsMap().get(ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE)) + .isEqualTo("404"); } else { - assertThat(span.getLabelsMap().get(ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE)).isEqualTo("200"); + assertThat(span.getLabelsMap().get(ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE)) + .isEqualTo("200"); } - + if (expectError) { // Verify error attributes assertThat(span.getLabelsMap()).containsKey(ObservabilityAttributes.ERROR_TYPE_ATTRIBUTE); - assertThat(span.getLabelsMap()).containsKey(ObservabilityAttributes.EXCEPTION_TYPE_ATTRIBUTE); - assertThat(span.getLabelsMap()).containsKey(ObservabilityAttributes.STATUS_MESSAGE_ATTRIBUTE); + assertThat(span.getLabelsMap()) + .containsKey(ObservabilityAttributes.EXCEPTION_TYPE_ATTRIBUTE); + assertThat(span.getLabelsMap()) + .containsKey(ObservabilityAttributes.STATUS_MESSAGE_ATTRIBUTE); } } } @@ -262,25 +286,35 @@ private void fetchAndValidateTrace(String traceId, boolean expectError) throws E private void validateMetrics() { Collection metrics = metricReader.collectAllMetrics(); logger.info("Collected " + metrics.size() + " metrics"); - + // GoldenSignalsMetricsRecorder.CLIENT_REQUEST_DURATION_METRIC_NAME is package-private String expectedMetricName = "gcp.client.request.duration"; - + MetricData durationMetric = metrics.stream() .filter(m -> m.getName().equals(expectedMetricName)) .findFirst() - .orElseThrow(() -> new AssertionError("Duration metric not found: " + expectedMetricName)); - + .orElseThrow( + () -> new AssertionError("Duration metric not found: " + expectedMetricName)); + logger.info("Found duration metric: " + durationMetric.getName()); - + // Assert that we have at least one point assertThat(durationMetric.getHistogramData().getPoints()).isNotEmpty(); - - io.opentelemetry.api.common.Attributes attributes = durationMetric.getHistogramData().getPoints().iterator().next().getAttributes(); - assertThat(attributes.get(AttributeKey.stringKey(ObservabilityAttributes.RPC_SYSTEM_NAME_ATTRIBUTE))).isEqualTo("http"); - assertThat(attributes.get(AttributeKey.stringKey(ObservabilityAttributes.GCP_CLIENT_SERVICE_ATTRIBUTE))).isEqualTo("compute"); - assertThat(attributes.get(AttributeKey.stringKey(ObservabilityAttributes.URL_TEMPLATE_ATTRIBUTE))).isEqualTo("compute/v1/projects/{project=*}/zones/{zone=*}/instances"); + + io.opentelemetry.api.common.Attributes attributes = + durationMetric.getHistogramData().getPoints().iterator().next().getAttributes(); + assertThat( + attributes.get( + AttributeKey.stringKey(ObservabilityAttributes.RPC_SYSTEM_NAME_ATTRIBUTE))) + .isEqualTo("http"); + assertThat( + attributes.get( + AttributeKey.stringKey(ObservabilityAttributes.GCP_CLIENT_SERVICE_ATTRIBUTE))) + .isEqualTo("compute"); + assertThat( + attributes.get(AttributeKey.stringKey(ObservabilityAttributes.URL_TEMPLATE_ATTRIBUTE))) + .isEqualTo("compute/v1/projects/{project=*}/zones/{zone=*}/instances"); } private void validateLogging(boolean expectError) { @@ -309,10 +343,12 @@ private void validateLogging(boolean expectError) { for (KeyValuePair kvp : event.getKeyValuePairs()) { mdc.put(kvp.key, String.valueOf(kvp.value)); } - + assertThat(mdc).containsEntry(ObservabilityAttributes.RPC_SYSTEM_NAME_ATTRIBUTE, "http"); - assertThat(mdc).containsEntry(ObservabilityAttributes.GCP_CLIENT_SERVICE_ATTRIBUTE, "compute"); - assertThat(mdc).containsEntry(ObservabilityAttributes.REPO_ATTRIBUTE, "googleapis/google-cloud-java"); + assertThat(mdc) + .containsEntry(ObservabilityAttributes.GCP_CLIENT_SERVICE_ATTRIBUTE, "compute"); + assertThat(mdc) + .containsEntry(ObservabilityAttributes.REPO_ATTRIBUTE, "googleapis/google-cloud-java"); assertThat(mdc).containsEntry(ObservabilityAttributes.HTTP_METHOD_ATTRIBUTE, "GET"); assertThat(mdc).containsKey("url.template"); assertThat(mdc).containsKey(ObservabilityAttributes.EXCEPTION_MESSAGE_ATTRIBUTE); From 9ad9b35e3534a5e91bc6786859ff25aa7d7dcedb Mon Sep 17 00:00:00 2001 From: Diego Date: Thu, 9 Apr 2026 12:21:43 -0400 Subject: [PATCH 03/15] test: include tests of resource name and url.full --- java-compute/google-cloud-compute/pom.xml | 14 ++++++++++++++ .../v1/integration/ITComputeGoldenSignals.java | 12 +++++++++--- .../google/api/gax/tracing/CompositeTracer.java | 7 +++++++ 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/java-compute/google-cloud-compute/pom.xml b/java-compute/google-cloud-compute/pom.xml index 0dc55727b19a..d41a118857ab 100644 --- a/java-compute/google-cloud-compute/pom.xml +++ b/java-compute/google-cloud-compute/pom.xml @@ -146,4 +146,18 @@ test + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + true + + + + + diff --git a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java index 5178ebcf237a..70ea190f98b4 100644 --- a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java +++ b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java @@ -257,12 +257,18 @@ private void fetchAndValidateTrace(String traceId, boolean expectError) throws E .isEqualTo("googleapis/google-cloud-java"); assertThat(span.getLabelsMap().get(ObservabilityAttributes.HTTP_URL_TEMPLATE_ATTRIBUTE)) .isEqualTo("compute/v1/projects/{project=*}/zones/{zone=*}/instances"); + String expectedDestinationResource; + if (expectError) { + expectedDestinationResource = "//compute.googleapis.com/projects/invalid-project-"; + } else { + expectedDestinationResource = + "//compute.googleapis.com/projects/" + DEFAULT_PROJECT + "/zones/us-central1-a"; + } assertThat(span.getLabelsMap().get(ObservabilityAttributes.DESTINATION_RESOURCE_ID_ATTRIBUTE)) - .isEqualTo("//compute.googleapis.com/projects/" + DEFAULT_PROJECT + "/zones/us-central1-a"); - + .startsWith(expectedDestinationResource); + // These might fail if not supported in HTTP/REST yet assertThat(span.getLabelsMap()).containsKey(ObservabilityAttributes.HTTP_URL_FULL_ATTRIBUTE); - assertThat(span.getLabelsMap()).containsKey(ObservabilityAttributes.HTTP_RESPONSE_BODY_SIZE); if (expectError) { assertThat(span.getLabelsMap().get(ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE)) diff --git a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeTracer.java b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeTracer.java index ca08c20c86b0..e4b28463d312 100644 --- a/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeTracer.java +++ b/sdk-platform-java/gax-java/gax/src/main/java/com/google/api/gax/tracing/CompositeTracer.java @@ -220,4 +220,11 @@ public void injectTraceContext(java.util.Map carrier) { child.injectTraceContext(carrier); } } + + @Override + public void requestUrlResolved(String requestUrl) { + for (ApiTracer child : children) { + child.requestUrlResolved(requestUrl); + } + } } From b86e268e7cd3df266335d2018b1dc34b68526663 Mon Sep 17 00:00:00 2001 From: Diego Date: Thu, 9 Apr 2026 12:48:22 -0400 Subject: [PATCH 04/15] test(o11y): fix TestAppender issues in ITComputeGoldenSignals --- .../cloud/compute/v1/integration/ITComputeGoldenSignals.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java index 70ea190f98b4..e3fa51686676 100644 --- a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java +++ b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java @@ -146,6 +146,9 @@ public void setUp() throws Exception { @After public void tearDown() throws Exception { + if (testAppender != null) { + testAppender.clearEvents(); + } if (traceClient != null) { traceClient.close(); } @@ -374,7 +377,6 @@ public static class TestAppender extends AppenderBase { @Override protected void append(ILoggingEvent eventObject) { - eventObject.getMDCPropertyMap(); events.add(eventObject); } From 03cd5d8713a2c1baa609bd5c41996ff5c072e669 Mon Sep 17 00:00:00 2001 From: Diego Date: Thu, 9 Apr 2026 14:17:40 -0400 Subject: [PATCH 05/15] deps: use current version of google-cloud-trace --- .vscode/settings.json | 30 +++++++++++++++++++++++ java-compute/google-cloud-compute/pom.xml | 2 +- sdk-platform-java/.vscode/settings.json | 23 +++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 .vscode/settings.json create mode 100644 sdk-platform-java/.vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000000..257d474d0dc7 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,30 @@ +{ + "workbench.colorCustomizations": { + "activityBar.activeBackground": "#6ad76a", + "activityBar.background": "#6ad76a", + "activityBar.foreground": "#15202b", + "activityBar.inactiveForeground": "#15202b99", + "activityBarBadge.background": "#6868d7", + "activityBarBadge.foreground": "#e7e7e7", + "commandCenter.border": "#15202b99", + "sash.hoverBorder": "#6ad76a", + "statusBar.background": "#42cc42", + "statusBar.foreground": "#15202b", + "statusBarItem.hoverBackground": "#2fac2f", + "statusBarItem.remoteBackground": "#42cc42", + "statusBarItem.remoteForeground": "#15202b", + "titleBar.activeBackground": "#42cc42", + "titleBar.activeForeground": "#15202b", + "titleBar.inactiveBackground": "#42cc4299", + "titleBar.inactiveForeground": "#15202b99" + }, + "peacock.color": "#42cc42", + "files.exclude": { + "**/.git": true, + "**/.svn": true, + "**/.hg": true, + "**/.DS_Store": true, + "**/Thumbs.db": true, + "java-*/**": true + } +} \ No newline at end of file diff --git a/java-compute/google-cloud-compute/pom.xml b/java-compute/google-cloud-compute/pom.xml index d41a118857ab..6f77b8db5fb5 100644 --- a/java-compute/google-cloud-compute/pom.xml +++ b/java-compute/google-cloud-compute/pom.xml @@ -123,7 +123,7 @@ com.google.cloud google-cloud-trace - 2.89.0-SNAPSHOT + 2.89.0 test diff --git a/sdk-platform-java/.vscode/settings.json b/sdk-platform-java/.vscode/settings.json new file mode 100644 index 000000000000..bc63bebe486c --- /dev/null +++ b/sdk-platform-java/.vscode/settings.json @@ -0,0 +1,23 @@ +{ + "workbench.colorCustomizations": { + "activityBar.activeBackground": "#b9ec4f", + "activityBar.background": "#b9ec4f", + "activityBar.foreground": "#15202b", + "activityBar.inactiveForeground": "#15202b99", + "activityBarBadge.background": "#19a3e6", + "activityBarBadge.foreground": "#15202b", + "commandCenter.border": "#15202b99", + "sash.hoverBorder": "#b9ec4f", + "statusBar.background": "#a7e721", + "statusBar.foreground": "#15202b", + "statusBarItem.hoverBackground": "#89c015", + "statusBarItem.remoteBackground": "#a7e721", + "statusBarItem.remoteForeground": "#15202b", + "titleBar.activeBackground": "#a7e721", + "titleBar.activeForeground": "#15202b", + "titleBar.inactiveBackground": "#a7e72199", + "titleBar.inactiveForeground": "#15202b99" + }, + "peacock.color": "#a7e721", + "java.compile.nullAnalysis.mode": "disabled" +} \ No newline at end of file From bc66f32bb4a567e97594ded834f173fad1d8f63f Mon Sep 17 00:00:00 2001 From: Diego Date: Thu, 9 Apr 2026 14:28:40 -0400 Subject: [PATCH 06/15] chore: format --- .../integration/ITComputeGoldenSignals.java | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java index e3fa51686676..9192ba00d6c9 100644 --- a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java +++ b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java @@ -1,3 +1,33 @@ +/* + * Copyright 2026 Google LLC + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following disclaimer + * in the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Google LLC nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + package com.google.cloud.compute.v1.integration; import static com.google.common.truth.Truth.assertThat; From b56b010c88847992d7ae14c4bae972c0687266f6 Mon Sep 17 00:00:00 2001 From: Diego Date: Thu, 9 Apr 2026 14:30:47 -0400 Subject: [PATCH 07/15] chore: remove unused files --- .vscode/settings.json | 30 ------------------------- sdk-platform-java/.vscode/settings.json | 23 ------------------- 2 files changed, 53 deletions(-) delete mode 100644 .vscode/settings.json delete mode 100644 sdk-platform-java/.vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 257d474d0dc7..000000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "workbench.colorCustomizations": { - "activityBar.activeBackground": "#6ad76a", - "activityBar.background": "#6ad76a", - "activityBar.foreground": "#15202b", - "activityBar.inactiveForeground": "#15202b99", - "activityBarBadge.background": "#6868d7", - "activityBarBadge.foreground": "#e7e7e7", - "commandCenter.border": "#15202b99", - "sash.hoverBorder": "#6ad76a", - "statusBar.background": "#42cc42", - "statusBar.foreground": "#15202b", - "statusBarItem.hoverBackground": "#2fac2f", - "statusBarItem.remoteBackground": "#42cc42", - "statusBarItem.remoteForeground": "#15202b", - "titleBar.activeBackground": "#42cc42", - "titleBar.activeForeground": "#15202b", - "titleBar.inactiveBackground": "#42cc4299", - "titleBar.inactiveForeground": "#15202b99" - }, - "peacock.color": "#42cc42", - "files.exclude": { - "**/.git": true, - "**/.svn": true, - "**/.hg": true, - "**/.DS_Store": true, - "**/Thumbs.db": true, - "java-*/**": true - } -} \ No newline at end of file diff --git a/sdk-platform-java/.vscode/settings.json b/sdk-platform-java/.vscode/settings.json deleted file mode 100644 index bc63bebe486c..000000000000 --- a/sdk-platform-java/.vscode/settings.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "workbench.colorCustomizations": { - "activityBar.activeBackground": "#b9ec4f", - "activityBar.background": "#b9ec4f", - "activityBar.foreground": "#15202b", - "activityBar.inactiveForeground": "#15202b99", - "activityBarBadge.background": "#19a3e6", - "activityBarBadge.foreground": "#15202b", - "commandCenter.border": "#15202b99", - "sash.hoverBorder": "#b9ec4f", - "statusBar.background": "#a7e721", - "statusBar.foreground": "#15202b", - "statusBarItem.hoverBackground": "#89c015", - "statusBarItem.remoteBackground": "#a7e721", - "statusBarItem.remoteForeground": "#15202b", - "titleBar.activeBackground": "#a7e721", - "titleBar.activeForeground": "#15202b", - "titleBar.inactiveBackground": "#a7e72199", - "titleBar.inactiveForeground": "#15202b99" - }, - "peacock.color": "#a7e721", - "java.compile.nullAnalysis.mode": "disabled" -} \ No newline at end of file From e810db92f0106770b41b899031f0656073080d87 Mon Sep 17 00:00:00 2001 From: Diego Date: Thu, 9 Apr 2026 14:41:37 -0400 Subject: [PATCH 08/15] fix: use correct license header --- .../integration/ITComputeGoldenSignals.java | 32 ++++++------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java index 9192ba00d6c9..00e84e5b1ae6 100644 --- a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java +++ b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java @@ -1,31 +1,17 @@ /* * Copyright 2026 Google LLC * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following disclaimer - * in the documentation and/or other materials provided with the - * distribution. - * * Neither the name of Google LLC nor the names of its - * contributors may be used to endorse or promote products derived from - * this software without specific prior written permission. + * https://www.apache.org/licenses/LICENSE-2.0 * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ package com.google.cloud.compute.v1.integration; From 522b6041799ade7b771d2b5ad1bd640d0cb5222c Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 15 Apr 2026 12:29:27 -0400 Subject: [PATCH 09/15] test(compute): refactor golden signals integration test for Prometheus Addressed multiple PR comments for naming, constants, and method extraction. --- .../integration/ITComputeGoldenSignals.java | 232 ++++++++++++++++-- 1 file changed, 208 insertions(+), 24 deletions(-) diff --git a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java index 00e84e5b1ae6..b50125d1b461 100644 --- a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java +++ b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java @@ -26,17 +26,23 @@ import com.google.api.gax.rpc.StatusCode; import com.google.api.gax.tracing.ApiTracerFactory; import com.google.api.gax.tracing.CompositeTracerFactory; -import com.google.api.gax.tracing.LoggingTracerFactory; import com.google.api.gax.tracing.ObservabilityAttributes; import com.google.api.gax.tracing.OpenTelemetryMetricsFactory; import com.google.api.gax.tracing.OpenTelemetryTracingFactory; import com.google.auth.oauth2.GoogleCredentials; import com.google.cloud.compute.v1.InstancesClient; import com.google.cloud.compute.v1.InstancesSettings; +import com.google.cloud.monitoring.v3.MetricServiceClient; import com.google.cloud.trace.v1.TraceServiceClient; import com.google.cloud.trace.v1.TraceServiceSettings; +import com.google.common.base.Stopwatch; import com.google.devtools.cloudtrace.v1.Trace; import com.google.devtools.cloudtrace.v1.TraceSpan; +import com.google.monitoring.v3.ListTimeSeriesRequest; +import com.google.monitoring.v3.ListTimeSeriesResponse; +import com.google.monitoring.v3.ProjectName; +import com.google.monitoring.v3.TimeInterval; +import com.google.protobuf.util.Timestamps; import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.api.trace.Span; @@ -46,15 +52,18 @@ import io.opentelemetry.api.trace.Tracer; import io.opentelemetry.context.Context; import io.opentelemetry.context.Scope; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.sdk.OpenTelemetrySdk; import io.opentelemetry.sdk.metrics.SdkMeterProvider; import io.opentelemetry.sdk.metrics.data.MetricData; +import io.opentelemetry.sdk.metrics.export.PeriodicMetricReader; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.testing.exporter.InMemoryMetricReader; import io.opentelemetry.sdk.trace.SdkTracerProvider; import io.opentelemetry.sdk.trace.export.BatchSpanProcessor; import java.time.Duration; +import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -76,18 +85,32 @@ public class ITComputeGoldenSignals extends BaseTest { private static final Logger logger = (Logger) LoggerFactory.getLogger(ITComputeGoldenSignals.class); private static final String TELEMETRY_ENDPOINT = "https://telemetry.googleapis.com"; + private static final String CLIENT_REQUEST_DURATION_METRIC = "gcp.client.request.duration"; + private static final String CLOUD_AVAILABILITY_ZONE = "cloud.availability_zone"; + private static final String CLOUD_REGION = "cloud.region"; + private static final String TEST_AVAILABILITY_ZONE = "us-central1-a"; + private static final String TEST_REGION = "us-central1"; + private static final String INSTANCE_NAME = "compute-test-instance"; + private static final String SERVICE_NAME_PREFIX = "compute-golden-signals-test-"; + // Test run ID used to isolate metrics in parallel CI runs. + private static final String METRIC_FILTER_TEMPLATE = + "metric.type=\"prometheus.googleapis.com/%s/histogram\" AND resource.type=\"prometheus_target\" AND resource.labels.job=\"" + + SERVICE_NAME_PREFIX + + "%s\""; private OpenTelemetrySdk openTelemetrySdk; private TraceServiceClient traceClient; private String rootSpanName; + private String testRunId; private Tracer tracer; private CompositeTracerFactory compositeFactory; - private InMemoryMetricReader metricReader; + private InMemoryMetricReader inMemoryReader; private TestAppender testAppender; @Before public void setUp() throws Exception { rootSpanName = "ComputeRootSpan-" + generateRandomHexString(8); + testRunId = generateRandomHexString(8); GoogleCredentials credentials = GoogleCredentials.getApplicationDefault(); credentials.refreshIfExpired(); @@ -106,9 +129,26 @@ public void setUp() throws Exception { Resource.getDefault() .merge( Resource.create( - Attributes.of(AttributeKey.stringKey("gcp.project_id"), DEFAULT_PROJECT))); + Attributes.builder() + .put("gcp.project_id", DEFAULT_PROJECT) + .put("test_run_id", testRunId) + .put("instance", INSTANCE_NAME) + .put("service.name", SERVICE_NAME_PREFIX + testRunId) + .put(CLOUD_AVAILABILITY_ZONE, TEST_AVAILABILITY_ZONE) + .put(CLOUD_REGION, TEST_REGION) + .build())); + + inMemoryReader = InMemoryMetricReader.create(); + // Note: The standard OTel OTLP exporter does not support passing GoogleCredentials directly. + OtlpGrpcMetricExporter metricExporter = + OtlpGrpcMetricExporter.builder() + .setEndpoint(TELEMETRY_ENDPOINT) + .addHeader("Authorization", "Bearer " + token) + .addHeader("x-goog-user-project", DEFAULT_PROJECT) + .build(); + PeriodicMetricReader exportedMetricsReader = + PeriodicMetricReader.builder(metricExporter).build(); - metricReader = InMemoryMetricReader.create(); openTelemetrySdk = OpenTelemetrySdk.builder() .setTracerProvider( @@ -118,7 +158,8 @@ public void setUp() throws Exception { .build()) .setMeterProvider( SdkMeterProvider.builder() - .registerMetricReader(metricReader) + .registerMetricReader(inMemoryReader) + .registerMetricReader(exportedMetricsReader) .setResource(resource) .build()) .build(); @@ -147,8 +188,7 @@ public void setUp() throws Exception { List factories = Arrays.asList( new OpenTelemetryTracingFactory(openTelemetrySdk), - new OpenTelemetryMetricsFactory(openTelemetrySdk), - new LoggingTracerFactory()); + new OpenTelemetryMetricsFactory(openTelemetrySdk)); compositeFactory = new CompositeTracerFactory(factories); // Initialize and attach TestAppender @@ -215,8 +255,9 @@ public void testComputeOperationTracing() throws Exception { } openTelemetrySdk.getSdkTracerProvider().forceFlush(); + openTelemetrySdk.getSdkMeterProvider().forceFlush(); fetchAndValidateTrace(localTraceId, false); - validateMetrics(); + verifyMetrics(false); validateLogging(false); } @@ -232,7 +273,7 @@ public void testComputeOperationTracing_Error() throws Exception { try (InstancesClient client = InstancesClient.create(settingsBuilder.build())) { logger.info("Triggering error by listing instances in invalid project..."); - client.list("invalid-project-" + UUID.randomUUID().toString(), DEFAULT_ZONE); + client.list("invalid-project-id", DEFAULT_ZONE); fail("Expected exception not thrown"); } catch (Exception e) { logger.info("Caught expected exception: " + e.getMessage()); @@ -242,8 +283,10 @@ public void testComputeOperationTracing_Error() throws Exception { } openTelemetrySdk.getSdkTracerProvider().forceFlush(); + openTelemetrySdk.getSdkMeterProvider().forceFlush(); + fetchAndValidateTrace(localTraceId, true); - validateMetrics(); + verifyMetrics(true); validateLogging(true); } @@ -278,7 +321,7 @@ private void fetchAndValidateTrace(String traceId, boolean expectError) throws E .isEqualTo("compute/v1/projects/{project=*}/zones/{zone=*}/instances"); String expectedDestinationResource; if (expectError) { - expectedDestinationResource = "//compute.googleapis.com/projects/invalid-project-"; + expectedDestinationResource = "//compute.googleapis.com/projects/invalid-project-id"; } else { expectedDestinationResource = "//compute.googleapis.com/projects/" + DEFAULT_PROJECT + "/zones/us-central1-a"; @@ -286,30 +329,38 @@ private void fetchAndValidateTrace(String traceId, boolean expectError) throws E assertThat(span.getLabelsMap().get(ObservabilityAttributes.DESTINATION_RESOURCE_ID_ATTRIBUTE)) .startsWith(expectedDestinationResource); - // These might fail if not supported in HTTP/REST yet - assertThat(span.getLabelsMap()).containsKey(ObservabilityAttributes.HTTP_URL_FULL_ATTRIBUTE); + String expectedUrl; + if (expectError) { + expectedUrl = + "https://compute.googleapis.com:443/compute/v1/projects/invalid-project-id/zones/us-central1-a/instances"; + } else { + expectedUrl = + "https://compute.googleapis.com:443/compute/v1/projects/" + + DEFAULT_PROJECT + + "/zones/us-central1-a/instances"; + } + assertThat(span.getLabelsMap().get(ObservabilityAttributes.HTTP_URL_FULL_ATTRIBUTE)) + .isEqualTo(expectedUrl); if (expectError) { assertThat(span.getLabelsMap().get(ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE)) .isEqualTo("404"); + // Verify error attributes + assertThat(span.getLabelsMap().get(ObservabilityAttributes.ERROR_TYPE_ATTRIBUTE)) + .isEqualTo("404"); + assertThat(span.getLabelsMap().get(ObservabilityAttributes.EXCEPTION_TYPE_ATTRIBUTE)) + .contains("NotFoundException"); + assertThat(span.getLabelsMap().get(ObservabilityAttributes.STATUS_MESSAGE_ATTRIBUTE)) + .contains("was not found"); } else { assertThat(span.getLabelsMap().get(ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE)) .isEqualTo("200"); } - - if (expectError) { - // Verify error attributes - assertThat(span.getLabelsMap()).containsKey(ObservabilityAttributes.ERROR_TYPE_ATTRIBUTE); - assertThat(span.getLabelsMap()) - .containsKey(ObservabilityAttributes.EXCEPTION_TYPE_ATTRIBUTE); - assertThat(span.getLabelsMap()) - .containsKey(ObservabilityAttributes.STATUS_MESSAGE_ATTRIBUTE); - } } } - private void validateMetrics() { - Collection metrics = metricReader.collectAllMetrics(); + private void validateMetrics(boolean expectError) { + Collection metrics = inMemoryReader.collectAllMetrics(); logger.info("Collected " + metrics.size() + " metrics"); // GoldenSignalsMetricsRecorder.CLIENT_REQUEST_DURATION_METRIC_NAME is package-private @@ -340,6 +391,139 @@ private void validateMetrics() { assertThat( attributes.get(AttributeKey.stringKey(ObservabilityAttributes.URL_TEMPLATE_ATTRIBUTE))) .isEqualTo("compute/v1/projects/{project=*}/zones/{zone=*}/instances"); + + // New assertions + String expectedProject = expectError ? "invalid-project-id" : DEFAULT_PROJECT; + String expectedStatus = expectError ? "404" : "200"; + + assertThat( + attributes.get( + AttributeKey.longKey(ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE))) + .isEqualTo(Long.valueOf(expectedStatus)); + + assertThat(attributes.get(AttributeKey.stringKey(ObservabilityAttributes.REPO_ATTRIBUTE))) + .isEqualTo("googleapis/google-cloud-java"); + } + + private void validateMetricsInCloudMonitoring(boolean expectError) throws Exception { + MetricServiceClient metricClient = MetricServiceClient.create(); + try { + // Use filter for the specific metric and project, isolated by testRunId + String filter = + String.format(METRIC_FILTER_TEMPLATE, CLIENT_REQUEST_DURATION_METRIC, testRunId); + + ProjectName name = ProjectName.of(DEFAULT_PROJECT); + + Instant start = Instant.now().minus(Duration.ofMinutes(5)); + Instant end = Instant.now().plus(Duration.ofMinutes(5)); + + TimeInterval interval = + TimeInterval.newBuilder() + .setStartTime(Timestamps.fromMillis(start.toEpochMilli())) + .setEndTime(Timestamps.fromMillis(end.toEpochMilli())) + .build(); + + ListTimeSeriesRequest request = + ListTimeSeriesRequest.newBuilder() + .setName(name.toString()) + .setFilter(filter) + .setInterval(interval) + .setView(ListTimeSeriesRequest.TimeSeriesView.FULL) + .build(); + + RetrySettings retrySettings = + RetrySettings.newBuilder() + .setInitialRetryDelayDuration(Duration.ofSeconds(10)) + .setRetryDelayMultiplier(2.0) + .setMaxRetryDelayDuration(Duration.ofSeconds(60)) + .setTotalTimeoutDuration(Duration.ofMinutes(10)) + .build(); + + com.google.monitoring.v3.TimeSeries targetTs = + pollForTimeSeries(metricClient, request, retrySettings); + + assertThat(targetTs).isNotNull(); + logger.info("Found target metrics in Cloud Monitoring!"); + + com.google.monitoring.v3.TimeSeries ts = targetTs; + Map metricLabels = ts.getMetric().getLabelsMap(); + logger.info("Metric labels from Cloud Monitoring: " + metricLabels); + + String expectedStatus = expectError ? "404" : "200"; + + // Verify attributes (Prometheus metrics retain dots in keys) + assertThat(metricLabels.get(ObservabilityAttributes.GCP_CLIENT_SERVICE_ATTRIBUTE)) + .isEqualTo("compute"); + assertThat(metricLabels.get(ObservabilityAttributes.RPC_SYSTEM_NAME_ATTRIBUTE)) + .isEqualTo("http"); + assertThat(metricLabels.get(ObservabilityAttributes.URL_DOMAIN_ATTRIBUTE)) + .isEqualTo("compute.googleapis.com"); + assertThat(metricLabels.get(ObservabilityAttributes.SERVER_ADDRESS_ATTRIBUTE)) + .isEqualTo("compute.googleapis.com"); + assertThat(metricLabels.get(ObservabilityAttributes.REPO_ATTRIBUTE)) + .isEqualTo("googleapis/google-cloud-java"); + + assertThat(metricLabels.get(ObservabilityAttributes.HTTP_RESPONSE_STATUS_ATTRIBUTE)) + .isEqualTo(expectedStatus); + + if (metricLabels.containsKey("url_template")) { + assertThat(metricLabels.get("url_template")).startsWith("/compute/v1/projects/"); + } + + } finally { + metricClient.close(); + } + } + + private com.google.monitoring.v3.TimeSeries pollForTimeSeries( + MetricServiceClient metricClient, ListTimeSeriesRequest request, RetrySettings retrySettings) + throws InterruptedException { + Stopwatch metricsPollingStopwatch = Stopwatch.createStarted(); + Duration currentDelay = retrySettings.getInitialRetryDelayDuration(); + com.google.monitoring.v3.TimeSeries targetTs = null; + + while (metricsPollingStopwatch.elapsed().compareTo(retrySettings.getTotalTimeoutDuration()) + < 0) { + try { + ListTimeSeriesResponse response = metricClient.listTimeSeriesCallable().call(request); + for (com.google.monitoring.v3.TimeSeries ts : response.getTimeSeriesList()) { + Map resourceLabels = ts.getResource().getLabelsMap(); + if ((SERVICE_NAME_PREFIX + testRunId).equals(resourceLabels.get("job"))) { + targetTs = ts; + break; + } + } + if (targetTs != null) { + break; + } + } catch (com.google.api.gax.rpc.NotFoundException e) { + logger.info("Metric not found yet (NotFoundException): " + e.getMessage()); + } catch (io.grpc.StatusRuntimeException e) { + if (e.getStatus().getCode() == io.grpc.Status.Code.NOT_FOUND) { + logger.info("Metric not found yet (gRPC NOT_FOUND): " + e.getMessage()); + } else { + throw e; + } + } + logger.info( + "Waiting for metrics in Cloud Monitoring, retrying in " + + currentDelay.getSeconds() + + " seconds..."); + Thread.sleep(currentDelay.toMillis()); + + currentDelay = + Duration.ofMillis( + (long) (currentDelay.toMillis() * retrySettings.getRetryDelayMultiplier())); + if (currentDelay.compareTo(retrySettings.getMaxRetryDelayDuration()) > 0) { + currentDelay = retrySettings.getMaxRetryDelayDuration(); + } + } + return targetTs; + } + + private void verifyMetrics(boolean expectError) throws Exception { + validateMetrics(expectError); + validateMetricsInCloudMonitoring(expectError); } private void validateLogging(boolean expectError) { From d85fdf646ddd4bb240a292d8b0546ee2e07b8d97 Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 15 Apr 2026 12:57:18 -0400 Subject: [PATCH 10/15] deps(compute): add cloud monitoring dependency --- java-compute/google-cloud-compute/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/java-compute/google-cloud-compute/pom.xml b/java-compute/google-cloud-compute/pom.xml index 6f77b8db5fb5..671d8f41444f 100644 --- a/java-compute/google-cloud-compute/pom.xml +++ b/java-compute/google-cloud-compute/pom.xml @@ -92,6 +92,12 @@ google-cloud-core test + + com.google.cloud + google-cloud-monitoring + 3.80.0 + test + io.opentelemetry opentelemetry-sdk From 0e807a9924233821fe4928ee82940ae8e90afe3f Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 15 Apr 2026 13:02:01 -0400 Subject: [PATCH 11/15] test(compute): document rationale for credential refresh in observability tests --- .../cloud/compute/v1/integration/ITComputeGoldenSignals.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java index b50125d1b461..87df3a020fff 100644 --- a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java +++ b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java @@ -113,6 +113,9 @@ public void setUp() throws Exception { testRunId = generateRandomHexString(8); GoogleCredentials credentials = GoogleCredentials.getApplicationDefault(); + // We explicitly refresh the credentials here to guarantee a valid token is extracted. + // The standard OTLP exporter does not automatically manage OAuth token lifecycles + // when they are passed as static header strings. credentials.refreshIfExpired(); String token = credentials.getAccessToken().getTokenValue(); From f0abae656f0f7cc82e39aa61a4d2b511aac0eaf5 Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 15 Apr 2026 13:07:08 -0400 Subject: [PATCH 12/15] chore: format --- .../cloud/compute/v1/integration/ITComputeGoldenSignals.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java index 87df3a020fff..6970d82c31be 100644 --- a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java +++ b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java @@ -114,7 +114,7 @@ public void setUp() throws Exception { GoogleCredentials credentials = GoogleCredentials.getApplicationDefault(); // We explicitly refresh the credentials here to guarantee a valid token is extracted. - // The standard OTLP exporter does not automatically manage OAuth token lifecycles + // The standard OTLP exporter does not automatically manage OAuth token lifecycles // when they are passed as static header strings. credentials.refreshIfExpired(); String token = credentials.getAccessToken().getTokenValue(); From 4cc769c6128f65d018e7d4107cf1489052d588d4 Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 15 Apr 2026 13:41:01 -0400 Subject: [PATCH 13/15] deps: update local trace dependecy to current --- java-compute/google-cloud-compute/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/java-compute/google-cloud-compute/pom.xml b/java-compute/google-cloud-compute/pom.xml index 9c4cbbe22437..f7a10513e005 100644 --- a/java-compute/google-cloud-compute/pom.xml +++ b/java-compute/google-cloud-compute/pom.xml @@ -129,7 +129,7 @@ com.google.cloud google-cloud-trace - 2.89.0 + 2.91.0 test From b8f007b278174b5b2e00c5e4e9fb850a59e08a15 Mon Sep 17 00:00:00 2001 From: cloud-java-bot Date: Wed, 15 Apr 2026 17:45:33 +0000 Subject: [PATCH 14/15] chore: generate libraries at Wed Apr 15 17:44:00 UTC 2026 --- gapic-libraries-bom/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gapic-libraries-bom/pom.xml b/gapic-libraries-bom/pom.xml index 9f1174f95b72..9cbef9e2cdee 100644 --- a/gapic-libraries-bom/pom.xml +++ b/gapic-libraries-bom/pom.xml @@ -4,7 +4,7 @@ com.google.cloud gapic-libraries-bom pom - 1.85.1 + 1.85.0 Google Cloud Java BOM BOM for the libraries in google-cloud-java repository. Users should not From f2b6e9fe340f3db9ca4e7271fd2041b0a6f259be Mon Sep 17 00:00:00 2001 From: Diego Date: Wed, 15 Apr 2026 16:14:26 -0400 Subject: [PATCH 15/15] fix: explicit auth scope for CI runs --- .../cloud/compute/v1/integration/ITComputeGoldenSignals.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java index 6970d82c31be..e4d556d70d8f 100644 --- a/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java +++ b/java-compute/google-cloud-compute/src/test/java/com/google/cloud/compute/v1/integration/ITComputeGoldenSignals.java @@ -112,7 +112,9 @@ public void setUp() throws Exception { rootSpanName = "ComputeRootSpan-" + generateRandomHexString(8); testRunId = generateRandomHexString(8); - GoogleCredentials credentials = GoogleCredentials.getApplicationDefault(); + GoogleCredentials credentials = + GoogleCredentials.getApplicationDefault() + .createScoped(Arrays.asList("https://www.googleapis.com/auth/cloud-platform")); // We explicitly refresh the credentials here to guarantee a valid token is extracted. // The standard OTLP exporter does not automatically manage OAuth token lifecycles // when they are passed as static header strings.