palantir/docker-compose-rule

Can't connect to DB after isListeningNow succeed

kasecato opened this issue · 6 comments

I cannot connect to MySQL even after isListeningNow succeeded. How can I wait until JDBC can connect?

    private DockerComposeRule dockerDb;
    private Connection conn;

    @Before
    public void before() throws IOException, InterruptedException, SQLException {
        dockerDb = DockerComposeRule.builder()
                .file("src/test/resources/docker-compose-test.yml")
                .waitingForService("db", HealthChecks.toHaveAllPortsOpen())
                .addClusterWait(new ClusterWait(
                        ClusterHealthCheck.transformingHealthCheck(
                                cluster -> cluster.container("db").port(3306),
                                target -> SuccessOrFailure.fromBoolean(target.isListeningNow(), target + " was not opened")),
                        Duration.standardSeconds(30)))
                .build();

        dockerDb.before();

        final MysqlDataSource dataSource = new MysqlDataSource() {{
            final String url = dockerDb.containers().container("db").port(3306).inFormat("jdbc:mysql://$HOST:$EXTERNAL_PORT/sampledb");
            setURL(url);
            setUser("root");
            setPassword("sa");
        }};

        conn = dataSource.getConnection();
        // I don't want to use like this codes
/*
        while (true) {
            try {
                conn = dataSource.getConnection();
                break;
            } catch (Exception e) {
                Thread.sleep(1000);
            }
        }
*/
    }

    @After
    public void tearDown() {
        dockerDb.after();
    }

    @Test
    public void test1() throws SQLException {
        conn.prepareStatement("SELECT 1").executeQuery();
}

I already wrote a JdbcContainerHealthCheck for my project, was originally going to submit a PR back here, but was unsure if it would be appropriate for tracking individual Health check types?

Would it be fair to have it under HealthChecks ? Or is it too specific of a use case?

Either way I would be happy to contribute it back.

@dotCipher I'd love to use JdbcContainerHealthCheck. Is it possible to paste code here?

@iamdanfox / @hpryce for input here, but I think the most scalable solution would be to commit back to the product instead of pasting code around.

I'd be more than happy to open a PR, but what kind of structure should we apply to arbitrary one-off HealthCheck<T> types? Gradle sub-module like health-checks-<type> or just in some package that comes with docker-compose-rule-core ?

@dotCipher I 100% agree another gradle project called something like health-checks would be amazing... I literally just wrote a crappy one again for the hundredth time! I personally don't think I have bandwidth to get to this, but I think we should definitely get a PR from someone merged in with one of these.

    @ClassRule public static final DockerComposeRule docker = DockerComposeRule.builder()
            .file("../docker-compose.yml")
            .saveLogsTo(LogDirectory.circleAwareLogDirectory(MyIntegTest.class))
            .waitingForService("postgres", new WaitForPostgres())
            .build()
import com.palantir.docker.compose.connection.Container;
import com.palantir.docker.compose.connection.waiting.HealthCheck;
import com.palantir.docker.compose.connection.waiting.SuccessOrFailure;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;

public class WaitForPostgres implements HealthCheck<Container> {

    @Override
    public SuccessOrFailure isHealthy(Container target) {
        String uri = target.port(5432)
                .inFormat("jdbc:postgresql://localhost:$EXTERNAL_PORT/database");

        return SuccessOrFailure.onResultOf(() -> select1(uri));
    }

    private boolean select1(String uri) throws SQLException {
        try (Connection connection = DriverManager.getConnection(uri, "username", "password");
                ResultSet result = connection.createStatement().executeQuery("SELECT 1")) {

            result.next();
            return result.getInt(1) == 1;
        }
    }

}

Put up #208 to hopefully consolidate most of the logic needed.

Instead of using the healthchecks defined in code, I would suggest implementing your healthchecks in docker-compose since it is now supported since version 2.1. For your example, you could use the following docker-compose directive in your file for a postgrs container (use the mysql client instead for your purpose):

healthcheck:
  test: ["CMD", "psql", "-h", "localhost", "-c", "SELECT version();"]
  interval: 1s
  timeout: 5s
  retries: 12

This will only pass when a JDBC connection can be made.