You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jclouds.apache.org by na...@apache.org on 2018/05/04 09:05:06 UTC

[1/2] jclouds git commit: Cleanup unused variables

Repository: jclouds
Updated Branches:
  refs/heads/2.1.x a32690a2e -> b144d9f47


Cleanup unused variables


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/c96028a2
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/c96028a2
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/c96028a2

Branch: refs/heads/2.1.x
Commit: c96028a2ae717c7c5d10786bb3c8650a2cc9cbf8
Parents: a32690a
Author: Ignasi Barrera <na...@apache.org>
Authored: Fri May 4 09:27:39 2018 +0200
Committer: Ignasi Barrera <na...@apache.org>
Committed: Fri May 4 11:04:51 2018 +0200

----------------------------------------------------------------------
 .../compute/config/AzurePredicatesModule.java   |  6 -----
 .../arm/features/VaultApiLiveTest.java          | 23 +++++---------------
 .../arm/features/VaultApiMockTest.java          |  3 ---
 3 files changed, 6 insertions(+), 26 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/c96028a2/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/config/AzurePredicatesModule.java
----------------------------------------------------------------------
diff --git a/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/config/AzurePredicatesModule.java b/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/config/AzurePredicatesModule.java
index c8dfd6e..9885075 100644
--- a/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/config/AzurePredicatesModule.java
+++ b/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/compute/config/AzurePredicatesModule.java
@@ -330,7 +330,6 @@ public class AzurePredicatesModule extends AbstractModule {
                     @Override
                     public boolean apply(final String name) {
                         checkNotNull(name, "name cannot be null");
-                        boolean present = false;
                         List<Vault.DeletedVault> vaults = api.getVaultApi(resourceGroup).listDeletedVaults();
                         return shouldBePresent == Iterables.any(vaults, new Predicate<Vault.DeletedVault>() {
                             @Override public boolean apply(Vault.DeletedVault input) {
@@ -380,7 +379,6 @@ public class AzurePredicatesModule extends AbstractModule {
                     @Override
                     public boolean apply(final String name) {
                         checkNotNull(name, "name cannot be null");
-                        boolean present = false;
                         DeletedKeyBundle key = api.getVaultApi(resourceGroup).getDeletedKey(vaultUri, name);
                         return shouldBePresent == (key != null);
                     }
@@ -408,7 +406,6 @@ public class AzurePredicatesModule extends AbstractModule {
                     @Override
                     public boolean apply(final String name) {
                         checkNotNull(name, "name cannot be null");
-                        boolean result = false;
                         KeyBundle key = api.getVaultApi(resourceGroup).getKey(vaultUri, name);
                         return key != null ? (isRecovered ? true : key.attributes().recoveryLevel().contains("Recoverable")) : false;
                     }
@@ -453,7 +450,6 @@ public class AzurePredicatesModule extends AbstractModule {
                 return retry(new Predicate<String>() {
                     @Override
                     public boolean apply(final String name) {
-                        boolean present = false;
                         checkNotNull(name, "name cannot be null");
                         DeletedSecretBundle secret = api.getVaultApi(resourceGroup).getDeletedSecret(vaultUri, name);
                         return shouldBePresent == (secret != null);
@@ -534,7 +530,6 @@ public class AzurePredicatesModule extends AbstractModule {
                 return retry(new Predicate<String>() {
                     @Override
                     public boolean apply(final String name) {
-                        boolean present = false;
                         checkNotNull(name, "name cannot be null");
                         DeletedCertificateBundle cert = api.getVaultApi(resourceGroup).getDeletedCertificate(vaultUri, name);
                         return shouldBePresent == (cert != null);
@@ -591,7 +586,6 @@ public class AzurePredicatesModule extends AbstractModule {
                     @Override
                     public boolean apply(final String name) {
                         checkNotNull(name, "name cannot be null");
-                        boolean result = false;
                         CertificateOperation certOp = api.getVaultApi(resourceGroup).getCertificateOperation(vaultUri, name);
                         return isCreate ? ((certOp != null) ? !certOp.status().equals("inProgress") : false) : (certOp == null);
                     }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/c96028a2/providers/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VaultApiLiveTest.java
----------------------------------------------------------------------
diff --git a/providers/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VaultApiLiveTest.java b/providers/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VaultApiLiveTest.java
index 026aa28..2c6b100 100644
--- a/providers/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VaultApiLiveTest.java
+++ b/providers/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VaultApiLiveTest.java
@@ -318,17 +318,6 @@ public class VaultApiLiveTest extends BaseAzureComputeApiLiveTest {
 
    @Test(dependsOnMethods = "testUpdateKey")
    public void testListKeyVersions() {
-      // Create a second version of the key
-      KeyAttributes keyAttr = KeyAttributes.create(true, null, null, null, null, null);
-      KeyBundle keyBundle = api().createKey(vaultUri,
-              KEY_NAME,
-              keyAttr,
-              null,
-              null,
-              3072,
-              "RSA",
-              null);
-
       // List key versions
       List<Key> keys = api().getKeyVersions(vaultUri, KEY_NAME);
       assertNotNull(keys);
@@ -427,7 +416,7 @@ public class VaultApiLiveTest extends BaseAzureComputeApiLiveTest {
 
    @Test(dependsOnMethods = {"testDeleteRecoverableKey", "testGetDeletedKey"})
    public void testRecoverDeletedKey() {
-      KeyBundle key = api().recoverDeletedKey(vaultUri, RECOVERABLE_KEY_NAME);
+      api().recoverDeletedKey(vaultUri, RECOVERABLE_KEY_NAME);
       checkState(recoverableKeyStatus.create(resourceGroupName, vaultUri, true).apply(RECOVERABLE_KEY_NAME),
               "key was not recovered before timeout");
    }
@@ -435,7 +424,7 @@ public class VaultApiLiveTest extends BaseAzureComputeApiLiveTest {
    @Test(dependsOnMethods = "testRecoverDeletedKey")
    public void testPurgeDeletedKey() {
       // delete the key
-      DeletedKeyBundle dkb = api().deleteKey(vaultUri, RECOVERABLE_KEY_NAME);
+      api().deleteKey(vaultUri, RECOVERABLE_KEY_NAME);
       checkState(deletedKeyStatus.create(resourceGroupName, vaultUri, true).apply(RECOVERABLE_KEY_NAME),
               "key was not deleted before timeout");
 
@@ -559,7 +548,7 @@ public class VaultApiLiveTest extends BaseAzureComputeApiLiveTest {
    public void testListSecretVersions() {
       // Create a second version of the secret
       SecretAttributes attributes = SecretAttributes.create(true, null, null, null, null, null);
-      SecretBundle secretBundle = api().setSecret(vaultUri,
+      api().setSecret(vaultUri,
               SECRET_NAME,
               attributes,
               "aNewSecretKey",
@@ -684,7 +673,7 @@ public class VaultApiLiveTest extends BaseAzureComputeApiLiveTest {
 
    @Test(dependsOnMethods = {"testDeleteRecoverableSecret", "testGetDeletedSecret"})
    public void testRecoverDeletedSecret() {
-      SecretBundle secret = api().recoverDeletedSecret(vaultUri, RECOVERABLE_SECRET_NAME);
+      api().recoverDeletedSecret(vaultUri, RECOVERABLE_SECRET_NAME);
       checkState(recoverableSecretStatus.create(resourceGroupName, vaultUri, true).apply(RECOVERABLE_SECRET_NAME),
               "secret was not created before timeout");
    }
@@ -692,7 +681,7 @@ public class VaultApiLiveTest extends BaseAzureComputeApiLiveTest {
    @Test(dependsOnMethods = "testRecoverDeletedSecret")
    public void testPurgeDeletedSecret() {
       // delete the secret
-      DeletedSecretBundle dsb = api().deleteSecret(vaultUri, RECOVERABLE_SECRET_NAME);
+      api().deleteSecret(vaultUri, RECOVERABLE_SECRET_NAME);
       checkState(deletedSecretStatus.create(resourceGroupName, vaultUri, true).apply(RECOVERABLE_SECRET_NAME),
               "secret was not deleted before timeout");
 
@@ -1041,7 +1030,7 @@ public class VaultApiLiveTest extends BaseAzureComputeApiLiveTest {
    @Test(dependsOnMethods = "testRecoverDeletedCertificate")
    public void testPurgeDeletedCertificate() {
       // delete the certificate
-      DeletedCertificateBundle dcb = api().deleteCertificate(vaultUri, RECOVERABLE_CERTIFICATE_NAME);
+      api().deleteCertificate(vaultUri, RECOVERABLE_CERTIFICATE_NAME);
       checkState(deletedCertificateStatus.create(resourceGroupName, vaultUri, true).apply(RECOVERABLE_CERTIFICATE_NAME),
               "certificate was not deleted before timeout");
 

http://git-wip-us.apache.org/repos/asf/jclouds/blob/c96028a2/providers/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VaultApiMockTest.java
----------------------------------------------------------------------
diff --git a/providers/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VaultApiMockTest.java b/providers/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VaultApiMockTest.java
index 7459f48..be3ae74 100644
--- a/providers/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VaultApiMockTest.java
+++ b/providers/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/features/VaultApiMockTest.java
@@ -84,15 +84,12 @@ public class VaultApiMockTest extends BaseAzureComputeApiMockTest {
    private URI vaultUri;
 
    private static String KEY_NAME = "myKey";
-   private static String TEMP_KEY_NAME = "myTempKey";
    private static String IMPORT_KEY_NAME = "myImportKey";
    private static String RECOVERABLE_KEY_NAME = "myRecoverableKey";
    private static String SECRET_NAME = "mySecret";
    private static String RECOVERABLE_SECRET_NAME = "myRecoverableSecret";
    private static String CERTIFICATE_NAME = "myCertificate";
-   private static String TEMP_CERTIFICATE_NAME = "myTempCertificate";
    private static String RECOVERABLE_CERTIFICATE_NAME = "myRecoverableCertificate";
-   private static String IMPORTABLE_CERTIFICATE_NAME = "myImportableCertificate";
    private static String CERTIFICATE_ISSUER_NAME = "globalsign01";
    private String IMPORTABLE_CERTIFICATE_PEM = stringFromResource("/vaultimportablecert.txt");
    private String sampleSecret = stringFromResource("/vaultsamplesecret.txt");


[2/2] jclouds git commit: JCLOUDS-1294: Attempt to retry RetryableErrors in Azure ARM

Posted by na...@apache.org.
JCLOUDS-1294: Attempt to retry RetryableErrors in Azure ARM


Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo
Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/b144d9f4
Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/b144d9f4
Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/b144d9f4

Branch: refs/heads/2.1.x
Commit: b144d9f47310e863536ec94b2c1eb53398129dfd
Parents: c96028a
Author: Ignasi Barrera <na...@apache.org>
Authored: Fri May 4 09:27:25 2018 +0200
Committer: Ignasi Barrera <na...@apache.org>
Committed: Fri May 4 11:04:55 2018 +0200

----------------------------------------------------------------------
 .../http/handlers/RateLimitRetryHandler.java    |  2 +-
 .../jclouds/azurecompute/arm/domain/Error.java  | 58 +++++++++++++
 .../arm/handlers/AzureComputeErrorHandler.java  | 13 ++-
 .../handlers/AzureRateLimitRetryHandler.java    | 28 +++++++
 .../handlers/AzureRetryableErrorHandler.java    | 81 ++++++++++++++++++
 .../AzureRetryableErrorHandlerTest.java         | 88 ++++++++++++++++++++
 6 files changed, 267 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/jclouds/blob/b144d9f4/core/src/main/java/org/jclouds/http/handlers/RateLimitRetryHandler.java
----------------------------------------------------------------------
diff --git a/core/src/main/java/org/jclouds/http/handlers/RateLimitRetryHandler.java b/core/src/main/java/org/jclouds/http/handlers/RateLimitRetryHandler.java
index 862ffd4..e5fda77 100644
--- a/core/src/main/java/org/jclouds/http/handlers/RateLimitRetryHandler.java
+++ b/core/src/main/java/org/jclouds/http/handlers/RateLimitRetryHandler.java
@@ -87,7 +87,7 @@ public abstract class RateLimitRetryHandler implements HttpRetryHandler {
       }
    }
 
-   private boolean delayRequestUntilAllowed(final HttpCommand command, final HttpResponse response) {
+   protected boolean delayRequestUntilAllowed(final HttpCommand command, final HttpResponse response) {
       Optional<Long> millisToNextAvailableRequest = millisToNextAvailableRequest(command, response);
       if (!millisToNextAvailableRequest.isPresent()) {
          logger.error("Cannot retry after rate limit error, no retry information provided in the response");

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b144d9f4/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/Error.java
----------------------------------------------------------------------
diff --git a/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/Error.java b/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/Error.java
new file mode 100644
index 0000000..19a045f
--- /dev/null
+++ b/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/domain/Error.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.azurecompute.arm.domain;
+
+import java.util.List;
+
+import org.jclouds.javax.annotation.Nullable;
+import org.jclouds.json.SerializedNames;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableList;
+
+@AutoValue
+public abstract class Error {
+
+   public abstract Details details();
+
+   @SerializedNames({ "error" })
+   public static Error create(Details details) {
+      return new AutoValue_Error(details);
+   }
+
+   Error() {
+
+   }
+
+   @AutoValue
+   public abstract static class Details {
+      public abstract String code();
+      public abstract String message();
+      public abstract List<Details> details();
+
+      @SerializedNames({ "code", "message", "details" })
+      public static Details create(String code, String message, @Nullable List<Details> details) {
+         return new AutoValue_Error_Details(code, message, details == null ? ImmutableList.<Details> of()
+               : ImmutableList.copyOf(details));
+      }
+
+      Details() {
+
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b144d9f4/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/handlers/AzureComputeErrorHandler.java
----------------------------------------------------------------------
diff --git a/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/handlers/AzureComputeErrorHandler.java b/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/handlers/AzureComputeErrorHandler.java
index 8492d51..6673001 100644
--- a/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/handlers/AzureComputeErrorHandler.java
+++ b/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/handlers/AzureComputeErrorHandler.java
@@ -16,6 +16,8 @@
  */
 package org.jclouds.azurecompute.arm.handlers;
 
+import static org.jclouds.azurecompute.arm.handlers.AzureRateLimitRetryHandler.isRateLimitError;
+
 import java.io.IOException;
 
 import javax.inject.Singleton;
@@ -38,7 +40,10 @@ public class AzureComputeErrorHandler implements HttpErrorHandler {
 
    @Override
    public void handleError(final HttpCommand command, final HttpResponse response) {
-      // it is important to always read fully and close streams
+      // It is important to always read fully and close streams
+      // For 429 errors the response body might have already been consumed as
+      // some errors report information in the response body that needs to be
+      // handled by the retry handlers.
       String message = parseMessage(response);
       Exception exception = message == null
               ? new HttpResponseException(command, response)
@@ -70,7 +75,11 @@ public class AzureComputeErrorHandler implements HttpErrorHandler {
                exception = new IllegalStateException(message, exception);
                break;
             case 429:
-               exception = new AzureComputeRateLimitExceededException(response, exception);
+               if (isRateLimitError(response)) {
+                  exception = new AzureComputeRateLimitExceededException(response, exception);
+               } else {
+                  exception = new IllegalStateException(message, exception);
+               }
                break;
             default:
          }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b144d9f4/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/handlers/AzureRateLimitRetryHandler.java
----------------------------------------------------------------------
diff --git a/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/handlers/AzureRateLimitRetryHandler.java b/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/handlers/AzureRateLimitRetryHandler.java
index ee5f5e5..e2c6270 100644
--- a/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/handlers/AzureRateLimitRetryHandler.java
+++ b/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/handlers/AzureRateLimitRetryHandler.java
@@ -16,6 +16,7 @@
  */
 package org.jclouds.azurecompute.arm.handlers;
 
+import javax.inject.Inject;
 import javax.inject.Singleton;
 
 import org.jclouds.http.HttpCommand;
@@ -26,14 +27,41 @@ import com.google.common.annotations.Beta;
 import com.google.common.base.Optional;
 import com.google.common.net.HttpHeaders;
 
+/**
+ * Handles 429 Too Many Requests responses.
+ * <p>
+ * The Azure ARM provider also returns this 429 HTTP status code for some errors
+ * when resources are busy or in a state where they cannot be modified. In this
+ * case this handler delegates to the {@link AzureRetryableErrorHandler} to
+ * determine if the requests can be retried.
+ */
 @Beta
 @Singleton
 public class AzureRateLimitRetryHandler extends RateLimitRetryHandler {
 
+   private final AzureRetryableErrorHandler retryableErrorHandler;
+
+   @Inject
+   AzureRateLimitRetryHandler(AzureRetryableErrorHandler retryableErrorHandler) {
+      this.retryableErrorHandler = retryableErrorHandler;
+   }
+
+   @Override
+   protected boolean delayRequestUntilAllowed(HttpCommand command, HttpResponse response) {
+      if (!isRateLimitError(response)) {
+         return retryableErrorHandler.shouldRetryRequest(command, response);
+      }
+      return super.delayRequestUntilAllowed(command, response);
+   }
+
    @Override
    protected Optional<Long> millisToNextAvailableRequest(HttpCommand command, HttpResponse response) {
       String secondsToNextAvailableRequest = response.getFirstHeaderOrNull(HttpHeaders.RETRY_AFTER);
       return secondsToNextAvailableRequest != null ? Optional.of(Long.valueOf(secondsToNextAvailableRequest) * 1000)
             : Optional.<Long> absent();
    }
+
+   public static boolean isRateLimitError(HttpResponse response) {
+      return response.getFirstHeaderOrNull(HttpHeaders.RETRY_AFTER) != null;
+   }
 }

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b144d9f4/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/handlers/AzureRetryableErrorHandler.java
----------------------------------------------------------------------
diff --git a/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/handlers/AzureRetryableErrorHandler.java b/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/handlers/AzureRetryableErrorHandler.java
new file mode 100644
index 0000000..07f7b5d
--- /dev/null
+++ b/providers/azurecompute-arm/src/main/java/org/jclouds/azurecompute/arm/handlers/AzureRetryableErrorHandler.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.azurecompute.arm.handlers;
+
+import static org.jclouds.azurecompute.arm.handlers.AzureRateLimitRetryHandler.isRateLimitError;
+
+import javax.annotation.Resource;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+
+import org.jclouds.azurecompute.arm.domain.Error;
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.functions.ParseJson;
+import org.jclouds.http.handlers.BackoffLimitedRetryHandler;
+import org.jclouds.logging.Logger;
+
+import com.google.common.annotations.Beta;
+
+/**
+ * This handles failed responses that return a <code>RetryableError</code>.
+ * <p>
+ * In order to determine if the error is retryable, the response body must be
+ * read, so this handler will have to buffer the response payload in memory so
+ * the response body can be read again in subsequent steps of the response
+ * processing flow.
+ */
+@Singleton
+@Beta
+public class AzureRetryableErrorHandler extends BackoffLimitedRetryHandler {
+
+   private static final String RETRYABLE_ERROR_CODE = "RetryableError";
+
+   @Resource
+   protected Logger logger = Logger.NULL;
+   private final ParseJson<Error> parseError;
+
+   @Inject
+   AzureRetryableErrorHandler(ParseJson<Error> parseError) {
+      this.parseError = parseError;
+   }
+
+   @Override
+   public boolean shouldRetryRequest(HttpCommand command, HttpResponse response) {
+      // Only consider retryable errors and discard rate limit ones
+      if (response.getStatusCode() != 429 || isRateLimitError(response)) {
+         return false;
+      }
+
+      try {
+         // Note that this will consume the response body. At this point,
+         // subsequent retry handlers or error handlers will not be able to read
+         // again the payload, but that should only be attempted when the
+         // command is not retryable and an exception should be thrown.
+         Error error = parseError.apply(response);
+         logger.debug("processing error: %s", error);
+
+         boolean isRetryable = RETRYABLE_ERROR_CODE.equals(error.details().code());
+         return isRetryable ? super.shouldRetryRequest(command, response) : false;
+      } catch (Exception ex) {
+         // If we can't parse the error, just assume it is not a retryable error
+         logger.warn("could not parse error. Request won't be retried: %s", ex.getMessage());
+         return false;
+      }
+   }
+
+}

http://git-wip-us.apache.org/repos/asf/jclouds/blob/b144d9f4/providers/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/handlers/AzureRetryableErrorHandlerTest.java
----------------------------------------------------------------------
diff --git a/providers/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/handlers/AzureRetryableErrorHandlerTest.java b/providers/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/handlers/AzureRetryableErrorHandlerTest.java
new file mode 100644
index 0000000..49103ae
--- /dev/null
+++ b/providers/azurecompute-arm/src/test/java/org/jclouds/azurecompute/arm/handlers/AzureRetryableErrorHandlerTest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jclouds.azurecompute.arm.handlers;
+
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import org.jclouds.http.HttpCommand;
+import org.jclouds.http.HttpRequest;
+import org.jclouds.http.HttpResponse;
+import org.jclouds.http.HttpRetryHandler;
+import org.jclouds.json.config.GsonModule;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+import com.google.common.net.HttpHeaders;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+
+@Test(groups = "unit", testName = "AzureRetryableErrorHandlerTest")
+public class AzureRetryableErrorHandlerTest {
+
+   private HttpRetryHandler handler;
+
+   @BeforeClass
+   public void setup() {
+      // Initialize an injector with just the Json features to get all
+      // serialization stuff
+      Injector injector = Guice.createInjector(new GsonModule());
+      handler = injector.getInstance(AzureRetryableErrorHandler.class);
+   }
+
+   @Test
+   public void testDoesNotRetryWhenNot429() {
+      HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost").build());
+      HttpResponse response = HttpResponse.builder().statusCode(400).build();
+
+      assertFalse(handler.shouldRetryRequest(command, response));
+   }
+
+   @Test
+   public void testDoesNotRetryWhenRateLimitError() {
+      HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost").build());
+      HttpResponse response = HttpResponse.builder().statusCode(429).addHeader(HttpHeaders.RETRY_AFTER, "15").build();
+
+      assertFalse(handler.shouldRetryRequest(command, response));
+   }
+
+   @Test
+   public void testDoesNotRetryWhenCannotParseError() {
+      HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost").build());
+      HttpResponse response = HttpResponse.builder().statusCode(429).payload("foo").build();
+
+      assertFalse(handler.shouldRetryRequest(command, response));
+   }
+
+   @Test
+   public void testDoesNotRetryWhenErrorNotRetryable() {
+      String nonRetryable = "{\"error\":{\"code\":\"ReferencedResourceNotProvisioned\",\"message\":\"Not provisioned\"}}";
+      HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost").build());
+      HttpResponse response = HttpResponse.builder().statusCode(429).payload(nonRetryable).build();
+
+      assertFalse(handler.shouldRetryRequest(command, response));
+   }
+
+   @Test
+   public void testRetriesWhenRetryableError() {
+      String retryable = "{\"error\":{\"code\":\"RetryableError\",\"message\":\"Resource busy\"}}";
+      HttpCommand command = new HttpCommand(HttpRequest.builder().method("GET").endpoint("http://localhost").build());
+      HttpResponse response = HttpResponse.builder().statusCode(429).payload(retryable).build();
+
+      assertTrue(handler.shouldRetryRequest(command, response));
+   }
+}