diff --git a/apis/docker/src/main/java/org/jclouds/docker/domain/Exec.java b/apis/docker/src/main/java/org/jclouds/docker/domain/Exec.java new file mode 100644 index 0000000000..7511f63547 --- /dev/null +++ b/apis/docker/src/main/java/org/jclouds/docker/domain/Exec.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.docker.domain; + +import org.jclouds.json.SerializedNames; + +import com.google.auto.value.AutoValue; + +/** + * Represents a response from Exec Create call (POST /containers/(id)/exec). + */ +@AutoValue +public abstract class Exec { + + public abstract String id(); + + @SerializedNames({ "Id"}) + public static Exec create(String id) { + return new AutoValue_Exec(id); + } +} diff --git a/apis/docker/src/main/java/org/jclouds/docker/domain/ExecCreateParams.java b/apis/docker/src/main/java/org/jclouds/docker/domain/ExecCreateParams.java new file mode 100644 index 0000000000..3f6009bc5f --- /dev/null +++ b/apis/docker/src/main/java/org/jclouds/docker/domain/ExecCreateParams.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.docker.domain; + +import static org.jclouds.docker.internal.NullSafeCopies.copyOf; + +import java.util.List; + +import org.jclouds.json.SerializedNames; + +import com.google.auto.value.AutoValue; + +/** + * Json Parameters (some of them) of Exec Create call. + */ +@AutoValue +public abstract class ExecCreateParams { + + public abstract boolean attachStdout(); + + public abstract boolean attachStderr(); + + public abstract List cmd(); + + @SerializedNames({ "AttachStdout", "AttachStderr", "Cmd" }) + private static ExecCreateParams create(boolean attachStdout, boolean attachStderr, List cmd) { + return builder().attachStdout(attachStdout).attachStderr(attachStderr).cmd(cmd).build(); + } + + /** + * Creates builder for {@link ExecCreateParams}, it sets + * {@link #attachStderr()} and {@link #attachStdout()} to true as a default. + * + * @return new {@link ExecCreateParams.Builder} instance + */ + public static Builder builder() { + return new AutoValue_ExecCreateParams.Builder().attachStderr(true).attachStdout(true); + } + + @AutoValue.Builder + public abstract static class Builder { + + public abstract Builder attachStdout(boolean b); + + public abstract Builder attachStderr(boolean b); + + public abstract Builder cmd(List cmd); + + abstract List cmd(); + + abstract ExecCreateParams autoBuild(); + + public ExecCreateParams build() { + cmd(copyOf(cmd())); + return autoBuild(); + } + } +} diff --git a/apis/docker/src/main/java/org/jclouds/docker/domain/ExecInspect.java b/apis/docker/src/main/java/org/jclouds/docker/domain/ExecInspect.java new file mode 100644 index 0000000000..d15bccfc13 --- /dev/null +++ b/apis/docker/src/main/java/org/jclouds/docker/domain/ExecInspect.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.docker.domain; + +import org.jclouds.json.SerializedNames; + +import com.google.auto.value.AutoValue; + +/** + * Represents a response (part of it) from Exec Inspect call ( + * GET /exec/(id)/json). + */ +@AutoValue +public abstract class ExecInspect { + + public abstract String id(); + + public abstract boolean running(); + + public abstract int exitCode(); + + @SerializedNames({ "ID", "Running", "ExitCode"}) + public static ExecInspect create(String id, boolean running, int exitCode) { + return new AutoValue_ExecInspect(id, running, exitCode); + } +} diff --git a/apis/docker/src/main/java/org/jclouds/docker/domain/ExecStartParams.java b/apis/docker/src/main/java/org/jclouds/docker/domain/ExecStartParams.java new file mode 100644 index 0000000000..f8046f8abf --- /dev/null +++ b/apis/docker/src/main/java/org/jclouds/docker/domain/ExecStartParams.java @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.jclouds.docker.domain; + +import org.jclouds.json.SerializedNames; + +import com.google.auto.value.AutoValue; + +/** + * Json Parameter(s) (some of them) of Exec Start call. + */ +@AutoValue +public abstract class ExecStartParams { + + public abstract boolean detach(); + + @SerializedNames({ "Detach" }) + public static ExecStartParams create(boolean detach) { + return builder().detach(detach).build(); + } + + public static Builder builder() { + return new AutoValue_ExecStartParams.Builder().detach(false); + } + + @AutoValue.Builder + public abstract static class Builder { + + public abstract Builder detach(boolean b); + + public abstract ExecStartParams build(); + } +} diff --git a/apis/docker/src/main/java/org/jclouds/docker/features/MiscApi.java b/apis/docker/src/main/java/org/jclouds/docker/features/MiscApi.java index 8e5548ede7..5df5d04b95 100644 --- a/apis/docker/src/main/java/org/jclouds/docker/features/MiscApi.java +++ b/apis/docker/src/main/java/org/jclouds/docker/features/MiscApi.java @@ -23,13 +23,21 @@ import javax.ws.rs.Consumes; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; import javax.ws.rs.core.MediaType; +import org.jclouds.docker.domain.Exec; +import org.jclouds.docker.domain.ExecCreateParams; +import org.jclouds.docker.domain.ExecInspect; +import org.jclouds.docker.domain.ExecStartParams; import org.jclouds.docker.domain.Info; import org.jclouds.docker.domain.Version; import org.jclouds.docker.options.BuildOptions; +import org.jclouds.docker.util.DockerInputStream; import org.jclouds.io.Payload; +import org.jclouds.rest.annotations.BinderParam; import org.jclouds.rest.annotations.Headers; +import org.jclouds.rest.binders.BindToJsonPayload; @Consumes(MediaType.APPLICATION_JSON) @Path("/v{jclouds.api-version}") @@ -82,4 +90,51 @@ public interface MiscApi { @Headers(keys = { "Content-Type", "Connection" }, values = { "application/tar", "close" }) InputStream build(Payload inputStream, BuildOptions options); + /** + * Sets up an exec instance in a running container with given Id. + * + * @param containerId + * container Id + * @param execCreateParams + * exec parameters + * @return an instance which holds exec identifier + */ + @Named("container:exec") + @POST + @Path("/containers/{id}/exec") + Exec execCreate(@PathParam("id") String containerId, + @BinderParam(BindToJsonPayload.class) ExecCreateParams execCreateParams); + + /** + * Starts a previously set up exec instance id. If + * {@link ExecStartParams#detach()} is true, this API returns after starting + * the exec command. Otherwise, this API sets up an interactive session with + * the exec command. + * + * @param execId + * exec instance id + * @param execStartParams + * start parameters + * @return raw docker stream which can be wrapped to + * {@link DockerInputStream} + * @see #execCreate(String, ExecCreateParams) + * @see DockerInputStream + */ + @Named("exec:start") + @POST + @Path("/exec/{id}/start") + InputStream execStart(@PathParam("id") String execId, + @BinderParam(BindToJsonPayload.class) ExecStartParams execStartParams); + + /** + * Returns low-level information about the exec command id. + * + * @param execId + * exec instance id + * @return details about exec instance + */ + @Named("exec:inspect") + @GET + @Path("/exec/{id}/json") + ExecInspect execInspect(@PathParam("id") String execId); } diff --git a/apis/docker/src/main/java/org/jclouds/docker/util/DockerInputStream.java b/apis/docker/src/main/java/org/jclouds/docker/util/DockerInputStream.java new file mode 100644 index 0000000000..dacd7b7681 --- /dev/null +++ b/apis/docker/src/main/java/org/jclouds/docker/util/DockerInputStream.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jclouds.docker.util; + +import java.io.DataInputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Extension to {@link DataInputStream} which adds method + * {@link #readStdStreamData()} to allow read multiplexed standard streams. + */ +public final class DockerInputStream extends DataInputStream { + + /** + * Ctor from superclass. + * + * @param in + * @see DataInputStream#DataInputStream(InputStream) + */ + public DockerInputStream(InputStream in) { + super(in); + } + + /** + * @return {@link StdStreamData} instance read from the input stream or + * null if we reached end of the stream. + * @throws IOException + */ + public StdStreamData readStdStreamData() throws IOException { + byte[] header = new byte[8]; + // try to read first byte from the message header - just to check if we + // are at the end + // of stream + if (-1 == read(header, 0, 1)) { + return null; + } + // read the rest of the header + readFully(header, 1, 7); + // decode size as an unsigned int + long size = (long) (header[4] & 0xFF) << 24 | (header[5] & 0xFF) << 16 | (header[6] & 0xFF) << 8 + | (header[7] & 0xFF); + + byte[] payload; + // The size from the header is an unsigned int so it can happen the byte + // array has not a sufficient size and we'll have to truncate the frame + payload = new byte[(int) Math.min(Integer.MAX_VALUE, size)]; + readFully(payload); + boolean truncated = false; + if (size > Integer.MAX_VALUE) { + truncated = true; + // skip the rest + readFully(new byte[(int) (size - Integer.MAX_VALUE)]); + } + return new StdStreamData(header[0], payload, truncated); + } + +} diff --git a/apis/docker/src/main/java/org/jclouds/docker/util/StdStreamData.java b/apis/docker/src/main/java/org/jclouds/docker/util/StdStreamData.java new file mode 100644 index 0000000000..f8f5f9a150 --- /dev/null +++ b/apis/docker/src/main/java/org/jclouds/docker/util/StdStreamData.java @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jclouds.docker.util; + +import java.util.Arrays; + +/** + * Representation of single message from docker-raw-stream. It holds stream + * type, data (payload) and flag which says if the payload was truncated. The + * truncation can occur when the frame size is greater than + * {@link Integer#MAX_VALUE}. + */ +public final class StdStreamData { + + private final StdStreamType type; + private final byte[] payload; + private final boolean truncated; + + /** + * Ctor. + * + * @param streamTypeId + * standard stream type (0=stdIn, 1=stdOut, 2=stdErr) + * @param payload + * message data - must not be null + * @param truncated + * @throws ArrayIndexOutOfBoundsException + * if streamTypeId is not an index in {@link StdStreamType} enum. + * @throws NullPointerException + * if provided payload is null + */ + StdStreamData(byte streamTypeId, byte[] payload, boolean truncated) + throws ArrayIndexOutOfBoundsException, NullPointerException { + this.type = StdStreamType.values()[streamTypeId]; + this.payload = Arrays.copyOf(payload, payload.length); + this.truncated = truncated; + } + + /** + * Type of stream. + * + * @return + */ + public StdStreamType getType() { + return type; + } + + /** + * Data from this message. + * + * @return payload. + */ + public byte[] getPayload() { + return payload; + } + + /** + * Flag which says if the payload was truncated (because of size). + * + * @return true if truncated + */ + public boolean isTruncated() { + return truncated; + } + + /** + * Standard streams enum. The order of entries is important! + */ + public static enum StdStreamType { + IN, OUT, ERR; + } +} diff --git a/apis/docker/src/test/java/org/jclouds/docker/features/MiscApiLiveTest.java b/apis/docker/src/test/java/org/jclouds/docker/features/MiscApiLiveTest.java index ce2bc9e1c9..8cdd76bf5e 100644 --- a/apis/docker/src/test/java/org/jclouds/docker/features/MiscApiLiveTest.java +++ b/apis/docker/src/test/java/org/jclouds/docker/features/MiscApiLiveTest.java @@ -16,23 +16,79 @@ */ package org.jclouds.docker.features; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertTrue; + +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.net.URISyntaxException; +import org.assertj.core.api.Fail; import org.jclouds.docker.compute.BaseDockerApiLiveTest; +import org.jclouds.docker.domain.Config; +import org.jclouds.docker.domain.Container; +import org.jclouds.docker.domain.Exec; +import org.jclouds.docker.domain.ExecCreateParams; +import org.jclouds.docker.domain.ExecInspect; +import org.jclouds.docker.domain.ExecStartParams; +import org.jclouds.docker.domain.Image; import org.jclouds.docker.options.BuildOptions; +import org.jclouds.docker.options.CreateImageOptions; +import org.jclouds.docker.options.RemoveContainerOptions; +import org.jclouds.docker.util.DockerInputStream; +import org.jclouds.docker.util.StdStreamData; +import org.testng.annotations.AfterClass; +import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; @Test(groups = "live", testName = "MiscApiLiveTest", singleThreaded = true) public class MiscApiLiveTest extends BaseDockerApiLiveTest { + protected static final String BUSYBOX_IMAGE_TAG = "busybox:ubuntu-12.04"; + private static String imageId; + private Container container = null; + private Image image = null; + private Exec exec = null; + + @BeforeClass + protected void init() { + if (api.getImageApi().inspectImage(BUSYBOX_IMAGE_TAG) == null) { + CreateImageOptions options = CreateImageOptions.Builder.fromImage(BUSYBOX_IMAGE_TAG); + InputStream createImageStream = api.getImageApi().createImage(options); + consumeStream(createImageStream); + } + image = api.getImageApi().inspectImage(BUSYBOX_IMAGE_TAG); + assertNotNull(image); + Config containerConfig = Config.builder().image(image.id()) + .cmd(ImmutableList.of("/bin/sh", "-c", "touch hello; while true; do echo hello world; sleep 1; done")) + .build(); + container = api.getContainerApi().createContainer("miscApiTest", containerConfig); + assertNotNull(container); + api.getContainerApi().startContainer(container.id()); + assertTrue(api.getContainerApi().inspectContainer(container.id()).state().running()); + } + + @AfterClass + protected void tearDown() { + if (container != null) { + if (api.getContainerApi().inspectContainer(container.id()) != null) { + api.getContainerApi().removeContainer(container.id(), RemoveContainerOptions.Builder.force(true)); + } + } + if (image != null) { + api.getImageApi().deleteImage(BUSYBOX_IMAGE_TAG); + } + } + @Test public void testVersion() { assertNotNull(api().getVersion().apiVersion()); @@ -61,9 +117,62 @@ public class MiscApiLiveTest extends BaseDockerApiLiveTest { assertNotNull(imageId); } + @Test + public void testExecCreate() { + exec = api().execCreate(container.id(), + ExecCreateParams.builder() + .cmd(ImmutableList. of("/bin/sh", "-c", + "echo -n Standard >&1 && echo -n Error >&2 && exit 2")) + .attachStderr(true).attachStdout(true).build()); + assertNotNull(exec); + assertNotNull(exec.id()); + } + + @Test(dependsOnMethods = "testExecCreate") + public void testExecStart() throws IOException { + final ExecStartParams startParams = ExecStartParams.builder().detach(false).build(); + DockerInputStream inputStream = null; + try { + inputStream = new DockerInputStream(api().execStart(exec.id(), startParams)); + assertNotNull(inputStream); + ByteArrayOutputStream baosOut = new ByteArrayOutputStream(); + ByteArrayOutputStream baosErr = new ByteArrayOutputStream(); + StdStreamData data = null; + while (null != (data = inputStream.readStdStreamData())) { + assertFalse(data.isTruncated()); + switch (data.getType()) { + case OUT: + baosOut.write(data.getPayload()); + break; + case ERR: + baosErr.write(data.getPayload()); + break; + default: + Fail.fail("Unexpected Stream type"); + break; + } + } + assertEquals(baosOut.toString(), "Standard"); + assertEquals(baosErr.toString(), "Error"); + } finally { + if (inputStream != null) { + inputStream.close(); + } + } + } + + @Test(dependsOnMethods = "testExecStart") + public void testExecInspect() throws IOException { + ExecInspect execInspect = api().execInspect(exec.id()); + assertNotNull(execInspect); + assertEquals(execInspect.id(), exec.id()); + assertEquals(execInspect.running(), false); + assertEquals(execInspect.exitCode(), 2); + } + + private MiscApi api() { return api.getMiscApi(); } - } diff --git a/apis/docker/src/test/java/org/jclouds/docker/features/MiscApiMockTest.java b/apis/docker/src/test/java/org/jclouds/docker/features/MiscApiMockTest.java index 4fe87caac6..dd5d965ac5 100644 --- a/apis/docker/src/test/java/org/jclouds/docker/features/MiscApiMockTest.java +++ b/apis/docker/src/test/java/org/jclouds/docker/features/MiscApiMockTest.java @@ -16,25 +16,40 @@ */ package org.jclouds.docker.features; -import com.squareup.okhttp.mockwebserver.MockResponse; -import com.squareup.okhttp.mockwebserver.MockWebServer; -import com.squareup.okhttp.mockwebserver.RecordedRequest; +import static org.assertj.core.api.Assertions.assertThat; +import static org.jclouds.docker.compute.BaseDockerApiLiveTest.tarredDockerfile; +import static org.testng.Assert.assertEquals; +import static org.testng.Assert.assertFalse; +import static org.testng.Assert.assertNotNull; +import static org.testng.Assert.assertNull; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +import javax.ws.rs.core.HttpHeaders; import org.jclouds.docker.DockerApi; +import org.jclouds.docker.config.DockerParserModule; +import org.jclouds.docker.domain.Exec; +import org.jclouds.docker.domain.ExecCreateParams; +import org.jclouds.docker.domain.ExecInspect; +import org.jclouds.docker.domain.ExecStartParams; import org.jclouds.docker.internal.BaseDockerMockTest; import org.jclouds.docker.parse.InfoParseTest; import org.jclouds.docker.parse.VersionParseTest; +import org.jclouds.docker.util.DockerInputStream; +import org.jclouds.docker.util.StdStreamData; +import org.jclouds.docker.util.StdStreamData.StdStreamType; import org.jclouds.io.Payload; import org.jclouds.io.Payloads; import org.testng.annotations.Test; -import static org.assertj.core.api.Assertions.assertThat; -import static org.jclouds.docker.compute.BaseDockerApiLiveTest.tarredDockerfile; -import static org.testng.Assert.assertEquals; -import java.io.File; -import java.io.FileInputStream; - -import javax.ws.rs.core.HttpHeaders; +import com.google.common.collect.ImmutableList; +import com.squareup.okhttp.mockwebserver.MockResponse; +import com.squareup.okhttp.mockwebserver.MockWebServer; +import com.squareup.okhttp.mockwebserver.RecordedRequest; /** * Mock tests for the {@link org.jclouds.docker.features.MiscApi} class. @@ -92,6 +107,64 @@ public class MiscApiMockTest extends BaseDockerMockTest { } } + + public void testExecCreate() throws Exception { + MockWebServer server = mockWebServer(new MockResponse().setBody(payloadFromResource("/exec.json"))); + MiscApi api = api(DockerApi.class, server.getUrl("/").toString(), new DockerParserModule()).getMiscApi(); + try { + final String containerId = "a40d212a0a379de00426a1da2a8fd3fd20d5f74fd7c2dd42f6c93a6b1b0e6974"; + final ExecCreateParams execParams = ExecCreateParams.builder() + .cmd(ImmutableList. of("/bin/sh", "-c", "echo -n Standard >&1 && echo -n Error >&2")) + .attachStderr(true).attachStdout(true).build(); + final Exec expectedExec = Exec.create("dbf45d296388032ebb9872edb75847f6655a72b4e9ab0d99ae1c75589c4ca957"); + assertEquals(api.execCreate(containerId, execParams), expectedExec); + assertSent(server, "POST", "/containers/" + containerId + "/exec"); + } finally { + server.shutdown(); + } + } + + public void testExecStart() throws Exception { + MockWebServer server = mockWebServer(new MockResponse().setBody(payloadFromResource("/exec.start"))); + MiscApi api = api(DockerApi.class, server.getUrl("/").toString(), new DockerParserModule()).getMiscApi(); + DockerInputStream dis = null; + try { + final String execId = "dbf45d296388032ebb9872edb75847f6655a72b4e9ab0d99ae1c75589c4ca957"; + final ExecStartParams startParams = ExecStartParams.builder().detach(false).build(); + dis = new DockerInputStream(api.execStart(execId, startParams)); + + final StdStreamData msg1 = dis.readStdStreamData(); + assertFalse(msg1.isTruncated()); + assertEquals(msg1.getPayload(), "Standard".getBytes(StandardCharsets.UTF_8)); + assertEquals(msg1.getType(), StdStreamType.OUT); + + final StdStreamData msg2 = dis.readStdStreamData(); + assertFalse(msg2.isTruncated()); + assertEquals(msg2.getPayload(), "Error".getBytes(StandardCharsets.UTF_8)); + assertEquals(msg2.getType(), StdStreamType.ERR); + + assertNull(dis.readStdStreamData()); + assertSent(server, "POST", "/exec/" + execId + "/start"); + } finally { + if (dis != null) { + dis.close(); + } + server.shutdown(); + } + } + + public void testExecInspect() throws IOException, InterruptedException { + MockWebServer server = mockWebServer(new MockResponse().setBody(payloadFromResource("/execInspect.json"))); + MiscApi api = api(DockerApi.class, server.getUrl("/").toString(), new DockerParserModule()).getMiscApi(); + final String expectedExecId = "fda1cf8064863fc0667c691c69793fdb7d0bd4a1fabb8250536abe5203e4208a"; + ExecInspect execInspect = api.execInspect(expectedExecId); + assertNotNull(execInspect); + assertEquals(execInspect.id(), expectedExecId); + assertEquals(execInspect.running(), false); + assertEquals(execInspect.exitCode(), 2); + assertSent(server, "GET", "/exec/" + expectedExecId + "/json"); + } + /** * Asserts that correct values of HTTP headers are used in Docker build REST * API calls. diff --git a/apis/docker/src/test/resources/exec.json b/apis/docker/src/test/resources/exec.json new file mode 100644 index 0000000000..d5f626505c --- /dev/null +++ b/apis/docker/src/test/resources/exec.json @@ -0,0 +1 @@ +{"Id":"dbf45d296388032ebb9872edb75847f6655a72b4e9ab0d99ae1c75589c4ca957"} diff --git a/apis/docker/src/test/resources/exec.start b/apis/docker/src/test/resources/exec.start new file mode 100644 index 0000000000..0d726243cc Binary files /dev/null and b/apis/docker/src/test/resources/exec.start differ diff --git a/apis/docker/src/test/resources/execInspect.json b/apis/docker/src/test/resources/execInspect.json new file mode 100644 index 0000000000..a47cf49d09 --- /dev/null +++ b/apis/docker/src/test/resources/execInspect.json @@ -0,0 +1 @@ +{"ID":"fda1cf8064863fc0667c691c69793fdb7d0bd4a1fabb8250536abe5203e4208a","Running":false,"ExitCode":2,"ProcessConfig":{"privileged":false,"user":"","tty":false,"entrypoint":"/bin/sh","arguments":["-c","echo -n Standard \u003e\u00261 \u0026\u0026 echo -n Error \u003e\u00262 \u0026\u0026 exit 2"]},"OpenStdin":false,"OpenStderr":true,"OpenStdout":true,"Container":{"State":{"Running":true,"Paused":false,"Restarting":false,"OOMKilled":false,"Dead":false,"Pid":1561,"ExitCode":0,"Error":"","StartedAt":"2015-09-29T12:36:21.011469908Z","FinishedAt":"0001-01-01T00:00:00Z"},"ID":"07695c5170a1cbf3402552202fd28d2beb81fce5958a244e9ad7c8cb0ded936e","Created":"2015-09-29T12:36:19.829846377Z","Path":"/bin/sh","Args":["-c","touch hello; while true; do echo hello world; sleep 1; done"],"Config":{"Hostname":"07695c5170a1","Domainname":"","User":"","AttachStdin":false,"AttachStdout":false,"AttachStderr":false,"PortSpecs":null,"ExposedPorts":null,"Tty":false,"OpenStdin":false,"StdinOnce":false,"Env":null,"Cmd":["/bin/sh","-c","touch hello; while true; do echo hello world; sleep 1; done"],"Image":"ff8f955d1fed83a6239675b9a767fc9502db9934922a2f69ac6bf4cad8a7d7be","Volumes":null,"VolumeDriver":"","WorkingDir":"","Entrypoint":null,"NetworkDisabled":false,"MacAddress":"","OnBuild":null,"Labels":{},"Init":""},"Image":"ff8f955d1fed83a6239675b9a767fc9502db9934922a2f69ac6bf4cad8a7d7be","NetworkSettings":{"Bridge":"","EndpointID":"78d38a6f4d63966b16ca824a533b70f0a3a00490586688a188f67ec0bb07e68f","Gateway":"172.17.42.1","GlobalIPv6Address":"","GlobalIPv6PrefixLen":0,"HairpinMode":false,"IPAddress":"172.17.0.3","IPPrefixLen":16,"IPv6Gateway":"","LinkLocalIPv6Address":"","LinkLocalIPv6PrefixLen":0,"MacAddress":"02:42:ac:11:00:03","NetworkID":"880f733909fa4ad45c8fbc95b80c83e22a7b46f8cd7fb23dea62b0996ef1c8c6","PortMapping":null,"Ports":{},"SandboxKey":"/var/run/docker/netns/07695c5170a1","SecondaryIPAddresses":null,"SecondaryIPv6Addresses":null},"ResolvConfPath":"/var/lib/docker/containers/07695c5170a1cbf3402552202fd28d2beb81fce5958a244e9ad7c8cb0ded936e/resolv.conf","HostnamePath":"/var/lib/docker/containers/07695c5170a1cbf3402552202fd28d2beb81fce5958a244e9ad7c8cb0ded936e/hostname","HostsPath":"/var/lib/docker/containers/07695c5170a1cbf3402552202fd28d2beb81fce5958a244e9ad7c8cb0ded936e/hosts","LogPath":"/var/lib/docker/containers/07695c5170a1cbf3402552202fd28d2beb81fce5958a244e9ad7c8cb0ded936e/07695c5170a1cbf3402552202fd28d2beb81fce5958a244e9ad7c8cb0ded936e-json.log","Name":"/miscApiTest","Driver":"devicemapper","ExecDriver":"native-0.2","MountLabel":"","ProcessLabel":"","RestartCount":0,"UpdateDns":false,"MountPoints":{},"Volumes":{},"VolumesRW":{},"Init":"","AppArmorProfile":""}}