diff --git a/.generator/src/generator/cli.py b/.generator/src/generator/cli.py index d3f91d0a70b..f5792cc7e74 100644 --- a/.generator/src/generator/cli.py +++ b/.generator/src/generator/cli.py @@ -87,6 +87,7 @@ def cli(specs, output): "PaginationIterator.java": env.get_template("PaginationIterator.j2"), "UnparsedObject.java": env.get_template("UnparsedObject.j2"), "ZstdEncoder.java": env.get_template("ZstdEncoder.j2"), + "RetryConfig.java": env.get_template("RetryConfig.j2"), } auth_files = { diff --git a/.generator/src/generator/templates/ApiClient.j2 b/.generator/src/generator/templates/ApiClient.j2 index 16772051087..f982e361f72 100644 --- a/.generator/src/generator/templates/ApiClient.j2 +++ b/.generator/src/generator/templates/ApiClient.j2 @@ -78,6 +78,7 @@ import {{ common_package_name }}.ApiException; import {{ common_package_name }}.ApiResponse; import {{ common_package_name }}.JSON; import {{ common_package_name }}.Pair; +import {{ common_package_name }}.RetryConfig; import {{ common_package_name }}.RFC3339DateFormat; import {{ common_package_name }}.ServerConfiguration; import {{ common_package_name }}.ServerVariable; @@ -162,6 +163,7 @@ public class ApiClient { protected Map operationServerIndex = new HashMap(); protected Map> operationServerVariables = new HashMap>(); protected boolean debugging = false; + protected RetryConfig retry = new RetryConfig(false, 2, 2, 3); protected boolean compress = true; protected ClientConfig clientConfig; protected int connectionTimeout = 0; @@ -288,6 +290,30 @@ public class ApiClient { return offsetDateTimeFormatter; } + /** + * Add custom retry object in the client + * @param retry retry object + * */ + public void setRetry(RetryConfig retry) { + this.retry = retry; + } + + /** + * Return the retryConfig object + * @return retryConfig + */ + public RetryConfig getRetry() { + return retry; + } + + /** + * Enable retry directly on the client instead of creating a new retry object + * @param enableRetry bool, enable retry or not + */ + public void enableRetry(boolean enableRetry){ + this.retry.setEnableRetry(enableRetry); + } + /** * Set the date format used to parse/format {@code OffsetDateTime} parameters. * @@ -1229,45 +1255,76 @@ public class ApiClient { throws ApiException { String contentEncoding = headerParams.get(HttpHeaders.CONTENT_ENCODING); - Entity entity = serialize(body, formParams, selectHeaderContentType(contentTypes), contentEncoding, isBodyNullable); + Entity entity = + serialize( + body, + formParams, + selectHeaderContentType(contentTypes), + contentEncoding, + isBodyNullable); Response response = null; try { - response = sendRequest(method, invocationBuilder, entity); - - int statusCode = response.getStatusInfo().getStatusCode(); - Map> responseHeaders = buildResponseHeaders(response); - - if (response.getStatusInfo() == Status.NO_CONTENT) { - return new ApiResponse(statusCode, responseHeaders); - } else if (response.getStatusInfo().getFamily() == Status.Family.SUCCESSFUL) { - if (returnType == null) { + int currentRetry = 0; + while (true){ + response = sendRequest(method, invocationBuilder, entity); + int statusCode = response.getStatusInfo().getStatusCode(); + Map> responseHeaders = buildResponseHeaders(response); + if (response.getStatusInfo() == Status.NO_CONTENT) { return new ApiResponse(statusCode, responseHeaders); + } else if (response.getStatusInfo().getFamily() == Status.Family.SUCCESSFUL) { + if (returnType == null) { + return new ApiResponse(statusCode, responseHeaders); + } else { + return new ApiResponse(statusCode, responseHeaders, deserialize(response, returnType)); + } + } else if (shouldRetry(currentRetry, statusCode, retry)){ + retry.sleepInterval(calculateRetryInterval(responseHeaders, retry, currentRetry)); + currentRetry++; } else { - return new ApiResponse(statusCode, responseHeaders, deserialize(response, returnType)); - } - } else { - String message = "error"; - String respBody = null; - if (response.hasEntity()) { - try { - respBody = String.valueOf(response.readEntity(String.class)); - message = respBody; - } catch (RuntimeException e) { - // e.printStackTrace(); + String message = "error"; + String respBody = null; + if (response.hasEntity()) { + try { + respBody = String.valueOf(response.readEntity(String.class)); + message = respBody; + } catch (RuntimeException e) { + // e.printStackTrace(); + } } - } - throw new ApiException( - response.getStatus(), message, buildResponseHeaders(response), respBody); - } + throw new ApiException( + response.getStatus(), message, buildResponseHeaders(response), respBody); + } + } } finally { - try { - response.close(); - } catch (Exception e) { - // it's not critical, since the response object is local in method invokeAPI; that's fine, - // just continue + try { + response.close(); + } catch (Exception e) { + // it's not critical, since the response object is local in method invokeAPI; that's fine, + // just continue + } + } + } + + private boolean shouldRetry(int retryCount, int statusCode, RetryConfig retryConfig){ + boolean statusToRetry = false; + if (statusCode == 429 || statusCode >= 500){ + statusToRetry = true; + } + return (retryConfig.maxRetries>retryCount && statusToRetry && retryConfig.isEnableRetry()); + } + + private int calculateRetryInterval(Map> responseHeaders, RetryConfig retryConfig, int retryCount){ + if ( responseHeaders.get("x-ratelimit-reset")!=null){ + List rateLimitHeader = responseHeaders.get("x-ratelimit-reset"); + return Integer.parseInt(rateLimitHeader.get(0)); + } else { + int retryInterval= (int) Math.pow (retry.backOffMultiplier, retryCount)* retryConfig.backOffBase; + if (getConnectTimeout()>0){ + retryInterval = Math.min(retryInterval, getConnectTimeout()); } + return retryInterval; } } diff --git a/.generator/src/generator/templates/RetryConfig.j2 b/.generator/src/generator/templates/RetryConfig.j2 new file mode 100644 index 00000000000..fb1ba52f885 --- /dev/null +++ b/.generator/src/generator/templates/RetryConfig.j2 @@ -0,0 +1,64 @@ +{% include "ApiInfo.j2" %} + +package {{ common_package_name }}; + +public class RetryConfig { + public boolean enableRetry; + public int backOffMultiplier; + public int backOffBase; + public int maxRetries; + + /** + * @param enableRetry Enable retry when rate limited + * @param backOffMultiplier Multiplier for retry backoff + * @param backOffBase Base for retry backoff + * @param maxRetries Maximum number of retries + */ + public RetryConfig(boolean enableRetry, int backOffMultiplier, int backOffBase, int maxRetries) { + this.enableRetry = enableRetry; + this.backOffMultiplier = backOffMultiplier; + this.backOffBase = backOffBase; + this.maxRetries = maxRetries; + } + + public boolean isEnableRetry() { + return enableRetry; + } + + public int getBackOffMultiplier() { + return backOffMultiplier; + } + + public int getBackOffBase() { + return backOffBase; + } + + public int getMaxRetries() { + return maxRetries; + } + + public void setEnableRetry(boolean enableRetry) { + this.enableRetry = enableRetry; + } + + public void setBackOffMultiplier(int backOffMultiplier) { + this.backOffMultiplier = backOffMultiplier; + } + + public void setBackOffBase(int backOffBase) { + this.backOffBase = backOffBase; + } + + public void setMaxRetries(int maxRetries) { + this.maxRetries = maxRetries; + } + + public void sleepInterval(int sleepInterval) { + try { + Thread.sleep(sleepInterval * 1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + e.printStackTrace(); + } + } +} \ No newline at end of file diff --git a/README.md b/README.md index c84f50c4c15..9036a245ca5 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,17 @@ If you want to enable requests logging, set the `debugging` flag on your client: defaultClient.setDebugging(true) ``` +### Enable Retry + +To enable the client to retry when rate limited (status 429) or status 500 and above: + +```java +defaultClient.enableRetry(true) +``` + +The interval between 2 retry attempts will be the value of the `x-ratelimit-reset` response header when available. If not, it will be : +`Math.pow (multiplier_for_retry_backoff, current_retry_count)*base_for_retry_backoff`. + ### Configure proxy You can provide custom `connectorProvider` implemtation to `clientConfig` to use proxy. See example below using `ApacheConnectorProvider`: diff --git a/src/main/java/com/datadog/api/client/ApiClient.java b/src/main/java/com/datadog/api/client/ApiClient.java index c0de105d8d4..8379583fed5 100644 --- a/src/main/java/com/datadog/api/client/ApiClient.java +++ b/src/main/java/com/datadog/api/client/ApiClient.java @@ -350,6 +350,7 @@ public class ApiClient { protected Map> operationServerVariables = new HashMap>(); protected boolean debugging = false; + protected RetryConfig retry = new RetryConfig(false, 2, 2, 3); protected boolean compress = true; protected ClientConfig clientConfig; protected int connectionTimeout = 0; @@ -517,6 +518,33 @@ public DateTimeFormatter getOffsetDateTimeFormatter() { return offsetDateTimeFormatter; } + /** + * Add custom retry object in the client + * + * @param retry retry object + */ + public void setRetry(RetryConfig retry) { + this.retry = retry; + } + + /** + * Return the retryConfig object + * + * @return retryConfig + */ + public RetryConfig getRetry() { + return retry; + } + + /** + * Enable retry directly on the client instead of creating a new retry object + * + * @param enableRetry bool, enable retry or not + */ + public void enableRetry(boolean enableRetry) { + this.retry.setEnableRetry(enableRetry); + } + /** * Set the date format used to parse/format {@code OffsetDateTime} parameters. * @@ -1525,32 +1553,37 @@ public ApiResponse invokeAPI( Response response = null; try { - response = sendRequest(method, invocationBuilder, entity); - - int statusCode = response.getStatusInfo().getStatusCode(); - Map> responseHeaders = buildResponseHeaders(response); - - if (response.getStatusInfo() == Status.NO_CONTENT) { - return new ApiResponse(statusCode, responseHeaders); - } else if (response.getStatusInfo().getFamily() == Status.Family.SUCCESSFUL) { - if (returnType == null) { + int currentRetry = 0; + while (true) { + response = sendRequest(method, invocationBuilder, entity); + int statusCode = response.getStatusInfo().getStatusCode(); + Map> responseHeaders = buildResponseHeaders(response); + if (response.getStatusInfo() == Status.NO_CONTENT) { return new ApiResponse(statusCode, responseHeaders); + } else if (response.getStatusInfo().getFamily() == Status.Family.SUCCESSFUL) { + if (returnType == null) { + return new ApiResponse(statusCode, responseHeaders); + } else { + return new ApiResponse( + statusCode, responseHeaders, deserialize(response, returnType)); + } + } else if (shouldRetry(currentRetry, statusCode, retry)) { + retry.sleepInterval(calculateRetryInterval(responseHeaders, retry, currentRetry)); + currentRetry++; } else { - return new ApiResponse(statusCode, responseHeaders, deserialize(response, returnType)); - } - } else { - String message = "error"; - String respBody = null; - if (response.hasEntity()) { - try { - respBody = String.valueOf(response.readEntity(String.class)); - message = respBody; - } catch (RuntimeException e) { - // e.printStackTrace(); + String message = "error"; + String respBody = null; + if (response.hasEntity()) { + try { + respBody = String.valueOf(response.readEntity(String.class)); + message = respBody; + } catch (RuntimeException e) { + // e.printStackTrace(); + } } + throw new ApiException( + response.getStatus(), message, buildResponseHeaders(response), respBody); } - throw new ApiException( - response.getStatus(), message, buildResponseHeaders(response), respBody); } } finally { try { @@ -1562,6 +1595,29 @@ public ApiResponse invokeAPI( } } + private boolean shouldRetry(int retryCount, int statusCode, RetryConfig retryConfig) { + boolean statusToRetry = false; + if (statusCode == 429 || statusCode >= 500) { + statusToRetry = true; + } + return (retryConfig.maxRetries > retryCount && statusToRetry && retryConfig.isEnableRetry()); + } + + private int calculateRetryInterval( + Map> responseHeaders, RetryConfig retryConfig, int retryCount) { + if (responseHeaders.get("x-ratelimit-reset") != null) { + List rateLimitHeader = responseHeaders.get("x-ratelimit-reset"); + return Integer.parseInt(rateLimitHeader.get(0)); + } else { + int retryInterval = + (int) Math.pow(retry.backOffMultiplier, retryCount) * retryConfig.backOffBase; + if (getConnectTimeout() > 0) { + retryInterval = Math.min(retryInterval, getConnectTimeout()); + } + return retryInterval; + } + } + private Response sendRequest( String method, Invocation.Builder invocationBuilder, Entity entity) { Response response; diff --git a/src/main/java/com/datadog/api/client/RetryConfig.java b/src/main/java/com/datadog/api/client/RetryConfig.java new file mode 100644 index 00000000000..c672725323d --- /dev/null +++ b/src/main/java/com/datadog/api/client/RetryConfig.java @@ -0,0 +1,68 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache-2.0 License. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +package com.datadog.api.client; + +public class RetryConfig { + public boolean enableRetry; + public int backOffMultiplier; + public int backOffBase; + public int maxRetries; + + /** + * @param enableRetry Enable retry when rate limited + * @param backOffMultiplier Multiplier for retry backoff + * @param backOffBase Base for retry backoff + * @param maxRetries Maximum number of retries + */ + public RetryConfig(boolean enableRetry, int backOffMultiplier, int backOffBase, int maxRetries) { + this.enableRetry = enableRetry; + this.backOffMultiplier = backOffMultiplier; + this.backOffBase = backOffBase; + this.maxRetries = maxRetries; + } + + public boolean isEnableRetry() { + return enableRetry; + } + + public int getBackOffMultiplier() { + return backOffMultiplier; + } + + public int getBackOffBase() { + return backOffBase; + } + + public int getMaxRetries() { + return maxRetries; + } + + public void setEnableRetry(boolean enableRetry) { + this.enableRetry = enableRetry; + } + + public void setBackOffMultiplier(int backOffMultiplier) { + this.backOffMultiplier = backOffMultiplier; + } + + public void setBackOffBase(int backOffBase) { + this.backOffBase = backOffBase; + } + + public void setMaxRetries(int maxRetries) { + this.maxRetries = maxRetries; + } + + public void sleepInterval(int sleepInterval) { + try { + Thread.sleep(sleepInterval * 1000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + e.printStackTrace(); + } + } +} diff --git a/src/test/java/com/datadog/api/MockRetryConfig.java b/src/test/java/com/datadog/api/MockRetryConfig.java new file mode 100644 index 00000000000..9e20f53e9a3 --- /dev/null +++ b/src/test/java/com/datadog/api/MockRetryConfig.java @@ -0,0 +1,29 @@ +package com.datadog.api; + +import com.datadog.api.client.RetryConfig; +import java.util.ArrayList; +import java.util.List; + +public class MockRetryConfig extends RetryConfig { + public List intervalList = new ArrayList(); + + public MockRetryConfig( + boolean enableRetry, int backOffMultiplier, int backOffBase, int maxRetries) { + super(enableRetry, backOffMultiplier, backOffBase, maxRetries); + } + + public List getIntervalList() { + return intervalList; + } + + @Override + public void sleepInterval(int sleepInterval) { + try { + intervalList.add(sleepInterval); + Thread.sleep(1); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + e.printStackTrace(); + } + } +} diff --git a/src/test/java/com/datadog/api/TestClient.java b/src/test/java/com/datadog/api/TestClient.java index ab9432bc083..90219730010 100644 --- a/src/test/java/com/datadog/api/TestClient.java +++ b/src/test/java/com/datadog/api/TestClient.java @@ -658,7 +658,9 @@ public MultivaluedMap getMetadata() { public MultivaluedMap getHeaders() { MultivaluedMap newHeaders = new MultivaluedHashMap(); for (Map.Entry> entry : this.headers.entrySet()) { - newHeaders.addAll(entry.getKey(), entry.getValue()); + for (Object o : entry.getValue()) { + newHeaders.add(entry.getKey(), o); + } } return newHeaders; } diff --git a/src/test/java/com/datadog/api/client/v2/api/RetryTest.java b/src/test/java/com/datadog/api/client/v2/api/RetryTest.java new file mode 100644 index 00000000000..0ed8b7007eb --- /dev/null +++ b/src/test/java/com/datadog/api/client/v2/api/RetryTest.java @@ -0,0 +1,57 @@ +package com.datadog.api.client.v2.api; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import com.datadog.api.MockRetryConfig; +import com.datadog.api.client.ApiException; +import com.datadog.api.client.v1.model.DashboardList; +import com.datadog.api.client.v2.model.*; +import java.security.NoSuchAlgorithmException; +import org.junit.Before; +import org.junit.Test; + +public class RetryTest extends V2APITest { + + private static long dashboardListID; + private static com.datadog.api.client.v1.api.DashboardListsApi dashboardListsApiV1; + private final DashboardListsApi api = new DashboardListsApi(generalApiClientWithRetry); + + @Override + public String getTracingEndpoint() { + return "dashboard-lists-retry-tests"; + } + + @Before + public void createDashboardList() throws ApiException, NoSuchAlgorithmException { + dashboardListsApiV1 = + new com.datadog.api.client.v1.api.DashboardListsApi(generalApiClientWithRetry); + DashboardList res = + dashboardListsApiV1.createDashboardList(new DashboardList().name(getUniqueEntityName())); + dashboardListID = res.getId(); + } + + @Test + public void retryWithDashboardListItemGetTest429() throws ApiException { + DashboardListItems getResponse = api.getDashboardListItems(dashboardListID); + MockRetryConfig retryConfig = (MockRetryConfig) generalApiClientWithRetry.getRetry(); + assertEquals(1, retryConfig.getIntervalList().get(0).intValue()); + assertEquals(1, retryConfig.getIntervalList().get(1).intValue()); + assertEquals(1, retryConfig.getIntervalList().get(2).intValue()); + assertNotNull(getResponse.getTotal()); + assertEquals(0, (long) getResponse.getTotal()); + assertEquals(0, getResponse.getDashboards().size()); + } + + @Test + public void retryWithDashboardListItemGetTest500() throws ApiException { + DashboardListItems getResponse = api.getDashboardListItems(dashboardListID); + MockRetryConfig mockRetryConfig = (MockRetryConfig) generalApiClientWithRetry.getRetry(); + assertEquals(2, mockRetryConfig.getIntervalList().get(3).intValue()); + assertEquals(4, mockRetryConfig.getIntervalList().get(4).intValue()); + assertEquals(8, mockRetryConfig.getIntervalList().get(5).intValue()); + assertNotNull(getResponse.getTotal()); + assertEquals(0, (long) getResponse.getTotal()); + assertEquals(0, getResponse.getDashboards().size()); + } +} diff --git a/src/test/java/com/datadog/api/client/v2/api/V2APITest.java b/src/test/java/com/datadog/api/client/v2/api/V2APITest.java index f35a283562c..e569c3e85f0 100644 --- a/src/test/java/com/datadog/api/client/v2/api/V2APITest.java +++ b/src/test/java/com/datadog/api/client/v2/api/V2APITest.java @@ -6,6 +6,7 @@ package com.datadog.api.client.v2.api; +import com.datadog.api.MockRetryConfig; import com.datadog.api.RecordingMode; import com.datadog.api.TestClient; import com.datadog.api.TestUtils; @@ -30,6 +31,14 @@ public abstract class V2APITest extends TestUtils.APITest { protected static ApiClient generalApiClient; protected static ApiClient generalApiUnitTestClient; + protected static ApiClient generalApiClientWithRetry; + + @BeforeClass + public static void initGeneralApiClientWithMockRetry() { + initGeneralApiClient(); + generalApiClientWithRetry = generalApiClient; + generalApiClientWithRetry.setRetry(new MockRetryConfig(true, 2, 2, 3)); + } @BeforeClass public static void initGeneralApiClient() { diff --git a/src/test/resources/cassettes/v2/RetryTest.retryWithDashboardListItemGetTest429.freeze b/src/test/resources/cassettes/v2/RetryTest.retryWithDashboardListItemGetTest429.freeze new file mode 100644 index 00000000000..3fc05aedd1c --- /dev/null +++ b/src/test/resources/cassettes/v2/RetryTest.retryWithDashboardListItemGetTest429.freeze @@ -0,0 +1 @@ +2023-08-08T10:58:09.270Z \ No newline at end of file diff --git a/src/test/resources/cassettes/v2/RetryTest.retryWithDashboardListItemGetTest429.json b/src/test/resources/cassettes/v2/RetryTest.retryWithDashboardListItemGetTest429.json new file mode 100644 index 00000000000..258ec437242 --- /dev/null +++ b/src/test/resources/cassettes/v2/RetryTest.retryWithDashboardListItemGetTest429.json @@ -0,0 +1,235 @@ +[{ + "httpRequest" : { + "method" : "POST", + "path" : "/api/v1/dashboard/lists/manual", + "headers" : { + "x-datadog-trace-id" : [ "7753420519099277221" ], + "x-datadog-origin" : [ "ciapp-test" ], + "content-length" : [ "58" ], + "Content-Type" : [ "application/json" ], + "Content-Language" : [ "" ], + "Accept-Encoding" : [ "deflate,gzip,x-gzip,zstd1" ], + "Accept" : [ "application/json" ] + }, + "keepAlive" : false, + "secure" : true, + "body" : { + "contentType" : "application/json", + "type" : "JSON", + "json" : { + "name" : "java-retryWithDashboardListItemGetTest429-local-1691492289" + } + } + }, + "httpResponse" : { + "statusCode" : 200, + "reasonPhrase" : "OK", + "headers" : { + "x-frame-options" : [ "SAMEORIGIN" ], + "x-content-type-options" : [ "nosniff" ], + "vary" : [ "Accept-Encoding" ], + "strict-transport-security" : [ "max-age=15724800;" ], + "content-security-policy" : [ "frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pube4f163c23bbf91c16b8f57f56af9fc58&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatadoghq.com" ], + "Date" : [ "Mon, 17 Oct 2022 07:00:49 GMT" ], + "Content-Type" : [ "application/json" ] + }, + "body" : { + "contentType" : "application/json", + "type" : "JSON", + "json" : { + "is_favorite" : false, + "name" : "java-retryWithDashboardListItemGetTest429-local-1691492289", + "dashboard_count" : 0, + "author" : { + "handle" : "frog@datadoghq.com", + "name" : null + }, + "created" : "2022-10-17T07:00:49.090621+00:00", + "type" : "manual_dashboard_list", + "dashboards" : null, + "modified" : "2022-10-17T07:00:49.090633+00:00", + "id" : 334687 + } + } + }, + "id" : "c012f980-4ed1-4d58-8e99-fe28876ebf8a", + "priority" : 0, + "timeToLive" : { + "unlimited" : true + }, + "times" : { + "remainingTimes" : 1 + } + },{ + "httpRequest" : { + "method" : "GET", + "path" : "/api/v2/dashboard/lists/manual/334687/dashboards", + "headers" : { + "x-datadog-trace-id" : [ "3402781242333921307" ], + "x-datadog-origin" : [ "ciapp-test" ], + "content-length" : [ "0" ], + "Accept-Encoding" : [ "deflate,gzip,x-gzip,zstd1" ], + "Accept" : [ "application/json" ] + }, + "keepAlive" : false, + "secure" : true + }, + "httpResponse" : { + "statusCode" : 429, + "reasonPhrase" : "Too many requests", + "headers" : { + "x-frame-options" : [ "SAMEORIGIN" ], + "x-content-type-options" : [ "nosniff" ], + "x-ratelimit-reset" : [ "1" ], + "strict-transport-security" : [ "max-age=15724800;" ], + "content-security-policy" : [ "frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pube4f163c23bbf91c16b8f57f56af9fc58&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatadoghq.com" ], + "Date" : [ "Mon, 17 Oct 2022 07:00:50 GMT" ], + "Content-Type" : [ "application/json" ] + }, + "body" : { + "contentType" : "application/json", + "type" : "JSON", + "json" : { + "total" : 0, + "dashboards" : [ ] + } + } + }, + "id" : "58cb9fa4-e67d-4fab-9466-4acf4c9a37a1", + "priority" : 0, + "timeToLive" : { + "unlimited" : true + }, + "times" : { + "remainingTimes" : 1 + } + },{ + "httpRequest" : { + "method" : "GET", + "path" : "/api/v2/dashboard/lists/manual/334687/dashboards", + "headers" : { + "x-datadog-trace-id" : [ "3402781242333921307" ], + "x-datadog-origin" : [ "ciapp-test" ], + "content-length" : [ "0" ], + "Accept-Encoding" : [ "deflate,gzip,x-gzip,zstd1" ], + "Accept" : [ "application/json" ] + }, + "keepAlive" : false, + "secure" : true + }, + "httpResponse" : { + "statusCode" : 429, + "reasonPhrase" : "Too many requests", + "headers" : { + "x-frame-options" : [ "SAMEORIGIN" ], + "x-content-type-options" : [ "nosniff" ], + "x-ratelimit-reset" : [ "1" ], + "strict-transport-security" : [ "max-age=15724800;" ], + "content-security-policy" : [ "frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pube4f163c23bbf91c16b8f57f56af9fc58&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatadoghq.com" ], + "Date" : [ "Mon, 17 Oct 2022 07:00:50 GMT" ], + "Content-Type" : [ "application/json" ] + }, + "body" : { + "contentType" : "application/json", + "type" : "JSON", + "json" : { + "total" : 0, + "dashboards" : [ ] + } + } + }, + "id" : "58cb9fa4-e67d-4fab-9466-4acf4c9a37a1", + "priority" : 0, + "timeToLive" : { + "unlimited" : true + }, + "times" : { + "remainingTimes" : 1 + } + },{ + "httpRequest" : { + "method" : "GET", + "path" : "/api/v2/dashboard/lists/manual/334687/dashboards", + "headers" : { + "x-datadog-trace-id" : [ "3402781242333921307" ], + "x-datadog-origin" : [ "ciapp-test" ], + "content-length" : [ "0" ], + "Accept-Encoding" : [ "deflate,gzip,x-gzip,zstd1" ], + "Accept" : [ "application/json" ] + }, + "keepAlive" : false, + "secure" : true + }, + "httpResponse" : { + "statusCode" : 429, + "reasonPhrase" : "Too many requests", + "headers" : { + "x-frame-options" : [ "SAMEORIGIN" ], + "x-content-type-options" : [ "nosniff" ], + "x-ratelimit-reset" : [ "1" ], + "strict-transport-security" : [ "max-age=15724800;" ], + "content-security-policy" : [ "frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pube4f163c23bbf91c16b8f57f56af9fc58&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatadoghq.com" ], + "Date" : [ "Mon, 17 Oct 2022 07:00:50 GMT" ], + "Content-Type" : [ "application/json" ] + }, + "body" : { + "contentType" : "application/json", + "type" : "JSON", + "json" : { + "total" : 0, + "dashboards" : [ ] + } + } + }, + "id" : "58cb9fa4-e67d-4fab-9466-4acf4c9a37a1", + "priority" : 0, + "timeToLive" : { + "unlimited" : true + }, + "times" : { + "remainingTimes" : 1 + } + } + ,{ + "httpRequest" : { + "method" : "GET", + "path" : "/api/v2/dashboard/lists/manual/334687/dashboards", + "headers" : { + "x-datadog-trace-id" : [ "3402781242333921307" ], + "x-datadog-origin" : [ "ciapp-test" ], + "content-length" : [ "0" ], + "Accept-Encoding" : [ "deflate,gzip,x-gzip,zstd1" ], + "Accept" : [ "application/json" ] + }, + "keepAlive" : false, + "secure" : true + }, + "httpResponse" : { + "statusCode" : 200, + "reasonPhrase" : "OK", + "headers" : { + "x-frame-options" : [ "SAMEORIGIN" ], + "x-content-type-options" : [ "nosniff" ], + "strict-transport-security" : [ "max-age=15724800;" ], + "content-security-policy" : [ "frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pube4f163c23bbf91c16b8f57f56af9fc58&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatadoghq.com" ], + "Date" : [ "Mon, 17 Oct 2022 07:00:50 GMT" ], + "Content-Type" : [ "application/json" ] + }, + "body" : { + "contentType" : "application/json", + "type" : "JSON", + "json" : { + "total" : 0, + "dashboards" : [ ] + } + } + }, + "id" : "58cb9fa4-e67d-4fab-9466-4acf4c9a47a1", + "priority" : 0, + "timeToLive" : { + "unlimited" : true + }, + "times" : { + "remainingTimes" : 1 + } + }] \ No newline at end of file diff --git a/src/test/resources/cassettes/v2/RetryTest.retryWithDashboardListItemGetTest500.freeze b/src/test/resources/cassettes/v2/RetryTest.retryWithDashboardListItemGetTest500.freeze new file mode 100644 index 00000000000..331bc9fe2ac --- /dev/null +++ b/src/test/resources/cassettes/v2/RetryTest.retryWithDashboardListItemGetTest500.freeze @@ -0,0 +1 @@ +2023-08-08T10:59:09.270Z \ No newline at end of file diff --git a/src/test/resources/cassettes/v2/RetryTest.retryWithDashboardListItemGetTest500.json b/src/test/resources/cassettes/v2/RetryTest.retryWithDashboardListItemGetTest500.json new file mode 100644 index 00000000000..956bffe5641 --- /dev/null +++ b/src/test/resources/cassettes/v2/RetryTest.retryWithDashboardListItemGetTest500.json @@ -0,0 +1,231 @@ +[{ + "httpRequest" : { + "method" : "POST", + "path" : "/api/v1/dashboard/lists/manual", + "headers" : { + "x-datadog-trace-id" : [ "7753420519099277221" ], + "x-datadog-origin" : [ "ciapp-test" ], + "content-length" : [ "58" ], + "Content-Type" : [ "application/json" ], + "Content-Language" : [ "" ], + "Accept-Encoding" : [ "deflate,gzip,x-gzip,zstd1" ], + "Accept" : [ "application/json" ] + }, + "keepAlive" : false, + "secure" : true, + "body" : { + "contentType" : "application/json", + "type" : "JSON", + "json" : { + "name" : "java-retryWithDashboardListItemGetTest500-local-1691492349" + } + } + }, + "httpResponse" : { + "statusCode" : 200, + "reasonPhrase" : "OK", + "headers" : { + "x-frame-options" : [ "SAMEORIGIN" ], + "x-content-type-options" : [ "nosniff" ], + "vary" : [ "Accept-Encoding" ], + "strict-transport-security" : [ "max-age=15724800;" ], + "content-security-policy" : [ "frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pube4f163c23bbf91c16b8f57f56af9fc58&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatadoghq.com" ], + "Date" : [ "Mon, 17 Oct 2022 07:00:49 GMT" ], + "Content-Type" : [ "application/json" ] + }, + "body" : { + "contentType" : "application/json", + "type" : "JSON", + "json" : { + "is_favorite" : false, + "name" : "java-retryWithDashboardListItemGetTest500-local-1691492349", + "dashboard_count" : 0, + "author" : { + "handle" : "frog@datadoghq.com", + "name" : null + }, + "created" : "2022-10-17T07:00:49.090621+00:00", + "type" : "manual_dashboard_list", + "dashboards" : null, + "modified" : "2022-10-17T07:00:49.090633+00:00", + "id" : 334687 + } + } + }, + "id" : "c012f980-4ed1-4d58-8e99-fe28876ebf8a", + "priority" : 0, + "timeToLive" : { + "unlimited" : true + }, + "times" : { + "remainingTimes" : 1 + } + },{ + "httpRequest" : { + "method" : "GET", + "path" : "/api/v2/dashboard/lists/manual/334687/dashboards", + "headers" : { + "x-datadog-trace-id" : [ "3402781242333921307" ], + "x-datadog-origin" : [ "ciapp-test" ], + "content-length" : [ "0" ], + "Accept-Encoding" : [ "deflate,gzip,x-gzip,zstd1" ], + "Accept" : [ "application/json" ] + }, + "keepAlive" : false, + "secure" : true + }, + "httpResponse" : { + "statusCode" : 500, + "reasonPhrase" : "Internal Server Error", + "headers" : { + "x-frame-options" : [ "SAMEORIGIN" ], + "x-content-type-options" : [ "nosniff" ], + "strict-transport-security" : [ "max-age=15724800;" ], + "content-security-policy" : [ "frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pube4f163c23bbf91c16b8f57f56af9fc58&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatadoghq.com" ], + "Date" : [ "Mon, 17 Oct 2022 07:00:50 GMT" ], + "Content-Type" : [ "application/json" ] + }, + "body" : { + "contentType" : "application/json", + "type" : "JSON", + "json" : { + "total" : 0, + "dashboards" : [ ] + } + } + }, + "id" : "58cb9fa4-e67d-4fab-9466-4acf4c9a37a1", + "priority" : 0, + "timeToLive" : { + "unlimited" : true + }, + "times" : { + "remainingTimes" : 1 + } + },{ + "httpRequest" : { + "method" : "GET", + "path" : "/api/v2/dashboard/lists/manual/334687/dashboards", + "headers" : { + "x-datadog-trace-id" : [ "3402781242333921307" ], + "x-datadog-origin" : [ "ciapp-test" ], + "content-length" : [ "0" ], + "Accept-Encoding" : [ "deflate,gzip,x-gzip,zstd1" ], + "Accept" : [ "application/json" ] + }, + "keepAlive" : false, + "secure" : true + }, + "httpResponse" : { + "statusCode" : 500, + "reasonPhrase" : "Internal Server Error", + "headers" : { + "x-frame-options" : [ "SAMEORIGIN" ], + "x-content-type-options" : [ "nosniff" ], + "strict-transport-security" : [ "max-age=15724800;" ], + "content-security-policy" : [ "frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pube4f163c23bbf91c16b8f57f56af9fc58&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatadoghq.com" ], + "Date" : [ "Mon, 17 Oct 2022 07:00:50 GMT" ], + "Content-Type" : [ "application/json" ] + }, + "body" : { + "contentType" : "application/json", + "type" : "JSON", + "json" : { + "total" : 0, + "dashboards" : [ ] + } + } + }, + "id" : "58cb9fa4-e67d-4fab-9466-4acf4c9a37a1", + "priority" : 0, + "timeToLive" : { + "unlimited" : true + }, + "times" : { + "remainingTimes" : 1 + } + },{ + "httpRequest" : { + "method" : "GET", + "path" : "/api/v2/dashboard/lists/manual/334687/dashboards", + "headers" : { + "x-datadog-trace-id" : [ "3402781242333921307" ], + "x-datadog-origin" : [ "ciapp-test" ], + "content-length" : [ "0" ], + "Accept-Encoding" : [ "deflate,gzip,x-gzip,zstd1" ], + "Accept" : [ "application/json" ] + }, + "keepAlive" : false, + "secure" : true + }, + "httpResponse" : { + "statusCode" : 500, + "reasonPhrase" : "Internal Server Error", + "headers" : { + "x-frame-options" : [ "SAMEORIGIN" ], + "x-content-type-options" : [ "nosniff" ], + "strict-transport-security" : [ "max-age=15724800;" ], + "content-security-policy" : [ "frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pube4f163c23bbf91c16b8f57f56af9fc58&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatadoghq.com" ], + "Date" : [ "Mon, 17 Oct 2022 07:00:50 GMT" ], + "Content-Type" : [ "application/json" ] + }, + "body" : { + "contentType" : "application/json", + "type" : "JSON", + "json" : { + "total" : 0, + "dashboards" : [ ] + } + } + }, + "id" : "58cb9fa4-e67d-4fab-9466-4acf4c9a37a1", + "priority" : 0, + "timeToLive" : { + "unlimited" : true + }, + "times" : { + "remainingTimes" : 1 + } + },{ + "httpRequest" : { + "method" : "GET", + "path" : "/api/v2/dashboard/lists/manual/334687/dashboards", + "headers" : { + "x-datadog-trace-id" : [ "3402781242333921307" ], + "x-datadog-origin" : [ "ciapp-test" ], + "content-length" : [ "0" ], + "Accept-Encoding" : [ "deflate,gzip,x-gzip,zstd1" ], + "Accept" : [ "application/json" ] + }, + "keepAlive" : false, + "secure" : true + }, + "httpResponse" : { + "statusCode" : 200, + "reasonPhrase" : "OK", + "headers" : { + "x-frame-options" : [ "SAMEORIGIN" ], + "x-content-type-options" : [ "nosniff" ], + "strict-transport-security" : [ "max-age=15724800;" ], + "content-security-policy" : [ "frame-ancestors 'self'; report-uri https://logs.browser-intake-datadoghq.com/api/v2/logs?dd-api-key=pube4f163c23bbf91c16b8f57f56af9fc58&dd-evp-origin=content-security-policy&ddsource=csp-report&ddtags=site%3Adatadoghq.com" ], + "Date" : [ "Mon, 17 Oct 2022 07:00:50 GMT" ], + "Content-Type" : [ "application/json" ] + }, + "body" : { + "contentType" : "application/json", + "type" : "JSON", + "json" : { + "total" : 0, + "dashboards" : [ ] + } + } + }, + "id" : "58cb9fa4-e67d-4fab-9466-4acf4c9a47a1", + "priority" : 0, + "timeToLive" : { + "unlimited" : true + }, + "times" : { + "remainingTimes" : 1 + } + }] \ No newline at end of file