Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Push release 0.4.6 to master branch #411

Merged
merged 10 commits into from
Oct 11, 2013
Merged
6 changes: 6 additions & 0 deletions ChangeLog.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
2013.09.30 Version 0.4.6
* Allow users to set the client-request-id for better tracking/debugging of storage requests. This is set on the OperationContext.
* Prevent a potential arithmetic overflow while calculating the exponential retry back-off interval in storage.
* Retry on IOException even when it is wrapped within an XMLStreamException in storage.
* Throw a more meaningful exception when connection is reset while parsing a request response in storage.

2013.08.26 Version 0.4.5
* Added support for managing affinity groups
* Added support for Media Services job notification
Expand Down
2 changes: 1 addition & 1 deletion microsoft-azure-api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.microsoft.windowsazure</groupId>
<artifactId>microsoft-windowsazure-api</artifactId>
<version>0.4.5</version>
<version>0.4.6</version>
<packaging>jar</packaging>

<name>Microsoft Windows Azure Client API</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,11 @@ public static class HeaderConstants {
*/
public static final String REQUEST_ID_HEADER = PREFIX_FOR_STORAGE_HEADER + "request-id";

/**
* The header that indicates the client request ID.
*/
public static final String CLIENT_REQUEST_ID_HEADER = PREFIX_FOR_STORAGE_HEADER + "client-request-id";

/**
* The header for the If-Match condition.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ public final class OperationContext {
/**
* The UUID representing the client side trace ID.
*/
// V2 expose when logging is available.
@SuppressWarnings("unused")
private final String clientTraceID;
private String clientRequestID;

/**
* The Logger object associated with this operation.
Expand Down Expand Up @@ -98,10 +96,17 @@ public final class OperationContext {
* Creates an instance of the <code>OperationContext</code> class.
*/
public OperationContext() {
this.clientTraceID = UUID.randomUUID().toString();
this.clientRequestID = UUID.randomUUID().toString();
this.requestResults = new ArrayList<RequestResult>();
}

/**
* @return the clientRequestID
*/
public String getClientRequestID() {
return this.clientRequestID;
}

/**
* @return the clientTimeInMs
*/
Expand Down Expand Up @@ -201,6 +206,14 @@ public void initialize() {
this.setCurrentRequestObject(null);
}

/**
* @param clientRequestID
* the clientRequestID to set
*/
public void setClientRequestID(final String clientRequestID) {
this.clientRequestID = clientRequestID;
}

/**
* @param clientTimeInMs
* the clientTimeInMs to set
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,14 +126,15 @@ public RetryResult shouldRetry(final int currentRetryCount, final int statusCode
if (currentRetryCount < this.maximumAttempts) {
// Calculate backoff Interval between 80% and 120% of the desired
// backoff, multiply by 2^n -1 for exponential
int incrementDelta = (int) (Math.pow(2, currentRetryCount) - 1);
double incrementDelta = (Math.pow(2, currentRetryCount) - 1);
final int boundedRandDelta = (int) (this.deltaBackoffIntervalInMs * 0.8)
+ this.randRef.nextInt((int) (this.deltaBackoffIntervalInMs * 1.2)
- (int) (this.deltaBackoffIntervalInMs * 0.8));
incrementDelta *= boundedRandDelta;

// Enforce max / min backoffs
return new RetryResult(Math.min(this.resolvedMinBackoff + incrementDelta, this.resolvedMaxBackoff), true);
return new RetryResult((int) Math.round(Math.min(this.resolvedMinBackoff + incrementDelta,
this.resolvedMaxBackoff)), true);
}
else {
return new RetryResult(-1, false);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.SocketException;

import javax.xml.stream.XMLStreamException;

Expand Down Expand Up @@ -89,7 +90,15 @@ public static StorageException translateException(final HttpURLConnection reques
int responseCode = 0;
try {
responseCode = request.getResponseCode();
responseMessage = request.getResponseMessage();

// When the exception is expected(IfNotExists) or we have already parsed the exception, we pass null as
// the cause. In such cases, we should just try to get the message from the request.
if (cause == null || cause instanceof SocketException) {
responseMessage = request.getResponseMessage();
}
else if (cause != null) {
responseMessage = cause.getMessage();
}
}
catch (final IOException e) {
// ignore errors
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ public static HttpURLConnection createURLConnection(final URI uri, final int tim
retConnection.setRequestProperty(Constants.HeaderConstants.STORAGE_VERSION_HEADER,
Constants.HeaderConstants.TARGET_STORAGE_VERSION);
retConnection.setRequestProperty(Constants.HeaderConstants.USER_AGENT, getUserAgent());
retConnection.setRequestProperty(Constants.HeaderConstants.CLIENT_REQUEST_ID_HEADER,
opContext.getClientRequestID());

// Java6 TODO remove me, this has to be manually set or it will
// sometimes default to application/x-www-form-urlencoded without us
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.URISyntaxException;
import java.security.InvalidKeyException;
Expand All @@ -34,6 +35,7 @@
import com.microsoft.windowsazure.services.core.storage.RetryPolicyFactory;
import com.microsoft.windowsazure.services.core.storage.RetryResult;
import com.microsoft.windowsazure.services.core.storage.SendingRequestEvent;
import com.microsoft.windowsazure.services.core.storage.StorageErrorCode;
import com.microsoft.windowsazure.services.core.storage.StorageErrorCodeStrings;
import com.microsoft.windowsazure.services.core.storage.StorageException;
import com.microsoft.windowsazure.services.core.storage.utils.Utility;
Expand Down Expand Up @@ -168,11 +170,23 @@ public static <CLIENT_TYPE, PARENT_TYPE, RESULT_TYPE> RESULT_TYPE executeWithRet
task.getResult().setException(translatedException);
}
catch (final XMLStreamException e) {
// Non Retryable, just throw
translatedException = StorageException
.translateException(getLastRequestObject(opContext), e, opContext);
// Non Retryable except when the inner exception is actually an IOException

// Only in the case of xml exceptions that are due to connection issues.
if (e.getCause() instanceof SocketException) {
translatedException = new StorageException(StorageErrorCode.SERVICE_INTERNAL_ERROR.toString(),
"An unknown failure occurred : ".concat(e.getCause().getMessage()),
HttpURLConnection.HTTP_INTERNAL_ERROR, null, e);
}
else {
translatedException = StorageException.translateException(getLastRequestObject(opContext), e,
opContext);
}

task.getResult().setException(translatedException);
throw translatedException;
if (!(e.getCause() instanceof IOException) && !(e.getCause() instanceof SocketException)) {
throw translatedException;
}
}
catch (final InvalidKeyException e) {
// Non Retryable, just throw
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.microsoft.windowsazure.services.blob.implementation.RFC1123DateConverter;
import com.microsoft.windowsazure.services.core.RFC1123DateConverter;
import com.microsoft.windowsazure.services.core.ServiceFilter;
import com.microsoft.windowsazure.services.core.UserAgentFilter;
import com.microsoft.windowsazure.services.core.utils.pipeline.ClientFilterAdapter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,6 +399,32 @@ public void testBlobLeaseBreak() throws URISyntaxException, StorageException, IO
Assert.assertTrue(operationContext.getLastResult().getStatusCode() == HttpURLConnection.HTTP_ACCEPTED);
}

@Test
public void testListBlobs() throws URISyntaxException, StorageException, IOException, InterruptedException {
final int length = 128;
final Random randGenerator = new Random();
final byte[] buff = new byte[length];
randGenerator.nextBytes(buff);

String blobName = "testBlob" + Integer.toString(randGenerator.nextInt(50000));
blobName = blobName.replace('-', '_');

final CloudBlobContainer existingContainer = bClient.getContainerReference(testSuiteContainerName);
final CloudBlob blobRef = existingContainer.getBlockBlobReference(blobName);
final BlobRequestOptions options = new BlobRequestOptions();
OperationContext context = new OperationContext();
context.setClientRequestID("My-Helpful-TraceId");

// Subscribe to sending request event once we move that. Currently, the sendingrequest event is fired
// after the connection is initiated and we don't have access to the request headers at that time.

blobRef.upload(new ByteArrayInputStream(buff), -1, null, options, context);

for (ListBlobItem blob : existingContainer.listBlobs()) {
Assert.assertEquals(blobRef.getClass(), blob.getClass());
}
}

@Test
public void testBlobLeaseRenew() throws URISyntaxException, StorageException, IOException, InterruptedException {
final int length = 128;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,12 @@
import org.junit.Test;

import com.microsoft.windowsazure.services.core.storage.AuthenticationScheme;
import com.microsoft.windowsazure.services.core.storage.OperationContext;
import com.microsoft.windowsazure.services.core.storage.ResultSegment;
import com.microsoft.windowsazure.services.core.storage.RetryExponentialRetry;
import com.microsoft.windowsazure.services.core.storage.RetryLinearRetry;
import com.microsoft.windowsazure.services.core.storage.RetryPolicy;
import com.microsoft.windowsazure.services.core.storage.RetryResult;
import com.microsoft.windowsazure.services.core.storage.StorageCredentialsSharedAccessSignature;
import com.microsoft.windowsazure.services.core.storage.StorageException;

Expand Down Expand Up @@ -672,4 +677,31 @@ private CloudTableClient getTableForSas(CloudTable table, SharedAccessTablePolic
.generateSharedAccessSignature(policy, accessIdentifier, startPk, startRk, endPk, endRk);
return new CloudTableClient(tClient.getEndpoint(), new StorageCredentialsSharedAccessSignature(sasString));
}

@Test
public void VerifyBackoffTimeOverflow() {
RetryExponentialRetry exponentialRetry = new RetryExponentialRetry(4000, 100000);
VerifyBackoffTimeOverflow(exponentialRetry, 100000);

RetryLinearRetry linearRetry = new RetryLinearRetry(4000, 100000);
VerifyBackoffTimeOverflow(linearRetry, 100000);
}

private void VerifyBackoffTimeOverflow(RetryPolicy retryPolicy, int maxAttempts) {
Exception e = new Exception();
OperationContext context = new OperationContext();
int previousRetryInterval = 1000; // larger than zero to ensure we never get zero back

for (int i = 0; i < maxAttempts; i++) {
RetryResult result = retryPolicy.shouldRetry(i, HttpURLConnection.HTTP_INTERNAL_ERROR, e, context);
int retryInterval = result.getBackOffIntervalInMs();
Assert.assertTrue(String.format("Attempt: '%d'", i), result.isShouldRetry());
Assert.assertTrue(String.format("Retry Interval: '%d', Previous Retry Interval: '%d', Attempt: '%d'",
retryInterval, previousRetryInterval, i), retryInterval >= previousRetryInterval);
previousRetryInterval = retryInterval;
}

Assert.assertFalse(retryPolicy.shouldRetry(maxAttempts, HttpURLConnection.HTTP_INTERNAL_ERROR, e, context)
.isShouldRetry());
}
}