]> source.dussan.org Git - sonarqube.git/commitdiff
Add ITs on cluster
authorEric Hartmann <hartmann.eric@gmail.com>
Wed, 17 May 2017 15:36:19 +0000 (17:36 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 5 Sep 2017 12:24:12 +0000 (14:24 +0200)
server/sonar-process/src/main/java/org/sonar/process/ProcessProperties.java
tests/src/test/java/org/sonarqube/tests/Category5Suite.java
tests/src/test/java/org/sonarqube/tests/cluster/Cluster.java [new file with mode: 0644]
tests/src/test/java/org/sonarqube/tests/cluster/DataCenterEdition.java [new file with mode: 0644]
tests/src/test/java/org/sonarqube/tests/cluster/DataCenterEditionTest.java [new file with mode: 0644]
tests/src/test/java/org/sonarqube/tests/cluster/StartupLogWatcherImpl.java [new file with mode: 0644]

index d93697144a0f6f88569745738b2a535905be293c..e9f6d46c0f6d9ae4904d9d046f7fb7ec91c69db0 100644 (file)
@@ -71,8 +71,9 @@ public class ProcessProperties {
   public static final String SEARCH_INITIAL_STATE_TIMEOUT = "sonar.search.initialStateTimeout";
 
   public static final String WEB_JAVA_OPTS = "sonar.web.javaOpts";
-
   public static final String WEB_JAVA_ADDITIONAL_OPTS = "sonar.web.javaAdditionalOpts";
+  public static final String WEB_PORT = "sonar.web.port";
+
   public static final String CE_JAVA_OPTS = "sonar.ce.javaOpts";
   public static final String CE_JAVA_ADDITIONAL_OPTS = "sonar.ce.javaAdditionalOpts";
 
index 19ed5d87d37b35b137f99ced23fe8ff4667bd98b..fd12d951546caabcf87b03919216352b81be00e7 100644 (file)
@@ -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 (file)
index 0000000..d6f0e10
--- /dev/null
@@ -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 (file)
index 0000000..07007e9
--- /dev/null
@@ -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 (file)
index 0000000..e7b7634
--- /dev/null
@@ -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 (file)
index 0000000..170ddc1
--- /dev/null
@@ -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);
+  }
+}