1717import com .google .auto .value .AutoValue ;
1818import com .google .common .collect .ImmutableMap ;
1919import io .opentelemetry .api .common .AttributeKey ;
20+ import io .opentelemetry .api .common .Attributes ;
21+ import io .opentelemetry .api .metrics .LongCounter ;
2022import io .opentelemetry .api .trace .Span ;
2123import io .opentelemetry .context .Scope ;
24+ import io .opentelemetry .exporter .otlp .http .metrics .OtlpHttpMetricExporter ;
25+ import io .opentelemetry .exporter .otlp .http .metrics .OtlpHttpMetricExporterBuilder ;
2226import io .opentelemetry .exporter .otlp .http .trace .OtlpHttpSpanExporter ;
2327import io .opentelemetry .exporter .otlp .http .trace .OtlpHttpSpanExporterBuilder ;
2428import io .opentelemetry .exporter .otlp .trace .OtlpGrpcSpanExporter ;
3438import io .opentelemetry .sdk .autoconfigure .spi .metrics .ConfigurableMetricExporterProvider ;
3539import io .opentelemetry .sdk .autoconfigure .spi .traces .ConfigurableSpanExporterProvider ;
3640import io .opentelemetry .sdk .common .CompletableResultCode ;
41+ import io .opentelemetry .sdk .metrics .Aggregation ;
42+ import io .opentelemetry .sdk .metrics .InstrumentType ;
43+ import io .opentelemetry .sdk .metrics .data .AggregationTemporality ;
44+ import io .opentelemetry .sdk .metrics .data .MetricData ;
3745import io .opentelemetry .sdk .metrics .export .MetricExporter ;
3846import io .opentelemetry .sdk .trace .data .SpanData ;
3947import io .opentelemetry .sdk .trace .export .SpanExporter ;
4755import java .util .List ;
4856import java .util .Map ;
4957import java .util .Map .Entry ;
58+ import java .util .Random ;
5059import java .util .Set ;
5160import java .util .concurrent .TimeUnit ;
5261import java .util .function .Supplier ;
6574import org .mockito .Mockito ;
6675import org .mockito .MockitoAnnotations ;
6776import org .mockito .junit .jupiter .MockitoExtension ;
77+ import org .mockito .stubbing .Answer ;
6878
6979@ ExtendWith (MockitoExtension .class )
7080class GcpAuthAutoConfigurationCustomizerProviderTest {
7181
7282 private static final String DUMMY_GCP_RESOURCE_PROJECT_ID = "my-gcp-resource-project-id" ;
7383 private static final String DUMMY_GCP_QUOTA_PROJECT_ID = "my-gcp-quota-project-id" ;
84+ private static final Random TEST_RANDOM = new Random ();
7485
7586 @ Mock private GoogleCredentials mockedGoogleCredentials ;
7687
77- @ Captor private ArgumentCaptor <Supplier <Map <String , String >>> headerSupplierCaptor ;
88+ @ Captor private ArgumentCaptor <Supplier <Map <String , String >>> traceHeaderSupplierCaptor ;
89+ @ Captor private ArgumentCaptor <Supplier <Map <String , String >>> metricHeaderSupplierCaptor ;
7890
79- private static final ImmutableMap <String , String > otelProperties =
91+ private static final ImmutableMap <String , String > defaultOtelPropertiesSpanExporter =
8092 ImmutableMap .of (
8193 "otel.exporter.otlp.traces.endpoint" ,
8294 "https://telemetry.googleapis.com/v1/traces" ,
@@ -89,13 +101,26 @@ class GcpAuthAutoConfigurationCustomizerProviderTest {
89101 "otel.resource.attributes" ,
90102 "foo=bar" );
91103
104+ private static final ImmutableMap <String , String > defaultOtelPropertiesMetricExporter =
105+ ImmutableMap .of (
106+ "otel.exporter.otlp.metrics.endpoint" ,
107+ "https://telemetry.googleapis.com/v1/metrics" ,
108+ "otel.traces.exporter" ,
109+ "none" ,
110+ "otel.metrics.exporter" ,
111+ "otlp" ,
112+ "otel.logs.exporter" ,
113+ "none" ,
114+ "otel.resource.attributes" ,
115+ "foo=bar" );
116+
92117 @ BeforeEach
93118 public void setup () {
94119 MockitoAnnotations .openMocks (this );
95120 }
96121
97122 @ Test
98- public void testCustomizerOtlpHttp () {
123+ public void testTraceCustomizerOtlpHttp () {
99124 // Set resource project system property
100125 System .setProperty (
101126 ConfigurableOption .GOOGLE_CLOUD_PROJECT .getSystemProperty (), DUMMY_GCP_RESOURCE_PROJECT_ID );
@@ -131,9 +156,10 @@ public void testCustomizerOtlpHttp() {
131156
132157 Mockito .verify (mockOtlpHttpSpanExporter , Mockito .times (1 )).toBuilder ();
133158 Mockito .verify (spyOtlpHttpSpanExporterBuilder , Mockito .times (1 ))
134- .setHeaders (headerSupplierCaptor .capture ());
135- assertEquals (2 , headerSupplierCaptor .getValue ().get ().size ());
136- assertThat (authHeadersQuotaProjectIsPresent (headerSupplierCaptor .getValue ().get ())).isTrue ();
159+ .setHeaders (traceHeaderSupplierCaptor .capture ());
160+ assertEquals (2 , traceHeaderSupplierCaptor .getValue ().get ().size ());
161+ assertThat (authHeadersQuotaProjectIsPresent (traceHeaderSupplierCaptor .getValue ().get ()))
162+ .isTrue ();
137163
138164 Mockito .verify (mockOtlpHttpSpanExporter , Mockito .atLeast (1 )).export (Mockito .anyCollection ());
139165
@@ -152,6 +178,90 @@ public void testCustomizerOtlpHttp() {
152178 }
153179 }
154180
181+ @ Test
182+ public void testMetricCustomizerOtlpHttp () {
183+ // Set resource project system property
184+ System .setProperty (
185+ ConfigurableOption .GOOGLE_CLOUD_PROJECT .getSystemProperty (), DUMMY_GCP_RESOURCE_PROJECT_ID );
186+ // Prepare mocks
187+ prepareMockBehaviorForGoogleCredentials ();
188+ OtlpHttpMetricExporter mockOtlpHttpMetricExporter = Mockito .mock (OtlpHttpMetricExporter .class );
189+ OtlpHttpMetricExporterBuilder otlpMetricExporterBuilder = OtlpHttpMetricExporter .builder ();
190+ OtlpHttpMetricExporterBuilder spyOtlpHttpMetricExporterBuilder =
191+ Mockito .spy (otlpMetricExporterBuilder );
192+ Mockito .when (spyOtlpHttpMetricExporterBuilder .build ()).thenReturn (mockOtlpHttpMetricExporter );
193+
194+ Mockito .when (mockOtlpHttpMetricExporter .shutdown ())
195+ .thenReturn (CompletableResultCode .ofSuccess ());
196+ List <MetricData > exportedMetrics = new ArrayList <>();
197+ Mockito .when (mockOtlpHttpMetricExporter .export (Mockito .anyCollection ()))
198+ .thenAnswer (
199+ invocationOnMock -> {
200+ exportedMetrics .addAll (invocationOnMock .getArgument (0 ));
201+ return CompletableResultCode .ofSuccess ();
202+ });
203+ Mockito .when (mockOtlpHttpMetricExporter .toBuilder ())
204+ .thenReturn (spyOtlpHttpMetricExporterBuilder );
205+ // mock the get default aggregation and aggregation temporality - they're required for valid
206+ // metric collection.
207+ Mockito .when (mockOtlpHttpMetricExporter .getDefaultAggregation (Mockito .any ()))
208+ .thenAnswer (
209+ (Answer <Aggregation >)
210+ invocationOnMock -> {
211+ InstrumentType instrumentType = invocationOnMock .getArgument (0 );
212+ return OtlpHttpMetricExporter .getDefault ().getDefaultAggregation (instrumentType );
213+ });
214+ Mockito .when (mockOtlpHttpMetricExporter .getAggregationTemporality (Mockito .any ()))
215+ .thenAnswer (
216+ (Answer <AggregationTemporality >)
217+ invocationOnMock -> {
218+ InstrumentType instrumentType = invocationOnMock .getArgument (0 );
219+ return OtlpHttpMetricExporter .getDefault ()
220+ .getAggregationTemporality (instrumentType );
221+ });
222+
223+ try (MockedStatic <GoogleCredentials > googleCredentialsMockedStatic =
224+ Mockito .mockStatic (GoogleCredentials .class )) {
225+ googleCredentialsMockedStatic
226+ .when (GoogleCredentials ::getApplicationDefault )
227+ .thenReturn (mockedGoogleCredentials );
228+
229+ OpenTelemetrySdk sdk = buildOpenTelemetrySdkWithExporter (mockOtlpHttpMetricExporter );
230+ generateTestMetric (sdk );
231+ CompletableResultCode code = sdk .shutdown ();
232+ CompletableResultCode joinResult = code .join (10 , TimeUnit .SECONDS );
233+ assertTrue (joinResult .isSuccess ());
234+
235+ Mockito .verify (mockOtlpHttpMetricExporter , Mockito .times (1 )).toBuilder ();
236+ Mockito .verify (spyOtlpHttpMetricExporterBuilder , Mockito .times (1 ))
237+ .setHeaders (metricHeaderSupplierCaptor .capture ());
238+ assertEquals (2 , metricHeaderSupplierCaptor .getValue ().get ().size ());
239+ assertThat (authHeadersQuotaProjectIsPresent (metricHeaderSupplierCaptor .getValue ().get ()))
240+ .isTrue ();
241+
242+ Mockito .verify (mockOtlpHttpMetricExporter , Mockito .atLeast (1 ))
243+ .export (Mockito .anyCollection ());
244+
245+ assertThat (exportedMetrics )
246+ .hasSizeGreaterThan (0 )
247+ .allSatisfy (
248+ metricData -> {
249+ assertThat (metricData .getResource ().getAttributes ().asMap ())
250+ .containsEntry (
251+ AttributeKey .stringKey (GCP_USER_PROJECT_ID_KEY ),
252+ DUMMY_GCP_RESOURCE_PROJECT_ID )
253+ .containsEntry (AttributeKey .stringKey ("foo" ), "bar" );
254+ assertThat (metricData .getLongSumData ().getPoints ())
255+ .hasSizeGreaterThan (0 )
256+ .allSatisfy (
257+ longPointData -> {
258+ assertThat (longPointData .getAttributes ().asMap ())
259+ .containsKey (AttributeKey .longKey ("work_loop" ));
260+ });
261+ });
262+ }
263+ }
264+
155265 @ Test
156266 public void testCustomizerOtlpGrpc () {
157267 // Set resource project system property
@@ -163,7 +273,7 @@ public void testCustomizerOtlpGrpc() {
163273 OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder =
164274 Mockito .spy (OtlpGrpcSpanExporter .builder ());
165275 List <SpanData > exportedSpans = new ArrayList <>();
166- configureGrpcMockExporters (
276+ configureGrpcMockSpanExporters (
167277 mockOtlpGrpcSpanExporter , spyOtlpGrpcSpanExporterBuilder , exportedSpans );
168278
169279 try (MockedStatic <GoogleCredentials > googleCredentialsMockedStatic =
@@ -180,9 +290,10 @@ public void testCustomizerOtlpGrpc() {
180290
181291 Mockito .verify (mockOtlpGrpcSpanExporter , Mockito .times (1 )).toBuilder ();
182292 Mockito .verify (spyOtlpGrpcSpanExporterBuilder , Mockito .times (1 ))
183- .setHeaders (headerSupplierCaptor .capture ());
184- assertEquals (2 , headerSupplierCaptor .getValue ().get ().size ());
185- assertThat (authHeadersQuotaProjectIsPresent (headerSupplierCaptor .getValue ().get ())).isTrue ();
293+ .setHeaders (traceHeaderSupplierCaptor .capture ());
294+ assertEquals (2 , traceHeaderSupplierCaptor .getValue ().get ().size ());
295+ assertThat (authHeadersQuotaProjectIsPresent (traceHeaderSupplierCaptor .getValue ().get ()))
296+ .isTrue ();
186297
187298 Mockito .verify (mockOtlpGrpcSpanExporter , Mockito .atLeast (1 )).export (Mockito .anyCollection ());
188299
@@ -256,7 +367,7 @@ public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) throws
256367 OtlpGrpcSpanExporterBuilder spyOtlpGrpcSpanExporterBuilder =
257368 Mockito .spy (OtlpGrpcSpanExporter .builder ());
258369 List <SpanData > exportedSpans = new ArrayList <>();
259- configureGrpcMockExporters (
370+ configureGrpcMockSpanExporters (
260371 mockOtlpGrpcSpanExporter , spyOtlpGrpcSpanExporterBuilder , exportedSpans );
261372
262373 try (MockedStatic <GoogleCredentials > googleCredentialsMockedStatic =
@@ -272,10 +383,10 @@ public void testQuotaProjectBehavior(QuotaProjectIdTestBehavior testCase) throws
272383 CompletableResultCode joinResult = code .join (10 , TimeUnit .SECONDS );
273384 assertTrue (joinResult .isSuccess ());
274385 Mockito .verify (spyOtlpGrpcSpanExporterBuilder , Mockito .times (1 ))
275- .setHeaders (headerSupplierCaptor .capture ());
386+ .setHeaders (traceHeaderSupplierCaptor .capture ());
276387
277388 // assert that the Authorization bearer token header is present
278- Map <String , String > exportHeaders = headerSupplierCaptor .getValue ().get ();
389+ Map <String , String > exportHeaders = traceHeaderSupplierCaptor .getValue ().get ();
279390 assertThat (exportHeaders ).containsEntry ("Authorization" , "Bearer fake" );
280391
281392 if (testCase .getExpectedQuotaProjectInHeader () == null ) {
@@ -362,7 +473,7 @@ private static Stream<Arguments> provideQuotaBehaviorTestCases() {
362473
363474 // Configure necessary behavior on the Grpc mock exporters to work
364475 // TODO: Potential improvement - make this work for Http exporter as well.
365- private static void configureGrpcMockExporters (
476+ private static void configureGrpcMockSpanExporters (
366477 OtlpGrpcSpanExporter mockGrpcExporter ,
367478 OtlpGrpcSpanExporterBuilder spyGrpcExporterBuilder ,
368479 List <SpanData > exportedSpanContainer ) {
@@ -430,25 +541,34 @@ private void prepareMockBehaviorForGoogleCredentials() {
430541 }
431542
432543 private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter (SpanExporter spanExporter ) {
433- return buildOpenTelemetrySdkWithExporter (spanExporter , null , otelProperties );
544+ return buildOpenTelemetrySdkWithExporter (
545+ spanExporter , OtlpHttpMetricExporter .getDefault (), defaultOtelPropertiesSpanExporter );
434546 }
435547
436548 @ SuppressWarnings ("UnusedMethod" )
437- private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter (SpanExporter spanExporter , ImmutableMap <String , String > customOTelProperties ) {
438- return buildOpenTelemetrySdkWithExporter (spanExporter , null , customOTelProperties );
549+ private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter (
550+ SpanExporter spanExporter , ImmutableMap <String , String > customOTelProperties ) {
551+ return buildOpenTelemetrySdkWithExporter (
552+ spanExporter , OtlpHttpMetricExporter .getDefault (), customOTelProperties );
439553 }
440554
441555 @ SuppressWarnings ("UnusedMethod" )
442556 private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter (MetricExporter metricExporter ) {
443- return buildOpenTelemetrySdkWithExporter (null , metricExporter , otelProperties );
557+ return buildOpenTelemetrySdkWithExporter (
558+ OtlpHttpSpanExporter .getDefault (), metricExporter , defaultOtelPropertiesMetricExporter );
444559 }
445560
446561 @ SuppressWarnings ("UnusedMethod" )
447- private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter (MetricExporter metricExporter , ImmutableMap <String , String > customOtelProperties ) {
448- return buildOpenTelemetrySdkWithExporter (null , metricExporter , customOtelProperties );
562+ private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter (
563+ MetricExporter metricExporter , ImmutableMap <String , String > customOtelProperties ) {
564+ return buildOpenTelemetrySdkWithExporter (
565+ OtlpHttpSpanExporter .getDefault (), metricExporter , customOtelProperties );
449566 }
450567
451- private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter (SpanExporter spanExporter , MetricExporter metricExporter , ImmutableMap <String , String > customOtelProperties ) {
568+ private OpenTelemetrySdk buildOpenTelemetrySdkWithExporter (
569+ SpanExporter spanExporter ,
570+ MetricExporter metricExporter ,
571+ ImmutableMap <String , String > customOtelProperties ) {
452572 SpiHelper spiHelper =
453573 SpiHelper .create (GcpAuthAutoConfigurationCustomizerProviderTest .class .getClassLoader ());
454574 AutoConfiguredOpenTelemetrySdkBuilder builder =
@@ -514,6 +634,19 @@ private static void generateTestSpan(OpenTelemetrySdk openTelemetrySdk) {
514634 }
515635 }
516636
637+ private static void generateTestMetric (OpenTelemetrySdk openTelemetrySdk ) {
638+ LongCounter longCounter =
639+ openTelemetrySdk
640+ .getMeter ("test" )
641+ .counterBuilder ("sample" )
642+ .setDescription ("sample counter" )
643+ .setUnit ("1" )
644+ .build ();
645+ long workOutput = busyloop ();
646+ long randomValue = TEST_RANDOM .nextInt (1000 );
647+ longCounter .add (randomValue , Attributes .of (AttributeKey .longKey ("work_loop" ), workOutput ));
648+ }
649+
517650 // loop to simulate work done
518651 private static long busyloop () {
519652 Instant start = Instant .now ();
0 commit comments