|
18 | 18 | */ |
19 | 19 | package org.elasticsearch.discovery.ec2; |
20 | 20 |
|
21 | | -import com.amazonaws.util.IOUtils; |
22 | | -import com.sun.net.httpserver.Headers; |
23 | | -import com.sun.net.httpserver.HttpExchange; |
24 | | -import com.sun.net.httpserver.HttpHandler; |
25 | | -import com.sun.net.httpserver.HttpServer; |
26 | 21 | import org.apache.http.NameValuePair; |
27 | 22 | import org.apache.http.client.utils.URLEncodedUtils; |
28 | 23 | import org.elasticsearch.common.SuppressForbidden; |
29 | | -import org.elasticsearch.mocksocket.MockHttpServer; |
30 | 24 | import org.elasticsearch.rest.RestStatus; |
| 25 | +import org.elasticsearch.test.fixture.AbstractHttpFixture; |
31 | 26 |
|
32 | 27 | import javax.xml.XMLConstants; |
33 | 28 | import javax.xml.stream.XMLOutputFactory; |
34 | 29 | import javax.xml.stream.XMLStreamWriter; |
35 | 30 | import java.io.IOException; |
36 | 31 | import java.io.StringWriter; |
37 | | -import java.lang.management.ManagementFactory; |
38 | | -import java.net.Inet6Address; |
39 | | -import java.net.InetAddress; |
40 | | -import java.net.InetSocketAddress; |
41 | | -import java.net.SocketAddress; |
42 | | -import java.nio.charset.StandardCharsets; |
43 | 32 | import java.nio.file.Files; |
44 | 33 | import java.nio.file.Path; |
45 | 34 | import java.nio.file.Paths; |
46 | | -import java.nio.file.StandardCopyOption; |
47 | | -import java.util.List; |
| 35 | +import java.util.Objects; |
48 | 36 | import java.util.UUID; |
49 | | -import java.util.function.Predicate; |
50 | 37 |
|
51 | | -import static java.util.Collections.singleton; |
52 | | -import static java.util.Collections.singletonList; |
| 38 | +import static java.nio.charset.StandardCharsets.UTF_8; |
53 | 39 |
|
54 | 40 | /** |
55 | 41 | * {@link AmazonEC2Fixture} is a fixture that emulates an AWS EC2 service. |
56 | | - * <p> |
57 | | - * It starts an asynchronous socket server that binds to a random local port. |
58 | 42 | */ |
59 | | -public class AmazonEC2Fixture { |
| 43 | +public class AmazonEC2Fixture extends AbstractHttpFixture { |
| 44 | + |
| 45 | + private final Path nodes; |
| 46 | + |
| 47 | + private AmazonEC2Fixture(final String workingDir, final String nodesUriPath) { |
| 48 | + super(workingDir); |
| 49 | + this.nodes = toPath(Objects.requireNonNull(nodesUriPath)); |
| 50 | + } |
60 | 51 |
|
61 | 52 | public static void main(String[] args) throws Exception { |
62 | 53 | if (args == null || args.length != 2) { |
63 | 54 | throw new IllegalArgumentException("AmazonEC2Fixture <working directory> <nodes transport uri file>"); |
64 | 55 | } |
65 | 56 |
|
66 | | - final InetSocketAddress socketAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 0); |
67 | | - final HttpServer httpServer = MockHttpServer.createHttp(socketAddress, 0); |
68 | | - |
69 | | - try { |
70 | | - final Path workingDirectory = toPath(args[0]); |
71 | | - /// Writes the PID of the current Java process in a `pid` file located in the working directory |
72 | | - writeFile(workingDirectory, "pid", ManagementFactory.getRuntimeMXBean().getName().split("@")[0]); |
73 | | - |
74 | | - final String addressAndPort = addressToString(httpServer.getAddress()); |
75 | | - // Writes the address and port of the http server in a `ports` file located in the working directory |
76 | | - writeFile(workingDirectory, "ports", addressAndPort); |
77 | | - |
78 | | - httpServer.createContext("/", new ResponseHandler(toPath(args[1]))); |
79 | | - httpServer.start(); |
80 | | - |
81 | | - // Wait to be killed |
82 | | - Thread.sleep(Long.MAX_VALUE); |
83 | | - |
84 | | - } finally { |
85 | | - httpServer.stop(0); |
86 | | - } |
87 | | - } |
88 | | - |
89 | | - @SuppressForbidden(reason = "Paths#get is fine - we don't have environment here") |
90 | | - private static Path toPath(final String dir) { |
91 | | - return Paths.get(dir); |
92 | | - } |
93 | | - |
94 | | - private static void writeFile(final Path dir, final String fileName, final String content) throws IOException { |
95 | | - final Path tempPidFile = Files.createTempFile(dir, null, null); |
96 | | - Files.write(tempPidFile, singleton(content)); |
97 | | - Files.move(tempPidFile, dir.resolve(fileName), StandardCopyOption.ATOMIC_MOVE); |
98 | | - } |
99 | | - |
100 | | - private static String addressToString(final SocketAddress address) { |
101 | | - final InetSocketAddress inetSocketAddress = (InetSocketAddress) address; |
102 | | - if (inetSocketAddress.getAddress() instanceof Inet6Address) { |
103 | | - return "[" + inetSocketAddress.getHostString() + "]:" + inetSocketAddress.getPort(); |
104 | | - } else { |
105 | | - return inetSocketAddress.getHostString() + ":" + inetSocketAddress.getPort(); |
106 | | - } |
| 57 | + final AmazonEC2Fixture fixture = new AmazonEC2Fixture(args[0], args[1]); |
| 58 | + fixture.listen(); |
107 | 59 | } |
108 | 60 |
|
109 | | - static class ResponseHandler implements HttpHandler { |
110 | | - |
111 | | - private final Path discoveryPath; |
112 | | - |
113 | | - ResponseHandler(final Path discoveryPath) { |
114 | | - this.discoveryPath = discoveryPath; |
115 | | - } |
116 | | - |
117 | | - @Override |
118 | | - public void handle(HttpExchange exchange) throws IOException { |
119 | | - RestStatus responseStatus = RestStatus.INTERNAL_SERVER_ERROR; |
120 | | - String responseBody = null; |
121 | | - String responseContentType = "text/plain"; |
122 | | - |
123 | | - final String path = exchange.getRequestURI().getRawPath(); |
124 | | - if ("/".equals(path)) { |
125 | | - final String method = exchange.getRequestMethod(); |
126 | | - final Headers headers = exchange.getRequestHeaders(); |
127 | | - |
128 | | - if ("GET".equals(method) && matchingHeader(headers, "User-agent", v -> v.startsWith("Apache Ant"))) { |
129 | | - // Replies to the fixture's waiting condition |
130 | | - responseStatus = RestStatus.OK; |
131 | | - responseBody = "AmazonEC2Fixture"; |
132 | | - |
133 | | - } else if ("POST".equals(method) && matchingHeader(headers, "User-agent", v -> v.startsWith("aws-sdk-java"))) { |
134 | | - // Simulate an EC2 DescribeInstancesResponse |
135 | | - responseStatus = RestStatus.OK; |
136 | | - responseContentType = "text/xml; charset=UTF-8"; |
137 | | - |
138 | | - for (NameValuePair parse : URLEncodedUtils.parse(IOUtils.toString(exchange.getRequestBody()), StandardCharsets.UTF_8)) { |
139 | | - if ("Action".equals(parse.getName())) { |
140 | | - responseBody = generateDescribeInstancesResponse(); |
141 | | - break; |
142 | | - } |
143 | | - } |
144 | | - } |
145 | | - } |
146 | | - |
147 | | - final byte[] response = responseBody != null ? responseBody.getBytes(StandardCharsets.UTF_8) : new byte[0]; |
148 | | - exchange.sendResponseHeaders(responseStatus.getStatus(), response.length); |
149 | | - exchange.getResponseHeaders().put("Content-Type", singletonList(responseContentType)); |
150 | | - if (response.length > 0) { |
151 | | - exchange.getResponseBody().write(response); |
152 | | - } |
153 | | - exchange.close(); |
154 | | - } |
155 | | - |
156 | | - /** Checks if the given {@link Headers} contains a header with a given name which has a value that matches a predicate **/ |
157 | | - private boolean matchingHeader(final Headers headers, final String headerName, final Predicate<String> predicate) { |
158 | | - if (headers != null && headers.isEmpty() == false) { |
159 | | - final List<String> values = headers.get(headerName); |
160 | | - if (values != null) { |
161 | | - for (String value : values) { |
162 | | - if (predicate.test(value)) { |
163 | | - return true; |
164 | | - } |
| 61 | + @Override |
| 62 | + protected Response handle(final Request request) throws IOException { |
| 63 | + if ("/".equals(request.getPath()) && ("POST".equals(request.getMethod()))) { |
| 64 | + final String userAgent = request.getHeader("User-Agent"); |
| 65 | + if (userAgent != null && userAgent.startsWith("aws-sdk-java")) { |
| 66 | + // Simulate an EC2 DescribeInstancesResponse |
| 67 | + byte[] responseBody = EMPTY_BYTE; |
| 68 | + for (NameValuePair parse : URLEncodedUtils.parse(new String(request.getBody(), UTF_8), UTF_8)) { |
| 69 | + if ("Action".equals(parse.getName())) { |
| 70 | + responseBody = generateDescribeInstancesResponse(); |
| 71 | + break; |
165 | 72 | } |
166 | 73 | } |
| 74 | + return new Response(RestStatus.OK.getStatus(), contentType("text/xml; charset=UTF-8"), responseBody); |
167 | 75 | } |
168 | | - return false; |
169 | 76 | } |
| 77 | + return null; |
| 78 | + } |
170 | 79 |
|
171 | | - /** |
172 | | - * Generates a XML response that describe the EC2 instances |
173 | | - */ |
174 | | - private String generateDescribeInstancesResponse() { |
175 | | - final XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory(); |
176 | | - xmlOutputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); |
177 | | - |
178 | | - final StringWriter out = new StringWriter(); |
179 | | - XMLStreamWriter sw; |
180 | | - try { |
181 | | - sw = xmlOutputFactory.createXMLStreamWriter(out); |
182 | | - sw.writeStartDocument(); |
| 80 | + /** |
| 81 | + * Generates a XML response that describe the EC2 instances |
| 82 | + */ |
| 83 | + private byte[] generateDescribeInstancesResponse() { |
| 84 | + final XMLOutputFactory xmlOutputFactory = XMLOutputFactory.newFactory(); |
| 85 | + xmlOutputFactory.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true); |
183 | 86 |
|
184 | | - String namespace = "http://ec2.amazonaws.com/doc/2013-02-01/"; |
185 | | - sw.setDefaultNamespace(namespace); |
186 | | - sw.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, "DescribeInstancesResponse", namespace); |
| 87 | + final StringWriter out = new StringWriter(); |
| 88 | + XMLStreamWriter sw; |
| 89 | + try { |
| 90 | + sw = xmlOutputFactory.createXMLStreamWriter(out); |
| 91 | + sw.writeStartDocument(); |
| 92 | + |
| 93 | + String namespace = "http://ec2.amazonaws.com/doc/2013-02-01/"; |
| 94 | + sw.setDefaultNamespace(namespace); |
| 95 | + sw.writeStartElement(XMLConstants.DEFAULT_NS_PREFIX, "DescribeInstancesResponse", namespace); |
| 96 | + { |
| 97 | + sw.writeStartElement("requestId"); |
| 98 | + sw.writeCharacters(UUID.randomUUID().toString()); |
| 99 | + sw.writeEndElement(); |
| 100 | + |
| 101 | + sw.writeStartElement("reservationSet"); |
187 | 102 | { |
188 | | - sw.writeStartElement("requestId"); |
189 | | - sw.writeCharacters(UUID.randomUUID().toString()); |
190 | | - sw.writeEndElement(); |
| 103 | + if (Files.exists(nodes)) { |
| 104 | + for (String address : Files.readAllLines(nodes)) { |
191 | 105 |
|
192 | | - sw.writeStartElement("reservationSet"); |
193 | | - { |
194 | | - if (Files.exists(discoveryPath)) { |
195 | | - for (String address : Files.readAllLines(discoveryPath)) { |
| 106 | + sw.writeStartElement("item"); |
| 107 | + { |
| 108 | + sw.writeStartElement("reservationId"); |
| 109 | + sw.writeCharacters(UUID.randomUUID().toString()); |
| 110 | + sw.writeEndElement(); |
196 | 111 |
|
197 | | - sw.writeStartElement("item"); |
| 112 | + sw.writeStartElement("instancesSet"); |
198 | 113 | { |
199 | | - sw.writeStartElement("reservationId"); |
200 | | - sw.writeCharacters(UUID.randomUUID().toString()); |
201 | | - sw.writeEndElement(); |
202 | | - |
203 | | - sw.writeStartElement("instancesSet"); |
| 114 | + sw.writeStartElement("item"); |
204 | 115 | { |
205 | | - sw.writeStartElement("item"); |
206 | | - { |
207 | | - sw.writeStartElement("instanceId"); |
208 | | - sw.writeCharacters(UUID.randomUUID().toString()); |
209 | | - sw.writeEndElement(); |
210 | | - |
211 | | - sw.writeStartElement("imageId"); |
212 | | - sw.writeCharacters(UUID.randomUUID().toString()); |
213 | | - sw.writeEndElement(); |
214 | | - |
215 | | - sw.writeStartElement("instanceState"); |
216 | | - { |
217 | | - sw.writeStartElement("code"); |
218 | | - sw.writeCharacters("16"); |
219 | | - sw.writeEndElement(); |
| 116 | + sw.writeStartElement("instanceId"); |
| 117 | + sw.writeCharacters(UUID.randomUUID().toString()); |
| 118 | + sw.writeEndElement(); |
220 | 119 |
|
221 | | - sw.writeStartElement("name"); |
222 | | - sw.writeCharacters("running"); |
223 | | - sw.writeEndElement(); |
224 | | - } |
225 | | - sw.writeEndElement(); |
| 120 | + sw.writeStartElement("imageId"); |
| 121 | + sw.writeCharacters(UUID.randomUUID().toString()); |
| 122 | + sw.writeEndElement(); |
226 | 123 |
|
227 | | - sw.writeStartElement("privateDnsName"); |
228 | | - sw.writeCharacters(address); |
| 124 | + sw.writeStartElement("instanceState"); |
| 125 | + { |
| 126 | + sw.writeStartElement("code"); |
| 127 | + sw.writeCharacters("16"); |
229 | 128 | sw.writeEndElement(); |
230 | 129 |
|
231 | | - sw.writeStartElement("dnsName"); |
232 | | - sw.writeCharacters(address); |
| 130 | + sw.writeStartElement("name"); |
| 131 | + sw.writeCharacters("running"); |
233 | 132 | sw.writeEndElement(); |
| 133 | + } |
| 134 | + sw.writeEndElement(); |
234 | 135 |
|
235 | | - sw.writeStartElement("instanceType"); |
236 | | - sw.writeCharacters("m1.medium"); |
237 | | - sw.writeEndElement(); |
| 136 | + sw.writeStartElement("privateDnsName"); |
| 137 | + sw.writeCharacters(address); |
| 138 | + sw.writeEndElement(); |
238 | 139 |
|
239 | | - sw.writeStartElement("placement"); |
240 | | - { |
241 | | - sw.writeStartElement("availabilityZone"); |
242 | | - sw.writeCharacters("use-east-1e"); |
243 | | - sw.writeEndElement(); |
| 140 | + sw.writeStartElement("dnsName"); |
| 141 | + sw.writeCharacters(address); |
| 142 | + sw.writeEndElement(); |
244 | 143 |
|
245 | | - sw.writeEmptyElement("groupName"); |
| 144 | + sw.writeStartElement("instanceType"); |
| 145 | + sw.writeCharacters("m1.medium"); |
| 146 | + sw.writeEndElement(); |
246 | 147 |
|
247 | | - sw.writeStartElement("tenancy"); |
248 | | - sw.writeCharacters("default"); |
249 | | - sw.writeEndElement(); |
250 | | - } |
| 148 | + sw.writeStartElement("placement"); |
| 149 | + { |
| 150 | + sw.writeStartElement("availabilityZone"); |
| 151 | + sw.writeCharacters("use-east-1e"); |
251 | 152 | sw.writeEndElement(); |
252 | 153 |
|
253 | | - sw.writeStartElement("privateIpAddress"); |
254 | | - sw.writeCharacters(address); |
255 | | - sw.writeEndElement(); |
| 154 | + sw.writeEmptyElement("groupName"); |
256 | 155 |
|
257 | | - sw.writeStartElement("ipAddress"); |
258 | | - sw.writeCharacters(address); |
| 156 | + sw.writeStartElement("tenancy"); |
| 157 | + sw.writeCharacters("default"); |
259 | 158 | sw.writeEndElement(); |
260 | 159 | } |
261 | 160 | sw.writeEndElement(); |
| 161 | + |
| 162 | + sw.writeStartElement("privateIpAddress"); |
| 163 | + sw.writeCharacters(address); |
| 164 | + sw.writeEndElement(); |
| 165 | + |
| 166 | + sw.writeStartElement("ipAddress"); |
| 167 | + sw.writeCharacters(address); |
| 168 | + sw.writeEndElement(); |
262 | 169 | } |
263 | 170 | sw.writeEndElement(); |
264 | 171 | } |
265 | 172 | sw.writeEndElement(); |
266 | 173 | } |
| 174 | + sw.writeEndElement(); |
267 | 175 | } |
268 | | - sw.writeEndElement(); |
269 | 176 | } |
270 | 177 | sw.writeEndElement(); |
271 | | - |
272 | | - sw.writeEndDocument(); |
273 | | - sw.flush(); |
274 | 178 | } |
275 | | - } catch (Exception e) { |
276 | | - throw new RuntimeException(e); |
| 179 | + sw.writeEndElement(); |
| 180 | + |
| 181 | + sw.writeEndDocument(); |
| 182 | + sw.flush(); |
277 | 183 | } |
278 | | - return out.toString(); |
| 184 | + } catch (Exception e) { |
| 185 | + throw new RuntimeException(e); |
279 | 186 | } |
| 187 | + return out.toString().getBytes(UTF_8); |
| 188 | + } |
| 189 | + |
| 190 | + @SuppressForbidden(reason = "Paths#get is fine - we don't have environment here") |
| 191 | + private static Path toPath(final String dir) { |
| 192 | + return Paths.get(dir); |
280 | 193 | } |
281 | 194 | } |
0 commit comments