diff --git a/gcp-auth-extension/README.md b/gcp-auth-extension/README.md
index 36d6504e..34cdf4bb 100644
--- a/gcp-auth-extension/README.md
+++ b/gcp-auth-extension/README.md
@@ -36,7 +36,10 @@ Here is a list of configurable options for the extension:
- `GOOGLE_CLOUD_PROJECT`: Environment variable that represents the Google Cloud Project ID to which the telemetry needs to be exported.
- Can also be configured using `google.cloud.project` system property.
- - If this option is not configured, the extension would infer GCP Project ID from the application default credentials. For more information on application default credentials, see [here](https://cloud.google.com/docs/authentication/application-default-credentials).
+ - This is a required option, the agent configuration will fail if this option is not set.
+- `GOOGLE_CLOUD_QUOTA_PROJECT`: Environment variable that represents the Google Cloud Quota Project ID which will be charged for the GCP API usage. To learn more about a *quota project*, see [here](https://cloud.google.com/docs/quotas/quota-project).
+ - Can also be configured using `google.cloud.quota.project` system property.
+ - If this option is not configured, the extension will use the Quota Project ID found in the Application Default Credentials (ADC), if available. For more information on application default credentials, see [here](https://cloud.google.com/docs/authentication/application-default-credentials).
## Usage
diff --git a/gcp-auth-extension/build.gradle.kts b/gcp-auth-extension/build.gradle.kts
index f4c67021..5bbf55f6 100644
--- a/gcp-auth-extension/build.gradle.kts
+++ b/gcp-auth-extension/build.gradle.kts
@@ -29,6 +29,7 @@ dependencies {
testCompileOnly("com.google.auto.service:auto-service-annotations")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine")
testImplementation("org.junit.jupiter:junit-jupiter-api")
+ testCompileOnly("org.junit.jupiter:junit-jupiter-params")
testImplementation("io.opentelemetry:opentelemetry-api")
testImplementation("io.opentelemetry:opentelemetry-exporter-otlp")
@@ -45,6 +46,9 @@ dependencies {
testImplementation("org.springframework.boot:spring-boot-starter:2.7.18")
testImplementation("org.springframework.boot:spring-boot-starter-test:2.7.18")
+ testAnnotationProcessor("com.google.auto.value:auto-value")
+ testCompileOnly("com.google.auto.value:auto-value-annotations")
+
agent("io.opentelemetry.javaagent:opentelemetry-javaagent")
}
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 520c59db..1bf90e48 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
@@ -18,7 +18,18 @@ public enum ConfigurableOption {
* Represents the Google Cloud Project ID option. Can be configured using the environment variable
* `GOOGLE_CLOUD_PROJECT` or the system property `google.cloud.project`.
*/
- GOOGLE_CLOUD_PROJECT("Google Cloud Project ID");
+ GOOGLE_CLOUD_PROJECT("Google Cloud Project ID"),
+
+ /**
+ * Represents the Google Cloud Quota Project ID option. Can be configured using the environment
+ * variable `GOOGLE_CLOUD_QUOTA_PROJECT` or the system property `google.cloud.quota.project`. The
+ * quota project is the project that is used for quota management and billing for the API usage.
+ *
+ *
The environment variable name is selected to be consistent with the official GCP client
+ * libraries.
+ */
+ GOOGLE_CLOUD_QUOTA_PROJECT("Google Cloud Quota Project ID");
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 2fa1a666..70e9bdd3 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
@@ -59,15 +59,16 @@ public class GcpAuthAutoConfigurationCustomizerProvider
*/
@Override
public void customize(AutoConfigurationCustomizer autoConfiguration) {
+ GoogleCredentials credentials;
try {
- GoogleCredentials credentials = GoogleCredentials.getApplicationDefault();
- autoConfiguration
- .addSpanExporterCustomizer(
- (exporter, configProperties) -> addAuthorizationHeaders(exporter, credentials))
- .addResourceCustomizer(GcpAuthAutoConfigurationCustomizerProvider::customizeResource);
+ credentials = GoogleCredentials.getApplicationDefault();
} catch (IOException e) {
throw new GoogleAuthException(Reason.FAILED_ADC_RETRIEVAL, e);
}
+ autoConfiguration
+ .addSpanExporterCustomizer(
+ (exporter, configProperties) -> addAuthorizationHeaders(exporter, credentials))
+ .addResourceCustomizer(GcpAuthAutoConfigurationCustomizerProvider::customizeResource);
}
@Override
@@ -100,24 +101,19 @@ public class GcpAuthAutoConfigurationCustomizerProvider
} catch (IOException e) {
throw new GoogleAuthException(Reason.FAILED_ADC_REFRESH, e);
}
- gcpHeaders.put(QUOTA_USER_PROJECT_HEADER, credentials.getQuotaProjectId());
gcpHeaders.put("Authorization", "Bearer " + credentials.getAccessToken().getTokenValue());
+ String configuredQuotaProjectId =
+ ConfigurableOption.GOOGLE_CLOUD_QUOTA_PROJECT.getConfiguredValueWithFallback(
+ credentials::getQuotaProjectId);
+ if (configuredQuotaProjectId != null && !configuredQuotaProjectId.isEmpty()) {
+ gcpHeaders.put(QUOTA_USER_PROJECT_HEADER, configuredQuotaProjectId);
+ }
return gcpHeaders;
}
// Updates the current resource with the attributes required for ingesting OTLP data on GCP.
private static Resource customizeResource(Resource resource, ConfigProperties configProperties) {
- String gcpProjectId =
- ConfigurableOption.GOOGLE_CLOUD_PROJECT.getConfiguredValueWithFallback(
- () -> {
- try {
- GoogleCredentials googleCredentials = GoogleCredentials.getApplicationDefault();
- return googleCredentials.getQuotaProjectId();
- } catch (IOException e) {
- throw new GoogleAuthException(Reason.FAILED_ADC_RETRIEVAL, e);
- }
- });
-
+ String gcpProjectId = ConfigurableOption.GOOGLE_CLOUD_PROJECT.getConfiguredValue();
Resource res =
Resource.create(
Attributes.of(AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), gcpProjectId));
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 c03d14c9..39f626e6 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
@@ -9,10 +9,12 @@ import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomiz
import static io.opentelemetry.contrib.gcp.auth.GcpAuthAutoConfigurationCustomizerProvider.QUOTA_USER_PROJECT_HEADER;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.google.auth.oauth2.AccessToken;
import com.google.auth.oauth2.GoogleCredentials;
+import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableMap;
import io.opentelemetry.api.common.AttributeKey;
import io.opentelemetry.api.trace.Span;
@@ -28,6 +30,7 @@ import io.opentelemetry.sdk.autoconfigure.internal.AutoConfigureUtil;
import io.opentelemetry.sdk.autoconfigure.internal.ComponentLoader;
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.traces.ConfigurableSpanExporterProvider;
import io.opentelemetry.sdk.common.CompletableResultCode;
import io.opentelemetry.sdk.trace.data.SpanData;
@@ -44,9 +47,14 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
+import java.util.stream.Stream;
+import javax.annotation.Nullable;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
@@ -58,6 +66,9 @@ import org.mockito.junit.jupiter.MockitoExtension;
@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";
+
@Mock private GoogleCredentials mockedGoogleCredentials;
@Captor private ArgumentCaptor>> headerSupplierCaptor;
@@ -74,16 +85,17 @@ class GcpAuthAutoConfigurationCustomizerProviderTest {
"foo=bar");
@BeforeEach
- @SuppressWarnings("CannotMockMethod")
public void setup() {
MockitoAnnotations.openMocks(this);
- Mockito.when(mockedGoogleCredentials.getQuotaProjectId()).thenReturn("test-project");
- Mockito.when(mockedGoogleCredentials.getAccessToken())
- .thenReturn(new AccessToken("fake", Date.from(Instant.now())));
}
@Test
public void testCustomizerOtlpHttp() {
+ // Set resource project system property
+ System.setProperty(
+ ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID);
+ // Prepare mocks
+ prepareMockBehaviorForGoogleCredentials();
OtlpHttpSpanExporter mockOtlpHttpSpanExporter = Mockito.mock(OtlpHttpSpanExporter.class);
OtlpHttpSpanExporterBuilder otlpSpanExporterBuilder = OtlpHttpSpanExporter.builder();
OtlpHttpSpanExporterBuilder spyOtlpHttpSpanExporterBuilder =
@@ -116,7 +128,7 @@ class GcpAuthAutoConfigurationCustomizerProviderTest {
Mockito.verify(spyOtlpHttpSpanExporterBuilder, Mockito.times(1))
.setHeaders(headerSupplierCaptor.capture());
assertEquals(2, headerSupplierCaptor.getValue().get().size());
- assertThat(verifyAuthHeaders(headerSupplierCaptor.getValue().get())).isTrue();
+ assertThat(authHeadersQuotaProjectIsPresent(headerSupplierCaptor.getValue().get())).isTrue();
Mockito.verify(mockOtlpHttpSpanExporter, Mockito.atLeast(1)).export(Mockito.anyCollection());
@@ -125,7 +137,9 @@ class GcpAuthAutoConfigurationCustomizerProviderTest {
.allSatisfy(
spanData -> {
assertThat(spanData.getResource().getAttributes().asMap())
- .containsEntry(AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), "test-project")
+ .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"));
@@ -135,20 +149,17 @@ class GcpAuthAutoConfigurationCustomizerProviderTest {
@Test
public void testCustomizerOtlpGrpc() {
+ // 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 otlpSpanExporterBuilder = OtlpGrpcSpanExporter.builder();
OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder =
- Mockito.spy(otlpSpanExporterBuilder);
- Mockito.when(spyOtlpGrpcSpanExporterBuilder.build()).thenReturn(mockOtlpGrpcSpanExporter);
- Mockito.when(mockOtlpGrpcSpanExporter.shutdown()).thenReturn(CompletableResultCode.ofSuccess());
+ Mockito.spy(OtlpGrpcSpanExporter.builder());
List exportedSpans = new ArrayList<>();
- Mockito.when(mockOtlpGrpcSpanExporter.export(Mockito.anyCollection()))
- .thenAnswer(
- invocationOnMock -> {
- exportedSpans.addAll(invocationOnMock.getArgument(0));
- return CompletableResultCode.ofSuccess();
- });
- Mockito.when(mockOtlpGrpcSpanExporter.toBuilder()).thenReturn(spyOtlpGrpcSpanExporterBuilder);
+ configureGrpcMockExporters(
+ mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans);
try (MockedStatic googleCredentialsMockedStatic =
Mockito.mockStatic(GoogleCredentials.class)) {
@@ -166,7 +177,7 @@ class GcpAuthAutoConfigurationCustomizerProviderTest {
Mockito.verify(spyOtlpGrpcSpanExporterBuilder, Mockito.times(1))
.setHeaders(headerSupplierCaptor.capture());
assertEquals(2, headerSupplierCaptor.getValue().get().size());
- verifyAuthHeaders(headerSupplierCaptor.getValue().get());
+ assertThat(authHeadersQuotaProjectIsPresent(headerSupplierCaptor.getValue().get())).isTrue();
Mockito.verify(mockOtlpGrpcSpanExporter, Mockito.atLeast(1)).export(Mockito.anyCollection());
@@ -175,7 +186,9 @@ class GcpAuthAutoConfigurationCustomizerProviderTest {
.allSatisfy(
spanData -> {
assertThat(spanData.getResource().getAttributes().asMap())
- .containsEntry(AttributeKey.stringKey(GCP_USER_PROJECT_ID_KEY), "test-project")
+ .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"));
@@ -183,6 +196,209 @@ class GcpAuthAutoConfigurationCustomizerProviderTest {
}
}
+ @Test
+ public void testCustomizerFailWithMissingResourceProject() {
+ OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class);
+ try (MockedStatic googleCredentialsMockedStatic =
+ Mockito.mockStatic(GoogleCredentials.class)) {
+ googleCredentialsMockedStatic
+ .when(GoogleCredentials::getApplicationDefault)
+ .thenReturn(mockedGoogleCredentials);
+
+ assertThrows(
+ ConfigurationException.class,
+ () -> buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter));
+ }
+ }
+
+ @ParameterizedTest
+ @MethodSource("provideQuotaBehaviorTestCases")
+ @SuppressWarnings("CannotMockMethod")
+ public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) {
+ // Set resource project system property
+ System.setProperty(
+ ConfigurableOption.GOOGLE_CLOUD_PROJECT.getSystemProperty(), DUMMY_GCP_RESOURCE_PROJECT_ID);
+ // Configure mock credentials to return fake access token
+ Mockito.when(mockedGoogleCredentials.getAccessToken())
+ .thenReturn(new AccessToken("fake", Date.from(Instant.now())));
+
+ // To prevent unncecessary stubbings, mock getQuotaProjectId only when necessary
+ if (testCase.getUserSpecifiedQuotaProjectId() == null
+ || testCase.getUserSpecifiedQuotaProjectId().isEmpty()) {
+ String quotaProjectFromCredential =
+ testCase.getIsQuotaProjectPresentInCredentials() ? DUMMY_GCP_QUOTA_PROJECT_ID : null;
+ Mockito.when(mockedGoogleCredentials.getQuotaProjectId())
+ .thenReturn(quotaProjectFromCredential);
+ }
+
+ // configure environment according to test case
+ String quotaProjectId = testCase.getUserSpecifiedQuotaProjectId(); // maybe empty string
+ if (testCase.getUserSpecifiedQuotaProjectId() != null) {
+ // user specified a quota project id
+ System.setProperty(
+ ConfigurableOption.GOOGLE_CLOUD_QUOTA_PROJECT.getSystemProperty(), quotaProjectId);
+ }
+
+ // prepare mock exporter
+ OtlpGrpcSpanExporter mockOtlpGrpcSpanExporter = Mockito.mock(OtlpGrpcSpanExporter.class);
+ OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder =
+ Mockito.spy(OtlpGrpcSpanExporter.builder());
+ List exportedSpans = new ArrayList<>();
+ configureGrpcMockExporters(
+ mockOtlpGrpcSpanExporter, spyOtlpGrpcSpanExporterBuilder, exportedSpans);
+
+ try (MockedStatic googleCredentialsMockedStatic =
+ Mockito.mockStatic(GoogleCredentials.class)) {
+ googleCredentialsMockedStatic
+ .when(GoogleCredentials::getApplicationDefault)
+ .thenReturn(mockedGoogleCredentials);
+
+ // Export telemetry to capture headers in the export calls
+ OpenTelemetrySdk sdk = buildOpenTelemetrySdkWithExporter(mockOtlpGrpcSpanExporter);
+ generateTestSpan(sdk);
+ CompletableResultCode code = sdk.shutdown();
+ CompletableResultCode joinResult = code.join(10, TimeUnit.SECONDS);
+ assertTrue(joinResult.isSuccess());
+ Mockito.verify(spyOtlpGrpcSpanExporterBuilder, Mockito.times(1))
+ .setHeaders(headerSupplierCaptor.capture());
+
+ // assert that the Authorization bearer token header is present
+ Map exportHeaders = headerSupplierCaptor.getValue().get();
+ assertThat(exportHeaders).containsEntry("Authorization", "Bearer fake");
+
+ if (testCase.getExpectedQuotaProjectInHeader() == null) {
+ // there should be no user quota project header
+ assertThat(exportHeaders).doesNotContainKey(QUOTA_USER_PROJECT_HEADER);
+ } else {
+ // there should be user quota project header with expected value
+ assertThat(exportHeaders)
+ .containsEntry(QUOTA_USER_PROJECT_HEADER, testCase.getExpectedQuotaProjectInHeader());
+ }
+ }
+ }
+
+ /**
+ * Test cases specifying expected value for the user quota project header given the user input and
+ * the current credentials state.
+ *
+ * {@code null} for {@link QuotaProjectIdTestBehavior#getUserSpecifiedQuotaProjectId()}
+ * indicates the case of user not specifying the quota project ID.
+ *
+ *
{@code null} value for {@link QuotaProjectIdTestBehavior#getExpectedQuotaProjectInHeader()}
+ * indicates the expectation that the QUOTA_USER_PROJECT_HEADER should not be present in the
+ * export headers.
+ *
+ *
{@code true} for {@link QuotaProjectIdTestBehavior#getIsQuotaProjectPresentInCredentials()}
+ * indicates that the mocked credentials are configured to provide DUMMY_GCP_QUOTA_PROJECT_ID as
+ * the quota project ID.
+ */
+ private static Stream provideQuotaBehaviorTestCases() {
+ return Stream.of(
+ Arguments.of(
+ QuotaProjectIdTestBehavior.builder()
+ .setUserSpecifiedQuotaProjectId(DUMMY_GCP_QUOTA_PROJECT_ID)
+ .setIsQuotaProjectPresentInCredentials(true)
+ .setExpectedQuotaProjectInHeader(DUMMY_GCP_QUOTA_PROJECT_ID)
+ .build()),
+ Arguments.of(
+ QuotaProjectIdTestBehavior.builder()
+ .setUserSpecifiedQuotaProjectId(DUMMY_GCP_QUOTA_PROJECT_ID)
+ .setIsQuotaProjectPresentInCredentials(false)
+ .setExpectedQuotaProjectInHeader(DUMMY_GCP_QUOTA_PROJECT_ID)
+ .build()),
+ Arguments.of(
+ QuotaProjectIdTestBehavior.builder()
+ .setUserSpecifiedQuotaProjectId("my-custom-quota-project-id")
+ .setIsQuotaProjectPresentInCredentials(true)
+ .setExpectedQuotaProjectInHeader("my-custom-quota-project-id")
+ .build()),
+ Arguments.of(
+ QuotaProjectIdTestBehavior.builder()
+ .setUserSpecifiedQuotaProjectId("my-custom-quota-project-id")
+ .setIsQuotaProjectPresentInCredentials(false)
+ .setExpectedQuotaProjectInHeader("my-custom-quota-project-id")
+ .build()),
+ Arguments.of(
+ QuotaProjectIdTestBehavior.builder()
+ .setUserSpecifiedQuotaProjectId("") // user explicitly specifies empty
+ .setIsQuotaProjectPresentInCredentials(true)
+ .setExpectedQuotaProjectInHeader(DUMMY_GCP_QUOTA_PROJECT_ID)
+ .build()),
+ Arguments.of(
+ QuotaProjectIdTestBehavior.builder()
+ .setUserSpecifiedQuotaProjectId(null) // user omits specifying quota project
+ .setIsQuotaProjectPresentInCredentials(true)
+ .setExpectedQuotaProjectInHeader(DUMMY_GCP_QUOTA_PROJECT_ID)
+ .build()),
+ Arguments.of(
+ QuotaProjectIdTestBehavior.builder()
+ .setUserSpecifiedQuotaProjectId("")
+ .setIsQuotaProjectPresentInCredentials(false)
+ .setExpectedQuotaProjectInHeader(null)
+ .build()),
+ Arguments.of(
+ QuotaProjectIdTestBehavior.builder()
+ .setUserSpecifiedQuotaProjectId(null)
+ .setIsQuotaProjectPresentInCredentials(false)
+ .setExpectedQuotaProjectInHeader(null)
+ .build()));
+ }
+
+ // 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(
+ 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()))
+ .thenAnswer(
+ invocationOnMock -> {
+ exportedSpanContainer.addAll(invocationOnMock.getArgument(0));
+ return CompletableResultCode.ofSuccess();
+ });
+ }
+
+ @AutoValue
+ abstract static class QuotaProjectIdTestBehavior {
+ // A null user specified quota represents the use case where user omits specifying quota
+ @Nullable
+ abstract String getUserSpecifiedQuotaProjectId();
+
+ abstract boolean getIsQuotaProjectPresentInCredentials();
+
+ // If expected quota project in header is null, the header entry should not be present in export
+ @Nullable
+ abstract String getExpectedQuotaProjectInHeader();
+
+ static Builder builder() {
+ return new AutoValue_GcpAuthAutoConfigurationCustomizerProviderTest_QuotaProjectIdTestBehavior
+ .Builder();
+ }
+
+ @AutoValue.Builder
+ abstract static class Builder {
+ abstract Builder setUserSpecifiedQuotaProjectId(String quotaProjectId);
+
+ abstract Builder setIsQuotaProjectPresentInCredentials(
+ boolean quotaProjectPresentInCredentials);
+
+ abstract Builder setExpectedQuotaProjectInHeader(String expectedQuotaProjectInHeader);
+
+ abstract QuotaProjectIdTestBehavior build();
+ }
+ }
+
+ @SuppressWarnings("CannotMockMethod")
+ private void prepareMockBehaviorForGoogleCredentials() {
+ Mockito.when(mockedGoogleCredentials.getQuotaProjectId())
+ .thenReturn(DUMMY_GCP_QUOTA_PROJECT_ID);
+ Mockito.when(mockedGoogleCredentials.getAccessToken())
+ .thenReturn(new AccessToken("fake", Date.from(Instant.now())));
+ }
+
private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter(SpanExporter spanExporter) {
SpiHelper spiHelper =
SpiHelper.create(GcpAuthAutoConfigurationCustomizerProviderTest.class.getClassLoader());
@@ -215,9 +431,12 @@ class GcpAuthAutoConfigurationCustomizerProviderTest {
return builder.build().getOpenTelemetrySdk();
}
- private static boolean verifyAuthHeaders(Map headers) {
+ private static boolean authHeadersQuotaProjectIsPresent(Map headers) {
Set> headerEntrySet = headers.entrySet();
- return headerEntrySet.contains(new SimpleEntry<>(QUOTA_USER_PROJECT_HEADER, "test-project"))
+ return headerEntrySet.contains(
+ new SimpleEntry<>(
+ QUOTA_USER_PROJECT_HEADER,
+ GcpAuthAutoConfigurationCustomizerProviderTest.DUMMY_GCP_QUOTA_PROJECT_ID))
&& headerEntrySet.contains(new SimpleEntry<>("Authorization", "Bearer fake"));
}