use of com.aws.greengrass.iot.model.IotCloudResponse in project aws-greengrass-nucleus by aws-greengrass.
the class CredentialRequestHandlerTest method GIVEN_4xx_response_code_WHEN_called_handle_THEN_expire_in_2_minutes.
@Test
@SuppressWarnings("PMD.CloseResource")
void GIVEN_4xx_response_code_WHEN_called_handle_THEN_expire_in_2_minutes() throws Exception {
byte[] response = {};
IotCloudResponse mockResponse = new IotCloudResponse(response, 400);
when(mockCloudHelper.sendHttpRequest(any(), any(), any(), any(), any())).thenReturn(mockResponse);
when(mockAuthNHandler.doAuthentication(anyString())).thenReturn("ServiceA");
when(mockAuthZHandler.isAuthorized(any(), any())).thenReturn(true);
CredentialRequestHandler handler = setupHandler();
handler.handle(mockExchange);
int expectedStatus = 400;
byte[] expectedResponse = String.format("TES responded with status code: %d. Caching response. ", expectedStatus).getBytes();
// expire in 2 minutes
handler.getAwsCredentials();
Instant expirationTime = Instant.now().plus(Duration.ofMinutes(CLOUD_4XX_ERROR_CACHE_IN_MIN));
Clock mockClock = Clock.fixed(expirationTime, ZoneId.of("UTC"));
handler.setClock(mockClock);
handler.getAwsCredentials();
verify(mockExchange, times(1)).sendResponseHeaders(expectedStatus, expectedResponse.length);
verify(mockStream, times(1)).write(expectedResponse);
mockStream.close();
}
use of com.aws.greengrass.iot.model.IotCloudResponse in project aws-greengrass-nucleus by aws-greengrass.
the class CredentialRequestHandlerTest method GIVEN_credential_handler_WHEN_called_handle_THEN_caches_creds.
@Test
@SuppressWarnings("PMD.CloseResource")
void GIVEN_credential_handler_WHEN_called_handle_THEN_caches_creds() throws Exception {
// Expiry time in the past will give 500 error, no caching
Instant expirationTime = Instant.now().minus(Duration.ofMinutes(1));
String responseStr = String.format(RESPONSE_STR, expirationTime.toString());
IotCloudResponse mockResponse = new IotCloudResponse(responseStr.getBytes(StandardCharsets.UTF_8), 200);
when(mockCloudHelper.sendHttpRequest(any(), any(), any(), any(), any())).thenReturn(mockResponse);
when(mockAuthNHandler.doAuthentication(anyString())).thenReturn("ServiceA");
when(mockAuthZHandler.isAuthorized(any(), any())).thenReturn(true);
CredentialRequestHandler handler = setupHandler();
handler.handle(mockExchange);
byte[] expectedResponse = ("TES responded with expired credentials: " + responseStr).getBytes();
int expectedStatus = 500;
verify(mockCloudHelper, times(1)).sendHttpRequest(any(), any(), any(), any(), any());
verify(mockExchange, times(1)).sendResponseHeaders(expectedStatus, expectedResponse.length);
verify(mockStream, times(1)).write(expectedResponse);
// Expiry time in recent future won't give error but there wil be no caching
expirationTime = Instant.now().plus(Duration.ofMinutes(TIME_BEFORE_CACHE_EXPIRE_IN_MIN - 1));
responseStr = String.format(RESPONSE_STR, expirationTime.toString());
mockResponse = new IotCloudResponse(responseStr.getBytes(StandardCharsets.UTF_8), 200);
when(mockCloudHelper.sendHttpRequest(any(), any(), any(), any(), any())).thenReturn(mockResponse);
handler.handle(mockExchange);
verify(mockCloudHelper, times(2)).sendHttpRequest(any(), any(), any(), any(), any());
// Expiry time in future will result in credentials being cached
expirationTime = Instant.now().plus(Duration.ofMinutes(TIME_BEFORE_CACHE_EXPIRE_IN_MIN + 1));
responseStr = String.format(RESPONSE_STR, expirationTime.toString());
mockResponse = new IotCloudResponse(responseStr.getBytes(StandardCharsets.UTF_8), 200);
when(mockCloudHelper.sendHttpRequest(any(), any(), any(), any(), any())).thenReturn(mockResponse);
handler.handle(mockExchange);
verify(mockCloudHelper, times(3)).sendHttpRequest(any(), any(), any(), any(), any());
// Credentials were cached
handler.handle(mockExchange);
verify(mockCloudHelper, times(3)).sendHttpRequest(any(), any(), any(), any(), any());
// Cached credentials expired
Clock mockClock = Clock.fixed(expirationTime, ZoneId.of("UTC"));
handler.setClock(mockClock);
handler.handle(mockExchange);
verify(mockCloudHelper, times(4)).sendHttpRequest(any(), any(), any(), any(), any());
mockStream.close();
}
use of com.aws.greengrass.iot.model.IotCloudResponse in project aws-greengrass-nucleus by aws-greengrass.
the class IotCloudHelperTest method GIVEN_error_code_once_WHEN_send_request_called_THEN_retry_and_success.
@Test
void GIVEN_error_code_once_WHEN_send_request_called_THEN_retry_and_success() throws Exception {
when(mockConnectionManager.getClient()).thenReturn(mockClient);
when(mockConnectionManager.getURI()).thenReturn(HOST);
ExecutableHttpRequest requestMock = mock(ExecutableHttpRequest.class);
when(requestMock.call()).thenThrow(IOException.class).thenReturn(HttpExecuteResponse.builder().response(SdkHttpResponse.builder().statusCode(STATUS_CODE).build()).responseBody(AbortableInputStream.create(new ByteArrayInputStream(CLOUD_RESPONSE))).build());
doReturn(requestMock).when(mockClient).prepareRequest(any());
IotCloudHelper cloudHelper = new IotCloudHelper();
final IotCloudResponse response = cloudHelper.sendHttpRequest(mockConnectionManager, null, IOT_CREDENTIALS_PATH, CredentialRequestHandler.IOT_CREDENTIALS_HTTP_VERB, null);
assertArrayEquals(CLOUD_RESPONSE, response.getResponseBody());
assertEquals(STATUS_CODE, response.getStatusCode());
}
use of com.aws.greengrass.iot.model.IotCloudResponse in project aws-greengrass-nucleus by aws-greengrass.
the class IotCloudHelperTest method GIVEN_valid_creds_WHEN_send_request_called_THEN_success.
@Test
void GIVEN_valid_creds_WHEN_send_request_called_THEN_success() throws Exception {
when(mockConnectionManager.getClient()).thenReturn(mockClient);
when(mockConnectionManager.getURI()).thenReturn(HOST);
ExecutableHttpRequest requestMock = mock(ExecutableHttpRequest.class);
when(requestMock.call()).thenReturn(HttpExecuteResponse.builder().response(SdkHttpResponse.builder().statusCode(STATUS_CODE).build()).responseBody(AbortableInputStream.create(new ByteArrayInputStream(CLOUD_RESPONSE))).build());
doReturn(requestMock).when(mockClient).prepareRequest(any());
IotCloudHelper cloudHelper = new IotCloudHelper();
final IotCloudResponse response = cloudHelper.sendHttpRequest(mockConnectionManager, null, IOT_CREDENTIALS_PATH, CredentialRequestHandler.IOT_CREDENTIALS_HTTP_VERB, null);
assertArrayEquals(CLOUD_RESPONSE, response.getResponseBody());
assertEquals(STATUS_CODE, response.getStatusCode());
}
use of com.aws.greengrass.iot.model.IotCloudResponse in project aws-greengrass-nucleus by aws-greengrass.
the class CredentialRequestHandler method getCredentialsBypassCache.
/**
* API to get credentials while bypassing the caching layer.
*
* @return credentials
*/
private byte[] getCredentialsBypassCache() {
byte[] response;
LOGGER.atDebug().kv(IOT_CRED_PATH_KEY, iotCredentialsPath).log("Got request for credentials, querying iot");
TESCache cacheEntry = tesCache.get(iotCredentialsPath);
// Use the future in order to prevent multiple concurrent requests for the same information.
// If a request is already underway then it should simply wait on the existing future instead of making a
// parallel call to the cloud.
CompletableFuture<Void> future;
synchronized (cacheEntry) {
future = cacheEntry.future.get();
if (future == null || future.isDone()) {
future = new CompletableFuture<>();
tesCache.get(iotCredentialsPath).future.set(future);
}
}
Instant newExpiry = tesCache.get(iotCredentialsPath).expiry;
try {
final IotCloudResponse cloudResponse = iotCloudHelper.sendHttpRequest(iotConnectionManager, thingName, iotCredentialsPath, IOT_CREDENTIALS_HTTP_VERB, null);
final String credentials = cloudResponse.toString();
final int cloudResponseCode = cloudResponse.getStatusCode();
LOGGER.atDebug().kv(IOT_CRED_PATH_KEY, iotCredentialsPath).kv("statusCode", cloudResponseCode).log("Received response from cloud: {}", cloudResponseCode == 200 ? "response code 200, not logging credentials" : credentials);
if (cloudResponseCode == 0) {
// Client errors should expire immediately
String responseString = "Failed to get credentials from TES";
response = responseString.getBytes(StandardCharsets.UTF_8);
newExpiry = Instant.now(clock);
tesCache.get(iotCredentialsPath).responseCode = HttpURLConnection.HTTP_INTERNAL_ERROR;
} else if (cloudResponseCode == HttpURLConnection.HTTP_OK) {
// Get response successfully, cache credentials according to expiry in response
try {
response = translateToAwsSdkFormat(credentials);
String expiryString = parseExpiryFromResponse(credentials);
Instant expiry = Instant.parse(expiryString);
if (expiry.isBefore(Instant.now(clock))) {
String responseString = "TES responded with expired credentials: " + credentials;
response = responseString.getBytes(StandardCharsets.UTF_8);
tesCache.get(iotCredentialsPath).responseCode = HttpURLConnection.HTTP_INTERNAL_ERROR;
LOGGER.atError().kv(IOT_CRED_PATH_KEY, iotCredentialsPath).log("Unable to cache expired credentials which expired at {}", expiry);
} else {
newExpiry = expiry.minus(Duration.ofMinutes(TIME_BEFORE_CACHE_EXPIRE_IN_MIN));
tesCache.get(iotCredentialsPath).responseCode = HttpURLConnection.HTTP_OK;
if (newExpiry.isBefore(Instant.now(clock))) {
LOGGER.atWarn().kv(IOT_CRED_PATH_KEY, iotCredentialsPath).log("Can't cache credentials as new credentials {} will " + "expire in less than {} minutes", expiry, TIME_BEFORE_CACHE_EXPIRE_IN_MIN);
} else {
LOGGER.atInfo().kv(IOT_CRED_PATH_KEY, iotCredentialsPath).log("Received IAM credentials that will be cached until {}", newExpiry);
}
}
} catch (AWSIotException e) {
String responseString = "Bad TES response: " + credentials;
response = responseString.getBytes(StandardCharsets.UTF_8);
tesCache.get(iotCredentialsPath).responseCode = HttpURLConnection.HTTP_INTERNAL_ERROR;
LOGGER.atError().kv(IOT_CRED_PATH_KEY, iotCredentialsPath).log("Unable to parse response body", e);
}
} else {
// Cloud errors should be cached
String responseString = String.format("TES responded with status code: %d. Caching response. %s", cloudResponseCode, credentials);
response = responseString.getBytes(StandardCharsets.UTF_8);
newExpiry = getExpiryPolicyForErr(cloudResponseCode);
tesCache.get(iotCredentialsPath).responseCode = cloudResponseCode;
LOGGER.atError().kv(IOT_CRED_PATH_KEY, iotCredentialsPath).log(responseString);
}
tesCache.get(iotCredentialsPath).expiry = newExpiry;
tesCache.get(iotCredentialsPath).credentials = response;
} catch (AWSIotException e) {
// Http connection error should expire immediately
String responseString = "Failed to get connection";
response = responseString.getBytes(StandardCharsets.UTF_8);
newExpiry = Instant.now(clock);
tesCache.get(iotCredentialsPath).responseCode = HttpURLConnection.HTTP_INTERNAL_ERROR;
tesCache.get(iotCredentialsPath).expiry = newExpiry;
tesCache.get(iotCredentialsPath).credentials = response;
LOGGER.atWarn().kv(IOT_CRED_PATH_KEY, iotCredentialsPath).log("Encountered error while fetching credentials", e);
} finally {
synchronized (cacheEntry) {
// Complete the future to notify listeners that we're done.
// Clear the future so that any new requests trigger an updated request instead of
// pulling from the cache when the cached credentials are invalid
CompletableFuture<Void> oldFuture = tesCache.get(iotCredentialsPath).future.getAndSet(null);
if (oldFuture != null && !oldFuture.isDone()) {
oldFuture.complete(null);
}
}
}
return response;
}
Aggregations