From 455b8b32d21855aa1e2eaa31e7107a757312155a Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Thu, 26 Apr 2018 08:16:39 +0200 Subject: [PATCH 01/31] add OkHttp transport --- core/build.gradle | 7 + .../DockerClientProviderStrategy.java | 4 +- .../transport/OkHttpDockerCmdExecFactory.java | 331 ++++++++++++++++++ core/src/test/resources/logback-test.xml | 10 +- 4 files changed, 344 insertions(+), 8 deletions(-) create mode 100644 core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java diff --git a/core/build.gradle b/core/build.gradle index 4124bf31549..2157ff51de4 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -115,6 +115,13 @@ dependencies { exclude(group: 'log4j') exclude(group: 'com.google.code.findbug') } + shaded ('de.gesellix:docker-filesocket:2018-02-17T21-56-51') { + exclude(group: 'com.squareup.okhttp3') + exclude(group: 'com.squareup.okio') + } + shaded 'com.squareup.okhttp3:okhttp:3.9.1' + shaded 'com.squareup.okio:okio:1.14.0' + shaded 'javax.ws.rs:javax.ws.rs-api:2.0.1' shaded 'org.rnorth:tcp-unix-socket-proxy:1.0.2' shaded 'org.zeroturnaround:zt-exec:1.8' diff --git a/core/src/main/java/org/testcontainers/dockerclient/DockerClientProviderStrategy.java b/core/src/main/java/org/testcontainers/dockerclient/DockerClientProviderStrategy.java index f078aeac5fc..ebe448f2aee 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/DockerClientProviderStrategy.java +++ b/core/src/main/java/org/testcontainers/dockerclient/DockerClientProviderStrategy.java @@ -12,7 +12,7 @@ import org.rnorth.ducttape.unreliables.Unreliables; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.testcontainers.dockerclient.transport.TestcontainersDockerCmdExecFactory; +import org.testcontainers.dockerclient.transport.OkHttpDockerCmdExecFactory; import org.testcontainers.utility.TestcontainersConfiguration; import java.util.ArrayList; @@ -166,7 +166,7 @@ public DockerClient getClient() { protected DockerClient getClientForConfig(DockerClientConfig config) { return DockerClientBuilder .getInstance(config) - .withDockerCmdExecFactory(new TestcontainersDockerCmdExecFactory()) + .withDockerCmdExecFactory(new OkHttpDockerCmdExecFactory()) .build(); } diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java new file mode 100644 index 00000000000..921abd39a08 --- /dev/null +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java @@ -0,0 +1,331 @@ +package org.testcontainers.dockerclient.transport; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.dockerjava.api.async.ResultCallback; +import com.github.dockerjava.api.command.PingCmd; +import com.github.dockerjava.api.model.Frame; +import com.github.dockerjava.core.AbstractDockerCmdExecFactory; +import com.github.dockerjava.core.InvocationBuilder; +import com.github.dockerjava.core.WebTarget; +import com.github.dockerjava.core.exec.PingCmdExec; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.MultimapBuilder; +import com.google.common.collect.SetMultimap; +import de.gesellix.docker.client.filesocket.UnixSocket; +import de.gesellix.docker.client.filesocket.UnixSocketFactory; +import lombok.SneakyThrows; +import lombok.Value; +import lombok.experimental.Wither; +import okhttp3.*; +import okio.BufferedSink; +import okio.Okio; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang.StringUtils; + +import javax.annotation.Nullable; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.util.*; +import java.util.stream.Collectors; + +public class OkHttpDockerCmdExecFactory extends AbstractDockerCmdExecFactory { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + @Override + protected WebTarget getBaseResource() { + UnixSocketFactory unixSocketFactory = new UnixSocketFactory(); + OkHttpClient okHttpClient = new OkHttpClient.Builder() + .socketFactory(unixSocketFactory) + .dns(unixSocketFactory) + .build(); + + return new OkHttpWebTarget( + getDockerClientConfig().getDockerHost(), + okHttpClient, + ImmutableList.of(), + MultimapBuilder.hashKeys().hashSetValues().build() + ); + } + + @Override + public PingCmd.Exec createPingCmdExec() { + return new PingCmdExec(getBaseResource(), getDockerClientConfig()) { + + @Override + protected Void execute(PingCmd command) { + WebTarget webResource = getBaseResource().path("/_ping"); + + // TODO contribute to docker-java, make it close the stream + IOUtils.closeQuietly(webResource.request().get()); + + return null; + } + }; + } + + @Override + public void close() throws IOException { + + } + + @Wither + @Value + private static class OkHttpWebTarget implements WebTarget { + + URI dockerHost; + + OkHttpClient okHttpClient; + + ImmutableList path; + + SetMultimap queryParams; + + @Override + public InvocationBuilder request() { + String resource = StringUtils.join(path, "/"); + + if (!resource.startsWith("/")) { + resource = "/" + resource; + } + + HttpUrl.Builder urlBuilder = new HttpUrl.Builder(); + + switch (dockerHost.getScheme()) { + case "unix": + urlBuilder + .scheme("http") + .host(new UnixSocket().encodeHostname(dockerHost.getPath())) + .encodedPath(resource); + break; + default: + throw new IllegalStateException("Unknown scheme, URI: " + dockerHost); + } + + for (Map.Entry> queryParamEntry : queryParams.asMap().entrySet()) { + String key = queryParamEntry.getKey(); + for (String paramValue : queryParamEntry.getValue()) { + urlBuilder.addQueryParameter(key, paramValue); + } + } + + HttpUrl url = urlBuilder.build(); + return new OkHttpInvocationBuilder(okHttpClient, url, Collections.emptyMap()); + } + + @Override + public OkHttpWebTarget path(String... components) { + return this.withPath( + ImmutableList.builder() + .addAll(path) + .add(components) + .build() + ); + } + + @Override + public OkHttpWebTarget resolveTemplate(String name, Object value) { + ImmutableList.Builder newPath = ImmutableList.builder(); + for (String component : path) { + component = component.replaceAll("\\{" + name + "\\}", value.toString()); + newPath.add(component); + } + return this.withPath(newPath.build()); + } + + @Override + public OkHttpWebTarget queryParam(String name, Object value) { + SetMultimap newQueryParams = HashMultimap.create(queryParams); + newQueryParams.put(name, value.toString()); + + return this.withQueryParams(newQueryParams); + } + + @Override + public OkHttpWebTarget queryParamsSet(String name, Set values) { + SetMultimap newQueryParams = HashMultimap.create(queryParams); + newQueryParams.replaceValues(name, values.stream().map(Object::toString).collect(Collectors.toSet())); + + return this.withQueryParams(newQueryParams); + } + + @Override + @SneakyThrows(JsonProcessingException.class) + public OkHttpWebTarget queryParamsJsonMap(String name, Map values) { + if (values == null || values.isEmpty()) { + return this; + } + + // when param value is JSON string + return queryParam(name, MAPPER.writeValueAsString(values)); + } + + @Value + @Wither + private static class OkHttpInvocationBuilder implements InvocationBuilder { + + OkHttpClient okHttpClient; + + HttpUrl httpUrl; + + Map headers; + + @Override + public OkHttpInvocationBuilder accept(com.github.dockerjava.core.MediaType mediaType) { + return header("accept", mediaType.getMediaType()); + } + + @Override + public OkHttpInvocationBuilder header(String name, String value) { + Map newHeaders = new HashMap<>(headers); + newHeaders.put(name, value); + return this.withHeaders(newHeaders); + } + + @Override + @SneakyThrows(IOException.class) + public void delete() { + Request request = new Request.Builder().url(httpUrl).delete().build(); + try (Response response = okHttpClient.newCall(request).execute()) { + + } + } + + @Override + public void get(ResultCallback resultCallback) { + // FIXME + return; + } + + @Override + @SneakyThrows(IOException.class) + public T get(TypeReference typeReference) { + try (InputStream inputStream = get()) { + return MAPPER.readValue(inputStream, typeReference); + } + } + + @Override + public void get(TypeReference typeReference, ResultCallback resultCallback) { + // FIXME + throw new IllegalStateException("doesn't seem to be used in docker-java"); + } + + @Override + @SneakyThrows(IOException.class) + public InputStream post(Object entity) { + Request request = new Request.Builder() + .url(httpUrl) + .post(RequestBody.create(null, MAPPER.writeValueAsBytes(entity))) + .build(); + + Response response = okHttpClient.newCall(request).execute(); + return response.body().byteStream(); + } + + @Override + @SneakyThrows(IOException.class) + public T post(Object entity, TypeReference typeReference) { + Request request = new Request.Builder() + .url(httpUrl) + .post(RequestBody.create(MediaType.parse("application/json"), MAPPER.writeValueAsBytes(entity))) + .build(); + + try ( + Response response = okHttpClient.newCall(request).execute(); + ) { + String inputStream = response.body().string(); + return MAPPER.readValue(inputStream, typeReference); + } + } + + @Override + @SneakyThrows(JsonProcessingException.class) + public void post(Object entity, TypeReference typeReference, ResultCallback resultCallback) { + post(typeReference, resultCallback, new ByteArrayInputStream(MAPPER.writeValueAsBytes(entity))); + } + + @Override + @SneakyThrows(IOException.class) + public T post(TypeReference typeReference, InputStream body) { + try (InputStream inputStream = post(body)) { + return MAPPER.readValue(inputStream, typeReference); + } + } + + @Override + public void post(Object entity, InputStream stdin, ResultCallback resultCallback) { + // FIXME + return; + } + + @Override + public void post(TypeReference typeReference, ResultCallback resultCallback, InputStream body) { + // FIXME + return; + } + + @Override + @SneakyThrows(IOException.class) + public void postStream(InputStream body) { + Request request = new Request.Builder() + .url(httpUrl) + .post(toRequestBody(body, null)) + .build(); + + try (Response response = okHttpClient.newCall(request).execute()) { + + } + } + + @Override + @SneakyThrows(IOException.class) + public InputStream get() { + Request request = new Request.Builder() + .url(httpUrl) + .get() + .build(); + + Response response = okHttpClient.newCall(request).execute(); + return response.body().byteStream(); + } + + @Override + @SneakyThrows(IOException.class) + public void put(InputStream body, com.github.dockerjava.core.MediaType mediaType) { + Request request = new Request.Builder() + .url(httpUrl) + .put(toRequestBody(body, mediaType.toString())) + .build(); + + try (Response response = okHttpClient.newCall(request).execute()) { + + } + } + + protected RequestBody toRequestBody(InputStream body, @Nullable String mediaType) { + return new RequestBody() { + @Nullable + @Override + public okhttp3.MediaType contentType() { + if (mediaType == null) { + return null; + } + return okhttp3.MediaType.parse(mediaType); + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + sink.writeAll(Okio.source(body)); + + } + }; + } + } + } +} diff --git a/core/src/test/resources/logback-test.xml b/core/src/test/resources/logback-test.xml index ed0e5b2659e..42a23f5fe13 100644 --- a/core/src/test/resources/logback-test.xml +++ b/core/src/test/resources/logback-test.xml @@ -8,22 +8,20 @@ - + + + - - - - PROFILER DENY - \ No newline at end of file + From a0bde0c04198a2a5cc02f1985c49191770d76a14 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Sat, 19 May 2018 22:56:33 +0200 Subject: [PATCH 02/31] implement chunked response handling based on Netty implementation --- .../transport/OkHttpDockerCmdExecFactory.java | 107 ++++++++++++++++-- 1 file changed, 99 insertions(+), 8 deletions(-) diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java index 921abd39a08..ebcfb4282b5 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java @@ -10,25 +10,34 @@ import com.github.dockerjava.core.InvocationBuilder; import com.github.dockerjava.core.WebTarget; import com.github.dockerjava.core.exec.PingCmdExec; +import com.github.dockerjava.netty.handler.FramedResponseStreamHandler; +import com.github.dockerjava.netty.handler.JsonResponseCallbackHandler; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.MultimapBuilder; import com.google.common.collect.SetMultimap; import de.gesellix.docker.client.filesocket.UnixSocket; import de.gesellix.docker.client.filesocket.UnixSocketFactory; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.SimpleChannelInboundHandler; import lombok.SneakyThrows; import lombok.Value; import lombok.experimental.Wither; import okhttp3.*; +import okhttp3.internal.connection.RealConnection; import okio.BufferedSink; +import okio.BufferedSource; import okio.Okio; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; +import org.testcontainers.DockerClientFactory; import javax.annotation.Nullable; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; +import java.lang.reflect.Field; import java.net.URI; import java.util.*; import java.util.stream.Collectors; @@ -140,6 +149,10 @@ public OkHttpWebTarget resolveTemplate(String name, Object value) { @Override public OkHttpWebTarget queryParam(String name, Object value) { + if (value == null) { + return this; + } + SetMultimap newQueryParams = HashMultimap.create(queryParams); newQueryParams.put(name, value.toString()); @@ -149,7 +162,7 @@ public OkHttpWebTarget queryParam(String name, Object value) { @Override public OkHttpWebTarget queryParamsSet(String name, Set values) { SetMultimap newQueryParams = HashMultimap.create(queryParams); - newQueryParams.replaceValues(name, values.stream().map(Object::toString).collect(Collectors.toSet())); + newQueryParams.replaceValues(name, values.stream().filter(Objects::nonNull).map(Object::toString).collect(Collectors.toSet())); return this.withQueryParams(newQueryParams); } @@ -198,8 +211,16 @@ public void delete() { @Override public void get(ResultCallback resultCallback) { - // FIXME - return; + Request request = new Request.Builder() + .url(httpUrl) + .get() + .build(); + + handleStreamedResponse( + okHttpClient.newCall(request), + resultCallback, + new FramedResponseStreamHandler(resultCallback) + ); } @Override @@ -259,15 +280,85 @@ public T post(TypeReference typeReference, InputStream body) { } @Override + @SneakyThrows(IOException.class) public void post(Object entity, InputStream stdin, ResultCallback resultCallback) { - // FIXME - return; + + Request request = new Request.Builder() + .url(httpUrl) + .post(RequestBody.create(MediaType.parse("application/json"), MAPPER.writeValueAsBytes(entity))) + .build(); + + OkHttpClient okHttpClient = this.okHttpClient; + + if (stdin != null) { + // FIXME there must be a better way of handling it + okHttpClient = okHttpClient.newBuilder() + .addNetworkInterceptor(new Interceptor() { + @Override + @SneakyThrows + public Response intercept(Chain chain) { + RealConnection connection = (RealConnection) chain.connection(); + + Field sinkField = RealConnection.class.getDeclaredField("sink"); + sinkField.setAccessible(true); + BufferedSink sink = (BufferedSink) sinkField.get(connection); + + Thread thread = new Thread(DockerClientFactory.TESTCONTAINERS_THREAD_GROUP, () -> { + try { + sink.writeAll(Okio.source(stdin)); + sink.flush(); + + Thread.sleep(100); + sink.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + thread.start(); + + return chain.proceed(chain.request()); + } + }) + .build(); + } + + handleStreamedResponse( + okHttpClient.newCall(request), + resultCallback, + new FramedResponseStreamHandler(resultCallback) + ); } @Override public void post(TypeReference typeReference, ResultCallback resultCallback, InputStream body) { - // FIXME - return; + Request request = new Request.Builder() + .url(httpUrl) + .post(toRequestBody(body, null)) + .build(); + + handleStreamedResponse( + okHttpClient.newCall(request), + resultCallback, + new JsonResponseCallbackHandler(typeReference, resultCallback) + ); + } + + protected void handleStreamedResponse(Call call, ResultCallback callback, SimpleChannelInboundHandler handler) { + try (Response response = call.execute()) { + callback.onStart(response); + BufferedSource source = response.body().source(); + + byte[] buffer = new byte[4 * 1024]; + while (!source.exhausted()) { + InputStream inputStream = source.inputStream(); + int bytesReaded = inputStream.read(buffer); + + handler.channelRead(null, Unpooled.wrappedBuffer(buffer, 0, bytesReaded)); + } + callback.onComplete(); + } catch (Exception e) { + callback.onError(e); + } } @Override @@ -297,7 +388,7 @@ public InputStream get() { @Override @SneakyThrows(IOException.class) - public void put(InputStream body, com.github.dockerjava.core.MediaType mediaType) { + public void put(InputStream body, com.github.dockerjava.core.MediaType mediaType) { Request request = new Request.Builder() .url(httpUrl) .put(toRequestBody(body, mediaType.toString())) From 85a84c2ff0f3959d1a7a94a7e61741b9f7cb9509 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Sat, 19 May 2018 23:13:32 +0200 Subject: [PATCH 03/31] handle response errors --- .../transport/OkHttpDockerCmdExecFactory.java | 64 ++++++++++++++----- 1 file changed, 47 insertions(+), 17 deletions(-) diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java index ebcfb4282b5..8072a02522a 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.github.dockerjava.api.async.ResultCallback; import com.github.dockerjava.api.command.PingCmd; +import com.github.dockerjava.api.exception.*; import com.github.dockerjava.api.model.Frame; import com.github.dockerjava.core.AbstractDockerCmdExecFactory; import com.github.dockerjava.core.InvocationBuilder; @@ -201,10 +202,9 @@ public OkHttpInvocationBuilder header(String name, String value) { } @Override - @SneakyThrows(IOException.class) public void delete() { Request request = new Request.Builder().url(httpUrl).delete().build(); - try (Response response = okHttpClient.newCall(request).execute()) { + try (Response response = execute(request)) { } } @@ -217,7 +217,7 @@ public void get(ResultCallback resultCallback) { .build(); handleStreamedResponse( - okHttpClient.newCall(request), + execute(request), resultCallback, new FramedResponseStreamHandler(resultCallback) ); @@ -245,7 +245,7 @@ public InputStream post(Object entity) { .post(RequestBody.create(null, MAPPER.writeValueAsBytes(entity))) .build(); - Response response = okHttpClient.newCall(request).execute(); + Response response = execute(request); return response.body().byteStream(); } @@ -257,9 +257,7 @@ public T post(Object entity, TypeReference typeReference) { .post(RequestBody.create(MediaType.parse("application/json"), MAPPER.writeValueAsBytes(entity))) .build(); - try ( - Response response = okHttpClient.newCall(request).execute(); - ) { + try (Response response = execute(request)) { String inputStream = response.body().string(); return MAPPER.readValue(inputStream, typeReference); } @@ -323,7 +321,7 @@ public Response intercept(Chain chain) { } handleStreamedResponse( - okHttpClient.newCall(request), + execute(request), resultCallback, new FramedResponseStreamHandler(resultCallback) ); @@ -337,14 +335,14 @@ public void post(TypeReference typeReference, ResultCallback resultCal .build(); handleStreamedResponse( - okHttpClient.newCall(request), + execute(request), resultCallback, new JsonResponseCallbackHandler(typeReference, resultCallback) ); } - protected void handleStreamedResponse(Call call, ResultCallback callback, SimpleChannelInboundHandler handler) { - try (Response response = call.execute()) { + protected void handleStreamedResponse(Response response, ResultCallback callback, SimpleChannelInboundHandler handler) { + try { callback.onStart(response); BufferedSource source = response.body().source(); @@ -358,43 +356,42 @@ protected void handleStreamedResponse(Call call, ResultCallback callback, callback.onComplete(); } catch (Exception e) { callback.onError(e); + } finally { + response.close(); } } @Override - @SneakyThrows(IOException.class) public void postStream(InputStream body) { Request request = new Request.Builder() .url(httpUrl) .post(toRequestBody(body, null)) .build(); - try (Response response = okHttpClient.newCall(request).execute()) { + try (Response response = execute(request)) { } } @Override - @SneakyThrows(IOException.class) public InputStream get() { Request request = new Request.Builder() .url(httpUrl) .get() .build(); - Response response = okHttpClient.newCall(request).execute(); + Response response = execute(request); return response.body().byteStream(); } @Override - @SneakyThrows(IOException.class) public void put(InputStream body, com.github.dockerjava.core.MediaType mediaType) { Request request = new Request.Builder() .url(httpUrl) .put(toRequestBody(body, mediaType.toString())) .build(); - try (Response response = okHttpClient.newCall(request).execute()) { + try (Response response = execute(request)) { } } @@ -417,6 +414,39 @@ public void writeTo(BufferedSink sink) throws IOException { } }; } + + @SneakyThrows(IOException.class) + protected Response execute(Request request) { + Response response = okHttpClient.newCall(request).execute(); + int code = response.code(); + if (code < 200 || code >= 300) { + try { + String body = response.body().string(); + switch (code) { + case 304: + throw new NotModifiedException(body); + case 400: + throw new BadRequestException(body); + case 401: + throw new UnauthorizedException(body); + case 404: + throw new NotFoundException(body); + case 406: + throw new NotAcceptableException(body); + case 409: + throw new ConflictException(body); + case 500: + throw new InternalServerErrorException(body); + default: + throw new DockerException(body, code); + } + } finally { + response.close(); + } + } else { + return response; + } + } } } } From d6a59bfafe9d35856c3a29581336b0db3eaafd36 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Sun, 20 May 2018 11:05:44 +0200 Subject: [PATCH 04/31] do not block caller's thread --- .../transport/OkHttpDockerCmdExecFactory.java | 33 ++++++++++++------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java index 8072a02522a..5bb87ec27a9 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java @@ -343,21 +343,32 @@ public void post(TypeReference typeReference, ResultCallback resultCal protected void handleStreamedResponse(Response response, ResultCallback callback, SimpleChannelInboundHandler handler) { try { - callback.onStart(response); - BufferedSource source = response.body().source(); + // TODO proper thread management + Thread thread = new Thread(new Runnable() { + @Override + @SneakyThrows + public void run() { + BufferedSource source = response.body().source(); + InputStream inputStream = source.inputStream(); + + byte[] buffer = new byte[4 * 1024]; + while (!source.exhausted() && !Thread.interrupted()) { + int bytesReceived = inputStream.read(buffer); + + handler.channelRead(null, Unpooled.wrappedBuffer(buffer, 0, bytesReceived)); + } + callback.onComplete(); + } + }); - byte[] buffer = new byte[4 * 1024]; - while (!source.exhausted()) { - InputStream inputStream = source.inputStream(); - int bytesReaded = inputStream.read(buffer); + callback.onStart(() -> { + thread.interrupt(); + response.close(); + }); - handler.channelRead(null, Unpooled.wrappedBuffer(buffer, 0, bytesReaded)); - } - callback.onComplete(); + thread.start(); } catch (Exception e) { callback.onError(e); - } finally { - response.close(); } } From ebd492091e649abc7eca2c8456cd4f7a981bcae2 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Sun, 20 May 2018 13:59:47 +0200 Subject: [PATCH 05/31] split files, support more protocols, fix blocking call --- .../transport/OkHttpDockerCmdExecFactory.java | 421 +----------------- .../okhttp/OkHttpInvocationBuilder.java | 344 ++++++++++++++ .../transport/okhttp/OkHttpWebTarget.java | 175 ++++++++ 3 files changed, 521 insertions(+), 419 deletions(-) create mode 100644 core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java create mode 100644 core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpWebTarget.java diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java index 5bb87ec27a9..bba09f8ebf1 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java @@ -1,63 +1,22 @@ package org.testcontainers.dockerclient.transport; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.dockerjava.api.async.ResultCallback; import com.github.dockerjava.api.command.PingCmd; -import com.github.dockerjava.api.exception.*; -import com.github.dockerjava.api.model.Frame; import com.github.dockerjava.core.AbstractDockerCmdExecFactory; -import com.github.dockerjava.core.InvocationBuilder; import com.github.dockerjava.core.WebTarget; import com.github.dockerjava.core.exec.PingCmdExec; -import com.github.dockerjava.netty.handler.FramedResponseStreamHandler; -import com.github.dockerjava.netty.handler.JsonResponseCallbackHandler; -import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.MultimapBuilder; -import com.google.common.collect.SetMultimap; -import de.gesellix.docker.client.filesocket.UnixSocket; -import de.gesellix.docker.client.filesocket.UnixSocketFactory; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import io.netty.channel.SimpleChannelInboundHandler; -import lombok.SneakyThrows; -import lombok.Value; -import lombok.experimental.Wither; -import okhttp3.*; -import okhttp3.internal.connection.RealConnection; -import okio.BufferedSink; -import okio.BufferedSource; -import okio.Okio; import org.apache.commons.io.IOUtils; -import org.apache.commons.lang.StringUtils; -import org.testcontainers.DockerClientFactory; +import org.testcontainers.dockerclient.transport.okhttp.OkHttpWebTarget; -import javax.annotation.Nullable; -import java.io.ByteArrayInputStream; import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.net.URI; -import java.util.*; -import java.util.stream.Collectors; public class OkHttpDockerCmdExecFactory extends AbstractDockerCmdExecFactory { - private static final ObjectMapper MAPPER = new ObjectMapper(); - @Override protected WebTarget getBaseResource() { - UnixSocketFactory unixSocketFactory = new UnixSocketFactory(); - OkHttpClient okHttpClient = new OkHttpClient.Builder() - .socketFactory(unixSocketFactory) - .dns(unixSocketFactory) - .build(); - return new OkHttpWebTarget( - getDockerClientConfig().getDockerHost(), - okHttpClient, + getDockerClientConfig(), ImmutableList.of(), MultimapBuilder.hashKeys().hashSetValues().build() ); @@ -84,380 +43,4 @@ public void close() throws IOException { } - @Wither - @Value - private static class OkHttpWebTarget implements WebTarget { - - URI dockerHost; - - OkHttpClient okHttpClient; - - ImmutableList path; - - SetMultimap queryParams; - - @Override - public InvocationBuilder request() { - String resource = StringUtils.join(path, "/"); - - if (!resource.startsWith("/")) { - resource = "/" + resource; - } - - HttpUrl.Builder urlBuilder = new HttpUrl.Builder(); - - switch (dockerHost.getScheme()) { - case "unix": - urlBuilder - .scheme("http") - .host(new UnixSocket().encodeHostname(dockerHost.getPath())) - .encodedPath(resource); - break; - default: - throw new IllegalStateException("Unknown scheme, URI: " + dockerHost); - } - - for (Map.Entry> queryParamEntry : queryParams.asMap().entrySet()) { - String key = queryParamEntry.getKey(); - for (String paramValue : queryParamEntry.getValue()) { - urlBuilder.addQueryParameter(key, paramValue); - } - } - - HttpUrl url = urlBuilder.build(); - return new OkHttpInvocationBuilder(okHttpClient, url, Collections.emptyMap()); - } - - @Override - public OkHttpWebTarget path(String... components) { - return this.withPath( - ImmutableList.builder() - .addAll(path) - .add(components) - .build() - ); - } - - @Override - public OkHttpWebTarget resolveTemplate(String name, Object value) { - ImmutableList.Builder newPath = ImmutableList.builder(); - for (String component : path) { - component = component.replaceAll("\\{" + name + "\\}", value.toString()); - newPath.add(component); - } - return this.withPath(newPath.build()); - } - - @Override - public OkHttpWebTarget queryParam(String name, Object value) { - if (value == null) { - return this; - } - - SetMultimap newQueryParams = HashMultimap.create(queryParams); - newQueryParams.put(name, value.toString()); - - return this.withQueryParams(newQueryParams); - } - - @Override - public OkHttpWebTarget queryParamsSet(String name, Set values) { - SetMultimap newQueryParams = HashMultimap.create(queryParams); - newQueryParams.replaceValues(name, values.stream().filter(Objects::nonNull).map(Object::toString).collect(Collectors.toSet())); - - return this.withQueryParams(newQueryParams); - } - - @Override - @SneakyThrows(JsonProcessingException.class) - public OkHttpWebTarget queryParamsJsonMap(String name, Map values) { - if (values == null || values.isEmpty()) { - return this; - } - - // when param value is JSON string - return queryParam(name, MAPPER.writeValueAsString(values)); - } - - @Value - @Wither - private static class OkHttpInvocationBuilder implements InvocationBuilder { - - OkHttpClient okHttpClient; - - HttpUrl httpUrl; - - Map headers; - - @Override - public OkHttpInvocationBuilder accept(com.github.dockerjava.core.MediaType mediaType) { - return header("accept", mediaType.getMediaType()); - } - - @Override - public OkHttpInvocationBuilder header(String name, String value) { - Map newHeaders = new HashMap<>(headers); - newHeaders.put(name, value); - return this.withHeaders(newHeaders); - } - - @Override - public void delete() { - Request request = new Request.Builder().url(httpUrl).delete().build(); - try (Response response = execute(request)) { - - } - } - - @Override - public void get(ResultCallback resultCallback) { - Request request = new Request.Builder() - .url(httpUrl) - .get() - .build(); - - handleStreamedResponse( - execute(request), - resultCallback, - new FramedResponseStreamHandler(resultCallback) - ); - } - - @Override - @SneakyThrows(IOException.class) - public T get(TypeReference typeReference) { - try (InputStream inputStream = get()) { - return MAPPER.readValue(inputStream, typeReference); - } - } - - @Override - public void get(TypeReference typeReference, ResultCallback resultCallback) { - // FIXME - throw new IllegalStateException("doesn't seem to be used in docker-java"); - } - - @Override - @SneakyThrows(IOException.class) - public InputStream post(Object entity) { - Request request = new Request.Builder() - .url(httpUrl) - .post(RequestBody.create(null, MAPPER.writeValueAsBytes(entity))) - .build(); - - Response response = execute(request); - return response.body().byteStream(); - } - - @Override - @SneakyThrows(IOException.class) - public T post(Object entity, TypeReference typeReference) { - Request request = new Request.Builder() - .url(httpUrl) - .post(RequestBody.create(MediaType.parse("application/json"), MAPPER.writeValueAsBytes(entity))) - .build(); - - try (Response response = execute(request)) { - String inputStream = response.body().string(); - return MAPPER.readValue(inputStream, typeReference); - } - } - - @Override - @SneakyThrows(JsonProcessingException.class) - public void post(Object entity, TypeReference typeReference, ResultCallback resultCallback) { - post(typeReference, resultCallback, new ByteArrayInputStream(MAPPER.writeValueAsBytes(entity))); - } - - @Override - @SneakyThrows(IOException.class) - public T post(TypeReference typeReference, InputStream body) { - try (InputStream inputStream = post(body)) { - return MAPPER.readValue(inputStream, typeReference); - } - } - - @Override - @SneakyThrows(IOException.class) - public void post(Object entity, InputStream stdin, ResultCallback resultCallback) { - - Request request = new Request.Builder() - .url(httpUrl) - .post(RequestBody.create(MediaType.parse("application/json"), MAPPER.writeValueAsBytes(entity))) - .build(); - - OkHttpClient okHttpClient = this.okHttpClient; - - if (stdin != null) { - // FIXME there must be a better way of handling it - okHttpClient = okHttpClient.newBuilder() - .addNetworkInterceptor(new Interceptor() { - @Override - @SneakyThrows - public Response intercept(Chain chain) { - RealConnection connection = (RealConnection) chain.connection(); - - Field sinkField = RealConnection.class.getDeclaredField("sink"); - sinkField.setAccessible(true); - BufferedSink sink = (BufferedSink) sinkField.get(connection); - - Thread thread = new Thread(DockerClientFactory.TESTCONTAINERS_THREAD_GROUP, () -> { - try { - sink.writeAll(Okio.source(stdin)); - sink.flush(); - - Thread.sleep(100); - sink.close(); - } catch (Exception e) { - throw new RuntimeException(e); - } - }); - thread.start(); - - return chain.proceed(chain.request()); - } - }) - .build(); - } - - handleStreamedResponse( - execute(request), - resultCallback, - new FramedResponseStreamHandler(resultCallback) - ); - } - - @Override - public void post(TypeReference typeReference, ResultCallback resultCallback, InputStream body) { - Request request = new Request.Builder() - .url(httpUrl) - .post(toRequestBody(body, null)) - .build(); - - handleStreamedResponse( - execute(request), - resultCallback, - new JsonResponseCallbackHandler(typeReference, resultCallback) - ); - } - - protected void handleStreamedResponse(Response response, ResultCallback callback, SimpleChannelInboundHandler handler) { - try { - // TODO proper thread management - Thread thread = new Thread(new Runnable() { - @Override - @SneakyThrows - public void run() { - BufferedSource source = response.body().source(); - InputStream inputStream = source.inputStream(); - - byte[] buffer = new byte[4 * 1024]; - while (!source.exhausted() && !Thread.interrupted()) { - int bytesReceived = inputStream.read(buffer); - - handler.channelRead(null, Unpooled.wrappedBuffer(buffer, 0, bytesReceived)); - } - callback.onComplete(); - } - }); - - callback.onStart(() -> { - thread.interrupt(); - response.close(); - }); - - thread.start(); - } catch (Exception e) { - callback.onError(e); - } - } - - @Override - public void postStream(InputStream body) { - Request request = new Request.Builder() - .url(httpUrl) - .post(toRequestBody(body, null)) - .build(); - - try (Response response = execute(request)) { - - } - } - - @Override - public InputStream get() { - Request request = new Request.Builder() - .url(httpUrl) - .get() - .build(); - - Response response = execute(request); - return response.body().byteStream(); - } - - @Override - public void put(InputStream body, com.github.dockerjava.core.MediaType mediaType) { - Request request = new Request.Builder() - .url(httpUrl) - .put(toRequestBody(body, mediaType.toString())) - .build(); - - try (Response response = execute(request)) { - - } - } - - protected RequestBody toRequestBody(InputStream body, @Nullable String mediaType) { - return new RequestBody() { - @Nullable - @Override - public okhttp3.MediaType contentType() { - if (mediaType == null) { - return null; - } - return okhttp3.MediaType.parse(mediaType); - } - - @Override - public void writeTo(BufferedSink sink) throws IOException { - sink.writeAll(Okio.source(body)); - - } - }; - } - - @SneakyThrows(IOException.class) - protected Response execute(Request request) { - Response response = okHttpClient.newCall(request).execute(); - int code = response.code(); - if (code < 200 || code >= 300) { - try { - String body = response.body().string(); - switch (code) { - case 304: - throw new NotModifiedException(body); - case 400: - throw new BadRequestException(body); - case 401: - throw new UnauthorizedException(body); - case 404: - throw new NotFoundException(body); - case 406: - throw new NotAcceptableException(body); - case 409: - throw new ConflictException(body); - case 500: - throw new InternalServerErrorException(body); - default: - throw new DockerException(body, code); - } - } finally { - response.close(); - } - } else { - return response; - } - } - } - } } diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java new file mode 100644 index 00000000000..139f886e3ce --- /dev/null +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java @@ -0,0 +1,344 @@ +package org.testcontainers.dockerclient.transport.okhttp; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.dockerjava.api.async.ResultCallback; +import com.github.dockerjava.api.exception.*; +import com.github.dockerjava.api.model.Frame; +import com.github.dockerjava.core.InvocationBuilder; +import com.github.dockerjava.netty.handler.FramedResponseStreamHandler; +import com.github.dockerjava.netty.handler.JsonResponseCallbackHandler; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.SimpleChannelInboundHandler; +import lombok.SneakyThrows; +import lombok.experimental.FieldDefaults; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import okhttp3.internal.connection.RealConnection; +import okio.BufferedSink; +import okio.BufferedSource; +import okio.Okio; +import org.testcontainers.DockerClientFactory; + +import javax.annotation.Nullable; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.util.concurrent.CompletableFuture; + +@Slf4j +@FieldDefaults(makeFinal = true) +public class OkHttpInvocationBuilder implements InvocationBuilder { + + ObjectMapper objectMapper; + + OkHttpClient okHttpClient; + + Request.Builder requestBuilder; + + public OkHttpInvocationBuilder(ObjectMapper objectMapper, OkHttpClient okHttpClient, HttpUrl httpUrl) { + this.objectMapper = objectMapper; + this.okHttpClient = okHttpClient; + + requestBuilder = new Request.Builder() + .url(httpUrl); + } + + @Override + public OkHttpInvocationBuilder accept(com.github.dockerjava.core.MediaType mediaType) { + return header("accept", mediaType.getMediaType()); + } + + @Override + public OkHttpInvocationBuilder header(String name, String value) { + requestBuilder.header(name, value); + return this; + } + + @Override + @SneakyThrows + public void delete() { + Request request = requestBuilder + .delete() + .build(); + + execute(request).get().close(); + } + + @Override + @SneakyThrows + public void get(ResultCallback resultCallback) { + Request request = requestBuilder + .get() + .build(); + + execute(request).whenCompleteAsync((response, e) -> { + if (e != null) { + resultCallback.onError(e); + } else { + handleStreamedResponse( + response, + resultCallback, + new FramedResponseStreamHandler(resultCallback) + ); + } + }); + } + + @Override + @SneakyThrows(IOException.class) + public T get(TypeReference typeReference) { + try (InputStream inputStream = get()) { + return objectMapper.readValue(inputStream, typeReference); + } + } + + @Override + public void get(TypeReference typeReference, ResultCallback resultCallback) { + // FIXME + throw new IllegalStateException("doesn't seem to be used in docker-java"); + } + + @Override + @SneakyThrows + public InputStream post(Object entity) { + Request request = requestBuilder + .post(RequestBody.create(null, objectMapper.writeValueAsBytes(entity))) + .build(); + + return execute(request).get().body().byteStream(); + } + + @Override + @SneakyThrows + public T post(Object entity, TypeReference typeReference) { + Request request = requestBuilder + .post(RequestBody.create(MediaType.parse("application/json"), objectMapper.writeValueAsBytes(entity))) + .build(); + + try (Response response = execute(request).get()) { + String inputStream = response.body().string(); + return objectMapper.readValue(inputStream, typeReference); + } + } + + @Override + @SneakyThrows(JsonProcessingException.class) + public void post(Object entity, TypeReference typeReference, ResultCallback resultCallback) { + post(typeReference, resultCallback, new ByteArrayInputStream(objectMapper.writeValueAsBytes(entity))); + } + + @Override + @SneakyThrows(IOException.class) + public T post(TypeReference typeReference, InputStream body) { + try (InputStream inputStream = post(body)) { + return objectMapper.readValue(inputStream, typeReference); + } + } + + @Override + @SneakyThrows + public void post(Object entity, InputStream stdin, ResultCallback resultCallback) { + Request request = requestBuilder + .post(RequestBody.create(MediaType.parse("application/json"), objectMapper.writeValueAsBytes(entity))) + .build(); + + OkHttpClient okHttpClient = this.okHttpClient; + + if (stdin != null) { + // FIXME there must be a better way of handling it + okHttpClient = okHttpClient.newBuilder() + .addNetworkInterceptor(new Interceptor() { + @Override + @SneakyThrows + public Response intercept(Chain chain) { + RealConnection connection = (RealConnection) chain.connection(); + + Field sinkField = RealConnection.class.getDeclaredField("sink"); + sinkField.setAccessible(true); + BufferedSink sink = (BufferedSink) sinkField.get(connection); + + Thread thread = new Thread(DockerClientFactory.TESTCONTAINERS_THREAD_GROUP, () -> { + try { + sink.writeAll(Okio.source(stdin)); + sink.flush(); + + Thread.sleep(100); + sink.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + thread.start(); + + return chain.proceed(chain.request()); + } + }) + .build(); + } + + execute(okHttpClient, request).whenCompleteAsync((response, e) -> { + if (e != null) { + resultCallback.onError(e); + } else { + handleStreamedResponse( + response, + resultCallback, + new FramedResponseStreamHandler(resultCallback) + ); + } + }); + } + + @Override + public void post(TypeReference typeReference, ResultCallback resultCallback, InputStream body) { + Request request = requestBuilder + .post(toRequestBody(body, null)) + .build(); + + execute(request).whenCompleteAsync((response, e) -> { + if (e != null) { + resultCallback.onError(e); + } else { + + handleStreamedResponse( + response, + resultCallback, + new JsonResponseCallbackHandler<>(typeReference, resultCallback) + ); + } + }); + } + + @Override + @SneakyThrows + public void postStream(InputStream body) { + Request request = requestBuilder + .post(toRequestBody(body, null)) + .build(); + + execute(request).get().close(); + } + + @Override + @SneakyThrows + public InputStream get() { + Request request = requestBuilder + .get() + .build(); + + return execute(request).get().body().byteStream(); + } + + @Override + @SneakyThrows + public void put(InputStream body, com.github.dockerjava.core.MediaType mediaType) { + Request request = requestBuilder + .put(toRequestBody(body, mediaType.toString())) + .build(); + + execute(request).get().close(); + } + + protected RequestBody toRequestBody(InputStream body, @Nullable String mediaType) { + return new RequestBody() { + @Nullable + @Override + public MediaType contentType() { + if (mediaType == null) { + return null; + } + return MediaType.parse(mediaType); + } + + @Override + public void writeTo(BufferedSink sink) throws IOException { + sink.writeAll(Okio.source(body)); + } + }; + } + + protected CompletableFuture execute(Request request) { + return execute(okHttpClient, request); + } + + protected CompletableFuture execute(OkHttpClient okHttpClient, Request request) { + CompletableFuture future = new CompletableFuture<>(); + + okHttpClient.newCall(request).enqueue(new Callback() { + @Override + public void onFailure(Call call, IOException e) { + future.completeExceptionally(e); + } + + @Override + public void onResponse(Call call, Response response) throws IOException { + int code = response.code(); + if (code < 200 || code >= 300) { + try { + String body = response.body().string(); + switch (code) { + case 304: + throw new NotModifiedException(body); + case 400: + throw new BadRequestException(body); + case 401: + throw new UnauthorizedException(body); + case 404: + throw new NotFoundException(body); + case 406: + throw new NotAcceptableException(body); + case 409: + throw new ConflictException(body); + case 500: + throw new InternalServerErrorException(body); + default: + throw new DockerException(body, code); + } + } catch (Exception e) { + future.completeExceptionally(e); + } finally { + response.close(); + } + } else { + future.complete(response); + } + } + }); + return future; + } + + protected void handleStreamedResponse(Response response, ResultCallback callback, SimpleChannelInboundHandler handler) { + try { + // TODO proper thread management + Thread thread = new Thread(new Runnable() { + @Override + @SneakyThrows + public void run() { + BufferedSource source = response.body().source(); + InputStream inputStream = source.inputStream(); + + byte[] buffer = new byte[4 * 1024]; + while (!source.exhausted() && !Thread.interrupted()) { + int bytesReceived = inputStream.read(buffer); + + handler.channelRead(null, Unpooled.wrappedBuffer(buffer, 0, bytesReceived)); + } + callback.onComplete(); + } + }); + + callback.onStart(() -> { + thread.interrupt(); + response.close(); + }); + + thread.start(); + } catch (Exception e) { + callback.onError(e); + } + } +} diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpWebTarget.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpWebTarget.java new file mode 100644 index 00000000000..d6d6f87357a --- /dev/null +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpWebTarget.java @@ -0,0 +1,175 @@ +package org.testcontainers.dockerclient.transport.okhttp; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.github.dockerjava.core.DockerClientConfig; +import com.github.dockerjava.core.InvocationBuilder; +import com.github.dockerjava.core.SSLConfig; +import com.github.dockerjava.core.WebTarget; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.SetMultimap; +import de.gesellix.docker.client.filesocket.UnixSocket; +import de.gesellix.docker.client.filesocket.UnixSocketFactory; +import lombok.SneakyThrows; +import lombok.Value; +import lombok.experimental.Wither; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; +import okhttp3.internal.Internal; +import org.apache.commons.lang.StringUtils; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.X509TrustManager; +import java.net.URI; +import java.security.cert.X509Certificate; +import java.util.Collection; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +@Wither +@Value +public class OkHttpWebTarget implements WebTarget { + + private static final ObjectMapper MAPPER = new ObjectMapper(); + + DockerClientConfig dockerClientConfig; + + ImmutableList path; + + SetMultimap queryParams; + + @Override + @SneakyThrows + public InvocationBuilder request() { + String resource = StringUtils.join(path, "/"); + + if (!resource.startsWith("/")) { + resource = "/" + resource; + } + + OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); + + SSLConfig sslConfig = dockerClientConfig.getSSLConfig(); + if (sslConfig != null) { + SSLContext sslContext = sslConfig.getSSLContext(); + if (sslContext != null) { + clientBuilder + .sslSocketFactory(sslContext.getSocketFactory(), new TrustAllX509TrustManager()); + } + } + + HttpUrl.Builder urlBuilder; + + URI dockerHost = dockerClientConfig.getDockerHost(); + switch (dockerHost.getScheme()) { + case "npipe": + // TODO support it + throw new IllegalArgumentException("npipe protocol is not supported yet"); + case "unix": + UnixSocketFactory unixSocketFactory = new UnixSocketFactory(); + clientBuilder + .socketFactory(unixSocketFactory) + .dns(unixSocketFactory); + + urlBuilder = new HttpUrl.Builder() + .scheme("http") + .host(new UnixSocket().encodeHostname(dockerHost.getPath())); + break; + case "tcp": + urlBuilder = new HttpUrl.Builder() + .scheme(sslConfig == null ? "http" : "https") + .host(dockerHost.getHost()) + .port(dockerHost.getPort()); + break; + default: + urlBuilder = Internal.instance.getHttpUrlChecked(dockerHost.toString()).newBuilder(); + } + + urlBuilder + .encodedPath(resource); + + for (Map.Entry> queryParamEntry : queryParams.asMap().entrySet()) { + String key = queryParamEntry.getKey(); + for (String paramValue : queryParamEntry.getValue()) { + urlBuilder.addQueryParameter(key, paramValue); + } + } + + return new OkHttpInvocationBuilder( + MAPPER, + clientBuilder.build(), + urlBuilder.build() + ); + } + + @Override + public OkHttpWebTarget path(String... components) { + return this.withPath( + ImmutableList.builder() + .addAll(path) + .add(components) + .build() + ); + } + + @Override + public OkHttpWebTarget resolveTemplate(String name, Object value) { + ImmutableList.Builder newPath = ImmutableList.builder(); + for (String component : path) { + component = component.replaceAll("\\{" + name + "\\}", value.toString()); + newPath.add(component); + } + return this.withPath(newPath.build()); + } + + @Override + public OkHttpWebTarget queryParam(String name, Object value) { + if (value == null) { + return this; + } + + SetMultimap newQueryParams = HashMultimap.create(queryParams); + newQueryParams.put(name, value.toString()); + + return this.withQueryParams(newQueryParams); + } + + @Override + public OkHttpWebTarget queryParamsSet(String name, Set values) { + SetMultimap newQueryParams = HashMultimap.create(queryParams); + newQueryParams.replaceValues(name, values.stream().filter(Objects::nonNull).map(Object::toString).collect(Collectors.toSet())); + + return this.withQueryParams(newQueryParams); + } + + @Override + @SneakyThrows(JsonProcessingException.class) + public OkHttpWebTarget queryParamsJsonMap(String name, Map values) { + if (values == null || values.isEmpty()) { + return this; + } + + // when param value is JSON string + return queryParam(name, MAPPER.writeValueAsString(values)); + } + + private static class TrustAllX509TrustManager implements X509TrustManager { + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) { + + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) { + + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } +} From 7727b3cac829271206dbf3dc2ddd46a910d2a5ac Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Sun, 20 May 2018 14:05:10 +0200 Subject: [PATCH 06/31] mark OkHttpInvocationBuilder & OkHttpWebTarget as internal classes --- .../dockerclient/DockerClientProviderStrategy.java | 2 +- .../transport/{ => okhttp}/OkHttpDockerCmdExecFactory.java | 3 +-- .../dockerclient/transport/okhttp/OkHttpInvocationBuilder.java | 2 +- .../dockerclient/transport/okhttp/OkHttpWebTarget.java | 2 +- 4 files changed, 4 insertions(+), 5 deletions(-) rename core/src/main/java/org/testcontainers/dockerclient/transport/{ => okhttp}/OkHttpDockerCmdExecFactory.java (91%) diff --git a/core/src/main/java/org/testcontainers/dockerclient/DockerClientProviderStrategy.java b/core/src/main/java/org/testcontainers/dockerclient/DockerClientProviderStrategy.java index ebe448f2aee..3048da3f030 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/DockerClientProviderStrategy.java +++ b/core/src/main/java/org/testcontainers/dockerclient/DockerClientProviderStrategy.java @@ -12,7 +12,7 @@ import org.rnorth.ducttape.unreliables.Unreliables; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.testcontainers.dockerclient.transport.OkHttpDockerCmdExecFactory; +import org.testcontainers.dockerclient.transport.okhttp.OkHttpDockerCmdExecFactory; import org.testcontainers.utility.TestcontainersConfiguration; import java.util.ArrayList; diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java similarity index 91% rename from core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java rename to core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java index bba09f8ebf1..f4ded33af8e 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/OkHttpDockerCmdExecFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java @@ -1,4 +1,4 @@ -package org.testcontainers.dockerclient.transport; +package org.testcontainers.dockerclient.transport.okhttp; import com.github.dockerjava.api.command.PingCmd; import com.github.dockerjava.core.AbstractDockerCmdExecFactory; @@ -7,7 +7,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.MultimapBuilder; import org.apache.commons.io.IOUtils; -import org.testcontainers.dockerclient.transport.okhttp.OkHttpWebTarget; import java.io.IOException; diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java index 139f886e3ce..9ec13f11081 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java @@ -31,7 +31,7 @@ @Slf4j @FieldDefaults(makeFinal = true) -public class OkHttpInvocationBuilder implements InvocationBuilder { +class OkHttpInvocationBuilder implements InvocationBuilder { ObjectMapper objectMapper; diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpWebTarget.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpWebTarget.java index d6d6f87357a..2d4ee0cf3a8 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpWebTarget.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpWebTarget.java @@ -31,7 +31,7 @@ @Wither @Value -public class OkHttpWebTarget implements WebTarget { +class OkHttpWebTarget implements WebTarget { private static final ObjectMapper MAPPER = new ObjectMapper(); From 788543f756573ac8b067b58bd8ea55545cd33dc5 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Sun, 20 May 2018 14:08:58 +0200 Subject: [PATCH 07/31] re-throw ExecutionException's cause --- .../okhttp/OkHttpInvocationBuilder.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java index 9ec13f11081..680e71adf78 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java @@ -28,6 +28,7 @@ import java.io.InputStream; import java.lang.reflect.Field; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; @Slf4j @FieldDefaults(makeFinal = true) @@ -65,7 +66,7 @@ public void delete() { .delete() .build(); - execute(request).get().close(); + executeAndGet(request).close(); } @Override @@ -109,7 +110,7 @@ public InputStream post(Object entity) { .post(RequestBody.create(null, objectMapper.writeValueAsBytes(entity))) .build(); - return execute(request).get().body().byteStream(); + return executeAndGet(request).body().byteStream(); } @Override @@ -119,7 +120,7 @@ public T post(Object entity, TypeReference typeReference) { .post(RequestBody.create(MediaType.parse("application/json"), objectMapper.writeValueAsBytes(entity))) .build(); - try (Response response = execute(request).get()) { + try (Response response = executeAndGet(request)) { String inputStream = response.body().string(); return objectMapper.readValue(inputStream, typeReference); } @@ -220,7 +221,7 @@ public void postStream(InputStream body) { .post(toRequestBody(body, null)) .build(); - execute(request).get().close(); + executeAndGet(request).close(); } @Override @@ -230,7 +231,7 @@ public InputStream get() { .get() .build(); - return execute(request).get().body().byteStream(); + return executeAndGet(request).body().byteStream(); } @Override @@ -240,7 +241,7 @@ public void put(InputStream body, com.github.dockerjava.core.MediaType mediaType .put(toRequestBody(body, mediaType.toString())) .build(); - execute(request).get().close(); + executeAndGet(request).close(); } protected RequestBody toRequestBody(InputStream body, @Nullable String mediaType) { @@ -261,6 +262,15 @@ public void writeTo(BufferedSink sink) throws IOException { }; } + @SneakyThrows + protected Response executeAndGet(Request request) { + try { + return execute(request).get(); + } catch (ExecutionException e) { + throw e.getCause(); + } + } + protected CompletableFuture execute(Request request) { return execute(okHttpClient, request); } From 01661260c0d9041e39304205b146c5df4c2b683a Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Sun, 20 May 2018 14:19:24 +0200 Subject: [PATCH 08/31] always close the response --- .../okhttp/OkHttpInvocationBuilder.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java index 680e71adf78..cff16943d11 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java @@ -328,16 +328,20 @@ protected void handleStreamedResponse(Response response, ResultCallback c @Override @SneakyThrows public void run() { - BufferedSource source = response.body().source(); - InputStream inputStream = source.inputStream(); + try { + BufferedSource source = response.body().source(); + InputStream inputStream = source.inputStream(); - byte[] buffer = new byte[4 * 1024]; - while (!source.exhausted() && !Thread.interrupted()) { - int bytesReceived = inputStream.read(buffer); + byte[] buffer = new byte[4 * 1024]; + while (!source.exhausted() && !Thread.interrupted()) { + int bytesReceived = inputStream.read(buffer); - handler.channelRead(null, Unpooled.wrappedBuffer(buffer, 0, bytesReceived)); + handler.channelRead(null, Unpooled.wrappedBuffer(buffer, 0, bytesReceived)); + } + callback.onComplete(); + } finally { + response.close(); } - callback.onComplete(); } }); From e1021b1738d29c17fd56d4819f4277ab83f2b6ab Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Sun, 20 May 2018 14:28:49 +0200 Subject: [PATCH 09/31] reuse OkHttpClient instance --- .../okhttp/OkHttpDockerCmdExecFactory.java | 59 +++++++++++++++++++ .../transport/okhttp/OkHttpWebTarget.java | 47 ++------------- 2 files changed, 64 insertions(+), 42 deletions(-) diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java index f4ded33af8e..202ba798bbd 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java @@ -2,19 +2,62 @@ import com.github.dockerjava.api.command.PingCmd; import com.github.dockerjava.core.AbstractDockerCmdExecFactory; +import com.github.dockerjava.core.DockerClientConfig; +import com.github.dockerjava.core.SSLConfig; import com.github.dockerjava.core.WebTarget; import com.github.dockerjava.core.exec.PingCmdExec; import com.google.common.collect.ImmutableList; import com.google.common.collect.MultimapBuilder; +import de.gesellix.docker.client.filesocket.UnixSocketFactory; +import lombok.SneakyThrows; +import okhttp3.OkHttpClient; import org.apache.commons.io.IOUtils; +import javax.net.ssl.SSLContext; +import javax.net.ssl.X509TrustManager; import java.io.IOException; +import java.net.URI; +import java.security.cert.X509Certificate; public class OkHttpDockerCmdExecFactory extends AbstractDockerCmdExecFactory { + private OkHttpClient okHttpClient; + + @Override + @SneakyThrows + public void init(DockerClientConfig dockerClientConfig) { + super.init(dockerClientConfig); + + OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); + + URI dockerHost = dockerClientConfig.getDockerHost(); + switch (dockerHost.getScheme()) { + case "npipe": + // TODO support it + throw new IllegalArgumentException("npipe protocol is not supported yet"); + case "unix": + UnixSocketFactory unixSocketFactory = new UnixSocketFactory(); + clientBuilder + .socketFactory(unixSocketFactory) + .dns(unixSocketFactory); + } + + SSLConfig sslConfig = dockerClientConfig.getSSLConfig(); + if (sslConfig != null) { + SSLContext sslContext = sslConfig.getSSLContext(); + if (sslContext != null) { + clientBuilder + .sslSocketFactory(sslContext.getSocketFactory(), new TrustAllX509TrustManager()); + } + } + + okHttpClient = clientBuilder.build(); + } + @Override protected WebTarget getBaseResource() { return new OkHttpWebTarget( + okHttpClient, getDockerClientConfig(), ImmutableList.of(), MultimapBuilder.hashKeys().hashSetValues().build() @@ -42,4 +85,20 @@ public void close() throws IOException { } + private static class TrustAllX509TrustManager implements X509TrustManager { + @Override + public void checkClientTrusted(X509Certificate[] x509Certificates, String s) { + + } + + @Override + public void checkServerTrusted(X509Certificate[] x509Certificates, String s) { + + } + + @Override + public X509Certificate[] getAcceptedIssuers() { + return new X509Certificate[0]; + } + } } diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpWebTarget.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpWebTarget.java index 2d4ee0cf3a8..44170945d03 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpWebTarget.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpWebTarget.java @@ -10,7 +10,6 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.SetMultimap; import de.gesellix.docker.client.filesocket.UnixSocket; -import de.gesellix.docker.client.filesocket.UnixSocketFactory; import lombok.SneakyThrows; import lombok.Value; import lombok.experimental.Wither; @@ -19,10 +18,7 @@ import okhttp3.internal.Internal; import org.apache.commons.lang.StringUtils; -import javax.net.ssl.SSLContext; -import javax.net.ssl.X509TrustManager; import java.net.URI; -import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Map; import java.util.Objects; @@ -35,6 +31,8 @@ class OkHttpWebTarget implements WebTarget { private static final ObjectMapper MAPPER = new ObjectMapper(); + OkHttpClient okHttpClient; + DockerClientConfig dockerClientConfig; ImmutableList path; @@ -50,37 +48,19 @@ public InvocationBuilder request() { resource = "/" + resource; } - OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); - - SSLConfig sslConfig = dockerClientConfig.getSSLConfig(); - if (sslConfig != null) { - SSLContext sslContext = sslConfig.getSSLContext(); - if (sslContext != null) { - clientBuilder - .sslSocketFactory(sslContext.getSocketFactory(), new TrustAllX509TrustManager()); - } - } - HttpUrl.Builder urlBuilder; URI dockerHost = dockerClientConfig.getDockerHost(); switch (dockerHost.getScheme()) { - case "npipe": - // TODO support it - throw new IllegalArgumentException("npipe protocol is not supported yet"); case "unix": - UnixSocketFactory unixSocketFactory = new UnixSocketFactory(); - clientBuilder - .socketFactory(unixSocketFactory) - .dns(unixSocketFactory); - urlBuilder = new HttpUrl.Builder() .scheme("http") .host(new UnixSocket().encodeHostname(dockerHost.getPath())); break; case "tcp": + SSLConfig sslConfig = dockerClientConfig.getSSLConfig(); urlBuilder = new HttpUrl.Builder() - .scheme(sslConfig == null ? "http" : "https") + .scheme(sslConfig != null && sslConfig.getSSLContext() != null ? "https" : "http") .host(dockerHost.getHost()) .port(dockerHost.getPort()); break; @@ -100,7 +80,7 @@ public InvocationBuilder request() { return new OkHttpInvocationBuilder( MAPPER, - clientBuilder.build(), + okHttpClient, urlBuilder.build() ); } @@ -155,21 +135,4 @@ public OkHttpWebTarget queryParamsJsonMap(String name, Map value // when param value is JSON string return queryParam(name, MAPPER.writeValueAsString(values)); } - - private static class TrustAllX509TrustManager implements X509TrustManager { - @Override - public void checkClientTrusted(X509Certificate[] x509Certificates, String s) { - - } - - @Override - public void checkServerTrusted(X509Certificate[] x509Certificates, String s) { - - } - - @Override - public X509Certificate[] getAcceptedIssuers() { - return new X509Certificate[0]; - } - } } From 360efb6bbc0d744f07023ce4451eae209f238ca7 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Mon, 21 May 2018 11:45:30 +0200 Subject: [PATCH 10/31] shade okhttp / okio, disable pooling of unix-socket based transport --- build.gradle | 4 +- core/build.gradle | 6 +- .../okhttp/OkHttpDockerCmdExecFactory.java | 35 ++- .../okhttp/OkHttpInvocationBuilder.java | 245 +++++++----------- .../transport/okhttp/OkHttpWebTarget.java | 33 +-- core/src/test/resources/logback-test.xml | 1 + 6 files changed, 142 insertions(+), 182 deletions(-) diff --git a/build.gradle b/build.gradle index 8a0190349e5..27f40567f2f 100644 --- a/build.gradle +++ b/build.gradle @@ -73,7 +73,9 @@ subprojects { "io.netty", "org.bouncycastle", "org.newsclub", - "org.zeroturnaround" + "org.zeroturnaround", + "okhttp3", + "okio", ].each { relocate(it, "org.testcontainers.shaded.$it") } } diff --git a/core/build.gradle b/core/build.gradle index a61f1a94d64..c8e728af33d 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -51,6 +51,8 @@ shadowJar { include(dependency('commons-lang:commons-lang')) include(dependency('commons-io:commons-io')) include(dependency('commons-codec:commons-codec')) + include(dependency('com.squareup.okhttp3:.*')) + include(dependency('com.squareup.okio:.*')) } } @@ -94,8 +96,8 @@ dependencies { exclude(group: 'com.squareup.okhttp3') exclude(group: 'com.squareup.okio') } - shaded 'com.squareup.okhttp3:okhttp:3.9.1' - shaded 'com.squareup.okio:okio:1.14.0' + shaded 'com.squareup.okhttp3:okhttp:3.10.0' + shaded 'com.squareup.okio:okio:1.14.1' shaded 'javax.ws.rs:javax.ws.rs-api:2.0.1' shaded 'org.zeroturnaround:zt-exec:1.8' diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java index 202ba798bbd..cdcc0db2168 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java @@ -8,9 +8,12 @@ import com.github.dockerjava.core.exec.PingCmdExec; import com.google.common.collect.ImmutableList; import com.google.common.collect.MultimapBuilder; +import de.gesellix.docker.client.filesocket.UnixSocket; import de.gesellix.docker.client.filesocket.UnixSocketFactory; import lombok.SneakyThrows; -import okhttp3.OkHttpClient; +import lombok.extern.slf4j.Slf4j; +import okhttp3.*; +import okhttp3.internal.Internal; import org.apache.commons.io.IOUtils; import javax.net.ssl.SSLContext; @@ -18,17 +21,22 @@ import java.io.IOException; import java.net.URI; import java.security.cert.X509Certificate; +import java.util.concurrent.TimeUnit; +@Slf4j public class OkHttpDockerCmdExecFactory extends AbstractDockerCmdExecFactory { private OkHttpClient okHttpClient; + private HttpUrl baseUrl; + @Override @SneakyThrows public void init(DockerClientConfig dockerClientConfig) { super.init(dockerClientConfig); - OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); + OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder() + .retryOnConnectionFailure(false); URI dockerHost = dockerClientConfig.getDockerHost(); switch (dockerHost.getScheme()) { @@ -38,6 +46,8 @@ public void init(DockerClientConfig dockerClientConfig) { case "unix": UnixSocketFactory unixSocketFactory = new UnixSocketFactory(); clientBuilder + // Disable pooling + .connectionPool(new ConnectionPool(0, 1, TimeUnit.SECONDS)) .socketFactory(unixSocketFactory) .dns(unixSocketFactory); } @@ -52,13 +62,32 @@ public void init(DockerClientConfig dockerClientConfig) { } okHttpClient = clientBuilder.build(); + + HttpUrl.Builder baseUrlBuilder; + + switch (dockerHost.getScheme()) { + case "unix": + baseUrlBuilder = new HttpUrl.Builder() + .scheme("http") + .host(new UnixSocket().encodeHostname(dockerHost.getPath())); + break; + case "tcp": + baseUrlBuilder = new HttpUrl.Builder() + .scheme(sslConfig != null && sslConfig.getSSLContext() != null ? "https" : "http") + .host(dockerHost.getHost()) + .port(dockerHost.getPort()); + break; + default: + baseUrlBuilder = Internal.instance.getHttpUrlChecked(dockerHost.toString()).newBuilder(); + } + baseUrl = baseUrlBuilder.build(); } @Override protected WebTarget getBaseResource() { return new OkHttpWebTarget( okHttpClient, - getDockerClientConfig(), + baseUrl, ImmutableList.of(), MultimapBuilder.hashKeys().hashSetValues().build() ); diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java index cff16943d11..76f69862006 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java @@ -20,15 +20,13 @@ import okio.BufferedSink; import okio.BufferedSource; import okio.Okio; -import org.testcontainers.DockerClientFactory; +import okio.Source; import javax.annotation.Nullable; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; @Slf4j @FieldDefaults(makeFinal = true) @@ -66,7 +64,7 @@ public void delete() { .delete() .build(); - executeAndGet(request).close(); + execute(request).close(); } @Override @@ -76,17 +74,11 @@ public void get(ResultCallback resultCallback) { .get() .build(); - execute(request).whenCompleteAsync((response, e) -> { - if (e != null) { - resultCallback.onError(e); - } else { - handleStreamedResponse( - response, - resultCallback, - new FramedResponseStreamHandler(resultCallback) - ); - } - }); + handleStreamedResponse( + request, + resultCallback, + new FramedResponseStreamHandler(resultCallback) + ); } @Override @@ -110,7 +102,7 @@ public InputStream post(Object entity) { .post(RequestBody.create(null, objectMapper.writeValueAsBytes(entity))) .build(); - return executeAndGet(request).body().byteStream(); + return execute(request).body().byteStream(); } @Override @@ -120,7 +112,7 @@ public T post(Object entity, TypeReference typeReference) { .post(RequestBody.create(MediaType.parse("application/json"), objectMapper.writeValueAsBytes(entity))) .build(); - try (Response response = executeAndGet(request)) { + try (Response response = execute(request)) { String inputStream = response.body().string(); return objectMapper.readValue(inputStream, typeReference); } @@ -152,46 +144,38 @@ public void post(Object entity, InputStream stdin, ResultCallback resultC if (stdin != null) { // FIXME there must be a better way of handling it okHttpClient = okHttpClient.newBuilder() - .addNetworkInterceptor(new Interceptor() { - @Override - @SneakyThrows - public Response intercept(Chain chain) { - RealConnection connection = (RealConnection) chain.connection(); - - Field sinkField = RealConnection.class.getDeclaredField("sink"); - sinkField.setAccessible(true); - BufferedSink sink = (BufferedSink) sinkField.get(connection); - - Thread thread = new Thread(DockerClientFactory.TESTCONTAINERS_THREAD_GROUP, () -> { - try { - sink.writeAll(Okio.source(stdin)); - sink.flush(); - - Thread.sleep(100); - sink.close(); - } catch (Exception e) { - throw new RuntimeException(e); + .addNetworkInterceptor(chain -> { + Response response = chain.proceed(chain.request()); + if (response.isSuccessful()) { + Thread thread = new Thread() { + @Override + @SneakyThrows + public void run() { + Field sinkField = RealConnection.class.getDeclaredField("sink"); + sinkField.setAccessible(true); + + try ( + BufferedSink sink = (BufferedSink) sinkField.get(chain.connection()); + Source source = Okio.source(stdin); + ) { + sink.writeAll(source); + sink.flush(); + } } - }); + }; thread.start(); - - return chain.proceed(chain.request()); } + return response; }) .build(); } - execute(okHttpClient, request).whenCompleteAsync((response, e) -> { - if (e != null) { - resultCallback.onError(e); - } else { - handleStreamedResponse( - response, - resultCallback, - new FramedResponseStreamHandler(resultCallback) - ); - } - }); + handleStreamedResponse( + okHttpClient, + request, + resultCallback, + new FramedResponseStreamHandler(resultCallback) + ); } @Override @@ -200,18 +184,11 @@ public void post(TypeReference typeReference, ResultCallback resultCal .post(toRequestBody(body, null)) .build(); - execute(request).whenCompleteAsync((response, e) -> { - if (e != null) { - resultCallback.onError(e); - } else { - - handleStreamedResponse( - response, - resultCallback, - new JsonResponseCallbackHandler<>(typeReference, resultCallback) - ); - } - }); + handleStreamedResponse( + request, + resultCallback, + new JsonResponseCallbackHandler<>(typeReference, resultCallback) + ); } @Override @@ -221,7 +198,7 @@ public void postStream(InputStream body) { .post(toRequestBody(body, null)) .build(); - executeAndGet(request).close(); + execute(request).close(); } @Override @@ -231,7 +208,7 @@ public InputStream get() { .get() .build(); - return executeAndGet(request).body().byteStream(); + return execute(request).body().byteStream(); } @Override @@ -241,7 +218,7 @@ public void put(InputStream body, com.github.dockerjava.core.MediaType mediaType .put(toRequestBody(body, mediaType.toString())) .build(); - executeAndGet(request).close(); + execute(request).close(); } protected RequestBody toRequestBody(InputStream body, @Nullable String mediaType) { @@ -257,102 +234,76 @@ public MediaType contentType() { @Override public void writeTo(BufferedSink sink) throws IOException { - sink.writeAll(Okio.source(body)); + try(Source source = Okio.source(body)) { + sink.writeAll(source); + } } }; } - @SneakyThrows - protected Response executeAndGet(Request request) { - try { - return execute(request).get(); - } catch (ExecutionException e) { - throw e.getCause(); - } + protected Response execute(Request request) { + return execute(okHttpClient, request); } - protected CompletableFuture execute(Request request) { - return execute(okHttpClient, request); + @SneakyThrows(IOException.class) + protected Response execute(OkHttpClient okHttpClient, Request request) { + Response response = okHttpClient.newCall(request).execute(); + if (!response.isSuccessful()) { + String body = response.body().string(); + switch (response.code()) { + case 304: + throw new NotModifiedException(body); + case 400: + throw new BadRequestException(body); + case 401: + throw new UnauthorizedException(body); + case 404: + throw new NotFoundException(body); + case 406: + throw new NotAcceptableException(body); + case 409: + throw new ConflictException(body); + case 500: + throw new InternalServerErrorException(body); + default: + throw new DockerException(body, response.code()); + } + } else { + return response; + } } - protected CompletableFuture execute(OkHttpClient okHttpClient, Request request) { - CompletableFuture future = new CompletableFuture<>(); + protected void handleStreamedResponse(Request request, ResultCallback callback, SimpleChannelInboundHandler handler) { + handleStreamedResponse(okHttpClient, request, callback, handler); + } - okHttpClient.newCall(request).enqueue(new Callback() { + protected void handleStreamedResponse(OkHttpClient okHttpClient, Request request, ResultCallback callback, SimpleChannelInboundHandler handler) { + Response response = execute(okHttpClient, request); + // TODO proper thread management + Thread thread = new Thread() { @Override - public void onFailure(Call call, IOException e) { - future.completeExceptionally(e); - } + @SneakyThrows + public void run() { + try { + BufferedSource source = response.body().source(); + InputStream inputStream = source.inputStream(); - @Override - public void onResponse(Call call, Response response) throws IOException { - int code = response.code(); - if (code < 200 || code >= 300) { - try { - String body = response.body().string(); - switch (code) { - case 304: - throw new NotModifiedException(body); - case 400: - throw new BadRequestException(body); - case 401: - throw new UnauthorizedException(body); - case 404: - throw new NotFoundException(body); - case 406: - throw new NotAcceptableException(body); - case 409: - throw new ConflictException(body); - case 500: - throw new InternalServerErrorException(body); - default: - throw new DockerException(body, code); - } - } catch (Exception e) { - future.completeExceptionally(e); - } finally { - response.close(); - } - } else { - future.complete(response); - } - } - }); - return future; - } + byte[] buffer = new byte[4 * 1024]; + while (!source.exhausted() && !Thread.interrupted()) { + int bytesReceived = inputStream.read(buffer); - protected void handleStreamedResponse(Response response, ResultCallback callback, SimpleChannelInboundHandler handler) { - try { - // TODO proper thread management - Thread thread = new Thread(new Runnable() { - @Override - @SneakyThrows - public void run() { - try { - BufferedSource source = response.body().source(); - InputStream inputStream = source.inputStream(); - - byte[] buffer = new byte[4 * 1024]; - while (!source.exhausted() && !Thread.interrupted()) { - int bytesReceived = inputStream.read(buffer); - - handler.channelRead(null, Unpooled.wrappedBuffer(buffer, 0, bytesReceived)); - } - callback.onComplete(); - } finally { - response.close(); + handler.channelRead(null, Unpooled.wrappedBuffer(buffer, 0, bytesReceived)); } + callback.onComplete(); + } catch (Exception e) { + callback.onError(e); + } finally { + response.close(); } - }); - - callback.onStart(() -> { - thread.interrupt(); - response.close(); - }); + } + }; - thread.start(); - } catch (Exception e) { - callback.onError(e); - } + callback.onStart(thread::interrupt); + thread.start(); } } diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpWebTarget.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpWebTarget.java index 44170945d03..85fe8c22288 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpWebTarget.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpWebTarget.java @@ -2,23 +2,18 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; -import com.github.dockerjava.core.DockerClientConfig; import com.github.dockerjava.core.InvocationBuilder; -import com.github.dockerjava.core.SSLConfig; import com.github.dockerjava.core.WebTarget; import com.google.common.collect.HashMultimap; import com.google.common.collect.ImmutableList; import com.google.common.collect.SetMultimap; -import de.gesellix.docker.client.filesocket.UnixSocket; import lombok.SneakyThrows; import lombok.Value; import lombok.experimental.Wither; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; -import okhttp3.internal.Internal; import org.apache.commons.lang.StringUtils; -import java.net.URI; import java.util.Collection; import java.util.Map; import java.util.Objects; @@ -33,7 +28,7 @@ class OkHttpWebTarget implements WebTarget { OkHttpClient okHttpClient; - DockerClientConfig dockerClientConfig; + HttpUrl baseUrl; ImmutableList path; @@ -48,40 +43,20 @@ public InvocationBuilder request() { resource = "/" + resource; } - HttpUrl.Builder urlBuilder; - - URI dockerHost = dockerClientConfig.getDockerHost(); - switch (dockerHost.getScheme()) { - case "unix": - urlBuilder = new HttpUrl.Builder() - .scheme("http") - .host(new UnixSocket().encodeHostname(dockerHost.getPath())); - break; - case "tcp": - SSLConfig sslConfig = dockerClientConfig.getSSLConfig(); - urlBuilder = new HttpUrl.Builder() - .scheme(sslConfig != null && sslConfig.getSSLContext() != null ? "https" : "http") - .host(dockerHost.getHost()) - .port(dockerHost.getPort()); - break; - default: - urlBuilder = Internal.instance.getHttpUrlChecked(dockerHost.toString()).newBuilder(); - } - - urlBuilder + HttpUrl.Builder baseUrlBuilder = baseUrl.newBuilder() .encodedPath(resource); for (Map.Entry> queryParamEntry : queryParams.asMap().entrySet()) { String key = queryParamEntry.getKey(); for (String paramValue : queryParamEntry.getValue()) { - urlBuilder.addQueryParameter(key, paramValue); + baseUrlBuilder.addQueryParameter(key, paramValue); } } return new OkHttpInvocationBuilder( MAPPER, okHttpClient, - urlBuilder.build() + baseUrlBuilder.build() ); } diff --git a/core/src/test/resources/logback-test.xml b/core/src/test/resources/logback-test.xml index 42a23f5fe13..034e76fb249 100644 --- a/core/src/test/resources/logback-test.xml +++ b/core/src/test/resources/logback-test.xml @@ -13,6 +13,7 @@ + From b905ecf72ec0cc1a991b2b5a3141b079880fac12 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Mon, 21 May 2018 12:04:17 +0200 Subject: [PATCH 11/31] do not block caller's thread (well, again :D) --- .../transport/okhttp/OkHttpInvocationBuilder.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java index 76f69862006..a1b26683bc2 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java @@ -278,13 +278,12 @@ protected void handleStreamedResponse(Request request, ResultCallback cal } protected void handleStreamedResponse(OkHttpClient okHttpClient, Request request, ResultCallback callback, SimpleChannelInboundHandler handler) { - Response response = execute(okHttpClient, request); // TODO proper thread management Thread thread = new Thread() { @Override @SneakyThrows public void run() { - try { + try (Response response = execute(okHttpClient, request)) { BufferedSource source = response.body().source(); InputStream inputStream = source.inputStream(); @@ -297,8 +296,6 @@ public void run() { callback.onComplete(); } catch (Exception e) { callback.onError(e); - } finally { - response.close(); } } }; From ff8a1a2dc45a41d8178f0eeb46f494e35d14670e Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Mon, 21 May 2018 12:25:50 +0200 Subject: [PATCH 12/31] retry on connection failure --- .../transport/okhttp/OkHttpDockerCmdExecFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java index cdcc0db2168..8dcc3b1c0d5 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java @@ -36,7 +36,7 @@ public void init(DockerClientConfig dockerClientConfig) { super.init(dockerClientConfig); OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder() - .retryOnConnectionFailure(false); + .retryOnConnectionFailure(true); URI dockerHost = dockerClientConfig.getDockerHost(); switch (dockerHost.getScheme()) { From 5fe2dee609a5316e015fc4a13cfdd59a87e46e30 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Mon, 21 May 2018 12:46:30 +0200 Subject: [PATCH 13/31] do not use mutable "." directory in DirectoryTarResourceTest --- .../testcontainers/utility/DirectoryTarResourceTest.java | 7 +++++-- core/src/test/resources/logback-test.xml | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/test/java/org/testcontainers/utility/DirectoryTarResourceTest.java b/core/src/test/java/org/testcontainers/utility/DirectoryTarResourceTest.java index 70c366e3c80..b73bfeed46e 100644 --- a/core/src/test/java/org/testcontainers/utility/DirectoryTarResourceTest.java +++ b/core/src/test/java/org/testcontainers/utility/DirectoryTarResourceTest.java @@ -29,14 +29,17 @@ public void simpleRecursiveFileTest() throws TimeoutException { final ToStringConsumer toString = new ToStringConsumer(); + // 'src' is expected to be the project base directory, so all source code/resources should be copied in + File directory = new File("src"); + GenericContainer container = new GenericContainer( new ImageFromDockerfile() .withDockerfileFromBuilder(builder -> builder.from("alpine:3.3") .copy("/tmp/foo", "/foo") - .cmd("cat /foo/src/test/resources/test-recursive-file.txt") + .cmd("cat /foo/test/resources/test-recursive-file.txt") .build() - ).withFileFromFile("/tmp/foo", new File("."))) // '.' is expected to be the project base directory, so all source code/resources should be copied in + ).withFileFromFile("/tmp/foo", directory)) .withStartupCheckStrategy(new OneShotStartupCheckStrategy()) .withLogConsumer(wait.andThen(toString)); diff --git a/core/src/test/resources/logback-test.xml b/core/src/test/resources/logback-test.xml index 034e76fb249..42a23f5fe13 100644 --- a/core/src/test/resources/logback-test.xml +++ b/core/src/test/resources/logback-test.xml @@ -13,7 +13,6 @@ - From 086c4db51ba11ce03e0ae18a340564224e1a8a0d Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Mon, 21 May 2018 15:20:04 +0200 Subject: [PATCH 14/31] replace docker-filesocket with plain AFUNIXSocket factory --- build.gradle | 1 + core/build.gradle | 7 +-- .../okhttp/OkHttpDockerCmdExecFactory.java | 27 +++++---- .../okhttp/OkHttpInvocationBuilder.java | 13 ++++- .../transport/okhttp/UnixSocketFactory.java | 57 +++++++++++++++++++ 5 files changed, 88 insertions(+), 17 deletions(-) create mode 100644 core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/UnixSocketFactory.java diff --git a/build.gradle b/build.gradle index 27f40567f2f..361fcdb9ba9 100644 --- a/build.gradle +++ b/build.gradle @@ -76,6 +76,7 @@ subprojects { "org.zeroturnaround", "okhttp3", "okio", + "net.sf.cglib", ].each { relocate(it, "org.testcontainers.shaded.$it") } } diff --git a/core/build.gradle b/core/build.gradle index c8e728af33d..17f19b8c0df 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -53,6 +53,7 @@ shadowJar { include(dependency('commons-codec:commons-codec')) include(dependency('com.squareup.okhttp3:.*')) include(dependency('com.squareup.okio:.*')) + include(dependency('cglib:cglib')) } } @@ -86,16 +87,14 @@ dependencies { exclude(group: "log4j", module: "log4j") } + compile 'cglib:cglib:3.2.6' + shaded ('com.github.docker-java:docker-java:3.1.0-rc-3') { exclude(group: 'org.glassfish.jersey.core') exclude(group: 'org.glassfish.jersey.connectors') exclude(group: 'log4j') exclude(group: 'com.google.code.findbug') } - shaded ('de.gesellix:docker-filesocket:2018-02-17T21-56-51') { - exclude(group: 'com.squareup.okhttp3') - exclude(group: 'com.squareup.okio') - } shaded 'com.squareup.okhttp3:okhttp:3.10.0' shaded 'com.squareup.okio:okio:1.14.1' diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java index 8dcc3b1c0d5..8b27d1a8d85 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java @@ -8,24 +8,27 @@ import com.github.dockerjava.core.exec.PingCmdExec; import com.google.common.collect.ImmutableList; import com.google.common.collect.MultimapBuilder; -import de.gesellix.docker.client.filesocket.UnixSocket; -import de.gesellix.docker.client.filesocket.UnixSocketFactory; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; -import okhttp3.*; +import okhttp3.Dns; +import okhttp3.HttpUrl; +import okhttp3.OkHttpClient; import okhttp3.internal.Internal; import org.apache.commons.io.IOUtils; import javax.net.ssl.SSLContext; import javax.net.ssl.X509TrustManager; import java.io.IOException; +import java.net.InetAddress; import java.net.URI; import java.security.cert.X509Certificate; -import java.util.concurrent.TimeUnit; +import java.util.Collections; @Slf4j public class OkHttpDockerCmdExecFactory extends AbstractDockerCmdExecFactory { + private static final String SOCKET_SUFFIX = ".socket"; + private OkHttpClient okHttpClient; private HttpUrl baseUrl; @@ -44,12 +47,16 @@ public void init(DockerClientConfig dockerClientConfig) { // TODO support it throw new IllegalArgumentException("npipe protocol is not supported yet"); case "unix": - UnixSocketFactory unixSocketFactory = new UnixSocketFactory(); + String socketPath = dockerHost.getPath(); clientBuilder - // Disable pooling - .connectionPool(new ConnectionPool(0, 1, TimeUnit.SECONDS)) - .socketFactory(unixSocketFactory) - .dns(unixSocketFactory); + .socketFactory(new UnixSocketFactory(socketPath)) + .dns(hostname -> { + if (hostname.endsWith(SOCKET_SUFFIX)) { + return Collections.singletonList(InetAddress.getByAddress(hostname, new byte[]{0, 0, 0, 0})); + } else { + return Dns.SYSTEM.lookup(hostname); + } + }); } SSLConfig sslConfig = dockerClientConfig.getSSLConfig(); @@ -69,7 +76,7 @@ public void init(DockerClientConfig dockerClientConfig) { case "unix": baseUrlBuilder = new HttpUrl.Builder() .scheme("http") - .host(new UnixSocket().encodeHostname(dockerHost.getPath())); + .host("docker" + SOCKET_SUFFIX); break; case "tcp": baseUrlBuilder = new HttpUrl.Builder() diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java index a1b26683bc2..cd6a020def9 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Field; +import java.util.concurrent.atomic.AtomicBoolean; @Slf4j @FieldDefaults(makeFinal = true) @@ -283,12 +284,19 @@ protected void handleStreamedResponse(OkHttpClient okHttpClient, Request req @Override @SneakyThrows public void run() { - try (Response response = execute(okHttpClient, request)) { + try ( + Response response = execute(okHttpClient, request.newBuilder().tag("streaming").build()); BufferedSource source = response.body().source(); InputStream inputStream = source.inputStream(); + ) { + AtomicBoolean shouldStop = new AtomicBoolean(); + callback.onStart(() -> { + shouldStop.set(true); + response.close(); + }); byte[] buffer = new byte[4 * 1024]; - while (!source.exhausted() && !Thread.interrupted()) { + while (!(shouldStop.get() || source.exhausted())) { int bytesReceived = inputStream.read(buffer); handler.channelRead(null, Unpooled.wrappedBuffer(buffer, 0, bytesReceived)); @@ -300,7 +308,6 @@ public void run() { } }; - callback.onStart(thread::interrupt); thread.start(); } } diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/UnixSocketFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/UnixSocketFactory.java new file mode 100644 index 00000000000..ac02099865b --- /dev/null +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/UnixSocketFactory.java @@ -0,0 +1,57 @@ +package org.testcontainers.dockerclient.transport.okhttp; + +import lombok.SneakyThrows; +import lombok.Value; +import net.sf.cglib.proxy.Enhancer; +import net.sf.cglib.proxy.InvocationHandler; +import org.newsclub.net.unix.AFUNIXSocket; +import org.newsclub.net.unix.AFUNIXSocketAddress; + +import javax.net.SocketFactory; +import java.io.File; +import java.net.InetAddress; +import java.net.Socket; + +@Value +public class UnixSocketFactory extends SocketFactory { + + String socketPath; + + @Override + @SneakyThrows + public Socket createSocket() { + AFUNIXSocket socket = AFUNIXSocket.connectTo(new AFUNIXSocketAddress(new File(socketPath))); + + Enhancer enhancer = new Enhancer(); + enhancer.setSuperclass(Socket.class); + enhancer.setCallback((InvocationHandler) (proxy, method, args) -> { + if ("connect".equals(method.getName())) { + return null; + } + + return method.invoke(socket, args); + }); + + return (Socket) enhancer.create(); + } + + @Override + public Socket createSocket(String s, int i) { + throw new UnsupportedOperationException(); + } + + @Override + public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) { + throw new UnsupportedOperationException(); + } + + @Override + public Socket createSocket(InetAddress inetAddress, int i) { + throw new UnsupportedOperationException(); + } + + @Override + public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) { + throw new UnsupportedOperationException(); + } +} From f64be0f620a55bc4fa5bdeda33d28833a7988ce9 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Mon, 21 May 2018 16:20:37 +0200 Subject: [PATCH 15/31] fix shaded dependencies, rename handleStreamedResponse -> executeAndStream --- build.gradle | 1 - core/build.gradle | 7 +++---- .../transport/okhttp/OkHttpInvocationBuilder.java | 14 +++++++------- 3 files changed, 10 insertions(+), 12 deletions(-) diff --git a/build.gradle b/build.gradle index 361fcdb9ba9..8d17d7c5b26 100644 --- a/build.gradle +++ b/build.gradle @@ -72,7 +72,6 @@ subprojects { "com.google", "io.netty", "org.bouncycastle", - "org.newsclub", "org.zeroturnaround", "okhttp3", "okio", diff --git a/core/build.gradle b/core/build.gradle index 17f19b8c0df..adc19d18072 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -46,14 +46,13 @@ shadowJar { include(dependency('com.google.guava:.*')) include(dependency('io.netty:.*')) include(dependency('org.bouncycastle:.*')) - include(dependency('org.newsclub.*:.*')) include(dependency('org.zeroturnaround:zt-exec')) include(dependency('commons-lang:commons-lang')) include(dependency('commons-io:commons-io')) include(dependency('commons-codec:commons-codec')) include(dependency('com.squareup.okhttp3:.*')) include(dependency('com.squareup.okio:.*')) - include(dependency('cglib:cglib')) + include(dependency('cglib:cglib-nodep')) } } @@ -87,16 +86,16 @@ dependencies { exclude(group: "log4j", module: "log4j") } - compile 'cglib:cglib:3.2.6' + shaded 'cglib:cglib-nodep:3.2.6' shaded ('com.github.docker-java:docker-java:3.1.0-rc-3') { exclude(group: 'org.glassfish.jersey.core') exclude(group: 'org.glassfish.jersey.connectors') exclude(group: 'log4j') exclude(group: 'com.google.code.findbug') + exclude(group: 'com.kohlschutter.junixsocket') } shaded 'com.squareup.okhttp3:okhttp:3.10.0' - shaded 'com.squareup.okio:okio:1.14.1' shaded 'javax.ws.rs:javax.ws.rs-api:2.0.1' shaded 'org.zeroturnaround:zt-exec:1.8' diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java index cd6a020def9..cc75ed03d48 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java @@ -75,7 +75,7 @@ public void get(ResultCallback resultCallback) { .get() .build(); - handleStreamedResponse( + executeAndStream( request, resultCallback, new FramedResponseStreamHandler(resultCallback) @@ -171,7 +171,7 @@ public void run() { .build(); } - handleStreamedResponse( + executeAndStream( okHttpClient, request, resultCallback, @@ -185,7 +185,7 @@ public void post(TypeReference typeReference, ResultCallback resultCal .post(toRequestBody(body, null)) .build(); - handleStreamedResponse( + executeAndStream( request, resultCallback, new JsonResponseCallbackHandler<>(typeReference, resultCallback) @@ -274,11 +274,11 @@ protected Response execute(OkHttpClient okHttpClient, Request request) { } } - protected void handleStreamedResponse(Request request, ResultCallback callback, SimpleChannelInboundHandler handler) { - handleStreamedResponse(okHttpClient, request, callback, handler); + protected void executeAndStream(Request request, ResultCallback callback, SimpleChannelInboundHandler handler) { + executeAndStream(okHttpClient, request, callback, handler); } - protected void handleStreamedResponse(OkHttpClient okHttpClient, Request request, ResultCallback callback, SimpleChannelInboundHandler handler) { + protected void executeAndStream(OkHttpClient okHttpClient, Request request, ResultCallback callback, SimpleChannelInboundHandler handler) { // TODO proper thread management Thread thread = new Thread() { @Override @@ -296,7 +296,7 @@ public void run() { }); byte[] buffer = new byte[4 * 1024]; - while (!(shouldStop.get() || source.exhausted())) { + while (!(shouldStop.get() || !source.isOpen() || source.exhausted())) { int bytesReceived = inputStream.read(buffer); handler.channelRead(null, Unpooled.wrappedBuffer(buffer, 0, bytesReceived)); From da52424af5ea018807aab4c7da4ccb0e4580575f Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Mon, 21 May 2018 17:38:11 +0200 Subject: [PATCH 16/31] fix shading --- core/build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/build.gradle b/core/build.gradle index adc19d18072..3c5c04ee7a1 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -18,6 +18,8 @@ shadowJar { mergeServiceFiles() + exclude 'org/newsclub/**' + [ 'META-INF/io.netty.versions.properties', 'META-INF/NOTICE', From 7cc4c78df288223565e435febcdfafb5a2112641 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Mon, 21 May 2018 18:40:22 +0200 Subject: [PATCH 17/31] remove isOpen check --- .../dockerclient/transport/okhttp/OkHttpInvocationBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java index cc75ed03d48..88b7f96e9d2 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java @@ -296,7 +296,7 @@ public void run() { }); byte[] buffer = new byte[4 * 1024]; - while (!(shouldStop.get() || !source.isOpen() || source.exhausted())) { + while (!(shouldStop.get() || source.exhausted())) { int bytesReceived = inputStream.read(buffer); handler.channelRead(null, Unpooled.wrappedBuffer(buffer, 0, bytesReceived)); From 23fdb7f3ab269b347be672f5c239b68416c3bfe1 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Mon, 21 May 2018 18:58:51 +0200 Subject: [PATCH 18/31] try jnr-unixsocket --- core/build.gradle | 2 +- .../okhttp/OkHttpInvocationBuilder.java | 36 ++++++------- .../transport/okhttp/UnixSocketFactory.java | 54 ++++++++++++++----- 3 files changed, 57 insertions(+), 35 deletions(-) diff --git a/core/build.gradle b/core/build.gradle index 3c5c04ee7a1..da85ecfdc13 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -88,7 +88,7 @@ dependencies { exclude(group: "log4j", module: "log4j") } - shaded 'cglib:cglib-nodep:3.2.6' + compile 'com.github.jnr:jnr-unixsocket:0.19' shaded ('com.github.docker-java:docker-java:3.1.0-rc-3') { exclude(group: 'org.glassfish.jersey.core') diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java index 88b7f96e9d2..43e2f55dea2 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java @@ -146,27 +146,23 @@ public void post(Object entity, InputStream stdin, ResultCallback resultC // FIXME there must be a better way of handling it okHttpClient = okHttpClient.newBuilder() .addNetworkInterceptor(chain -> { - Response response = chain.proceed(chain.request()); - if (response.isSuccessful()) { - Thread thread = new Thread() { - @Override - @SneakyThrows - public void run() { - Field sinkField = RealConnection.class.getDeclaredField("sink"); - sinkField.setAccessible(true); - - try ( - BufferedSink sink = (BufferedSink) sinkField.get(chain.connection()); - Source source = Okio.source(stdin); - ) { - sink.writeAll(source); - sink.flush(); - } + Thread thread = new Thread() { + @Override + @SneakyThrows + public void run() { + Field sinkField = RealConnection.class.getDeclaredField("sink"); + sinkField.setAccessible(true); + + try ( + BufferedSink sink = (BufferedSink) sinkField.get(chain.connection()); + Source source = Okio.source(stdin); + ) { + sink.writeAll(source); } - }; - thread.start(); - } - return response; + } + }; + thread.start(); + return chain.proceed(chain.request()); }) .build(); } diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/UnixSocketFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/UnixSocketFactory.java index ac02099865b..f1a7850b1f6 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/UnixSocketFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/UnixSocketFactory.java @@ -1,16 +1,16 @@ package org.testcontainers.dockerclient.transport.okhttp; +import jnr.unixsocket.UnixSocket; +import jnr.unixsocket.UnixSocketAddress; +import jnr.unixsocket.UnixSocketChannel; import lombok.SneakyThrows; import lombok.Value; -import net.sf.cglib.proxy.Enhancer; -import net.sf.cglib.proxy.InvocationHandler; -import org.newsclub.net.unix.AFUNIXSocket; -import org.newsclub.net.unix.AFUNIXSocketAddress; import javax.net.SocketFactory; -import java.io.File; +import java.io.*; import java.net.InetAddress; import java.net.Socket; +import java.net.SocketAddress; @Value public class UnixSocketFactory extends SocketFactory { @@ -20,19 +20,45 @@ public class UnixSocketFactory extends SocketFactory { @Override @SneakyThrows public Socket createSocket() { - AFUNIXSocket socket = AFUNIXSocket.connectTo(new AFUNIXSocketAddress(new File(socketPath))); + return new UnixSocket(UnixSocketChannel.open()) { - Enhancer enhancer = new Enhancer(); - enhancer.setSuperclass(Socket.class); - enhancer.setCallback((InvocationHandler) (proxy, method, args) -> { - if ("connect".equals(method.getName())) { - return null; + @Override + public void connect(SocketAddress addr, Integer timeout) throws IOException { + addr = new UnixSocketAddress(socketPath); + super.connect(addr, timeout); } - return method.invoke(socket, args); - }); + @Override + public void connect(SocketAddress endpoint, int timeout) throws IOException { + connect(endpoint, new Integer(timeout)); + } + + @Override + public InputStream getInputStream() throws IOException { + return new FilterInputStream(super.getInputStream()) { + @Override + public void close() throws IOException { + shutdownInput(); + } + }; + } - return (Socket) enhancer.create(); + @Override + public OutputStream getOutputStream() throws IOException { + return new FilterOutputStream(super.getOutputStream()) { + + @Override + public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); + } + + @Override + public void close() throws IOException { + shutdownOutput(); + } + }; + } + }; } @Override From 65aa80f04e1a784cd7c6569531b2ce067cef698c Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Mon, 21 May 2018 21:51:02 +0200 Subject: [PATCH 19/31] remove cglib --- build.gradle | 1 - core/build.gradle | 1 - 2 files changed, 2 deletions(-) diff --git a/build.gradle b/build.gradle index 8d17d7c5b26..761828d401d 100644 --- a/build.gradle +++ b/build.gradle @@ -75,7 +75,6 @@ subprojects { "org.zeroturnaround", "okhttp3", "okio", - "net.sf.cglib", ].each { relocate(it, "org.testcontainers.shaded.$it") } } diff --git a/core/build.gradle b/core/build.gradle index da85ecfdc13..e83fc1e05d0 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -54,7 +54,6 @@ shadowJar { include(dependency('commons-codec:commons-codec')) include(dependency('com.squareup.okhttp3:.*')) include(dependency('com.squareup.okio:.*')) - include(dependency('cglib:cglib-nodep')) } } From 3a82facb8ed6b8111bc9d48d8b59c22f2c22e1f4 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Mon, 21 May 2018 22:12:24 +0200 Subject: [PATCH 20/31] try org.scala-sbt.ipcsocket:ipcsocket --- core/build.gradle | 2 +- .../okhttp/OkHttpDockerCmdExecFactory.java | 4 ++++ .../transport/okhttp/UnixSocketFactory.java | 18 ++++-------------- 3 files changed, 9 insertions(+), 15 deletions(-) diff --git a/core/build.gradle b/core/build.gradle index e83fc1e05d0..ba8a4c6140c 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -87,7 +87,7 @@ dependencies { exclude(group: "log4j", module: "log4j") } - compile 'com.github.jnr:jnr-unixsocket:0.19' + compile 'org.scala-sbt.ipcsocket:ipcsocket:1.0.0' shaded ('com.github.docker-java:docker-java:3.1.0-rc-3') { exclude(group: 'org.glassfish.jersey.core') diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java index 8b27d1a8d85..d85dbc7eefe 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java @@ -10,6 +10,7 @@ import com.google.common.collect.MultimapBuilder; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import okhttp3.ConnectionPool; import okhttp3.Dns; import okhttp3.HttpUrl; import okhttp3.OkHttpClient; @@ -23,6 +24,7 @@ import java.net.URI; import java.security.cert.X509Certificate; import java.util.Collections; +import java.util.concurrent.TimeUnit; @Slf4j public class OkHttpDockerCmdExecFactory extends AbstractDockerCmdExecFactory { @@ -49,6 +51,8 @@ public void init(DockerClientConfig dockerClientConfig) { case "unix": String socketPath = dockerHost.getPath(); clientBuilder + // Disable pooling + .connectionPool(new ConnectionPool(0, 1, TimeUnit.SECONDS)) .socketFactory(new UnixSocketFactory(socketPath)) .dns(hostname -> { if (hostname.endsWith(SOCKET_SUFFIX)) { diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/UnixSocketFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/UnixSocketFactory.java index f1a7850b1f6..a9a431dbdb3 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/UnixSocketFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/UnixSocketFactory.java @@ -1,10 +1,8 @@ package org.testcontainers.dockerclient.transport.okhttp; -import jnr.unixsocket.UnixSocket; -import jnr.unixsocket.UnixSocketAddress; -import jnr.unixsocket.UnixSocketChannel; import lombok.SneakyThrows; import lombok.Value; +import org.scalasbt.ipcsocket.UnixDomainSocket; import javax.net.SocketFactory; import java.io.*; @@ -20,21 +18,13 @@ public class UnixSocketFactory extends SocketFactory { @Override @SneakyThrows public Socket createSocket() { - return new UnixSocket(UnixSocketChannel.open()) { - - @Override - public void connect(SocketAddress addr, Integer timeout) throws IOException { - addr = new UnixSocketAddress(socketPath); - super.connect(addr, timeout); - } - + return new UnixDomainSocket(socketPath) { @Override public void connect(SocketAddress endpoint, int timeout) throws IOException { - connect(endpoint, new Integer(timeout)); } @Override - public InputStream getInputStream() throws IOException { + public InputStream getInputStream() { return new FilterInputStream(super.getInputStream()) { @Override public void close() throws IOException { @@ -44,7 +34,7 @@ public void close() throws IOException { } @Override - public OutputStream getOutputStream() throws IOException { + public OutputStream getOutputStream() { return new FilterOutputStream(super.getOutputStream()) { @Override From d9c6db7c59eae07bb1b22d3e57b6993191457757 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Mon, 21 May 2018 22:28:52 +0200 Subject: [PATCH 21/31] experimental npipe support --- .../NpipeSocketClientProviderStrategy.java | 64 ++++++++++++++++ .../okhttp/NamedPipeSocketFactory.java | 73 +++++++++++++++++++ .../okhttp/OkHttpDockerCmdExecFactory.java | 15 +++- .../okhttp/OkHttpInvocationBuilder.java | 35 +++++---- ....dockerclient.DockerClientProviderStrategy | 3 +- 5 files changed, 169 insertions(+), 21 deletions(-) create mode 100644 core/src/main/java/org/testcontainers/dockerclient/NpipeSocketClientProviderStrategy.java create mode 100644 core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/NamedPipeSocketFactory.java diff --git a/core/src/main/java/org/testcontainers/dockerclient/NpipeSocketClientProviderStrategy.java b/core/src/main/java/org/testcontainers/dockerclient/NpipeSocketClientProviderStrategy.java new file mode 100644 index 00000000000..d1acdc6aaae --- /dev/null +++ b/core/src/main/java/org/testcontainers/dockerclient/NpipeSocketClientProviderStrategy.java @@ -0,0 +1,64 @@ +package org.testcontainers.dockerclient; + +import com.github.dockerjava.core.DefaultDockerClientConfig; +import com.github.dockerjava.core.DockerClientConfig; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang.SystemUtils; +import org.jetbrains.annotations.NotNull; +import org.testcontainers.utility.ComparableVersion; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +@Slf4j +public class NpipeSocketClientProviderStrategy extends DockerClientProviderStrategy { + + protected static final String DOCKER_SOCK_PATH = "//./pipe/docker_engine"; + private static final String SOCKET_LOCATION = "npipe://" + DOCKER_SOCK_PATH; + + private static final String PING_TIMEOUT_DEFAULT = "10"; + private static final String PING_TIMEOUT_PROPERTY_NAME = "testcontainers.npipesocketprovider.timeout"; + + public static final int PRIORITY = EnvironmentAndSystemPropertyClientProviderStrategy.PRIORITY - 20; + + @Override + protected boolean isApplicable() { + return SystemUtils.IS_OS_WINDOWS; + } + + @Override + public void test() throws InvalidConfigurationException { + try { + config = tryConfiguration(SOCKET_LOCATION); + log.info("Accessing docker with local Unix socket"); + } catch (Exception | UnsatisfiedLinkError e) { + throw new InvalidConfigurationException("ping failed", e); + } + } + + @NotNull + protected DockerClientConfig tryConfiguration(String dockerHost) { + config = DefaultDockerClientConfig.createDefaultConfigBuilder() + .withDockerHost(dockerHost) + .withDockerTlsVerify(false) + .build(); + client = getClientForConfig(config); + + final int timeout = Integer.parseInt(System.getProperty(PING_TIMEOUT_PROPERTY_NAME, PING_TIMEOUT_DEFAULT)); + ping(client, timeout); + + return config; + } + + @Override + public String getDescription() { + return "local Npipe socket (" + SOCKET_LOCATION + ")"; + } + + @Override + protected int getPriority() { + return PRIORITY; + } +} diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/NamedPipeSocketFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/NamedPipeSocketFactory.java new file mode 100644 index 00000000000..3b065198625 --- /dev/null +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/NamedPipeSocketFactory.java @@ -0,0 +1,73 @@ +package org.testcontainers.dockerclient.transport.okhttp; + +import lombok.SneakyThrows; +import lombok.Value; +import org.scalasbt.ipcsocket.Win32NamedPipeSocket; + +import javax.net.SocketFactory; +import java.io.*; +import java.net.InetAddress; +import java.net.Socket; +import java.net.SocketAddress; + +@Value +public class NamedPipeSocketFactory extends SocketFactory { + + String socketPath; + + @Override + @SneakyThrows + public Socket createSocket() { + return new Win32NamedPipeSocket(socketPath) { + @Override + public void connect(SocketAddress endpoint, int timeout) throws IOException { + } + + @Override + public InputStream getInputStream() { + return new FilterInputStream(super.getInputStream()) { + @Override + public void close() throws IOException { + shutdownInput(); + } + }; + } + + @Override + public OutputStream getOutputStream() { + return new FilterOutputStream(super.getOutputStream()) { + + @Override + public void write(byte[] b, int off, int len) throws IOException { + out.write(b, off, len); + } + + @Override + public void close() throws IOException { + shutdownOutput(); + } + }; + } + }; + } + + @Override + public Socket createSocket(String s, int i) { + throw new UnsupportedOperationException(); + } + + @Override + public Socket createSocket(String s, int i, InetAddress inetAddress, int i1) { + throw new UnsupportedOperationException(); + } + + @Override + public Socket createSocket(InetAddress inetAddress, int i) { + throw new UnsupportedOperationException(); + } + + @Override + public Socket createSocket(InetAddress inetAddress, int i, InetAddress inetAddress1, int i1) { + throw new UnsupportedOperationException(); + } +} diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java index d85dbc7eefe..6b40fa71bbc 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java @@ -45,15 +45,21 @@ public void init(DockerClientConfig dockerClientConfig) { URI dockerHost = dockerClientConfig.getDockerHost(); switch (dockerHost.getScheme()) { - case "npipe": - // TODO support it - throw new IllegalArgumentException("npipe protocol is not supported yet"); case "unix": + case "npipe": String socketPath = dockerHost.getPath(); + + if ("unix".equals(dockerHost.getScheme())) { + clientBuilder + .socketFactory(new UnixSocketFactory(socketPath)); + } else { + clientBuilder + .socketFactory(new NamedPipeSocketFactory(socketPath)); + } + clientBuilder // Disable pooling .connectionPool(new ConnectionPool(0, 1, TimeUnit.SECONDS)) - .socketFactory(new UnixSocketFactory(socketPath)) .dns(hostname -> { if (hostname.endsWith(SOCKET_SUFFIX)) { return Collections.singletonList(InetAddress.getByAddress(hostname, new byte[]{0, 0, 0, 0})); @@ -78,6 +84,7 @@ public void init(DockerClientConfig dockerClientConfig) { switch (dockerHost.getScheme()) { case "unix": + case "npipe": baseUrlBuilder = new HttpUrl.Builder() .scheme("http") .host("docker" + SOCKET_SUFFIX); diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java index 43e2f55dea2..8738cb8c351 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java @@ -146,23 +146,26 @@ public void post(Object entity, InputStream stdin, ResultCallback resultC // FIXME there must be a better way of handling it okHttpClient = okHttpClient.newBuilder() .addNetworkInterceptor(chain -> { - Thread thread = new Thread() { - @Override - @SneakyThrows - public void run() { - Field sinkField = RealConnection.class.getDeclaredField("sink"); - sinkField.setAccessible(true); - - try ( - BufferedSink sink = (BufferedSink) sinkField.get(chain.connection()); - Source source = Okio.source(stdin); - ) { - sink.writeAll(source); + Response response = chain.proceed(chain.request()); + if (response.isSuccessful()) { + Thread thread = new Thread() { + @Override + @SneakyThrows + public void run() { + Field sinkField = RealConnection.class.getDeclaredField("sink"); + sinkField.setAccessible(true); + + try ( + BufferedSink sink = (BufferedSink) sinkField.get(chain.connection()); + Source source = Okio.source(stdin); + ) { + sink.writeAll(source); + } } - } - }; - thread.start(); - return chain.proceed(chain.request()); + }; + thread.start(); + } + return response; }) .build(); } diff --git a/core/src/main/resources/META-INF/services/org.testcontainers.dockerclient.DockerClientProviderStrategy b/core/src/main/resources/META-INF/services/org.testcontainers.dockerclient.DockerClientProviderStrategy index 9c8db41a429..25bbb30d725 100644 --- a/core/src/main/resources/META-INF/services/org.testcontainers.dockerclient.DockerClientProviderStrategy +++ b/core/src/main/resources/META-INF/services/org.testcontainers.dockerclient.DockerClientProviderStrategy @@ -2,4 +2,5 @@ org.testcontainers.dockerclient.EnvironmentAndSystemPropertyClientProviderStrate org.testcontainers.dockerclient.UnixSocketClientProviderStrategy org.testcontainers.dockerclient.ProxiedUnixSocketClientProviderStrategy org.testcontainers.dockerclient.DockerMachineClientProviderStrategy -org.testcontainers.dockerclient.WindowsClientProviderStrategy \ No newline at end of file +org.testcontainers.dockerclient.WindowsClientProviderStrategy +org.testcontainers.dockerclient.NpipeSocketClientProviderStrategy From 32ac15dbe3ec1185420eb98db21f3218ceb7003f Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Tue, 22 May 2018 07:51:37 +0200 Subject: [PATCH 22/31] fix pipe name --- .../dockerclient/transport/okhttp/NamedPipeSocketFactory.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/NamedPipeSocketFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/NamedPipeSocketFactory.java index 3b065198625..27b310fd759 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/NamedPipeSocketFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/NamedPipeSocketFactory.java @@ -18,7 +18,8 @@ public class NamedPipeSocketFactory extends SocketFactory { @Override @SneakyThrows public Socket createSocket() { - return new Win32NamedPipeSocket(socketPath) { + return new Win32NamedPipeSocket(socketPath.replace("/", "\\\\")) { + @Override public void connect(SocketAddress endpoint, int timeout) throws IOException { } From 40b47c28209585dfcad2e3eda9d678d88b3e9378 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Tue, 22 May 2018 07:52:56 +0200 Subject: [PATCH 23/31] fix double escaping --- .../dockerclient/transport/okhttp/NamedPipeSocketFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/NamedPipeSocketFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/NamedPipeSocketFactory.java index 27b310fd759..0caa6d2bfb1 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/NamedPipeSocketFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/NamedPipeSocketFactory.java @@ -18,7 +18,7 @@ public class NamedPipeSocketFactory extends SocketFactory { @Override @SneakyThrows public Socket createSocket() { - return new Win32NamedPipeSocket(socketPath.replace("/", "\\\\")) { + return new Win32NamedPipeSocket(socketPath.replace("/", "\\")) { @Override public void connect(SocketAddress endpoint, int timeout) throws IOException { From 6ed7479dbe24924b33b85418156a81d8ffd5df58 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Tue, 22 May 2018 23:09:25 +0200 Subject: [PATCH 24/31] shade ipcsocket --- build.gradle | 1 + core/build.gradle | 7 ++++++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 761828d401d..edee9d34d28 100644 --- a/build.gradle +++ b/build.gradle @@ -75,6 +75,7 @@ subprojects { "org.zeroturnaround", "okhttp3", "okio", + "org.scalasbt.ipcsocket", ].each { relocate(it, "org.testcontainers.shaded.$it") } } diff --git a/core/build.gradle b/core/build.gradle index ba8a4c6140c..15a0300738f 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -54,6 +54,7 @@ shadowJar { include(dependency('commons-codec:commons-codec')) include(dependency('com.squareup.okhttp3:.*')) include(dependency('com.squareup.okio:.*')) + include(dependency('org.scala-sbt.ipcsocket:ipcsocket')) } } @@ -87,7 +88,11 @@ dependencies { exclude(group: "log4j", module: "log4j") } - compile 'org.scala-sbt.ipcsocket:ipcsocket:1.0.0' + compile "net.java.dev.jna:jna-platform:4.5.1" + + shaded ('org.scala-sbt.ipcsocket:ipcsocket:1.0.0') { + exclude(group: "net.java.dev.jna") + } shaded ('com.github.docker-java:docker-java:3.1.0-rc-3') { exclude(group: 'org.glassfish.jersey.core') From c5eda4f3520b50915f842642ad387c3d4ec780ab Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Wed, 23 May 2018 07:38:57 +0200 Subject: [PATCH 25/31] disable read timeout --- .../transport/okhttp/OkHttpDockerCmdExecFactory.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java index 6b40fa71bbc..267bd8d34fb 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java @@ -41,6 +41,7 @@ public void init(DockerClientConfig dockerClientConfig) { super.init(dockerClientConfig); OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder() + .readTimeout(0, TimeUnit.SECONDS) .retryOnConnectionFailure(true); URI dockerHost = dockerClientConfig.getDockerHost(); From 8cabd49da275f8ee5f2fca453dcc77d0a20fb28f Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Mon, 28 May 2018 07:51:35 +0200 Subject: [PATCH 26/31] make OkHttp opt-in-able --- circle.yml | 16 ++++++++++++++ .../DockerClientProviderStrategy.java | 22 +++++++++++++++---- .../utility/TestcontainersConfiguration.java | 4 ++++ 3 files changed, 38 insertions(+), 4 deletions(-) diff --git a/circle.yml b/circle.yml index a793bf8cb6a..5eee264cc1f 100644 --- a/circle.yml +++ b/circle.yml @@ -14,6 +14,21 @@ jobs: when: always - store_test_results: path: ~/junit + okhttp: + steps: + - checkout + - run: + command: | + echo "transport.type=okhttp" >> core/src/test/resources/ + ./gradlew testcontainers:check + - run: + name: Save test results + command: | + mkdir -p ~/junit/ + find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/junit/ \; + when: always + - store_test_results: + path: ~/junit modules-no-jdbc-test-no-selenium: steps: - checkout @@ -65,6 +80,7 @@ workflows: test_all: jobs: - core + - okhttp - modules-no-jdbc-test-no-selenium - modules-jdbc-test - selenium diff --git a/core/src/main/java/org/testcontainers/dockerclient/DockerClientProviderStrategy.java b/core/src/main/java/org/testcontainers/dockerclient/DockerClientProviderStrategy.java index 3048da3f030..117acece7db 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/DockerClientProviderStrategy.java +++ b/core/src/main/java/org/testcontainers/dockerclient/DockerClientProviderStrategy.java @@ -12,6 +12,7 @@ import org.rnorth.ducttape.unreliables.Unreliables; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.testcontainers.dockerclient.transport.TestcontainersDockerCmdExecFactory; import org.testcontainers.dockerclient.transport.okhttp.OkHttpDockerCmdExecFactory; import org.testcontainers.utility.TestcontainersConfiguration; @@ -164,10 +165,23 @@ public DockerClient getClient() { } protected DockerClient getClientForConfig(DockerClientConfig config) { - return DockerClientBuilder - .getInstance(config) - .withDockerCmdExecFactory(new OkHttpDockerCmdExecFactory()) - .build(); + DockerClientBuilder clientBuilder = DockerClientBuilder + .getInstance(config); + + String transportType = TestcontainersConfiguration.getInstance().getTransportType(); + if ("okhttp".equals(transportType)) { + clientBuilder + .withDockerCmdExecFactory(new OkHttpDockerCmdExecFactory()); + } else if ("netty".equals(transportType)) { + clientBuilder + .withDockerCmdExecFactory(new TestcontainersDockerCmdExecFactory()); + } else { + throw new IllegalArgumentException("Unknown transport type: " + transportType); + } + + LOGGER.info("Will use '{}' transport", transportType); + + return clientBuilder.build(); } protected void ping(DockerClient client, int timeoutInSeconds) { diff --git a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java index 1b5f9ce9a59..add7ad7b026 100644 --- a/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java +++ b/core/src/main/java/org/testcontainers/utility/TestcontainersConfiguration.java @@ -66,6 +66,10 @@ public String getDockerClientStrategyClassName() { return (String) properties.get("docker.client.strategy"); } + public String getTransportType() { + return properties.getProperty("transport.type", "netty"); + } + @Synchronized public boolean updateGlobalConfig(@NonNull String prop, @NonNull String value) { try { From 9a0e3024b28937d8bb461388145597c4706f0b06 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Mon, 28 May 2018 07:54:14 +0200 Subject: [PATCH 27/31] fix properties file name --- circle.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/circle.yml b/circle.yml index 5eee264cc1f..b216656417f 100644 --- a/circle.yml +++ b/circle.yml @@ -19,7 +19,7 @@ jobs: - checkout - run: command: | - echo "transport.type=okhttp" >> core/src/test/resources/ + echo "transport.type=okhttp" >> core/src/test/resources/testcontainers.properties ./gradlew testcontainers:check - run: name: Save test results From c6f962b2f047996d75169925e7f9714a7038a146 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Mon, 28 May 2018 09:51:52 +0200 Subject: [PATCH 28/31] fix typo --- .../dockerclient/NpipeSocketClientProviderStrategy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/testcontainers/dockerclient/NpipeSocketClientProviderStrategy.java b/core/src/main/java/org/testcontainers/dockerclient/NpipeSocketClientProviderStrategy.java index d1acdc6aaae..8d9758bfeff 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/NpipeSocketClientProviderStrategy.java +++ b/core/src/main/java/org/testcontainers/dockerclient/NpipeSocketClientProviderStrategy.java @@ -32,7 +32,7 @@ protected boolean isApplicable() { public void test() throws InvalidConfigurationException { try { config = tryConfiguration(SOCKET_LOCATION); - log.info("Accessing docker with local Unix socket"); + log.info("Accessing docker with {}", getDescription()); } catch (Exception | UnsatisfiedLinkError e) { throw new InvalidConfigurationException("ping failed", e); } From 5cf871b5117be60ce66568d64ae03a4c78aa11e8 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Mon, 28 May 2018 10:00:19 +0200 Subject: [PATCH 29/31] fix Codacy warnings --- .../NpipeSocketClientProviderStrategy.java | 12 +++--------- .../okhttp/NamedPipeSocketFactory.java | 7 ++++++- .../okhttp/OkHttpDockerCmdExecFactory.java | 1 + .../okhttp/OkHttpInvocationBuilder.java | 19 ++++++++++++++++--- .../transport/okhttp/UnixSocketFactory.java | 7 ++++++- 5 files changed, 32 insertions(+), 14 deletions(-) diff --git a/core/src/main/java/org/testcontainers/dockerclient/NpipeSocketClientProviderStrategy.java b/core/src/main/java/org/testcontainers/dockerclient/NpipeSocketClientProviderStrategy.java index 8d9758bfeff..25b779ac368 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/NpipeSocketClientProviderStrategy.java +++ b/core/src/main/java/org/testcontainers/dockerclient/NpipeSocketClientProviderStrategy.java @@ -5,12 +5,6 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.SystemUtils; import org.jetbrains.annotations.NotNull; -import org.testcontainers.utility.ComparableVersion; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; @Slf4j public class NpipeSocketClientProviderStrategy extends DockerClientProviderStrategy { @@ -41,9 +35,9 @@ public void test() throws InvalidConfigurationException { @NotNull protected DockerClientConfig tryConfiguration(String dockerHost) { config = DefaultDockerClientConfig.createDefaultConfigBuilder() - .withDockerHost(dockerHost) - .withDockerTlsVerify(false) - .build(); + .withDockerHost(dockerHost) + .withDockerTlsVerify(false) + .build(); client = getClientForConfig(config); final int timeout = Integer.parseInt(System.getProperty(PING_TIMEOUT_PROPERTY_NAME, PING_TIMEOUT_DEFAULT)); diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/NamedPipeSocketFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/NamedPipeSocketFactory.java index 0caa6d2bfb1..0071dea036d 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/NamedPipeSocketFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/NamedPipeSocketFactory.java @@ -5,7 +5,11 @@ import org.scalasbt.ipcsocket.Win32NamedPipeSocket; import javax.net.SocketFactory; -import java.io.*; +import java.io.FilterInputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; import java.net.SocketAddress; @@ -22,6 +26,7 @@ public Socket createSocket() { @Override public void connect(SocketAddress endpoint, int timeout) throws IOException { + // Do nothing since it's not "connectable" } @Override diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java index 267bd8d34fb..08e0dde046b 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpDockerCmdExecFactory.java @@ -68,6 +68,7 @@ public void init(DockerClientConfig dockerClientConfig) { return Dns.SYSTEM.lookup(hostname); } }); + default: } SSLConfig sslConfig = dockerClientConfig.getSSLConfig(); diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java index 8738cb8c351..17727aa3c16 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/OkHttpInvocationBuilder.java @@ -4,7 +4,14 @@ import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.dockerjava.api.async.ResultCallback; -import com.github.dockerjava.api.exception.*; +import com.github.dockerjava.api.exception.BadRequestException; +import com.github.dockerjava.api.exception.ConflictException; +import com.github.dockerjava.api.exception.DockerException; +import com.github.dockerjava.api.exception.InternalServerErrorException; +import com.github.dockerjava.api.exception.NotAcceptableException; +import com.github.dockerjava.api.exception.NotFoundException; +import com.github.dockerjava.api.exception.NotModifiedException; +import com.github.dockerjava.api.exception.UnauthorizedException; import com.github.dockerjava.api.model.Frame; import com.github.dockerjava.core.InvocationBuilder; import com.github.dockerjava.netty.handler.FramedResponseStreamHandler; @@ -12,10 +19,16 @@ import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.SimpleChannelInboundHandler; +import lombok.AccessLevel; import lombok.SneakyThrows; import lombok.experimental.FieldDefaults; import lombok.extern.slf4j.Slf4j; -import okhttp3.*; +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; import okhttp3.internal.connection.RealConnection; import okio.BufferedSink; import okio.BufferedSource; @@ -30,7 +43,7 @@ import java.util.concurrent.atomic.AtomicBoolean; @Slf4j -@FieldDefaults(makeFinal = true) +@FieldDefaults(level = AccessLevel.PRIVATE, makeFinal = true) class OkHttpInvocationBuilder implements InvocationBuilder { ObjectMapper objectMapper; diff --git a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/UnixSocketFactory.java b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/UnixSocketFactory.java index a9a431dbdb3..c23575c2307 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/UnixSocketFactory.java +++ b/core/src/main/java/org/testcontainers/dockerclient/transport/okhttp/UnixSocketFactory.java @@ -5,7 +5,11 @@ import org.scalasbt.ipcsocket.UnixDomainSocket; import javax.net.SocketFactory; -import java.io.*; +import java.io.FilterInputStream; +import java.io.FilterOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.net.InetAddress; import java.net.Socket; import java.net.SocketAddress; @@ -21,6 +25,7 @@ public Socket createSocket() { return new UnixDomainSocket(socketPath) { @Override public void connect(SocketAddress endpoint, int timeout) throws IOException { + // Do nothing since it's not "connectable" } @Override From d384b16b2b5315d4779d9527fef919de3faf16be Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Mon, 11 Jun 2018 22:28:05 +0200 Subject: [PATCH 30/31] workaround docker-java (does not support npipe) --- .../NpipeSocketClientProviderStrategy.java | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/core/src/main/java/org/testcontainers/dockerclient/NpipeSocketClientProviderStrategy.java b/core/src/main/java/org/testcontainers/dockerclient/NpipeSocketClientProviderStrategy.java index 25b779ac368..b8d85f11623 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/NpipeSocketClientProviderStrategy.java +++ b/core/src/main/java/org/testcontainers/dockerclient/NpipeSocketClientProviderStrategy.java @@ -2,10 +2,14 @@ import com.github.dockerjava.core.DefaultDockerClientConfig; import com.github.dockerjava.core.DockerClientConfig; +import lombok.RequiredArgsConstructor; +import lombok.experimental.Delegate; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.SystemUtils; import org.jetbrains.annotations.NotNull; +import java.net.URI; + @Slf4j public class NpipeSocketClientProviderStrategy extends DockerClientProviderStrategy { @@ -25,7 +29,7 @@ protected boolean isApplicable() { @Override public void test() throws InvalidConfigurationException { try { - config = tryConfiguration(SOCKET_LOCATION); + config = tryConfiguration(); log.info("Accessing docker with {}", getDescription()); } catch (Exception | UnsatisfiedLinkError e) { throw new InvalidConfigurationException("ping failed", e); @@ -33,11 +37,20 @@ public void test() throws InvalidConfigurationException { } @NotNull - protected DockerClientConfig tryConfiguration(String dockerHost) { - config = DefaultDockerClientConfig.createDefaultConfigBuilder() - .withDockerHost(dockerHost) - .withDockerTlsVerify(false) - .build(); + private DockerClientConfig tryConfiguration() { + URI dockerHost = URI.create(SOCKET_LOCATION); + + config = new DelegatingDockerClientConfig( + DefaultDockerClientConfig.createDefaultConfigBuilder() + .withDockerHost("tcp://localhost:0") + .withDockerTlsVerify(false) + .build() + ) { + @Override + public URI getDockerHost() { + return dockerHost; + } + }; client = getClientForConfig(config); final int timeout = Integer.parseInt(System.getProperty(PING_TIMEOUT_PROPERTY_NAME, PING_TIMEOUT_DEFAULT)); @@ -55,4 +68,11 @@ public String getDescription() { protected int getPriority() { return PRIORITY; } + + @RequiredArgsConstructor + private static class DelegatingDockerClientConfig implements DockerClientConfig { + + @Delegate + final DockerClientConfig dockerClientConfig; + } } From 2e1bc261f776dfa8fd89c869b1eeb177f04f7da5 Mon Sep 17 00:00:00 2001 From: Sergei Egorov Date: Mon, 11 Jun 2018 22:41:51 +0200 Subject: [PATCH 31/31] support npipe in DockerClientConfigUtils --- .../org/testcontainers/dockerclient/DockerClientConfigUtils.java | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java b/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java index d9c1dd2ce58..bfeabc63f8a 100644 --- a/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java +++ b/core/src/main/java/org/testcontainers/dockerclient/DockerClientConfigUtils.java @@ -52,6 +52,7 @@ public static String getDockerHostIpAddress(DockerClientConfig config) { case "tcp": return config.getDockerHost().getHost(); case "unix": + case "npipe": if (IN_A_CONTAINER) { return getDefaultGateway().orElse("localhost"); }