Преглед изворни кода

SONAR-8818 Add Hazelcast and its configuration (#1699)

* SONAR-8818 Introduce Hazelcast
tags/6.4-RC1
Eric Hartmann пре 7 година
родитељ
комит
5b80ef6227

+ 7
- 0
pom.xml Прегледај датотеку

@@ -74,6 +74,8 @@

<protobuf.version>3.0.0-beta-2</protobuf.version>

<hazelcast.version>3.8</hazelcast.version>

<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.min.version>3.2</maven.min.version>
<timestamp>${maven.build.timestamp}</timestamp>
@@ -660,6 +662,11 @@
<artifactId>jna</artifactId>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
<version>${hazelcast.version}</version>
</dependency>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>

+ 6
- 2
sonar-application/pom.xml Прегледај датотеку

@@ -31,6 +31,10 @@
<version>${project.version}</version>
</dependency>

<dependency>
<groupId>com.hazelcast</groupId>
<artifactId>hazelcast</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
@@ -236,8 +240,8 @@
<configuration>
<rules>
<requireFilesSize>
<minsize>111000000</minsize>
<maxsize>118000000</maxsize>
<minsize>126000000</minsize>
<maxsize>134000000</maxsize>
<files>
<file>${project.build.directory}/sonarqube-${project.version}.zip</file>
</files>

+ 10
- 1
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();
}

+ 8
- 3
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;
}

+ 103
- 0
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();
}
}
}

+ 65
- 0
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;
}
}

+ 185
- 0
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));
}
}
}

+ 29
- 2
sonar-application/src/test/java/org/sonar/application/AppLoggingTest.java Прегледај датотеку

@@ -31,6 +31,7 @@ import ch.qos.logback.core.encoder.Encoder;
import ch.qos.logback.core.joran.spi.ConsoleTarget;
import ch.qos.logback.core.rolling.RollingFileAppender;
import java.io.File;
import java.io.IOException;
import java.util.Iterator;
import java.util.Properties;
import org.junit.AfterClass;
@@ -39,9 +40,10 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.sonar.process.logging.LogbackHelper;
import org.slf4j.LoggerFactory;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
import org.sonar.process.logging.LogbackHelper;

import static org.assertj.core.api.Assertions.assertThat;
import static org.slf4j.Logger.ROOT_LOGGER_NAME;
@@ -230,7 +232,7 @@ public class AppLoggingTest {

expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("log level ERROR in property sonar.log.level is not a supported value (allowed levels are [TRACE, DEBUG, INFO])");
underTest.configure(props);
}

@@ -244,6 +246,31 @@ public class AppLoggingTest {
underTest.configure(props);
}

@Test
public void no_info_log_from_hazelcast() throws IOException {
props.set(ClusterParameters.ENABLED.getName(), "true");
new ClusterProperties(props).populateProps(props);
underTest.configure(props);

assertThat(
LoggerFactory.getLogger("com.hazelcast").isInfoEnabled()
).isEqualTo(false);
}

@Test
public void configure_logging_for_hazelcast() throws IOException {
props.set(ClusterParameters.ENABLED.getName(), "true");
props.set(ClusterParameters.HAZELCAST_LOG_LEVEL.getName(), "INFO");
underTest.configure(props);

assertThat(
LoggerFactory.getLogger("com.hazelcast").isInfoEnabled()
).isEqualTo(true);
assertThat(
LoggerFactory.getLogger("com.hazelcast").isDebugEnabled()
).isEqualTo(false);
}

private void emulateRunFromSonarScript() {
props.set("sonar.wrapped", "true");
}

+ 8
- 4
sonar-application/src/test/java/org/sonar/application/AppTest.java Прегледај датотеку

@@ -87,7 +87,8 @@ public class AppTest {
public void start_all_processes_if_cluster_mode_is_disabled() throws Exception {
Props props = initDefaultProps();
Monitor monitor = mock(Monitor.class);
App app = new App(props.rawProperties(), properties -> props, monitor, checkFsConfigOnReload, javaCommandFactory);
Cluster cluster = mock(Cluster.class);
App app = new App(props.rawProperties(), properties -> props, monitor, checkFsConfigOnReload, javaCommandFactory, cluster);
app.start();

ArgumentCaptor<Supplier<List<JavaCommand>>> argument = newJavaCommandArgumentCaptor();
@@ -146,7 +147,8 @@ public class AppTest {
// setup an App that emulate reloading of props returning different configuration
Iterator<Props> propsIterator = Arrays.asList(initialProps, props).iterator();
Monitor monitor = mock(Monitor.class);
App app = new App(initialProps.rawProperties(), properties -> propsIterator.next(), monitor, checkFsConfigOnReload, javaCommandFactory);
Cluster cluster = mock(Cluster.class);
App app = new App(initialProps.rawProperties(), properties -> propsIterator.next(), monitor, checkFsConfigOnReload, javaCommandFactory, cluster);
// start App and capture the JavaCommand supplier it provides to the Monitor
app.start();
Supplier<List<JavaCommand>> supplier = captureJavaCommandsSupplier(monitor);
@@ -175,7 +177,8 @@ public class AppTest {
// setup an App that emulate reloading of props returning different configuration
Iterator<Props> propsIterator = Arrays.asList(initialProps, props).iterator();
Monitor monitor = mock(Monitor.class);
App app = new App(initialProps.rawProperties(), properties -> propsIterator.next(), monitor, checkFsConfigOnReload, javaCommandFactory);
Cluster cluster = mock(Cluster.class);
App app = new App(initialProps.rawProperties(), properties -> propsIterator.next(), monitor, checkFsConfigOnReload, javaCommandFactory, cluster);
// start App and capture the JavaCommand supplier it provides to the Monitor
app.start();
Supplier<List<JavaCommand>> supplier = captureJavaCommandsSupplier(monitor);
@@ -213,7 +216,8 @@ public class AppTest {

private List<JavaCommand> start(Props props) throws Exception {
Monitor monitor = mock(Monitor.class);
App app = new App(props.rawProperties(), properties -> props, monitor, checkFsConfigOnReload, javaCommandFactory);
Cluster cluster = mock(Cluster.class);
App app = new App(props.rawProperties(), properties -> props, monitor, checkFsConfigOnReload, javaCommandFactory, cluster);
app.start();
ArgumentCaptor<Supplier<List<JavaCommand>>> argument = newJavaCommandArgumentCaptor();
verify(monitor).start(argument.capture());

+ 181
- 0
sonar-application/src/test/java/org/sonar/application/ClusterPropertiesTest.java Прегледај датотеку

@@ -0,0 +1,181 @@
/*
* 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.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Properties;
import java.util.stream.Stream;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.process.Props;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.MapEntry.entry;

public class ClusterPropertiesTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();

@Test
public void test_default_values() throws Exception {
ClusterProperties props = new ClusterProperties(new Props(new Properties()));

assertThat(props.getInterfaces())
.isEqualTo(Collections.emptyList());
assertThat(props.isPortAutoincrement())
.isEqualTo(false);
assertThat(props.getPort())
.isEqualTo(9003);
assertThat(props.isEnabled())
.isEqualTo(false);
assertThat(props.getMembers())
.isEqualTo(Collections.emptyList());
assertThat(props.getName())
.isEqualTo("");
assertThat(props.getLogLevel())
.isEqualTo("WARN");
}


@Test
public void test_port_parameter() {
Props props = new Props(new Properties());
props.set(ClusterParameters.ENABLED.getName(), "true");
props.set(ClusterParameters.NAME.getName(), "sonarqube");

Stream.of("-50", "0", "65536", "128563").forEach(
port -> {
props.set(ClusterParameters.PORT.getName(), port);

ClusterProperties clusterProperties = new ClusterProperties(props);
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
String.format("Cluster port have been set to %s which is outside the range [1-65535].", port)
);
clusterProperties.validate();

}
);
}

@Test
public void test_interfaces_parameter() {
Props props = new Props(new Properties());
props.set(ClusterParameters.ENABLED.getName(), "true");
props.set(ClusterParameters.NAME.getName(), "sonarqube");
props.set(ClusterParameters.INTERFACES.getName(), "8.8.8.8"); // This IP belongs to Google

ClusterProperties clusterProperties = new ClusterProperties(props);
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
String.format("Interface %s is not available on this machine.", "8.8.8.8")
);
clusterProperties.validate();
}

@Test
public void test_missing_name() {
Props props = new Props(new Properties());
props.set(ClusterParameters.ENABLED.getName(), "true");
props.set(ClusterParameters.NAME.getName(), "");
ClusterProperties clusterProperties = new ClusterProperties(props);
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage(
String.format("Cluster have been enabled but a %s has not been defined.",
ClusterParameters.NAME.getName())
);
clusterProperties.validate();
}

@Test
public void validate_does_not_fail_if_cluster_enabled_and_name_specified() {
Props props = new Props(new Properties());
props.set(ClusterParameters.ENABLED.getName(), "true");
props.set(ClusterParameters.NAME.getName(), "sonarqube");
ClusterProperties clusterProperties = new ClusterProperties(props);
clusterProperties.validate();
}

@Test
public void test_members() {
Props props = new Props(new Properties());
props.set(ClusterParameters.ENABLED.getName(), "true");

assertThat(
retrieveMembers(props)
).isEqualTo(
Collections.emptyList()
);

props.set(ClusterParameters.MEMBERS.getName(), "192.168.1.1");
assertThat(
retrieveMembers(props)
).isEqualTo(
Arrays.asList("192.168.1.1:" + ClusterParameters.PORT.getDefaultValue())
);

props.set(ClusterParameters.MEMBERS.getName(), "192.168.1.2:5501");
assertThat(
retrieveMembers(props)
).containsExactlyInAnyOrder(
"192.168.1.2:5501"
);

props.set(ClusterParameters.MEMBERS.getName(), "192.168.1.2:5501,192.168.1.1");
assertThat(
retrieveMembers(props)
).containsExactlyInAnyOrder(
"192.168.1.2:5501", "192.168.1.1:" + ClusterParameters.PORT.getDefaultValue()
);
}

@Test
public void test_cluster_properties() {
Props props = new Props(new Properties());
props.set(ClusterParameters.ENABLED.getName(), "true");
props.set(ClusterParameters.MEMBERS.getName(), "192.168.1.1");
props.set(ClusterParameters.INTERFACES.getName(), "192.168.1.30");
props.set(ClusterParameters.PORT.getName(), "9003");
props.set(ClusterParameters.HAZELCAST_LOG_LEVEL.getName(), "INFO");
props.set(ClusterParameters.PORT_AUTOINCREMENT.getName(), "false");
props.set(ClusterParameters.NAME.getName(), "sonarqube");

ClusterProperties clusterProperties = new ClusterProperties(props);
clusterProperties.populateProps(props);

assertThat(props.rawProperties()).containsOnly(
entry(ClusterParameters.HAZELCAST_LOG_LEVEL.getName(), "INFO"),
entry(ClusterParameters.PORT.getName(), "9003"),
entry(ClusterParameters.ENABLED.getName(), "true"),
entry(ClusterParameters.INTERFACES.getName(), "192.168.1.30"),
entry(ClusterParameters.MEMBERS.getName(), "192.168.1.1:" + ClusterParameters.PORT.getDefaultValue()),
entry(ClusterParameters.NAME.getName(), "sonarqube"),
entry(ClusterParameters.PORT_AUTOINCREMENT.getName(), "false")
);
}

private List<String> retrieveMembers(Props props) {
return new ClusterProperties(props).getMembers();
}
}

+ 164
- 0
sonar-application/src/test/java/org/sonar/application/ClusterTest.java Прегледај датотеку

@@ -0,0 +1,164 @@
/*
* 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.hazelcast.core.HazelcastException;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.NetworkInterface;
import java.net.ServerSocket;
import java.net.SocketException;
import java.util.Enumeration;
import java.util.Properties;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.process.Props;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;

public class ClusterTest {
@Rule
public ExpectedException expectedException = ExpectedException.none();

@Test
public void test_cluster() throws Exception {
Properties properties = new Properties();
properties.put(ClusterParameters.ENABLED.getName(), "true");
ClusterProperties clusterProperties = toClusterProperties(properties);

try (Cluster cluster = new Cluster(clusterProperties)) {
assertThat(
cluster.isActive()
).isEqualTo(true);
}

properties.put(ClusterParameters.ENABLED.getName(), "false");
clusterProperties = toClusterProperties(properties);
try (Cluster cluster = new Cluster(clusterProperties)) {
assertThat(
cluster.isActive()
).isEqualTo(false);
}
}

@Test
public void test_interface() throws Exception {
String ipAddress = findIPv4Address().getHostAddress();
int port = findAvailablePort();

Properties properties = new Properties();
properties.put(ClusterParameters.INTERFACES.getName(), ipAddress);
properties.put(ClusterParameters.PORT.getName(), Integer.toString(port));
properties.put(ClusterParameters.ENABLED.getName(), "true");
ClusterProperties clusterProperties = toClusterProperties(properties);

try (Cluster cluster = new Cluster(clusterProperties)) {
assertThat(
cluster.hazelcastInstance.getConfig().getNetworkConfig().getInterfaces().isEnabled()
).isEqualTo(true);
assertThat(
cluster.hazelcastInstance.getConfig().getNetworkConfig().getInterfaces().getInterfaces()
).containsExactly(ipAddress);
InetSocketAddress localSocket = (InetSocketAddress) cluster.hazelcastInstance.getLocalEndpoint().getSocketAddress();
assertThat(
(localSocket).getPort()
).isEqualTo(port);
assertThat(
(localSocket).getAddress().getHostAddress()
).isEqualTo(ipAddress);
}
}

@Test
public void test_with_already_used_port() throws IOException {
InetAddress ipAddress = findIPv4Address();

Cluster cluster = null;

try (ServerSocket socket = new ServerSocket(0, 50, ipAddress)) {
Properties properties = new Properties();
properties.put(ClusterParameters.INTERFACES.getName(), ipAddress.getHostAddress());
properties.put(ClusterParameters.PORT.getName(), Integer.toString(socket.getLocalPort()));
properties.put(ClusterParameters.ENABLED.getName(), "true");
ClusterProperties clusterProperties = toClusterProperties(properties);

expectedException.expect(HazelcastException.class);
cluster = new Cluster(clusterProperties);
} finally {
if (cluster != null) {
cluster.close();
}
}
}

@Test
public void test_adding_port_to_members() throws Exception {
InetAddress ipAddress = findIPv4Address();

Properties properties = new Properties();
properties.put(ClusterParameters.ENABLED.getName(), "true");
properties.put(ClusterParameters.MEMBERS.getName(), ipAddress.getHostAddress());
ClusterProperties clusterProperties = toClusterProperties(properties);

try (Cluster cluster = new Cluster(clusterProperties)) {
assertThat(
cluster.hazelcastInstance.getConfig().getNetworkConfig().getJoin().getTcpIpConfig().getMembers()
).containsExactly(ipAddress.getHostAddress() + ":9003");
}
}

private ClusterProperties toClusterProperties(Properties properties) {
return new ClusterProperties(new Props(properties));
}

private InetAddress findIPv4Address() throws SocketException {
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();

while (interfaces.hasMoreElements()) {
NetworkInterface networkInterface = interfaces.nextElement();
if (!networkInterface.isVirtual()
&& !networkInterface.isLoopback()
&& !networkInterface.getName().startsWith("docker")) {
Enumeration<InetAddress> ips = networkInterface.getInetAddresses();
while (ips.hasMoreElements()) {
InetAddress ip = ips.nextElement();

if (ip instanceof Inet4Address) {
return ip;
}
}
}
}

fail("Missing IPv4 address");
return null;
}

private int findAvailablePort() throws IOException {
try (ServerSocket ignored = new ServerSocket(0)) {
return ignored.getLocalPort();
}
}
}

Loading…
Откажи
Сачувај