-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Add DockerHealthcheckWaitStrategy #618
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
OMG, some cached Gradle file locks in Travis... |
CHANGELOG.md
Outdated
| - Deprecated `WaitStrategy` and implementations in favour of classes with same names in `org.testcontainers.containers.strategy` ([\#600](https://github.com/testcontainers/testcontainers-java/pull/600)) | ||
| - Added `ContainerState` interface representing the state of a started container ([\#600](https://github.com/testcontainers/testcontainers-java/pull/600)) | ||
| - Added `WaitStrategyTarget` interface which is the target of the new `WaitStrategy` ([\#600](https://github.com/testcontainers/testcontainers-java/pull/600)) | ||
| - Added `DockerHealthcheckWaitStrategy` that is based on Docker's built-in [healthcheck](https://docs.docker.com/engine/reference/builder/#healthcheck). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
PR link? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course, there was no PR when I added this line 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have the same problem every time I submit a PR :D
| */ | ||
| default Boolean isHealthy() { | ||
| try { | ||
| return getContainerId() != null && DockerClientFactory.instance().client().inspectContainerCmd(getContainerId()).exec().getState().getHealth().getStatus().equals(STATE_HEALTHY); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
could you please extract the result of DockerClientFactory.instance().client().inspectContainerCmd(getContainerId()).exec()?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gladly, I simply copied this from the isRunning() method and thought this code was leveraging lazy evaluation of && (cargo cult 🛩).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah, but that was an old code and we should improve bit by bit :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to do a little refactoring here and wanted to simply use getContainerInfo(), but this seems to be implemented by the getter of the GenericContainer, which is only initially set during start in the tryStart() method. So my question, is it intended for this public API to basically return a cached version of InspectContainerResponse or am I missing something?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, of course. Most of getters (i.e. exposed ports) use InspectContainerResponse and if we would query Docker every time we call them that will add a significant delay
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the clarification. I've thus added the getCurrentContainerInfo() default method to the interface. I would have been fine with a private method for this, since I was mostly for avoiding code duplication, but I think it's actually useful to also have this as a public API?
| /** | ||
| * @return has the container health state 'healthy'? | ||
| */ | ||
| default Boolean isHealthy() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
unwrap to boolean
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above, isRunning() did use Boolean, so I wanted to be consistent.
| protected void waitUntilReady() { | ||
|
|
||
| try { | ||
| Unreliables.retryUntilTrue((int) startupTimeout.getSeconds(), TimeUnit.SECONDS, () -> waitStrategyTarget.isHealthy()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
waitStrategyTarget::isHealthy ?
|
|
||
| @Before | ||
| public void setUp() { | ||
| // Using a Dockerfile here, since Dockerfile builder DSL doesn't support HEALTHCHECK |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should add HEALTHCHECK support to the DSL?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitly, but another PR I assume?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure, up to you
| @@ -0,0 +1,8 @@ | |||
| FROM alpine:latest | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please pin the version
|
|
||
| @Test | ||
| public void containerStartFailsIfContainerIsUnhealthy() { | ||
| container.withCommand("ash"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this test fails because container exits immediately and not because of the healthcheck.
We should use some long-running command here and wait until healthcheck fails with "unhealthy" status
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I looked into it, but I can check again. But yes, good idea.
rnorth
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a good change to me.
Aside from a tiny comment on the test, there's one more thing I can think of: documentation. Happy to defer that until we do a general (much needed) tidy of of docs in this area, though.
| container = new GenericContainer(new ImageFromDockerfile() | ||
| .withFileFromClasspath("write_file_and_loop.sh", "health-wait-strategy-dockerfile/write_file_and_loop.sh") | ||
| .withFileFromClasspath("Dockerfile", "health-wait-strategy-dockerfile/Dockerfile")) | ||
| .waitingFor(new DockerHealthcheckWaitStrategy().withStartupTimeout(Duration.ofSeconds(3))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason not to use Wait.forHealthcheck() here? It seems like the preferable style, so might be good to use it anywhere people might look for examples.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right, I'll change it.
|
|
||
| try { | ||
| Boolean running = getCurrentContainerInfo().getState().getRunning(); | ||
| return running != null && running; // avoid NPE when unboxing |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Boolean.TRUE.equals(running) ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thx, I think it's obvious that I'm not used to working with boxed primitives :D
bsideup
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@kiview approved with a comment about Boolean.TRUE
|
@rnorth You are right about the documentation, I'll try to come up with something. |
Adds a DockerHealthcheckWaitStrategy that relies on the built-in Docker healthcheck.
TBH I was not sure what's our current template for creating
WaitStrategytests, so I tried to keep it as simple as possible. Thewrite_file_and_loop.shcould surely be included into the DockerfileCMD, but this way it seemed to be a bit easier for debugging purposes.This PR also implicitly adds a
isHealthy()method to theContainerStateinterface.Regarding backwards compatibility, I'm not sure how docker-java behaves if the health state is missing. docker-java itself documents to support it since Docker version 1.12.