• 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.junit.Assert.assertFalse;
21 import static org.junit.Assert.assertTrue;
22 import static org.junit.Assert.fail;
23 
24 import com.google.common.base.Charsets;
25 import io.grpc.gcp.observability.ObservabilityConfig.LogFilter;
26 import io.opencensus.trace.Sampler;
27 import io.opencensus.trace.samplers.Samplers;
28 import java.io.File;
29 import java.io.IOException;
30 import java.nio.file.Files;
31 import java.nio.file.Paths;
32 import java.util.Collections;
33 import java.util.HashSet;
34 import java.util.List;
35 import java.util.Map;
36 import java.util.Set;
37 import java.util.stream.Collectors;
38 import java.util.stream.Stream;
39 import org.junit.Rule;
40 import org.junit.Test;
41 import org.junit.rules.TemporaryFolder;
42 import org.junit.runner.RunWith;
43 import org.junit.runners.JUnit4;
44 
45 @RunWith(JUnit4.class)
46 public class ObservabilityConfigImplTest {
47   private static final String LOG_FILTERS = "{\n"
48       + "    \"project_id\": \"grpc-testing\",\n"
49       + "    \"cloud_logging\": {\n"
50       + "    \"client_rpc_events\": [{\n"
51       + "        \"methods\": [\"*\"],\n"
52       + "        \"max_metadata_bytes\": 4096\n"
53       + "    }"
54       + "    ],\n"
55       + "    \"server_rpc_events\": [{\n"
56       + "        \"methods\": [\"*\"],\n"
57       + "        \"max_metadata_bytes\": 32,\n"
58       + "        \"max_message_bytes\": 64\n"
59       + "    }"
60       + "    ]\n"
61       + "    }\n"
62       + "}";
63 
64   private static final String CLIENT_LOG_FILTERS = "{\n"
65       + "    \"project_id\": \"grpc-testing\",\n"
66       + "    \"cloud_logging\": {\n"
67       + "    \"client_rpc_events\": [{\n"
68       + "        \"methods\": [\"*\"],\n"
69       + "        \"max_metadata_bytes\": 4096,\n"
70       + "        \"max_message_bytes\": 2048\n"
71       + "    },"
72       + "   {\n"
73       + "        \"methods\": [\"service1/Method2\", \"Service2/*\"],\n"
74       + "        \"exclude\": true\n"
75       + "    }"
76       + "    ]\n"
77       + "    }\n"
78       + "}";
79 
80   private static final String SERVER_LOG_FILTERS = "{\n"
81           + "    \"project_id\": \"grpc-testing\",\n"
82           + "    \"cloud_logging\": {\n"
83           + "    \"server_rpc_events\": [{\n"
84           + "        \"methods\": [\"service1/method4\", \"service2/method234\"],\n"
85           + "        \"max_metadata_bytes\": 32,\n"
86           + "        \"max_message_bytes\": 64\n"
87           + "    },"
88           + "   {\n"
89           + "        \"methods\": [\"service4/*\", \"Service2/*\"],\n"
90           + "        \"exclude\": true\n"
91           + "    }"
92           + "    ]\n"
93           + "    }\n"
94           + "}";
95 
96   private static final String VALID_LOG_FILTERS = "{\n"
97       + "    \"project_id\": \"grpc-testing\",\n"
98       + "    \"cloud_logging\": {\n"
99       + "    \"server_rpc_events\": [{\n"
100       + "        \"methods\": [\"service.Service1/*\", \"service2.Service4/method4\"],\n"
101       + "        \"max_metadata_bytes\": 16,\n"
102       + "        \"max_message_bytes\": 64\n"
103       + "    }"
104       + "    ]\n"
105       + "    }\n"
106       + "}";
107 
108 
109   private static final String PROJECT_ID = "{\n"
110       + "    \"project_id\": \"grpc-testing\",\n"
111       + "    \"cloud_logging\": {},\n"
112       + "    \"project_id\": \"grpc-testing\"\n"
113       + "}";
114 
115   private static final String EMPTY_CONFIG = "{}";
116 
117   private static final String ENABLE_CLOUD_MONITORING_AND_TRACING = "{\n"
118       + "    \"project_id\": \"grpc-testing\",\n"
119       + "    \"cloud_monitoring\": {},\n"
120       + "    \"cloud_trace\": {}\n"
121       + "}";
122 
123   private static final String ENABLE_CLOUD_MONITORING = "{\n"
124       + "    \"project_id\": \"grpc-testing\",\n"
125       + "    \"cloud_monitoring\": {}\n"
126       + "}";
127 
128   private static final String ENABLE_CLOUD_TRACE = "{\n"
129       + "    \"project_id\": \"grpc-testing\",\n"
130       + "    \"cloud_trace\": {}\n"
131       + "}";
132 
133   private static final String TRACING_ALWAYS_SAMPLER = "{\n"
134       + "    \"project_id\": \"grpc-testing\",\n"
135       + "    \"cloud_trace\": {\n"
136       + "      \"sampling_rate\": 1.00\n"
137       + "    }\n"
138       + "}";
139 
140   private static final String TRACING_NEVER_SAMPLER = "{\n"
141       + "    \"project_id\": \"grpc-testing\",\n"
142       + "    \"cloud_trace\": {\n"
143       + "      \"sampling_rate\": 0.00\n"
144       + "    }\n"
145       + "}";
146 
147   private static final String TRACING_PROBABILISTIC_SAMPLER = "{\n"
148       + "    \"project_id\": \"grpc-testing\",\n"
149       + "    \"cloud_trace\": {\n"
150       + "      \"sampling_rate\": 0.75\n"
151       + "    }\n"
152       + "}";
153 
154   private static final String TRACING_DEFAULT_SAMPLER = "{\n"
155       + "    \"project_id\": \"grpc-testing\",\n"
156       + "    \"cloud_trace\": {}\n"
157       + "}";
158 
159   private static final String GLOBAL_TRACING_BAD_PROBABILISTIC_SAMPLER = "{\n"
160       + "    \"project_id\": \"grpc-testing\",\n"
161       + "    \"cloud_trace\": {\n"
162       + "      \"sampling_rate\": -0.75\n"
163       + "    }\n"
164       + "}";
165 
166   private static final String CUSTOM_TAGS = "{\n"
167           + "    \"project_id\": \"grpc-testing\",\n"
168           + "    \"cloud_logging\": {},\n"
169           + "    \"labels\": {\n"
170           + "      \"SOURCE_VERSION\" : \"J2e1Cf\",\n"
171           + "      \"SERVICE_NAME\" : \"payment-service\",\n"
172           + "      \"ENTRYPOINT_SCRIPT\" : \"entrypoint.sh\"\n"
173           + "    }\n"
174           + "}";
175 
176   private static final String BAD_CUSTOM_TAGS =
177       "{\n"
178           + "    \"project_id\": \"grpc-testing\",\n"
179           + "    \"cloud_monitoring\": {},\n"
180           + "    \"labels\": {\n"
181           + "      \"SOURCE_VERSION\" : \"J2e1Cf\",\n"
182           + "      \"SERVICE_NAME\" : { \"SUB_SERVICE_NAME\" : \"payment-service\"},\n"
183           + "      \"ENTRYPOINT_SCRIPT\" : \"entrypoint.sh\"\n"
184           + "    }\n"
185           + "}";
186 
187   private static final String LOG_FILTER_GLOBAL_EXCLUDE =
188       "{\n"
189           + "    \"project_id\": \"grpc-testing\",\n"
190           + "    \"cloud_logging\": {\n"
191           + "    \"client_rpc_events\": [{\n"
192           + "        \"methods\": [\"service1/Method2\", \"*\"],\n"
193           + "        \"max_metadata_bytes\": 20,\n"
194           + "        \"max_message_bytes\": 15,\n"
195           + "        \"exclude\": true\n"
196           + "    }"
197           + "    ]\n"
198           + "    }\n"
199           + "}";
200 
201   private static final String LOG_FILTER_INVALID_METHOD =
202       "{\n"
203           + "    \"project_id\": \"grpc-testing\",\n"
204           + "    \"cloud_logging\": {\n"
205           + "    \"client_rpc_events\": [{\n"
206           + "        \"methods\": [\"s*&%ervice1/Method2\", \"*\"],\n"
207           + "        \"max_metadata_bytes\": 20\n"
208           + "    }"
209           + "    ]\n"
210           + "    }\n"
211           + "}";
212 
213   ObservabilityConfigImpl observabilityConfig = new ObservabilityConfigImpl();
214 
215   @Rule public TemporaryFolder tempFolder = new TemporaryFolder();
216 
217   @Test
nullConfig()218   public void nullConfig() throws IOException {
219     try {
220       observabilityConfig.parse(null);
221       fail("exception expected!");
222     } catch (IllegalArgumentException iae) {
223       assertThat(iae.getMessage()).isEqualTo("GRPC_GCP_OBSERVABILITY_CONFIG value is null!");
224     }
225   }
226 
227   @Test
emptyConfig()228   public void emptyConfig() throws IOException {
229     observabilityConfig.parse(EMPTY_CONFIG);
230     assertFalse(observabilityConfig.isEnableCloudLogging());
231     assertFalse(observabilityConfig.isEnableCloudMonitoring());
232     assertFalse(observabilityConfig.isEnableCloudTracing());
233     assertThat(observabilityConfig.getClientLogFilters()).isEmpty();
234     assertThat(observabilityConfig.getServerLogFilters()).isEmpty();
235     assertThat(observabilityConfig.getSampler()).isNull();
236     assertThat(observabilityConfig.getProjectId()).isNull();
237     assertThat(observabilityConfig.getCustomTags()).isEmpty();
238   }
239 
240   @Test
emptyConfigFile()241   public void emptyConfigFile() throws IOException {
242     File configFile = tempFolder.newFile();
243     try {
244       observabilityConfig.parseFile(configFile.getAbsolutePath());
245       fail("exception expected!");
246     } catch (IllegalArgumentException iae) {
247       assertThat(iae.getMessage()).isEqualTo(
248           "GRPC_GCP_OBSERVABILITY_CONFIG_FILE is empty!");
249     }
250   }
251 
252   @Test
setProjectId()253   public void setProjectId() throws IOException {
254     observabilityConfig.parse(PROJECT_ID);
255     assertTrue(observabilityConfig.isEnableCloudLogging());
256     assertThat(observabilityConfig.getProjectId()).isEqualTo("grpc-testing");
257   }
258 
259   @Test
logFilters()260   public void logFilters() throws IOException {
261     observabilityConfig.parse(LOG_FILTERS);
262     assertTrue(observabilityConfig.isEnableCloudLogging());
263     assertThat(observabilityConfig.getProjectId()).isEqualTo("grpc-testing");
264 
265     List<LogFilter> clientLogFilters = observabilityConfig.getClientLogFilters();
266     assertThat(clientLogFilters).hasSize(1);
267     assertThat(clientLogFilters.get(0).headerBytes).isEqualTo(4096);
268     assertThat(clientLogFilters.get(0).messageBytes).isEqualTo(0);
269     assertThat(clientLogFilters.get(0).excludePattern).isFalse();
270     assertThat(clientLogFilters.get(0).matchAll).isTrue();
271     assertThat(clientLogFilters.get(0).services).isEmpty();
272     assertThat(clientLogFilters.get(0).methods).isEmpty();
273 
274     List<LogFilter> serverLogFilters = observabilityConfig.getServerLogFilters();
275     assertThat(serverLogFilters).hasSize(1);
276     assertThat(serverLogFilters.get(0).headerBytes).isEqualTo(32);
277     assertThat(serverLogFilters.get(0).messageBytes).isEqualTo(64);
278     assertThat(serverLogFilters.get(0).excludePattern).isFalse();
279     assertThat(serverLogFilters.get(0).matchAll).isTrue();
280     assertThat(serverLogFilters.get(0).services).isEmpty();
281     assertThat(serverLogFilters.get(0).methods).isEmpty();
282   }
283 
284   @Test
setClientLogFilters()285   public void setClientLogFilters() throws IOException {
286     observabilityConfig.parse(CLIENT_LOG_FILTERS);
287     assertTrue(observabilityConfig.isEnableCloudLogging());
288     assertThat(observabilityConfig.getProjectId()).isEqualTo("grpc-testing");
289     List<LogFilter> logFilterList = observabilityConfig.getClientLogFilters();
290     assertThat(logFilterList).hasSize(2);
291     assertThat(logFilterList.get(0).headerBytes).isEqualTo(4096);
292     assertThat(logFilterList.get(0).messageBytes).isEqualTo(2048);
293     assertThat(logFilterList.get(0).excludePattern).isFalse();
294     assertThat(logFilterList.get(0).matchAll).isTrue();
295     assertThat(logFilterList.get(0).services).isEmpty();
296     assertThat(logFilterList.get(0).methods).isEmpty();
297 
298     assertThat(logFilterList.get(1).headerBytes).isEqualTo(0);
299     assertThat(logFilterList.get(1).messageBytes).isEqualTo(0);
300     assertThat(logFilterList.get(1).excludePattern).isTrue();
301     assertThat(logFilterList.get(1).matchAll).isFalse();
302     assertThat(logFilterList.get(1).services).isEqualTo(Collections.singleton("Service2"));
303     assertThat(logFilterList.get(1).methods)
304         .isEqualTo(Collections.singleton("service1/Method2"));
305   }
306 
307   @Test
setServerLogFilters()308   public void setServerLogFilters() throws IOException {
309     Set<String> expectedMethods = Stream.of("service1/method4", "service2/method234")
310         .collect(Collectors.toCollection(HashSet::new));
311     observabilityConfig.parse(SERVER_LOG_FILTERS);
312     assertTrue(observabilityConfig.isEnableCloudLogging());
313     List<LogFilter> logFilterList = observabilityConfig.getServerLogFilters();
314     assertThat(logFilterList).hasSize(2);
315     assertThat(logFilterList.get(0).headerBytes).isEqualTo(32);
316     assertThat(logFilterList.get(0).messageBytes).isEqualTo(64);
317     assertThat(logFilterList.get(0).excludePattern).isFalse();
318     assertThat(logFilterList.get(0).matchAll).isFalse();
319     assertThat(logFilterList.get(0).services).isEmpty();
320     assertThat(logFilterList.get(0).methods)
321         .isEqualTo(expectedMethods);
322 
323     Set<String> expectedServices = Stream.of("service4", "Service2")
324         .collect(Collectors.toCollection(HashSet::new));
325     assertThat(logFilterList.get(1).headerBytes).isEqualTo(0);
326     assertThat(logFilterList.get(1).messageBytes).isEqualTo(0);
327     assertThat(logFilterList.get(1).excludePattern).isTrue();
328     assertThat(logFilterList.get(1).matchAll).isFalse();
329     assertThat(logFilterList.get(1).services).isEqualTo(expectedServices);
330     assertThat(logFilterList.get(1).methods).isEmpty();
331   }
332 
333   @Test
enableCloudMonitoring()334   public void enableCloudMonitoring() throws IOException {
335     observabilityConfig.parse(ENABLE_CLOUD_MONITORING);
336     assertTrue(observabilityConfig.isEnableCloudMonitoring());
337   }
338 
339   @Test
enableCloudTracing()340   public void enableCloudTracing() throws IOException {
341     observabilityConfig.parse(ENABLE_CLOUD_TRACE);
342     assertTrue(observabilityConfig.isEnableCloudTracing());
343   }
344 
345   @Test
enableCloudMonitoringAndTracing()346   public void enableCloudMonitoringAndTracing() throws IOException {
347     observabilityConfig.parse(ENABLE_CLOUD_MONITORING_AND_TRACING);
348     assertFalse(observabilityConfig.isEnableCloudLogging());
349     assertTrue(observabilityConfig.isEnableCloudMonitoring());
350     assertTrue(observabilityConfig.isEnableCloudTracing());
351   }
352 
353   @Test
alwaysSampler()354   public void alwaysSampler() throws IOException {
355     observabilityConfig.parse(TRACING_ALWAYS_SAMPLER);
356     assertTrue(observabilityConfig.isEnableCloudTracing());
357     Sampler sampler = observabilityConfig.getSampler();
358     assertThat(sampler).isNotNull();
359     assertThat(sampler).isEqualTo(Samplers.alwaysSample());
360   }
361 
362   @Test
neverSampler()363   public void neverSampler() throws IOException {
364     observabilityConfig.parse(TRACING_NEVER_SAMPLER);
365     assertTrue(observabilityConfig.isEnableCloudTracing());
366     Sampler sampler = observabilityConfig.getSampler();
367     assertThat(sampler).isNotNull();
368     assertThat(sampler).isEqualTo(Samplers.probabilitySampler(0.0));
369   }
370 
371   @Test
probabilisticSampler()372   public void probabilisticSampler() throws IOException {
373     observabilityConfig.parse(TRACING_PROBABILISTIC_SAMPLER);
374     assertTrue(observabilityConfig.isEnableCloudTracing());
375     Sampler sampler = observabilityConfig.getSampler();
376     assertThat(sampler).isNotNull();
377     assertThat(sampler).isEqualTo(Samplers.probabilitySampler(0.75));
378   }
379 
380   @Test
defaultSampler()381   public void defaultSampler() throws IOException {
382     observabilityConfig.parse(TRACING_DEFAULT_SAMPLER);
383     assertTrue(observabilityConfig.isEnableCloudTracing());
384     Sampler sampler = observabilityConfig.getSampler();
385     assertThat(sampler).isNotNull();
386     assertThat(sampler).isEqualTo(Samplers.probabilitySampler(0.00));
387   }
388 
389   @Test
badProbabilisticSampler_error()390   public void badProbabilisticSampler_error() throws IOException {
391     try {
392       observabilityConfig.parse(GLOBAL_TRACING_BAD_PROBABILISTIC_SAMPLER);
393       fail("exception expected!");
394     } catch (IllegalArgumentException iae) {
395       assertThat(iae.getMessage()).isEqualTo(
396           "'sampling_rate' needs to be between [0.0, 1.0]");
397     }
398   }
399 
400   @Test
configFileLogFilters()401   public void configFileLogFilters() throws Exception {
402     File configFile = tempFolder.newFile();
403     Files.write(
404         Paths.get(configFile.getAbsolutePath()), CLIENT_LOG_FILTERS.getBytes(Charsets.US_ASCII));
405     observabilityConfig.parseFile(configFile.getAbsolutePath());
406     assertTrue(observabilityConfig.isEnableCloudLogging());
407     assertThat(observabilityConfig.getProjectId()).isEqualTo("grpc-testing");
408     List<LogFilter> logFilters = observabilityConfig.getClientLogFilters();
409     assertThat(logFilters).hasSize(2);
410     assertThat(logFilters.get(0).headerBytes).isEqualTo(4096);
411     assertThat(logFilters.get(0).messageBytes).isEqualTo(2048);
412     assertThat(logFilters.get(1).headerBytes).isEqualTo(0);
413     assertThat(logFilters.get(1).messageBytes).isEqualTo(0);
414 
415     assertThat(logFilters).hasSize(2);
416     assertThat(logFilters.get(0).headerBytes).isEqualTo(4096);
417     assertThat(logFilters.get(0).messageBytes).isEqualTo(2048);
418     assertThat(logFilters.get(0).excludePattern).isFalse();
419     assertThat(logFilters.get(0).matchAll).isTrue();
420     assertThat(logFilters.get(0).services).isEmpty();
421     assertThat(logFilters.get(0).methods).isEmpty();
422 
423     assertThat(logFilters.get(1).headerBytes).isEqualTo(0);
424     assertThat(logFilters.get(1).messageBytes).isEqualTo(0);
425     assertThat(logFilters.get(1).excludePattern).isTrue();
426     assertThat(logFilters.get(1).matchAll).isFalse();
427     assertThat(logFilters.get(1).services).isEqualTo(Collections.singleton("Service2"));
428     assertThat(logFilters.get(1).methods)
429         .isEqualTo(Collections.singleton("service1/Method2"));
430   }
431 
432   @Test
customTags()433   public void customTags() throws IOException {
434     observabilityConfig.parse(CUSTOM_TAGS);
435     assertTrue(observabilityConfig.isEnableCloudLogging());
436     Map<String, String> customTags = observabilityConfig.getCustomTags();
437     assertThat(customTags).hasSize(3);
438     assertThat(customTags).containsEntry("SOURCE_VERSION", "J2e1Cf");
439     assertThat(customTags).containsEntry("SERVICE_NAME", "payment-service");
440     assertThat(customTags).containsEntry("ENTRYPOINT_SCRIPT", "entrypoint.sh");
441   }
442 
443   @Test
badCustomTags()444   public void badCustomTags() throws IOException {
445     try {
446       observabilityConfig.parse(BAD_CUSTOM_TAGS);
447       fail("exception expected!");
448     } catch (IllegalArgumentException iae) {
449       assertThat(iae.getMessage()).isEqualTo(
450               "'labels' needs to be a map of <string, string>");
451     }
452   }
453 
454   @Test
globalLogFilterExclude()455   public void globalLogFilterExclude() throws IOException {
456     try {
457       observabilityConfig.parse(LOG_FILTER_GLOBAL_EXCLUDE);
458       fail("exception expected!");
459     } catch (IllegalArgumentException iae) {
460       assertThat(iae.getMessage()).isEqualTo(
461           "cannot have 'exclude' and '*' wildcard in the same filter");
462     }
463   }
464 
465   @Test
logFilterInvalidMethod()466   public void logFilterInvalidMethod() throws IOException {
467     try {
468       observabilityConfig.parse(LOG_FILTER_INVALID_METHOD);
469       fail("exception expected!");
470     } catch (IllegalArgumentException iae) {
471       assertThat(iae.getMessage()).contains(
472           "invalid service or method filter");
473     }
474   }
475 
476   @Test
validLogFilter()477   public void validLogFilter() throws Exception {
478     observabilityConfig.parse(VALID_LOG_FILTERS);
479     assertTrue(observabilityConfig.isEnableCloudLogging());
480     assertThat(observabilityConfig.getProjectId()).isEqualTo("grpc-testing");
481     List<LogFilter> logFilterList = observabilityConfig.getServerLogFilters();
482     assertThat(logFilterList).hasSize(1);
483     assertThat(logFilterList.get(0).headerBytes).isEqualTo(16);
484     assertThat(logFilterList.get(0).messageBytes).isEqualTo(64);
485     assertThat(logFilterList.get(0).excludePattern).isFalse();
486     assertThat(logFilterList.get(0).matchAll).isFalse();
487     assertThat(logFilterList.get(0).services).isEqualTo(Collections.singleton("service.Service1"));
488     assertThat(logFilterList.get(0).methods)
489         .isEqualTo(Collections.singleton("service2.Service4/method4"));
490   }
491 }
492