Wait container command needs possibility to abort operation by marcuslinke · Pull Request #357 · docker-java/docker-java

9 changes: 6 additions & 3 deletions src/main/java/com/github/dockerjava/api/command/WaitContainerCmd.java

Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
package com.github.dockerjava.api.command;

import com.github.dockerjava.api.NotFoundException;
import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.model.BuildResponseItem;
import com.github.dockerjava.api.model.WaitResponse;

/**
* Wait a container
*
* Block until container stops, then returns its exit code
*/
public interface WaitContainerCmd extends SyncDockerCmd<Integer> {
public interface WaitContainerCmd extends AsyncDockerCmd<WaitContainerCmd, WaitResponse> {

public String getContainerId();

Expand All @@ -18,9 +21,9 @@ public interface WaitContainerCmd extends SyncDockerCmd<Integer> {
* container not found
*/
@Override
public Integer exec() throws NotFoundException;
public <T extends ResultCallback<WaitResponse>> T exec(T resultCallback);

public static interface Exec extends DockerCmdSyncExec<WaitContainerCmd, Integer> {
public static interface Exec extends DockerCmdAsyncExec<WaitContainerCmd, WaitResponse> {
}

}

19 changes: 19 additions & 0 deletions src/main/java/com/github/dockerjava/api/model/WaitResponse.java

Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.github.dockerjava.api.model;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;


/**
* Represents a wait container command response
*/
@JsonIgnoreProperties(ignoreUnknown = false)
public class WaitResponse {

@JsonProperty("StatusCode")
private Integer statusCode;

public Integer getStatusCode() {
return statusCode;
}
}

22 changes: 21 additions & 1 deletion src/main/java/com/github/dockerjava/core/async/ResultCallbackTemplate.java

Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import javax.annotation.CheckForNull;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.dockerjava.api.async.ResultCallback;
import com.google.common.base.Throwables;

/**
* Abstract template implementation of {@link ResultCallback}
Expand All @@ -30,6 +33,8 @@ public abstract class ResultCallbackTemplate<RC_T extends ResultCallback<A_RES_T

private boolean closed = false;

private Throwable firstError = null;

@Override
public void onStart(Closeable stream) {
this.stream = stream;
Expand All @@ -38,12 +43,15 @@ public void onStart(Closeable stream) {

@Override
public void onError(Throwable throwable) {

if (closed)
return;

if (this.firstError == null)
this.firstError = throwable;

try {
LOGGER.error("Error during callback", throwable);
throw new RuntimeException(throwable);
} finally {
try {
close();
Expand Down Expand Up @@ -76,6 +84,8 @@ public void close() throws IOException {
@SuppressWarnings("unchecked")
public RC_T awaitCompletion() throws InterruptedException {
completed.await();
// eventually (re)throws RuntimeException
getFirstError();
return (RC_T) this;
}

Expand All @@ -87,4 +97,14 @@ public RC_T awaitCompletion(long timeout, TimeUnit timeUnit) throws InterruptedE
completed.await(timeout, timeUnit);
return (RC_T) this;
}

@CheckForNull
protected RuntimeException getFirstError() {
Copy link

Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I usually mark @CheckForNull and @Nonnull to know what logic about nullness i implemented in method.

if (firstError != null) {
// this call throws a RuntimeException
return Throwables.propagate(firstError);
} else {
return null;
}
}
}

5 changes: 3 additions & 2 deletions src/main/java/com/github/dockerjava/core/command/WaitContainerCmdImpl.java

Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,14 @@
import static com.google.common.base.Preconditions.checkNotNull;

import com.github.dockerjava.api.command.WaitContainerCmd;
import com.github.dockerjava.api.model.WaitResponse;

/**
* Wait a container
*
*
* Block until container stops, then returns its exit code
*/
public class WaitContainerCmdImpl extends AbstrDockerCmd<WaitContainerCmd, Integer> implements WaitContainerCmd {
public class WaitContainerCmdImpl extends AbstrAsyncDockerCmd<WaitContainerCmd, WaitResponse> implements WaitContainerCmd {

private String containerId;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/*
* Created on 21.07.2015
*/
package com.github.dockerjava.core.command;

import java.util.concurrent.TimeUnit;

import javax.annotation.CheckForNull;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.dockerjava.api.DockerClientException;
import com.github.dockerjava.api.model.WaitResponse;
import com.github.dockerjava.core.async.ResultCallbackTemplate;
import com.google.common.base.Throwables;

/**
*
* @author marcus
*
*/
public class WaitContainerResultCallback extends ResultCallbackTemplate<WaitContainerResultCallback, WaitResponse> {

private final static Logger LOGGER = LoggerFactory.getLogger(WaitContainerResultCallback.class);

@CheckForNull
private WaitResponse waitResponse = null;

@Override
public void onNext(WaitResponse waitResponse) {
this.waitResponse = waitResponse;
LOGGER.debug(waitResponse.toString());
}

/**
* Awaits the status code from the container.
*
* @throws DockerClientException
* if the wait operation fails.
*/
public Integer awaitStatusCode() {
try {
awaitCompletion();
} catch (InterruptedException e) {
throw new DockerClientException("", e);
}

return getStatusCode();
}

/**
* Awaits the status code from the container.
*
* @throws DockerClientException
* if the wait operation fails.
*/
public Integer awaitStatusCode(long timeout, TimeUnit timeUnit) {
try {
awaitCompletion(timeout, timeUnit);
} catch (InterruptedException e) {
throw new DockerClientException("Awaiting status code interrupted: ", e);
}

return getStatusCode();
}

private Integer getStatusCode() {
if (waitResponse == null) {
throw new DockerClientException("Error while wait container");
} else {
return waitResponse.getStatusCode();
}
}
}

27 changes: 18 additions & 9 deletions src/main/java/com/github/dockerjava/jaxrs/WaitContainerCmdExec.java

Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
package com.github.dockerjava.jaxrs;

import com.fasterxml.jackson.databind.node.ObjectNode;
import com.github.dockerjava.api.command.WaitContainerCmd;
import com.github.dockerjava.core.DockerClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static javax.ws.rs.client.Entity.entity;

import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;

public class WaitContainerCmdExec extends AbstrSyncDockerCmdExec<WaitContainerCmd, Integer> implements
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.github.dockerjava.api.async.ResultCallback;
import com.github.dockerjava.api.command.WaitContainerCmd;
import com.github.dockerjava.api.model.WaitResponse;
import com.github.dockerjava.core.DockerClientConfig;
import com.github.dockerjava.core.async.JsonStreamProcessor;
import com.github.dockerjava.jaxrs.async.AbstractCallbackNotifier;
import com.github.dockerjava.jaxrs.async.POSTCallbackNotifier;

public class WaitContainerCmdExec extends AbstrAsyncDockerCmdExec<WaitContainerCmd, WaitResponse> implements
WaitContainerCmd.Exec {

private static final Logger LOGGER = LoggerFactory.getLogger(WaitContainerCmdExec.class);
Expand All @@ -19,14 +26,16 @@ public WaitContainerCmdExec(WebTarget baseResource, DockerClientConfig dockerCli
}

@Override
protected Integer execute(WaitContainerCmd command) {
protected AbstractCallbackNotifier<WaitResponse> callbackNotifier(WaitContainerCmd command,
ResultCallback<WaitResponse> resultCallback) {

WebTarget webResource = getBaseResource().path("/containers/{id}/wait").resolveTemplate("id",
command.getContainerId());

LOGGER.trace("POST: {}", webResource);
ObjectNode ObjectNode = webResource.request().accept(MediaType.APPLICATION_JSON).post(null, ObjectNode.class);

return ObjectNode.get("StatusCode").asInt();
return new POSTCallbackNotifier<WaitResponse>(new JsonStreamProcessor<WaitResponse>(
WaitResponse.class), resultCallback, webResource.request().accept(MediaType.APPLICATION_JSON), entity(null, MediaType.APPLICATION_JSON));
}

}

3 changes: 2 additions & 1 deletion src/test/java/com/github/dockerjava/client/DockerClientTest.java

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import com.github.dockerjava.api.DockerException;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.core.command.WaitContainerResultCallback;

/**
* Unit test for DockerClient.
Expand Down Expand Up @@ -61,7 +62,7 @@ public void testRunShlex() throws DockerException {
CreateContainerResponse container = dockerClient.createContainerCmd("busybox").withCmd(commands).exec();
dockerClient.startContainerCmd(container.getId());

int exitcode = dockerClient.waitContainerCmd(container.getId()).exec();
int exitcode = dockerClient.waitContainerCmd(container.getId()).exec(new WaitContainerResultCallback()).awaitStatusCode();
assertThat(exitcode, equalTo(0));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ private String execBuild(BuildImageCmd buildImageCmd) throws Exception {
assertThat(container.getId(), not(isEmptyString()));

dockerClient.startContainerCmd(container.getId()).exec();
dockerClient.waitContainerCmd(container.getId()).exec();
dockerClient.waitContainerCmd(container.getId()).exec(new WaitContainerResultCallback()).awaitStatusCode();

return containerLog(container.getId());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public void testContainerDiff() throws DockerException {
assertThat(container.getId(), not(isEmptyString()));
dockerClient.startContainerCmd(container.getId()).exec();

int exitCode = dockerClient.waitContainerCmd(container.getId()).exec();
int exitCode = dockerClient.waitContainerCmd(container.getId()).exec(new WaitContainerResultCallback()).awaitStatusCode();
assertThat(exitCode, equalTo(0));

List<ChangeLog> filesystemDiff = dockerClient.containerDiffCmd(container.getId()).exec();
Expand Down

3 changes: 2 additions & 1 deletion src/test/java/com/github/dockerjava/core/command/FrameReaderITest.java

Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ public void deleteDockerContainerImage() throws Exception {
public void canCloseFrameReaderAndReadExpectedLines() throws Exception {

// wait for the container to be successfully executed
int exitCode = dockerClient.waitContainerCmd(dockerfileFixture.getContainerId()).exec();
int exitCode = dockerClient.waitContainerCmd(dockerfileFixture.getContainerId())
.exec(new WaitContainerResultCallback()).awaitStatusCode();
assertEquals(0, exitCode);

Iterator<Frame> response = getLoggingFrames().iterator();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@

import com.github.dockerjava.api.NotFoundException;
import com.github.dockerjava.api.command.CreateContainerResponse;
import com.github.dockerjava.api.model.Frame;
import com.github.dockerjava.client.AbstractDockerClientTest;

@Test(groups = "integration")
Expand Down Expand Up @@ -56,7 +55,8 @@ public void asyncLogContainer() throws Exception {

dockerClient.startContainerCmd(container.getId()).exec();

int exitCode = dockerClient.waitContainerCmd(container.getId()).exec();
int exitCode = dockerClient.waitContainerCmd(container.getId()).exec(new WaitContainerResultCallback())
.awaitStatusCode();

assertThat(exitCode, equalTo(0));

Expand All @@ -80,7 +80,7 @@ public void onError(Throwable throwable) {
assertEquals(throwable.getClass().getName(), NotFoundException.class.getName());

try {
// close the callback to prevent the call to onFinish
// close the callback to prevent the call to onComplete
close();
} catch (IOException e) {
throw new RuntimeException();
Expand Down Expand Up @@ -111,7 +111,8 @@ public void asyncMultipleLogContainer() throws Exception {

dockerClient.startContainerCmd(container.getId()).exec();

int exitCode = dockerClient.waitContainerCmd(container.getId()).exec();
int exitCode = dockerClient.waitContainerCmd(container.getId()).exec(new WaitContainerResultCallback())
.awaitStatusCode();

assertThat(exitCode, equalTo(0));

Expand Down Expand Up @@ -150,7 +151,8 @@ public void asyncLogContainerWithSince() throws Exception {

dockerClient.startContainerCmd(container.getId()).exec();

int exitCode = dockerClient.waitContainerCmd(container.getId()).exec();
int exitCode = dockerClient.waitContainerCmd(container.getId()).exec(new WaitContainerResultCallback())
.awaitStatusCode();

assertThat(exitCode, equalTo(0));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ public void removeContainer() throws DockerException {
CreateContainerResponse container = dockerClient.createContainerCmd("busybox").withCmd("true").exec();

dockerClient.startContainerCmd(container.getId()).exec();
dockerClient.waitContainerCmd(container.getId()).exec();
dockerClient.waitContainerCmd(container.getId()).exec(new WaitContainerResultCallback()).awaitStatusCode();

LOG.info("Removing container: {}", container.getId());
dockerClient.removeContainerCmd(container.getId()).exec();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ public void startContainerWithVolumes() throws DockerException {

dockerClient.startContainerCmd(container.getId()).exec();

dockerClient.waitContainerCmd(container.getId()).exec();
dockerClient.waitContainerCmd(container.getId()).exec(new WaitContainerResultCallback()).awaitStatusCode();

inspectContainerResponse = dockerClient.inspectContainerCmd(container.getId()).exec();

Expand Down