From 4e81f9cc14dd6daa138dce046ccee5ba04b02cc1 Mon Sep 17 00:00:00 2001 From: Igor Kulikov Date: Wed, 31 Oct 2018 19:23:00 +0200 Subject: [PATCH] Update black box tests infrastructure. --- docker/.env | 2 + docker/docker-compose.postgres.volumes.yml | 36 ++++++ docker/docker-compose.yml | 2 +- docker/tb-node/conf/logback.xml | 2 +- msa/black-box-tests/pom.xml | 6 + .../server/msa/ContainerTestSuite.java | 34 +++-- .../server/msa/DockerComposeExecutor.java | 119 ++++++++++++++++++ .../server/msa/ThingsBoardDbInstaller.java | 106 ++++++++++++++++ 8 files changed, 298 insertions(+), 9 deletions(-) create mode 100644 docker/docker-compose.postgres.volumes.yml create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/DockerComposeExecutor.java create mode 100644 msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java diff --git a/docker/.env b/docker/.env index c03845dfe0..0138501ebc 100644 --- a/docker/.env +++ b/docker/.env @@ -15,4 +15,6 @@ TB_VERSION=latest DATABASE=postgres +LOAD_BALANCER_NAME=haproxy-certbot + KAFKA_TOPICS="js.eval.requests:100:1:delete --config=retention.ms=60000 --config=segment.bytes=26214400 --config=retention.bytes=104857600,tb.transport.api.requests:30:1:delete --config=retention.ms=60000 --config=segment.bytes=26214400 --config=retention.bytes=104857600,tb.rule-engine:30:1" diff --git a/docker/docker-compose.postgres.volumes.yml b/docker/docker-compose.postgres.volumes.yml new file mode 100644 index 0000000000..d94b066cb0 --- /dev/null +++ b/docker/docker-compose.postgres.volumes.yml @@ -0,0 +1,36 @@ +# +# Copyright © 2016-2018 The Thingsboard Authors +# +# Licensed 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. +# + +version: '2.2' + +services: + postgres: + volumes: + - postgres-db-volume:/var/lib/postgresql/data + tb1: + volumes: + - tb-log-volume:/var/log/thingsboard + tb2: + volumes: + - tb-log-volume:/var/log/thingsboard + +volumes: + postgres-db-volume: + external: true + name: ${POSTGRES_DATA_VOLUME} + tb-log-volume: + external: true + name: ${TB_LOG_VOLUME} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 1741db3694..0a09d8a024 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -145,7 +145,7 @@ services: - tb-web-ui.env haproxy: restart: always - container_name: haproxy-certbot + container_name: "${LOAD_BALANCER_NAME}" image: xalauc/haproxy-certbot:1.7.9 volumes: - ./haproxy/config:/config diff --git a/docker/tb-node/conf/logback.xml b/docker/tb-node/conf/logback.xml index 1c69f537bc..6ec2d0b2d0 100644 --- a/docker/tb-node/conf/logback.xml +++ b/docker/tb-node/conf/logback.xml @@ -24,7 +24,7 @@ /var/log/thingsboard/${TB_HOST}/thingsboard.log - /var/log/thingsboard/thingsboard.%d{yyyy-MM-dd}.%i.log + /var/log/thingsboard/${TB_HOST}/thingsboard.%d{yyyy-MM-dd}.%i.log 100MB 30 3GB diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index 2af4d2c14e..9f5ac0912c 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -36,6 +36,7 @@ ${basedir}/../.. true 1.9.1 + 1.10 1.3.9 4.5.6 @@ -46,6 +47,11 @@ testcontainers ${testcontainers.version} + + org.zeroturnaround + zt-exec + ${zeroturnaround.version} + org.java-websocket Java-WebSocket diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java index 495fd94d2e..3233617823 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java @@ -17,23 +17,43 @@ package org.thingsboard.server.msa; import org.junit.ClassRule; import org.junit.extensions.cpsuite.ClasspathSuite; +import org.junit.rules.ExternalResource; import org.junit.runner.RunWith; import org.testcontainers.containers.DockerComposeContainer; import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.utility.Base58; import java.io.File; import java.time.Duration; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*Test"}) public class ContainerTestSuite { + private static DockerComposeContainer testContainer; + @ClassRule - public static DockerComposeContainer composeContainer = new DockerComposeContainer( - new File("./../../docker/docker-compose.yml"), - new File("./../../docker/docker-compose.postgres.yml")) - .withPull(false) - .withLocalCompose(true) - .withTailChildContainers(true) - .withExposedService("tb-web-ui1", 8080, Wait.forHttp("/login").withStartupTimeout(Duration.ofSeconds(120))); + public static ThingsBoardDbInstaller installTb = new ThingsBoardDbInstaller(); + + @ClassRule + public static DockerComposeContainer getTestContainer() { + if (testContainer == null) { + testContainer = new DockerComposeContainer( + new File("./../../docker/docker-compose.yml"), + new File("./../../docker/docker-compose.postgres.yml"), + new File("./../../docker/docker-compose.postgres.volumes.yml")) + .withPull(false) + .withLocalCompose(true) + .withTailChildContainers(true) + .withEnv("POSTGRES_DATA_VOLUME", installTb.getPostgresDataVolume()) + .withEnv("TB_LOG_VOLUME", installTb.getTbLogVolume()) + .withEnv("LOAD_BALANCER_NAME", "") + .withExposedService("haproxy", 80, Wait.forHttp("/swagger-ui.html").withStartupTimeout(Duration.ofSeconds(120))); + } + return testContainer; + } } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/DockerComposeExecutor.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/DockerComposeExecutor.java new file mode 100644 index 0000000000..25d2e6ee4c --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/DockerComposeExecutor.java @@ -0,0 +1,119 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed 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.thingsboard.server.msa; + + +import com.google.common.base.Splitter; +import com.google.common.collect.Maps; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.SystemUtils; +import org.testcontainers.containers.ContainerLaunchException; +import org.testcontainers.utility.CommandLine; +import org.zeroturnaround.exec.InvalidExitValueException; +import org.zeroturnaround.exec.ProcessExecutor; +import org.zeroturnaround.exec.stream.slf4j.Slf4jStream; + +import java.io.File; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static java.util.stream.Collectors.joining; + +@Slf4j +public class DockerComposeExecutor { + + String ENV_PROJECT_NAME = "COMPOSE_PROJECT_NAME"; + String ENV_COMPOSE_FILE = "COMPOSE_FILE"; + + private static final String COMPOSE_EXECUTABLE = SystemUtils.IS_OS_WINDOWS ? "docker-compose.exe" : "docker-compose"; + private static final String DOCKER_EXECUTABLE = SystemUtils.IS_OS_WINDOWS ? "docker.exe" : "docker"; + + private final List composeFiles; + private final String identifier; + private String cmd = ""; + private Map env = new HashMap<>(); + + public DockerComposeExecutor(List composeFiles, String identifier) { + validateFileList(composeFiles); + this.composeFiles = composeFiles; + this.identifier = identifier; + } + + public DockerComposeExecutor withCommand(String cmd) { + this.cmd = cmd; + return this; + } + + public DockerComposeExecutor withEnv(Map env) { + this.env = env; + return this; + } + + public void invokeCompose() { + // bail out early + if (!CommandLine.executableExists(COMPOSE_EXECUTABLE)) { + throw new ContainerLaunchException("Local Docker Compose not found. Is " + COMPOSE_EXECUTABLE + " on the PATH?"); + } + final Map environment = Maps.newHashMap(env); + environment.put(ENV_PROJECT_NAME, identifier); + final Stream absoluteDockerComposeFilePaths = composeFiles.stream().map(File::getAbsolutePath).map(Objects::toString); + final String composeFileEnvVariableValue = absoluteDockerComposeFilePaths.collect(joining(File.pathSeparator + "")); + log.debug("Set env COMPOSE_FILE={}", composeFileEnvVariableValue); + final File pwd = composeFiles.get(0).getAbsoluteFile().getParentFile().getAbsoluteFile(); + environment.put(ENV_COMPOSE_FILE, composeFileEnvVariableValue); + log.info("Local Docker Compose is running command: {}", cmd); + final List command = Splitter.onPattern(" ").omitEmptyStrings().splitToList(COMPOSE_EXECUTABLE + " " + cmd); + try { + new ProcessExecutor().command(command).redirectOutput(Slf4jStream.of(log).asInfo()).redirectError(Slf4jStream.of(log).asError()).environment(environment).directory(pwd).exitValueNormal().executeNoTimeout(); + log.info("Docker Compose has finished running"); + } catch (InvalidExitValueException e) { + throw new ContainerLaunchException("Local Docker Compose exited abnormally with code " + e.getExitValue() + " whilst running command: " + cmd); + } catch (Exception e) { + throw new ContainerLaunchException("Error running local Docker Compose command: " + cmd, e); + } + } + + public void invokeDocker() { + // bail out early + if (!CommandLine.executableExists(DOCKER_EXECUTABLE)) { + throw new ContainerLaunchException("Local Docker not found. Is " + DOCKER_EXECUTABLE + " on the PATH?"); + } + final File pwd = composeFiles.get(0).getAbsoluteFile().getParentFile().getAbsoluteFile(); + log.info("Local Docker is running command: {}", cmd); + final List command = Splitter.onPattern(" ").omitEmptyStrings().splitToList(DOCKER_EXECUTABLE + " " + cmd); + try { + new ProcessExecutor().command(command).redirectOutput(Slf4jStream.of(log).asInfo()).redirectError(Slf4jStream.of(log).asError()).directory(pwd).exitValueNormal().executeNoTimeout(); + log.info("Docker has finished running"); + } catch (InvalidExitValueException e) { + throw new ContainerLaunchException("Local Docker exited abnormally with code " + e.getExitValue() + " whilst running command: " + cmd); + } catch (Exception e) { + throw new ContainerLaunchException("Error running local Docker command: " + cmd, e); + } + } + + void validateFileList(List composeFiles) { + checkNotNull(composeFiles); + checkArgument(!composeFiles.isEmpty(), "No docker compose file have been provided"); + } + + +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java new file mode 100644 index 0000000000..0a902b9c5e --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java @@ -0,0 +1,106 @@ +/** + * Copyright © 2016-2018 The Thingsboard Authors + * + * Licensed 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.thingsboard.server.msa; + +import org.junit.rules.ExternalResource; +import org.testcontainers.utility.Base58; + +import java.io.File; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ThingsBoardDbInstaller extends ExternalResource { + + private final static String POSTGRES_DATA_VOLUME = "tb-postgres-test-data-volume"; + private final static String TB_LOG_VOLUME = "tb-log-test-volume"; + + private final DockerComposeExecutor dockerCompose; + + private final String postgresDataVolume; + private final String tbLogVolume; + + public ThingsBoardDbInstaller() { + List composeFiles = Arrays.asList(new File("./../../docker/docker-compose.yml"), + new File("./../../docker/docker-compose.postgres.yml"), + new File("./../../docker/docker-compose.postgres.volumes.yml")); + + String identifier = Base58.randomString(6).toLowerCase(); + String project = identifier + Base58.randomString(6).toLowerCase(); + + postgresDataVolume = project + "_" + POSTGRES_DATA_VOLUME; + tbLogVolume = project + "_" + TB_LOG_VOLUME; + + dockerCompose = new DockerComposeExecutor(composeFiles, project); + + Map env = new HashMap<>(); + env.put("POSTGRES_DATA_VOLUME", postgresDataVolume); + env.put("TB_LOG_VOLUME", tbLogVolume); + dockerCompose.withEnv(env); + } + + public String getPostgresDataVolume() { + return postgresDataVolume; + } + + public String getTbLogVolume() { + return tbLogVolume; + } + + @Override + protected void before() throws Throwable { + try { + + dockerCompose.withCommand("volume create " + postgresDataVolume); + dockerCompose.invokeDocker(); + + dockerCompose.withCommand("volume create " + tbLogVolume); + dockerCompose.invokeDocker(); + + dockerCompose.withCommand("up -d redis postgres"); + dockerCompose.invokeCompose(); + + dockerCompose.withCommand("run --no-deps --rm -e INSTALL_TB=true -e LOAD_DEMO=true tb1"); + dockerCompose.invokeCompose(); + + } finally { + try { + dockerCompose.withCommand("down -v"); + dockerCompose.invokeCompose(); + } catch (Exception e) {} + } + } + + @Override + protected void after() { + File tbLogsDir = new File("./target/tb-logs/"); + tbLogsDir.mkdirs(); + + dockerCompose.withCommand("run -d --rm --name tb-logs-container -v " + tbLogVolume + ":/root alpine tail -f /dev/null"); + dockerCompose.invokeDocker(); + + dockerCompose.withCommand("cp tb-logs-container:/root/. "+tbLogsDir.getAbsolutePath()); + dockerCompose.invokeDocker(); + + dockerCompose.withCommand("rm -f tb-logs-container"); + dockerCompose.invokeDocker(); + + dockerCompose.withCommand("volume rm -f " + postgresDataVolume + " " + tbLogVolume); + dockerCompose.invokeDocker(); + } + +}