Skip to content

Commit

Permalink
Introduce testcontainer based cluster integration tests (#2055)
Browse files Browse the repository at this point in the history
* Introduce testcontainer cluster tests

*Motivation*

Introduce testcontainer based cluster tests for replacing existing arquilian tests.

testcontainer is more flexible and more nature to how we write unit tests. arquilian is configuration based and hard to change cluster at runtime.
it is possible to add bookies/brokers/proxies at testing runtime using testcontainers.

current arquilian based integration tests can only run in linux environment (which is using host network). it is impossible to run arquillian tests in mac environment,
where host network is meaningless. changing to testcontainers is allowing running integration tests on mac to speed up development.

*Changes*

This mirrors the exact same setup as existing arquillian tests - 1 zk container, 1 cs container, 3 bk containers, 2 broker containers and 1 proxy container.

add a semantic integration test module to using the new testcontainer test base `PulsarClusterTestBase`.

*Result*

- both arquillian and testcontainers exist.
- new integration tests are encouraged to be written using `PulsarClusterTestBase`. It is just as easy as how you write normal unit tests.
- existing arquillian tests will be migrated in future PRs.

* all modules under `tests/integration` should only be run after `-DintegrationTests` is specified
  • Loading branch information
sijie committed Jul 1, 2018
1 parent 99c0ed2 commit 4d1cf20
Show file tree
Hide file tree
Showing 17 changed files with 948 additions and 7 deletions.
27 changes: 27 additions & 0 deletions pom.xml
Expand Up @@ -122,6 +122,9 @@ flexible messaging model and an intuitive client API.</description>
<testRealAWS>false</testRealAWS>
<testRetryCount>1</testRetryCount>

<!-- apache commons -->
<commons-compress.version>1.15</commons-compress.version>

<bookkeeper.version>4.7.1</bookkeeper.version>
<zookeeper.version>3.5.4-beta</zookeeper.version>
<netty.version>4.1.22.Final</netty.version>
Expand Down Expand Up @@ -155,7 +158,10 @@ flexible messaging model and an intuitive client API.</description>
<avro.version>1.8.2</avro.version>

<!-- test dependencies -->
<arquillian-cube.version>1.15.1</arquillian-cube.version>
<arquillian-junit.version>1.1.14.Final</arquillian-junit.version>
<disruptor.version>3.4.0</disruptor.version>
<testcontainers.version>1.8.0</testcontainers.version>

<!-- Plugin dependencies -->
<protobuf-maven-plugin.version>0.5.0</protobuf-maven-plugin.version>
Expand Down Expand Up @@ -390,6 +396,12 @@ flexible messaging model and an intuitive client API.</description>
<version>3.4</version>
</dependency>

<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
<version>${commons-compress.version}</version>
</dependency>

<dependency>
<groupId>commons-configuration</groupId>
<artifactId>commons-configuration</artifactId>
Expand Down Expand Up @@ -783,6 +795,21 @@ flexible messaging model and an intuitive client API.</description>
<artifactId>disruptor</artifactId>
<version>${disruptor.version}</version>
</dependency>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
<version>${testcontainers.version}</version>
</dependency>
<dependency>
<groupId>org.arquillian.cube</groupId>
<artifactId>arquillian-cube-docker</artifactId>
<version>${arquillian-cube.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.arquillian.junit</groupId>
<artifactId>arquillian-junit-standalone</artifactId>
<version>${arquillian-junit.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down
16 changes: 16 additions & 0 deletions tests/integration-tests-topologies/pom.xml
Expand Up @@ -36,5 +36,21 @@
<packaging>jar</packaging>

<name>Apache Pulsar :: Tests :: Common topologies for Arquillian based integration tests</name>
<dependencies>
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers</artifactId>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.pulsar.tests</groupId>
<artifactId>integration-tests-utils</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>

</project>
@@ -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.pulsar.tests.containers;

/**
* A pulsar container that runs bookkeeper.
*/
public class BKContainer extends PulsarContainer<BKContainer> {

public BKContainer(String clusterName, String hostName) {
super(
clusterName, hostName, hostName, "bin/run-bookie.sh", BOOKIE_PORT, INVALID_PORT);
}
}
@@ -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.pulsar.tests.containers;

/**
* A pulsar container that runs bookkeeper.
*/
public class BrokerContainer extends PulsarContainer<BrokerContainer> {

public BrokerContainer(String clusterName, String hostName) {
super(
clusterName, hostName, hostName, "bin/run-broker.sh", BROKER_PORT, INVALID_PORT);
}
}
@@ -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.
*/
package org.apache.pulsar.tests.containers;

/**
* A pulsar container that runs configuration store.
*/
public class CSContainer extends PulsarContainer<CSContainer> {

public static final String NAME = "configuration-store";

public CSContainer(String clusterName) {
super(
clusterName,
NAME,
NAME,
"bin/run-global-zk.sh",
CS_PORT,
INVALID_PORT);
}
}
@@ -0,0 +1,116 @@
/**
* 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.pulsar.tests.containers;

import static java.nio.charset.StandardCharsets.UTF_8;

import com.github.dockerjava.api.command.LogContainerCmd;
import com.github.dockerjava.api.model.Frame;
import com.github.dockerjava.core.command.LogContainerResultCallback;
import java.util.Objects;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.testcontainers.containers.GenericContainer;

/**
* A base container provides chaos capability.
*/
@Slf4j
public class ChaosContainer<SelfT extends ChaosContainer<SelfT>> extends GenericContainer<SelfT> {

protected final String clusterName;

protected ChaosContainer(String clusterName, String image) {
super(image);
this.clusterName = clusterName;
}

public void tailContainerLog() {
CompletableFuture.runAsync(() -> {
while (null == containerId) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
return;
}
}

LogContainerCmd logContainerCmd = this.dockerClient.logContainerCmd(containerId);
logContainerCmd.withStdOut(true).withStdErr(true).withFollowStream(true);
logContainerCmd.exec(new LogContainerResultCallback() {
@Override
public void onNext(Frame item) {
log.info(new String(item.getPayload(), UTF_8));
}
});
});
}

public String getContainerLog() {
StringBuilder sb = new StringBuilder();

LogContainerCmd logContainerCmd = this.dockerClient.logContainerCmd(containerId);
logContainerCmd.withStdOut(true).withStdErr(true);
try {
logContainerCmd.exec(new LogContainerResultCallback() {
@Override
public void onNext(Frame item) {
sb.append(new String(item.getPayload(), UTF_8));
}
}).awaitCompletion();
} catch (InterruptedException e) {

}
return sb.toString();
}

public ExecResult execCmd(String... cmd) throws Exception {
String cmdString = StringUtils.join(cmd, " ");

log.info("DOCKER.exec({}:{}): Executing ...", containerId, cmdString);

ExecResult result = execInContainer(cmd);

log.info("Docker.exec({}:{}): Done", containerId, cmdString);
log.info("Docker.exec({}:{}): Stdout -\n{}", containerId, cmdString, result.getStdout());
log.info("Docker.exec({}:{}): Stderr -\n{}", containerId, cmdString, result.getStderr());

return result;
}

@Override
public boolean equals(Object o) {
if (!(o instanceof ChaosContainer)) {
return false;
}

ChaosContainer another = (ChaosContainer) o;
return clusterName.equals(another.clusterName)
&& super.equals(another);
}

@Override
public int hashCode() {
return 31 * super.hashCode() + Objects.hash(
clusterName);
}

}
@@ -0,0 +1,38 @@
/**
* 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.pulsar.tests.containers;

/**
* A pulsar container that runs bookkeeper.
*/
public class ProxyContainer extends PulsarContainer<ProxyContainer> {

public ProxyContainer(String clusterName, String hostName) {
super(
clusterName, hostName, hostName, "bin/run-proxy.sh", BROKER_PORT, BROKER_HTTP_PORT);
}

public String getPlainTextServiceUrl() {
return "pulsar://" + getContainerIpAddress() + ":" + getMappedPort(BROKER_PORT);
}

public String getHttpServiceUrl() {
return "http://" + getContainerIpAddress() + ":" + getMappedPort(BROKER_HTTP_PORT);
}
}

0 comments on commit 4d1cf20

Please sign in to comment.