Commit 4e81f9cc14dd6daa138dce046ccee5ba04b02cc1

Authored by Igor Kulikov
1 parent 00074144

Update black box tests infrastructure.

@@ -15,4 +15,6 @@ TB_VERSION=latest @@ -15,4 +15,6 @@ TB_VERSION=latest
15 15
16 DATABASE=postgres 16 DATABASE=postgres
17 17
  18 +LOAD_BALANCER_NAME=haproxy-certbot
  19 +
18 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" 20 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"
  1 +#
  2 +# Copyright © 2016-2018 The Thingsboard Authors
  3 +#
  4 +# Licensed under the Apache License, Version 2.0 (the "License");
  5 +# you may not use this file except in compliance with the License.
  6 +# You may obtain a copy of the License at
  7 +#
  8 +# http://www.apache.org/licenses/LICENSE-2.0
  9 +#
  10 +# Unless required by applicable law or agreed to in writing, software
  11 +# distributed under the License is distributed on an "AS IS" BASIS,
  12 +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +# See the License for the specific language governing permissions and
  14 +# limitations under the License.
  15 +#
  16 +
  17 +version: '2.2'
  18 +
  19 +services:
  20 + postgres:
  21 + volumes:
  22 + - postgres-db-volume:/var/lib/postgresql/data
  23 + tb1:
  24 + volumes:
  25 + - tb-log-volume:/var/log/thingsboard
  26 + tb2:
  27 + volumes:
  28 + - tb-log-volume:/var/log/thingsboard
  29 +
  30 +volumes:
  31 + postgres-db-volume:
  32 + external: true
  33 + name: ${POSTGRES_DATA_VOLUME}
  34 + tb-log-volume:
  35 + external: true
  36 + name: ${TB_LOG_VOLUME}
@@ -145,7 +145,7 @@ services: @@ -145,7 +145,7 @@ services:
145 - tb-web-ui.env 145 - tb-web-ui.env
146 haproxy: 146 haproxy:
147 restart: always 147 restart: always
148 - container_name: haproxy-certbot 148 + container_name: "${LOAD_BALANCER_NAME}"
149 image: xalauc/haproxy-certbot:1.7.9 149 image: xalauc/haproxy-certbot:1.7.9
150 volumes: 150 volumes:
151 - ./haproxy/config:/config 151 - ./haproxy/config:/config
@@ -24,7 +24,7 @@ @@ -24,7 +24,7 @@
24 <file>/var/log/thingsboard/${TB_HOST}/thingsboard.log</file> 24 <file>/var/log/thingsboard/${TB_HOST}/thingsboard.log</file>
25 <rollingPolicy 25 <rollingPolicy
26 class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> 26 class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
27 - <fileNamePattern>/var/log/thingsboard/thingsboard.%d{yyyy-MM-dd}.%i.log</fileNamePattern> 27 + <fileNamePattern>/var/log/thingsboard/${TB_HOST}/thingsboard.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
28 <maxFileSize>100MB</maxFileSize> 28 <maxFileSize>100MB</maxFileSize>
29 <maxHistory>30</maxHistory> 29 <maxHistory>30</maxHistory>
30 <totalSizeCap>3GB</totalSizeCap> 30 <totalSizeCap>3GB</totalSizeCap>
@@ -36,6 +36,7 @@ @@ -36,6 +36,7 @@
36 <main.dir>${basedir}/../..</main.dir> 36 <main.dir>${basedir}/../..</main.dir>
37 <blackBoxTests.skip>true</blackBoxTests.skip> 37 <blackBoxTests.skip>true</blackBoxTests.skip>
38 <testcontainers.version>1.9.1</testcontainers.version> 38 <testcontainers.version>1.9.1</testcontainers.version>
  39 + <zeroturnaround.version>1.10</zeroturnaround.version>
39 <java-websocket.version>1.3.9</java-websocket.version> 40 <java-websocket.version>1.3.9</java-websocket.version>
40 <httpclient.version>4.5.6</httpclient.version> 41 <httpclient.version>4.5.6</httpclient.version>
41 </properties> 42 </properties>
@@ -47,6 +48,11 @@ @@ -47,6 +48,11 @@
47 <version>${testcontainers.version}</version> 48 <version>${testcontainers.version}</version>
48 </dependency> 49 </dependency>
49 <dependency> 50 <dependency>
  51 + <groupId>org.zeroturnaround</groupId>
  52 + <artifactId>zt-exec</artifactId>
  53 + <version>${zeroturnaround.version}</version>
  54 + </dependency>
  55 + <dependency>
50 <groupId>org.java-websocket</groupId> 56 <groupId>org.java-websocket</groupId>
51 <artifactId>Java-WebSocket</artifactId> 57 <artifactId>Java-WebSocket</artifactId>
52 <version>${java-websocket.version}</version> 58 <version>${java-websocket.version}</version>
@@ -17,23 +17,43 @@ package org.thingsboard.server.msa; @@ -17,23 +17,43 @@ package org.thingsboard.server.msa;
17 17
18 import org.junit.ClassRule; 18 import org.junit.ClassRule;
19 import org.junit.extensions.cpsuite.ClasspathSuite; 19 import org.junit.extensions.cpsuite.ClasspathSuite;
  20 +import org.junit.rules.ExternalResource;
20 import org.junit.runner.RunWith; 21 import org.junit.runner.RunWith;
21 import org.testcontainers.containers.DockerComposeContainer; 22 import org.testcontainers.containers.DockerComposeContainer;
22 import org.testcontainers.containers.wait.strategy.Wait; 23 import org.testcontainers.containers.wait.strategy.Wait;
  24 +import org.testcontainers.utility.Base58;
23 25
24 import java.io.File; 26 import java.io.File;
25 import java.time.Duration; 27 import java.time.Duration;
  28 +import java.util.Arrays;
  29 +import java.util.HashMap;
  30 +import java.util.List;
  31 +import java.util.Map;
26 32
27 @RunWith(ClasspathSuite.class) 33 @RunWith(ClasspathSuite.class)
28 @ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*Test"}) 34 @ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*Test"})
29 public class ContainerTestSuite { 35 public class ContainerTestSuite {
30 36
  37 + private static DockerComposeContainer testContainer;
  38 +
  39 + @ClassRule
  40 + public static ThingsBoardDbInstaller installTb = new ThingsBoardDbInstaller();
  41 +
31 @ClassRule 42 @ClassRule
32 - public static DockerComposeContainer composeContainer = new DockerComposeContainer(  
33 - new File("./../../docker/docker-compose.yml"),  
34 - new File("./../../docker/docker-compose.postgres.yml"))  
35 - .withPull(false)  
36 - .withLocalCompose(true)  
37 - .withTailChildContainers(true)  
38 - .withExposedService("tb-web-ui1", 8080, Wait.forHttp("/login").withStartupTimeout(Duration.ofSeconds(120))); 43 + public static DockerComposeContainer getTestContainer() {
  44 + if (testContainer == null) {
  45 + testContainer = new DockerComposeContainer(
  46 + new File("./../../docker/docker-compose.yml"),
  47 + new File("./../../docker/docker-compose.postgres.yml"),
  48 + new File("./../../docker/docker-compose.postgres.volumes.yml"))
  49 + .withPull(false)
  50 + .withLocalCompose(true)
  51 + .withTailChildContainers(true)
  52 + .withEnv("POSTGRES_DATA_VOLUME", installTb.getPostgresDataVolume())
  53 + .withEnv("TB_LOG_VOLUME", installTb.getTbLogVolume())
  54 + .withEnv("LOAD_BALANCER_NAME", "")
  55 + .withExposedService("haproxy", 80, Wait.forHttp("/swagger-ui.html").withStartupTimeout(Duration.ofSeconds(120)));
  56 + }
  57 + return testContainer;
  58 + }
39 } 59 }
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package org.thingsboard.server.msa;
  18 +
  19 +
  20 +import com.google.common.base.Splitter;
  21 +import com.google.common.collect.Maps;
  22 +import lombok.extern.slf4j.Slf4j;
  23 +import org.apache.commons.lang3.SystemUtils;
  24 +import org.testcontainers.containers.ContainerLaunchException;
  25 +import org.testcontainers.utility.CommandLine;
  26 +import org.zeroturnaround.exec.InvalidExitValueException;
  27 +import org.zeroturnaround.exec.ProcessExecutor;
  28 +import org.zeroturnaround.exec.stream.slf4j.Slf4jStream;
  29 +
  30 +import java.io.File;
  31 +import java.util.HashMap;
  32 +import java.util.List;
  33 +import java.util.Map;
  34 +import java.util.Objects;
  35 +import java.util.stream.Stream;
  36 +
  37 +import static com.google.common.base.Preconditions.checkArgument;
  38 +import static com.google.common.base.Preconditions.checkNotNull;
  39 +import static java.util.stream.Collectors.joining;
  40 +
  41 +@Slf4j
  42 +public class DockerComposeExecutor {
  43 +
  44 + String ENV_PROJECT_NAME = "COMPOSE_PROJECT_NAME";
  45 + String ENV_COMPOSE_FILE = "COMPOSE_FILE";
  46 +
  47 + private static final String COMPOSE_EXECUTABLE = SystemUtils.IS_OS_WINDOWS ? "docker-compose.exe" : "docker-compose";
  48 + private static final String DOCKER_EXECUTABLE = SystemUtils.IS_OS_WINDOWS ? "docker.exe" : "docker";
  49 +
  50 + private final List<File> composeFiles;
  51 + private final String identifier;
  52 + private String cmd = "";
  53 + private Map<String, String> env = new HashMap<>();
  54 +
  55 + public DockerComposeExecutor(List<File> composeFiles, String identifier) {
  56 + validateFileList(composeFiles);
  57 + this.composeFiles = composeFiles;
  58 + this.identifier = identifier;
  59 + }
  60 +
  61 + public DockerComposeExecutor withCommand(String cmd) {
  62 + this.cmd = cmd;
  63 + return this;
  64 + }
  65 +
  66 + public DockerComposeExecutor withEnv(Map<String, String> env) {
  67 + this.env = env;
  68 + return this;
  69 + }
  70 +
  71 + public void invokeCompose() {
  72 + // bail out early
  73 + if (!CommandLine.executableExists(COMPOSE_EXECUTABLE)) {
  74 + throw new ContainerLaunchException("Local Docker Compose not found. Is " + COMPOSE_EXECUTABLE + " on the PATH?");
  75 + }
  76 + final Map<String, String> environment = Maps.newHashMap(env);
  77 + environment.put(ENV_PROJECT_NAME, identifier);
  78 + final Stream<String> absoluteDockerComposeFilePaths = composeFiles.stream().map(File::getAbsolutePath).map(Objects::toString);
  79 + final String composeFileEnvVariableValue = absoluteDockerComposeFilePaths.collect(joining(File.pathSeparator + ""));
  80 + log.debug("Set env COMPOSE_FILE={}", composeFileEnvVariableValue);
  81 + final File pwd = composeFiles.get(0).getAbsoluteFile().getParentFile().getAbsoluteFile();
  82 + environment.put(ENV_COMPOSE_FILE, composeFileEnvVariableValue);
  83 + log.info("Local Docker Compose is running command: {}", cmd);
  84 + final List<String> command = Splitter.onPattern(" ").omitEmptyStrings().splitToList(COMPOSE_EXECUTABLE + " " + cmd);
  85 + try {
  86 + new ProcessExecutor().command(command).redirectOutput(Slf4jStream.of(log).asInfo()).redirectError(Slf4jStream.of(log).asError()).environment(environment).directory(pwd).exitValueNormal().executeNoTimeout();
  87 + log.info("Docker Compose has finished running");
  88 + } catch (InvalidExitValueException e) {
  89 + throw new ContainerLaunchException("Local Docker Compose exited abnormally with code " + e.getExitValue() + " whilst running command: " + cmd);
  90 + } catch (Exception e) {
  91 + throw new ContainerLaunchException("Error running local Docker Compose command: " + cmd, e);
  92 + }
  93 + }
  94 +
  95 + public void invokeDocker() {
  96 + // bail out early
  97 + if (!CommandLine.executableExists(DOCKER_EXECUTABLE)) {
  98 + throw new ContainerLaunchException("Local Docker not found. Is " + DOCKER_EXECUTABLE + " on the PATH?");
  99 + }
  100 + final File pwd = composeFiles.get(0).getAbsoluteFile().getParentFile().getAbsoluteFile();
  101 + log.info("Local Docker is running command: {}", cmd);
  102 + final List<String> command = Splitter.onPattern(" ").omitEmptyStrings().splitToList(DOCKER_EXECUTABLE + " " + cmd);
  103 + try {
  104 + new ProcessExecutor().command(command).redirectOutput(Slf4jStream.of(log).asInfo()).redirectError(Slf4jStream.of(log).asError()).directory(pwd).exitValueNormal().executeNoTimeout();
  105 + log.info("Docker has finished running");
  106 + } catch (InvalidExitValueException e) {
  107 + throw new ContainerLaunchException("Local Docker exited abnormally with code " + e.getExitValue() + " whilst running command: " + cmd);
  108 + } catch (Exception e) {
  109 + throw new ContainerLaunchException("Error running local Docker command: " + cmd, e);
  110 + }
  111 + }
  112 +
  113 + void validateFileList(List<File> composeFiles) {
  114 + checkNotNull(composeFiles);
  115 + checkArgument(!composeFiles.isEmpty(), "No docker compose file have been provided");
  116 + }
  117 +
  118 +
  119 +}
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.msa;
  17 +
  18 +import org.junit.rules.ExternalResource;
  19 +import org.testcontainers.utility.Base58;
  20 +
  21 +import java.io.File;
  22 +import java.util.Arrays;
  23 +import java.util.HashMap;
  24 +import java.util.List;
  25 +import java.util.Map;
  26 +
  27 +public class ThingsBoardDbInstaller extends ExternalResource {
  28 +
  29 + private final static String POSTGRES_DATA_VOLUME = "tb-postgres-test-data-volume";
  30 + private final static String TB_LOG_VOLUME = "tb-log-test-volume";
  31 +
  32 + private final DockerComposeExecutor dockerCompose;
  33 +
  34 + private final String postgresDataVolume;
  35 + private final String tbLogVolume;
  36 +
  37 + public ThingsBoardDbInstaller() {
  38 + List<File> composeFiles = Arrays.asList(new File("./../../docker/docker-compose.yml"),
  39 + new File("./../../docker/docker-compose.postgres.yml"),
  40 + new File("./../../docker/docker-compose.postgres.volumes.yml"));
  41 +
  42 + String identifier = Base58.randomString(6).toLowerCase();
  43 + String project = identifier + Base58.randomString(6).toLowerCase();
  44 +
  45 + postgresDataVolume = project + "_" + POSTGRES_DATA_VOLUME;
  46 + tbLogVolume = project + "_" + TB_LOG_VOLUME;
  47 +
  48 + dockerCompose = new DockerComposeExecutor(composeFiles, project);
  49 +
  50 + Map<String, String> env = new HashMap<>();
  51 + env.put("POSTGRES_DATA_VOLUME", postgresDataVolume);
  52 + env.put("TB_LOG_VOLUME", tbLogVolume);
  53 + dockerCompose.withEnv(env);
  54 + }
  55 +
  56 + public String getPostgresDataVolume() {
  57 + return postgresDataVolume;
  58 + }
  59 +
  60 + public String getTbLogVolume() {
  61 + return tbLogVolume;
  62 + }
  63 +
  64 + @Override
  65 + protected void before() throws Throwable {
  66 + try {
  67 +
  68 + dockerCompose.withCommand("volume create " + postgresDataVolume);
  69 + dockerCompose.invokeDocker();
  70 +
  71 + dockerCompose.withCommand("volume create " + tbLogVolume);
  72 + dockerCompose.invokeDocker();
  73 +
  74 + dockerCompose.withCommand("up -d redis postgres");
  75 + dockerCompose.invokeCompose();
  76 +
  77 + dockerCompose.withCommand("run --no-deps --rm -e INSTALL_TB=true -e LOAD_DEMO=true tb1");
  78 + dockerCompose.invokeCompose();
  79 +
  80 + } finally {
  81 + try {
  82 + dockerCompose.withCommand("down -v");
  83 + dockerCompose.invokeCompose();
  84 + } catch (Exception e) {}
  85 + }
  86 + }
  87 +
  88 + @Override
  89 + protected void after() {
  90 + File tbLogsDir = new File("./target/tb-logs/");
  91 + tbLogsDir.mkdirs();
  92 +
  93 + dockerCompose.withCommand("run -d --rm --name tb-logs-container -v " + tbLogVolume + ":/root alpine tail -f /dev/null");
  94 + dockerCompose.invokeDocker();
  95 +
  96 + dockerCompose.withCommand("cp tb-logs-container:/root/. "+tbLogsDir.getAbsolutePath());
  97 + dockerCompose.invokeDocker();
  98 +
  99 + dockerCompose.withCommand("rm -f tb-logs-container");
  100 + dockerCompose.invokeDocker();
  101 +
  102 + dockerCompose.withCommand("volume rm -f " + postgresDataVolume + " " + tbLogVolume);
  103 + dockerCompose.invokeDocker();
  104 + }
  105 +
  106 +}