diff options
author | Eric Hartmann <hartmann.eric@gmail.com> | 2017-05-17 17:36:19 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2017-09-05 14:24:12 +0200 |
commit | c7ae1857b15ac53d7442cdf33685036455b8deb4 (patch) | |
tree | bc781869e388cb9203e636e2b375f2d98fa015db /tests | |
parent | 958fa89f09a58082139b440269704c4e4955cdd7 (diff) | |
download | sonarqube-c7ae1857b15ac53d7442cdf33685036455b8deb4.tar.gz sonarqube-c7ae1857b15ac53d7442cdf33685036455b8deb4.zip |
Add ITs on cluster
Diffstat (limited to 'tests')
5 files changed, 432 insertions, 1 deletions
diff --git a/tests/src/test/java/org/sonarqube/tests/Category5Suite.java b/tests/src/test/java/org/sonarqube/tests/Category5Suite.java index 19ed5d87d37..fd12d951546 100644 --- a/tests/src/test/java/org/sonarqube/tests/Category5Suite.java +++ b/tests/src/test/java/org/sonarqube/tests/Category5Suite.java @@ -22,6 +22,7 @@ package org.sonarqube.tests; import org.junit.runner.RunWith; import org.junit.runners.Suite; import org.sonarqube.tests.ce.CeWorkersTest; +import org.sonarqube.tests.cluster.DataCenterEditionTest; import org.sonarqube.tests.qualityProfile.ActiveRuleEsResilienceTest; import org.sonarqube.tests.qualityProfile.BuiltInQualityProfilesNotificationTest; import org.sonarqube.tests.rule.RuleEsResilienceTest; @@ -62,7 +63,8 @@ import org.sonarqube.tests.user.UserEsResilienceTest; TelemetryUploadTest.class, TelemetryOptOutTest.class, // ce - CeWorkersTest.class + CeWorkersTest.class, + DataCenterEditionTest.class }) public class Category5Suite { diff --git a/tests/src/test/java/org/sonarqube/tests/cluster/Cluster.java b/tests/src/test/java/org/sonarqube/tests/cluster/Cluster.java new file mode 100644 index 00000000000..d6f0e10c813 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/cluster/Cluster.java @@ -0,0 +1,312 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonarqube.tests.cluster; + +import com.google.common.net.HostAndPort; +import com.sonar.orchestrator.Orchestrator; +import com.sonar.orchestrator.OrchestratorBuilder; +import com.sonar.orchestrator.util.NetworkUtils; +import java.net.Inet4Address; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Enumeration; +import java.util.List; +import java.util.Properties; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import static org.sonarqube.tests.cluster.Cluster.NodeType.CE; +import static org.sonarqube.tests.cluster.Cluster.NodeType.ES; +import static org.sonarqube.tests.cluster.Cluster.NodeType.WEB; + +public class Cluster { + + protected static final String CLUSTER_ENABLED = "sonar.cluster.enabled"; + protected static final String CLUSTER_CE_DISABLED = "sonar.cluster.ce.disabled"; + protected static final String CLUSTER_SEARCH_DISABLED = "sonar.cluster.search.disabled"; + protected static final String CLUSTER_SEARCH_HOSTS = "sonar.cluster.search.hosts"; + protected static final String CLUSTER_WEB_DISABLED = "sonar.cluster.web.disabled"; + protected static final String CLUSTER_HOSTS = "sonar.cluster.hosts"; + protected static final String CLUSTER_PORT = "sonar.cluster.port"; + protected static final String CLUSTER_NETWORK_INTERFACES = "sonar.cluster.networkInterfaces"; + protected static final String CLUSTER_NAME = "sonar.cluster.name"; + + protected static final String SEARCH_HOST = "sonar.search.host"; + protected static final String SEARCH_PORT = "sonar.search.port"; + protected static final String SEARCH_JAVA_OPTS = "sonar.search.javaOpts"; + + protected static final String WEB_JAVA_OPTS = "sonar.web.javaOpts"; + protected static final String WEB_PORT = "sonar.web.port"; + + protected static final String CE_JAVA_OPTS = "sonar.ce.javaOpts"; + + public enum NodeType { + ES, CE, WEB; + + public static final EnumSet<NodeType> ALL = EnumSet.allOf(NodeType.class); + } + + private final List<Node> nodes; + private final ForkJoinPool forkJoinPool = new ForkJoinPool(5); + + private Cluster(List<Node> nodes) { + this.nodes = nodes; + assignPorts(); + completeNodesConfiguration(); + buildOrchestrators(); + } + + public void start() throws ExecutionException, InterruptedException { + forkJoinPool.submit( + () -> nodes.parallelStream().forEach( + node -> node.getOrchestrator().start() + ) + ).get(); + } + + public void stop() throws ExecutionException, InterruptedException { + forkJoinPool.submit( + () -> nodes.parallelStream().forEach( + node -> node.getOrchestrator().stop() + ) + ).get(); + } + + public void stopAll(Predicate<Node> predicate) throws ExecutionException, InterruptedException { + forkJoinPool.submit( + () -> nodes.parallelStream() + .filter(predicate) + .forEach(n -> n.getOrchestrator().stop()) + ).get(); + } + + public void startAll(Predicate<Node> predicate) throws ExecutionException, InterruptedException { + forkJoinPool.submit( + () -> nodes.parallelStream() + .filter(predicate) + .forEach(n -> n.getOrchestrator().start()) + ).get(); + } + + public List<Node> getNodes() { + return Collections.unmodifiableList(nodes); + } + + private void assignPorts() { + nodes.stream().forEach( + node -> { + node.setHzPort(NetworkUtils.getNextAvailablePort(getNonloopbackIPv4Address())); + if (node.getTypes().contains(ES)) { + node.setEsPort(NetworkUtils.getNextAvailablePort(getNonloopbackIPv4Address())); + } + if (node.getTypes().contains(WEB)) { + node.setWebPort(NetworkUtils.getNextAvailablePort(getNonloopbackIPv4Address())); + } + } + ); + } + + public static InetAddress getNonloopbackIPv4Address() { + try { + Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces(); + for (NetworkInterface networkInterface : Collections.list(nets)) { + if (!networkInterface.isLoopback() && networkInterface.isUp() && !isBlackListed(networkInterface)) { + Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses(); + while (inetAddresses.hasMoreElements()) { + InetAddress inetAddress = inetAddresses.nextElement(); + if (inetAddress instanceof Inet4Address) { + return inetAddress; + } + } + } + } + } catch (SocketException se) { + throw new RuntimeException("Cannot find a non loopback card required for tests", se); + } + throw new RuntimeException("Cannot find a non loopback card required for tests"); + } + + private static boolean isBlackListed(NetworkInterface networkInterface) { + return networkInterface.getName().startsWith("docker") || + networkInterface.getName().startsWith("vboxnet"); + } + + private void completeNodesConfiguration() { + String inet = getNonloopbackIPv4Address().getHostAddress(); + String clusterHosts = nodes.stream() + .map(node -> HostAndPort.fromParts(inet, node.getHzPort()).toString()) + .collect(Collectors.joining(",")); + String elasticsearchHosts = nodes.stream() + .filter(node -> node.getTypes().contains(ES)) + .map(node -> HostAndPort.fromParts(inet, node.getEsPort()).toString()) + .collect(Collectors.joining(",")); + + nodes.stream().forEach( + node -> { + node.addProperty(CLUSTER_NETWORK_INTERFACES, inet); + node.addProperty(CLUSTER_HOSTS, clusterHosts); + node.addProperty(CLUSTER_PORT, Integer.toString(node.getHzPort())); + node.addProperty(CLUSTER_SEARCH_HOSTS, elasticsearchHosts); + node.addProperty(SEARCH_PORT, Integer.toString(node.getEsPort())); + node.addProperty(SEARCH_HOST, inet); + node.addProperty(WEB_PORT, Integer.toString(node.getWebPort())); + + if (!node.getTypes().contains(CE)) { + node.addProperty(CLUSTER_CE_DISABLED, "true"); + } + if (!node.getTypes().contains(WEB)) { + node.addProperty(CLUSTER_WEB_DISABLED, "true"); + } + if (!node.getTypes().contains(ES)) { + node.addProperty(CLUSTER_SEARCH_DISABLED, "true"); + } + } + ); + } + + private void buildOrchestrators() { + nodes.stream().limit(1).forEach( + node -> buildOrchestrator(node, false) + ); + nodes.stream().skip(1).forEach( + node -> buildOrchestrator(node, true) + ); + } + + private void buildOrchestrator(Node node, boolean keepDatabase) { + OrchestratorBuilder builder = Orchestrator.builderEnv() + .setOrchestratorProperty("orchestrator.keepDatabase", Boolean.toString(keepDatabase)) + .setStartupLogWatcher(new StartupLogWatcherImpl()); + + node.getProperties().entrySet().stream().forEach( + e -> builder.setServerProperty((String) e.getKey(), (String) e.getValue()) + ); + + node.setOrchestrator(builder.build()); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private final List<Node> nodes = new ArrayList<>(); + + public Cluster build() { + return new Cluster(nodes); + } + + public Builder addNode(EnumSet<NodeType> types) { + nodes.add(new Node(types)); + return this; + } + } + + /** + * A cluster node + */ + public static class Node { + private final EnumSet<NodeType> types; + private int webPort; + private int esPort; + private int hzPort; + private Orchestrator orchestrator = null; + private Properties properties = new Properties(); + + public Node(EnumSet<NodeType> types) { + this.types = types; + + // Default properties + properties.setProperty(CLUSTER_ENABLED, "true"); + properties.setProperty(CLUSTER_NAME, "sonarqube"); + properties.setProperty(CE_JAVA_OPTS, "-Xmx256m"); + properties.setProperty(WEB_JAVA_OPTS, "-Xmx256m"); + properties.setProperty(SEARCH_JAVA_OPTS, "-Xmx256m -Xms256m " + + "-XX:+UseConcMarkSweepGC " + + "-XX:CMSInitiatingOccupancyFraction=75 " + + "-XX:+UseCMSInitiatingOccupancyOnly " + + "-XX:+AlwaysPreTouch " + + "-server " + + "-Xss1m " + + "-Djava.awt.headless=true " + + "-Dfile.encoding=UTF-8 " + + "-Djna.nosys=true " + + "-Djdk.io.permissionsUseCanonicalPath=true " + + "-Dio.netty.noUnsafe=true " + + "-Dio.netty.noKeySetOptimization=true " + + "-Dio.netty.recycler.maxCapacityPerThread=0 " + + "-Dlog4j.shutdownHookEnabled=false " + + "-Dlog4j2.disable.jmx=true " + + "-Dlog4j.skipJansi=true " + + "-XX:+HeapDumpOnOutOfMemoryError"); + } + + public Properties getProperties() { + return properties; + } + + public Orchestrator getOrchestrator() { + return orchestrator; + } + + private void setOrchestrator(Orchestrator orchestrator) { + this.orchestrator = orchestrator; + } + + public EnumSet<NodeType> getTypes() { + return types; + } + + public int getWebPort() { + return webPort; + } + + public int getEsPort() { + return esPort; + } + + public int getHzPort() { + return hzPort; + } + + private void setWebPort(int webPort) { + this.webPort = webPort; + } + + private void setEsPort(int esPort) { + this.esPort = esPort; + } + + private void setHzPort(int hzPort) { + this.hzPort = hzPort; + } + + private void addProperty(String key, String value) { + properties.setProperty(key, value); + } + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/cluster/DataCenterEdition.java b/tests/src/test/java/org/sonarqube/tests/cluster/DataCenterEdition.java new file mode 100644 index 00000000000..07007e97c67 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/cluster/DataCenterEdition.java @@ -0,0 +1,51 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonarqube.tests.cluster; + +import java.util.concurrent.ExecutionException; + +import static java.util.EnumSet.of; +import static org.sonarqube.tests.cluster.Cluster.NodeType.CE; +import static org.sonarqube.tests.cluster.Cluster.NodeType.ES; +import static org.sonarqube.tests.cluster.Cluster.NodeType.WEB; + +public class DataCenterEdition { + + private final Cluster cluster; + + public DataCenterEdition() { + cluster = Cluster.builder() + .addNode(of(ES)) + .addNode(of(ES)) + .addNode(of(ES)) + .addNode(of(WEB, CE)) + .addNode(of(WEB, CE)) + .build(); + } + + public void stop() throws ExecutionException, InterruptedException { + cluster.stop(); + } + + public void start() throws ExecutionException, InterruptedException { + cluster.start(); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/cluster/DataCenterEditionTest.java b/tests/src/test/java/org/sonarqube/tests/cluster/DataCenterEditionTest.java new file mode 100644 index 00000000000..e7b7634fcf3 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/cluster/DataCenterEditionTest.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonarqube.tests.cluster; + +import java.util.concurrent.ExecutionException; +import org.junit.Test; + +public class DataCenterEditionTest { + @Test + public void launch() throws ExecutionException, InterruptedException { + DataCenterEdition dce = new DataCenterEdition(); + dce.start(); + dce.stop(); + } +} diff --git a/tests/src/test/java/org/sonarqube/tests/cluster/StartupLogWatcherImpl.java b/tests/src/test/java/org/sonarqube/tests/cluster/StartupLogWatcherImpl.java new file mode 100644 index 00000000000..170ddc16872 --- /dev/null +++ b/tests/src/test/java/org/sonarqube/tests/cluster/StartupLogWatcherImpl.java @@ -0,0 +1,33 @@ +/* + * SonarQube + * Copyright (C) 2009-2017 SonarSource SA + * mailto:info AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package org.sonarqube.tests.cluster; + + +import com.sonar.orchestrator.server.StartupLogWatcher; + +public class StartupLogWatcherImpl implements StartupLogWatcher { + private static final String STARTUP_EXPECTED_MESSAGE = "SonarQube is up"; + + @Override + public boolean isStarted(String logLine) { + return logLine.contains(STARTUP_EXPECTED_MESSAGE); + } +} |