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