Repository: jclouds Updated Branches: refs/heads/master b06795ebe -> 00f7ee073
jclouds was not properly retrying on an expect: 100-continue PUT request that requires re-athentication because of java protocol code JCLOUDS-1179 This fix also addresses the same problem with other providers No currently working tests because of https://github.com/square/okhttp/issues/675 This is a common problem in other tools as well https://curl.haxx.se/mail/lib-2004-08/0002.html Project: http://git-wip-us.apache.org/repos/asf/jclouds/repo Commit: http://git-wip-us.apache.org/repos/asf/jclouds/commit/00f7ee07 Tree: http://git-wip-us.apache.org/repos/asf/jclouds/tree/00f7ee07 Diff: http://git-wip-us.apache.org/repos/asf/jclouds/diff/00f7ee07 Branch: refs/heads/master Commit: 00f7ee0738fb855119ab048a40832efe97bc6335 Parents: b06795e Author: Zack Shoylev <zack.shoy...@rackspace.com> Authored: Tue Oct 11 14:23:57 2016 -0500 Committer: Zack Shoylev <zack.shoy...@rackspace.com> Committed: Thu Oct 13 12:17:19 2016 -0500 ---------------------------------------------------------------------- .../swift/v1/features/ObjectApiMockTest.java | 71 ++++++ .../src/test/resources/access2.json | 249 +++++++++++++++++++ .../BaseHttpCommandExecutorService.java | 11 + 3 files changed, 331 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/jclouds/blob/00f7ee07/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java ---------------------------------------------------------------------- diff --git a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java index 5e3d0f6..28299de 100644 --- a/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java +++ b/apis/openstack-swift/src/test/java/org/jclouds/openstack/swift/v1/features/ObjectApiMockTest.java @@ -255,6 +255,77 @@ public class ObjectApiMockTest extends BaseOpenStackMockTest<SwiftApi> { } } + public void testCreateWith401Retry() throws Exception { + // TODO: requires upgrade to okhttp mockwebserver 3.6 + + MockWebServer server = mockOpenStackServer(); + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access.json")))); + + // PUT 1 - establishes auth tokens + // Respond to request + // This part will work in mockwebserver 3.6+ + // server.enqueue(new MockResponse().setResponseCode(100).setStatus("Continue")); + + // Finish request + server.enqueue(addCommonHeaders(new MockResponse() + .setResponseCode(201) + .addHeader("ETag", "d9f5eb4bba4e2f2f046e54611bc8196b"))); + + // PUT 2 + // Respond to request + // server.enqueue(new MockResponse().setStatus("HTTP/1.1 100 Continue").clearHeaders()); + // token expired! + server.enqueue(addCommonHeaders(new MockResponse().setResponseCode(401).setBody("401 Unauthorized"))); + // re-auth + server.enqueue(addCommonHeaders(new MockResponse().setBody(stringFromResource("/access2.json")))); + + // Finally success + server.enqueue(addCommonHeaders(new MockResponse() + .setResponseCode(201) + .addHeader("ETag", "d9f5eb4bba4e2f2f046e54611bc8196b"))); + + try { + Properties overrides = new Properties(); + overrides.setProperty(PROPERTY_MAX_RETRIES, 5 + ""); + + SwiftApi api = api(server.getUrl("/").toString(), "openstack-swift", overrides); + assertEquals( + api.getObjectApi("DFW", "myContainer").put("myObject1", PAYLOAD, + metadata(metadata)), "d9f5eb4bba4e2f2f046e54611bc8196b"); + + assertEquals( + api.getObjectApi("DFW", "myContainer").put("myObject2", PAYLOAD, + metadata(metadata)), "d9f5eb4bba4e2f2f046e54611bc8196b"); + + assertEquals(server.getRequestCount(), 5); + + ////// + + // First auth (auth cache empty + assertAuthentication(server); + + // PUT 1 request + RecordedRequest replace = server.takeRequest(); + assertRequest(replace, "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject1"); + + // token expired + + // PUT 2 request + replace = server.takeRequest(); + assertRequest(replace, "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject2"); + + // PUT 2 request re-auth + assertAuthentication(server); + + // PUT 2 request retry + replace = server.takeRequest(); + assertRequest(replace, "PUT", "/v1/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9/myContainer/myObject2"); + + } finally { + server.shutdown(); + } + } + /** upper-cases first char, and lower-cases rest!! **/ public void testGetWithoutKnowingServerMessesWithMetadataKeyCaseFormat() throws Exception { MockWebServer server = mockOpenStackServer(); http://git-wip-us.apache.org/repos/asf/jclouds/blob/00f7ee07/apis/openstack-swift/src/test/resources/access2.json ---------------------------------------------------------------------- diff --git a/apis/openstack-swift/src/test/resources/access2.json b/apis/openstack-swift/src/test/resources/access2.json new file mode 100644 index 0000000..7d0d9f3 --- /dev/null +++ b/apis/openstack-swift/src/test/resources/access2.json @@ -0,0 +1,249 @@ +{ + "access":{ + "token":{ + "id":"bb03a23aa8271291a7bbb9bbb2aaaaaa", + "expires":"2013-08-02T16:55:24.229-05:00", + "tenant":{ + "id":"888888", + "name":"888888" + }, + "RAX-AUTH:authenticatedBy":[ + "PASSWORD" + ] + }, + "serviceCatalog":[ + { + "name":"cloudFilesCDN", + "endpoints":[ + { + "region":"ORD", + "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "publicURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9" + }, + { + "region":"DFW", + "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "publicURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9" + }, + { + "region":"SYD", + "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "publicURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9" + } + ], + "type":"rax:object-cdn" + }, + { + "name":"cloudFiles", + "endpoints":[ + { + "region":"ORD", + "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "publicURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "internalURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9" + }, + { + "region":"DFW", + "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "publicURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "internalURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9" + }, + { + "region":"SYD", + "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "publicURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "internalURL":"URL/v1\/MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9" + } + ], + "type":"object-store" + }, + { + "name":"cloudLoadBalancers", + "endpoints":[ + { + "region":"SYD", + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888" + }, + { + "region":"DFW", + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888" + }, + { + "region":"ORD", + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888" + } + ], + "type":"rax:load-balancer" + }, + { + "name":"cloudDatabases", + "endpoints":[ + { + "region":"SYD", + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888" + }, + { + "region":"DFW", + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888" + }, + { + "region":"ORD", + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888" + } + ], + "type":"rax:database" + }, + { + "name":"cloudBlockStorage", + "endpoints":[ + { + "region":"SYD", + "tenantId":"888888", + "publicURL":"URL/v1\/888888" + }, + { + "region":"DFW", + "tenantId":"888888", + "publicURL":"URL/v1\/888888" + }, + { + "region":"ORD", + "tenantId":"888888", + "publicURL":"URL/v1\/888888" + } + ], + "type":"volume" + }, + { + "name":"cloudServersOpenStack", + "endpoints":[ + { + "region":"SYD", + "tenantId":"888888", + "publicURL":"URL/v2\/888888", + "versionInfo":"https:\/\/syd.servers.api.rackspacecloud.com\/v2", + "versionList":"https:\/\/syd.servers.api.rackspacecloud.com\/", + "versionId":"2" + }, + { + "region":"DFW", + "tenantId":"888888", + "publicURL":"URL/v2\/888888", + "versionInfo":"https:\/\/dfw.servers.api.rackspacecloud.com\/v2", + "versionList":"https:\/\/dfw.servers.api.rackspacecloud.com\/", + "versionId":"2" + }, + { + "region":"ORD", + "tenantId":"888888", + "publicURL":"URL/v2\/888888", + "versionInfo":"https:\/\/ord.servers.api.rackspacecloud.com\/v2", + "versionList":"https:\/\/ord.servers.api.rackspacecloud.com\/", + "versionId":"2" + } + ], + "type":"compute" + }, + { + "name":"autoscale", + "endpoints":[ + { + "region":"ORD", + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888", + "versionInfo":null, + "versionList":null, + "versionId":"1.0" + }, + { + "region":"DFW", + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888", + "versionInfo":null, + "versionList":null, + "versionId":"1.0" + } + ], + "type":"rax:autoscale" + }, + { + "name":"cloudMonitoring", + "endpoints":[ + { + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888" + } + ], + "type":"rax:monitor" + }, + { + "name":"cloudBackup", + "endpoints":[ + { + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888" + } + ], + "type":"rax:backup" + }, + { + "name":"cloudServers", + "endpoints":[ + { + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888", + "versionInfo":"https:\/\/servers.api.rackspacecloud.com\/v1.0", + "versionList":"https:\/\/servers.api.rackspacecloud.com\/", + "versionId":"1.0" + } + ], + "type":"compute" + }, + { + "name":"cloudDNS", + "endpoints":[ + { + "tenantId":"888888", + "publicURL":"URL/v1.0\/888888" + } + ], + "type":"rax:dns" + } + ], + "user":{ + "id":"335853", + "roles":[ + { + "id":"10000150", + "description":"Checkmate Access role", + "name":"checkmate" + }, + { + "tenantId":"MossoCloudFS_5bcf396e-39dd-45ff-93a1-712b9aba90a9", + "id":"5", + "description":"A Role that allows a user access to keystone Service methods", + "name":"object-store:default" + }, + { + "tenantId":"888888", + "id":"6", + "description":"A Role that allows a user access to keystone Service methods", + "name":"compute:default" + }, + { + "id":"3", + "description":"User Admin Role.", + "name":"identity:user-admin" + } + ], + "name":"test", + "RAX-AUTH:defaultRegion":"ORD" + } + } +} http://git-wip-us.apache.org/repos/asf/jclouds/blob/00f7ee07/core/src/main/java/org/jclouds/http/internal/BaseHttpCommandExecutorService.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/jclouds/http/internal/BaseHttpCommandExecutorService.java b/core/src/main/java/org/jclouds/http/internal/BaseHttpCommandExecutorService.java index ad286cc..f92329a 100644 --- a/core/src/main/java/org/jclouds/http/internal/BaseHttpCommandExecutorService.java +++ b/core/src/main/java/org/jclouds/http/internal/BaseHttpCommandExecutorService.java @@ -25,6 +25,7 @@ import static org.jclouds.http.HttpUtils.wirePayloadIfEnabled; import static org.jclouds.util.Throwables2.getFirstThrowableOfType; import java.io.IOException; +import java.net.ProtocolException; import java.util.Set; import javax.annotation.Resource; @@ -145,6 +146,16 @@ public abstract class BaseHttpCommandExecutorService<Q> implements HttpCommandEx } boolean shouldContinue(HttpCommand command, IOException response) { + // Even though Java does not want to handle it this way, + // treat a Protocol Exception on PUT with 100-Continue as a case of Unauthorized (and attempt to retry) + if (command.getCurrentRequest().getMethod().equals("PUT") + && command.getCurrentRequest().getHeaders().containsEntry("Expect", "100-continue") + && response instanceof ProtocolException + && response.getMessage().equals("Server rejected operation") + ) { + logger.debug("Caught a protocol exception on a 100-continue PUT request. Attempting to retry."); + return isIdempotent(command) && retryHandler.shouldRetryRequest(command, HttpResponse.builder().statusCode(401).message("Unauthorized").build()); + } return isIdempotent(command) && ioRetryHandler.shouldRetryRequest(command, response); }