]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9738 Fail if the cluster name differs from node to node
authorDaniel Schwarz <daniel.schwarz@sonarsource.com>
Tue, 22 Aug 2017 09:10:25 +0000 (11:10 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 5 Sep 2017 12:24:13 +0000 (14:24 +0200)
19 files changed:
server/sonar-ce/src/main/java/org/sonar/ce/cluster/HazelcastClientWrapperImpl.java
server/sonar-ce/src/test/java/org/sonar/ce/cluster/HazelcastClientWrapperImplTest.java
server/sonar-ce/src/test/java/org/sonar/ce/cluster/HazelcastTestHelper.java
server/sonar-ce/src/test/java/org/sonar/ce/container/ComputeEngineContainerImplTest.java
server/sonar-main/src/main/java/org/sonar/application/AppState.java
server/sonar-main/src/main/java/org/sonar/application/AppStateImpl.java
server/sonar-main/src/main/java/org/sonar/application/cluster/AppStateClusterImpl.java
server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterProperties.java
server/sonar-main/src/main/java/org/sonar/application/cluster/HazelcastCluster.java
server/sonar-main/src/test/java/org/sonar/application/TestAppState.java
server/sonar-main/src/test/java/org/sonar/application/cluster/AppStateClusterImplTest.java
server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterPropertiesTest.java
server/sonar-main/src/test/java/org/sonar/application/cluster/HazelcastClusterTest.java
server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java
server/sonar-process/src/main/java/org/sonar/process/cluster/ClusterObjectKeys.java
server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsTest.java
sonar-application/src/main/java/org/sonar/application/App.java
tests/src/test/java/org/sonarqube/tests/cluster/Cluster.java
tests/src/test/java/org/sonarqube/tests/cluster/DataCenterEditionTest.java

index b913387c84a7a2661ae1c7f6b538d8a466e27bbf..a4b40580540d8b7f066e231633424a49bbfb224c 100644 (file)
@@ -41,6 +41,7 @@ import static org.sonar.process.cluster.ClusterObjectKeys.CLIENT_UUIDS;
  */
 public class HazelcastClientWrapperImpl implements Startable, HazelcastClientWrapper {
 
+  private static final String HAZELCAST_CLUSTER_NAME = "sonarqube";
   private final ClientConfig hzConfig;
 
   @VisibleForTesting
@@ -48,15 +49,13 @@ public class HazelcastClientWrapperImpl implements Startable, HazelcastClientWra
 
   public HazelcastClientWrapperImpl(Configuration config) {
     boolean clusterEnabled = config.getBoolean(ProcessProperties.CLUSTER_ENABLED).orElse(false);
-    String clusterName = config.get(ProcessProperties.CLUSTER_NAME).orElse(null);
     String clusterLocalEndPoint = config.get(ProcessProperties.CLUSTER_LOCALENDPOINT).orElse(null);
 
     checkState(clusterEnabled, "Cluster is not enabled");
     checkState(isNotEmpty(clusterLocalEndPoint), "LocalEndPoint have not been set");
-    checkState(isNotEmpty(clusterName), "sonar.cluster.name is missing");
 
     hzConfig = new ClientConfig();
-    hzConfig.getGroupConfig().setName(clusterName);
+    hzConfig.getGroupConfig().setName(HAZELCAST_CLUSTER_NAME);
     hzConfig.getNetworkConfig().addAddress(clusterLocalEndPoint);
 
     // Tweak HazelCast configuration
index 3c483a80f3e9ca1e9812e1e2add532e3064f4649..58be787c69f61722404b57bdf0a6d7b224df8be3 100644 (file)
@@ -70,9 +70,9 @@ public class HazelcastClientWrapperImplTest {
   @BeforeClass
   public static void setupHazelcastClusterAndHazelcastClient() {
     int port = NetworkUtils.getNextAvailablePort(InetAddress.getLoopbackAddress());
-    hzCluster = HazelcastTestHelper.createHazelcastCluster("cluster_with_client", port);
+    hzCluster = HazelcastTestHelper.createHazelcastCluster(port);
 
-    MapSettings settings = createClusterSettings("cluster_with_client", "localhost:" + port);
+    MapSettings settings = createClusterSettings("localhost:" + port);
     hzClient = new HazelcastClientWrapperImpl(settings.asConfig());
   }
 
@@ -92,7 +92,7 @@ public class HazelcastClientWrapperImplTest {
 
   @Test
   public void start_throws_ISE_if_LOCALENDPOINT_is_incorrect() {
-    MapSettings settings = createClusterSettings("sonarqube", "\u4563\u1432\u1564");
+    MapSettings settings = createClusterSettings("\u4563\u1432\u1564");
     HazelcastClientWrapperImpl hzClient = new HazelcastClientWrapperImpl(settings.asConfig());
 
     expectedException.expect(IllegalStateException.class);
@@ -103,7 +103,7 @@ public class HazelcastClientWrapperImplTest {
 
   @Test
   public void constructor_throws_ISE_if_LOCALENDPOINT_is_empty() {
-    MapSettings settings = createClusterSettings("sonarqube", "");
+    MapSettings settings = createClusterSettings("");
 
     expectedException.expect(IllegalStateException.class);
     expectedException.expectMessage("LocalEndPoint have not been set");
@@ -113,7 +113,7 @@ public class HazelcastClientWrapperImplTest {
 
   @Test
   public void constructor_throws_ISE_if_CLUSTER_ENABLED_is_false() {
-    MapSettings settings = createClusterSettings("sonarqube", "localhost:9003");
+    MapSettings settings = createClusterSettings("localhost:9003");
     settings.setProperty(ProcessProperties.CLUSTER_ENABLED, false);
 
     expectedException.expect(IllegalStateException.class);
@@ -124,7 +124,7 @@ public class HazelcastClientWrapperImplTest {
 
   @Test
   public void constructor_throws_ISE_if_missing_CLUSTER_ENABLED() {
-    MapSettings settings = createClusterSettings("sonarqube", "localhost:9003");
+    MapSettings settings = createClusterSettings("localhost:9003");
     settings.removeProperty(ProcessProperties.CLUSTER_ENABLED);
 
     expectedException.expect(IllegalStateException.class);
@@ -133,20 +133,9 @@ public class HazelcastClientWrapperImplTest {
     new HazelcastClientWrapperImpl(settings.asConfig());
   }
 
-  @Test
-  public void constructor_throws_ISE_if_missing_CLUSTER_NAME() {
-    MapSettings settings = createClusterSettings("sonarqube", "localhost:9003");
-    settings.removeProperty(ProcessProperties.CLUSTER_NAME);
-
-    expectedException.expect(IllegalStateException.class);
-    expectedException.expectMessage("sonar.cluster.name is missing");
-
-    new HazelcastClientWrapperImpl(settings.asConfig());
-  }
-
   @Test
   public void constructor_throws_ISE_if_missing_CLUSTER_LOCALENDPOINT() {
-    MapSettings settings = createClusterSettings("sonarqube", "localhost:9003");
+    MapSettings settings = createClusterSettings("localhost:9003");
     settings.removeProperty(ProcessProperties.CLUSTER_LOCALENDPOINT);
 
     expectedException.expect(IllegalStateException.class);
@@ -159,8 +148,8 @@ public class HazelcastClientWrapperImplTest {
   public void client_must_connect_to_hazelcast() throws InterruptedException {
     int port = NetworkUtils.getNextAvailablePort(InetAddress.getLoopbackAddress());
     // Launch a fake Hazelcast instance
-    HazelcastInstance hzInstance = HazelcastTestHelper.createHazelcastCluster("client_must_connect_to_hazelcast", port);
-    MapSettings settings = createClusterSettings("client_must_connect_to_hazelcast", "localhost:" + port);
+    HazelcastInstance hzInstance = HazelcastTestHelper.createHazelcastCluster(port);
+    MapSettings settings = createClusterSettings("localhost:" + port);
 
     HazelcastClientWrapperImpl hazelcastClientWrapperImpl = new HazelcastClientWrapperImpl(settings.asConfig());
     ClientListenerImpl clientListener = new ClientListenerImpl();
@@ -177,8 +166,8 @@ public class HazelcastClientWrapperImplTest {
 
   @Test
   public void client_must_be_able_to_set_ReplicatedMap_objects() throws InterruptedException {
+    hzClient.start();
     try {
-      hzClient.start();
 
       Set<String> setTest = new HashSet<>();
       setTest.addAll(
@@ -196,8 +185,8 @@ public class HazelcastClientWrapperImplTest {
 
   @Test
   public void client_must_be_able_to_retrieve_Set_objects() {
+    hzClient.start();
     try {
-      hzClient.start();
 
       // Set
       Set<String> setTest = new HashSet<>();
@@ -211,8 +200,8 @@ public class HazelcastClientWrapperImplTest {
 
   @Test
   public void client_must_be_able_to_retrieve_List_objects() {
+    hzClient.start();
     try {
-      hzClient.start();
 
       // List
       List<String> listTest = Arrays.asList("1", "2");
@@ -225,9 +214,8 @@ public class HazelcastClientWrapperImplTest {
 
   @Test
   public void client_must_be_able_to_retrieve_Map_objects() {
+    hzClient.start();
     try {
-      hzClient.start();
-
       Map mapTest = new HashMap<>();
       mapTest.put("a", Arrays.asList("123", "456"));
       hzCluster.getMap("TEST3").putAll(mapTest);
@@ -240,8 +228,8 @@ public class HazelcastClientWrapperImplTest {
 
   @Test
   public void configuration_tweaks_of_hazelcast_must_be_present() {
+    hzClient.start();
     try {
-      hzClient.start();
       HazelcastClientInstanceImpl realClient = ((HazelcastClientProxy) hzClient.hzInstance).client;
       assertThat(realClient.getClientConfig().getProperty("hazelcast.tcp.join.port.try.count")).isEqualTo("10");
       assertThat(realClient.getClientConfig().getProperty("hazelcast.phone.home.enabled")).isEqualTo("false");
@@ -260,12 +248,9 @@ public class HazelcastClientWrapperImplTest {
     memoryAppender.start();
     lc.getLogger("com.hazelcast").addAppender(memoryAppender);
 
-    try {
-      hzClient.start();
-    } finally {
-      hzClient.stop();
-      memoryAppender.stop();
-    }
+    hzClient.start();
+    hzClient.stop();
+    memoryAppender.stop();
     assertThat(memoryAppender.events).isNotEmpty();
     memoryAppender.events.stream().forEach(
       e -> assertThat(e.getLoggerName()).startsWith("com.hazelcast"));
@@ -285,9 +270,8 @@ public class HazelcastClientWrapperImplTest {
     }
   }
 
-  private static MapSettings createClusterSettings(String name, String localEndPoint) {
+  private static MapSettings createClusterSettings(String localEndPoint) {
     return new MapSettings(new PropertyDefinitions())
-      .setProperty(ProcessProperties.CLUSTER_NAME, name)
       .setProperty(ProcessProperties.CLUSTER_LOCALENDPOINT, localEndPoint)
       .setProperty(ProcessProperties.CLUSTER_ENABLED, "true");
   }
index d195bd01f023674926a73de8e2ce956bb386e8bc..5774dbce735f80b2d28b702936ed07db5c382272 100644 (file)
@@ -29,14 +29,14 @@ import com.hazelcast.core.Hazelcast;
 import com.hazelcast.core.HazelcastInstance;
 import java.net.InetAddress;
 
-import static org.sonar.process.NetworkUtils.getHostName;
+import static org.sonar.process.NetworkUtils.getHostname;
 import static org.sonar.process.cluster.ClusterObjectKeys.CLIENT_UUIDS;
 
 public class HazelcastTestHelper {
 
-  public static HazelcastInstance createHazelcastCluster(String clusterName, int port) {
+  public static HazelcastInstance createHazelcastCluster(int port) {
     Config hzConfig = new Config();
-    hzConfig.getGroupConfig().setName(clusterName);
+    hzConfig.getGroupConfig().setName("sonarqube");
 
     // Configure the network instance
     NetworkConfig netConfig = hzConfig.getNetworkConfig();
@@ -66,7 +66,7 @@ public class HazelcastTestHelper {
       .setProperty("hazelcast.logging.type", "slf4j");
 
     // Trying to resolve the hostname
-    hzConfig.getMemberAttributeConfig().setStringAttribute("HOSTNAME", getHostName());
+    hzConfig.getMemberAttributeConfig().setStringAttribute("HOSTNAME", getHostname());
 
     // We are not using the partition group of Hazelcast, so disabling it
     hzConfig.getPartitionGroupConfig().setEnabled(false);
index 3578e586c659c54b08ad6d1580ef864208620581..2a775ea8298efc138e654f873483a68c1a621b25 100644 (file)
@@ -60,7 +60,6 @@ import static org.sonar.process.ProcessProperties.PATH_TEMP;
 public class ComputeEngineContainerImplTest {
   private static final int CONTAINER_ITSELF = 1;
   private static final int COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION = CONTAINER_ITSELF + 1;
-  private static final String CLUSTER_NAME = "test";
 
   @Rule
   public TemporaryFolder tempFolder = new TemporaryFolder();
@@ -83,13 +82,12 @@ public class ComputeEngineContainerImplTest {
   @Test
   public void real_start_with_cluster() throws IOException {
     int port = NetworkUtils.getNextAvailablePort(InetAddress.getLoopbackAddress());
-    HazelcastInstance hzInstance = HazelcastTestHelper.createHazelcastCluster(CLUSTER_NAME, port);
+    HazelcastInstance hzInstance = HazelcastTestHelper.createHazelcastCluster(port);
 
     Properties properties = getProperties();
     properties.setProperty(ProcessProperties.CLUSTER_NODE_TYPE, "application");
     properties.setProperty(ProcessProperties.CLUSTER_ENABLED, "true");
     properties.setProperty(ProcessProperties.CLUSTER_LOCALENDPOINT, String.format("%s:%d", hzInstance.getCluster().getLocalMember().getAddress().getHost(), port));
-    properties.setProperty(ProcessProperties.CLUSTER_NAME, CLUSTER_NAME);
 
     // required persisted properties
     insertProperty(CoreProperties.SERVER_ID, "a_startup_id");
index 43cb69e75c33f45253a4f6ca43f07498387588a0..7546f90da07805ecc96fdd96cbcb21fa0ff8875a 100644 (file)
@@ -52,6 +52,8 @@ public interface AppState extends AutoCloseable {
 
   void registerSonarQubeVersion(String sonarqubeVersion);
 
+  void registerClusterName(String clusterName);
+
   Optional<String> getLeaderHostName();
 
   @Override
index 9c5e03c296c934a9347d83595307e01ff08b0474..093ca304e25aa90e1cc9c7bcc7e0fb387d212883 100644 (file)
@@ -67,9 +67,14 @@ public class AppStateImpl implements AppState {
     // Nothing to do on non clustered version
   }
 
+  @Override
+  public void registerClusterName(String clusterName) {
+    // Nothing to do on non clustered version
+  }
+
   @Override
   public Optional<String> getLeaderHostName() {
-    return Optional.of(NetworkUtils.getHostName());
+    return Optional.of(NetworkUtils.getHostname());
   }
 
   @Override
index 19c001081b6b714a975da552e7ce18c0a5823033..2af58b8b19608b81c51e306ddcfd4108b85342d5 100644 (file)
@@ -53,7 +53,7 @@ public class AppStateClusterImpl implements AppState {
     appSettings.getProps().set(ProcessProperties.CLUSTER_MEMBERUUID, hazelcastCluster.getLocalUUID());
 
     String members = hazelcastCluster.getMembers().stream().collect(Collectors.joining(","));
-    LOGGER.info("Joined the cluster [{}] that contains the following hosts : [{}]", hazelcastCluster.getName(), members);
+    LOGGER.info("Joined a SonarQube cluster that contains the following hosts : [{}]", members);
   }
 
   @Override
@@ -95,6 +95,11 @@ public class AppStateClusterImpl implements AppState {
     hazelcastCluster.registerSonarQubeVersion(sonarqubeVersion);
   }
 
+  @Override
+  public void registerClusterName(String clusterName) {
+    hazelcastCluster.registerClusterName(clusterName);
+  }
+
   @Override
   public Optional<String> getLeaderHostName() {
     return hazelcastCluster.getLeaderHostName();
index 22c403a20d46a7a6db653440ef04432a01c74703..6d0dd9fdc38862e010208907de75169f19fca258 100644 (file)
@@ -40,11 +40,11 @@ import org.sonar.process.ProcessProperties;
 public final class ClusterProperties {
   static final String DEFAULT_PORT = "9003";
   private static final Logger LOGGER = LoggerFactory.getLogger(ClusterProperties.class);
+  public static final String HAZELCAST_CLUSTER_NAME = "sonarqube";
 
   private final int port;
   private final List<String> hosts;
   private final List<String> networkInterfaces;
-  private final String name;
   private final NodeType nodeType;
 
   ClusterProperties(AppSettings appSettings) {
@@ -52,7 +52,6 @@ public final class ClusterProperties {
     networkInterfaces = extractNetworkInterfaces(
       appSettings.getProps().value(ProcessProperties.CLUSTER_NODE_HOST, "")
     );
-    name = appSettings.getProps().nonNullValue(ProcessProperties.CLUSTER_NAME);
     hosts = extractHosts(
       appSettings.getProps().value(ProcessProperties.CLUSTER_HOSTS, "")
     );
@@ -75,10 +74,6 @@ public final class ClusterProperties {
     return networkInterfaces;
   }
 
-  String getName() {
-    return name;
-  }
-
   void validate() {
     // Test validity of port
     checkArgument(
index d9cbe2c5df64af43721fbf9b697ae7391b713f3e..b17332218be25ca4dc021e7925c9fb53b7f6c90c 100644 (file)
@@ -46,13 +46,19 @@ import java.util.Optional;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.application.AppStateListener;
+import org.sonar.process.MessageException;
 import org.sonar.process.NodeType;
 import org.sonar.process.ProcessId;
 
+import static java.lang.String.format;
 import static java.util.stream.Collectors.toList;
-import static org.sonar.process.NetworkUtils.getHostName;
+import static org.sonar.application.cluster.ClusterProperties.HAZELCAST_CLUSTER_NAME;
+import static org.sonar.process.NetworkUtils.getHostname;
+import static org.sonar.process.NetworkUtils.getIPAddresses;
 import static org.sonar.process.cluster.ClusterObjectKeys.CLIENT_UUIDS;
+import static org.sonar.process.cluster.ClusterObjectKeys.CLUSTER_NAME;
 import static org.sonar.process.cluster.ClusterObjectKeys.HOSTNAME;
+import static org.sonar.process.cluster.ClusterObjectKeys.IP_ADDRESSES;
 import static org.sonar.process.cluster.ClusterObjectKeys.LEADER;
 import static org.sonar.process.cluster.ClusterObjectKeys.NODE_TYPE;
 import static org.sonar.process.cluster.ClusterObjectKeys.OPERATIONAL_PROCESSES;
@@ -91,7 +97,7 @@ public class HazelcastCluster implements AutoCloseable {
   List<String> getMembers() {
     return hzInstance.getCluster().getMembers().stream()
       .filter(m -> !m.localMember())
-      .map(m -> m.getStringAttribute(HOSTNAME))
+      .map(m -> format("%s (%s)", m.getStringAttribute(HOSTNAME), m.getStringAttribute(IP_ADDRESSES)))
       .collect(toList());
   }
 
@@ -149,7 +155,29 @@ public class HazelcastCluster implements AutoCloseable {
     String clusterVersion = sqVersion.get();
     if (!sqVersion.get().equals(sonarqubeVersion)) {
       throw new IllegalStateException(
-        String.format("The local version %s is not the same as the cluster %s", sonarqubeVersion, clusterVersion)
+        format("The local version %s is not the same as the cluster %s", sonarqubeVersion, clusterVersion)
+      );
+    }
+  }
+
+  public void registerClusterName(String nodeValue) {
+    IAtomicReference<String> property = hzInstance.getAtomicReference(CLUSTER_NAME);
+    if (property.get() == null) {
+      ILock lock = hzInstance.getLock(CLUSTER_NAME);
+      lock.lock();
+      try {
+        if (property.get() == null) {
+          property.set(nodeValue);
+        }
+      } finally {
+        lock.unlock();
+      }
+    }
+
+    String clusterValue = property.get();
+    if (!property.get().equals(nodeValue)) {
+      throw new MessageException(
+        format("This node has a cluster name [%s], which does not match [%s] from the cluster", nodeValue, clusterValue)
       );
     }
   }
@@ -182,7 +210,7 @@ public class HazelcastCluster implements AutoCloseable {
 
   public static HazelcastCluster create(ClusterProperties clusterProperties) {
     Config hzConfig = new Config();
-    hzConfig.getGroupConfig().setName(clusterProperties.getName());
+    hzConfig.getGroupConfig().setName(HAZELCAST_CLUSTER_NAME);
 
     // Configure the network instance
     NetworkConfig netConfig = hzConfig.getNetworkConfig();
@@ -216,7 +244,9 @@ public class HazelcastCluster implements AutoCloseable {
 
     // Trying to resolve the hostname
     hzConfig.getMemberAttributeConfig()
-      .setStringAttribute(HOSTNAME, getHostName());
+      .setStringAttribute(HOSTNAME, getHostname());
+    hzConfig.getMemberAttributeConfig()
+      .setStringAttribute(IP_ADDRESSES, getIPAddresses());
     hzConfig.getMemberAttributeConfig()
       .setStringAttribute(NODE_TYPE, clusterProperties.getNodeType().getValue());
 
@@ -230,7 +260,8 @@ public class HazelcastCluster implements AutoCloseable {
     if (leaderId != null) {
       Optional<Member> leader = hzInstance.getCluster().getMembers().stream().filter(m -> m.getUuid().equals(leaderId)).findFirst();
       if (leader.isPresent()) {
-        return Optional.of(leader.get().getStringAttribute(HOSTNAME));
+        return Optional.of(
+          format("%s (%s)", leader.get().getStringAttribute(HOSTNAME), leader.get().getStringAttribute(IP_ADDRESSES)));
       }
     }
     return Optional.empty();
@@ -238,7 +269,7 @@ public class HazelcastCluster implements AutoCloseable {
 
   String getLocalEndPoint() {
     Address localAddress = hzInstance.getCluster().getLocalMember().getAddress();
-    return String.format("%s:%d", localAddress.getHost(), localAddress.getPort());
+    return format("%s:%d", localAddress.getHost(), localAddress.getPort());
   }
 
   private class OperationalProcessListener implements EntryListener<ClusterProcess, Boolean> {
index d01b12901c7a391adcfe489e0f378e71091ff04b..9ac3dea9bec2247e3a02c9ba48acd7d971f52c5d 100644 (file)
@@ -77,9 +77,14 @@ public class TestAppState implements AppState {
     // nothing to do
   }
 
+  @Override
+  public void registerClusterName(String clusterName) {
+    // nothing to do
+  }
+
   @Override
   public Optional<String> getLeaderHostName() {
-    return Optional.of(NetworkUtils.getHostName());
+    return Optional.of(NetworkUtils.getHostname());
   }
 
   @Override
index fd0497c4932ad5c9c93ab27081ab176cd178995b..e80e932b5281282129cc207a46257349128d221d 100644 (file)
@@ -30,9 +30,11 @@ import org.junit.rules.Timeout;
 import org.slf4j.Logger;
 import org.sonar.application.AppStateListener;
 import org.sonar.application.config.TestAppSettings;
+import org.sonar.process.MessageException;
 import org.sonar.process.ProcessId;
 import org.sonar.process.ProcessProperties;
 
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Matchers.anyString;
 import static org.mockito.Matchers.eq;
@@ -41,6 +43,7 @@ import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.sonar.application.cluster.HazelcastTestHelper.createHazelcastClient;
 import static org.sonar.application.cluster.HazelcastTestHelper.newClusterSettings;
+import static org.sonar.process.cluster.ClusterObjectKeys.CLUSTER_NAME;
 import static org.sonar.process.cluster.ClusterObjectKeys.SONARQUBE_VERSION;
 
 public class AppStateClusterImplTest {
@@ -82,8 +85,7 @@ public class AppStateClusterImplTest {
 
     try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) {
       verify(logger).info(
-        eq("Joined the cluster [{}] that contains the following hosts : [{}]"),
-        eq("sonarqube"),
+        eq("Joined a SonarQube cluster that contains the following hosts : [{}]"),
         anyString()
       );
     }
@@ -120,6 +122,22 @@ public class AppStateClusterImplTest {
     }
   }
 
+  @Test
+  public void registerClusterName_publishes_clusterName_on_first_call() {
+    TestAppSettings settings = newClusterSettings();
+    String clusterName = randomAlphanumeric(20);
+
+    try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) {
+      appStateCluster.registerClusterName(clusterName);
+
+      HazelcastInstance hzInstance = createHazelcastClient(appStateCluster);
+      assertThat(hzInstance.getAtomicReference(CLUSTER_NAME).get())
+        .isNotNull()
+        .isInstanceOf(String.class)
+        .isEqualTo(clusterName);
+    }
+  }
+
   @Test
   public void reset_throws_always_ISE() {
     TestAppSettings settings = newClusterSettings();
@@ -147,4 +165,21 @@ public class AppStateClusterImplTest {
       appStateCluster.registerSonarQubeVersion("2.0.0");
     }
   }
+
+  @Test
+  public void registerClusterName_throws_MessageException_if_clusterName_is_different() throws Exception {
+    // Now launch an instance that try to be part of the hzInstance cluster
+    TestAppSettings settings = newClusterSettings();
+
+    try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) {
+      // Register first version
+      appStateCluster.registerClusterName("goodClusterName");
+
+      expectedException.expect(MessageException.class);
+      expectedException.expectMessage("This node has a cluster name [badClusterName], which does not match [goodClusterName] from the cluster");
+
+      // Registering a second different cluster name must trigger an exception
+      appStateCluster.registerClusterName("badClusterName");
+    }
+  }
 }
index d08d0e7b7a67747fdfa240c393f684d0387a251a..68c257040f7cc1fd6b8a2d224b32094477027cd3 100644 (file)
@@ -51,8 +51,6 @@ public class ClusterPropertiesTest {
       .isEqualTo(9003);
     assertThat(props.getHosts())
       .isEqualTo(Collections.emptyList());
-    assertThat(props.getName())
-      .isEqualTo("sonarqube");
   }
 
   @Test
index bda7d36a7025de4798aaf046bb3410c80b47b4c3..d07cbcb93d4d1914a14356b61d80f477dc8aca70 100644 (file)
@@ -48,6 +48,7 @@ import org.sonar.process.ProcessId;
 import org.sonar.process.ProcessProperties;
 import org.sonar.process.cluster.ClusterObjectKeys;
 
+import static java.lang.String.format;
 import static junit.framework.TestCase.fail;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
@@ -106,7 +107,8 @@ public class HazelcastClusterTest {
     ClusterProperties clusterProperties = new ClusterProperties(newClusterSettings());
     try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
       assertThat(hzCluster.tryToLockWebLeader()).isTrue();
-      assertThat(hzCluster.getLeaderHostName().get()).isEqualTo(NetworkUtils.getHostName());
+      assertThat(hzCluster.getLeaderHostName().get()).isEqualTo(
+        format("%s (%s)", NetworkUtils.getHostname(), NetworkUtils.getIPAddresses()));
     }
   }
 
@@ -137,12 +139,12 @@ public class HazelcastClusterTest {
   }
 
   @Test
-  public void cluster_name_comes_from_configuration() {
+  public void hazelcast_cluster_name_is_hardcoded_and_not_affected_by_settings() {
     TestAppSettings testAppSettings = newClusterSettings();
     testAppSettings.set(CLUSTER_NAME, "a_cluster_");
     ClusterProperties clusterProperties = new ClusterProperties(testAppSettings);
     try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
-      assertThat(hzCluster.getName()).isEqualTo("a_cluster_");
+      assertThat(hzCluster.getName()).isEqualTo("sonarqube");
     }
   }
 
index 342797de10f64c29ff2177c83d6d0a7a1af10bd8..d643cf9ca75b8d4cbbc2d7fbdc0ec644c421d503 100644 (file)
@@ -30,7 +30,6 @@ import java.util.HashSet;
 import java.util.Set;
 import java.util.stream.Collectors;
 
-import static java.lang.String.format;
 import static java.util.Collections.list;
 import static org.apache.commons.lang.StringUtils.isBlank;
 
@@ -49,29 +48,39 @@ public final class NetworkUtils {
 
   /**
    * Identifying the localhost machine
-   * It will try to retrieve the hostname and the IPv4 addresses
+   * It will try to retrieve the hostname
    *
-   * @return "hostname (ipv4_1, ipv4_2...)"
+   * @return "hostname"
    */
-  public static String getHostName() {
+  public static String getHostname() {
     String hostname;
-    String ips;
     try {
       hostname = InetAddress.getLocalHost().getHostName();
     } catch (UnknownHostException e) {
       hostname = "unresolved hostname";
     }
 
+    return hostname;
+  }
+
+  /**
+   * Identifying the IPs addresses
+   *
+   * @return "ipv4_1, ipv4_2"
+   */
+  public static String getIPAddresses() {
+    String ips;
+
     try {
       ips = list(NetworkInterface.getNetworkInterfaces()).stream()
         .flatMap(netif -> list(netif.getInetAddresses()).stream()
           .filter(inetAddress ->
-          // Removing IPv6 for the time being
-          inetAddress instanceof Inet4Address &&
-          // Removing loopback addresses, useless for identifying a server
-            !inetAddress.isLoopbackAddress() &&
-            // Removing interfaces without IPs
-            !isBlank(inetAddress.getHostAddress()))
+            // Removing IPv6 for the time being
+            inetAddress instanceof Inet4Address &&
+              // Removing loopback addresses, useless for identifying a server
+              !inetAddress.isLoopbackAddress() &&
+              // Removing interfaces without IPs
+              !isBlank(inetAddress.getHostAddress()))
           .map(InetAddress::getHostAddress))
         .filter(p -> !isBlank(p))
         .collect(Collectors.joining(","));
@@ -79,7 +88,7 @@ public final class NetworkUtils {
       ips = "unresolved IPs";
     }
 
-    return format("%s (%s)", hostname, ips);
+    return ips;
   }
 
   /**
index 2306ce7aca2579a8a30d1208d3ac2ed32a9c8b00..0227fcb7c773309e745d299213352b170e542656 100644 (file)
@@ -41,6 +41,14 @@ public final class ClusterObjectKeys {
    * The key of the hostname attribute of a member
    */
   public static final String HOSTNAME = "HOSTNAME";
+  /**
+   * The key of the ips list attribute of a member
+   */
+  public static final String IP_ADDRESSES = "IP_ADDRESSES";
+  /**
+   * The key of the node name attribute of a member
+   */
+  public static final String NODE_NAME = "NODE_NAME";
 
   /**
    * The role of the sonar-application inside the SonarQube cluster
@@ -51,6 +59,10 @@ public final class ClusterObjectKeys {
    * The key of atomic reference holding the SonarQube version of the cluster
    */
   public static final String SONARQUBE_VERSION = "SONARQUBE_VERSION";
+  /**
+   * The key of atomic reference holding the name of the cluster (used for precondition checks)
+   */
+  public static final String CLUSTER_NAME = "CLUSTER_NAME";
   /**
    * The key of the Set holding the UUIDs of clients
    */
index bb8e3d26823f0a2716fd34028ce6d70f4b115229..6450790490d8fd474ec0aa8a1d61b273de4aef7b 100644 (file)
@@ -80,7 +80,12 @@ public class NetworkUtilsTest {
   }
 
   @Test
-  public void getHostName_must_return_a_value() {
-    assertThat(NetworkUtils.getHostName()).containsPattern(".* \\(.*\\)");
+  public void getHostname_must_return_a_value() {
+    assertThat(NetworkUtils.getHostname()).containsPattern(".+");
+  }
+
+  @Test
+  public void getIPAddresses_must_return_a_value() {
+    assertThat(NetworkUtils.getIPAddresses()).matches("(\\d+\\.\\d+\\.\\d+\\.\\d+,?)+");
   }
 }
index 123467455eece19627d3ee93fe52ff08ea544d66..ad3710088ce8acd82e5c5ba8242884e26662ec0e 100644 (file)
@@ -32,6 +32,7 @@ import org.sonar.application.process.StopRequestWatcherImpl;
 import org.sonar.process.SystemExit;
 
 import static org.sonar.application.config.SonarQubeVersionHelper.getSonarqubeVersion;
+import static org.sonar.process.ProcessProperties.CLUSTER_NAME;
 
 public class App {
 
@@ -48,6 +49,7 @@ public class App {
 
     try (AppState appState = new AppStateFactory(settings).create()) {
       appState.registerSonarQubeVersion(getSonarqubeVersion());
+      appState.registerClusterName(settings.getProps().value(CLUSTER_NAME, "sonarqube"));
       AppReloader appReloader = new AppReloaderImpl(settingsLoader, fileSystem, appState, logging);
       fileSystem.reset();
       CommandFactory commandFactory = new CommandFactoryImpl(settings.getProps(), fileSystem.getTempDir());
index cf81dd3342c420c9b2d8bea47a30e8e2eff5e309..6c8416cbc8dd4af1bdf50d2ac60cf5c690664caa 100644 (file)
@@ -29,6 +29,7 @@ import java.net.InetAddress;
 import java.net.NetworkInterface;
 import java.net.SocketException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.Enumeration;
@@ -36,6 +37,7 @@ import java.util.List;
 import java.util.Properties;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ForkJoinPool;
+import java.util.function.Consumer;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
 import javax.annotation.CheckForNull;
@@ -218,8 +220,10 @@ public class Cluster {
       return new Cluster(nodes);
     }
 
-    public Builder addNode(NodeType type) {
-      nodes.add(new Node(type));
+    public Builder addNode(NodeType type, Consumer<Node>... consumers) {
+      Node node = new Node(type);
+      Arrays.stream(consumers).forEach(c -> c.accept(node));
+      nodes.add(node);
       return this;
     }
   }
index 2a52d6f7818cea8f825d758ac2aa5d2b7a99022e..52f7930e43485eca74362ffc7655854935708ec9 100644 (file)
@@ -91,6 +91,26 @@ public class DataCenterEditionTest {
     dce.stop();
   }
 
+  @Test
+  public void using_different_cluster_names_should_fail() throws ExecutionException, InterruptedException, SQLException {
+    Cluster cluster = Cluster.builder()
+      .addNode(SEARCH, n -> n.getProperties().setProperty(Cluster.CLUSTER_NAME, "goodClusterName"))
+      .addNode(SEARCH, n -> n.getProperties().setProperty(Cluster.CLUSTER_NAME, "goodClusterName"))
+      .addNode(SEARCH, n -> n.getProperties().setProperty(Cluster.CLUSTER_NAME, "goodClusterName"))
+      .addNode(APPLICATION, n -> n.getProperties().setProperty(Cluster.CLUSTER_NAME, "goodClusterName"))
+      .addNode(APPLICATION, n -> n.getProperties().setProperty(Cluster.CLUSTER_NAME, "badClusterName"))
+      .build();
+    cluster.startAll(n -> "goodClusterName".equals(n.getProperties().getProperty(Cluster.CLUSTER_NAME)));
+
+    try {
+      cluster.startAll(n -> "badClusterName".equals(n.getProperties().getProperty(Cluster.CLUSTER_NAME)));
+      fail("A node with a bad cluster name was able to join the cluster");
+    } catch (Exception e) {
+      // we expect, that joining the cluster fails
+      System.out.println(e);
+    }
+  }
+
   private void assertDatabaseInitialized(Database database) {
     assertThat(countRowsOfMigration(database)).isGreaterThan(0);
   }