Skip to content
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

Add DockerHealthcheckWaitStrategy #618

Merged
merged 9 commits into from
Apr 3, 2018
Merged

Conversation

kiview
Copy link
Member

@kiview kiview commented Mar 22, 2018

Adds a DockerHealthcheckWaitStrategy that relies on the built-in Docker healthcheck.

TBH I was not sure what's our current template for creating WaitStrategy tests, so I tried to keep it as simple as possible. The write_file_and_loop.sh could surely be included into the Dockerfile CMD, but this way it seemed to be a bit easier for debugging purposes.

This PR also implicitly adds a isHealthy() method to the ContainerState interface.

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.

@kiview kiview requested review from rnorth and bsideup March 22, 2018 16:26
@kiview
Copy link
Member Author

kiview commented Mar 22, 2018

OMG, some cached Gradle file locks in Travis...

CHANGELOG.md Outdated
@@ -14,6 +14,7 @@ All notable changes to this project will be documented in this file.
- 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).
Copy link
Member

Choose a reason for hiding this comment

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

PR link? :)

Copy link
Member Author

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 😅

Copy link
Member

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);
Copy link
Member

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()?

Copy link
Member Author

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 🛩).

Copy link
Member

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 :)

Copy link
Member Author

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?

Copy link
Member

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

Copy link
Member Author

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() {
Copy link
Member

Choose a reason for hiding this comment

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

unwrap to boolean

Copy link
Member Author

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());
Copy link
Member

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
Copy link
Member

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?

Copy link
Member Author

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?

Copy link
Member

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
Copy link
Member

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");
Copy link
Member

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

Copy link
Member Author

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.

Copy link
Member

@rnorth rnorth left a 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)));
Copy link
Member

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.

Copy link
Member Author

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
Copy link
Member

Choose a reason for hiding this comment

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

Boolean.TRUE.equals(running) ;)

Copy link
Member Author

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

Copy link
Member

@bsideup bsideup left a 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

@kiview
Copy link
Member Author

kiview commented Mar 26, 2018

@rnorth You are right about the documentation, I'll try to come up with something.

@kiview kiview merged commit ac1d4af into master Apr 3, 2018
@kiview kiview deleted the docker-health-wait-strategy branch April 3, 2018 11:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants