You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bookkeeper.apache.org by si...@apache.org on 2018/01/18 00:44:43 UTC

[bookkeeper] branch master updated: Utilities for working with Arquillian and Docker

This is an automated email from the ASF dual-hosted git repository.

sijie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/bookkeeper.git


The following commit(s) were added to refs/heads/master by this push:
     new 1f66e47  Utilities for working with Arquillian and Docker
1f66e47 is described below

commit 1f66e478dd78fb2f9ba66b7c92da2ec17c5be089
Author: Ivan Kelly <iv...@apache.org>
AuthorDate: Wed Jan 17 16:44:37 2018 -0800

    Utilities for working with Arquillian and Docker
    
    This patch contains the following utilities.
    - An arquillian StopAction which copy logs from /var/log/bookkeeper to
      the target/ directory.
    - An arquillian StopAction which dumps the docker log to the target/
      directory.
    - An arquillian AwaitStrategy which checks whether a zookeeper cluster
      is running.
    - An arquillian AwaitStrategy which returns immediately (surprising
      arquillian didn't already have this).
    - Utilities for working with a cluster of zookeeper/bookkeeper running
      on docker.
    
    Master Issue: #903
    
    Author: Ivan Kelly <iv...@apache.org>
    
    Reviewers: Enrico Olivelli <eo...@gmail.com>, Jia Zhai <None>
    
    This closes #974 from ivankelly/arquillian-util
---
 tests/integration-tests-utils/pom.xml              |  84 ++++++++++
 .../bookkeeper/tests/BookKeeperClusterUtils.java   | 153 +++++++++++++++++++
 .../tests/BookKeeperLogsToTargetDirStopAction.java |  43 ++++++
 .../org/apache/bookkeeper/tests/DockerUtils.java   | 170 +++++++++++++++++++++
 .../bookkeeper/tests/LogToTargetDirStopAction.java |  41 +++++
 .../apache/bookkeeper/tests/NoopAwaitStrategy.java |  30 ++++
 .../bookkeeper/tests/ZooKeeperAwaitStrategy.java   |  57 +++++++
 .../src/main/resources/log4j.properties            |  37 +++++
 tests/pom.xml                                      |   1 +
 9 files changed, 616 insertions(+)

diff --git a/tests/integration-tests-utils/pom.xml b/tests/integration-tests-utils/pom.xml
new file mode 100644
index 0000000..3593ec5
--- /dev/null
+++ b/tests/integration-tests-utils/pom.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+   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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="
+  http://maven.apache.org/POM/4.0.0
+  http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+  <parent>
+    <groupId>org.apache.bookkeeper.tests</groupId>
+    <artifactId>tests-parent</artifactId>
+    <version>4.7.0-SNAPSHOT</version>
+  </parent>
+
+  <groupId>org.apache.bookkeeper.tests</groupId>
+  <artifactId>integration-tests-utils</artifactId>
+  <packaging>jar</packaging>
+
+  <name>Apache BookKeeper :: Tests :: Utility module for Arquillian based integration tests</name>
+
+  <properties>
+    <arquillian-cube.version>1.13.0</arquillian-cube.version>
+    <commons-compress.version>1.15</commons-compress.version>
+  </properties>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.commons</groupId>
+      <artifactId>commons-compress</artifactId>
+      <version>${commons-compress.version}</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.zookeeper</groupId>
+      <artifactId>zookeeper</artifactId>
+      <version>${zookeeper.version}</version>
+      <scope>compile</scope>
+      <exclusions>
+        <exclusion>
+          <groupId>net.java.dev.javacc</groupId>
+          <artifactId>javacc</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.slf4j</groupId>
+          <artifactId>slf4j-log4j12</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>org.slf4j</groupId>
+          <artifactId>slf4j-api</artifactId>
+        </exclusion>
+        <exclusion>
+          <groupId>log4j</groupId>
+          <artifactId>log4j</artifactId>
+	</exclusion>
+        <exclusion>
+          <groupId>io.netty</groupId>
+          <artifactId>netty</artifactId>
+        </exclusion>
+      </exclusions>
+    </dependency>
+
+    <dependency>
+      <groupId>org.arquillian.cube</groupId>
+      <artifactId>arquillian-cube-docker</artifactId>
+      <version>${arquillian-cube.version}</version>
+    </dependency>
+
+  </dependencies>
+</project>
diff --git a/tests/integration-tests-utils/src/main/java/org/apache/bookkeeper/tests/BookKeeperClusterUtils.java b/tests/integration-tests-utils/src/main/java/org/apache/bookkeeper/tests/BookKeeperClusterUtils.java
new file mode 100644
index 0000000..d70d543
--- /dev/null
+++ b/tests/integration-tests-utils/src/main/java/org/apache/bookkeeper/tests/BookKeeperClusterUtils.java
@@ -0,0 +1,153 @@
+/**
+ * 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.apache.bookkeeper.tests;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import com.github.dockerjava.api.DockerClient;
+
+import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import java.net.Socket;
+
+import org.apache.zookeeper.ZooKeeper;
+import org.apache.zookeeper.CreateMode;
+import org.apache.zookeeper.ZooDefs.Ids;
+import org.apache.zookeeper.Watcher.Event.KeeperState;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class BookKeeperClusterUtils {
+    private static final Logger LOG = LoggerFactory.getLogger(BookKeeperClusterUtils.class);
+
+    public static String zookeeperConnectString(DockerClient docker) {
+        return DockerUtils.cubeIdsMatching("zookeeper").stream()
+            .map((id) -> DockerUtils.getContainerIP(docker, id)).collect(Collectors.joining(":"));
+    }
+
+    public static ZooKeeper zookeeperClient(DockerClient docker) throws Exception {
+        String connectString = BookKeeperClusterUtils.zookeeperConnectString(docker);
+        CompletableFuture<Void> future = new CompletableFuture<>();
+        ZooKeeper zk = new ZooKeeper(connectString, 10000,
+                                     (e) -> {
+                                         if (e.getState().equals(KeeperState.SyncConnected)) {
+                                             future.complete(null);
+                                         }
+                                     });
+        future.get();
+        return zk;
+    }
+
+    public static boolean zookeeperRunning(DockerClient docker, String containerId) {
+        String ip = DockerUtils.getContainerIP(docker, containerId);
+        try (Socket socket = new Socket(ip, 2181)) {
+            socket.setSoTimeout(1000);
+            socket.getOutputStream().write("ruok".getBytes(UTF_8));
+            byte[] resp = new byte[4];
+            if (socket.getInputStream().read(resp) == 4) {
+                return new String(resp, UTF_8).equals("imok");
+            }
+        } catch (IOException e) {
+            // ignore, we'll return fallthrough to return false
+        }
+        return false;
+    }
+
+    public static void legacyMetadataFormat(DockerClient docker) throws Exception {
+        try (ZooKeeper zk = BookKeeperClusterUtils.zookeeperClient(docker)) {
+            zk.create("/ledgers", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
+            zk.create("/ledgers/available", new byte[0], Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
+        }
+    }
+
+    private static boolean waitBookieState(DockerClient docker, String containerId,
+                                           int timeout, TimeUnit timeoutUnit,
+                                           boolean upOrDown) {
+        long timeoutMillis = timeoutUnit.toMillis(timeout);
+        long pollMillis = 1000;
+        String bookieId = DockerUtils.getContainerIP(docker, containerId) + ":3181";
+        try (ZooKeeper zk = BookKeeperClusterUtils.zookeeperClient(docker)) {
+            String path = "/ledgers/available/" + bookieId;
+            while (timeoutMillis > 0) {
+                if ((zk.exists(path, false) != null) == upOrDown) {
+                    return true;
+                }
+                Thread.sleep(pollMillis);
+                timeoutMillis -= pollMillis;
+            }
+        } catch (Exception e) {
+            LOG.error("Exception checking for bookie state", e);
+            return false;
+        }
+        LOG.warn("Bookie {} didn't go {} after {} seconds",
+                 containerId, upOrDown ? "up" : "down",
+                 timeoutUnit.toSeconds(timeout));
+        return false;
+    }
+
+    public static boolean waitBookieUp(DockerClient docker, String containerId,
+                                       int timeout, TimeUnit timeoutUnit) {
+        return waitBookieState(docker, containerId, timeout, timeoutUnit, true);
+    }
+
+    public static boolean waitBookieDown(DockerClient docker, String containerId,
+                                         int timeout, TimeUnit timeoutUnit) {
+        return waitBookieState(docker, containerId, timeout, timeoutUnit, false);
+    }
+
+    public static boolean startBookieWithVersion(DockerClient docker, String containerId, String version) {
+        try {
+            DockerUtils.runCommand(docker, containerId, "supervisorctl", "start", "bookkeeper-" + version);
+        } catch (Exception e) {
+            LOG.error("Exception starting bookie", e);
+            return false;
+        }
+        return waitBookieUp(docker, containerId, 10, TimeUnit.SECONDS);
+    }
+
+    private static boolean allTrue(boolean accumulator, boolean result) {
+        return accumulator && result;
+    }
+
+    public static boolean startAllBookiesWithVersion(DockerClient docker, String version)
+            throws Exception {
+        return DockerUtils.cubeIdsMatching("bookkeeper").stream()
+            .map((b) -> startBookieWithVersion(docker, b, version))
+            .reduce(true, BookKeeperClusterUtils::allTrue);
+    }
+
+    public static boolean stopBookie(DockerClient docker, String containerId) {
+        try {
+            DockerUtils.runCommand(docker, containerId, "supervisorctl", "stop", "all");
+        } catch (Exception e) {
+            LOG.error("Exception stopping bookie", e);
+            return false;
+        }
+        return waitBookieDown(docker, containerId, 10, TimeUnit.SECONDS);
+    }
+
+    public static boolean stopAllBookies(DockerClient docker) {
+        return DockerUtils.cubeIdsMatching("bookkeeper").stream()
+            .map((b) -> stopBookie(docker, b))
+            .reduce(true, BookKeeperClusterUtils::allTrue);
+    }
+}
diff --git a/tests/integration-tests-utils/src/main/java/org/apache/bookkeeper/tests/BookKeeperLogsToTargetDirStopAction.java b/tests/integration-tests-utils/src/main/java/org/apache/bookkeeper/tests/BookKeeperLogsToTargetDirStopAction.java
new file mode 100644
index 0000000..cab00f8
--- /dev/null
+++ b/tests/integration-tests-utils/src/main/java/org/apache/bookkeeper/tests/BookKeeperLogsToTargetDirStopAction.java
@@ -0,0 +1,43 @@
+/**
+ * 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.apache.bookkeeper.tests;
+
+import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
+import org.arquillian.cube.impl.model.CubeId;
+import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
+
+public class BookKeeperLogsToTargetDirStopAction implements BeforeStopAction {
+    private DockerClientExecutor dockerClientExecutor;
+    private CubeId containerID;
+
+    public void setDockerClientExecutor(DockerClientExecutor executor) {
+        this.dockerClientExecutor = executor;
+    }
+
+    public void setContainerID(CubeId containerID) {
+        this.containerID = containerID;
+    }
+
+    @Override
+    public void doBeforeStop() {
+        DockerUtils.dumpContainerLogToTarget(dockerClientExecutor.getDockerClient(), containerID.getId());
+        DockerUtils.dumpContainerLogDirToTarget(dockerClientExecutor.getDockerClient(),
+                                                containerID.getId(), "/var/log/bookkeeper");
+    }
+}
diff --git a/tests/integration-tests-utils/src/main/java/org/apache/bookkeeper/tests/DockerUtils.java b/tests/integration-tests-utils/src/main/java/org/apache/bookkeeper/tests/DockerUtils.java
new file mode 100644
index 0000000..7d183a1
--- /dev/null
+++ b/tests/integration-tests-utils/src/main/java/org/apache/bookkeeper/tests/DockerUtils.java
@@ -0,0 +1,170 @@
+/**
+ * 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.apache.bookkeeper.tests;
+
+import com.github.dockerjava.api.DockerClient;
+import com.github.dockerjava.api.async.ResultCallback;
+import com.github.dockerjava.api.model.Frame;
+import com.github.dockerjava.api.model.ContainerNetwork;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
+import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DockerUtils {
+    private static final Logger LOG = LoggerFactory.getLogger(DockerUtils.class);
+
+    private static File getTargetDirectory(String containerId) {
+        String base = System.getProperty("maven.buildDirectory");
+        if (base == null) {
+            base = "target";
+        }
+        File directory = new File(base + "/container-logs/" + containerId);
+        if (!directory.exists() && !directory.mkdirs()) {
+            LOG.error("Error creating directory for container logs.");
+        }
+        return directory;
+    }
+
+    public static void dumpContainerLogToTarget(DockerClient docker, String containerId) {
+        File output = new File(getTargetDirectory(containerId), "docker.log");
+        try (FileOutputStream os = new FileOutputStream(output)) {
+            CompletableFuture<Boolean> future = new CompletableFuture<>();
+            docker.logContainerCmd(containerId).withStdOut(true)
+                .withStdErr(true).withTimestamps(true).exec(new ResultCallback<Frame>() {
+                        @Override
+                        public void close() {}
+
+                        @Override
+                        public void onStart(Closeable closeable) {}
+
+                        @Override
+                        public void onNext(Frame object) {
+                            try {
+                                os.write(object.getPayload());
+                            } catch (IOException e) {
+                                onError(e);
+                            }
+                        }
+
+                        @Override
+                        public void onError(Throwable throwable) {
+                            future.completeExceptionally(throwable);
+                        }
+
+                        @Override
+                        public void onComplete() {
+                            future.complete(true);
+                        }
+                    });
+            future.get();
+        } catch (ExecutionException|IOException e) {
+            LOG.error("Error dumping log for {}", containerId, e);
+        } catch (InterruptedException ie) {
+            Thread.currentThread().interrupt();
+            LOG.info("Interrupted dumping log from container {}", containerId, ie);
+        }
+    }
+
+    public static void dumpContainerLogDirToTarget(DockerClient docker, String containerId, String path) {
+        final int READ_BLOCK_SIZE = 10000;
+
+        try (InputStream dockerStream = docker.copyArchiveFromContainerCmd(containerId, path).exec();
+             TarArchiveInputStream stream = new TarArchiveInputStream(dockerStream)) {
+            TarArchiveEntry entry = stream.getNextTarEntry();
+            while (entry != null) {
+                if (entry.isFile()) {
+                    File output = new File(getTargetDirectory(containerId), entry.getName().replace("/", "-"));
+                    try (FileOutputStream os = new FileOutputStream(output)) {
+                        byte[] block = new byte[READ_BLOCK_SIZE];
+                        int read = stream.read(block, 0, READ_BLOCK_SIZE);
+                        while (read > -1) {
+                            os.write(block, 0, read);
+                            read = stream.read(block, 0, READ_BLOCK_SIZE);
+                        }
+                    }
+                }
+                entry = stream.getNextTarEntry();
+            }
+        } catch (IOException e) {
+            LOG.error("Error reading bk logs from container {}", containerId, e);
+        }
+    }
+
+    public static String getContainerIP(DockerClient docker, String containerId) {
+        for (Map.Entry<String, ContainerNetwork> e : docker.inspectContainerCmd(containerId)
+                 .exec().getNetworkSettings().getNetworks().entrySet()) {
+            return e.getValue().getIpAddress();
+        }
+        throw new IllegalArgumentException("Container " + containerId + " has no networks");
+    }
+
+    public static void runCommand(DockerClient docker, String containerId, String... cmd) throws Exception {
+        CompletableFuture<Boolean> future = new CompletableFuture<>();
+        String execid = docker.execCreateCmd(containerId).withCmd(cmd).exec().getId();
+        docker.execStartCmd(execid).withDetach(false).exec(new ResultCallback<Frame>() {
+                @Override
+                public void close() {}
+
+                @Override
+                public void onStart(Closeable closeable) {
+                }
+
+                @Override
+                public void onNext(Frame object) {
+                    LOG.info("DOCKER.exec({}): {}", cmd, object);
+                }
+
+                @Override
+                public void onError(Throwable throwable) {
+                    future.completeExceptionally(throwable);
+                }
+
+                @Override
+                public void onComplete() {
+                    future.complete(true);
+                }
+            });
+        future.get();
+    }
+
+    public static Set<String> cubeIdsMatching(String needle) {
+        Pattern pattern = Pattern.compile("^arq.cube.docker.([^.]*).ip$");
+        return System.getProperties().keySet().stream()
+            .map(k -> pattern.matcher(k.toString()))
+            .filter(m -> m.matches())
+            .map(m -> m.group(1))
+            .filter(m -> m.contains(needle))
+            .collect(Collectors.toSet());
+    }
+}
diff --git a/tests/integration-tests-utils/src/main/java/org/apache/bookkeeper/tests/LogToTargetDirStopAction.java b/tests/integration-tests-utils/src/main/java/org/apache/bookkeeper/tests/LogToTargetDirStopAction.java
new file mode 100644
index 0000000..24f9761
--- /dev/null
+++ b/tests/integration-tests-utils/src/main/java/org/apache/bookkeeper/tests/LogToTargetDirStopAction.java
@@ -0,0 +1,41 @@
+/**
+ * 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.apache.bookkeeper.tests;
+
+import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
+import org.arquillian.cube.impl.model.CubeId;
+import org.arquillian.cube.spi.beforeStop.BeforeStopAction;
+
+public class LogToTargetDirStopAction implements BeforeStopAction {
+    private DockerClientExecutor dockerClientExecutor;
+    private CubeId containerID;
+
+    public void setDockerClientExecutor(DockerClientExecutor executor) {
+        this.dockerClientExecutor = executor;
+    }
+
+    public void setContainerID(CubeId containerID) {
+        this.containerID = containerID;
+    }
+
+    @Override
+    public void doBeforeStop() {
+        DockerUtils.dumpContainerLogToTarget(dockerClientExecutor.getDockerClient(), containerID.getId());
+    }
+}
diff --git a/tests/integration-tests-utils/src/main/java/org/apache/bookkeeper/tests/NoopAwaitStrategy.java b/tests/integration-tests-utils/src/main/java/org/apache/bookkeeper/tests/NoopAwaitStrategy.java
new file mode 100644
index 0000000..6df8167
--- /dev/null
+++ b/tests/integration-tests-utils/src/main/java/org/apache/bookkeeper/tests/NoopAwaitStrategy.java
@@ -0,0 +1,30 @@
+/**
+ *
+ * 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.apache.bookkeeper.tests;
+
+import org.arquillian.cube.spi.await.AwaitStrategy;
+
+public class NoopAwaitStrategy implements AwaitStrategy {
+    @Override
+    public boolean await() {
+        return true;
+    }
+}
diff --git a/tests/integration-tests-utils/src/main/java/org/apache/bookkeeper/tests/ZooKeeperAwaitStrategy.java b/tests/integration-tests-utils/src/main/java/org/apache/bookkeeper/tests/ZooKeeperAwaitStrategy.java
new file mode 100644
index 0000000..86d2d69
--- /dev/null
+++ b/tests/integration-tests-utils/src/main/java/org/apache/bookkeeper/tests/ZooKeeperAwaitStrategy.java
@@ -0,0 +1,57 @@
+/**
+ *
+ * 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.apache.bookkeeper.tests;
+
+import java.util.concurrent.TimeUnit;
+
+import org.arquillian.cube.spi.Cube;
+import org.arquillian.cube.spi.await.AwaitStrategy;
+import org.arquillian.cube.spi.metadata.HasPortBindings;
+import org.arquillian.cube.docker.impl.client.config.Await;
+import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
+import org.arquillian.cube.docker.impl.util.Ping;
+import org.arquillian.cube.docker.impl.util.PingCommand;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ZooKeeperAwaitStrategy implements AwaitStrategy {
+    private static final Logger LOG = LoggerFactory.getLogger(ZooKeeperAwaitStrategy.class);
+
+    private static final int DEFAULT_POLL_ITERATIONS = 10;
+    private static final int DEFAULT_SLEEP_TIME = 1;
+    private static final TimeUnit DEFAULT_SLEEP_TIMEUNIT = TimeUnit.SECONDS;
+
+    private Cube<?> cube;
+    private DockerClientExecutor dockerClientExecutor;
+
+    @Override
+    public boolean await() {
+        return Ping.ping(DEFAULT_POLL_ITERATIONS, DEFAULT_SLEEP_TIME, DEFAULT_SLEEP_TIMEUNIT,
+                new PingCommand() {
+                    @Override
+                    public boolean call() {
+                        return BookKeeperClusterUtils.zookeeperRunning(dockerClientExecutor.getDockerClient(),
+                                                                       cube.getId());
+                    }
+                });
+    }
+}
diff --git a/tests/integration-tests-utils/src/main/resources/log4j.properties b/tests/integration-tests-utils/src/main/resources/log4j.properties
new file mode 100644
index 0000000..09ac2e7
--- /dev/null
+++ b/tests/integration-tests-utils/src/main/resources/log4j.properties
@@ -0,0 +1,37 @@
+#
+#
+# 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.
+#
+#
+
+# Format is "<default threshold> (, <appender>)+
+
+# DEFAULT: console appender only, level INFO
+log4j.rootLogger=INFO,CONSOLE
+
+#
+# Log INFO level and above messages to the console
+#
+log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
+log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
+log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} - %-5p - [%t:%C{1}@%L] - %m%n
+
+#disable zookeeper logging
+log4j.logger.org.apache.zookeeper=OFF
+log4j.logger.org.apache.bookkeeper.bookie=INFO
+log4j.logger.org.apache.bookkeeper.meta=INFO
diff --git a/tests/pom.xml b/tests/pom.xml
index 2bc561d..f817001 100644
--- a/tests/pom.xml
+++ b/tests/pom.xml
@@ -36,6 +36,7 @@
     <module>bookkeeper-server-shaded-test</module>
     <module>bookkeeper-server-tests-shaded-test</module>
     <module>docker-all-versions-image</module>
+    <module>integration-tests-utils</module>
   </modules>
   <build>
     <plugins>

-- 
To stop receiving notification emails like this one, please contact
['"commits@bookkeeper.apache.org" <co...@bookkeeper.apache.org>'].