|
21 | 21 | import datadog.trace.api.Config; |
22 | 22 | import datadog.trace.api.featureflag.FeatureFlaggingGateway; |
23 | 23 | import datadog.trace.api.featureflag.ufc.v1.ServerConfiguration; |
| 24 | +import java.io.ByteArrayOutputStream; |
24 | 25 | import java.io.IOException; |
25 | 26 | import java.net.HttpURLConnection; |
26 | 27 | import java.net.SocketTimeoutException; |
27 | 28 | import java.util.ArrayList; |
| 29 | +import java.util.Arrays; |
28 | 30 | import java.util.List; |
29 | 31 | import java.util.concurrent.BlockingQueue; |
30 | 32 | import java.util.concurrent.CountDownLatch; |
|
36 | 38 | import java.util.concurrent.ScheduledExecutorService; |
37 | 39 | import java.util.concurrent.TimeUnit; |
38 | 40 | import java.util.concurrent.atomic.AtomicInteger; |
| 41 | +import java.util.zip.GZIPOutputStream; |
39 | 42 | import okhttp3.Call; |
40 | 43 | import okhttp3.HttpUrl; |
41 | 44 | import okhttp3.OkHttpClient; |
@@ -157,13 +160,67 @@ void realHttpClientSendsAgentlessHeadersAndReadsResponse() throws Exception { |
157 | 160 | assertEquals("test-api-key", server.getLastRequest().getHeader("DD-API-KEY")); |
158 | 161 | assertEquals("etag-a", server.getLastRequest().getHeader("If-None-Match")); |
159 | 162 | assertEquals("java", server.getLastRequest().getHeader("Datadog-Meta-Lang")); |
| 163 | + assertEquals("gzip", server.getLastRequest().getHeader("Accept-Encoding")); |
160 | 164 | } finally { |
161 | 165 | httpClient.dispatcher().executorService().shutdownNow(); |
162 | 166 | httpClient.connectionPool().evictAll(); |
163 | 167 | } |
164 | 168 | } |
165 | 169 | } |
166 | 170 |
|
| 171 | + @Test |
| 172 | + void handlesGzipAndKeepsLastKnownGoodWhenNextResponseIsTruncated() throws Exception { |
| 173 | + final byte[] compressedConfig = gzip(emptyConfig()); |
| 174 | + final byte[] truncatedConfig = Arrays.copyOf(compressedConfig, compressedConfig.length - 8); |
| 175 | + final AtomicInteger responses = new AtomicInteger(); |
| 176 | + try (JavaTestHttpServer server = |
| 177 | + JavaTestHttpServer.httpServer( |
| 178 | + s -> |
| 179 | + s.handlers( |
| 180 | + h -> |
| 181 | + h.get( |
| 182 | + CONFIG_PATH, |
| 183 | + api -> { |
| 184 | + final boolean firstResponse = responses.getAndIncrement() == 0; |
| 185 | + final byte[] body = |
| 186 | + firstResponse ? compressedConfig : truncatedConfig; |
| 187 | + api.getResponse() |
| 188 | + .addHeader("Content-Encoding", "gzip") |
| 189 | + .addHeader("ETag", firstResponse ? "etag-good" : "etag-bad") |
| 190 | + .sendWithType("application/json", body); |
| 191 | + })))) { |
| 192 | + final HttpUrl endpoint = HttpUrl.get(server.getAddress().resolve(CONFIG_PATH)); |
| 193 | + final OkHttpClient httpClient = new OkHttpClient.Builder().build(); |
| 194 | + final AgentlessConfigurationSource service = |
| 195 | + new AgentlessConfigurationSource( |
| 196 | + endpoint, |
| 197 | + config(), |
| 198 | + 30_000, |
| 199 | + new AgentlessConfigurationSource.OkHttpUfcHttpClient(httpClient), |
| 200 | + Executors.newSingleThreadScheduledExecutor(), |
| 201 | + delay -> {}, |
| 202 | + () -> 1.0); |
| 203 | + final ArgumentCaptor<ServerConfiguration> configuration = |
| 204 | + ArgumentCaptor.forClass(ServerConfiguration.class); |
| 205 | + FeatureFlaggingGateway.addConfigListener(listener); |
| 206 | + |
| 207 | + try { |
| 208 | + assertTrue(service.pollOnce()); |
| 209 | + assertFalse(service.pollOnce()); |
| 210 | + |
| 211 | + verify(listener).accept(configuration.capture()); |
| 212 | + assertEquals("Staging", configuration.getValue().environment.name); |
| 213 | + assertEquals(4, responses.get()); |
| 214 | + assertEquals("gzip", server.getLastRequest().getHeader("Accept-Encoding")); |
| 215 | + assertEquals("etag-good", server.getLastRequest().getHeader("If-None-Match")); |
| 216 | + } finally { |
| 217 | + service.close(); |
| 218 | + httpClient.dispatcher().executorService().shutdownNow(); |
| 219 | + httpClient.connectionPool().evictAll(); |
| 220 | + } |
| 221 | + } |
| 222 | + } |
| 223 | + |
167 | 224 | @Test |
168 | 225 | void downloadsAndAppliesLargeUfcWithoutPayloadLimit() throws Exception { |
169 | 226 | final int flagCount = 5_000; |
@@ -378,22 +435,27 @@ void appliesAcceptedJsonApiUfcThroughGatewayAndSendsApiKey() throws Exception { |
378 | 435 | } |
379 | 436 |
|
380 | 437 | @Test |
381 | | - void appliesRawUfcFromConfiguredCompatibilityEndpoint() throws Exception { |
382 | | - final FakeClient client = new FakeClient(response(200, "etag-a", emptyConfigAttributes())); |
| 438 | + void customEndpointRequiresJsonApiAndKeepsLastKnownGoodOnRawUfc() throws Exception { |
| 439 | + final FakeClient client = |
| 440 | + new FakeClient( |
| 441 | + response(200, "etag-good", emptyConfig()), |
| 442 | + response(200, "etag-raw", emptyConfigAttributes())); |
383 | 443 | final Config config = config(); |
384 | 444 | lenient() |
385 | 445 | .when(config.getFeatureFlaggingConfigurationSourceAgentlessBaseUrl()) |
386 | | - .thenReturn("http://compatibility-backend/custom/ufc"); |
| 446 | + .thenReturn("http://custom-backend/custom/ufc"); |
387 | 447 | final AgentlessConfigurationSource service = service(client, config); |
388 | 448 | final ArgumentCaptor<ServerConfiguration> configuration = |
389 | 449 | ArgumentCaptor.forClass(ServerConfiguration.class); |
390 | 450 | FeatureFlaggingGateway.addConfigListener(listener); |
391 | 451 |
|
392 | 452 | assertTrue(service.pollOnce()); |
| 453 | + assertFalse(service.pollOnce()); |
393 | 454 |
|
394 | 455 | verify(listener).accept(configuration.capture()); |
395 | 456 | assertEquals("Staging", configuration.getValue().environment.name); |
396 | | - assertTrue(configuration.getValue().flags.isEmpty()); |
| 457 | + assertNull(client.requests.get(0).etag); |
| 458 | + assertEquals("etag-good", client.requests.get(1).etag); |
397 | 459 | } |
398 | 460 |
|
399 | 461 | @Test |
@@ -1032,6 +1094,14 @@ private static String largeConfig(final int flagCount) { |
1032 | 1094 | return jsonApiResponse("universal-flag-configuration", json.append("}}").toString()); |
1033 | 1095 | } |
1034 | 1096 |
|
| 1097 | + private static byte[] gzip(final String value) throws IOException { |
| 1098 | + final ByteArrayOutputStream output = new ByteArrayOutputStream(); |
| 1099 | + try (GZIPOutputStream gzip = new GZIPOutputStream(output)) { |
| 1100 | + gzip.write(value.getBytes(UTF_8)); |
| 1101 | + } |
| 1102 | + return output.toByteArray(); |
| 1103 | + } |
| 1104 | + |
1035 | 1105 | private static void awaitCalls(final FakeClient client, final int count) throws Exception { |
1036 | 1106 | for (int i = 0; i < 100; i++) { |
1037 | 1107 | if (client.calls.get() >= count) { |
|
0 commit comments