/*
* Copyright 2011-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed 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://aws.amazon.com/apache2.0
*
* This file 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 com.amazonaws.internal;
import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
import static com.github.tomakehurst.wiremock.client.WireMock.get;
import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import org.junit.BeforeClass;
import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.runners.MockitoJUnitRunner;
import com.amazonaws.AmazonClientException;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.retry.internal.CredentialsEndpointRetryParameters;
import com.amazonaws.retry.internal.CredentialsEndpointRetryPolicy;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import utils.http.SocketUtils;
@RunWith(MockitoJUnitRunner.class)
public class EC2CredentialsUtilsTest {
@ClassRule
public static WireMockRule mockServer = new WireMockRule(0);
private static final String CREDENTIALS_PATH = "/dummy/credentials/path";
private static final String SUCCESS_BODY = "{\"AccessKeyId\":\"ACCESS_KEY_ID\",\"SecretAccessKey\":\"SECRET_ACCESS_KEY\","
+ "\"Token\":\"TOKEN_TOKEN_TOKEN\",\"Expiration\":\"3000-05-03T04:55:54Z\"}";
private static URI endpoint;
private static CustomRetryPolicy customRetryPolicy;
private static EC2CredentialsUtils ec2CredentialsUtils;
@Mock
private ConnectionUtils mockConnection;
@BeforeClass
public static void setup() throws URISyntaxException {
endpoint = new URI("http://localhost:" + mockServer.port() + CREDENTIALS_PATH);
customRetryPolicy = new CustomRetryPolicy();
ec2CredentialsUtils = EC2CredentialsUtils.getInstance();
}
/**
* When a connection to end host cannot be opened, throws {@link IOException}.
*/
@Test (expected = IOException.class)
public void readResourceThrowsIOExceptionWhenNoConnection() throws IOException, URISyntaxException {
int port = 0;
try {
port = SocketUtils.getUnusedPort();
} catch (IOException ioexception) {
fail("Unable to find an unused port");
}
ec2CredentialsUtils.readResource(new URI("http://localhost:" + port));
}
/**
* When server returns with status code 200,
* the test successfully returns the body from the response.
*/
@Test
public void readResouceReturnsResponseBodyFor200Response() throws IOException {
generateStub(200, SUCCESS_BODY);
assertEquals(SUCCESS_BODY, ec2CredentialsUtils.readResource(endpoint));
}
/**
* When server returns with 404 status code,
* the test should throw AmazonClientException.
*/
@Test
public void readResouceReturnsAceFor404ErrorResponse() throws Exception {
try {
ec2CredentialsUtils.readResource(new URI("http://localhost:" + mockServer.port() + "/dummyPath"));
fail("Expected AmazonClientException");
} catch (AmazonClientException ace) {
assertTrue(ace.getMessage().contains("The requested metadata is not found at"));
}
}
/**
* When server returns a status code other than 200 and 404,
* the test should throw AmazonServiceException. The request
* is not retried.
*/
@Test
public void readResouceReturnsAseFor5xxResponse() throws IOException {
generateStub(500, "{\"code\":\"500 Internal Server Error\",\"message\":\"ERROR_MESSAGE\"}");
try {
ec2CredentialsUtils.readResource(endpoint);
fail("Expected AmazonServiceException");
} catch (AmazonServiceException ase) {
assertEquals(500, ase.getStatusCode());
assertEquals("500 Internal Server Error", ase.getErrorCode());
assertEquals("ERROR_MESSAGE", ase.getErrorMessage());
}
}
/**
* When server returns a status code other than 200 and 404
* and error body message is not in Json format,
* the test throws AmazonServiceException.
*/
@Test
public void readResouceNonJsonErrorBody() throws IOException {
generateStub(500, "Non Json error body");
try {
ec2CredentialsUtils.readResource(endpoint);
fail("Expected AmazonServiceException");
} catch (AmazonServiceException ase) {
assertEquals(500, ase.getStatusCode());
assertNotNull(ase.getErrorMessage());
}
}
/**
* When readResource is called with default retry policy and IOException occurs,
* the request is not retried.
*/
@Test
public void readResouceWithDefaultRetryPolicy_DoesNotRetry_ForIoException() throws IOException {
Mockito.when(mockConnection.connectToEndpoint(endpoint)).thenThrow(new IOException());
try {
new EC2CredentialsUtils(mockConnection).readResource(endpoint);
fail("Expected an IOexception");
} catch (IOException exception) {
Mockito.verify(mockConnection, Mockito.times(1)).connectToEndpoint(endpoint);
}
}
/**
* When readResource is called with custom retry policy and IOException occurs,
* the request is retried and the number of retries is equal to the value
* returned by getMaxRetries method of the custom retry policy.
*/
@Test
public void readResouceWithCustomRetryPolicy_DoesRetry_ForIoException() throws IOException {
Mockito.when(mockConnection.connectToEndpoint(endpoint)).thenThrow(new IOException());
try {
new EC2CredentialsUtils(mockConnection).readResource(endpoint, customRetryPolicy);
fail("Expected an IOexception");
} catch (IOException exception) {
Mockito.verify(mockConnection, Mockito.times(CustomRetryPolicy.MAX_RETRIES + 1)).connectToEndpoint(endpoint);
}
}
/**
* When readResource is called with custom retry policy
* and the exception is not an IOException,
* then the request is not retried.
*/
@Test
public void readResouceWithCustomRetryPolicy_DoesNotRetry_ForNonIoException() throws IOException {
generateStub(500, "Non Json error body");
Mockito.when(mockConnection.connectToEndpoint(endpoint)).thenCallRealMethod();
try {
new EC2CredentialsUtils(mockConnection).readResource(endpoint, customRetryPolicy);
fail("Expected an AmazonServiceException");
} catch (AmazonServiceException ase) {
Mockito.verify(mockConnection, Mockito.times(1)).connectToEndpoint(endpoint);
}
}
private void generateStub(int statusCode, String message) {
stubFor(
get(urlPathEqualTo(CREDENTIALS_PATH))
.willReturn(aResponse()
.withStatus(statusCode)
.withHeader("Content-Type", "application/json")
.withHeader("charset", "utf-8")
.withBody(message)));
}
/**
* Retry policy that retries only if a request fails with an IOException.
*/
private static class CustomRetryPolicy implements CredentialsEndpointRetryPolicy {
private static final int MAX_RETRIES = 3;
@Override
public boolean shouldRetry(int retriesAttempted, CredentialsEndpointRetryParameters retryParams) {
if (retriesAttempted >= MAX_RETRIES) {
return false;
}
if (retryParams.getException() != null && retryParams.getException() instanceof IOException) {
return true;
}
return false;
}
}
}