Skip to content

Fix tests + refactoring of cartridge container bootstrap #46

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

Merged
merged 11 commits into from
May 18, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 0 additions & 5 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,6 @@
<version>1.2.3</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.docker-java</groupId>
<artifactId>docker-java</artifactId>
<version>3.2.13</version>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,13 @@
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeoutException;
import java.util.function.Supplier;

import static org.testcontainers.containers.PathUtils.normalizePath;

Expand Down Expand Up @@ -102,8 +103,11 @@ public class TarantoolCartridgeContainer extends GenericContainer<TarantoolCartr
private static final String ENV_TARANTOOL_RUNDIR = "TARANTOOL_RUNDIR";
private static final String ENV_TARANTOOL_DATADIR = "TARANTOOL_DATADIR";
private static final String ENV_TARANTOOL_INSTANCES_FILE = "TARANTOOL_INSTANCES_FILE";

private final CartridgeConfigParser instanceFileParser;
private final TarantoolContainerClientHelper clientHelper;
private final String TARANTOOL_RUN_DIR;

private boolean useFixedPorts = false;
private String routerHost = ROUTER_HOST;
private int routerPort = ROUTER_PORT;
Expand All @@ -113,7 +117,6 @@ public class TarantoolCartridgeContainer extends GenericContainer<TarantoolCartr
private String directoryResourcePath = SCRIPT_RESOURCE_DIRECTORY;
private String instanceDir = INSTANCE_DIR;
private String topologyConfigurationFile;
private String replicasetsFileName;

/**
* Create a container with default image and specified instances file from the classpath resources. Assumes that
Expand Down Expand Up @@ -181,35 +184,39 @@ public TarantoolCartridgeContainer(String dockerFile, String buildImageName,
*/
public TarantoolCartridgeContainer(String dockerFile, String buildImageName, String instancesFile,
String topologyConfigurationFile, final Map<String, String> buildArgs) {
this(withArguments(buildImage(dockerFile, buildImageName), instancesFile, buildArgs),
instancesFile, topologyConfigurationFile);
this(buildImage(dockerFile, buildImageName), instancesFile, topologyConfigurationFile, buildArgs);
}

private TarantoolCartridgeContainer(ImageFromDockerfile image, String instancesFile, String topologyConfigurationFile,
Map<String, String> buildArgs) {
super(withBuildArgs(image, buildArgs));

TARANTOOL_RUN_DIR = mergeBuildArguments(buildArgs).getOrDefault(ENV_TARANTOOL_RUNDIR, "/tmp/run");

private TarantoolCartridgeContainer(Future<String> image, String instancesFile, String topologyConfigurationFile) {
super(image);
if (instancesFile == null || instancesFile.isEmpty()) {
throw new IllegalArgumentException("Instance file name must not be null or empty");
}
if (topologyConfigurationFile == null || topologyConfigurationFile.isEmpty()) {
throw new IllegalArgumentException("Topology configuration file must not be null or empty");
}
String fileType = topologyConfigurationFile.substring(topologyConfigurationFile.lastIndexOf('.') + 1);
if (fileType.equals("lua")) {
this.topologyConfigurationFile = topologyConfigurationFile;
}else{
this.replicasetsFileName = topologyConfigurationFile.substring(topologyConfigurationFile.lastIndexOf('/')+1);
}
this.topologyConfigurationFile = topologyConfigurationFile;
this.instanceFileParser = new CartridgeConfigParser(instancesFile);
this.clientHelper = new TarantoolContainerClientHelper(this);
}

private static Future<String> withArguments(ImageFromDockerfile image, String instancesFile,
final Map<String, String> buildArgs) {
if (!buildArgs.isEmpty()) {
image.withBuildArgs(buildArgs);
private static ImageFromDockerfile withBuildArgs(ImageFromDockerfile image, Map<String, String> buildArgs) {
Map<String, String> args = mergeBuildArguments(buildArgs);

if (!args.isEmpty()) {
image.withBuildArgs(args);
}

return image;
}

private static Map<String, String> mergeBuildArguments(Map<String, String> buildArgs) {
Map<String, String> args = new HashMap<>(buildArgs);

for (String envVariable : Arrays.asList(
ENV_TARANTOOL_VERSION,
ENV_TARANTOOL_SERVER_USER,
Expand All @@ -222,11 +229,11 @@ private static Future<String> withArguments(ImageFromDockerfile image, String in
ENV_TARANTOOL_INSTANCES_FILE
)) {
String variableValue = System.getenv(envVariable);
if (variableValue != null) {
image.withBuildArg(envVariable, variableValue);
if (variableValue != null && !args.containsKey(envVariable)) {
args.put(envVariable, variableValue);
}
}
return image;
return args;
}

private static ImageFromDockerfile buildImage(String dockerFile, String buildImageName) {
Expand Down Expand Up @@ -458,38 +465,25 @@ protected void containerIsStarting(InspectContainerResponse containerInfo) {
}

private boolean setupTopology() {
if (topologyConfigurationFile == null) {
String runDirPath = null;
try {
Container.ExecResult envVariablesContainer = this.execInContainer("env");
String stdout = envVariablesContainer.getStdout();
int exitCode = envVariablesContainer.getExitCode();
if (exitCode != 0) {
logger().error("Failed to bootstrap replica sets topology: {}", stdout);
}
int startInd = stdout.lastIndexOf(ENV_TARANTOOL_RUNDIR + "=");
try {
runDirPath = stdout.substring(startInd,
stdout.indexOf('\n', startInd)).split("=")[1];
} catch (Exception e) {
logger().error("Missing dir-run environment variable: {}", e.getMessage());
}
String fileType = topologyConfigurationFile.substring(topologyConfigurationFile.lastIndexOf('.') + 1);

} catch (Exception e) {
logger().error("Failed to get environment variables: {}", e.getMessage());
}
if (fileType.equals("yml")) {
String replicasetsFileName = topologyConfigurationFile
.substring(topologyConfigurationFile.lastIndexOf('/') + 1);

try {
Container.ExecResult lsResult = this.execInContainer("cartridge", "replicasets", "--run-dir=" + runDirPath,
"--file=" + this.replicasetsFileName, "setup", "--bootstrap-vshard");
String stdout = lsResult.getStdout();
int exitCode = lsResult.getExitCode();
if (exitCode != 0) {
logger().error("Failed to bootstrap replica sets topology: {}", stdout);
Container.ExecResult result = execInContainer("cartridge",
"replicasets",
"--run-dir=" + TARANTOOL_RUN_DIR,
"--file=" + replicasetsFileName, "setup", "--bootstrap-vshard");
if (result.getExitCode() != 0) {
throw new RuntimeException("Failed to change the app topology via cartridge CLI: "
+ result.getStdout());
}
} catch (Exception e) {
logger().error("Failed to bootstrap replica sets topology: {}", e.getMessage());
throw new RuntimeException("Failed to change the app topology: " + e.getMessage());
}

} else {
try {
executeScript(topologyConfigurationFile).get();
Expand Down Expand Up @@ -539,14 +533,65 @@ private void bootstrapVshard() {
protected void containerIsStarted(InspectContainerResponse containerInfo, boolean reused) {
super.containerIsStarted(containerInfo, reused);

waitUntilRouterIsUp(60);
retryingSetupTopology();
// wait until Roles are configured
waitUntilCartridgeIsHealthy(10);
bootstrapVshard();

logger().info("Tarantool Cartridge cluster is started");
logger().info("Tarantool Cartridge router is listening at {}:{}", getRouterHost(), getRouterPort());
logger().info("Tarantool Cartridge HTTP API is available at {}:{}", getAPIHost(), getAPIPort());
}

private void waitUntilRouterIsUp(int secondsToWait) {
waitUntilTrue(secondsToWait, this::routerIsUp);
}

private void waitUntilCartridgeIsHealthy(int secondsToWait) {
waitUntilTrue(secondsToWait, this::isCartridgeHealthy);
}

private void waitUntilTrue(int secondsToWait, Supplier<Boolean> waitFunc) {
int secondsPassed = 0;
boolean result = waitFunc.get();
while (!result && secondsPassed < secondsToWait) {
result = waitFunc.get();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
break;
}
}
if (!result) {
throw new RuntimeException("Failed to change the app topology after retry");
}
}

private boolean routerIsUp() {
String healthyCmd = " local cartridge = package.loaded['cartridge']" +
" return assert(cartridge ~= nil)";
try {
List<?> result = executeCommand(healthyCmd).get();
return (Boolean) result.get(0);
} catch (Exception e) {
logger().warn("Error while waiting for router instance to be up: " + e.getMessage());
return false;
}
}

private boolean isCartridgeHealthy() {
String healthyCmd = " local cartridge = package.loaded['cartridge']" +
" return assert(cartridge) and assert(cartridge.is_healthy())";
try {
List<?> result = executeCommand(healthyCmd).get();
return (Boolean) result.get(0);
} catch (Exception e) {
logger().warn("Error while waiting for cartridge healthy state: " + e.getMessage());
return false;
}
}

@Override
public CompletableFuture<List<?>> executeScript(String scriptResourcePath) throws Exception {
return clientHelper.executeScript(scriptResourcePath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ private TarantoolClient<TarantoolTuple, TarantoolResult<TarantoolTuple>> createC
.withCredentials(container.getUsername(), container.getPassword())
.withAddress(container.getHost(), container.getPort())
.withRequestTimeout(5000)
.withRetryingByNumberOfAttempts(10,
TarantoolRequestRetryPolicies.retryNetworkErrors()
.or(TarantoolRequestRetryPolicies.retryNetworkErrors()), b -> b.withDelay(100))
.withRetryingByNumberOfAttempts(15,
TarantoolRequestRetryPolicies.retryNetworkErrors(),
b -> b.withDelay(100))
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@
import com.github.dockerjava.api.command.BuildImageCmd;
import com.github.dockerjava.api.command.BuildImageResultCallback;
import com.github.dockerjava.api.model.Image;
import com.github.dockerjava.core.DockerClientBuilder;
import org.apache.commons.lang3.StringUtils;
import org.testcontainers.DockerClientFactory;

import java.util.Arrays;
import java.util.Collection;
Expand All @@ -21,8 +20,6 @@
*/
class TarantoolContainerImageHelper {

private static final DockerClient dockerClient = DockerClientBuilder.getInstance().build();

private TarantoolContainerImageHelper() {
}

Expand All @@ -35,7 +32,7 @@ private TarantoolContainerImageHelper() {
static String getImage(TarantoolImageParams imageParams) {
final String tag = imageParams.getTag();

if (StringUtils.isEmpty(tag)) {
if (tag == null || tag.isEmpty()) {
throw new IllegalArgumentException("Image tag is null or empty!");
}

Expand All @@ -52,7 +49,7 @@ static String getImage(TarantoolImageParams imageParams) {
* @param imageParams parameters for building tarantool image
*/
private static void buildImage(TarantoolImageParams imageParams) {
final BuildImageCmd buildImageCmd = dockerClient.buildImageCmd(imageParams.getDockerfile());
final BuildImageCmd buildImageCmd = getDockerClient().buildImageCmd(imageParams.getDockerfile());

final Map<String, String> buildArgs = imageParams.getBuildArgs();
for (Map.Entry<String, String> entry : buildArgs.entrySet()) {
Expand All @@ -71,11 +68,16 @@ private static void buildImage(TarantoolImageParams imageParams) {
* @return true if image exist and false if not
*/
private static boolean hasImage(String tag) {
final List<Image> images = dockerClient.listImagesCmd().exec();
final List<Image> images = getDockerClient().listImagesCmd().exec();
return images.stream()
.map(Image::getRepoTags)
.map(Arrays::asList)
.flatMap(Collection::stream)
.anyMatch(repoTag -> repoTag.equals(tag));
}


private static DockerClient getDockerClient() {
return DockerClientFactory.instance().client();
}
}
10 changes: 3 additions & 7 deletions src/main/resources/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
FROM centos:7 AS tarantool-base
ARG TARANTOOL_VERSION=2.8
ARG TARANTOOL_SERVER_USER="tarantool"
ARG TARANTOOL_SERVER_UID=1000
ARG TARANTOOL_SERVER_GROUP="tarantool"
ARG TARANTOOL_SERVER_GID=1000
ARG TARANTOOL_SERVER_USER="root"
ARG TARANTOOL_SERVER_GROUP="root"
ARG TARANTOOL_WORKDIR="/app"
ARG TARANTOOL_RUNDIR="/tmp/run"
ARG TARANTOOL_DATADIR="/tmp/data"
Expand All @@ -15,9 +13,7 @@ ENV TARANTOOL_INSTANCES_FILE=$TARANTOOL_INSTANCES_FILE
RUN curl -L https://tarantool.io/installer.sh | VER=$TARANTOOL_VERSION /bin/bash -s -- --repo-only && \
yum -y install cmake make gcc gcc-c++ git unzip tarantool tarantool-devel cartridge-cli && \
yum clean all
RUN groupadd -g $TARANTOOL_SERVER_GID $TARANTOOL_SERVER_GROUP && \
useradd -u $TARANTOOL_SERVER_UID -g $TARANTOOL_SERVER_GID -m -s /bin/bash $TARANTOOL_SERVER_USER \
|| true
RUN groupadd $TARANTOOL_SERVER_GROUP && useradd -m -s /bin/bash $TARANTOOL_SERVER_USER || true
USER $TARANTOOL_SERVER_USER:$TARANTOOL_SERVER_GROUP
RUN cartridge version

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.testcontainers.containers;

import org.junit.jupiter.api.Test;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.output.Slf4jLogConsumer;
import org.testcontainers.junit.jupiter.Container;
import org.testcontainers.junit.jupiter.Testcontainers;
import org.testcontainers.utility.MountableFile;

import java.time.Duration;

/**
* @author Alexey Kuzin
*/
@Testcontainers
public class TarantoolCartridgeBootstrapFromLuaWithFixedPortsTest {

@Container
private static final TarantoolCartridgeContainer container =
new TarantoolCartridgeContainer(
"Dockerfile",
"cartridge/instances_fixedport.yml",
"cartridge/topology_fixedport.lua")
.withCopyFileToContainer(MountableFile.forClasspathResource("cartridge"), "/app")
.withCopyFileToContainer(MountableFile.forClasspathResource("cartridge/instances_fixedport.yml"),"/app/instances.yml")
.withStartupTimeout(Duration.ofSeconds(300))
.withUseFixedPorts(true)
.withAPIPort(18081)
.withRouterPort(13301)
.withLogConsumer(new Slf4jLogConsumer(
LoggerFactory.getLogger(TarantoolCartridgeBootstrapFromLuaWithFixedPortsTest.class)));

@Test
public void test_StaticClusterContainer_StartsSuccessfully_ifFilesAreCopied() throws Exception {
CartridgeContainerTestUtils.executeProfileReplaceSmokeTest(container);
}
}
Loading