Issue 973. Performance problems with Synaptic's Atmos service and writing new blobs

This commit is contained in:
Andrei Savu 2012-10-08 16:26:40 +03:00
parent 5b213c2171
commit 03dc864115
8 changed files with 148 additions and 6 deletions

View File

@ -45,6 +45,7 @@ import org.jclouds.atmos.functions.ParseSystemMetadataFromHeaders;
import org.jclouds.atmos.functions.ParseUserMetadataFromHeaders;
import org.jclouds.atmos.functions.ReturnEndpointIfAlreadyExists;
import org.jclouds.atmos.functions.ReturnTrueIfGroupACLIsOtherRead;
import org.jclouds.atmos.functions.ThrowIllegalStateExceptionOn400;
import org.jclouds.atmos.options.ListOptions;
import org.jclouds.atmos.options.PutOptions;
import org.jclouds.blobstore.functions.ThrowContainerNotFoundOn404;
@ -116,6 +117,7 @@ public interface AtmosAsyncClient {
*/
@POST
@Path("/{parent}/{name}")
@ExceptionParser(ThrowIllegalStateExceptionOn400.class)
@Consumes(MediaType.WILDCARD)
ListenableFuture<URI> createFile(
@PathParam("parent") String parent,

View File

@ -0,0 +1,71 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.atmos.functions;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Throwables;
import com.google.inject.Inject;
import org.jclouds.atmos.domain.AtmosError;
import org.jclouds.atmos.util.AtmosUtils;
import org.jclouds.http.HttpCommand;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpResponseException;
import org.jclouds.http.HttpUtils;
import static com.google.common.base.Preconditions.checkNotNull;
/**
* @author Andrei Savu
* @see Error codes section at <a href="https://www.synaptic.att.com/assets/us/en/home/Atmos_Programmers_Guide_1.3.4A.pdf" />
*/
public class ThrowIllegalStateExceptionOn400 implements Function<Exception, Object> {
private final AtmosUtils utils;
@Inject
public ThrowIllegalStateExceptionOn400(AtmosUtils utils) {
this.utils = checkNotNull(utils, "utils is null");
}
@Override
public Object apply(Exception from) {
if (from instanceof HttpResponseException) {
HttpResponseException exception = (HttpResponseException) from;
if (exception.getResponse().getStatusCode() == 400) {
AtmosError error = parseErrorFromResponse(exception);
if (error.getCode() == 1016) {
throw new IllegalStateException("The resource you are trying to create\n" +
"already exists.", from);
}
}
}
throw Throwables.propagate(from);
}
@VisibleForTesting
protected AtmosError parseErrorFromResponse(HttpResponseException responseException) {
HttpResponse response = responseException.getResponse();
HttpCommand command = responseException.getCommand();
byte[] content = HttpUtils.closeClientButKeepContentStream(response);
return utils.parseAtmosErrorFromContent(command, response, new String(content));
}
}

View File

@ -37,6 +37,7 @@ import com.google.inject.Inject;
/**
* Handles Retryable responses with error codes in the 4xx range
*
* @see Error codes section at <a href="https://www.synaptic.att.com/assets/us/en/home/Atmos_Programmers_Guide_1.3.4A.pdf" />
* @author Adrian Cole
*/
public class AtmosClientErrorRetryHandler implements HttpRetryHandler {
@ -62,14 +63,14 @@ public class AtmosClientErrorRetryHandler implements HttpRetryHandler {
if (response.getStatusCode() == 404 && command.getCurrentRequest().getMethod().equals("DELETE")) {
command.incrementFailureCount();
return true;
} else if (response.getStatusCode() == 409 || response.getStatusCode() == 400) {
} else if (response.getStatusCode() == 409) {
byte[] content = HttpUtils.closeClientButKeepContentStream(response);
// Content can be null in the case of HEAD requests
if (content != null) {
try {
AtmosError error = utils.parseAtmosErrorFromContent(command, response,
new String(content));
if (error.getCode() == 1016) {
if (error.getCode() == 1006) {
return backoffHandler.shouldRetryRequest(command, response);
}
// don't increment count before here, since backoff handler does already

View File

@ -27,6 +27,7 @@ import javax.inject.Provider;
import org.jclouds.atmos.AtmosClient;
import org.jclouds.atmos.blobstore.functions.BlobToObject;
import org.jclouds.atmos.domain.AtmosError;
import org.jclouds.atmos.domain.AtmosObject;
import org.jclouds.atmos.filters.SignRequest;
import org.jclouds.atmos.options.PutOptions;
import org.jclouds.atmos.xml.ErrorHandler;
@ -70,8 +71,15 @@ public class AtmosUtils {
public static String putBlob(final AtmosClient sync, Crypto crypto, BlobToObject blob2Object, String container,
Blob blob, PutOptions options) {
final String path = container + "/" + blob.getMetadata().getName();
final AtmosObject object = blob2Object.apply(blob);
try {
sync.createFile(container, object, options);
} catch(IllegalStateException e) {
deleteAndEnsureGone(sync, path);
sync.createFile(container, blob2Object.apply(blob), options);
sync.createFile(container, object, options);
}
return path;
}

View File

@ -35,6 +35,7 @@ import org.jclouds.atmos.functions.ParseObjectFromHeadersAndHttpContent;
import org.jclouds.atmos.functions.ParseSystemMetadataFromHeaders;
import org.jclouds.atmos.functions.ReturnEndpointIfAlreadyExists;
import org.jclouds.atmos.functions.ReturnTrueIfGroupACLIsOtherRead;
import org.jclouds.atmos.functions.ThrowIllegalStateExceptionOn400;
import org.jclouds.atmos.options.ListOptions;
import org.jclouds.atmos.options.PutOptions;
import org.jclouds.blobstore.binders.BindBlobToMultipartFormTest;
@ -172,7 +173,7 @@ public class AtmosAsyncClientTest extends BaseAsyncClientTest<AtmosAsyncClient>
assertResponseParserClassEquals(method, request, ParseURIFromListOrLocationHeaderIf20x.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, null);
assertExceptionParserClassEquals(method, ThrowIllegalStateExceptionOn400.class);
checkFilters(request);
}
@ -190,7 +191,7 @@ public class AtmosAsyncClientTest extends BaseAsyncClientTest<AtmosAsyncClient>
assertResponseParserClassEquals(method, request, ParseURIFromListOrLocationHeaderIf20x.class);
assertSaxResponseParserClassEquals(method, null);
assertExceptionParserClassEquals(method, null);
assertExceptionParserClassEquals(method, ThrowIllegalStateExceptionOn400.class);
checkFilters(request);
}

View File

@ -32,6 +32,8 @@ public class AtmosLiveTest extends BaseBlobLiveTest {
public AtmosLiveTest() {
provider = "atmos";
}
@Override
protected void checkMD5(String container, String name, byte[] md5) {
// atmos does not support content-md5 yet
assertEquals(view.getBlobStore().blobMetadata(container, name).getContentMetadata().getContentMD5(), null);

View File

@ -0,0 +1,54 @@
/**
* Licensed to jclouds, Inc. (jclouds) under one or more
* contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. jclouds 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.atmos.functions;
import org.jclouds.atmos.domain.AtmosError;
import org.jclouds.atmos.util.AtmosUtils;
import org.jclouds.http.HttpResponse;
import org.jclouds.http.HttpResponseException;
import org.testng.annotations.Test;
/**
* @author Andrei Savu
*/
public class ThrowIllegalStateExceptionOn400Test {
@Test(expectedExceptions = IllegalStateException.class)
public void testResourceAlreadyExists() {
new ThrowIllegalStateExceptionOn400(new AtmosUtils()) {
@Override
protected AtmosError parseErrorFromResponse(HttpResponseException ignore) {
return new AtmosError(1016, "Resource already exists");
}
}.apply(new HttpResponseException("Resource already exists", null,
HttpResponse.builder().statusCode(400).build(), (Throwable) null));
}
@Test(expectedExceptions = RuntimeException.class)
public void testNotFoundPropagates() {
new ThrowIllegalStateExceptionOn400(new AtmosUtils()).apply(new RuntimeException());
}
@Test(expectedExceptions = NullPointerException.class)
public void testNullIsBad() {
new ThrowIllegalStateExceptionOn400(new AtmosUtils()).apply(null);
}
}

View File

@ -29,4 +29,7 @@ import org.testng.annotations.Test;
@Test(groups = "live", sequential = true, testName = "SynapticStorageClientLiveTest")
public class SynapticStorageClientLiveTest extends AtmosClientLiveTest {
public SynapticStorageClientLiveTest() {
provider = "synaptic-storage";
}
}