aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-application/src/main/java/org/sonar/application
diff options
context:
space:
mode:
authorEric Hartmann <hartmann.eric@gmail.Com>2017-02-26 14:47:10 +0100
committerGitHub <noreply@github.com>2017-02-26 14:47:10 +0100
commit5b80ef622737b940407647f9431b7a7942861b05 (patch)
tree9152572464f38061e29986fa2cfe38a90e2f0d1c /sonar-application/src/main/java/org/sonar/application
parentee94866db117108ef272d33213fea15c095a7894 (diff)
downloadsonarqube-5b80ef622737b940407647f9431b7a7942861b05.tar.gz
sonarqube-5b80ef622737b940407647f9431b7a7942861b05.zip
SONAR-8818 Add Hazelcast and its configuration (#1699)
* SONAR-8818 Introduce Hazelcast
Diffstat (limited to 'sonar-application/src/main/java/org/sonar/application')
-rw-r--r--sonar-application/src/main/java/org/sonar/application/App.java11
-rw-r--r--sonar-application/src/main/java/org/sonar/application/AppLogging.java11
-rw-r--r--sonar-application/src/main/java/org/sonar/application/Cluster.java103
-rw-r--r--sonar-application/src/main/java/org/sonar/application/ClusterParameters.java65
-rw-r--r--sonar-application/src/main/java/org/sonar/application/ClusterProperties.java185
5 files changed, 371 insertions, 4 deletions
diff --git a/sonar-application/src/main/java/org/sonar/application/App.java b/sonar-application/src/main/java/org/sonar/application/App.java
index 01c965f822c..c1a7e61d566 100644
--- a/sonar-application/src/main/java/org/sonar/application/App.java
+++ b/sonar-application/src/main/java/org/sonar/application/App.java
@@ -50,6 +50,7 @@ public class App implements Stoppable {
private final JavaCommandFactory javaCommandFactory;
private final Monitor monitor;
private final Supplier<List<JavaCommand>> javaCommandSupplier;
+ private final Cluster cluster;
private App(Properties commandLineArguments) {
this.commandLineArguments = commandLineArguments;
@@ -59,8 +60,12 @@ public class App implements Stoppable {
AppFileSystem appFileSystem = new AppFileSystem(props);
appFileSystem.verifyProps();
+ ClusterProperties clusterProperties = new ClusterProperties(props);
+ clusterProperties.populateProps(props);
AppLogging logging = new AppLogging();
logging.configure(props);
+ clusterProperties.validate();
+ this.cluster = new Cluster(clusterProperties);
// used by orchestrator
boolean watchForHardStop = props.valueAsBoolean(ProcessProperties.ENABLE_STOP_COMMAND, false);
@@ -76,12 +81,13 @@ public class App implements Stoppable {
@VisibleForTesting
App(Properties commandLineArguments, Function<Properties, Props> propsSupplier, Monitor monitor, CheckFSConfigOnReload checkFsConfigOnReload,
- JavaCommandFactory javaCommandFactory) {
+ JavaCommandFactory javaCommandFactory, Cluster cluster) {
this.commandLineArguments = commandLineArguments;
this.propsSupplier = propsSupplier;
this.javaCommandFactory = javaCommandFactory;
this.monitor = monitor;
this.javaCommandSupplier = new ReloadableCommandSupplier(propsSupplier.apply(commandLineArguments), checkFsConfigOnReload);
+ this.cluster = cluster;
}
public void start() throws InterruptedException {
@@ -109,6 +115,9 @@ public class App implements Stoppable {
@Override
public void stopAsync() {
+ if (cluster != null) {
+ cluster.close();
+ }
if (monitor != null) {
monitor.stop();
}
diff --git a/sonar-application/src/main/java/org/sonar/application/AppLogging.java b/sonar-application/src/main/java/org/sonar/application/AppLogging.java
index d3600dffee1..7ea96ff62bb 100644
--- a/sonar-application/src/main/java/org/sonar/application/AppLogging.java
+++ b/sonar-application/src/main/java/org/sonar/application/AppLogging.java
@@ -19,15 +19,16 @@
*/
package org.sonar.application;
+import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.FileAppender;
-import org.sonar.process.logging.LogLevelConfig;
-import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.ProcessId;
import org.sonar.process.Props;
+import org.sonar.process.logging.LogLevelConfig;
+import org.sonar.process.logging.LogbackHelper;
import org.sonar.process.logging.RootLoggerConfig;
import static org.slf4j.Logger.ROOT_LOGGER_NAME;
@@ -128,7 +129,11 @@ class AppLogging {
} else {
configureWithWrapperWritingToFile(ctx);
}
- helper.apply(LogLevelConfig.newBuilder().rootLevelFor(ProcessId.APP).build(), props);
+ helper.apply(
+ LogLevelConfig.newBuilder()
+ .rootLevelFor(ProcessId.APP)
+ .immutableLevel("com.hazelcast", Level.toLevel(props.value(ClusterParameters.HAZELCAST_LOG_LEVEL.getName())))
+ .build(), props);
return ctx;
}
diff --git a/sonar-application/src/main/java/org/sonar/application/Cluster.java b/sonar-application/src/main/java/org/sonar/application/Cluster.java
new file mode 100644
index 00000000000..621af696320
--- /dev/null
+++ b/sonar-application/src/main/java/org/sonar/application/Cluster.java
@@ -0,0 +1,103 @@
+/*
+ * 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.sonar.application;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.hazelcast.cluster.ClusterState;
+import com.hazelcast.config.Config;
+import com.hazelcast.config.JoinConfig;
+import com.hazelcast.config.NetworkConfig;
+import com.hazelcast.core.Hazelcast;
+import com.hazelcast.core.HazelcastInstance;
+import javax.annotation.Nonnull;
+
+/**
+ * Manager for the cluster communication between Main Processes
+ */
+public class Cluster implements AutoCloseable {
+ /**
+ * The Hazelcast instance.
+ */
+ @VisibleForTesting
+ final HazelcastInstance hazelcastInstance;
+
+ /**
+ * Instantiates a new Cluster.
+ *
+ * @param clusterProperties The properties of the cluster read from configuration
+ */
+ protected Cluster(@Nonnull ClusterProperties clusterProperties) {
+ if (clusterProperties.isEnabled()) {
+ Config hzConfig = new Config();
+ // Configure the network instance
+ NetworkConfig netConfig = hzConfig.getNetworkConfig();
+ netConfig.setPort(clusterProperties.getPort())
+ .setPortAutoIncrement(clusterProperties.isPortAutoincrement());
+
+ if (!clusterProperties.getInterfaces().isEmpty()) {
+ netConfig.getInterfaces()
+ .setEnabled(true)
+ .setInterfaces(clusterProperties.getInterfaces());
+ }
+
+ // Only allowing TCP/IP configuration
+ JoinConfig joinConfig = netConfig.getJoin();
+ joinConfig.getAwsConfig().setEnabled(false);
+ joinConfig.getMulticastConfig().setEnabled(false);
+ joinConfig.getTcpIpConfig().setEnabled(true);
+ joinConfig.getTcpIpConfig().setMembers(clusterProperties.getMembers());
+
+ // Tweak HazelCast configuration
+ hzConfig
+ // Increase the number of tries
+ .setProperty("hazelcast.tcp.join.port.try.count", "10")
+ // Don't bind on all interfaces
+ .setProperty("hazelcast.socket.bind.any", "false")
+ // Don't phone home
+ .setProperty("hazelcast.phone.home.enabled", "false")
+ // Use slf4j for logging
+ .setProperty("hazelcast.logging.type", "slf4j");
+
+ // We are not using the partition group of Hazelcast, so disabling it
+ hzConfig.getPartitionGroupConfig().setEnabled(false);
+
+ hazelcastInstance = Hazelcast.newHazelcastInstance(hzConfig);
+ } else {
+ hazelcastInstance = null;
+ }
+ }
+
+ /**
+ * Is the cluster active
+ *
+ * @return the boolean
+ */
+ public boolean isActive() {
+ return hazelcastInstance != null && hazelcastInstance.getCluster().getClusterState() == ClusterState.ACTIVE;
+ }
+
+ @Override
+ public void close() {
+ if (hazelcastInstance != null) {
+ hazelcastInstance.shutdown();
+ }
+ }
+}
diff --git a/sonar-application/src/main/java/org/sonar/application/ClusterParameters.java b/sonar-application/src/main/java/org/sonar/application/ClusterParameters.java
new file mode 100644
index 00000000000..c0e15e0ca1b
--- /dev/null
+++ b/sonar-application/src/main/java/org/sonar/application/ClusterParameters.java
@@ -0,0 +1,65 @@
+/*
+ * 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.sonar.application;
+
+import javax.annotation.Nonnull;
+import org.apache.commons.lang.StringUtils;
+
+enum ClusterParameters {
+ ENABLED("sonar.cluster.enabled", Boolean.FALSE.toString()),
+ MEMBERS("sonar.cluster.members", ""),
+ PORT("sonar.cluster.port", Integer.toString(9003)),
+ PORT_AUTOINCREMENT("sonar.cluster.port_autoincrement", Boolean.FALSE.toString()),
+ INTERFACES("sonar.cluster.interfaces", ""),
+ NAME("sonar.cluster.name", ""),
+ HAZELCAST_LOG_LEVEL("sonar.log.level.app.hazelcast", "WARN");
+
+ private final String name;
+ private final String defaultValue;
+
+ ClusterParameters(@Nonnull String name, @Nonnull String defaultValue) {
+ this.name = name;
+ this.defaultValue = defaultValue;
+ }
+
+ String getName() {
+ return name;
+ }
+
+ String getDefaultValue() {
+ return defaultValue;
+ }
+
+ boolean getDefaultValueAsBoolean() {
+ return "true".equalsIgnoreCase(defaultValue);
+ }
+
+ Integer getDefaultValueAsInt() {
+ if (StringUtils.isNotEmpty(defaultValue)) {
+ try {
+ return Integer.parseInt(defaultValue);
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException("Default value of property " + name + " is not an integer: " + defaultValue, e);
+ }
+ }
+ return null;
+ }
+}
diff --git a/sonar-application/src/main/java/org/sonar/application/ClusterProperties.java b/sonar-application/src/main/java/org/sonar/application/ClusterProperties.java
new file mode 100644
index 00000000000..1b86838ac5c
--- /dev/null
+++ b/sonar-application/src/main/java/org/sonar/application/ClusterProperties.java
@@ -0,0 +1,185 @@
+/*
+ * 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.sonar.application;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.Props;
+
+/**
+ * Properties of the cluster configuration
+ */
+final class ClusterProperties {
+ private static final Logger LOGGER = LoggerFactory.getLogger(ClusterProperties.class);
+
+ private final int port;
+ private final boolean enabled;
+ private final boolean portAutoincrement;
+ private final List<String> members;
+ private final List<String> interfaces;
+ private final String name;
+ private final String logLevel;
+
+ ClusterProperties(@Nonnull Props props) {
+ port = props.valueAsInt(ClusterParameters.PORT.getName(), ClusterParameters.PORT.getDefaultValueAsInt());
+ enabled = props.valueAsBoolean(ClusterParameters.ENABLED.getName(), ClusterParameters.ENABLED.getDefaultValueAsBoolean());
+ portAutoincrement = props.valueAsBoolean(ClusterParameters.PORT_AUTOINCREMENT.getName(), ClusterParameters.PORT_AUTOINCREMENT.getDefaultValueAsBoolean());
+ interfaces = extractInterfaces(
+ props.value(ClusterParameters.INTERFACES.getName(), ClusterParameters.INTERFACES.getDefaultValue())
+ );
+ name = props.value(ClusterParameters.NAME.getName(), ClusterParameters.NAME.getDefaultValue());
+ logLevel = props.value(ClusterParameters.HAZELCAST_LOG_LEVEL.getName(), ClusterParameters.HAZELCAST_LOG_LEVEL.getDefaultValue());
+ members = extractMembers(
+ props.value(ClusterParameters.MEMBERS.getName(), ClusterParameters.MEMBERS.getDefaultValue())
+ );
+ }
+
+ void populateProps(@Nonnull Props props) {
+ props.set(ClusterParameters.PORT.getName(), Integer.toString(port));
+ props.set(ClusterParameters.ENABLED.getName(), Boolean.toString(enabled));
+ props.set(ClusterParameters.PORT_AUTOINCREMENT.getName(), Boolean.toString(portAutoincrement));
+ props.set(ClusterParameters.INTERFACES.getName(), interfaces.stream().collect(Collectors.joining(",")));
+ props.set(ClusterParameters.NAME.getName(), name);
+ props.set(ClusterParameters.HAZELCAST_LOG_LEVEL.getName(), logLevel);
+ props.set(ClusterParameters.MEMBERS.getName(), members.stream().collect(Collectors.joining(",")));
+ }
+
+ int getPort() {
+ return port;
+ }
+
+ boolean isEnabled() {
+ return enabled;
+ }
+
+ boolean isPortAutoincrement() {
+ return portAutoincrement;
+ }
+
+ List<String> getMembers() {
+ return members;
+ }
+
+ List<String> getInterfaces() {
+ return interfaces;
+ }
+
+ String getName() {
+ return name;
+ }
+
+ String getLogLevel() {
+ return logLevel;
+ }
+
+ void validate() {
+ if (!enabled) {
+ return;
+ }
+ // Name is required in cluster mode
+ checkArgument(
+ StringUtils.isNotEmpty(name),
+ "Cluster have been enabled but a %s has not been defined.",
+ ClusterParameters.NAME.getName()
+ );
+
+ // Test validity of port
+ checkArgument(
+ port > 0 && port < 65_536,
+ "Cluster port have been set to %d which is outside the range [1-65535].",
+ port
+ );
+
+ // Test the interfaces parameter
+ try {
+ List<String> localInterfaces = findAllLocalIPs();
+
+ interfaces.forEach(
+ inet -> checkArgument(
+ StringUtils.isEmpty(inet) || localInterfaces.contains(inet),
+ "Interface %s is not available on this machine.",
+ inet
+ )
+ );
+ } catch (SocketException e) {
+ LOGGER.warn("Unable to retrieve network interfaces. Interfaces won't be checked", e);
+ }
+ }
+
+ private static List<String> extractMembers(final String members) {
+ List<String> result = new ArrayList<>();
+ for (String member : members.split(",")) {
+ if (StringUtils.isNotEmpty(member)) {
+ if (!member.contains(":")) {
+ result.add(
+ String.format("%s:%s", member, ClusterParameters.PORT.getDefaultValue())
+ );
+ } else {
+ result.add(member);
+ }
+ }
+ }
+ return result;
+ }
+
+ private static List<String> extractInterfaces(final String interfaces) {
+ List<String> result = new ArrayList<>();
+ for (String iface : interfaces.split(",")) {
+ if (StringUtils.isNotEmpty(iface)) {
+ result.add(iface);
+ }
+ }
+ return result;
+ }
+
+ private static List<String> findAllLocalIPs() throws SocketException {
+ Enumeration<NetworkInterface> netInterfaces = NetworkInterface.getNetworkInterfaces();
+ List<String> localInterfaces = new ArrayList<>();
+
+ while (netInterfaces.hasMoreElements()) {
+ NetworkInterface networkInterface = netInterfaces.nextElement();
+ Enumeration<InetAddress> ips = networkInterface.getInetAddresses();
+ while (ips.hasMoreElements()) {
+ InetAddress ip = ips.nextElement();
+ localInterfaces.add(ip.getHostAddress());
+ }
+ }
+ return localInterfaces;
+ }
+
+ private static void checkArgument(boolean expression,
+ @Nullable String messageTemplate,
+ @Nullable Object... args) {
+ if (!expression) {
+ throw new IllegalArgumentException(String.format(messageTemplate, args));
+ }
+ }
+}