/*
* Copyright 2010-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.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. 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.auth;
import static com.amazonaws.SDKGlobalConfiguration.EC2_METADATA_SERVICE_OVERRIDE_SYSTEM_PROPERTY;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import org.apache.commons.io.IOUtils;
import com.amazonaws.util.EC2MetadataUtils;
/**
* Mock server for imitating the Amazon EC2 Instance Metadata Service. Tests can
* use this class to start up a server on a localhost port, and control what
* response the server will send when a connection is made.
*/
public class EC2MetadataServiceMock {
private EC2MockMetadataServiceListenerThread hosmMockServerThread;
private static final String OUTPUT_HEADERS = "HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: ";
private static final String OUTPUT_END_OF_HEADERS = "\r\n\r\n";
/**
* Sets the name of the file that should be sent back as the response from
* this mock server. The files are loaded from the com/amazonaws/auth
* directory of the tst folder, and no file extension should be specified.
*
* @param responseFileName
* The name of the file that should be sent back as the response
* from this mock server.
*/
public void setResponseFileName(String responseFileName) {
hosmMockServerThread.setResponseFileName(responseFileName);
}
/**
* Accepts a newline delimited list of security credential names that the
* mock metadata service should advertise as available.
*
* @param securityCredentialNames
* A newline delimited list of security credentials that the
* metadata service will advertise as available.
*/
public void setAvailableSecurityCredentials(String securityCredentialNames) {
hosmMockServerThread.setAvailableSecurityCredentials(securityCredentialNames);
}
public void start() throws IOException {
hosmMockServerThread = new EC2MockMetadataServiceListenerThread(startServerSocket());
hosmMockServerThread.start();
}
public void stop() {
hosmMockServerThread.stopServer();
}
private ServerSocket startServerSocket() {
try {
ServerSocket serverSocket = new ServerSocket(0);
System.setProperty(EC2_METADATA_SERVICE_OVERRIDE_SYSTEM_PROPERTY, "http://localhost:" + serverSocket.getLocalPort());
System.out.println("Started mock metadata service at: " + System.getProperty(EC2_METADATA_SERVICE_OVERRIDE_SYSTEM_PROPERTY));
return serverSocket;
} catch (IOException ioe) {
throw new RuntimeException("Unable to start mock EC2 metadata server", ioe);
}
}
/**
* Thread subclass that listens for connections on an opened server socket
* and response with a predefined response file.
*/
private static class EC2MockMetadataServiceListenerThread extends Thread {
private ServerSocket serverSocket;
private String responseFileName;
private String securityCredentialNames;
public EC2MockMetadataServiceListenerThread(ServerSocket serverSocket) {
this.serverSocket = serverSocket;
}
public void setResponseFileName(String responseFileName) {
this.responseFileName = responseFileName;
}
public void setAvailableSecurityCredentials(String securityCredentialNames) {
this.securityCredentialNames = securityCredentialNames;
}
@Override
public void run() {
while (true) {
Socket socket = null;
try {
socket = serverSocket.accept();
} catch (IOException e) {
// Just exit if the socket gets shut down while we're waiting
return;
}
OutputStream outputStream = null;
try {
outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
String requestLine = reader.readLine();
String[] strings = requestLine.split(" ");
String resourcePath = strings[1];
String httpResponse = null;
if (resourcePath.equals(EC2MetadataUtils.SECURITY_CREDENTIALS_RESOURCE)) {
httpResponse = formHttpResponse(securityCredentialNames);
outputStream.write(httpResponse.getBytes());
} else if (resourcePath.startsWith(EC2MetadataUtils.SECURITY_CREDENTIALS_RESOURCE)) {
String responseFilePath = "/com/amazonaws/auth/" + responseFileName + ".json";
System.out.println("Serving: " + responseFilePath);
InputStream responseFileInputStream = this.getClass().getResourceAsStream(responseFilePath);
List<String> dataFromFile = IOUtils.readLines(responseFileInputStream);
StringBuilder credentialsString = new StringBuilder();
for(String line : dataFromFile)
credentialsString.append(line);
httpResponse = formHttpResponse(credentialsString
.toString());
outputStream.write(httpResponse.getBytes());
} else {
throw new RuntimeException("Unknown resource requested: " + resourcePath);
}
} catch (IOException e) {
throw new RuntimeException("Unable to respond to request", e);
} finally {
try {outputStream.close();} catch (Exception e) {}
}
}
}
private String formHttpResponse(String content){
StringBuilder outputStringToWrite = new StringBuilder(
OUTPUT_HEADERS);
outputStringToWrite.append(content.length());
outputStringToWrite.append(OUTPUT_END_OF_HEADERS);
outputStringToWrite.append(content);
return outputStringToWrite.toString();
}
public void stopServer() {
if (serverSocket != null) {
try {
serverSocket.close();
} catch (IOException e) {
throw new RuntimeException("Unable to stop server", e);
}
}
}
}
}