From be8e25dcaa4b26bdbdf8abb660afbc2038425bfd Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Fri, 16 May 2025 16:26:40 +0000 Subject: [PATCH 01/11] Add auth support for OTLP metrics export --- ...thAutoConfigurationCustomizerProvider.java | 112 +++++++++++++++++- ...toConfigurationCustomizerProviderTest.java | 2 + 2 files changed, 108 insertions(+), 6 deletions(-) diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java index 053aeef7d..2dac37252 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java @@ -10,13 +10,18 @@ import io.opentelemetry.api.common.AttributeKey; import io.opentelemetry.api.common.Attributes; import io.opentelemetry.contrib.gcp.auth.GoogleAuthException.Reason; +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer; import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.export.SpanExporter; import java.io.IOException; @@ -24,7 +29,10 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import java.util.stream.Collectors; +import javax.annotation.Nonnull; /** * An AutoConfigurationCustomizerProvider for Google Cloud Platform (GCP) OpenTelemetry (OTLP) @@ -46,13 +54,31 @@ public class GcpAuthAutoConfigurationCustomizerProvider static final String QUOTA_USER_PROJECT_HEADER = "x-goog-user-project"; static final String GCP_USER_PROJECT_ID_KEY = "gcp.project_id"; + private static final String OTEL_EXPORTER_OTLP_ENDPOINT = "otel.exporter.otlp.endpoint"; + private static final String OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = + "otel.exporter.otlp.traces.endpoint"; + private static final String OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = + "otel.exporter.otlp.metrics.endpoint"; + /** - * Customizes the provided {@link AutoConfigurationCustomizer}. + * Customizes the provided {@link AutoConfigurationCustomizer} such that authenticated exports to + * GCP Telemetry API are possible from the configured OTLP exporter. * *

This method attempts to retrieve Google Application Default Credentials (ADC) and performs - * the following: - Adds authorization headers to the configured {@link SpanExporter} based on the - * retrieved credentials. - Adds default properties for OTLP endpoint and resource attributes for - * GCP integration. + * the following: + * + *

+ * + * The 'customization' performed includes customizing the exporters by adding required headers to + * the export calls made and customizing the resource by adding required resource attributes to + * enable GCP integration. * * @param autoConfiguration the AutoConfigurationCustomizer to customize. * @throws GoogleAuthException if there's an error retrieving Google Application Default @@ -61,7 +87,7 @@ public class GcpAuthAutoConfigurationCustomizerProvider * not configured through environment variables or system properties. */ @Override - public void customize(AutoConfigurationCustomizer autoConfiguration) { + public void customize(@Nonnull AutoConfigurationCustomizer autoConfiguration) { GoogleCredentials credentials; try { credentials = GoogleCredentials.getApplicationDefault(); @@ -70,7 +96,11 @@ public void customize(AutoConfigurationCustomizer autoConfiguration) { } autoConfiguration .addSpanExporterCustomizer( - (exporter, configProperties) -> addAuthorizationHeaders(exporter, credentials)) + (spanExporter, configProperties) -> + customizeSpanExporter(spanExporter, configProperties, credentials)) + .addMetricExporterCustomizer( + (metricExporter, configProperties) -> + customizeMetricExporter(metricExporter, configProperties, credentials)) .addResourceCustomizer(GcpAuthAutoConfigurationCustomizerProvider::customizeResource); } @@ -79,6 +109,58 @@ public int order() { return Integer.MAX_VALUE - 1; } + // This method evaluates if the span exporter should be modified to enable export to GCP. + private static boolean shouldCustomizeSpanExporter(ConfigProperties configProperties) { + String baseEndpoint = configProperties.getString(OTEL_EXPORTER_OTLP_ENDPOINT); + if (baseEndpoint != null && isKnownGcpTelemetryEndpoint(baseEndpoint)) { + return true; + } + String tracesEndpoint = configProperties.getString(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT); + return tracesEndpoint != null && isKnownGcpTelemetryEndpoint(tracesEndpoint); + } + + // This method evaluates if the metric exporter should be modified to enable export to GCP. + private static boolean shouldCustomizeMetricExporter(ConfigProperties configProperties) { + String baseEndpoint = configProperties.getString(OTEL_EXPORTER_OTLP_ENDPOINT); + if (baseEndpoint != null && isKnownGcpTelemetryEndpoint(baseEndpoint)) { + return true; + } + String metricsEndpoint = configProperties.getString(OTEL_EXPORTER_OTLP_METRICS_ENDPOINT); + return metricsEndpoint != null && isKnownGcpTelemetryEndpoint(metricsEndpoint); + } + + // This method evaluates if the endpoint provided by the user is a known GCP telemetry endpoint. + private static boolean isKnownGcpTelemetryEndpoint(String endpoint) { + String knownBaseEndpointRegex = "^https://telemetry\\.googleapis\\.com(?:[:/].*)?$"; + String knownBaseSandboxEndpoint = + "^https://staging-telemetry\\.sandbox\\.googleapis\\.com(?:[:/].*)?$"; + String knownRegionalizedEndpointRegex = + "^https://([a-z0-9]+(?:-[a-z0-9]+)*)\\.rep\\.googleapis\\.com(?:[:/].*)?$"; + // create a combined regex that matches any of the above. + String knownGcpEndpointRegex = + String.join( + "|", knownBaseEndpointRegex, knownBaseSandboxEndpoint, knownRegionalizedEndpointRegex); + Pattern knownGcpEndpointPattern = Pattern.compile(knownGcpEndpointRegex); + Matcher gcpEndpointMatcher = knownGcpEndpointPattern.matcher(endpoint); + return gcpEndpointMatcher.matches(); + } + + private static SpanExporter customizeSpanExporter( + SpanExporter exporter, ConfigProperties configProperties, GoogleCredentials credentials) { + if (shouldCustomizeSpanExporter(configProperties)) { + return addAuthorizationHeaders(exporter, credentials); + } + return exporter; + } + + private static MetricExporter customizeMetricExporter( + MetricExporter exporter, ConfigProperties configProperties, GoogleCredentials credentials) { + if (shouldCustomizeMetricExporter(configProperties)) { + return addAuthorizationHeaders(exporter, credentials); + } + return exporter; + } + // Adds authorization headers to the calls made by the OtlpGrpcSpanExporter and // OtlpHttpSpanExporter. private static SpanExporter addAuthorizationHeaders( @@ -97,6 +179,24 @@ private static SpanExporter addAuthorizationHeaders( return exporter; } + // Adds authorization headers to the calls made by the OtlpGrpcMetricExporter and + // OtlpHttpMetricExporter. + private static MetricExporter addAuthorizationHeaders( + MetricExporter exporter, GoogleCredentials credentials) { + if (exporter instanceof OtlpHttpMetricExporter) { + OtlpHttpMetricExporterBuilder builder = + ((OtlpHttpMetricExporter) exporter) + .toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials)); + return builder.build(); + } else if (exporter instanceof OtlpGrpcMetricExporter) { + OtlpGrpcMetricExporterBuilder builder = + ((OtlpGrpcMetricExporter) exporter) + .toBuilder().setHeaders(() -> getRequiredHeaderMap(credentials)); + return builder.build(); + } + return exporter; + } + private static Map getRequiredHeaderMap(GoogleCredentials credentials) { Map> gcpHeaders; try { diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index 5cbee0890..2a9943721 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -76,6 +76,8 @@ class GcpAuthAutoConfigurationCustomizerProviderTest { private static final ImmutableMap otelProperties = ImmutableMap.of( + "otel.exporter.otlp.traces.endpoint", + "https://telemetry.googleapis.com/v1/traces", "otel.traces.exporter", "otlp", "otel.metrics.exporter", From 3e1cf2962479a6cf8010da703814fd835f50a6dc Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Fri, 16 May 2025 18:11:39 +0000 Subject: [PATCH 02/11] Add test methods to support metrics export testing --- ...toConfigurationCustomizerProviderTest.java | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index 2a9943721..997a87eea 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -31,8 +31,10 @@ import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException; +import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider; import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider; import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SpanExporter; import java.io.IOException; @@ -428,10 +430,29 @@ private void prepareMockBehaviorForGoogleCredentials() { } private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter(SpanExporter spanExporter) { + return buildOpenTelemetrySdkWithExporter(spanExporter, null, otelProperties); + } + + @SuppressWarnings("UnusedMethod") + private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter(SpanExporter spanExporter, ImmutableMap customOTelProperties) { + return buildOpenTelemetrySdkWithExporter(spanExporter, null, customOTelProperties); + } + + @SuppressWarnings("UnusedMethod") + private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter(MetricExporter metricExporter) { + return buildOpenTelemetrySdkWithExporter(null, metricExporter, otelProperties); + } + + @SuppressWarnings("UnusedMethod") + private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter(MetricExporter metricExporter, ImmutableMap customOtelProperties) { + return buildOpenTelemetrySdkWithExporter(null, metricExporter, customOtelProperties); + } + + private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter(SpanExporter spanExporter, MetricExporter metricExporter, ImmutableMap customOtelProperties) { SpiHelper spiHelper = SpiHelper.create(GcpAuthAutoConfigurationCustomizerProviderTest.class.getClassLoader()); AutoConfiguredOpenTelemetrySdkBuilder builder = - AutoConfiguredOpenTelemetrySdk.builder().addPropertiesSupplier(() -> otelProperties); + AutoConfiguredOpenTelemetrySdk.builder().addPropertiesSupplier(() -> customOtelProperties); AutoConfigureUtil.setComponentLoader( builder, new ComponentLoader() { @@ -453,6 +474,21 @@ public String getName() { } }); } + if (spiClass == ConfigurableMetricExporterProvider.class) { + return Collections.singletonList( + (T) + new ConfigurableMetricExporterProvider() { + @Override + public MetricExporter createExporter(ConfigProperties configProperties) { + return metricExporter; + } + + @Override + public String getName() { + return "otlp"; + } + }); + } return spiHelper.load(spiClass); } }); From 716fad9db46d5c80976cc62760d45eccf97c074b Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Mon, 19 May 2025 00:15:26 +0000 Subject: [PATCH 03/11] Remove staging endpoint --- .../gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java index 2dac37252..e264ec633 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java @@ -132,14 +132,11 @@ private static boolean shouldCustomizeMetricExporter(ConfigProperties configProp // This method evaluates if the endpoint provided by the user is a known GCP telemetry endpoint. private static boolean isKnownGcpTelemetryEndpoint(String endpoint) { String knownBaseEndpointRegex = "^https://telemetry\\.googleapis\\.com(?:[:/].*)?$"; - String knownBaseSandboxEndpoint = - "^https://staging-telemetry\\.sandbox\\.googleapis\\.com(?:[:/].*)?$"; String knownRegionalizedEndpointRegex = "^https://([a-z0-9]+(?:-[a-z0-9]+)*)\\.rep\\.googleapis\\.com(?:[:/].*)?$"; // create a combined regex that matches any of the above. String knownGcpEndpointRegex = - String.join( - "|", knownBaseEndpointRegex, knownBaseSandboxEndpoint, knownRegionalizedEndpointRegex); + String.join("|", knownBaseEndpointRegex, knownRegionalizedEndpointRegex); Pattern knownGcpEndpointPattern = Pattern.compile(knownGcpEndpointRegex); Matcher gcpEndpointMatcher = knownGcpEndpointPattern.matcher(endpoint); return gcpEndpointMatcher.matches(); From 452093ef9158ba4de3eac25d56a56308940bd610 Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Mon, 19 May 2025 00:39:04 +0000 Subject: [PATCH 04/11] Add unit test for metrics auth customization --- ...toConfigurationCustomizerProviderTest.java | 175 +++++++++++++++--- 1 file changed, 154 insertions(+), 21 deletions(-) diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index 997a87eea..b1e89518c 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -17,8 +17,12 @@ import com.google.auto.value.AutoValue; import com.google.common.collect.ImmutableMap; import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.metrics.LongCounter; import io.opentelemetry.api.trace.Span; import io.opentelemetry.context.Scope; +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporter; +import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; @@ -34,6 +38,10 @@ import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider; import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider; import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.metrics.Aggregation; +import io.opentelemetry.sdk.metrics.InstrumentType; +import io.opentelemetry.sdk.metrics.data.AggregationTemporality; +import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.export.MetricExporter; import io.opentelemetry.sdk.trace.data.SpanData; import io.opentelemetry.sdk.trace.export.SpanExporter; @@ -47,6 +55,7 @@ import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.Random; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -65,18 +74,21 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.stubbing.Answer; @ExtendWith(MockitoExtension.class) class GcpAuthAutoConfigurationCustomizerProviderTest { private static final String DUMMY_GCP_RESOURCE_PROJECT_ID = "my-gcp-resource-project-id"; private static final String DUMMY_GCP_QUOTA_PROJECT_ID = "my-gcp-quota-project-id"; + private static final Random TEST_RANDOM = new Random(); @Mock private GoogleCredentials mockedGoogleCredentials; - @Captor private ArgumentCaptor>> headerSupplierCaptor; + @Captor private ArgumentCaptor>> traceHeaderSupplierCaptor; + @Captor private ArgumentCaptor>> metricHeaderSupplierCaptor; - private static final ImmutableMap otelProperties = + private static final ImmutableMap defaultOtelPropertiesSpanExporter = ImmutableMap.of( "otel.exporter.otlp.traces.endpoint", "https://telemetry.googleapis.com/v1/traces", @@ -89,13 +101,26 @@ class GcpAuthAutoConfigurationCustomizerProviderTest { "otel.resource.attributes", "foo=bar"); + private static final ImmutableMap defaultOtelPropertiesMetricExporter = + ImmutableMap.of( + "otel.exporter.otlp.metrics.endpoint", + "https://telemetry.googleapis.com/v1/metrics", + "otel.traces.exporter", + "none", + "otel.metrics.exporter", + "otlp", + "otel.logs.exporter", + "none", + "otel.resource.attributes", + "foo=bar"); + @BeforeEach public void setup() { MockitoAnnotations.openMocks(this); } @Test - public void testCustomizerOtlpHttp() { + public void testTraceCustomizerOtlpHttp() { // Set resource project system property System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); @@ -131,9 +156,10 @@ public void testCustomizerOtlpHttp() { Mockito.verify(mockOtlpHttpSpanExporter, Mockito.times(1)).toBuilder(); Mockito.verify(spyOtlpHttpSpanExporterBuilder, Mockito.times(1)) - .setHeaders(headerSupplierCaptor.capture()); - assertEquals(2, headerSupplierCaptor.getValue().get().size()); - assertThat(authHeadersQuotaProjectIsPresent(headerSupplierCaptor.getValue().get())).isTrue(); + .setHeaders(traceHeaderSupplierCaptor.capture()); + assertEquals(2, traceHeaderSupplierCaptor.getValue().get().size()); + assertThat(authHeadersQuotaProjectIsPresent(traceHeaderSupplierCaptor.getValue().get())) + .isTrue(); Mockito.verify(mockOtlpHttpSpanExporter, Mockito.atLeast(1)).export(Mockito.anyCollection()); @@ -152,6 +178,90 @@ public void testCustomizerOtlpHttp() { } } + @Test + public void testMetricCustomizerOtlpHttp() { + // Set resource project system property + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + // Prepare mocks + prepareMockBehaviorForGoogleCredentials(); + OtlpHttpMetricExporter mockOtlpHttpMetricExporter = Mockito.mock(OtlpHttpMetricExporter.class); + OtlpHttpMetricExporterBuilder otlpMetricExporterBuilder = OtlpHttpMetricExporter.builder(); + OtlpHttpMetricExporterBuilder spyOtlpHttpMetricExporterBuilder = + Mockito.spy(otlpMetricExporterBuilder); + Mockito.when(spyOtlpHttpMetricExporterBuilder.build()).thenReturn(mockOtlpHttpMetricExporter); + + Mockito.when(mockOtlpHttpMetricExporter.shutdown()) + .thenReturn(CompletableResultCode.ofSuccess()); + List exportedMetrics = new ArrayList<>(); + Mockito.when(mockOtlpHttpMetricExporter.export(Mockito.anyCollection())) + .thenAnswer( + invocationOnMock -> { + exportedMetrics.addAll(invocationOnMock.getArgument(0)); + return CompletableResultCode.ofSuccess(); + }); + Mockito.when(mockOtlpHttpMetricExporter.toBuilder()) + .thenReturn(spyOtlpHttpMetricExporterBuilder); + // mock the get default aggregation and aggregation temporality - they're required for valid + // metric collection. + Mockito.when(mockOtlpHttpMetricExporter.getDefaultAggregation(Mockito.any())) + .thenAnswer( + (Answer) + invocationOnMock -> { + InstrumentType instrumentType = invocationOnMock.getArgument(0); + return OtlpHttpMetricExporter.getDefault().getDefaultAggregation(instrumentType); + }); + Mockito.when(mockOtlpHttpMetricExporter.getAggregationTemporality(Mockito.any())) + .thenAnswer( + (Answer) + invocationOnMock -> { + InstrumentType instrumentType = invocationOnMock.getArgument(0); + return OtlpHttpMetricExporter.getDefault() + .getAggregationTemporality(instrumentType); + }); + + try (MockedStatic googleCredentialsMockedStatic = + Mockito.mockStatic(GoogleCredentials.class)) { + googleCredentialsMockedStatic + .when(GoogleCredentials::getApplicationDefault) + .thenReturn(mockedGoogleCredentials); + + OpenTelemetrySdk sdk = buildOpenTelemetrySdkWithExporter(mockOtlpHttpMetricExporter); + generateTestMetric(sdk); + CompletableResultCode code = sdk.shutdown(); + CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS); + assertTrue(joinResult.isSuccess()); + + Mockito.verify(mockOtlpHttpMetricExporter, Mockito.times(1)).toBuilder(); + Mockito.verify(spyOtlpHttpMetricExporterBuilder, Mockito.times(1)) + .setHeaders(metricHeaderSupplierCaptor.capture()); + assertEquals(2, metricHeaderSupplierCaptor.getValue().get().size()); + assertThat(authHeadersQuotaProjectIsPresent(metricHeaderSupplierCaptor.getValue().get())) + .isTrue(); + + Mockito.verify(mockOtlpHttpMetricExporter, Mockito.atLeast(1)) + .export(Mockito.anyCollection()); + + assertThat(exportedMetrics) + .hasSizeGreaterThan(0) + .allSatisfy( + metricData -> { + assertThat(metricData.getResource().getAttributes().asMap()) + .containsEntry( + AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), + DUMMY_GCP_RESOURCE_PROJECT_ID) + .containsEntry(AttributeKey.stringKey("foo"), "bar"); + assertThat(metricData.getLongSumData().getPoints()) + .hasSizeGreaterThan(0) + .allSatisfy( + longPointData -> { + assertThat(longPointData.getAttributes().asMap()) + .containsKey(AttributeKey.longKey("work_loop")); + }); + }); + } + } + @Test public void testCustomizerOtlpGrpc() { // Set resource project system property @@ -163,7 +273,7 @@ public void testCustomizerOtlpGrpc() { OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = Mockito.spy(OtlpGrpcSpanExporter.builder()); List exportedSpans = new ArrayList<>(); - configureGrpcMockExporters( + configureGrpcMockSpanExporters( mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); try (MockedStatic googleCredentialsMockedStatic = @@ -180,9 +290,10 @@ public void testCustomizerOtlpGrpc() { Mockito.verify(mockOtlpGrpcSpanExporter, Mockito.times(1)).toBuilder(); Mockito.verify(spyOtlpGrpcSpanExporterBuilder, Mockito.times(1)) - .setHeaders(headerSupplierCaptor.capture()); - assertEquals(2, headerSupplierCaptor.getValue().get().size()); - assertThat(authHeadersQuotaProjectIsPresent(headerSupplierCaptor.getValue().get())).isTrue(); + .setHeaders(traceHeaderSupplierCaptor.capture()); + assertEquals(2, traceHeaderSupplierCaptor.getValue().get().size()); + assertThat(authHeadersQuotaProjectIsPresent(traceHeaderSupplierCaptor.getValue().get())) + .isTrue(); Mockito.verify(mockOtlpGrpcSpanExporter, Mockito.atLeast(1)).export(Mockito.anyCollection()); @@ -256,7 +367,7 @@ public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) throws OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = Mockito.spy(OtlpGrpcSpanExporter.builder()); List exportedSpans = new ArrayList<>(); - configureGrpcMockExporters( + configureGrpcMockSpanExporters( mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); try (MockedStatic googleCredentialsMockedStatic = @@ -272,10 +383,10 @@ public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) throws CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS); assertTrue(joinResult.isSuccess()); Mockito.verify(spyOtlpGrpcSpanExporterBuilder, Mockito.times(1)) - .setHeaders(headerSupplierCaptor.capture()); + .setHeaders(traceHeaderSupplierCaptor.capture()); // assert that the Authorization bearer token header is present - Map exportHeaders = headerSupplierCaptor.getValue().get(); + Map exportHeaders = traceHeaderSupplierCaptor.getValue().get(); assertThat(exportHeaders).containsEntry("Authorization", "Bearer fake"); if (testCase.getExpectedQuotaProjectInHeader() == null) { @@ -362,7 +473,7 @@ private static Stream provideQuotaBehaviorTestCases() { // Configure necessary behavior on the Grpc mock exporters to work // TODO: Potential improvement - make this work for Http exporter as well. - private static void configureGrpcMockExporters( + private static void configureGrpcMockSpanExporters( OtlpGrpcSpanExporter mockGrpcExporter, OtlpGrpcSpanExporterBuilder spyGrpcExporterBuilder, List exportedSpanContainer) { @@ -430,25 +541,34 @@ private void prepareMockBehaviorForGoogleCredentials() { } private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter(SpanExporter spanExporter) { - return buildOpenTelemetrySdkWithExporter(spanExporter, null, otelProperties); + return buildOpenTelemetrySdkWithExporter( + spanExporter, OtlpHttpMetricExporter.getDefault(), defaultOtelPropertiesSpanExporter); } @SuppressWarnings("UnusedMethod") - private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter(SpanExporter spanExporter, ImmutableMap customOTelProperties) { - return buildOpenTelemetrySdkWithExporter(spanExporter, null, customOTelProperties); + private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter( + SpanExporter spanExporter, ImmutableMap customOTelProperties) { + return buildOpenTelemetrySdkWithExporter( + spanExporter, OtlpHttpMetricExporter.getDefault(), customOTelProperties); } @SuppressWarnings("UnusedMethod") private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter(MetricExporter metricExporter) { - return buildOpenTelemetrySdkWithExporter(null, metricExporter, otelProperties); + return buildOpenTelemetrySdkWithExporter( + OtlpHttpSpanExporter.getDefault(), metricExporter, defaultOtelPropertiesMetricExporter); } @SuppressWarnings("UnusedMethod") - private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter(MetricExporter metricExporter, ImmutableMap customOtelProperties) { - return buildOpenTelemetrySdkWithExporter(null, metricExporter, customOtelProperties); + private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter( + MetricExporter metricExporter, ImmutableMap customOtelProperties) { + return buildOpenTelemetrySdkWithExporter( + OtlpHttpSpanExporter.getDefault(), metricExporter, customOtelProperties); } - private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter(SpanExporter spanExporter, MetricExporter metricExporter, ImmutableMap customOtelProperties) { + private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter( + SpanExporter spanExporter, + MetricExporter metricExporter, + ImmutableMap customOtelProperties) { SpiHelper spiHelper = SpiHelper.create(GcpAuthAutoConfigurationCustomizerProviderTest.class.getClassLoader()); AutoConfiguredOpenTelemetrySdkBuilder builder = @@ -514,6 +634,19 @@ private static void generateTestSpan(OpenTelemetrySdk openTelemetrySdk) { } } + private static void generateTestMetric(OpenTelemetrySdk openTelemetrySdk) { + LongCounter longCounter = + openTelemetrySdk + .getMeter("test") + .counterBuilder("sample") + .setDescription("sample counter") + .setUnit("1") + .build(); + long workOutput = busyloop(); + long randomValue = TEST_RANDOM.nextInt(1000); + longCounter.add(randomValue, Attributes.of(AttributeKey.longKey("work_loop"), workOutput)); + } + // loop to simulate work done private static long busyloop() { Instant start = Instant.now(); From 282371089a8986c8476544a2be263d6bf636a1a4 Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Mon, 19 May 2025 00:51:39 +0000 Subject: [PATCH 05/11] Refactor test method to separate configuration --- ...toConfigurationCustomizerProviderTest.java | 79 +++++++++++-------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index b1e89518c..dd6bed4fc 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -142,6 +142,7 @@ public void testTraceCustomizerOtlpHttp() { }); Mockito.when(mockOtlpHttpSpanExporter.toBuilder()).thenReturn(spyOtlpHttpSpanExporterBuilder); + // begin assertions try (MockedStatic googleCredentialsMockedStatic = Mockito.mockStatic(GoogleCredentials.class)) { googleCredentialsMockedStatic @@ -189,37 +190,11 @@ public void testMetricCustomizerOtlpHttp() { OtlpHttpMetricExporterBuilder otlpMetricExporterBuilder = OtlpHttpMetricExporter.builder(); OtlpHttpMetricExporterBuilder spyOtlpHttpMetricExporterBuilder = Mockito.spy(otlpMetricExporterBuilder); - Mockito.when(spyOtlpHttpMetricExporterBuilder.build()).thenReturn(mockOtlpHttpMetricExporter); - - Mockito.when(mockOtlpHttpMetricExporter.shutdown()) - .thenReturn(CompletableResultCode.ofSuccess()); List exportedMetrics = new ArrayList<>(); - Mockito.when(mockOtlpHttpMetricExporter.export(Mockito.anyCollection())) - .thenAnswer( - invocationOnMock -> { - exportedMetrics.addAll(invocationOnMock.getArgument(0)); - return CompletableResultCode.ofSuccess(); - }); - Mockito.when(mockOtlpHttpMetricExporter.toBuilder()) - .thenReturn(spyOtlpHttpMetricExporterBuilder); - // mock the get default aggregation and aggregation temporality - they're required for valid - // metric collection. - Mockito.when(mockOtlpHttpMetricExporter.getDefaultAggregation(Mockito.any())) - .thenAnswer( - (Answer) - invocationOnMock -> { - InstrumentType instrumentType = invocationOnMock.getArgument(0); - return OtlpHttpMetricExporter.getDefault().getDefaultAggregation(instrumentType); - }); - Mockito.when(mockOtlpHttpMetricExporter.getAggregationTemporality(Mockito.any())) - .thenAnswer( - (Answer) - invocationOnMock -> { - InstrumentType instrumentType = invocationOnMock.getArgument(0); - return OtlpHttpMetricExporter.getDefault() - .getAggregationTemporality(instrumentType); - }); + configureHttpMockMetricExporter( + mockOtlpHttpMetricExporter, spyOtlpHttpMetricExporterBuilder, exportedMetrics); + // begin assertions try (MockedStatic googleCredentialsMockedStatic = Mockito.mockStatic(GoogleCredentials.class)) { googleCredentialsMockedStatic @@ -263,7 +238,7 @@ public void testMetricCustomizerOtlpHttp() { } @Test - public void testCustomizerOtlpGrpc() { + public void testTraceCustomizerOtlpGrpc() { // Set resource project system property System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); @@ -273,9 +248,10 @@ public void testCustomizerOtlpGrpc() { OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = Mockito.spy(OtlpGrpcSpanExporter.builder()); List exportedSpans = new ArrayList<>(); - configureGrpcMockSpanExporters( + configureGrpcMockSpanExporter( mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); + // begin assertions try (MockedStatic googleCredentialsMockedStatic = Mockito.mockStatic(GoogleCredentials.class)) { googleCredentialsMockedStatic @@ -367,7 +343,7 @@ public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) throws OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = Mockito.spy(OtlpGrpcSpanExporter.builder()); List exportedSpans = new ArrayList<>(); - configureGrpcMockSpanExporters( + configureGrpcMockSpanExporter( mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); try (MockedStatic googleCredentialsMockedStatic = @@ -471,9 +447,9 @@ private static Stream provideQuotaBehaviorTestCases() { .build())); } - // Configure necessary behavior on the Grpc mock exporters to work + // Configure necessary behavior on the gRPC mock span exporters to work. // TODO: Potential improvement - make this work for Http exporter as well. - private static void configureGrpcMockSpanExporters( + private static void configureGrpcMockSpanExporter( OtlpGrpcSpanExporter mockGrpcExporter, OtlpGrpcSpanExporterBuilder spyGrpcExporterBuilder, List exportedSpanContainer) { @@ -488,6 +464,41 @@ private static void configureGrpcMockSpanExporters( }); } + // Configure necessary behavior on the http mock metric exporters to work. + private static void configureHttpMockMetricExporter( + OtlpHttpMetricExporter mockOtlpHttpMetricExporter, + OtlpHttpMetricExporterBuilder spyOtlpHttpMetricExporterBuilder, + List exportedMetricContainer) { + Mockito.when(spyOtlpHttpMetricExporterBuilder.build()).thenReturn(mockOtlpHttpMetricExporter); + Mockito.when(mockOtlpHttpMetricExporter.shutdown()) + .thenReturn(CompletableResultCode.ofSuccess()); + Mockito.when(mockOtlpHttpMetricExporter.toBuilder()) + .thenReturn(spyOtlpHttpMetricExporterBuilder); + Mockito.when(mockOtlpHttpMetricExporter.export(Mockito.anyCollection())) + .thenAnswer( + invocationOnMock -> { + exportedMetricContainer.addAll(invocationOnMock.getArgument(0)); + return CompletableResultCode.ofSuccess(); + }); + // mock the get default aggregation and aggregation temporality - they're required for valid + // metric collection. + Mockito.when(mockOtlpHttpMetricExporter.getDefaultAggregation(Mockito.any())) + .thenAnswer( + (Answer) + invocationOnMock -> { + InstrumentType instrumentType = invocationOnMock.getArgument(0); + return OtlpHttpMetricExporter.getDefault().getDefaultAggregation(instrumentType); + }); + Mockito.when(mockOtlpHttpMetricExporter.getAggregationTemporality(Mockito.any())) + .thenAnswer( + (Answer) + invocationOnMock -> { + InstrumentType instrumentType = invocationOnMock.getArgument(0); + return OtlpHttpMetricExporter.getDefault() + .getAggregationTemporality(instrumentType); + }); + } + @AutoValue abstract static class QuotaProjectIdTestBehavior { // A null user specified quota represents the use case where user omits specifying quota From c2392376396d00b48abe0ed7216baee07f01b531 Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Mon, 19 May 2025 21:11:36 +0000 Subject: [PATCH 06/11] Add option to specify target signals for authentication --- .../contrib/gcp/auth/ConfigurableOption.java | 22 +++++- ...thAutoConfigurationCustomizerProvider.java | 71 +++++++------------ 2 files changed, 45 insertions(+), 48 deletions(-) diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java index 7928f9ab4..5f4a627e1 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/ConfigurableOption.java @@ -30,7 +30,27 @@ public enum ConfigurableOption { * href="https://cloud.google.com/docs/quotas/set-quota-project">official GCP client * libraries. */ - GOOGLE_CLOUD_QUOTA_PROJECT("Google Cloud Quota Project ID"); + GOOGLE_CLOUD_QUOTA_PROJECT("Google Cloud Quota Project ID"), + + /** + * Specifies a comma-separated list of OpenTelemetry signals for which this authentication + * extension should be active. The authentication mechanisms provided by this extension will only + * be applied to the listed signals. If not set, {@code all} is assumed to be set which means + * authentication is enabled for all supported signals. + * + *

Valid signal values are: + * + *

    + *
  • {@code metrics} - Enables authentication for metric exports. + *
  • {@code traces} - Enables authentication for trace exports. + *
  • {@code all} - Enables authentication for all exports. + *
+ * + *

The values are case-sensitive. Whitespace around commas and values is ignored. Can be + * configured using the environment variable `GOOGLE_OTEL_AUTH_TARGET_SIGNALS` or the system + * property `google.otel.auth.target.signals`. + */ + GOOGLE_OTEL_AUTH_TARGET_SIGNALS("Target Signals for Google Auth Extension"); private final String userReadableName; private final String environmentVariableName; diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java index e264ec633..b14a8e040 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java @@ -25,12 +25,11 @@ import io.opentelemetry.sdk.resources.Resource; import io.opentelemetry.sdk.trace.export.SpanExporter; import java.io.IOException; +import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Collectors; import javax.annotation.Nonnull; @@ -54,11 +53,9 @@ public class GcpAuthAutoConfigurationCustomizerProvider static final String QUOTA_USER_PROJECT_HEADER = "x-goog-user-project"; static final String GCP_USER_PROJECT_ID_KEY = "gcp.project_id"; - private static final String OTEL_EXPORTER_OTLP_ENDPOINT = "otel.exporter.otlp.endpoint"; - private static final String OTEL_EXPORTER_OTLP_TRACES_ENDPOINT = - "otel.exporter.otlp.traces.endpoint"; - private static final String OTEL_EXPORTER_OTLP_METRICS_ENDPOINT = - "otel.exporter.otlp.metrics.endpoint"; + private static final String SIGNAL_TYPE_TRACES = "traces"; + private static final String SIGNAL_TYPE_METRICS = "metrics"; + private static final String SIGNAL_TYPE_ALL = "all"; /** * Customizes the provided {@link AutoConfigurationCustomizer} such that authenticated exports to @@ -96,11 +93,10 @@ public void customize(@Nonnull AutoConfigurationCustomizer autoConfiguration) { } autoConfiguration .addSpanExporterCustomizer( - (spanExporter, configProperties) -> - customizeSpanExporter(spanExporter, configProperties, credentials)) + (spanExporter, configProperties) -> customizeSpanExporter(spanExporter, credentials)) .addMetricExporterCustomizer( (metricExporter, configProperties) -> - customizeMetricExporter(metricExporter, configProperties, credentials)) + customizeMetricExporter(metricExporter, credentials)) .addResourceCustomizer(GcpAuthAutoConfigurationCustomizerProvider::customizeResource); } @@ -109,55 +105,36 @@ public int order() { return Integer.MAX_VALUE - 1; } - // This method evaluates if the span exporter should be modified to enable export to GCP. - private static boolean shouldCustomizeSpanExporter(ConfigProperties configProperties) { - String baseEndpoint = configProperties.getString(OTEL_EXPORTER_OTLP_ENDPOINT); - if (baseEndpoint != null && isKnownGcpTelemetryEndpoint(baseEndpoint)) { - return true; - } - String tracesEndpoint = configProperties.getString(OTEL_EXPORTER_OTLP_TRACES_ENDPOINT); - return tracesEndpoint != null && isKnownGcpTelemetryEndpoint(tracesEndpoint); - } - - // This method evaluates if the metric exporter should be modified to enable export to GCP. - private static boolean shouldCustomizeMetricExporter(ConfigProperties configProperties) { - String baseEndpoint = configProperties.getString(OTEL_EXPORTER_OTLP_ENDPOINT); - if (baseEndpoint != null && isKnownGcpTelemetryEndpoint(baseEndpoint)) { - return true; - } - String metricsEndpoint = configProperties.getString(OTEL_EXPORTER_OTLP_METRICS_ENDPOINT); - return metricsEndpoint != null && isKnownGcpTelemetryEndpoint(metricsEndpoint); - } - - // This method evaluates if the endpoint provided by the user is a known GCP telemetry endpoint. - private static boolean isKnownGcpTelemetryEndpoint(String endpoint) { - String knownBaseEndpointRegex = "^https://telemetry\\.googleapis\\.com(?:[:/].*)?$"; - String knownRegionalizedEndpointRegex = - "^https://([a-z0-9]+(?:-[a-z0-9]+)*)\\.rep\\.googleapis\\.com(?:[:/].*)?$"; - // create a combined regex that matches any of the above. - String knownGcpEndpointRegex = - String.join("|", knownBaseEndpointRegex, knownRegionalizedEndpointRegex); - Pattern knownGcpEndpointPattern = Pattern.compile(knownGcpEndpointRegex); - Matcher gcpEndpointMatcher = knownGcpEndpointPattern.matcher(endpoint); - return gcpEndpointMatcher.matches(); - } - private static SpanExporter customizeSpanExporter( - SpanExporter exporter, ConfigProperties configProperties, GoogleCredentials credentials) { - if (shouldCustomizeSpanExporter(configProperties)) { + SpanExporter exporter, GoogleCredentials credentials) { + if (isSignalTargeted(SIGNAL_TYPE_TRACES)) { return addAuthorizationHeaders(exporter, credentials); } return exporter; } private static MetricExporter customizeMetricExporter( - MetricExporter exporter, ConfigProperties configProperties, GoogleCredentials credentials) { - if (shouldCustomizeMetricExporter(configProperties)) { + MetricExporter exporter, GoogleCredentials credentials) { + if (isSignalTargeted(SIGNAL_TYPE_METRICS)) { return addAuthorizationHeaders(exporter, credentials); } return exporter; } + // Checks if the auth extension is configured to target the passed signal for authentication. + private static boolean isSignalTargeted(String signal) { + String targetedSignals = + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getConfiguredValueWithFallback( + () -> SIGNAL_TYPE_ALL); + return Arrays.stream(targetedSignals.split(",")) + .map(String::trim) + .map( + targetedSignal -> + targetedSignal.equals(signal) || targetedSignal.equals(SIGNAL_TYPE_ALL)) + .findFirst() + .isPresent(); + } + // Adds authorization headers to the calls made by the OtlpGrpcSpanExporter and // OtlpHttpSpanExporter. private static SpanExporter addAuthorizationHeaders( From 7c840671a23e03ed19c52d62ba981c722e0dbc3e Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Mon, 19 May 2025 21:59:12 +0000 Subject: [PATCH 07/11] Add documentation for GOOGLE_OTEL_AUTH_TARGET_SIGNALS --- gcp-auth-extension/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gcp-auth-extension/README.md b/gcp-auth-extension/README.md index bb2c32886..ba5e1be15 100644 --- a/gcp-auth-extension/README.md +++ b/gcp-auth-extension/README.md @@ -47,6 +47,10 @@ Here is a list of required and optional configuration available for the extensio - Can also be configured using `google.cloud.quota.project` system property. +- `GOOGLE_OTEL_AUTH_TARGET_SIGNALS`: Environment variable that specifies a comma-separated list of OpenTelemetry signals for which this authentication extension should be active. Valid values contain - `metrics`, `traces` or `all`. If left unspecified, `all` is assumed meaning the extension will attempt to apply authentication to exports for all signals. + + - Can also be configured using `google.otel.auth.target.signals` system property. + ## Usage ### With OpenTelemetry Java agent From ef52dd18ed0638f3d1c9114b505618bec07835ee Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Mon, 19 May 2025 22:06:24 +0000 Subject: [PATCH 08/11] Add test for metric customizer gRPC --- ...toConfigurationCustomizerProviderTest.java | 137 +++++++++++++++--- 1 file changed, 116 insertions(+), 21 deletions(-) diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index dd6bed4fc..0a32ce4ad 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -25,6 +25,8 @@ import io.opentelemetry.exporter.otlp.http.metrics.OtlpHttpMetricExporterBuilder; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporter; import io.opentelemetry.exporter.otlp.http.trace.OtlpHttpSpanExporterBuilder; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter; +import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporterBuilder; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter; import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporterBuilder; import io.opentelemetry.sdk.OpenTelemetrySdk; @@ -119,6 +121,7 @@ public void setup() { MockitoAnnotations.openMocks(this); } + // TODO: Use parameterized test for testing traces customizer for http & grpc. @Test public void testTraceCustomizerOtlpHttp() { // Set resource project system property @@ -179,6 +182,58 @@ public void testTraceCustomizerOtlpHttp() { } } + @Test + public void testTraceCustomizerOtlpGrpc() { + // Set resource project system property + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + // Prepare mocks + prepareMockBehaviorForGoogleCredentials(); + OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); + OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = + Mockito.spy(OtlpGrpcSpanExporter.builder()); + List exportedSpans = new ArrayList<>(); + configureGrpcMockSpanExporter( + mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); + + // begin assertions + try (MockedStatic googleCredentialsMockedStatic = + Mockito.mockStatic(GoogleCredentials.class)) { + googleCredentialsMockedStatic + .when(GoogleCredentials::getApplicationDefault) + .thenReturn(mockedGoogleCredentials); + + OpenTelemetrySdk sdk = buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); + generateTestSpan(sdk); + CompletableResultCode code = sdk.shutdown(); + CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS); + assertTrue(joinResult.isSuccess()); + + Mockito.verify(mockOtlpGrpcSpanExporter, Mockito.times(1)).toBuilder(); + Mockito.verify(spyOtlpGrpcSpanExporterBuilder, Mockito.times(1)) + .setHeaders(traceHeaderSupplierCaptor.capture()); + assertEquals(2, traceHeaderSupplierCaptor.getValue().get().size()); + assertThat(authHeadersQuotaProjectIsPresent(traceHeaderSupplierCaptor.getValue().get())) + .isTrue(); + + Mockito.verify(mockOtlpGrpcSpanExporter, Mockito.atLeast(1)).export(Mockito.anyCollection()); + + assertThat(exportedSpans) + .hasSizeGreaterThan(0) + .allSatisfy( + spanData -> { + assertThat(spanData.getResource().getAttributes().asMap()) + .containsEntry( + AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), + DUMMY_GCP_RESOURCE_PROJECT_ID) + .containsEntry(AttributeKey.stringKey("foo"), "bar"); + assertThat(spanData.getAttributes().asMap()) + .containsKey(AttributeKey.longKey("work_loop")); + }); + } + } + + // TODO: Use parameterized test for testing metrics customizer for http & grpc. @Test public void testMetricCustomizerOtlpHttp() { // Set resource project system property @@ -238,18 +293,19 @@ public void testMetricCustomizerOtlpHttp() { } @Test - public void testTraceCustomizerOtlpGrpc() { + public void testMetricCustomizerOtlpGrpc() { // Set resource project system property System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); // Prepare mocks prepareMockBehaviorForGoogleCredentials(); - OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); - OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = - Mockito.spy(OtlpGrpcSpanExporter.builder()); - List exportedSpans = new ArrayList<>(); - configureGrpcMockSpanExporter( - mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); + OtlpGrpcMetricExporter mockOtlpGrpcMetricExporter = Mockito.mock(OtlpGrpcMetricExporter.class); + OtlpGrpcMetricExporterBuilder otlpMetricExporterBuilder = OtlpGrpcMetricExporter.builder(); + OtlpGrpcMetricExporterBuilder spyOtlpGrpcMetricExporterBuilder = + Mockito.spy(otlpMetricExporterBuilder); + List exportedMetrics = new ArrayList<>(); + configureGrpcMockMetricExporter( + mockOtlpGrpcMetricExporter, spyOtlpGrpcMetricExporterBuilder, exportedMetrics); // begin assertions try (MockedStatic googleCredentialsMockedStatic = @@ -258,32 +314,38 @@ public void testTraceCustomizerOtlpGrpc() { .when(GoogleCredentials::getApplicationDefault) .thenReturn(mockedGoogleCredentials); - OpenTelemetrySdk sdk = buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter); - generateTestSpan(sdk); + OpenTelemetrySdk sdk = buildOpenTelemetrySdkWithExporter(mockOtlpGrpcMetricExporter); + generateTestMetric(sdk); CompletableResultCode code = sdk.shutdown(); CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS); assertTrue(joinResult.isSuccess()); - Mockito.verify(mockOtlpGrpcSpanExporter, Mockito.times(1)).toBuilder(); - Mockito.verify(spyOtlpGrpcSpanExporterBuilder, Mockito.times(1)) - .setHeaders(traceHeaderSupplierCaptor.capture()); - assertEquals(2, traceHeaderSupplierCaptor.getValue().get().size()); - assertThat(authHeadersQuotaProjectIsPresent(traceHeaderSupplierCaptor.getValue().get())) + Mockito.verify(mockOtlpGrpcMetricExporter, Mockito.times(1)).toBuilder(); + Mockito.verify(spyOtlpGrpcMetricExporterBuilder, Mockito.times(1)) + .setHeaders(metricHeaderSupplierCaptor.capture()); + assertEquals(2, metricHeaderSupplierCaptor.getValue().get().size()); + assertThat(authHeadersQuotaProjectIsPresent(metricHeaderSupplierCaptor.getValue().get())) .isTrue(); - Mockito.verify(mockOtlpGrpcSpanExporter, Mockito.atLeast(1)).export(Mockito.anyCollection()); + Mockito.verify(mockOtlpGrpcMetricExporter, Mockito.atLeast(1)) + .export(Mockito.anyCollection()); - assertThat(exportedSpans) + assertThat(exportedMetrics) .hasSizeGreaterThan(0) .allSatisfy( - spanData -> { - assertThat(spanData.getResource().getAttributes().asMap()) + metricData -> { + assertThat(metricData.getResource().getAttributes().asMap()) .containsEntry( AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), DUMMY_GCP_RESOURCE_PROJECT_ID) .containsEntry(AttributeKey.stringKey("foo"), "bar"); - assertThat(spanData.getAttributes().asMap()) - .containsKey(AttributeKey.longKey("work_loop")); + assertThat(metricData.getLongSumData().getPoints()) + .hasSizeGreaterThan(0) + .allSatisfy( + longPointData -> { + assertThat(longPointData.getAttributes().asMap()) + .containsKey(AttributeKey.longKey("work_loop")); + }); }); } } @@ -448,7 +510,6 @@ private static Stream provideQuotaBehaviorTestCases() { } // Configure necessary behavior on the gRPC mock span exporters to work. - // TODO: Potential improvement - make this work for Http exporter as well. private static void configureGrpcMockSpanExporter( OtlpGrpcSpanExporter mockGrpcExporter, OtlpGrpcSpanExporterBuilder spyGrpcExporterBuilder, @@ -499,6 +560,40 @@ private static void configureHttpMockMetricExporter( }); } + private static void configureGrpcMockMetricExporter( + OtlpGrpcMetricExporter mockOtlpGrpcMetricExporter, + OtlpGrpcMetricExporterBuilder spyOtlpGrpcMetricExporterBuilder, + List exportedMetricContainer) { + Mockito.when(spyOtlpGrpcMetricExporterBuilder.build()).thenReturn(mockOtlpGrpcMetricExporter); + Mockito.when(mockOtlpGrpcMetricExporter.shutdown()) + .thenReturn(CompletableResultCode.ofSuccess()); + Mockito.when(mockOtlpGrpcMetricExporter.toBuilder()) + .thenReturn(spyOtlpGrpcMetricExporterBuilder); + Mockito.when(mockOtlpGrpcMetricExporter.export(Mockito.anyCollection())) + .thenAnswer( + invocationOnMock -> { + exportedMetricContainer.addAll(invocationOnMock.getArgument(0)); + return CompletableResultCode.ofSuccess(); + }); + // mock the get default aggregation and aggregation temporality - they're required for valid + // metric collection. + Mockito.when(mockOtlpGrpcMetricExporter.getDefaultAggregation(Mockito.any())) + .thenAnswer( + (Answer) + invocationOnMock -> { + InstrumentType instrumentType = invocationOnMock.getArgument(0); + return OtlpGrpcMetricExporter.getDefault().getDefaultAggregation(instrumentType); + }); + Mockito.when(mockOtlpGrpcMetricExporter.getAggregationTemporality(Mockito.any())) + .thenAnswer( + (Answer) + invocationOnMock -> { + InstrumentType instrumentType = invocationOnMock.getArgument(0); + return OtlpGrpcMetricExporter.getDefault() + .getAggregationTemporality(instrumentType); + }); + } + @AutoValue abstract static class QuotaProjectIdTestBehavior { // A null user specified quota represents the use case where user omits specifying quota From 7d3535d44df83c3ebe3af902ebc85fd7dc7f0ca4 Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Tue, 20 May 2025 03:41:36 +0000 Subject: [PATCH 09/11] Fix signal target evaluation logic --- ...uthAutoConfigurationCustomizerProvider.java | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java index b14a8e040..d6c68e54f 100644 --- a/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java +++ b/gcp-auth-extension/src/main/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProvider.java @@ -53,9 +53,9 @@ public class GcpAuthAutoConfigurationCustomizerProvider static final String QUOTA_USER_PROJECT_HEADER = "x-goog-user-project"; static final String GCP_USER_PROJECT_ID_KEY = "gcp.project_id"; - private static final String SIGNAL_TYPE_TRACES = "traces"; - private static final String SIGNAL_TYPE_METRICS = "metrics"; - private static final String SIGNAL_TYPE_ALL = "all"; + static final String SIGNAL_TYPE_TRACES = "traces"; + static final String SIGNAL_TYPE_METRICS = "metrics"; + static final String SIGNAL_TYPE_ALL = "all"; /** * Customizes the provided {@link AutoConfigurationCustomizer} such that authenticated exports to @@ -122,17 +122,15 @@ private static MetricExporter customizeMetricExporter( } // Checks if the auth extension is configured to target the passed signal for authentication. - private static boolean isSignalTargeted(String signal) { - String targetedSignals = + private static boolean isSignalTargeted(String checkSignal) { + String userSpecifiedTargetedSignals = ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getConfiguredValueWithFallback( () -> SIGNAL_TYPE_ALL); - return Arrays.stream(targetedSignals.split(",")) + return Arrays.stream(userSpecifiedTargetedSignals.split(",")) .map(String::trim) - .map( + .anyMatch( targetedSignal -> - targetedSignal.equals(signal) || targetedSignal.equals(SIGNAL_TYPE_ALL)) - .findFirst() - .isPresent(); + targetedSignal.equals(checkSignal) || targetedSignal.equals(SIGNAL_TYPE_ALL)); } // Adds authorization headers to the calls made by the OtlpGrpcSpanExporter and From 3f75d5f8f81f526fcb140a17b59b49e8325494c0 Mon Sep 17 00:00:00 2001 From: Pranav Sharma Date: Tue, 20 May 2025 04:28:48 +0000 Subject: [PATCH 10/11] Add target signal behavior test --- ...toConfigurationCustomizerProviderTest.java | 303 +++++++++++++++++- 1 file changed, 292 insertions(+), 11 deletions(-) diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index 0a32ce4ad..3bc7f1231 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -7,6 +7,9 @@ import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.GCP_USER_PROJECT_ID_KEY; import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.QUOTA_USER_PROJECT_HEADER; +import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.SIGNAL_TYPE_ALL; +import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.SIGNAL_TYPE_METRICS; +import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.SIGNAL_TYPE_TRACES; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -40,6 +43,7 @@ import io.opentelemetry.sdk.autoconfigure.spi.metrics.ConfigurableMetricExporterProvider; import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider; import io.opentelemetry.sdk.common.CompletableResultCode; +import io.opentelemetry.sdk.common.export.MemoryMode; import io.opentelemetry.sdk.metrics.Aggregation; import io.opentelemetry.sdk.metrics.InstrumentType; import io.opentelemetry.sdk.metrics.data.AggregationTemporality; @@ -62,6 +66,7 @@ import java.util.concurrent.TimeUnit; import java.util.function.Supplier; import java.util.stream.Stream; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -127,6 +132,8 @@ public void testTraceCustomizerOtlpHttp() { // Set resource project system property System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_TRACES); // Prepare mocks prepareMockBehaviorForGoogleCredentials(); OtlpHttpSpanExporter mockOtlpHttpSpanExporter = Mockito.mock(OtlpHttpSpanExporter.class); @@ -187,6 +194,8 @@ public void testTraceCustomizerOtlpGrpc() { // Set resource project system property System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_TRACES); // Prepare mocks prepareMockBehaviorForGoogleCredentials(); OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); @@ -239,6 +248,8 @@ public void testMetricCustomizerOtlpHttp() { // Set resource project system property System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_METRICS); // Prepare mocks prepareMockBehaviorForGoogleCredentials(); OtlpHttpMetricExporter mockOtlpHttpMetricExporter = Mockito.mock(OtlpHttpMetricExporter.class); @@ -297,6 +308,8 @@ public void testMetricCustomizerOtlpGrpc() { // Set resource project system property System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_METRICS); // Prepare mocks prepareMockBehaviorForGoogleCredentials(); OtlpGrpcMetricExporter mockOtlpGrpcMetricExporter = Mockito.mock(OtlpGrpcMetricExporter.class); @@ -352,6 +365,8 @@ public void testMetricCustomizerOtlpGrpc() { @Test public void testCustomizerFailWithMissingResourceProject() { + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_ALL); OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); try (MockedStatic googleCredentialsMockedStatic = Mockito.mockStatic(GoogleCredentials.class)) { @@ -372,6 +387,8 @@ public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) throws // Set resource project system property System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_ALL); // Prepare request metadata AccessToken fakeAccessToken = new AccessToken("fake", Date.from(Instant.now())); @@ -438,6 +455,215 @@ public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) throws } } + @ParameterizedTest + @MethodSource("provideTargetSignalBehaviorTestCases") + public void testTargetSignalsBehavior(TargetSignalBehavior testCase) { + // Set resource project system property + System.setProperty( + ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); + // Prepare mocks + // Prepare mocked credential + prepareMockBehaviorForGoogleCredentials(); + + // Prepare mocked span exporter + OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class); + OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder = + Mockito.spy(OtlpGrpcSpanExporter.builder()); + List exportedSpans = new ArrayList<>(); + configureGrpcMockSpanExporter( + mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); + configureGrpcMockSpanExporter( + mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans); + + // Prepare mocked metrics exporter + OtlpGrpcMetricExporter mockOtlpGrpcMetricExporter = Mockito.mock(OtlpGrpcMetricExporter.class); + OtlpGrpcMetricExporterBuilder otlpMetricExporterBuilder = OtlpGrpcMetricExporter.builder(); + OtlpGrpcMetricExporterBuilder spyOtlpGrpcMetricExporterBuilder = + Mockito.spy(otlpMetricExporterBuilder); + List exportedMetrics = new ArrayList<>(); + configureGrpcMockMetricExporter( + mockOtlpGrpcMetricExporter, spyOtlpGrpcMetricExporterBuilder, exportedMetrics); + + // configure environment according to test case + System.setProperty( + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), + testCase.getConfiguredTargetSignals()); + + // Build Autoconfigured OpenTelemetry SDK using the mocks and send signals + try (MockedStatic googleCredentialsMockedStatic = + Mockito.mockStatic(GoogleCredentials.class)) { + googleCredentialsMockedStatic + .when(GoogleCredentials::getApplicationDefault) + .thenReturn(mockedGoogleCredentials); + + OpenTelemetrySdk sdk = + buildOpenTelemetrySdkWithExporter( + mockOtlpGrpcSpanExporter, + mockOtlpGrpcMetricExporter, + testCase.getUserSpecifiedOtelProperties()); + generateTestMetric(sdk); + generateTestSpan(sdk); + CompletableResultCode code = sdk.shutdown(); + CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS); + assertTrue(joinResult.isSuccess()); + + // Check Traces modification conditions + if (testCase.getExpectedIsTraceSignalModified()) { + // If traces signal is expected to be modified, auth headers must be present + Mockito.verify(spyOtlpGrpcSpanExporterBuilder, Mockito.times(1)) + .setHeaders(traceHeaderSupplierCaptor.capture()); + assertEquals(2, traceHeaderSupplierCaptor.getValue().get().size()); + assertThat(authHeadersQuotaProjectIsPresent(traceHeaderSupplierCaptor.getValue().get())) + .isTrue(); + } else { + // If traces signals is not expected to be modified then no interaction with the builder + // should be made + Mockito.verifyNoInteractions(spyOtlpGrpcSpanExporterBuilder); + } + + // Check Metric modification conditions + if (testCase.getExpectedIsMetricsSignalModified()) { + // If metrics signal is expected to be modified, auth headers must be present + Mockito.verify(spyOtlpGrpcMetricExporterBuilder, Mockito.times(1)) + .setHeaders(metricHeaderSupplierCaptor.capture()); + assertEquals(2, metricHeaderSupplierCaptor.getValue().get().size()); + assertThat(authHeadersQuotaProjectIsPresent(metricHeaderSupplierCaptor.getValue().get())) + .isTrue(); + } else { + // If metrics signals is not expected to be modified then no interaction with the builder + // should be made + Mockito.verifyNoInteractions(spyOtlpGrpcMetricExporterBuilder); + } + } + } + + /** Test cases specifying expected behavior for GOOGLE_OTEL_AUTH_TARGET_SIGNALS */ + private static Stream provideTargetSignalBehaviorTestCases() { + return Stream.of( + Arguments.of( + TargetSignalBehavior.builder() + .setConfiguredTargetSignals("traces") + .setUserSpecifiedOtelProperties(defaultOtelPropertiesSpanExporter) + .setExpectedIsMetricsSignalModified(false) + .setExpectedIsTraceSignalModified(true) + .build()), + Arguments.of( + TargetSignalBehavior.builder() + .setConfiguredTargetSignals("metrics") + .setUserSpecifiedOtelProperties(defaultOtelPropertiesMetricExporter) + .setExpectedIsMetricsSignalModified(true) + .setExpectedIsTraceSignalModified(false) + .build()), + Arguments.of( + TargetSignalBehavior.builder() + .setConfiguredTargetSignals("all") + .setUserSpecifiedOtelProperties( + ImmutableMap.of( + "otel.exporter.otlp.metrics.endpoint", + "https://localhost:4813/v1/metrics", + "otel.exporter.otlp.traces.endpoint", + "https://localhost:4813/v1/traces", + "otel.traces.exporter", + "otlp", + "otel.metrics.exporter", + "otlp", + "otel.logs.exporter", + "none")) + .setExpectedIsMetricsSignalModified(true) + .setExpectedIsTraceSignalModified(true) + .build()), + Arguments.of( + TargetSignalBehavior.builder() + .setConfiguredTargetSignals("metrics, traces") + .setUserSpecifiedOtelProperties( + ImmutableMap.of( + "otel.exporter.otlp.metrics.endpoint", + "https://localhost:4813/v1/metrics", + "otel.exporter.otlp.traces.endpoint", + "https://localhost:4813/v1/traces", + "otel.traces.exporter", + "otlp", + "otel.metrics.exporter", + "otlp", + "otel.logs.exporter", + "none")) + .setExpectedIsMetricsSignalModified(true) + .setExpectedIsTraceSignalModified(true) + .build()), + Arguments.of( + TargetSignalBehavior.builder() + .setConfiguredTargetSignals("") + .setUserSpecifiedOtelProperties( + ImmutableMap.of( + "otel.exporter.otlp.metrics.endpoint", + "https://localhost:4813/v1/metrics", + "otel.exporter.otlp.traces.endpoint", + "https://localhost:4813/v1/traces", + "otel.traces.exporter", + "otlp", + "otel.metrics.exporter", + "otlp", + "otel.logs.exporter", + "none")) + .setExpectedIsMetricsSignalModified(true) + .setExpectedIsTraceSignalModified(true) + .build()), + Arguments.of( + TargetSignalBehavior.builder() + .setConfiguredTargetSignals("all") + .setUserSpecifiedOtelProperties( + ImmutableMap.of( + "otel.exporter.otlp.metrics.endpoint", + "https://localhost:4813/v1/metrics", + "otel.exporter.otlp.traces.endpoint", + "https://localhost:4813/v1/traces", + "otel.traces.exporter", + "none", + "otel.metrics.exporter", + "none", + "otel.logs.exporter", + "none")) + .setExpectedIsMetricsSignalModified(false) + .setExpectedIsTraceSignalModified(false) + .build()), + Arguments.of( + TargetSignalBehavior.builder() + .setConfiguredTargetSignals("metric, trace") + .setUserSpecifiedOtelProperties( + ImmutableMap.of( + "otel.exporter.otlp.metrics.endpoint", + "https://localhost:4813/v1/metrics", + "otel.exporter.otlp.traces.endpoint", + "https://localhost:4813/v1/traces", + "otel.traces.exporter", + "otlp", + "otel.metrics.exporter", + "otlp", + "otel.logs.exporter", + "none")) + .setExpectedIsMetricsSignalModified(false) + .setExpectedIsTraceSignalModified(false) + .build()), + Arguments.of( + TargetSignalBehavior.builder() + .setConfiguredTargetSignals("metrics, trace") + .setUserSpecifiedOtelProperties( + ImmutableMap.of( + "otel.exporter.otlp.metrics.endpoint", + "https://localhost:4813/v1/metrics", + "otel.exporter.otlp.traces.endpoint", + "https://localhost:4813/v1/traces", + "otel.traces.exporter", + "otlp", + "otel.metrics.exporter", + "otlp", + "otel.logs.exporter", + "none")) + .setExpectedIsMetricsSignalModified(true) + .setExpectedIsTraceSignalModified(false) + .build())); + } + /** * Test cases specifying expected value for the user quota project header given the user input and * the current credentials state. @@ -510,14 +736,19 @@ private static Stream provideQuotaBehaviorTestCases() { } // Configure necessary behavior on the gRPC mock span exporters to work. + // Mockito.lenient is used here because this method is used with parameterized tests where based + // on certain inputs, certain stubbings may not be required. private static void configureGrpcMockSpanExporter( OtlpGrpcSpanExporter mockGrpcExporter, OtlpGrpcSpanExporterBuilder spyGrpcExporterBuilder, List exportedSpanContainer) { - Mockito.when(spyGrpcExporterBuilder.build()).thenReturn(mockGrpcExporter); - Mockito.when(mockGrpcExporter.shutdown()).thenReturn(CompletableResultCode.ofSuccess()); - Mockito.when(mockGrpcExporter.toBuilder()).thenReturn(spyGrpcExporterBuilder); - Mockito.when(mockGrpcExporter.export(Mockito.anyCollection())) + Mockito.lenient().when(spyGrpcExporterBuilder.build()).thenReturn(mockGrpcExporter); + Mockito.lenient() + .when(mockGrpcExporter.shutdown()) + .thenReturn(CompletableResultCode.ofSuccess()); + Mockito.lenient().when(mockGrpcExporter.toBuilder()).thenReturn(spyGrpcExporterBuilder); + Mockito.lenient() + .when(mockGrpcExporter.export(Mockito.anyCollection())) .thenAnswer( invocationOnMock -> { exportedSpanContainer.addAll(invocationOnMock.getArgument(0)); @@ -560,16 +791,24 @@ private static void configureHttpMockMetricExporter( }); } + // Configure necessary behavior on the gRPC mock metrics exporters to work. + // Mockito.lenient is used here because this method is used with parameterized tests where based + // on certain inputs, certain stubbings may not be required. private static void configureGrpcMockMetricExporter( OtlpGrpcMetricExporter mockOtlpGrpcMetricExporter, OtlpGrpcMetricExporterBuilder spyOtlpGrpcMetricExporterBuilder, List exportedMetricContainer) { - Mockito.when(spyOtlpGrpcMetricExporterBuilder.build()).thenReturn(mockOtlpGrpcMetricExporter); - Mockito.when(mockOtlpGrpcMetricExporter.shutdown()) + Mockito.lenient() + .when(spyOtlpGrpcMetricExporterBuilder.build()) + .thenReturn(mockOtlpGrpcMetricExporter); + Mockito.lenient() + .when(mockOtlpGrpcMetricExporter.shutdown()) .thenReturn(CompletableResultCode.ofSuccess()); - Mockito.when(mockOtlpGrpcMetricExporter.toBuilder()) + Mockito.lenient() + .when(mockOtlpGrpcMetricExporter.toBuilder()) .thenReturn(spyOtlpGrpcMetricExporterBuilder); - Mockito.when(mockOtlpGrpcMetricExporter.export(Mockito.anyCollection())) + Mockito.lenient() + .when(mockOtlpGrpcMetricExporter.export(Mockito.anyCollection())) .thenAnswer( invocationOnMock -> { exportedMetricContainer.addAll(invocationOnMock.getArgument(0)); @@ -577,14 +816,16 @@ private static void configureGrpcMockMetricExporter( }); // mock the get default aggregation and aggregation temporality - they're required for valid // metric collection. - Mockito.when(mockOtlpGrpcMetricExporter.getDefaultAggregation(Mockito.any())) + Mockito.lenient() + .when(mockOtlpGrpcMetricExporter.getDefaultAggregation(Mockito.any())) .thenAnswer( (Answer) invocationOnMock -> { InstrumentType instrumentType = invocationOnMock.getArgument(0); return OtlpGrpcMetricExporter.getDefault().getDefaultAggregation(instrumentType); }); - Mockito.when(mockOtlpGrpcMetricExporter.getAggregationTemporality(Mockito.any())) + Mockito.lenient() + .when(mockOtlpGrpcMetricExporter.getAggregationTemporality(Mockito.any())) .thenAnswer( (Answer) invocationOnMock -> { @@ -592,6 +833,9 @@ private static void configureGrpcMockMetricExporter( return OtlpGrpcMetricExporter.getDefault() .getAggregationTemporality(instrumentType); }); + Mockito.lenient() + .when(mockOtlpGrpcMetricExporter.getMemoryMode()) + .thenReturn(MemoryMode.IMMUTABLE_DATA); } @AutoValue @@ -630,11 +874,48 @@ abstract static class Builder { } } + @AutoValue + abstract static class TargetSignalBehavior { + @Nonnull + abstract String getConfiguredTargetSignals(); + + @Nonnull + abstract ImmutableMap getUserSpecifiedOtelProperties(); + + abstract boolean getExpectedIsTraceSignalModified(); + + abstract boolean getExpectedIsMetricsSignalModified(); + + static Builder builder() { + return new AutoValue_GcpAuthAutoConfigurationCustomizerProviderTest_TargetSignalBehavior + .Builder(); + } + + @AutoValue.Builder + abstract static class Builder { + abstract Builder setConfiguredTargetSignals(String targetSignals); + + abstract Builder setUserSpecifiedOtelProperties(Map oTelProperties); + + // Set whether the combination of specified OTel properties and configured target signals + // should lead to modification of the OTLP trace exporters. + abstract Builder setExpectedIsTraceSignalModified(boolean expectedModified); + + // Set whether the combination of specified OTel properties and configured target signals + // should lead to modification of the OTLP metrics exporters. + abstract Builder setExpectedIsMetricsSignalModified(boolean expectedModified); + + abstract TargetSignalBehavior build(); + } + } + + // Mockito.lenient is used here because this method is used with parameterized tests where based @SuppressWarnings("CannotMockMethod") private void prepareMockBehaviorForGoogleCredentials() { AccessToken fakeAccessToken = new AccessToken("fake", Date.from(Instant.now())); try { - Mockito.when(mockedGoogleCredentials.getRequestMetadata()) + Mockito.lenient() + .when(mockedGoogleCredentials.getRequestMetadata()) .thenReturn( ImmutableMap.of( "Authorization", From 42baadd6878d2157898d4cba12f59eb6500409f6 Mon Sep 17 00:00:00 2001 From: otelbot <197425009+otelbot@users.noreply.github.com> Date: Tue, 20 May 2025 04:31:32 +0000 Subject: [PATCH 11/11] ./gradlew spotlessApply --- .../GcpAuthAutoConfigurationCustomizerProviderTest.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java index 3bc7f1231..de96dc382 100644 --- a/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java +++ b/gcp-auth-extension/src/test/java/io/opentelemetry/contrib/gcp/auth/GcpAuthAutoConfigurationCustomizerProviderTest.java @@ -249,7 +249,8 @@ public void testMetricCustomizerOtlpHttp() { System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); System.setProperty( - ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_METRICS); + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), + SIGNAL_TYPE_METRICS); // Prepare mocks prepareMockBehaviorForGoogleCredentials(); OtlpHttpMetricExporter mockOtlpHttpMetricExporter = Mockito.mock(OtlpHttpMetricExporter.class); @@ -309,7 +310,8 @@ public void testMetricCustomizerOtlpGrpc() { System.setProperty( ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID); System.setProperty( - ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), SIGNAL_TYPE_METRICS); + ConfigurableOption.GOOGLE_OTEL_AUTH_TARGET_SIGNALS.getSystemProperty(), + SIGNAL_TYPE_METRICS); // Prepare mocks prepareMockBehaviorForGoogleCredentials(); OtlpGrpcMetricExporter mockOtlpGrpcMetricExporter = Mockito.mock(OtlpGrpcMetricExporter.class);