diff options
13 files changed, 286 insertions, 97 deletions
diff --git a/server/sonar-process-monitor/pom.xml b/server/sonar-process-monitor/pom.xml index f4f7bac9633..6f094b9b7e2 100644 --- a/server/sonar-process-monitor/pom.xml +++ b/server/sonar-process-monitor/pom.xml @@ -13,6 +13,15 @@ <artifactId>sonar-process-monitor</artifactId> <name>SonarQube :: Process Monitor</name> + <properties> + <!-- + version as stored in JAR and displayed in webapp. It is + overridden on Travis when replacing SNAPSHOT version by + build unique version, for instance "6.3.0.12345". + --> + <buildVersion>${project.version}</buildVersion> + </properties> + <dependencies> <dependency> <groupId>${project.groupId}</groupId> @@ -32,7 +41,6 @@ <artifactId>hazelcast</artifactId> </dependency> - <dependency> <groupId>com.google.code.findbugs</groupId> <artifactId>jsr305</artifactId> @@ -60,4 +68,14 @@ <scope>test</scope> </dependency> </dependencies> + + <build> + <resources> + <resource> + <!-- Used to resolve variables in file sonarqube-version.txt --> + <directory>src/main/resources</directory> + <filtering>true</filtering> + </resource> + </resources> + </build> </project> diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/AppState.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppState.java index 0f536a8aa10..212fa7b33f7 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/application/AppState.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppState.java @@ -49,6 +49,8 @@ public interface AppState extends AutoCloseable { void reset(); + void registerSonarQubeVersion(String sonarqubeVersion); + @Override void close(); } diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateImpl.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateImpl.java index 71177205b41..3e748b70f37 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateImpl.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppStateImpl.java @@ -61,6 +61,11 @@ public class AppStateImpl implements AppState { } @Override + public void registerSonarQubeVersion(String sonarqubeVersion) { + // Nothing to do on non clustered version + } + + @Override public void close() { // nothing to do } diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/AppStateClusterImpl.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/AppStateClusterImpl.java index 39a1dc370f9..3f25a987408 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/AppStateClusterImpl.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/cluster/AppStateClusterImpl.java @@ -38,14 +38,16 @@ import java.util.EnumMap; import java.util.List; import java.util.Map; import javax.annotation.Nonnull; -import org.sonar.application.config.AppSettings; import org.sonar.application.AppState; import org.sonar.application.AppStateListener; +import org.sonar.application.config.AppSettings; import org.sonar.process.ProcessId; public class AppStateClusterImpl implements AppState { - static final String OPERATIONAL_PROCESSES = "operational_processes"; - private static final String LEADER = "leader"; + static final String OPERATIONAL_PROCESSES = "OPERATIONAL_PROCESSES"; + static final String LEADER = "LEADER"; + + static final String SONARQUBE_VERSION = "SONARQUBE_VERSION"; private final List<AppStateListener> listeners = new ArrayList<>(); private final ReplicatedMap<ClusterProcess, Boolean> operationalProcesses; @@ -73,7 +75,9 @@ public class AppStateClusterImpl implements AppState { // Configure the network instance NetworkConfig netConfig = hzConfig.getNetworkConfig(); - netConfig.setPort(clusterProperties.getPort()); + netConfig + .setPort(clusterProperties.getPort()) + .setReuseAddress(true); if (!clusterProperties.getNetworkInterfaces().isEmpty()) { netConfig.getInterfaces() @@ -171,6 +175,30 @@ public class AppStateClusterImpl implements AppState { } } + @Override + public void registerSonarQubeVersion(String sonarqubeVersion) { + IAtomicReference<String> sqVersion = hzInstance.getAtomicReference(SONARQUBE_VERSION); + if (sqVersion.get() == null) { + ILock lock = hzInstance.getLock(SONARQUBE_VERSION); + lock.lock(); + try { + if (sqVersion.get() == null) { + sqVersion.set(sonarqubeVersion); + } + } finally { + lock.unlock(); + } + } + + String clusterVersion = sqVersion.get(); + if (!sqVersion.get().equals(sonarqubeVersion)) { + hzInstance.shutdown(); + throw new IllegalStateException( + String.format("The local version %s is not the same as the cluster %s", sonarqubeVersion, clusterVersion) + ); + } + } + private String getLocalUuid() { return hzInstance.getLocalEndpoint().getUuid(); } diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/config/SonarQubeVersionHelper.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/SonarQubeVersionHelper.java new file mode 100644 index 00000000000..11b23e9bea8 --- /dev/null +++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/config/SonarQubeVersionHelper.java @@ -0,0 +1,59 @@ +/* + * 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.config; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; + +import static java.lang.String.format; + +public class SonarQubeVersionHelper { + private static final String SONARQUBE_VERSION_PATH = "/sonarqube-version.txt"; + + private static String sonarqubeVersion; + + private SonarQubeVersionHelper() { + // only static methods + } + + public static String getSonarqubeVersion() { + if (sonarqubeVersion == null) { + loadVersion(); + } + return sonarqubeVersion; + } + + private static synchronized void loadVersion() { + try { + try (BufferedReader in = new BufferedReader( + new InputStreamReader( + SonarQubeVersionHelper.class.getResourceAsStream(SONARQUBE_VERSION_PATH), + StandardCharsets.UTF_8 + ))) { + sonarqubeVersion = in.readLine(); + } + } catch (IOException e) { + throw new IllegalStateException(format("Cannot load %s from classpath", SONARQUBE_VERSION_PATH), e); + } + } +} diff --git a/server/sonar-process-monitor/src/main/resources/sonarqube-version.txt b/server/sonar-process-monitor/src/main/resources/sonarqube-version.txt new file mode 100644 index 00000000000..6b7ce460f25 --- /dev/null +++ b/server/sonar-process-monitor/src/main/resources/sonarqube-version.txt @@ -0,0 +1 @@ +${buildVersion} diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/TestAppState.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/TestAppState.java index e69624382eb..40d4196cdd8 100644 --- a/server/sonar-process-monitor/src/test/java/org/sonar/application/TestAppState.java +++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/TestAppState.java @@ -71,6 +71,11 @@ public class TestAppState implements AppState { } @Override + public void registerSonarQubeVersion(String sonarqubeVersion) { + // nothing to do + } + + @Override public void close() { // nothing to do } diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/AppStateClusterImplTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/AppStateClusterImplTest.java index 1aaf3bcd6f4..98d44cb0a4a 100644 --- a/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/AppStateClusterImplTest.java +++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/AppStateClusterImplTest.java @@ -40,6 +40,8 @@ import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.sonar.application.cluster.AppStateClusterImpl.OPERATIONAL_PROCESSES; +import static org.sonar.application.cluster.AppStateClusterImpl.SONARQUBE_VERSION; +import static org.sonar.application.cluster.HazelcastTestHelper.createHazelcastClient; public class AppStateClusterImplTest { @@ -95,7 +97,7 @@ public class AppStateClusterImplTest { try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) { appStateCluster.addListener(listener); - HazelcastInstance hzInstance = HazelcastHelper.createHazelcastClient(appStateCluster); + HazelcastInstance hzInstance = createHazelcastClient(appStateCluster); String uuid = UUID.randomUUID().toString(); ReplicatedMap<ClusterProcess, Boolean> replicatedMap = hzInstance.getReplicatedMap(OPERATIONAL_PROCESSES); // process is not up yet --> no events are sent to listeners @@ -116,6 +118,41 @@ public class AppStateClusterImplTest { } } + @Test + public void registerSonarQubeVersion_publishes_version_on_first_call() { + TestAppSettings settings = newClusterSettings(); + + try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) { + appStateCluster.registerSonarQubeVersion("6.4.1.5"); + + HazelcastInstance hzInstance = createHazelcastClient(appStateCluster); + assertThat(hzInstance.getAtomicReference(SONARQUBE_VERSION).get()) + .isNotNull() + .isInstanceOf(String.class) + .isEqualTo("6.4.1.5"); + } + } + + @Test + public void registerSonarQubeVersion_throws_ISE_if_initial_version_is_different() throws Exception { + // Now launch an instance that try to be part of the hzInstance cluster + TestAppSettings settings = new TestAppSettings(); + settings.set(ProcessProperties.CLUSTER_ENABLED, "true"); + settings.set(ProcessProperties.CLUSTER_NAME, "sonarqube"); + + + try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) { + // Register first version + appStateCluster.registerSonarQubeVersion("1.0.0"); + + expectedException.expect(IllegalStateException.class); + expectedException.expectMessage("The local version 2.0.0 is not the same as the cluster 1.0.0"); + + // Registering a second different version must trigger an exception + appStateCluster.registerSonarQubeVersion("2.0.0"); + } + } + private static TestAppSettings newClusterSettings() { TestAppSettings settings = new TestAppSettings(); settings.set(ProcessProperties.CLUSTER_ENABLED, "true"); diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/ClusterProcessTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/ClusterProcessTest.java new file mode 100644 index 00000000000..5472d575746 --- /dev/null +++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/ClusterProcessTest.java @@ -0,0 +1,40 @@ +/* + * 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.cluster; + +import org.junit.Test; +import org.sonar.process.ProcessId; + +import static org.assertj.core.api.Assertions.assertThat; + +public class ClusterProcessTest { + @Test + public void test_equality() { + ClusterProcess clusterProcess = new ClusterProcess("A", ProcessId.WEB_SERVER); + + assertThat(clusterProcess) + .isNotEqualTo(null) + .isEqualTo(clusterProcess) + .isNotEqualTo(new ClusterProcess("B", ProcessId.WEB_SERVER)) + .isNotEqualTo(new ClusterProcess("A", ProcessId.ELASTICSEARCH)) + .isEqualTo(new ClusterProcess("A", ProcessId.WEB_SERVER)); + } +} diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/HazelcastHelper.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/HazelcastHelper.java deleted file mode 100644 index 9cfe2559e67..00000000000 --- a/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/HazelcastHelper.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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.cluster; - -import com.hazelcast.client.HazelcastClient; -import com.hazelcast.client.config.ClientConfig; -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 java.net.InetSocketAddress; -import java.util.Collection; - -public class HazelcastHelper { - static HazelcastInstance createHazelcastNode(AppStateClusterImpl appStateCluster) { - Config hzConfig = new Config() - .setInstanceName(appStateCluster.hzInstance.getName() + "_1"); - - // Configure the network instance - NetworkConfig netConfig = hzConfig.getNetworkConfig(); - netConfig.setPort(9003).setPortAutoIncrement(true); - Collection<String> interfaces = appStateCluster.hzInstance.getConfig().getNetworkConfig().getInterfaces().getInterfaces(); - if (!interfaces.isEmpty()) { - netConfig.getInterfaces().addInterface( - interfaces.iterator().next() - ); - } - - // Only allowing TCP/IP configuration - JoinConfig joinConfig = netConfig.getJoin(); - joinConfig.getAwsConfig().setEnabled(false); - joinConfig.getMulticastConfig().setEnabled(false); - joinConfig.getTcpIpConfig().setEnabled(true); - - InetSocketAddress socketAddress = (InetSocketAddress) appStateCluster.hzInstance.getLocalEndpoint().getSocketAddress(); - joinConfig.getTcpIpConfig().addMember( - String.format("%s:%d", - socketAddress.getHostString(), - socketAddress.getPort() - ) - ); - - // 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); - - return Hazelcast.newHazelcastInstance(hzConfig); - } - - static HazelcastInstance createHazelcastClient(AppStateClusterImpl appStateCluster) { - ClientConfig clientConfig = new ClientConfig(); - InetSocketAddress socketAddress = (InetSocketAddress) appStateCluster.hzInstance.getLocalEndpoint().getSocketAddress(); - - clientConfig.getNetworkConfig().getAddresses().add( - String.format("%s:%d", - socketAddress.getHostString(), - socketAddress.getPort() - )); - clientConfig.getGroupConfig().setName(appStateCluster.hzInstance.getConfig().getGroupConfig().getName()); - return HazelcastClient.newHazelcastClient(clientConfig); - } -} diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/HazelcastTestHelper.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/HazelcastTestHelper.java new file mode 100644 index 00000000000..106390dc2d1 --- /dev/null +++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/cluster/HazelcastTestHelper.java @@ -0,0 +1,42 @@ +/* + * 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.cluster; + +import com.hazelcast.client.HazelcastClient; +import com.hazelcast.client.config.ClientConfig; +import com.hazelcast.core.HazelcastInstance; +import java.net.InetSocketAddress; + +public class HazelcastTestHelper { + + static HazelcastInstance createHazelcastClient(AppStateClusterImpl appStateCluster) { + ClientConfig clientConfig = new ClientConfig(); + InetSocketAddress socketAddress = (InetSocketAddress) appStateCluster.hzInstance.getLocalEndpoint().getSocketAddress(); + + clientConfig.getNetworkConfig().getAddresses().add( + String.format("%s:%d", + socketAddress.getHostString(), + socketAddress.getPort() + )); + clientConfig.getGroupConfig().setName(appStateCluster.hzInstance.getConfig().getGroupConfig().getName()); + return HazelcastClient.newHazelcastClient(clientConfig); + } +} diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/config/SonarQubeVersionHelperTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/SonarQubeVersionHelperTest.java new file mode 100644 index 00000000000..93c1aa065c0 --- /dev/null +++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/config/SonarQubeVersionHelperTest.java @@ -0,0 +1,40 @@ +/* + * 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.config; + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +public class SonarQubeVersionHelperTest { + @Test + public void getSonarQubeVersion_must_not_return_an_empty_string() { + assertThat(SonarQubeVersionHelper.getSonarqubeVersion()).isNotEmpty(); + } + + @Test + public void getSonarQubeVersion_must_always_return_same_value() { + String sonarqubeVersion = SonarQubeVersionHelper.getSonarqubeVersion(); + for (int i = 0; i < 3; i++) { + assertThat(SonarQubeVersionHelper.getSonarqubeVersion()).isEqualTo(sonarqubeVersion); + } + } +} 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 efc4a89d234..3f9d0470f0c 100644 --- a/sonar-application/src/main/java/org/sonar/application/App.java +++ b/sonar-application/src/main/java/org/sonar/application/App.java @@ -31,6 +31,8 @@ import org.sonar.application.process.StopRequestWatcher; import org.sonar.application.process.StopRequestWatcherImpl; import org.sonar.process.SystemExit; +import static org.sonar.application.config.SonarQubeVersionHelper.getSonarqubeVersion; + public class App { private final SystemExit systemExit = new SystemExit(); @@ -45,6 +47,7 @@ public class App { AppFileSystem fileSystem = new AppFileSystem(settings); try (AppState appState = new AppStateFactory(settings).create()) { + appState.registerSonarQubeVersion(getSonarqubeVersion()); AppReloader appReloader = new AppReloaderImpl(settingsLoader, fileSystem, appState, logging); JavaCommandFactory javaCommandFactory = new JavaCommandFactoryImpl(settings); fileSystem.reset(); |