• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2022 The gRPC Authors
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package io.grpc.gcp.observability;
18 
19 import static com.google.common.truth.Truth.assertThat;
20 import static org.mockito.Mockito.mock;
21 import static org.mockito.Mockito.when;
22 
23 import com.google.cloud.monitoring.v3.MetricServiceClient;
24 import com.google.cloud.monitoring.v3.MetricServiceClient.ListTimeSeriesPagedResponse;
25 import com.google.monitoring.v3.ListTimeSeriesRequest;
26 import com.google.monitoring.v3.ProjectName;
27 import com.google.monitoring.v3.TimeInterval;
28 import com.google.monitoring.v3.TimeSeries;
29 import com.google.protobuf.util.Timestamps;
30 import io.grpc.ManagedChannelBuilder;
31 import io.grpc.Server;
32 import io.grpc.ServerBuilder;
33 import io.grpc.StaticTestingClassLoader;
34 import io.grpc.gcp.observability.interceptors.InternalLoggingChannelInterceptor;
35 import io.grpc.gcp.observability.interceptors.InternalLoggingServerInterceptor;
36 import io.grpc.gcp.observability.logging.GcpLogSink;
37 import io.grpc.gcp.observability.logging.Sink;
38 import io.grpc.testing.GrpcCleanupRule;
39 import io.grpc.testing.protobuf.SimpleServiceGrpc;
40 import java.io.IOException;
41 import java.util.Collections;
42 import java.util.Map;
43 import java.util.concurrent.TimeUnit;
44 import java.util.regex.Pattern;
45 import org.junit.ClassRule;
46 import org.junit.Ignore;
47 import org.junit.Test;
48 import org.junit.runner.RunWith;
49 import org.junit.runners.JUnit4;
50 
51 @RunWith(JUnit4.class)
52 public class MetricsTest {
53 
54   @ClassRule
55   public static final GrpcCleanupRule cleanupRule = new GrpcCleanupRule();
56 
57   private static final String PROJECT_ID = "PROJECT";
58   private static final String TEST_CLIENT_METHOD = "grpc.testing.SimpleService/UnaryRpc";
59   private static final String CUSTOM_TAG_KEY = "Version";
60   private static final String CUSTOM_TAG_VALUE =
61       String.format("C67J9A-%s", String.valueOf(System.currentTimeMillis()));
62   private static final Map<String, String> CUSTOM_TAGS = Collections.singletonMap(CUSTOM_TAG_KEY,
63       CUSTOM_TAG_VALUE);
64 
65   private final StaticTestingClassLoader classLoader =
66       new StaticTestingClassLoader(getClass().getClassLoader(),
67           Pattern.compile("io\\.grpc\\..*|io\\.opencensus\\..*"));
68 
69   /**
70    * End to end cloud monitoring test.
71    *
72    * <p>Ignoring test, because it calls external Cloud Monitoring APIs. To test cloud monitoring
73    * setup locally,
74    * 1. Set up Cloud auth credentials
75    * 2. Assign permissions to service account to write metrics to project specified by variable
76    * PROJECT_ID
77    * 3. Comment @Ignore annotation
78    * 4. This test is expected to pass when ran with above setup. This has been verified manually.
79    */
80   @Ignore
81   @Test
testMetricsExporter()82   public void testMetricsExporter() throws Exception {
83     Class<?> runnable =
84         classLoader.loadClass(MetricsTest.StaticTestingClassTestMetricsExporter.class.getName());
85     ((Runnable) runnable.getDeclaredConstructor().newInstance()).run();
86   }
87 
88   public static final class StaticTestingClassTestMetricsExporter implements Runnable {
89 
90     @Override
run()91     public void run() {
92       Sink mockSink = mock(GcpLogSink.class);
93       ObservabilityConfig mockConfig = mock(ObservabilityConfig.class);
94       InternalLoggingChannelInterceptor.Factory mockChannelInterceptorFactory =
95           mock(InternalLoggingChannelInterceptor.Factory.class);
96       InternalLoggingServerInterceptor.Factory mockServerInterceptorFactory =
97           mock(InternalLoggingServerInterceptor.Factory.class);
98 
99       when(mockConfig.isEnableCloudMonitoring()).thenReturn(true);
100       when(mockConfig.getProjectId()).thenReturn(PROJECT_ID);
101 
102       try {
103         GcpObservability observability =
104             GcpObservability.grpcInit(
105                 mockSink, mockConfig, mockChannelInterceptorFactory, mockServerInterceptorFactory);
106         observability.registerStackDriverExporter(PROJECT_ID, CUSTOM_TAGS);
107 
108         Server server =
109             ServerBuilder.forPort(0)
110                 .addService(new ObservabilityTestHelper.SimpleServiceImpl())
111                 .build()
112                 .start();
113         int port = cleanupRule.register(server).getPort();
114         SimpleServiceGrpc.SimpleServiceBlockingStub stub =
115             SimpleServiceGrpc.newBlockingStub(
116                 cleanupRule.register(
117                     ManagedChannelBuilder.forAddress("localhost", port).usePlaintext().build()));
118         assertThat(ObservabilityTestHelper.makeUnaryRpcViaClientStub("buddy", stub))
119             .isEqualTo("Hello buddy");
120         // Adding sleep to ensure metrics are exported before querying cloud monitoring backend
121         TimeUnit.SECONDS.sleep(40);
122 
123         // This checks Cloud monitoring for the new metrics that was just exported.
124         MetricServiceClient metricServiceClient = MetricServiceClient.create();
125         // Restrict time to last 1 minute
126         long startMillis = System.currentTimeMillis() - ((60 * 1) * 1000);
127         TimeInterval interval =
128             TimeInterval.newBuilder()
129                 .setStartTime(Timestamps.fromMillis(startMillis))
130                 .setEndTime(Timestamps.fromMillis(System.currentTimeMillis()))
131                 .build();
132         // Timeseries data
133         String metricsFilter =
134             String.format(
135                 "metric.type=\"custom.googleapis.com/opencensus/grpc.io/client/completed_rpcs\""
136                     + " AND metric.labels.grpc_client_method=\"%s\""
137                     + " AND metric.labels.%s=%s",
138                 TEST_CLIENT_METHOD, CUSTOM_TAG_KEY, CUSTOM_TAG_VALUE);
139         ListTimeSeriesRequest metricsRequest =
140             ListTimeSeriesRequest.newBuilder()
141                 .setName(ProjectName.of(PROJECT_ID).toString())
142                 .setFilter(metricsFilter)
143                 .setInterval(interval)
144                 .build();
145         ListTimeSeriesPagedResponse response = metricServiceClient.listTimeSeries(metricsRequest);
146         assertThat(response.iterateAll()).isNotEmpty();
147         for (TimeSeries ts : response.iterateAll()) {
148           assertThat(ts.getMetric().getLabelsMap().get("grpc_client_method"))
149               .isEqualTo(TEST_CLIENT_METHOD);
150           assertThat(ts.getMetric().getLabelsMap().get("grpc_client_status")).isEqualTo("OK");
151           assertThat(ts.getPoints(0).getValue().getInt64Value()).isEqualTo(1);
152         }
153         observability.close();
154       } catch (IOException | InterruptedException e) {
155         throw new AssertionError("Exception while testing metrics", e);
156       }
157     }
158   }
159 }
160