import java.util.concurrent.locks.Lock;
import org.picocontainer.Startable;
import org.sonar.ce.taskprocessor.CeWorkerFactory;
-import org.sonar.process.cluster.ClusterObjectKeys;
-import org.sonar.process.cluster.HazelcastClient;
+import org.sonar.process.cluster.hz.HazelcastObjects;
+import org.sonar.process.cluster.hz.HazelcastMember;
import static org.sonar.core.util.stream.MoreCollectors.toSet;
-import static org.sonar.process.cluster.ClusterObjectKeys.WORKER_UUIDS;
+import static org.sonar.process.cluster.hz.HazelcastObjects.WORKER_UUIDS;
/**
* Provide the set of worker's UUID in a clustered SonarQube instance
*/
public class CeDistributedInformationImpl implements CeDistributedInformation, Startable {
- private final HazelcastClient hazelcastClient;
+ private final HazelcastMember hazelcastMember;
private final CeWorkerFactory ceCeWorkerFactory;
- public CeDistributedInformationImpl(HazelcastClient hazelcastClient, CeWorkerFactory ceCeWorkerFactory) {
- this.hazelcastClient = hazelcastClient;
+ public CeDistributedInformationImpl(HazelcastMember hazelcastMember, CeWorkerFactory ceCeWorkerFactory) {
+ this.hazelcastMember = hazelcastMember;
this.ceCeWorkerFactory = ceCeWorkerFactory;
}
@Override
public Set<String> getWorkerUUIDs() {
- Set<String> connectedWorkerUUIDs = hazelcastClient.getMemberUuids();
+ Set<String> connectedWorkerUUIDs = hazelcastMember.getMemberUuids();
return getClusteredWorkerUUIDs().entrySet().stream()
.filter(e -> connectedWorkerUUIDs.contains(e.getKey()))
@Override
public void broadcastWorkerUUIDs() {
- getClusteredWorkerUUIDs().put(hazelcastClient.getUUID(), ceCeWorkerFactory.getWorkerUUIDs());
+ getClusteredWorkerUUIDs().put(hazelcastMember.getUuid(), ceCeWorkerFactory.getWorkerUUIDs());
}
@Override
public Lock acquireCleanJobLock() {
- return hazelcastClient.getLock(ClusterObjectKeys.CE_CLEANING_JOB_LOCK);
+ return hazelcastMember.getLock(HazelcastObjects.CE_CLEANING_JOB_LOCK);
}
@Override
@Override
public void stop() {
// Removing the worker UUIDs
- getClusteredWorkerUUIDs().remove(hazelcastClient.getUUID());
+ getClusteredWorkerUUIDs().remove(hazelcastMember.getUuid());
}
private Map<String, Set<String>> getClusteredWorkerUUIDs() {
- return hazelcastClient.getReplicatedMap(WORKER_UUIDS);
+ return hazelcastMember.getReplicatedMap(WORKER_UUIDS);
}
}
import org.sonar.db.DbClient;
import org.sonar.db.DefaultDatabase;
import org.sonar.db.purge.PurgeProfiler;
+import org.sonar.process.NetworkUtils;
+import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
-import org.sonar.process.cluster.ClusterProperties;
import org.sonar.process.logging.LogbackHelper;
+import org.sonar.server.cluster.StartableHazelcastMember;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.component.index.ComponentIndexer;
import org.sonar.server.computation.task.projectanalysis.ProjectAnalysisTaskModule;
import org.sonar.server.debt.DebtModelPluginRepository;
import org.sonar.server.debt.DebtRulesXMLImporter;
import org.sonar.server.event.NewAlerts;
-import org.sonar.server.hz.HazelcastLocalClient;
import org.sonar.server.issue.IssueFieldsSetter;
import org.sonar.server.issue.index.IssueIndex;
import org.sonar.server.issue.index.IssueIndexer;
SonarRuntimeImpl.forSonarQube(ApiVersion.load(System2.INSTANCE), SonarQubeSide.COMPUTE_ENGINE),
CeProcessLogging.class,
UuidFactoryImpl.INSTANCE,
+ NetworkUtils.INSTANCE,
WebServerImpl.class,
LogbackHelper.class,
DefaultDatabase.class,
// cleaning
CeCleaningModule.class);
- if (props.valueAsBoolean(ClusterProperties.CLUSTER_ENABLED)) {
+ if (props.valueAsBoolean(ProcessProperties.CLUSTER_ENABLED)) {
container.add(
- HazelcastLocalClient.class,
+ StartableHazelcastMember.class,
CeDistributedInformationImpl.class);
} else {
container.add(StandaloneCeDistributedInformation.class);
import java.util.Set;
import org.junit.Test;
import org.sonar.ce.taskprocessor.CeWorkerFactory;
-import org.sonar.server.hz.HazelcastLocalClient;
+import org.sonar.server.cluster.StartableHazelcastMember;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.MapEntry.entry;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import static org.sonar.process.cluster.ClusterObjectKeys.WORKER_UUIDS;
+import static org.sonar.process.cluster.hz.HazelcastObjects.WORKER_UUIDS;
public class CeDistributedInformationImplTest {
private String clientUUID1 = "1";
clientUUID3, ImmutableSet.of("4", "5", "6")
);
- private HazelcastLocalClient hzClientWrapper = mock(HazelcastLocalClient.class);
+ private StartableHazelcastMember hzClientWrapper = mock(StartableHazelcastMember.class);
@Test
public void getWorkerUUIDs_returns_union_of_workers_uuids_of_local_and_cluster_worker_uuids() {
- when(hzClientWrapper.getUUID()).thenReturn(clientUUID1);
+ when(hzClientWrapper.getUuid()).thenReturn(clientUUID1);
when(hzClientWrapper.getMemberUuids()).thenReturn(ImmutableSet.of(clientUUID1, clientUUID2, clientUUID3));
when(hzClientWrapper.getReplicatedMap(WORKER_UUIDS)).thenReturn(workerMap);
@Test
public void getWorkerUUIDs_must_filter_absent_client() {
- when(hzClientWrapper.getUUID()).thenReturn(clientUUID1);
+ when(hzClientWrapper.getUuid()).thenReturn(clientUUID1);
when(hzClientWrapper.getMemberUuids()).thenReturn(ImmutableSet.of(clientUUID1, clientUUID2));
when(hzClientWrapper.getReplicatedMap(WORKER_UUIDS)).thenReturn(workerMap);
connectedClients.add(clientUUID1);
connectedClients.add(clientUUID2);
- when(hzClientWrapper.getUUID()).thenReturn(clientUUID1);
+ when(hzClientWrapper.getUuid()).thenReturn(clientUUID1);
when(hzClientWrapper.getMemberUuids()).thenReturn(connectedClients);
when(hzClientWrapper.getReplicatedMap(WORKER_UUIDS)).thenReturn(modifiableWorkerMap);
Map modifiableWorkerMap = new HashMap();
modifiableWorkerMap.putAll(workerMap);
- when(hzClientWrapper.getUUID()).thenReturn(clientUUID1);
+ when(hzClientWrapper.getUuid()).thenReturn(clientUUID1);
when(hzClientWrapper.getMemberUuids()).thenReturn(connectedClients);
when(hzClientWrapper.getReplicatedMap(WORKER_UUIDS)).thenReturn(modifiableWorkerMap);
*/
package org.sonar.ce.container;
-import com.hazelcast.core.HazelcastInstance;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.util.Date;
+import java.util.Optional;
import java.util.Properties;
import java.util.stream.Collectors;
import org.apache.commons.dbcp.BasicDataSource;
+import org.hamcrest.CoreMatchers;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.process.ProcessId;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
-import org.sonar.server.hz.HazelcastLocalClient;
+import org.sonar.server.cluster.StartableHazelcastMember;
import static java.lang.String.valueOf;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assume.assumeThat;
import static org.mockito.Mockito.mock;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_INDEX;
+import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_KEY;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_HOST;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_PORT;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_TYPE;
import static org.sonar.process.ProcessProperties.PATH_DATA;
import static org.sonar.process.ProcessProperties.PATH_HOME;
import static org.sonar.process.ProcessProperties.PATH_TEMP;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_LOCALENDPOINT;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_TYPE;
public class ComputeEngineContainerImplTest {
private static final int CONTAINER_ITSELF = 1;
@Test
public void real_start_with_cluster() throws IOException {
- int port = NetworkUtils.INSTANCE.getNextAvailablePort(InetAddress.getLoopbackAddress());
- HazelcastInstance hzInstance = HazelcastTestHelper.createHazelcastCluster(NetworkUtils.INSTANCE.getHostname(), port);
+ Optional<InetAddress> localhost = NetworkUtils.INSTANCE.getLocalNonLoopbackIpv4Address();
+ // test is ignored if offline
+ assumeThat(localhost.isPresent(), CoreMatchers.is(true));
Properties properties = getProperties();
- properties.setProperty(CLUSTER_NODE_TYPE, "application");
+ properties.setProperty(PROPERTY_PROCESS_KEY, ProcessId.COMPUTE_ENGINE.getKey());
properties.setProperty(CLUSTER_ENABLED, "true");
- properties.setProperty(CLUSTER_LOCALENDPOINT, String.format("%s:%d", hzInstance.getCluster().getLocalMember().getAddress().getHost(), port));
+ properties.setProperty(CLUSTER_NODE_TYPE, "application");
+ properties.setProperty(CLUSTER_NODE_HOST, localhost.get().getHostAddress());
+ properties.setProperty(CLUSTER_NODE_PORT, "" + NetworkUtils.INSTANCE.getNextAvailablePort(localhost.get()));
// required persisted properties
insertProperty(CoreProperties.SERVER_ID, "a_startup_id");
assertThat(
picoContainer.getComponentAdapters().stream()
.map(ComponentAdapter::getComponentImplementation)
- .collect(Collectors.toList())).contains((Class) HazelcastLocalClient.class,
+ .collect(Collectors.toList())).contains((Class) StartableHazelcastMember.class,
(Class) CeDistributedInformationImpl.class);
underTest.stop();
}
);
assertThat(picoContainer.getParent().getParent().getParent().getComponentAdapters()).hasSize(
COMPONENTS_IN_LEVEL_1_AT_CONSTRUCTION
- + 25 // level 1
+ + 26 // level 1
+ 49 // content of DaoModule
+ 3 // content of EsSearchModule
+ 64 // content of CorePropertyDefinitions
assertThat(
picoContainer.getComponentAdapters().stream()
.map(ComponentAdapter::getComponentImplementation)
- .collect(Collectors.toList())).doesNotContain((Class) HazelcastLocalClient.class,
+ .collect(Collectors.toList())).doesNotContain((Class) StartableHazelcastMember.class,
(Class) CeDistributedInformationImpl.class).contains(
(Class) StandaloneCeDistributedInformation.class);
assertThat(picoContainer.getParent().getParent().getParent().getParent()).isNull();
import com.hazelcast.config.Config;
import com.hazelcast.config.JoinConfig;
import com.hazelcast.config.NetworkConfig;
-import com.hazelcast.core.Client;
-import com.hazelcast.core.ClientListener;
import com.hazelcast.core.Hazelcast;
import com.hazelcast.core.HazelcastInstance;
import java.net.InetAddress;
-import org.sonar.process.cluster.ClusterObjectKeys;
/**
* TODO move outside main sources
// We are not using the partition group of Hazelcast, so disabling it
hzConfig.getPartitionGroupConfig().setEnabled(false);
HazelcastInstance hzInstance = Hazelcast.newHazelcastInstance(hzConfig);
- hzInstance.getClientService().addClientListener(new ConnectedClientListener(hzInstance));
return hzInstance;
}
-
- private static class ConnectedClientListener implements ClientListener {
- private final HazelcastInstance hzInstance;
-
- private ConnectedClientListener(HazelcastInstance hzInstance) {
- this.hzInstance = hzInstance;
- }
-
- @Override
- public void clientConnected(Client client) {
- hzInstance.getSet(ClusterObjectKeys.LOCAL_MEMBER_UUIDS).add(client.getUuid());
- }
-
- @Override
- public void clientDisconnected(Client client) {
- hzInstance.getSet(ClusterObjectKeys.LOCAL_MEMBER_UUIDS).remove(client.getUuid());
- }
- }
}
import static org.slf4j.Logger.ROOT_LOGGER_NAME;
import static org.sonar.application.process.StreamGobbler.LOGGER_GOBBLER;
-import static org.sonar.process.cluster.ClusterProperties.HAZELCAST_LOG_LEVEL;
import static org.sonar.process.logging.RootLoggerConfig.newRootLoggerConfigBuilder;
/**
LogLevelConfig.newBuilder(helper.getRootLoggerName())
.rootLevelFor(ProcessId.APP)
.immutableLevel("com.hazelcast",
- Level.toLevel(
- appSettings.getProps().nonNullValue(HAZELCAST_LOG_LEVEL)))
+ Level.toLevel("WARN"))
.build(),
appSettings.getProps());
import org.sonar.process.Props;
import static java.lang.String.format;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
import static org.sonar.process.ProcessProperties.PATH_DATA;
import static org.sonar.process.ProcessProperties.PATH_LOGS;
import static org.sonar.process.ProcessProperties.PATH_TEMP;
import org.sonar.application.cluster.ClusterAppStateImpl;
import org.sonar.application.config.AppSettings;
import org.sonar.application.config.ClusterSettings;
+import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.Props;
+import org.sonar.process.cluster.NodeType;
+import org.sonar.process.cluster.hz.HazelcastMember;
+import org.sonar.process.cluster.hz.HazelcastMemberBuilder;
+
+import static java.util.Arrays.asList;
public class AppStateFactory {
}
public AppState create() {
- return ClusterSettings.isClusterEnabled(settings) ? new ClusterAppStateImpl(settings) : new AppStateImpl();
+ if (ClusterSettings.isClusterEnabled(settings)) {
+ HazelcastMember hzMember = createHzMember(settings.getProps());
+ return new ClusterAppStateImpl(hzMember);
+ }
+ return new AppStateImpl();
+ }
+
+ private static HazelcastMember createHzMember(Props props) {
+ HazelcastMemberBuilder builder = new HazelcastMemberBuilder()
+ .setClusterName("sonarqube")
+ .setNetworkInterface(props.nonNullValue(ProcessProperties.CLUSTER_NODE_HOST))
+ .setMembers(asList(props.nonNullValue(ProcessProperties.CLUSTER_HOSTS).split(",")))
+ .setNodeType(NodeType.parse(props.nonNullValue(ProcessProperties.CLUSTER_NODE_TYPE)))
+ .setNodeName(props.nonNullValue(ProcessProperties.CLUSTER_NODE_NAME))
+ .setPort(Integer.parseInt(props.nonNullValue(ProcessProperties.CLUSTER_NODE_PORT)))
+ .setProcessId(ProcessId.APP);
+ return builder.build();
}
}
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.application.cluster.ClusterAppState;
-import org.sonar.application.cluster.SearchNodeHealthProvider;
+import org.sonar.application.cluster.health.SearchNodeHealthProvider;
import org.sonar.application.command.CommandFactory;
import org.sonar.application.command.EsCommand;
import org.sonar.application.command.JavaCommand;
import org.sonar.application.process.SQProcess;
import org.sonar.process.NetworkUtils;
import org.sonar.process.ProcessId;
-import org.sonar.process.cluster.health.HealthStateSharing;
-import org.sonar.process.cluster.health.HealthStateSharingImpl;
+import org.sonar.application.cluster.health.HealthStateSharing;
+import org.sonar.application.cluster.health.HealthStateSharingImpl;
public class SchedulerImpl implements Scheduler, ProcessEventListener, ProcessLifecycleListener, AppStateListener {
&& ClusterSettings.isLocalElasticsearchEnabled(settings)) {
ClusterAppState clusterAppState = (ClusterAppState) appState;
this.healthStateSharing = new HealthStateSharingImpl(
- clusterAppState.getHazelcastClient(),
+ clusterAppState.getHazelcastMember(),
new SearchNodeHealthProvider(settings.getProps(), clusterAppState, NetworkUtils.INSTANCE));
this.healthStateSharing.start();
}
package org.sonar.application.cluster;
import org.sonar.application.AppState;
-import org.sonar.process.cluster.HazelcastClient;
+import org.sonar.process.cluster.hz.HazelcastMember;
public interface ClusterAppState extends AppState {
- HazelcastClient getHazelcastClient();
+ HazelcastMember getHazelcastMember();
}
package org.sonar.application.cluster;
+import com.hazelcast.core.EntryEvent;
+import com.hazelcast.core.EntryListener;
+import com.hazelcast.core.HazelcastInstanceNotActiveException;
+import com.hazelcast.core.IAtomicReference;
+import com.hazelcast.core.MapEvent;
+import com.hazelcast.core.Member;
+import com.hazelcast.core.MemberAttributeEvent;
+import com.hazelcast.core.MembershipEvent;
+import com.hazelcast.core.MembershipListener;
+import com.hazelcast.core.ReplicatedMap;
+import java.util.ArrayList;
import java.util.EnumMap;
+import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.stream.Collectors;
-import javax.annotation.Nonnull;
+import java.util.concurrent.locks.Lock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.application.AppStateListener;
-import org.sonar.application.config.AppSettings;
+import org.sonar.process.MessageException;
import org.sonar.process.ProcessId;
-import org.sonar.process.cluster.HazelcastClient;
+import org.sonar.process.cluster.NodeType;
+import org.sonar.process.cluster.hz.HazelcastMember;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_LOCALENDPOINT;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_MEMBERUUID;
+import static java.lang.String.format;
+import static org.sonar.process.cluster.hz.HazelcastObjects.CLUSTER_NAME;
+import static org.sonar.process.cluster.hz.HazelcastObjects.LEADER;
+import static org.sonar.process.cluster.hz.HazelcastObjects.OPERATIONAL_PROCESSES;
+import static org.sonar.process.cluster.hz.HazelcastObjects.SONARQUBE_VERSION;
public class ClusterAppStateImpl implements ClusterAppState {
- private static Logger LOGGER = LoggerFactory.getLogger(ClusterAppStateImpl.class);
- private final Map<ProcessId, Boolean> localProcesses = new EnumMap<>(ProcessId.class);
- private final HazelcastCluster hazelcastCluster;
+ private static final Logger LOGGER = LoggerFactory.getLogger(ClusterAppStateImpl.class);
- public ClusterAppStateImpl(AppSettings appSettings) {
- if (!appSettings.getProps().valueAsBoolean(CLUSTER_ENABLED)) {
- throw new IllegalStateException("Cluster is not enabled on this instance");
- }
+ private final HazelcastMember hzMember;
+ private final List<AppStateListener> listeners = new ArrayList<>();
+ private final Map<ProcessId, Boolean> operationalLocalProcesses = new EnumMap<>(ProcessId.class);
+ private final ReplicatedMap<ClusterProcess, Boolean> operationalProcesses;
+ private final String operationalProcessListenerUUID;
+ private final String nodeDisconnectedListenerUUID;
- ClusterProperties clusterProperties = new ClusterProperties(appSettings);
- clusterProperties.validate();
+ public ClusterAppStateImpl(HazelcastMember hzMember) {
+ this.hzMember = hzMember;
- hazelcastCluster = HazelcastCluster.create(clusterProperties);
- // Add the local endpoint to be used by processes
- appSettings.getProps().set(CLUSTER_LOCALENDPOINT, hazelcastCluster.getLocalEndPoint());
- appSettings.getProps().set(CLUSTER_MEMBERUUID, hazelcastCluster.getLocalUUID());
+ // Get or create the replicated map
+ operationalProcesses = (ReplicatedMap) hzMember.getReplicatedMap(OPERATIONAL_PROCESSES);
+ operationalProcessListenerUUID = operationalProcesses.addEntryListener(new OperationalProcessListener());
+ nodeDisconnectedListenerUUID = hzMember.getCluster().addMembershipListener(new NodeDisconnectedListener());
+ }
- String members = hazelcastCluster.getMembers().stream().collect(Collectors.joining(","));
- LOGGER.info("Joined a SonarQube cluster that contains the following hosts : [{}]", members);
+ @Override
+ public HazelcastMember getHazelcastMember() {
+ return hzMember;
}
@Override
- public void addListener(@Nonnull AppStateListener listener) {
- hazelcastCluster.addListener(listener);
+ public void addListener(AppStateListener listener) {
+ listeners.add(listener);
}
@Override
- public boolean isOperational(@Nonnull ProcessId processId, boolean local) {
+ public boolean isOperational(ProcessId processId, boolean local) {
if (local) {
- return localProcesses.computeIfAbsent(processId, p -> false);
+ return operationalLocalProcesses.computeIfAbsent(processId, p -> false);
}
- return hazelcastCluster.isOperational(processId);
+ for (Map.Entry<ClusterProcess, Boolean> entry : operationalProcesses.entrySet()) {
+ if (entry.getKey().getProcessId().equals(processId) && entry.getValue()) {
+ return true;
+ }
+ }
+ return false;
}
@Override
- public void setOperational(@Nonnull ProcessId processId) {
- localProcesses.put(processId, true);
- hazelcastCluster.setOperational(processId);
+ public void setOperational(ProcessId processId) {
+ operationalLocalProcesses.put(processId, true);
+ operationalProcesses.put(new ClusterProcess(hzMember.getUuid(), processId), Boolean.TRUE);
}
@Override
public boolean tryToLockWebLeader() {
- return hazelcastCluster.tryToLockWebLeader();
+ IAtomicReference<String> leader = hzMember.getAtomicReference(LEADER);
+ if (leader.get() == null) {
+ Lock lock = hzMember.getLock(LEADER);
+ lock.lock();
+ try {
+ if (leader.get() == null) {
+ leader.set(hzMember.getUuid());
+ return true;
+ }
+ return false;
+ } finally {
+ lock.unlock();
+ }
+ } else {
+ return false;
+ }
}
@Override
throw new IllegalStateException("state reset is not supported in cluster mode");
}
- @Override
- public void close() {
- hazelcastCluster.close();
- }
-
@Override
public void registerSonarQubeVersion(String sonarqubeVersion) {
- hazelcastCluster.registerSonarQubeVersion(sonarqubeVersion);
+ IAtomicReference<String> sqVersion = hzMember.getAtomicReference(SONARQUBE_VERSION);
+ if (sqVersion.get() == null) {
+ Lock lock = hzMember.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)) {
+ throw new IllegalStateException(
+ format("The local version %s is not the same as the cluster %s", sonarqubeVersion, clusterVersion));
+ }
}
@Override
public void registerClusterName(String clusterName) {
- hazelcastCluster.registerClusterName(clusterName);
+ IAtomicReference<String> property = hzMember.getAtomicReference(CLUSTER_NAME);
+ if (property.get() == null) {
+ Lock lock = hzMember.getLock(CLUSTER_NAME);
+ lock.lock();
+ try {
+ if (property.get() == null) {
+ property.set(clusterName);
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+
+ String clusterValue = property.get();
+ if (!property.get().equals(clusterName)) {
+ throw new MessageException(
+ format("This node has a cluster name [%s], which does not match [%s] from the cluster", clusterName, clusterValue));
+ }
}
@Override
public Optional<String> getLeaderHostName() {
- return hazelcastCluster.getLeaderHostName();
- }
-
- HazelcastCluster getHazelcastCluster() {
- return hazelcastCluster;
+ String leaderId = (String) hzMember.getAtomicReference(LEADER).get();
+ if (leaderId != null) {
+ Optional<Member> leader = hzMember.getCluster().getMembers().stream().filter(m -> m.getUuid().equals(leaderId)).findFirst();
+ if (leader.isPresent()) {
+ return Optional.of(
+ format("%s (%s)", leader.get().getStringAttribute(HazelcastMember.Attribute.HOSTNAME), leader.get().getStringAttribute(HazelcastMember.Attribute.IP_ADDRESSES)));
+ }
+ }
+ return Optional.empty();
}
@Override
- public HazelcastClient getHazelcastClient() {
- return hazelcastCluster.getHazelcastClient();
+ public void close() {
+ if (hzMember != null) {
+ try {
+ // Removing listeners
+ operationalProcesses.removeEntryListener(operationalProcessListenerUUID);
+ hzMember.getCluster().removeMembershipListener(nodeDisconnectedListenerUUID);
+
+ // Removing the operationalProcess from the replicated map
+ operationalProcesses.keySet().forEach(
+ clusterNodeProcess -> {
+ if (clusterNodeProcess.getNodeUuid().equals(hzMember.getUuid())) {
+ operationalProcesses.remove(clusterNodeProcess);
+ }
+ });
+
+ // Shutdown Hazelcast properly
+ hzMember.close();
+ } catch (HazelcastInstanceNotActiveException e) {
+ // hazelcastCluster may be already closed by the shutdown hook
+ LOGGER.debug("Unable to close Hazelcast cluster", e);
+ }
+ }
}
- /**
- * Only used for testing purpose
- *
- * @param logger
- */
- static void setLogger(Logger logger) {
- ClusterAppStateImpl.LOGGER = logger;
+ private class OperationalProcessListener implements EntryListener<ClusterProcess, Boolean> {
+ @Override
+ public void entryAdded(EntryEvent<ClusterProcess, Boolean> event) {
+ if (event.getValue()) {
+ listeners.forEach(appStateListener -> appStateListener.onAppStateOperational(event.getKey().getProcessId()));
+ }
+ }
+
+ @Override
+ public void entryRemoved(EntryEvent<ClusterProcess, Boolean> event) {
+ // Ignore it
+ }
+
+ @Override
+ public void entryUpdated(EntryEvent<ClusterProcess, Boolean> event) {
+ if (event.getValue()) {
+ listeners.forEach(appStateListener -> appStateListener.onAppStateOperational(event.getKey().getProcessId()));
+ }
+ }
+
+ @Override
+ public void entryEvicted(EntryEvent<ClusterProcess, Boolean> event) {
+ // Ignore it
+ }
+
+ @Override
+ public void mapCleared(MapEvent event) {
+ // Ignore it
+ }
+
+ @Override
+ public void mapEvicted(MapEvent event) {
+ // Ignore it
+ }
}
+ private class NodeDisconnectedListener implements MembershipListener {
+ @Override
+ public void memberAdded(MembershipEvent membershipEvent) {
+ // Nothing to do
+ }
+
+ @Override
+ public void memberRemoved(MembershipEvent membershipEvent) {
+ removeOperationalProcess(membershipEvent.getMember().getUuid());
+ if (membershipEvent.getMembers().stream()
+ .noneMatch(this::isAppNode)) {
+ purgeSharedMemoryForAppNodes();
+ }
+ }
+
+ @Override
+ public void memberAttributeChanged(MemberAttributeEvent memberAttributeEvent) {
+ // Nothing to do
+ }
+
+ private boolean isAppNode(Member member) {
+ return NodeType.APPLICATION.getValue().equals(member.getStringAttribute(HazelcastMember.Attribute.NODE_TYPE));
+ }
+
+ private void removeOperationalProcess(String uuid) {
+ for (ClusterProcess clusterProcess : operationalProcesses.keySet()) {
+ if (clusterProcess.getNodeUuid().equals(uuid)) {
+ LOGGER.debug("Set node process off for [{}:{}] : ", clusterProcess.getNodeUuid(), clusterProcess.getProcessId());
+ hzMember.getReplicatedMap(OPERATIONAL_PROCESSES).put(clusterProcess, Boolean.FALSE);
+ }
+ }
+ }
+
+ private void purgeSharedMemoryForAppNodes() {
+ LOGGER.info("No more application nodes, clearing cluster information about application nodes.");
+ hzMember.getAtomicReference(LEADER).clear();
+ hzMember.getAtomicReference(SONARQUBE_VERSION).clear();
+ }
+ }
}
+++ /dev/null
-/*
- * 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 java.net.InetAddress;
-import java.net.NetworkInterface;
-import java.net.SocketException;
-import java.util.ArrayList;
-import java.util.Enumeration;
-import java.util.List;
-import javax.annotation.Nullable;
-import org.apache.commons.lang.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.application.config.AppSettings;
-import org.sonar.process.cluster.NodeType;
-
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_HOSTS;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_HOST;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_NAME;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_PORT;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_TYPE;
-
-/**
- * Properties of the cluster configuration
- */
-final class ClusterProperties {
- private static final String DEFAULT_PORT = "9003";
- private static final Logger LOGGER = LoggerFactory.getLogger(ClusterProperties.class);
- static final String HAZELCAST_CLUSTER_NAME = "sonarqube";
-
- private final int port;
- private final List<String> hosts;
- private final List<String> networkInterfaces;
- private final NodeType nodeType;
- private final String nodeName;
-
- ClusterProperties(AppSettings appSettings) {
- port = appSettings.getProps().valueAsInt(CLUSTER_NODE_PORT);
- networkInterfaces = extractNetworkInterfaces(appSettings.getProps().nonNullValue(CLUSTER_NODE_HOST));
- hosts = extractHosts(appSettings.getProps().nonNullValue(CLUSTER_HOSTS));
- nodeType = NodeType.parse(appSettings.getProps().nonNullValue(CLUSTER_NODE_TYPE));
- nodeName = appSettings.getProps().nonNullValue(CLUSTER_NODE_NAME);
- }
-
- int getPort() {
- return port;
- }
-
- public NodeType getNodeType() {
- return nodeType;
- }
-
- List<String> getHosts() {
- return hosts;
- }
-
- List<String> getNetworkInterfaces() {
- return networkInterfaces;
- }
-
- public String getNodeName() {
- return nodeName;
- }
-
- void validate() {
- // 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 networkInterfaces parameter
- try {
- List<String> localInterfaces = findAllLocalIPs();
-
- networkInterfaces.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 networkInterfaces. Interfaces won't be checked", e);
- }
- }
-
- private static List<String> extractHosts(final String hosts) {
- List<String> result = new ArrayList<>();
- for (String host : hosts.split(",")) {
- if (StringUtils.isNotEmpty(host)) {
- if (!host.contains(":")) {
- result.add(
- String.format("%s:%s", host, DEFAULT_PORT));
- } else {
- result.add(host);
- }
- }
- }
- return result;
- }
-
- private static List<String> extractNetworkInterfaces(final String networkInterfaces) {
- List<String> result = new ArrayList<>();
- for (String iface : networkInterfaces.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));
- }
- }
-}
+++ /dev/null
-/*
- * 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.google.common.collect.ImmutableSet;
-import com.hazelcast.config.Config;
-import com.hazelcast.config.JoinConfig;
-import com.hazelcast.config.NetworkConfig;
-import com.hazelcast.core.Client;
-import com.hazelcast.core.ClientListener;
-import com.hazelcast.core.EntryEvent;
-import com.hazelcast.core.EntryListener;
-import com.hazelcast.core.Hazelcast;
-import com.hazelcast.core.HazelcastInstance;
-import com.hazelcast.core.HazelcastInstanceNotActiveException;
-import com.hazelcast.core.IAtomicReference;
-import com.hazelcast.core.ILock;
-import com.hazelcast.core.MapEvent;
-import com.hazelcast.core.Member;
-import com.hazelcast.core.MemberAttributeEvent;
-import com.hazelcast.core.MembershipEvent;
-import com.hazelcast.core.MembershipListener;
-import com.hazelcast.core.ReplicatedMap;
-import com.hazelcast.nio.Address;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.locks.Lock;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.application.AppStateListener;
-import org.sonar.process.MessageException;
-import org.sonar.process.NetworkUtils;
-import org.sonar.process.ProcessId;
-import org.sonar.process.cluster.NodeType;
-import org.sonar.process.cluster.ClusterObjectKeys;
-import org.sonar.process.cluster.HazelcastClient;
-
-import static java.lang.String.format;
-import static java.util.stream.Collectors.toList;
-import static org.sonar.application.cluster.ClusterProperties.HAZELCAST_CLUSTER_NAME;
-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.LOCAL_MEMBER_UUIDS;
-import static org.sonar.process.cluster.ClusterObjectKeys.NODE_NAME;
-import static org.sonar.process.cluster.ClusterObjectKeys.NODE_TYPE;
-import static org.sonar.process.cluster.ClusterObjectKeys.OPERATIONAL_PROCESSES;
-import static org.sonar.process.cluster.ClusterObjectKeys.SONARQUBE_VERSION;
-
-public class HazelcastCluster implements AutoCloseable {
- private static final Logger LOGGER = LoggerFactory.getLogger(HazelcastCluster.class);
-
- private final List<AppStateListener> listeners = new ArrayList<>();
- private final ReplicatedMap<ClusterProcess, Boolean> operationalProcesses;
- private final String operationalProcessListenerUUID;
- private final String clientListenerUUID;
- private final String nodeDisconnectedListenerUUID;
-
- protected final HazelcastInstance hzInstance;
-
- private HazelcastCluster(Config hzConfig) {
- // Create the Hazelcast instance
- hzInstance = Hazelcast.newHazelcastInstance(hzConfig);
-
- // Get or create the replicated map
- operationalProcesses = hzInstance.getReplicatedMap(OPERATIONAL_PROCESSES);
- operationalProcessListenerUUID = operationalProcesses.addEntryListener(new OperationalProcessListener());
- clientListenerUUID = hzInstance.getClientService().addClientListener(new ConnectedClientListener());
- nodeDisconnectedListenerUUID = hzInstance.getCluster().addMembershipListener(new NodeDisconnectedListener());
- }
-
- String getLocalUUID() {
- return hzInstance.getLocalEndpoint().getUuid();
- }
-
- String getName() {
- return hzInstance.getConfig().getGroupConfig().getName();
- }
-
- List<String> getMembers() {
- return hzInstance.getCluster().getMembers().stream()
- .filter(m -> !m.localMember())
- .map(m -> format("%s (%s)", m.getStringAttribute(HOSTNAME), m.getStringAttribute(IP_ADDRESSES)))
- .collect(toList());
- }
-
- void addListener(AppStateListener listener) {
- listeners.add(listener);
- }
-
- boolean isOperational(ProcessId processId) {
- for (Map.Entry<ClusterProcess, Boolean> entry : operationalProcesses.entrySet()) {
- if (entry.getKey().getProcessId().equals(processId) && entry.getValue()) {
- return true;
- }
- }
- return false;
- }
-
- void setOperational(ProcessId processId) {
- operationalProcesses.put(new ClusterProcess(getLocalUUID(), processId), Boolean.TRUE);
- }
-
- boolean tryToLockWebLeader() {
- IAtomicReference<String> leader = hzInstance.getAtomicReference(LEADER);
- if (leader.get() == null) {
- ILock lock = hzInstance.getLock(LEADER);
- lock.lock();
- try {
- if (leader.get() == null) {
- leader.set(getLocalUUID());
- return true;
- } else {
- return false;
- }
- } finally {
- lock.unlock();
- }
- } else {
- return false;
- }
- }
-
- 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)) {
- throw new IllegalStateException(
- format("The local version %s is not the same as the cluster %s", sonarqubeVersion, clusterVersion));
- }
- }
-
- public String getSonarQubeVersion() {
- IAtomicReference<String> sqVersion = hzInstance.getAtomicReference(SONARQUBE_VERSION);
- return sqVersion.get();
- }
-
- 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));
- }
- }
-
- @Override
- public void close() {
- if (hzInstance != null) {
- try {
- // Removing listeners
- operationalProcesses.removeEntryListener(operationalProcessListenerUUID);
- hzInstance.getClientService().removeClientListener(clientListenerUUID);
- hzInstance.getCluster().removeMembershipListener(nodeDisconnectedListenerUUID);
-
- // Removing the operationalProcess from the replicated map
- operationalProcesses.keySet().forEach(
- clusterNodeProcess -> {
- if (clusterNodeProcess.getNodeUuid().equals(getLocalUUID())) {
- operationalProcesses.remove(clusterNodeProcess);
- }
- });
-
- // Shutdown Hazelcast properly
- hzInstance.shutdown();
- } catch (HazelcastInstanceNotActiveException e) {
- // hazelcastCluster may be already closed by the shutdown hook
- LOGGER.debug("Unable to close Hazelcast cluster", e);
- }
- }
- }
-
- public static HazelcastCluster create(ClusterProperties clusterProperties) {
- Config hzConfig = new Config();
- hzConfig.getGroupConfig().setName(HAZELCAST_CLUSTER_NAME);
-
- // Configure the network instance
- NetworkConfig netConfig = hzConfig.getNetworkConfig();
- netConfig
- .setPort(clusterProperties.getPort())
- .setReuseAddress(true);
-
- if (!clusterProperties.getNetworkInterfaces().isEmpty()) {
- netConfig.getInterfaces()
- .setEnabled(true)
- .setInterfaces(clusterProperties.getNetworkInterfaces());
- }
-
- // 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.getHosts());
-
- // 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");
-
- // Trying to resolve the hostname
- hzConfig.getMemberAttributeConfig()
- .setStringAttribute(NODE_NAME, clusterProperties.getNodeName());
- hzConfig.getMemberAttributeConfig()
- .setStringAttribute(HOSTNAME, NetworkUtils.INSTANCE.getHostname());
- hzConfig.getMemberAttributeConfig()
- .setStringAttribute(IP_ADDRESSES, NetworkUtils.INSTANCE.getIPAddresses());
- hzConfig.getMemberAttributeConfig()
- .setStringAttribute(NODE_TYPE, clusterProperties.getNodeType().getValue());
-
- // We are not using the partition group of Hazelcast, so disabling it
- hzConfig.getPartitionGroupConfig().setEnabled(false);
- return new HazelcastCluster(hzConfig);
- }
-
- Optional<String> getLeaderHostName() {
- String leaderId = (String) hzInstance.getAtomicReference(LEADER).get();
- if (leaderId != null) {
- Optional<Member> leader = hzInstance.getCluster().getMembers().stream().filter(m -> m.getUuid().equals(leaderId)).findFirst();
- if (leader.isPresent()) {
- return Optional.of(
- format("%s (%s)", leader.get().getStringAttribute(HOSTNAME), leader.get().getStringAttribute(IP_ADDRESSES)));
- }
- }
- return Optional.empty();
- }
-
- String getLocalEndPoint() {
- Address localAddress = hzInstance.getCluster().getLocalMember().getAddress();
- return format("%s:%d", localAddress.getHost(), localAddress.getPort());
- }
-
- public HazelcastClient getHazelcastClient() {
- return new HazelcastInstanceClient(hzInstance);
- }
-
- private static class HazelcastInstanceClient implements HazelcastClient {
- private final HazelcastInstance hzInstance;
-
- private HazelcastInstanceClient(HazelcastInstance hzInstance) {
- this.hzInstance = hzInstance;
- }
-
- @Override
- public <E> Set<E> getSet(String s) {
- return hzInstance.getSet(s);
- }
-
- @Override
- public <E> List<E> getList(String s) {
- return hzInstance.getList(s);
- }
-
- @Override
- public <K, V> Map<K, V> getMap(String s) {
- return hzInstance.getMap(s);
- }
-
- @Override
- public <K, V> Map<K, V> getReplicatedMap(String s) {
- return hzInstance.getReplicatedMap(s);
- }
-
- @Override
- public String getUUID() {
- return hzInstance.getLocalEndpoint().getUuid();
- }
-
- @Override
- public Set<String> getMemberUuids() {
- ImmutableSet.Builder<String> builder = ImmutableSet.builder();
- builder.addAll(hzInstance.getSet(ClusterObjectKeys.LOCAL_MEMBER_UUIDS));
- hzInstance.getCluster().getMembers().stream().map(Member::getUuid).forEach(builder::add);
- return builder.build();
- }
-
- @Override
- public Lock getLock(String s) {
- return hzInstance.getLock(s);
- }
-
- @Override
- public long getClusterTime() {
- return hzInstance.getCluster().getClusterTime();
- }
- }
-
- private class OperationalProcessListener implements EntryListener<ClusterProcess, Boolean> {
- @Override
- public void entryAdded(EntryEvent<ClusterProcess, Boolean> event) {
- if (event.getValue()) {
- listeners.forEach(appStateListener -> appStateListener.onAppStateOperational(event.getKey().getProcessId()));
- }
- }
-
- @Override
- public void entryRemoved(EntryEvent<ClusterProcess, Boolean> event) {
- // Ignore it
- }
-
- @Override
- public void entryUpdated(EntryEvent<ClusterProcess, Boolean> event) {
- if (event.getValue()) {
- listeners.forEach(appStateListener -> appStateListener.onAppStateOperational(event.getKey().getProcessId()));
- }
- }
-
- @Override
- public void entryEvicted(EntryEvent<ClusterProcess, Boolean> event) {
- // Ignore it
- }
-
- @Override
- public void mapCleared(MapEvent event) {
- // Ignore it
- }
-
- @Override
- public void mapEvicted(MapEvent event) {
- // Ignore it
- }
- }
-
- private class ConnectedClientListener implements ClientListener {
- @Override
- public void clientConnected(Client client) {
- hzInstance.getSet(LOCAL_MEMBER_UUIDS).add(client.getUuid());
- }
-
- @Override
- public void clientDisconnected(Client client) {
- hzInstance.getSet(LOCAL_MEMBER_UUIDS).remove(client.getUuid());
- }
- }
-
- private class NodeDisconnectedListener implements MembershipListener {
- @Override
- public void memberAdded(MembershipEvent membershipEvent) {
- // Nothing to do
- }
-
- @Override
- public void memberRemoved(MembershipEvent membershipEvent) {
- removeOperationalProcess(membershipEvent.getMember().getUuid());
- if (membershipEvent.getMembers().stream()
- .noneMatch(this::isAppNode)) {
- purgeSharedMemoryForAppNodes();
- }
- }
-
- @Override
- public void memberAttributeChanged(MemberAttributeEvent memberAttributeEvent) {
- // Nothing to do
- }
-
- private boolean isAppNode(Member member) {
- return NodeType.APPLICATION.getValue().equals(member.getStringAttribute(NODE_TYPE));
- }
-
- private void removeOperationalProcess(String uuid) {
- for (ClusterProcess clusterProcess : operationalProcesses.keySet()) {
- if (clusterProcess.getNodeUuid().equals(uuid)) {
- LOGGER.debug("Set node process off for [{}:{}] : ", clusterProcess.getNodeUuid(), clusterProcess.getProcessId());
- hzInstance.getReplicatedMap(OPERATIONAL_PROCESSES).put(clusterProcess, Boolean.FALSE);
- }
- }
- }
-
- private void purgeSharedMemoryForAppNodes() {
- LOGGER.info("No more application nodes, clearing cluster information about application nodes.");
- hzInstance.getAtomicReference(LEADER).clear();
- hzInstance.getAtomicReference(SONARQUBE_VERSION).clear();
- }
- }
-}
+++ /dev/null
-/*
- * 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.sonar.process.NetworkUtils;
-import org.sonar.process.ProcessId;
-import org.sonar.process.Props;
-import org.sonar.process.cluster.health.NodeDetails;
-import org.sonar.process.cluster.health.NodeHealth;
-import org.sonar.process.cluster.health.NodeHealthProvider;
-
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_HOST;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_NAME;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_PORT;
-
-public class SearchNodeHealthProvider implements NodeHealthProvider {
-
- private final ClusterAppState clusterAppState;
- private final NodeDetails nodeDetails;
-
- public SearchNodeHealthProvider(Props props, ClusterAppState clusterAppState, NetworkUtils networkUtils) {
- this(props, clusterAppState, networkUtils, new Clock());
- }
-
- SearchNodeHealthProvider(Props props, ClusterAppState clusterAppState, NetworkUtils networkUtils, Clock clock) {
- this.clusterAppState = clusterAppState;
- this.nodeDetails = NodeDetails.newNodeDetailsBuilder()
- .setType(NodeDetails.Type.SEARCH)
- .setName(props.nonNullValue(CLUSTER_NODE_NAME))
- .setHost(getHost(props, networkUtils))
- .setPort(Integer.valueOf(props.nonNullValue(CLUSTER_NODE_PORT)))
- .setStartedAt(clock.now())
- .build();
- }
-
- private static String getHost(Props props, NetworkUtils networkUtils) {
- String host = props.value(CLUSTER_NODE_HOST);
- if (host != null && !host.isEmpty()) {
- return host;
- }
- return networkUtils.getHostname();
- }
-
- @Override
- public NodeHealth get() {
- NodeHealth.Builder builder = NodeHealth.newNodeHealthBuilder();
- if (clusterAppState.isOperational(ProcessId.ELASTICSEARCH, true)) {
- builder.setStatus(NodeHealth.Status.GREEN);
- } else {
- builder.setStatus(NodeHealth.Status.RED)
- .addCause("Elasticsearch is not operational");
- }
- return builder
- .setDetails(nodeDetails)
- .build();
- }
-
- static class Clock {
- long now() {
- return System.currentTimeMillis();
- }
- }
-}
--- /dev/null
+/*
+ * 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.health;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import org.sonar.process.cluster.health.HealthStateRefresherExecutorService;
+
+class DelegateHealthStateRefresherExecutorService implements HealthStateRefresherExecutorService {
+ private final ScheduledExecutorService delegate;
+
+ DelegateHealthStateRefresherExecutorService(ScheduledExecutorService delegate) {
+ this.delegate = delegate;
+ }
+
+ @Override
+ public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
+ return delegate.schedule(command, delay, unit);
+ }
+
+ @Override
+ public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
+ return delegate.schedule(callable, delay, unit);
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
+ return delegate.scheduleAtFixedRate(command, initialDelay, period, unit);
+ }
+
+ @Override
+ public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
+ return delegate.scheduleWithFixedDelay(command, initialDelay, delay, unit);
+ }
+
+ @Override
+ public void shutdown() {
+ delegate.shutdown();
+ }
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ return delegate.shutdownNow();
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return delegate.isShutdown();
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return delegate.isTerminated();
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
+ return delegate.awaitTermination(timeout, unit);
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ return delegate.submit(task);
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ return delegate.submit(task, result);
+ }
+
+ @Override
+ public Future<?> submit(Runnable task) {
+ return delegate.submit(task);
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
+ return delegate.invokeAll(tasks);
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException {
+ return delegate.invokeAll(tasks, timeout, unit);
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
+ return delegate.invokeAny(tasks);
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
+ return delegate.invokeAny(tasks, timeout, unit);
+ }
+
+ @Override
+ public void execute(Runnable command) {
+ delegate.execute(command);
+ }
+}
--- /dev/null
+/*
+ * 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.health;
+
+public interface HealthStateSharing {
+ void start();
+
+ void stop();
+}
--- /dev/null
+/*
+ * 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.health;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.cluster.health.HealthStateRefresher;
+import org.sonar.process.cluster.health.HealthStateRefresherExecutorService;
+import org.sonar.process.cluster.health.NodeHealthProvider;
+import org.sonar.process.cluster.health.SharedHealthStateImpl;
+import org.sonar.process.cluster.hz.HazelcastMember;
+
+import static java.lang.String.format;
+
+public class HealthStateSharingImpl implements HealthStateSharing {
+ private static final Logger LOG = LoggerFactory.getLogger(HealthStateSharingImpl.class);
+
+ private final HazelcastMember hzMember;
+ private final NodeHealthProvider nodeHealthProvider;
+ private HealthStateRefresherExecutorService executorService;
+ private HealthStateRefresher healthStateRefresher;
+
+ public HealthStateSharingImpl(HazelcastMember hzMember, NodeHealthProvider nodeHealthProvider) {
+ this.hzMember = hzMember;
+ this.nodeHealthProvider = nodeHealthProvider;
+ }
+
+ @Override
+ public void start() {
+ executorService = new DelegateHealthStateRefresherExecutorService(
+ Executors.newSingleThreadScheduledExecutor(
+ new ThreadFactoryBuilder()
+ .setDaemon(false)
+ .setNameFormat("health_state_refresh-%d")
+ .build()));
+ healthStateRefresher = new HealthStateRefresher(executorService, nodeHealthProvider, new SharedHealthStateImpl(hzMember));
+ healthStateRefresher.start();
+ }
+
+ @Override
+ public void stop() {
+ healthStateRefresher.stop();
+ stopExecutorService(executorService);
+ }
+
+ private static void stopExecutorService(ScheduledExecutorService executorService) {
+ // Disable new tasks from being submitted
+ executorService.shutdown();
+ try {
+ // Wait a while for existing tasks to terminate
+ if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
+ // Cancel currently executing tasks
+ executorService.shutdownNow();
+ // Wait a while for tasks to respond to being canceled
+ if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
+ LOG.warn("Pool {} did not terminate", HealthStateSharingImpl.class.getSimpleName());
+ }
+ }
+ } catch (InterruptedException ie) {
+ LOG.warn(format("Termination of pool %s failed", HealthStateSharingImpl.class.getSimpleName()), ie);
+ // (Re-)Cancel if current thread also interrupted
+ executorService.shutdownNow();
+ Thread.currentThread().interrupt();
+ }
+ }
+
+}
--- /dev/null
+/*
+ * 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.health;
+
+import org.sonar.application.cluster.ClusterAppState;
+import org.sonar.process.NetworkUtils;
+import org.sonar.process.ProcessId;
+import org.sonar.process.Props;
+import org.sonar.process.cluster.health.NodeDetails;
+import org.sonar.process.cluster.health.NodeHealth;
+import org.sonar.process.cluster.health.NodeHealthProvider;
+
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_HOST;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_NAME;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_PORT;
+
+public class SearchNodeHealthProvider implements NodeHealthProvider {
+
+ private final ClusterAppState clusterAppState;
+ private final NodeDetails nodeDetails;
+
+ public SearchNodeHealthProvider(Props props, ClusterAppState clusterAppState, NetworkUtils networkUtils) {
+ this(props, clusterAppState, networkUtils, new Clock());
+ }
+
+ SearchNodeHealthProvider(Props props, ClusterAppState clusterAppState, NetworkUtils networkUtils, Clock clock) {
+ this.clusterAppState = clusterAppState;
+ this.nodeDetails = NodeDetails.newNodeDetailsBuilder()
+ .setType(NodeDetails.Type.SEARCH)
+ .setName(props.nonNullValue(CLUSTER_NODE_NAME))
+ .setHost(getHost(props, networkUtils))
+ .setPort(Integer.valueOf(props.nonNullValue(CLUSTER_NODE_PORT)))
+ .setStartedAt(clock.now())
+ .build();
+ }
+
+ private static String getHost(Props props, NetworkUtils networkUtils) {
+ String host = props.value(CLUSTER_NODE_HOST);
+ if (host != null && !host.isEmpty()) {
+ return host;
+ }
+ return networkUtils.getHostname();
+ }
+
+ @Override
+ public NodeHealth get() {
+ NodeHealth.Builder builder = NodeHealth.newNodeHealthBuilder();
+ if (clusterAppState.isOperational(ProcessId.ELASTICSEARCH, true)) {
+ builder.setStatus(NodeHealth.Status.GREEN);
+ } else {
+ builder.setStatus(NodeHealth.Status.RED)
+ .addCause("Elasticsearch is not operational");
+ }
+ return builder
+ .setDetails(nodeDetails)
+ .build();
+ }
+
+ static class Clock {
+ long now() {
+ return System.currentTimeMillis();
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.application.cluster.health;
+
+import javax.annotation.ParametersAreNonnullByDefault;
import java.util.function.Consumer;
import org.slf4j.LoggerFactory;
import org.sonar.process.ConfigurationUtils;
+import org.sonar.process.NetworkUtils;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
private final Consumer<Props>[] consumers;
public AppSettingsLoaderImpl(String[] cliArguments) {
- this(cliArguments, detectHomeDir(), new FileSystemSettings(), new JdbcSettings(), new ClusterSettings());
+ this(cliArguments, detectHomeDir(),
+ new FileSystemSettings(), new JdbcSettings(), new ClusterSettings(NetworkUtils.INSTANCE));
}
AppSettingsLoaderImpl(String[] cliArguments, File homeDir, Consumer<Props>... consumers) {
*/
package org.sonar.application.config;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.net.HostAndPort;
-import com.google.common.net.InetAddresses;
import java.net.InetAddress;
-import java.net.NetworkInterface;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.function.Consumer;
import org.apache.commons.lang.StringUtils;
import org.sonar.process.MessageException;
-import org.sonar.process.cluster.NodeType;
+import org.sonar.process.NetworkUtils;
import org.sonar.process.ProcessId;
import org.sonar.process.Props;
+import org.sonar.process.cluster.NodeType;
-import static com.google.common.net.InetAddresses.forString;
import static java.lang.String.format;
import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.Collections.singletonList;
import static java.util.stream.Collectors.joining;
import static org.apache.commons.lang.StringUtils.isBlank;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_HOSTS;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_HOST;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_TYPE;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_SEARCH_HOSTS;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_WEB_LEADER;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_HOSTS;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_HOST;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_TYPE;
+import static org.sonar.process.ProcessProperties.CLUSTER_SEARCH_HOSTS;
+import static org.sonar.process.ProcessProperties.CLUSTER_WEB_STARTUP_LEADER;
import static org.sonar.process.ProcessProperties.JDBC_URL;
import static org.sonar.process.ProcessProperties.SEARCH_HOST;
public class ClusterSettings implements Consumer<Props> {
+ private final NetworkUtils network;
+
+ public ClusterSettings(NetworkUtils network) {
+ this.network = network;
+ }
+
@Override
public void accept(Props props) {
if (isClusterEnabled(props)) {
- checkProperties(props);
+ checkClusterProperties(props);
}
}
- private static void checkProperties(Props props) {
- // Cluster web leader is not allowed
- if (props.value(CLUSTER_WEB_LEADER) != null) {
- throw new MessageException(format("Property [%s] is forbidden", CLUSTER_WEB_LEADER));
- }
-
- // Mandatory properties
- ensureMandatoryProperty(props, CLUSTER_NODE_TYPE);
- String nodeTypeValue = props.nonNullValue(CLUSTER_NODE_TYPE);
- if (!NodeType.isValid(nodeTypeValue)) {
- throw new MessageException(format("Invalid value for property [%s]: [%s], only [%s] are allowed", CLUSTER_NODE_TYPE, nodeTypeValue,
- Arrays.stream(NodeType.values()).map(NodeType::getValue).collect(joining(", "))));
+ private void checkClusterProperties(Props props) {
+ // for internal use
+ if (props.value(CLUSTER_WEB_STARTUP_LEADER) != null) {
+ throw new MessageException(format("Property [%s] is forbidden", CLUSTER_WEB_STARTUP_LEADER));
}
- ensureMandatoryProperty(props, CLUSTER_HOSTS);
- ensureMandatoryProperty(props, CLUSTER_SEARCH_HOSTS);
- NodeType nodeType = NodeType.parse(nodeTypeValue);
+ NodeType nodeType = toNodeType(props);
switch (nodeType) {
case APPLICATION:
ensureNotH2(props);
- ensureMandatoryProperty(props, "sonar.auth.jwtBase64Hs256Secret");
+ requireValue(props, "sonar.auth.jwtBase64Hs256Secret");
break;
case SEARCH:
- ensureMandatoryProperty(props, SEARCH_HOST);
- ensureNotLoopback(props, SEARCH_HOST);
+ requireValue(props, SEARCH_HOST);
+ ensureLocalButNotLoopbackAddress(props, SEARCH_HOST);
break;
default:
- throw new IllegalArgumentException("Unsupported node type: " + nodeType);
+ throw new UnsupportedOperationException("Unknown value: " + nodeType);
}
-
- // Loopback interfaces are forbidden for the ports accessed
- // by other nodes of cluster
- ensureNotLoopback(props, CLUSTER_HOSTS);
- ensureNotLoopback(props, CLUSTER_NODE_HOST);
- ensureNotLoopback(props, CLUSTER_SEARCH_HOSTS);
-
- ensureLocalAddress(props, SEARCH_HOST);
- ensureLocalAddress(props, CLUSTER_NODE_HOST);
+ ensureNotLoopbackAddresses(props, CLUSTER_HOSTS);
+ requireValue(props, CLUSTER_NODE_HOST);
+ ensureLocalButNotLoopbackAddress(props, CLUSTER_NODE_HOST);
+ ensureNotLoopbackAddresses(props, CLUSTER_SEARCH_HOSTS);
}
- private static void ensureNotH2(Props props) {
- String jdbcUrl = props.value(JDBC_URL);
- if (isBlank(jdbcUrl) || jdbcUrl.startsWith("jdbc:h2:")) {
- throw new MessageException("Embedded database is not supported in cluster mode");
+ private static NodeType toNodeType(Props props) {
+ String nodeTypeValue = requireValue(props, CLUSTER_NODE_TYPE);
+ if (!NodeType.isValid(nodeTypeValue)) {
+ throw new MessageException(format("Invalid value for property %s: [%s], only [%s] are allowed", CLUSTER_NODE_TYPE, nodeTypeValue,
+ Arrays.stream(NodeType.values()).map(NodeType::getValue).collect(joining(", "))));
}
+ return NodeType.parse(nodeTypeValue);
}
- private static void ensureMandatoryProperty(Props props, String key) {
- if (isBlank(props.value(key))) {
- throw new MessageException(format("Property [%s] is mandatory", key));
+ private static String requireValue(Props props, String key) {
+ String value = props.value(key);
+ if (isBlank(value)) {
+ throw new MessageException(format("Property %s is mandatory", key));
}
+ return value;
}
- @VisibleForTesting
- private static void ensureNotLoopback(Props props, String key) {
- String ipList = props.value(key);
- if (ipList == null) {
- return;
+ private static void ensureNotH2(Props props) {
+ String jdbcUrl = props.value(JDBC_URL);
+ if (isBlank(jdbcUrl) || jdbcUrl.startsWith("jdbc:h2:")) {
+ throw new MessageException("Embedded database is not supported in cluster mode");
}
-
- stream(ipList.split(","))
- .filter(StringUtils::isNotBlank)
- .map(StringUtils::trim)
- .forEach(ip -> {
- InetAddress inetAddress = convertToInetAddress(ip, key);
- if (inetAddress.isLoopbackAddress()) {
- throw new MessageException(format("The interface address [%s] of [%s] must not be a loopback address", ip, key));
- }
- });
}
- private static void ensureLocalAddress(Props props, String key) {
- String ipList = props.value(key);
-
- if (ipList == null) {
- return;
- }
-
- stream(ipList.split(","))
+ private void ensureNotLoopbackAddresses(Props props, String propertyKey) {
+ stream(requireValue(props, propertyKey).split(","))
.filter(StringUtils::isNotBlank)
.map(StringUtils::trim)
+ .map(s -> StringUtils.substringBefore(s, ":"))
.forEach(ip -> {
- InetAddress inetAddress = convertToInetAddress(ip, key);
try {
- if (NetworkInterface.getByInetAddress(inetAddress) == null) {
- throw new MessageException(format("The interface address [%s] of [%s] is not a local address", ip, key));
+ if (network.isLoopbackInetAddress(network.toInetAddress(ip))) {
+ throw new MessageException(format("Property %s must not be a loopback address: %s", propertyKey, ip));
}
- } catch (SocketException e) {
- throw new MessageException(format("The interface address [%s] of [%s] is not a local address", ip, key));
+ } catch (UnknownHostException e) {
+ throw new MessageException(format("Property %s must not a valid address: %s [%s]", propertyKey, ip, e.getMessage()));
}
});
}
- private static InetAddress convertToInetAddress(String text, String key) {
- InetAddress inetAddress;
- HostAndPort hostAndPort = HostAndPort.fromString(text);
- if (!InetAddresses.isInetAddress(hostAndPort.getHostText())) {
- try {
- inetAddress = InetAddress.getByName(hostAndPort.getHostText());
- } catch (UnknownHostException e) {
- throw new MessageException(format("The interface address [%s] of [%s] cannot be resolved : %s", text, key, e.getMessage()));
+ private void ensureLocalButNotLoopbackAddress(Props props, String propertyKey) {
+ String propertyValue = props.nonNullValue(propertyKey).trim();
+ try {
+ InetAddress address = network.toInetAddress(propertyValue);
+ if (!network.isLocalInetAddress(address) || network.isLoopbackInetAddress(address)) {
+ throw new MessageException(format("Property %s must be a local non-loopback address: %s", propertyKey, propertyValue));
}
- } else {
- inetAddress = forString(hostAndPort.getHostText());
+ } catch (UnknownHostException | SocketException e) {
+ throw new MessageException(format("Property %s must be a local non-loopback address: %s [%s]", propertyKey, propertyValue, e.getMessage()));
}
-
- return inetAddress;
}
public static boolean isClusterEnabled(AppSettings settings) {
if (!isClusterEnabled(settings)) {
return asList(ProcessId.ELASTICSEARCH, ProcessId.WEB_SERVER, ProcessId.COMPUTE_ENGINE);
}
- NodeType nodeType = NodeType.parse(settings.getValue(CLUSTER_NODE_TYPE).orElse(null));
+ NodeType nodeType = NodeType.parse(settings.getValue(CLUSTER_NODE_TYPE).orElse(""));
switch (nodeType) {
case APPLICATION:
return asList(ProcessId.WEB_SERVER, ProcessId.COMPUTE_ENGINE);
public static boolean isLocalElasticsearchEnabled(AppSettings settings) {
// elasticsearch is enabled on "search" nodes, but disabled on "application" nodes
if (isClusterEnabled(settings.getProps())) {
- return NodeType.parse(settings.getValue(CLUSTER_NODE_TYPE).orElse(null)) == NodeType.SEARCH;
+ return NodeType.parse(settings.getValue(CLUSTER_NODE_TYPE).orElse("")) == NodeType.SEARCH;
}
// elasticsearch is enabled in standalone mode
import org.sonar.process.System2;
import static java.lang.String.valueOf;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NAME;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_NAME;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_SEARCH_HOSTS;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_NAME;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_NAME;
+import static org.sonar.process.ProcessProperties.CLUSTER_SEARCH_HOSTS;
public class EsSettings {
import static org.assertj.core.api.Assertions.assertThat;
import static org.slf4j.Logger.ROOT_LOGGER_NAME;
import static org.sonar.application.process.StreamGobbler.LOGGER_GOBBLER;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
-import static org.sonar.process.cluster.ClusterProperties.HAZELCAST_LOG_LEVEL;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
public class AppLoggingTest {
LoggerFactory.getLogger("com.hazelcast").isInfoEnabled()).isEqualTo(false);
}
- @Test
- public void configure_logging_for_hazelcast() throws IOException {
- settings.getProps().set(CLUSTER_ENABLED, "true");
- settings.getProps().set(HAZELCAST_LOG_LEVEL, "INFO");
- underTest.configure();
-
- assertThat(
- LoggerFactory.getLogger("com.hazelcast").isInfoEnabled()).isEqualTo(true);
- assertThat(
- LoggerFactory.getLogger("com.hazelcast").isDebugEnabled()).isEqualTo(false);
- }
-
private void emulateRunFromSonarScript() {
settings.getProps().set("sonar.wrapped", "true");
}
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
public class AppReloaderImplTest {
*/
package org.sonar.application;
+import java.net.InetAddress;
+import java.util.Optional;
+import org.hamcrest.CoreMatchers;
import org.junit.Test;
import org.sonar.application.cluster.ClusterAppStateImpl;
import org.sonar.application.config.TestAppSettings;
+import org.sonar.process.NetworkUtils;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NAME;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_TYPE;
+import static org.junit.Assume.assumeThat;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_HOSTS;
+import static org.sonar.process.ProcessProperties.CLUSTER_NAME;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_HOST;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_TYPE;
public class AppStateFactoryTest {
@Test
public void create_cluster_implementation_if_cluster_is_enabled() {
+ Optional<InetAddress> ip = NetworkUtils.INSTANCE.getLocalNonLoopbackIpv4Address();
+ assumeThat(ip.isPresent(), CoreMatchers.is(true));
+
settings.set(CLUSTER_ENABLED, "true");
settings.set(CLUSTER_NODE_TYPE, "application");
+ settings.set(CLUSTER_NODE_HOST, ip.get().getHostAddress());
+ settings.set(CLUSTER_HOSTS, ip.get().getHostAddress());
settings.set(CLUSTER_NAME, "foo");
AppState appState = underTest.create();
assertThat(appState).isInstanceOf(ClusterAppStateImpl.class);
- ((ClusterAppStateImpl) appState).close();
+ appState.close();
}
@Test
import org.sonar.application.process.ProcessLauncher;
import org.sonar.application.process.ProcessMonitor;
import org.sonar.process.ProcessId;
-import org.sonar.process.cluster.HazelcastClient;
+import org.sonar.process.cluster.hz.HazelcastMember;
import static java.util.Collections.synchronizedList;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.sonar.process.ProcessId.COMPUTE_ENGINE;
import static org.sonar.process.ProcessId.ELASTICSEARCH;
import static org.sonar.process.ProcessId.WEB_SERVER;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_HOST;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_NAME;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_PORT;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_TYPE;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_HOST;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_NAME;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_PORT;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_TYPE;
public class SchedulerImplTest {
private TestCommandFactory javaCommandFactory = new TestCommandFactory();
private TestProcessLauncher processLauncher = new TestProcessLauncher();
private TestAppState appState = new TestAppState();
- private HazelcastClient hazelcastClient = mock(HazelcastClient.class);
- private TestClusterAppState clusterAppState = new TestClusterAppState(hazelcastClient);
+ private HazelcastMember hazelcastMember = mock(HazelcastMember.class);
+ private TestClusterAppState clusterAppState = new TestClusterAppState(hazelcastMember);
private List<ProcessId> orderedStops = synchronizedList(new ArrayList<>());
@Before
package org.sonar.application;
import org.sonar.application.cluster.ClusterAppState;
-import org.sonar.process.cluster.HazelcastClient;
+import org.sonar.process.cluster.hz.HazelcastMember;
public class TestClusterAppState extends TestAppState implements ClusterAppState {
- private final HazelcastClient hazelcastClient;
+ private final HazelcastMember hazelcastMember;
- public TestClusterAppState(HazelcastClient hazelcastClient) {
- this.hazelcastClient = hazelcastClient;
+ public TestClusterAppState(HazelcastMember hazelcastMember) {
+ this.hazelcastMember = hazelcastMember;
}
@Override
- public HazelcastClient getHazelcastClient() {
- return hazelcastClient;
+ public HazelcastMember getHazelcastMember() {
+ return hazelcastMember;
}
}
*/
package org.sonar.application.cluster;
-import com.hazelcast.core.HazelcastInstance;
-import java.io.IOException;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.DisableOnDebug;
import org.junit.rules.ExpectedException;
import org.junit.rules.TestRule;
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 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;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.timeout;
import static org.mockito.Mockito.verify;
-import static org.sonar.application.cluster.HazelcastClusterTestHelper.createHazelcastClient;
-import static org.sonar.application.cluster.HazelcastClusterTestHelper.newApplicationSettings;
-import static org.sonar.process.cluster.ClusterObjectKeys.CLUSTER_NAME;
-import static org.sonar.process.cluster.ClusterObjectKeys.SONARQUBE_VERSION;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
+import static org.sonar.application.cluster.HazelcastTesting.newHzMember;
+import static org.sonar.process.cluster.hz.HazelcastObjects.CLUSTER_NAME;
+import static org.sonar.process.cluster.hz.HazelcastObjects.SONARQUBE_VERSION;
public class ClusterAppStateImplTest {
@Rule
public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
- @Test
- public void instantiation_throws_ISE_if_cluster_mode_is_disabled() throws Exception {
- TestAppSettings settings = new TestAppSettings();
- settings.set(CLUSTER_ENABLED, "false");
-
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("Cluster is not enabled on this instance");
-
- new ClusterAppStateImpl(settings);
- }
-
@Test
public void tryToLockWebLeader_returns_true_only_for_the_first_call() throws Exception {
- TestAppSettings settings = newApplicationSettings();
-
- try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(settings)) {
+ try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(newHzMember())) {
assertThat(underTest.tryToLockWebLeader()).isEqualTo(true);
assertThat(underTest.tryToLockWebLeader()).isEqualTo(false);
}
}
- @Test
- public void log_when_sonarqube_is_joining_a_cluster() throws IOException, InterruptedException, IllegalAccessException, NoSuchFieldException {
- // Now launch an instance that try to be part of the hzInstance cluster
- TestAppSettings settings = newApplicationSettings();
-
- Logger logger = mock(Logger.class);
- ClusterAppStateImpl.setLogger(logger);
-
- try (ClusterAppStateImpl appStateCluster = new ClusterAppStateImpl(settings)) {
- verify(logger).info(
- eq("Joined a SonarQube cluster that contains the following hosts : [{}]"),
- anyString());
- }
- }
-
@Test
public void test_listeners() throws InterruptedException {
AppStateListener listener = mock(AppStateListener.class);
- try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(newApplicationSettings())) {
+ try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(newHzMember())) {
underTest.addListener(listener);
underTest.setOperational(ProcessId.ELASTICSEARCH);
@Test
public void registerSonarQubeVersion_publishes_version_on_first_call() {
- TestAppSettings settings = newApplicationSettings();
- try (ClusterAppStateImpl appStateCluster = new ClusterAppStateImpl(settings)) {
- appStateCluster.registerSonarQubeVersion("6.4.1.5");
+ try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(newHzMember())) {
+ underTest.registerSonarQubeVersion("6.4.1.5");
- HazelcastInstance hzInstance = createHazelcastClient(appStateCluster);
- assertThat(hzInstance.getAtomicReference(SONARQUBE_VERSION).get())
- .isNotNull()
- .isInstanceOf(String.class)
+ assertThat(underTest.getHazelcastMember().getAtomicReference(SONARQUBE_VERSION).get())
.isEqualTo("6.4.1.5");
}
}
@Test
public void registerClusterName_publishes_clusterName_on_first_call() {
- TestAppSettings settings = newApplicationSettings();
- String clusterName = randomAlphanumeric(20);
+ try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(newHzMember())) {
+ underTest.registerClusterName("foo");
- try (ClusterAppStateImpl appStateCluster = new ClusterAppStateImpl(settings)) {
- appStateCluster.registerClusterName(clusterName);
-
- HazelcastInstance hzInstance = createHazelcastClient(appStateCluster);
- assertThat(hzInstance.getAtomicReference(CLUSTER_NAME).get())
- .isNotNull()
- .isInstanceOf(String.class)
- .isEqualTo(clusterName);
+ assertThat(underTest.getHazelcastMember().getAtomicReference(CLUSTER_NAME).get())
+ .isEqualTo("foo");
}
}
@Test
- public void reset_throws_always_ISE() {
- TestAppSettings settings = newApplicationSettings();
-
- try (ClusterAppStateImpl appStateCluster = new ClusterAppStateImpl(settings)) {
+ public void reset_always_throws_ISE() {
+ try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(newHzMember())) {
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("state reset is not supported in cluster mode");
- appStateCluster.reset();
+
+ underTest.reset();
}
}
@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 = newApplicationSettings();
-
- try (ClusterAppStateImpl appStateCluster = new ClusterAppStateImpl(settings)) {
+ try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(newHzMember())) {
// Register first version
- appStateCluster.registerSonarQubeVersion("1.0.0");
+ underTest.getHazelcastMember().getAtomicReference(SONARQUBE_VERSION).set("6.6.0.1111");
expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("The local version 2.0.0 is not the same as the cluster 1.0.0");
+ expectedException.expectMessage("The local version 6.7.0.9999 is not the same as the cluster 6.6.0.1111");
// Registering a second different version must trigger an exception
- appStateCluster.registerSonarQubeVersion("2.0.0");
+ underTest.registerSonarQubeVersion("6.7.0.9999");
}
}
@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 = newApplicationSettings();
-
- try (ClusterAppStateImpl appStateCluster = new ClusterAppStateImpl(settings)) {
+ try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(newHzMember())) {
// Register first version
- appStateCluster.registerClusterName("goodClusterName");
+ underTest.getHazelcastMember().getAtomicReference(CLUSTER_NAME).set("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");
+ underTest.registerClusterName("badClusterName");
}
}
}
+++ /dev/null
-/*
- * 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 java.util.Arrays;
-import java.util.Collections;
-import java.util.stream.Stream;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.application.config.AppSettings;
-import org.sonar.application.config.TestAppSettings;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_HOSTS;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NAME;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_HOST;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_PORT;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_TYPE;
-
-public class ClusterPropertiesTest {
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- private AppSettings appSettings = new TestAppSettings();
-
- @Test
- public void test_default_values() throws Exception {
- appSettings.getProps().set(CLUSTER_ENABLED, "true");
- appSettings.getProps().set(CLUSTER_NODE_TYPE, "application");
- ClusterProperties props = new ClusterProperties(appSettings);
-
- assertThat(props.getNetworkInterfaces())
- .isEqualTo(Collections.emptyList());
- assertThat(props.getPort())
- .isEqualTo(9003);
- assertThat(props.getHosts())
- .isEqualTo(Collections.emptyList());
- }
-
- @Test
- public void test_port_parameter() {
- appSettings.getProps().set(CLUSTER_ENABLED, "true");
- appSettings.getProps().set(CLUSTER_NAME, "sonarqube");
- appSettings.getProps().set(CLUSTER_NODE_TYPE, "application");
-
- Stream.of("-50", "0", "65536", "128563").forEach(
- port -> {
- appSettings.getProps().set(CLUSTER_NODE_PORT, port);
-
- ClusterProperties clusterProperties = new ClusterProperties(appSettings);
- 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() {
- appSettings.getProps().set(CLUSTER_ENABLED, "true");
- appSettings.getProps().set(CLUSTER_NAME, "sonarqube");
- appSettings.getProps().set(CLUSTER_NODE_HOST, "8.8.8.8"); // This IP belongs to Google
- appSettings.getProps().set(CLUSTER_NODE_TYPE, "application");
-
- ClusterProperties clusterProperties = new ClusterProperties(appSettings);
- 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 validate_does_not_fail_if_cluster_enabled_and_name_specified() {
- appSettings.getProps().set(CLUSTER_ENABLED, "true");
- appSettings.getProps().set(CLUSTER_NAME, "sonarqube");
- appSettings.getProps().set(CLUSTER_NODE_TYPE, "application");
-
- ClusterProperties clusterProperties = new ClusterProperties(appSettings);
- clusterProperties.validate();
- }
-
- @Test
- public void test_members() {
- appSettings.getProps().set(CLUSTER_ENABLED, "true");
- appSettings.getProps().set(CLUSTER_NAME, "sonarqube");
- appSettings.getProps().set(CLUSTER_NODE_TYPE, "application");
-
- assertThat(
- new ClusterProperties(appSettings).getHosts()).isEqualTo(
- Collections.emptyList());
-
- appSettings.getProps().set(CLUSTER_HOSTS, "192.168.1.1");
- assertThat(
- new ClusterProperties(appSettings).getHosts()).isEqualTo(
- Arrays.asList("192.168.1.1:9003"));
-
- appSettings.getProps().set(CLUSTER_HOSTS, "192.168.1.2:5501");
- assertThat(
- new ClusterProperties(appSettings).getHosts()).containsExactlyInAnyOrder(
- "192.168.1.2:5501");
-
- appSettings.getProps().set(CLUSTER_HOSTS, "192.168.1.2:5501,192.168.1.1");
- assertThat(
- new ClusterProperties(appSettings).getHosts()).containsExactlyInAnyOrder(
- "192.168.1.2:5501", "192.168.1.1:9003");
- }
-}
+++ /dev/null
-/*
- * 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 ch.qos.logback.classic.LoggerContext;
-import ch.qos.logback.classic.spi.ILoggingEvent;
-import ch.qos.logback.core.AppenderBase;
-import com.hazelcast.core.HazelcastInstance;
-import com.hazelcast.core.ItemEvent;
-import com.hazelcast.core.ItemListener;
-import com.hazelcast.core.ReplicatedMap;
-import java.net.InetAddress;
-import java.util.AbstractMap;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import org.junit.After;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.DisableOnDebug;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TestRule;
-import org.junit.rules.Timeout;
-import org.slf4j.LoggerFactory;
-import org.sonar.process.NetworkUtils;
-import org.sonar.application.AppStateListener;
-import org.sonar.application.config.TestAppSettings;
-import org.sonar.process.cluster.ClusterObjectKeys;
-import org.sonar.process.ProcessId;
-
-import static java.lang.String.format;
-import static junit.framework.TestCase.fail;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.within;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.timeout;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.sonar.application.cluster.HazelcastClusterTestHelper.closeAllHazelcastClients;
-import static org.sonar.application.cluster.HazelcastClusterTestHelper.createHazelcastClient;
-import static org.sonar.application.cluster.HazelcastClusterTestHelper.newApplicationSettings;
-import static org.sonar.application.cluster.HazelcastClusterTestHelper.newSearchSettings;
-import static org.sonar.process.cluster.ClusterObjectKeys.LEADER;
-import static org.sonar.process.cluster.ClusterObjectKeys.OPERATIONAL_PROCESSES;
-import static org.sonar.process.cluster.ClusterObjectKeys.SONARQUBE_VERSION;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_HOSTS;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NAME;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_HOST;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_PORT;
-
-public class HazelcastClusterTest {
- @Rule
- public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
-
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- @After
- public void closeHazelcastClients() {
- closeAllHazelcastClients();
- }
-
- @Test
- public void test_two_tryToLockWebLeader_must_return_true() {
- ClusterProperties clusterProperties = new ClusterProperties(newApplicationSettings());
- try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
- assertThat(hzCluster.tryToLockWebLeader()).isEqualTo(true);
- assertThat(hzCluster.tryToLockWebLeader()).isEqualTo(false);
- }
- }
-
- @Test
- public void when_another_process_locked_webleader_tryToLockWebLeader_must_return_false() {
- ClusterProperties clusterProperties = new ClusterProperties(newApplicationSettings());
- try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
- HazelcastInstance hzInstance = createHazelcastClient(hzCluster);
- hzInstance.getAtomicReference(LEADER).set("aaaa");
- assertThat(hzCluster.tryToLockWebLeader()).isEqualTo(false);
- }
- }
-
- @Test
- public void when_no_leader_getLeaderHostName_must_return_NO_LEADER() {
- ClusterProperties clusterProperties = new ClusterProperties(newApplicationSettings());
- try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
- assertThat(hzCluster.getLeaderHostName()).isEmpty();
- }
- }
-
- @Test
- public void when_no_leader_getLeaderHostName_must_return_the_hostname() {
- ClusterProperties clusterProperties = new ClusterProperties(newApplicationSettings());
- try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
- assertThat(hzCluster.tryToLockWebLeader()).isTrue();
- assertThat(hzCluster.getLeaderHostName().get()).isEqualTo(
- format("%s (%s)", NetworkUtils.INSTANCE.getHostname(), NetworkUtils.INSTANCE.getIPAddresses()));
- }
- }
-
- @Test
- public void members_must_be_empty_when_there_is_no_other_node() {
- ClusterProperties clusterProperties = new ClusterProperties(newApplicationSettings());
- try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
- assertThat(hzCluster.getMembers()).isEmpty();
- }
- }
-
- @Test
- public void set_operational_is_writing_to_cluster() {
- ClusterProperties clusterProperties = new ClusterProperties(newApplicationSettings());
- try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
- hzCluster.setOperational(ProcessId.ELASTICSEARCH);
-
- assertThat(hzCluster.isOperational(ProcessId.ELASTICSEARCH)).isTrue();
- assertThat(hzCluster.isOperational(ProcessId.WEB_SERVER)).isFalse();
- assertThat(hzCluster.isOperational(ProcessId.COMPUTE_ENGINE)).isFalse();
-
- // Connect via Hazelcast client to test values
- HazelcastInstance hzInstance = createHazelcastClient(hzCluster);
- ReplicatedMap<ClusterProcess, Boolean> operationalProcesses = hzInstance.getReplicatedMap(OPERATIONAL_PROCESSES);
- assertThat(operationalProcesses)
- .containsExactly(new AbstractMap.SimpleEntry<>(new ClusterProcess(hzCluster.getLocalUUID(), ProcessId.ELASTICSEARCH), Boolean.TRUE));
- }
- }
-
- @Test
- public void hazelcast_cluster_name_is_hardcoded_and_not_affected_by_settings() {
- TestAppSettings testAppSettings = newApplicationSettings();
- testAppSettings.set(CLUSTER_NAME, "a_cluster_");
- ClusterProperties clusterProperties = new ClusterProperties(testAppSettings);
- try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
- assertThat(hzCluster.getName()).isEqualTo("sonarqube");
- }
- }
-
- @Test
- public void cluster_must_keep_a_list_of_clients() throws InterruptedException {
- TestAppSettings testAppSettings = newApplicationSettings();
- testAppSettings.set(CLUSTER_NAME, "a_cluster_");
- ClusterProperties clusterProperties = new ClusterProperties(testAppSettings);
- try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
- assertThat(hzCluster.hzInstance.getSet(ClusterObjectKeys.LOCAL_MEMBER_UUIDS)).isEmpty();
- HazelcastInstance hzClient = createHazelcastClient(hzCluster);
- assertThat(hzCluster.hzInstance.getSet(ClusterObjectKeys.LOCAL_MEMBER_UUIDS)).containsExactly(hzClient.getLocalEndpoint().getUuid());
-
- CountDownLatch latch = new CountDownLatch(1);
- hzCluster.hzInstance.getSet(ClusterObjectKeys.LOCAL_MEMBER_UUIDS).addItemListener(new ItemListener<Object>() {
- @Override
- public void itemAdded(ItemEvent<Object> item) {
- }
-
- @Override
- public void itemRemoved(ItemEvent<Object> item) {
- latch.countDown();
- }
- }, false);
-
- hzClient.shutdown();
- if (latch.await(5, TimeUnit.SECONDS)) {
- assertThat(hzCluster.hzInstance.getSet(ClusterObjectKeys.LOCAL_MEMBER_UUIDS).size()).isEqualTo(0);
- } else {
- fail("The client UUID have not been removed from the Set within 5 seconds' time lapse");
- }
- }
- }
-
- @Test
- public void localUUID_must_not_be_empty() {
- ClusterProperties clusterProperties = new ClusterProperties(newApplicationSettings());
- try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
- assertThat(hzCluster.getLocalUUID()).isNotEmpty();
- }
- }
-
- @Test
- public void when_a_process_is_set_operational_listener_must_be_triggered() {
- ClusterProperties clusterProperties = new ClusterProperties(newApplicationSettings());
- try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
- AppStateListener listener = mock(AppStateListener.class);
- hzCluster.addListener(listener);
-
- // ElasticSearch is not operational
- assertThat(hzCluster.isOperational(ProcessId.ELASTICSEARCH)).isFalse();
-
- // Simulate a node that set ElasticSearch operational
- HazelcastInstance hzInstance = createHazelcastClient(hzCluster);
- ReplicatedMap<ClusterProcess, Boolean> operationalProcesses = hzInstance.getReplicatedMap(OPERATIONAL_PROCESSES);
- operationalProcesses.put(new ClusterProcess(UUID.randomUUID().toString(), ProcessId.ELASTICSEARCH), Boolean.TRUE);
- verify(listener, timeout(20_000)).onAppStateOperational(ProcessId.ELASTICSEARCH);
- verifyNoMoreInteractions(listener);
-
- // ElasticSearch is operational
- assertThat(hzCluster.isOperational(ProcessId.ELASTICSEARCH)).isTrue();
- }
- }
-
- @Test
- public void registerSonarQubeVersion_publishes_version_on_first_call() {
- ClusterProperties clusterProperties = new ClusterProperties(newApplicationSettings());
- try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
- hzCluster.registerSonarQubeVersion("1.0.0.0");
-
- HazelcastInstance hzInstance = createHazelcastClient(hzCluster);
- assertThat(hzInstance.getAtomicReference(SONARQUBE_VERSION).get()).isEqualTo("1.0.0.0");
- }
- }
-
- @Test
- public void registerSonarQubeVersion_throws_ISE_if_initial_version_is_different() throws Exception {
- ClusterProperties clusterProperties = new ClusterProperties(newApplicationSettings());
- try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
- // Register first version
- hzCluster.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
- hzCluster.registerSonarQubeVersion("2.0.0");
- }
- }
-
- @Test
- public void simulate_network_cluster() throws InterruptedException {
- TestAppSettings settings = newApplicationSettings();
- settings.set(CLUSTER_NODE_HOST, InetAddress.getLoopbackAddress().getHostAddress());
- AppStateListener listener = mock(AppStateListener.class);
-
- try (ClusterAppStateImpl appStateCluster = new ClusterAppStateImpl(settings)) {
- appStateCluster.addListener(listener);
-
- HazelcastInstance hzInstance = createHazelcastClient(appStateCluster.getHazelcastCluster());
- String uuid = UUID.randomUUID().toString();
- ReplicatedMap<ClusterProcess, Boolean> replicatedMap = hzInstance.getReplicatedMap(OPERATIONAL_PROCESSES);
- // process is not up yet --> no events are sent to listeners
- replicatedMap.put(
- new ClusterProcess(uuid, ProcessId.ELASTICSEARCH),
- Boolean.FALSE);
-
- // process is up yet --> notify listeners
- replicatedMap.replace(
- new ClusterProcess(uuid, ProcessId.ELASTICSEARCH),
- Boolean.TRUE);
-
- // should be called only once
- verify(listener, timeout(20_000)).onAppStateOperational(ProcessId.ELASTICSEARCH);
- verifyNoMoreInteractions(listener);
-
- hzInstance.shutdown();
- }
- }
-
- @Test
- public void hazelcast_must_log_through_sl4fj() {
- MemoryAppender<ILoggingEvent> memoryAppender = new MemoryAppender<>();
- LoggerContext lc = (LoggerContext) LoggerFactory.getILoggerFactory();
- lc.reset();
- memoryAppender.setContext(lc);
- memoryAppender.start();
- lc.getLogger("com.hazelcast").addAppender(memoryAppender);
-
- try (ClusterAppStateImpl appStateCluster = new ClusterAppStateImpl(newApplicationSettings())) {
- }
-
- assertThat(memoryAppender.events).isNotEmpty();
- memoryAppender.events.stream().forEach(
- e -> assertThat(e.getLoggerName()).startsWith("com.hazelcast"));
- }
-
- @Test
- public void getClusterTime_returns_time_of_cluster() {
- ClusterProperties clusterProperties = new ClusterProperties(newApplicationSettings());
- try (HazelcastCluster hzCluster = HazelcastCluster.create(clusterProperties)) {
- assertThat(hzCluster.getHazelcastClient().getClusterTime())
- .isCloseTo(hzCluster.hzInstance.getCluster().getClusterTime(), within(1000L));
- }
- }
-
- @Test
- public void removing_the_last_application_node_must_clear_web_leader() throws InterruptedException {
- try (ClusterAppStateImpl appStateCluster = new ClusterAppStateImpl(newSearchSettings())) {
- TestAppSettings appSettings = newApplicationSettings();
- appSettings.set(CLUSTER_HOSTS, appStateCluster.getHazelcastCluster().getLocalEndPoint());
- appSettings.set(CLUSTER_NODE_PORT, "9004");
- ClusterProperties clusterProperties = new ClusterProperties(appSettings);
- // Simulate a connection from an application node
- HazelcastCluster appNode = HazelcastCluster.create(clusterProperties);
- appNode.tryToLockWebLeader();
- appNode.setOperational(ProcessId.WEB_SERVER);
- appNode.setOperational(ProcessId.COMPUTE_ENGINE);
- appNode.registerSonarQubeVersion("6.6.0.22999");
-
- assertThat(appStateCluster.getLeaderHostName()).isPresent();
- assertThat(appStateCluster.isOperational(ProcessId.WEB_SERVER, false)).isTrue();
- assertThat(appStateCluster.isOperational(ProcessId.COMPUTE_ENGINE, false)).isTrue();
- assertThat(appStateCluster.getHazelcastCluster().getSonarQubeVersion()).isEqualTo("6.6.0.22999");
-
- // Shutdown the node
- appNode.close();
-
- // Propagation of all information take some time, let's wait 5s maximum
- int counter = 10;
- while (appStateCluster.getHazelcastCluster().getSonarQubeVersion() != null && counter > 0) {
- Thread.sleep(500);
- counter--;
- }
-
- assertThat(appStateCluster.getLeaderHostName()).isNotPresent();
- assertThat(appStateCluster.isOperational(ProcessId.WEB_SERVER, false)).isFalse();
- assertThat(appStateCluster.isOperational(ProcessId.COMPUTE_ENGINE, false)).isFalse();
- assertThat(appStateCluster.getHazelcastCluster().getSonarQubeVersion()).isNull();
- }
-
- }
-
- @Test
- public void configuration_tweaks_of_hazelcast_must_be_present() {
- try (HazelcastCluster hzCluster = HazelcastCluster.create(new ClusterProperties(newApplicationSettings()))) {
- assertThat(hzCluster.hzInstance.getConfig().getProperty("hazelcast.tcp.join.port.try.count")).isEqualTo("10");
- assertThat(hzCluster.hzInstance.getConfig().getProperty("hazelcast.phone.home.enabled")).isEqualTo("false");
- assertThat(hzCluster.hzInstance.getConfig().getProperty("hazelcast.logging.type")).isEqualTo("slf4j");
- assertThat(hzCluster.hzInstance.getConfig().getProperty("hazelcast.socket.bind.any")).isEqualTo("false");
- }
- }
-
- private class MemoryAppender<E> extends AppenderBase<E> {
- private final List<E> events = new ArrayList();
-
- @Override
- protected void append(E eventObject) {
- events.add(eventObject);
- }
- }
-}
+++ /dev/null
-/*
- * 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;
-import java.util.ArrayList;
-import java.util.List;
-import org.sonar.application.config.TestAppSettings;
-
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NAME;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_TYPE;
-
-public class HazelcastClusterTestHelper {
-
- // Be careful this test won't work if parallel tests is used
- private static final List<HazelcastInstance> HAZELCAST_INSTANCES = new ArrayList<>();
-
- static HazelcastInstance createHazelcastClient(HazelcastCluster hzCluster) {
- ClientConfig clientConfig = new ClientConfig();
- InetSocketAddress socketAddress = (InetSocketAddress) hzCluster.hzInstance.getLocalEndpoint().getSocketAddress();
-
- clientConfig.getNetworkConfig().getAddresses().add(
- String.format("%s:%d",
- socketAddress.getHostString(),
- socketAddress.getPort()));
- clientConfig.getGroupConfig().setName(hzCluster.getName());
- HazelcastInstance hazelcastInstance = HazelcastClient.newHazelcastClient(clientConfig);
- HAZELCAST_INSTANCES.add(hazelcastInstance);
- return hazelcastInstance;
- }
-
- static HazelcastInstance createHazelcastClient(ClusterAppStateImpl appStateCluster) {
- return createHazelcastClient(appStateCluster.getHazelcastCluster());
- }
-
- static void closeAllHazelcastClients() {
- HAZELCAST_INSTANCES.stream().forEach(
- hz -> {
- try {
- hz.shutdown();
- } catch (Exception ex) {
- // Ignore it
- }
- });
- }
-
- static TestAppSettings newApplicationSettings() {
- TestAppSettings settings = new TestAppSettings();
- settings.set(CLUSTER_ENABLED, "true");
- settings.set(CLUSTER_NAME, "sonarqube");
- settings.set(CLUSTER_NODE_TYPE, "application");
- return settings;
- }
-
- static TestAppSettings newSearchSettings() {
- TestAppSettings settings = new TestAppSettings();
- settings.set(CLUSTER_ENABLED, "true");
- settings.set(CLUSTER_NAME, "sonarqube");
- settings.set(CLUSTER_NODE_TYPE, "search");
- return settings;
- }
-}
--- /dev/null
+/*
+ * 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 java.net.InetAddress;
+import org.sonar.process.NetworkUtils;
+import org.sonar.process.ProcessId;
+import org.sonar.process.cluster.NodeType;
+import org.sonar.process.cluster.hz.HazelcastMember;
+import org.sonar.process.cluster.hz.HazelcastMemberBuilder;
+
+public class HazelcastTesting {
+
+ private HazelcastTesting() {
+ // do not instantiate
+ }
+
+ public static HazelcastMember newHzMember() {
+ // use loopback for support of offline builds
+ InetAddress loopback = InetAddress.getLoopbackAddress();
+
+ return new HazelcastMemberBuilder()
+ .setNodeType(NodeType.APPLICATION)
+ .setProcessId(ProcessId.COMPUTE_ENGINE)
+ .setClusterName("foo")
+ .setNodeName("bar")
+ .setPort(NetworkUtils.INSTANCE.getNextAvailablePort(loopback))
+ .setNetworkInterface(loopback.getHostAddress())
+ .build();
+ }
+}
+++ /dev/null
-/*
- * 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 java.util.Properties;
-import java.util.Random;
-import javax.annotation.Nullable;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.sonar.process.NetworkUtils;
-import org.sonar.process.ProcessId;
-import org.sonar.process.Props;
-import org.sonar.process.cluster.ClusterProperties;
-import org.sonar.process.cluster.health.NodeHealth;
-
-import static java.lang.String.valueOf;
-import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
-import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_HOST;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_NAME;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_PORT;
-
-public class SearchNodeHealthProviderTest {
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- private final Random random = new Random();
- private SearchNodeHealthProvider.Clock clock = mock(SearchNodeHealthProvider.Clock.class);
- private NetworkUtils networkUtils = mock(NetworkUtils.class);
- private ClusterAppState clusterAppState = mock(ClusterAppState.class);
-
- @Test
- public void constructor_throws_IAE_if_property_node_name_is_not_set() {
- Props props = new Props(new Properties());
-
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Missing property: sonar.cluster.node.name");
-
- new SearchNodeHealthProvider(props, clusterAppState, networkUtils);
- }
-
- @Test
- public void constructor_throws_NPE_if_NetworkUtils_getHostname_returns_null_and_property_is_not_set() {
- Properties properties = new Properties();
- properties.put(ClusterProperties.CLUSTER_NODE_NAME, randomAlphanumeric(3));
- Props props = new Props(properties);
-
- expectedException.expect(NullPointerException.class);
-
- new SearchNodeHealthProvider(props, clusterAppState, networkUtils, clock);
- }
-
- @Test
- public void constructor_throws_IAE_if_property_node_port_is_not_set() {
- Properties properties = new Properties();
- properties.put(ClusterProperties.CLUSTER_NODE_NAME, randomAlphanumeric(3));
- when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(34));
- Props props = new Props(properties);
-
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Missing property: sonar.cluster.node.port");
-
- new SearchNodeHealthProvider(props, clusterAppState, networkUtils, clock);
- }
-
- @Test
- public void constructor_throws_FormatException_if_property_node_port_is_not_an_integer() {
- String port = randomAlphanumeric(3);
- Properties properties = new Properties();
- properties.put(ClusterProperties.CLUSTER_NODE_NAME, randomAlphanumeric(3));
- properties.put(ClusterProperties.CLUSTER_NODE_PORT, port);
- when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(34));
- Props props = new Props(properties);
-
- expectedException.expect(NumberFormatException.class);
- expectedException.expectMessage("For input string: \"" + port + "\"");
-
- new SearchNodeHealthProvider(props, clusterAppState, networkUtils, clock);
- }
-
- @Test
- public void get_returns_name_and_port_from_properties_at_constructor_time() {
- String name = randomAlphanumeric(3);
- int port = 1 + random.nextInt(4);
- Properties properties = new Properties();
- properties.setProperty(CLUSTER_NODE_NAME, name);
- properties.setProperty(CLUSTER_NODE_PORT, valueOf(port));
- when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(34));
- when(clock.now()).thenReturn(1L + random.nextInt(87));
- SearchNodeHealthProvider underTest = new SearchNodeHealthProvider(new Props(properties), clusterAppState, networkUtils, clock);
-
- NodeHealth nodeHealth = underTest.get();
-
- assertThat(nodeHealth.getDetails().getName()).isEqualTo(name);
- assertThat(nodeHealth.getDetails().getPort()).isEqualTo(port);
-
- // change values in properties
- properties.setProperty(CLUSTER_NODE_NAME, randomAlphanumeric(6));
- properties.setProperty(CLUSTER_NODE_PORT, valueOf(1 + random.nextInt(99)));
-
- NodeHealth newNodeHealth = underTest.get();
-
- assertThat(newNodeHealth.getDetails().getName()).isEqualTo(name);
- assertThat(newNodeHealth.getDetails().getPort()).isEqualTo(port);
- }
-
- @Test
- public void get_returns_host_from_property_if_set_at_constructor_time() {
- String host = randomAlphanumeric(55);
- Properties properties = new Properties();
- properties.setProperty(CLUSTER_NODE_NAME, randomAlphanumeric(3));
- properties.setProperty(CLUSTER_NODE_PORT, valueOf(1 + random.nextInt(4)));
- properties.setProperty(CLUSTER_NODE_HOST, host);
- when(clock.now()).thenReturn(1L + random.nextInt(87));
- SearchNodeHealthProvider underTest = new SearchNodeHealthProvider(new Props(properties), clusterAppState, networkUtils, clock);
-
- NodeHealth nodeHealth = underTest.get();
-
- assertThat(nodeHealth.getDetails().getHost()).isEqualTo(host);
-
- // change now
- properties.setProperty(CLUSTER_NODE_HOST, randomAlphanumeric(96));
-
- NodeHealth newNodeHealth = underTest.get();
-
- assertThat(newNodeHealth.getDetails().getHost()).isEqualTo(host);
- }
-
- @Test
- public void get_returns_host_from_NetworkUtils_getHostname_if_property_is_not_set_at_constructor_time() {
- getReturnsHostFromNetworkUtils(null);
- }
-
- @Test
- public void get_returns_host_from_NetworkUtils_getHostname_if_property_is_empty_at_constructor_time() {
- getReturnsHostFromNetworkUtils(random.nextBoolean() ? "" : " ");
- }
-
- private void getReturnsHostFromNetworkUtils(@Nullable String hostPropertyValue) {
- String host = randomAlphanumeric(34);
- Properties properties = new Properties();
- properties.setProperty(CLUSTER_NODE_NAME, randomAlphanumeric(3));
- properties.setProperty(CLUSTER_NODE_PORT, valueOf(1 + random.nextInt(4)));
- if (hostPropertyValue != null) {
- properties.setProperty(CLUSTER_NODE_HOST, hostPropertyValue);
- }
- when(clock.now()).thenReturn(1L + random.nextInt(87));
- when(networkUtils.getHostname()).thenReturn(host);
- SearchNodeHealthProvider underTest = new SearchNodeHealthProvider(new Props(properties), clusterAppState, networkUtils, clock);
-
- NodeHealth nodeHealth = underTest.get();
-
- assertThat(nodeHealth.getDetails().getHost()).isEqualTo(host);
-
- // change now
- when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(96));
-
- NodeHealth newNodeHealth = underTest.get();
-
- assertThat(newNodeHealth.getDetails().getHost()).isEqualTo(host);
- }
-
- @Test
- public void get_returns_started_from_System2_now_at_constructor_time() {
- Properties properties = new Properties();
- long now = setRequiredPropertiesAndMocks(properties);
- SearchNodeHealthProvider underTest = new SearchNodeHealthProvider(new Props(properties), clusterAppState, networkUtils, clock);
-
- NodeHealth nodeHealth = underTest.get();
-
- assertThat(nodeHealth.getDetails().getStartedAt()).isEqualTo(now);
-
- // change now
- when(clock.now()).thenReturn(now);
-
- NodeHealth newNodeHealth = underTest.get();
-
- assertThat(newNodeHealth.getDetails().getStartedAt()).isEqualTo(now);
- }
-
- @Test
- public void get_returns_status_GREEN_if_elasticsearch_process_is_operational_in_ClusterAppState() {
- Properties properties = new Properties();
- setRequiredPropertiesAndMocks(properties);
- when(clusterAppState.isOperational(ProcessId.ELASTICSEARCH, true)).thenReturn(true);
- SearchNodeHealthProvider underTest = new SearchNodeHealthProvider(new Props(properties), clusterAppState, networkUtils, clock);
-
- NodeHealth nodeHealth = underTest.get();
-
- assertThat(nodeHealth.getStatus()).isEqualTo(NodeHealth.Status.GREEN);
- }
-
- @Test
- public void get_returns_status_RED_with_cause_if_elasticsearch_process_is_not_operational_in_ClusterAppState() {
- Properties properties = new Properties();
- setRequiredPropertiesAndMocks(properties);
- when(clusterAppState.isOperational(ProcessId.ELASTICSEARCH, true)).thenReturn(false);
- SearchNodeHealthProvider underTest = new SearchNodeHealthProvider(new Props(properties), clusterAppState, networkUtils, clock);
-
- NodeHealth nodeHealth = underTest.get();
-
- assertThat(nodeHealth.getStatus()).isEqualTo(NodeHealth.Status.RED);
- assertThat(nodeHealth.getCauses()).containsOnly("Elasticsearch is not operational");
- }
-
- private long setRequiredPropertiesAndMocks(Properties properties) {
- properties.setProperty(CLUSTER_NODE_NAME, randomAlphanumeric(3));
- properties.setProperty(CLUSTER_NODE_PORT, valueOf(1 + random.nextInt(4)));
- long now = 1L + random.nextInt(87);
- when(clock.now()).thenReturn(now);
- when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(34));
- return now;
- }
-}
--- /dev/null
+/*
+ * 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.health;
+
+import java.util.Collection;
+import java.util.Random;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.junit.Test;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+public class DelegateHealthStateRefresherExecutorServiceTest {
+ private Random random = new Random();
+ private Runnable runnable = mock(Runnable.class);
+ private Callable callable = mock(Callable.class);
+ private Collection<Callable<Object>> callables = IntStream.range(0, random.nextInt(5))
+ .mapToObj(i -> (Callable<Object>) mock(Callable.class))
+ .collect(Collectors.toList());
+ private int initialDelay = random.nextInt(333);
+ private int delay = random.nextInt(333);
+ private int period = random.nextInt(333);
+ private int timeout = random.nextInt(333);
+ private Object result = new Object();
+ private ScheduledExecutorService executorService = mock(ScheduledExecutorService.class);
+ private DelegateHealthStateRefresherExecutorService underTest = new DelegateHealthStateRefresherExecutorService(executorService);
+
+ @Test
+ public void schedule() {
+ underTest.schedule(runnable, delay, SECONDS);
+
+ verify(executorService).schedule(runnable, delay, SECONDS);
+ }
+
+ @Test
+ public void schedule1() {
+ underTest.schedule(callable, delay, SECONDS);
+
+ verify(executorService).schedule(callable, delay, SECONDS);
+ }
+
+ @Test
+ public void scheduleAtFixedRate() {
+ underTest.scheduleAtFixedRate(runnable, initialDelay, period, SECONDS);
+ verify(executorService).scheduleAtFixedRate(runnable, initialDelay, period, SECONDS);
+ }
+
+ @Test
+ public void scheduleWithFixeddelay() {
+ underTest.scheduleWithFixedDelay(runnable, initialDelay, delay, TimeUnit.SECONDS);
+ verify(executorService).scheduleWithFixedDelay(runnable, initialDelay, delay, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void shutdown() {
+ underTest.shutdown();
+ verify(executorService).shutdown();
+ }
+
+ @Test
+ public void shutdownNow() {
+ underTest.shutdownNow();
+ verify(executorService).shutdownNow();
+ }
+
+ @Test
+ public void isShutdown() {
+ underTest.isShutdown();
+ verify(executorService).isShutdown();
+ }
+
+ @Test
+ public void isTerminated() {
+ underTest.isTerminated();
+ verify(executorService).isTerminated();
+ }
+
+ @Test
+ public void awaitTermination() throws InterruptedException {
+ underTest.awaitTermination(timeout, TimeUnit.SECONDS);
+
+ verify(executorService).awaitTermination(timeout, TimeUnit.SECONDS);
+ }
+
+ @Test
+ public void submit() {
+ underTest.submit(callable);
+
+ verify(executorService).submit(callable);
+ }
+
+ @Test
+ public void submit1() {
+ underTest.submit(runnable, result);
+
+ verify(executorService).submit(runnable, result);
+ }
+
+ @Test
+ public void submit2() {
+ underTest.submit(runnable);
+ verify(executorService).submit(runnable);
+ }
+
+ @Test
+ public void invokeAll() throws InterruptedException {
+ underTest.invokeAll(callables);
+ verify(executorService).invokeAll(callables);
+ }
+
+ @Test
+ public void invokeAll1() throws InterruptedException {
+ underTest.invokeAll(callables, timeout, SECONDS);
+ verify(executorService).invokeAll(callables, timeout, SECONDS);
+ }
+
+ @Test
+ public void invokeAny() throws InterruptedException, ExecutionException {
+ underTest.invokeAny(callables);
+ verify(executorService).invokeAny(callables);
+ }
+
+ @Test
+ public void invokeAny2() throws InterruptedException, ExecutionException, TimeoutException {
+ underTest.invokeAny(callables, timeout, SECONDS);
+ verify(executorService).invokeAny(callables, timeout, SECONDS);
+ }
+
+}
--- /dev/null
+/*
+ * 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.health;
+
+import java.util.Properties;
+import java.util.Random;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.application.cluster.ClusterAppState;
+import org.sonar.process.NetworkUtils;
+import org.sonar.process.ProcessId;
+import org.sonar.process.Props;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.cluster.health.NodeHealth;
+
+import static java.lang.String.valueOf;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.AssertionsForInterfaceTypes.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_HOST;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_NAME;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_PORT;
+
+public class SearchNodeHealthProviderTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private final Random random = new Random();
+ private SearchNodeHealthProvider.Clock clock = mock(SearchNodeHealthProvider.Clock.class);
+ private NetworkUtils networkUtils = mock(NetworkUtils.class);
+ private ClusterAppState clusterAppState = mock(ClusterAppState.class);
+
+ @Test
+ public void constructor_throws_IAE_if_property_node_name_is_not_set() {
+ Props props = new Props(new Properties());
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Missing property: sonar.cluster.node.name");
+
+ new SearchNodeHealthProvider(props, clusterAppState, networkUtils);
+ }
+
+ @Test
+ public void constructor_throws_NPE_if_NetworkUtils_getHostname_returns_null_and_property_is_not_set() {
+ Properties properties = new Properties();
+ properties.put(ProcessProperties.CLUSTER_NODE_NAME, randomAlphanumeric(3));
+ Props props = new Props(properties);
+
+ expectedException.expect(NullPointerException.class);
+
+ new SearchNodeHealthProvider(props, clusterAppState, networkUtils, clock);
+ }
+
+ @Test
+ public void constructor_throws_IAE_if_property_node_port_is_not_set() {
+ Properties properties = new Properties();
+ properties.put(ProcessProperties.CLUSTER_NODE_NAME, randomAlphanumeric(3));
+ when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(34));
+ Props props = new Props(properties);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Missing property: sonar.cluster.node.port");
+
+ new SearchNodeHealthProvider(props, clusterAppState, networkUtils, clock);
+ }
+
+ @Test
+ public void constructor_throws_FormatException_if_property_node_port_is_not_an_integer() {
+ String port = randomAlphanumeric(3);
+ Properties properties = new Properties();
+ properties.put(ProcessProperties.CLUSTER_NODE_NAME, randomAlphanumeric(3));
+ properties.put(ProcessProperties.CLUSTER_NODE_PORT, port);
+ when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(34));
+ Props props = new Props(properties);
+
+ expectedException.expect(NumberFormatException.class);
+ expectedException.expectMessage("For input string: \"" + port + "\"");
+
+ new SearchNodeHealthProvider(props, clusterAppState, networkUtils, clock);
+ }
+
+ @Test
+ public void get_returns_name_and_port_from_properties_at_constructor_time() {
+ String name = randomAlphanumeric(3);
+ int port = 1 + random.nextInt(4);
+ Properties properties = new Properties();
+ properties.setProperty(CLUSTER_NODE_NAME, name);
+ properties.setProperty(CLUSTER_NODE_PORT, valueOf(port));
+ when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(34));
+ when(clock.now()).thenReturn(1L + random.nextInt(87));
+ SearchNodeHealthProvider underTest = new SearchNodeHealthProvider(new Props(properties), clusterAppState, networkUtils, clock);
+
+ NodeHealth nodeHealth = underTest.get();
+
+ assertThat(nodeHealth.getDetails().getName()).isEqualTo(name);
+ assertThat(nodeHealth.getDetails().getPort()).isEqualTo(port);
+
+ // change values in properties
+ properties.setProperty(CLUSTER_NODE_NAME, randomAlphanumeric(6));
+ properties.setProperty(CLUSTER_NODE_PORT, valueOf(1 + random.nextInt(99)));
+
+ NodeHealth newNodeHealth = underTest.get();
+
+ assertThat(newNodeHealth.getDetails().getName()).isEqualTo(name);
+ assertThat(newNodeHealth.getDetails().getPort()).isEqualTo(port);
+ }
+
+ @Test
+ public void get_returns_host_from_property_if_set_at_constructor_time() {
+ String host = randomAlphanumeric(55);
+ Properties properties = new Properties();
+ properties.setProperty(CLUSTER_NODE_NAME, randomAlphanumeric(3));
+ properties.setProperty(CLUSTER_NODE_PORT, valueOf(1 + random.nextInt(4)));
+ properties.setProperty(CLUSTER_NODE_HOST, host);
+ when(clock.now()).thenReturn(1L + random.nextInt(87));
+ SearchNodeHealthProvider underTest = new SearchNodeHealthProvider(new Props(properties), clusterAppState, networkUtils, clock);
+
+ NodeHealth nodeHealth = underTest.get();
+
+ assertThat(nodeHealth.getDetails().getHost()).isEqualTo(host);
+
+ // change now
+ properties.setProperty(CLUSTER_NODE_HOST, randomAlphanumeric(96));
+
+ NodeHealth newNodeHealth = underTest.get();
+
+ assertThat(newNodeHealth.getDetails().getHost()).isEqualTo(host);
+ }
+
+ @Test
+ public void get_returns_host_from_NetworkUtils_getHostname_if_property_is_not_set_at_constructor_time() {
+ getReturnsHostFromNetworkUtils(null);
+ }
+
+ @Test
+ public void get_returns_host_from_NetworkUtils_getHostname_if_property_is_empty_at_constructor_time() {
+ getReturnsHostFromNetworkUtils(random.nextBoolean() ? "" : " ");
+ }
+
+ private void getReturnsHostFromNetworkUtils(@Nullable String hostPropertyValue) {
+ String host = randomAlphanumeric(34);
+ Properties properties = new Properties();
+ properties.setProperty(CLUSTER_NODE_NAME, randomAlphanumeric(3));
+ properties.setProperty(CLUSTER_NODE_PORT, valueOf(1 + random.nextInt(4)));
+ if (hostPropertyValue != null) {
+ properties.setProperty(CLUSTER_NODE_HOST, hostPropertyValue);
+ }
+ when(clock.now()).thenReturn(1L + random.nextInt(87));
+ when(networkUtils.getHostname()).thenReturn(host);
+ SearchNodeHealthProvider underTest = new SearchNodeHealthProvider(new Props(properties), clusterAppState, networkUtils, clock);
+
+ NodeHealth nodeHealth = underTest.get();
+
+ assertThat(nodeHealth.getDetails().getHost()).isEqualTo(host);
+
+ // change now
+ when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(96));
+
+ NodeHealth newNodeHealth = underTest.get();
+
+ assertThat(newNodeHealth.getDetails().getHost()).isEqualTo(host);
+ }
+
+ @Test
+ public void get_returns_started_from_System2_now_at_constructor_time() {
+ Properties properties = new Properties();
+ long now = setRequiredPropertiesAndMocks(properties);
+ SearchNodeHealthProvider underTest = new SearchNodeHealthProvider(new Props(properties), clusterAppState, networkUtils, clock);
+
+ NodeHealth nodeHealth = underTest.get();
+
+ assertThat(nodeHealth.getDetails().getStartedAt()).isEqualTo(now);
+
+ // change now
+ when(clock.now()).thenReturn(now);
+
+ NodeHealth newNodeHealth = underTest.get();
+
+ assertThat(newNodeHealth.getDetails().getStartedAt()).isEqualTo(now);
+ }
+
+ @Test
+ public void get_returns_status_GREEN_if_elasticsearch_process_is_operational_in_ClusterAppState() {
+ Properties properties = new Properties();
+ setRequiredPropertiesAndMocks(properties);
+ when(clusterAppState.isOperational(ProcessId.ELASTICSEARCH, true)).thenReturn(true);
+ SearchNodeHealthProvider underTest = new SearchNodeHealthProvider(new Props(properties), clusterAppState, networkUtils, clock);
+
+ NodeHealth nodeHealth = underTest.get();
+
+ assertThat(nodeHealth.getStatus()).isEqualTo(NodeHealth.Status.GREEN);
+ }
+
+ @Test
+ public void get_returns_status_RED_with_cause_if_elasticsearch_process_is_not_operational_in_ClusterAppState() {
+ Properties properties = new Properties();
+ setRequiredPropertiesAndMocks(properties);
+ when(clusterAppState.isOperational(ProcessId.ELASTICSEARCH, true)).thenReturn(false);
+ SearchNodeHealthProvider underTest = new SearchNodeHealthProvider(new Props(properties), clusterAppState, networkUtils, clock);
+
+ NodeHealth nodeHealth = underTest.get();
+
+ assertThat(nodeHealth.getStatus()).isEqualTo(NodeHealth.Status.RED);
+ assertThat(nodeHealth.getCauses()).containsOnly("Elasticsearch is not operational");
+ }
+
+ private long setRequiredPropertiesAndMocks(Properties properties) {
+ properties.setProperty(CLUSTER_NODE_NAME, randomAlphanumeric(3));
+ properties.setProperty(CLUSTER_NODE_PORT, valueOf(1 + random.nextInt(4)));
+ long now = 1L + random.nextInt(87);
+ when(clock.now()).thenReturn(now);
+ when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(34));
+ return now;
+ }
+}
package org.sonar.application.config;
-import java.net.NetworkInterface;
+import java.net.InetAddress;
import java.net.SocketException;
-import java.util.Collections;
-import java.util.Enumeration;
+import java.util.Optional;
+import org.hamcrest.CoreMatchers;
import org.junit.Before;
import org.junit.Rule;
-import org.junit.experimental.theories.DataPoints;
-import org.junit.experimental.theories.FromDataPoints;
-import org.junit.experimental.theories.Theories;
-import org.junit.experimental.theories.Theory;
+import org.junit.Test;
import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
import org.sonar.process.MessageException;
-
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_HOSTS;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_HOST;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_TYPE;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_SEARCH_HOSTS;
+import org.sonar.process.NetworkUtils;
+import org.sonar.process.ProcessProperties;
+
+import static org.junit.Assume.assumeThat;
+import static org.mockito.Mockito.spy;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_HOSTS;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_HOST;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_TYPE;
+import static org.sonar.process.ProcessProperties.CLUSTER_SEARCH_HOSTS;
import static org.sonar.process.ProcessProperties.JDBC_URL;
import static org.sonar.process.ProcessProperties.SEARCH_HOST;
-@RunWith(Theories.class)
public class ClusterSettingsLoopbackTest {
- private TestAppSettings settings;
- private static final String LOOPBACK_FORBIDDEN = " must not be a loopback address";
- private static final String NOT_LOCAL_ADDRESS = " is not a local address";
- private static final String NOT_RESOLVABLE = " cannot be resolved";
-
- @DataPoints("parameter")
- public static final ValueAndResult[] VALID_SINGLE_IP = {
- // Valid IPs
- new ValueAndResult("1.2.3.4", NOT_LOCAL_ADDRESS),
- new ValueAndResult("1.2.3.4:9001", NOT_LOCAL_ADDRESS),
- new ValueAndResult("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb", NOT_LOCAL_ADDRESS),
- new ValueAndResult("[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]:9001", NOT_LOCAL_ADDRESS),
-
- // Valid Name
- new ValueAndResult("www.sonarqube.org", NOT_LOCAL_ADDRESS),
- new ValueAndResult("www.google.fr", NOT_LOCAL_ADDRESS),
- new ValueAndResult("www.google.com, www.sonarsource.com, wwww.sonarqube.org", NOT_LOCAL_ADDRESS),
-
- new ValueAndResult("...", NOT_RESOLVABLE),
- new ValueAndResult("භඦආ\uD801\uDC8C\uD801\uDC8B", NOT_RESOLVABLE),
-
- // Valide IPs List
- new ValueAndResult("1.2.3.4,2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb", NOT_LOCAL_ADDRESS),
- new ValueAndResult("1.2.3.4:9001,[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]:9001", NOT_LOCAL_ADDRESS),
- new ValueAndResult("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb,1.2.3.4:9001", NOT_LOCAL_ADDRESS),
- new ValueAndResult("[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]:9001,2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccc", NOT_LOCAL_ADDRESS),
-
- // Loopback IPs
- new ValueAndResult("localhost", LOOPBACK_FORBIDDEN),
- new ValueAndResult("127.0.0.1", LOOPBACK_FORBIDDEN),
- new ValueAndResult("127.1.1.1", LOOPBACK_FORBIDDEN),
- new ValueAndResult("127.243.136.241", LOOPBACK_FORBIDDEN),
- new ValueAndResult("::1", LOOPBACK_FORBIDDEN),
- new ValueAndResult("0:0:0:0:0:0:0:1", LOOPBACK_FORBIDDEN),
- new ValueAndResult("localhost:9001", LOOPBACK_FORBIDDEN),
- new ValueAndResult("127.0.0.1:9001", LOOPBACK_FORBIDDEN),
- new ValueAndResult("127.1.1.1:9001", LOOPBACK_FORBIDDEN),
- new ValueAndResult("127.243.136.241:9001", LOOPBACK_FORBIDDEN),
- new ValueAndResult("[::1]:9001", LOOPBACK_FORBIDDEN),
- new ValueAndResult("[0:0:0:0:0:0:0:1]:9001", LOOPBACK_FORBIDDEN),
-
- // Loopback IPs list
- new ValueAndResult("127.0.0.1,192.168.11.25", LOOPBACK_FORBIDDEN),
- new ValueAndResult("192.168.11.25,127.1.1.1", LOOPBACK_FORBIDDEN),
- new ValueAndResult("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb,0:0:0:0:0:0:0:1", LOOPBACK_FORBIDDEN),
- new ValueAndResult("0:0:0:0:0:0:0:1,2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb", LOOPBACK_FORBIDDEN),
- new ValueAndResult("2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb,::1", LOOPBACK_FORBIDDEN),
- new ValueAndResult("::1,2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb", LOOPBACK_FORBIDDEN),
- new ValueAndResult("::1,2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb,2a01:e34:ef1f:dbb0:b3f6:a978:c5c0:9ccb", LOOPBACK_FORBIDDEN),
- new ValueAndResult("localhost:9001", LOOPBACK_FORBIDDEN),
- new ValueAndResult("127.0.0.1:9001", LOOPBACK_FORBIDDEN),
- new ValueAndResult("127.1.1.1:9001", LOOPBACK_FORBIDDEN),
- new ValueAndResult("127.243.136.241:9001", LOOPBACK_FORBIDDEN),
- new ValueAndResult("[::1]:9001", LOOPBACK_FORBIDDEN),
- new ValueAndResult("[0:0:0:0:0:0:0:1]:9001", LOOPBACK_FORBIDDEN),
- new ValueAndResult("127.0.0.1,192.168.11.25:9001", LOOPBACK_FORBIDDEN),
- new ValueAndResult("192.168.11.25:9001,127.1.1.1:9001", LOOPBACK_FORBIDDEN),
- new ValueAndResult("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb,[0:0:0:0:0:0:0:1]:9001", LOOPBACK_FORBIDDEN),
- new ValueAndResult("[0:0:0:0:0:0:0:1]:9001,[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]:9001", LOOPBACK_FORBIDDEN),
- new ValueAndResult("[2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb]:9001,[::1]:9001", LOOPBACK_FORBIDDEN),
- new ValueAndResult("[::1]:9001,[2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb]:9001", LOOPBACK_FORBIDDEN),
- new ValueAndResult("[::1]:9001,[2a01:e34:ef1f:dbb0:c3f6:a978:c5c0:9ccb]:9001,[2a01:e34:ef1f:dbb0:b3f6:a978:c5c0:9ccb]:9001", LOOPBACK_FORBIDDEN)
- };
-
- @DataPoints("key")
- public static final Key[] KEYS = {
- new Key(SEARCH_HOST, false, false),
- new Key(CLUSTER_NODE_HOST, true, false),
- new Key(CLUSTER_SEARCH_HOSTS, true, true),
- new Key(CLUSTER_HOSTS, true, true)
- };
-
- @DataPoints("unresolvable_hosts")
- public static final String[] UNRESOLVABLE_HOSTS = {
- };
-
@Rule
public ExpectedException expectedException = ExpectedException.none();
+ private InetAddress loopback = InetAddress.getLoopbackAddress();
+ private InetAddress nonLoopbackLocal;
+ private NetworkUtils network = spy(NetworkUtils.INSTANCE);
+
@Before
- public void resetSettings() {
- settings = getClusterSettings();
- }
+ public void setUp() throws Exception {
+ Optional<InetAddress> opt = network.getLocalNonLoopbackIpv4Address();
+ assumeThat(opt.isPresent(), CoreMatchers.is(true));
- @Theory
- public void accept_throws_MessageException(
- @FromDataPoints("key") Key propertyKey,
- @FromDataPoints("parameter") ValueAndResult valueAndResult) {
- // Skip the test if the value is a list and if the key is not accepting a list
- if (settings == null) {
- System.out.println("No network found, skipping the test");
- return;
- }
- if ((valueAndResult.isList() && propertyKey.acceptList) || !valueAndResult.isList()) {
- settings.set(propertyKey.getKey(), valueAndResult.getValue());
-
- // If the key accept non local IPs there won't be any exception
- if (!propertyKey.acceptNonLocal || valueAndResult.getMessage() != NOT_LOCAL_ADDRESS) {
- expectedException.expect(MessageException.class);
- expectedException.expectMessage(valueAndResult.getMessage());
- }
-
- new ClusterSettings().accept(settings.getProps());
- }
+ nonLoopbackLocal = opt.get();
}
- private static TestAppSettings getClusterSettings() {
- String localAddress = null;
- try {
- Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
- for (NetworkInterface networkInterface : Collections.list(nets)) {
- if (!networkInterface.isLoopback() && networkInterface.isUp()) {
- localAddress = networkInterface.getInetAddresses().nextElement().getHostAddress();
- }
- }
- if (localAddress == null) {
- return null;
- }
-
- } catch (SocketException e) {
- return null;
- }
-
- TestAppSettings testAppSettings = new TestAppSettings()
- .set(CLUSTER_ENABLED, "true")
- .set(CLUSTER_NODE_TYPE, "search")
- .set(CLUSTER_SEARCH_HOSTS, "192.168.233.1, 192.168.233.2,192.168.233.3")
- .set(CLUSTER_HOSTS, "192.168.233.1, 192.168.233.2,192.168.233.3")
- .set(SEARCH_HOST, localAddress)
- .set(JDBC_URL, "jdbc:mysql://localhost:3306/sonar?useUnicode=true&characterEncoding=utf8&rewriteBatchedStatements=true&useConfigs=maxPerformance");
- return testAppSettings;
+ @Test
+ public void ClusterSettings_throws_MessageException_if_host_of_search_node_is_loopback() throws Exception {
+ verifySearchFailureIfLoopback(ProcessProperties.CLUSTER_NODE_HOST);
+ verifySearchFailureIfLoopback(ProcessProperties.CLUSTER_SEARCH_HOSTS);
+ verifySearchFailureIfLoopback(ProcessProperties.CLUSTER_HOSTS);
+ verifySearchFailureIfLoopback(ProcessProperties.SEARCH_HOST);
}
- private static class Key {
- private final String key;
- private final boolean acceptList;
- private final boolean acceptNonLocal;
-
- private Key(String key, boolean acceptList, boolean acceptNonLocal) {
- this.key = key;
- this.acceptList = acceptList;
- this.acceptNonLocal = acceptNonLocal;
- }
-
- public String getKey() {
- return key;
- }
+ @Test
+ public void ClusterSettings_throws_MessageException_if_host_of_app_node_is_loopback() throws Exception {
+ verifyAppFailureIfLoopback(ProcessProperties.CLUSTER_NODE_HOST);
+ verifyAppFailureIfLoopback(ProcessProperties.CLUSTER_SEARCH_HOSTS);
+ verifyAppFailureIfLoopback(ProcessProperties.CLUSTER_HOSTS);
+ }
- public boolean acceptList() {
- return acceptList;
- }
+ private void verifySearchFailureIfLoopback(String propertyKey) throws Exception {
+ TestAppSettings settings = newSettingsForSearchNode();
+ verifyFailure(propertyKey, settings);
+ }
- public boolean acceptNonLocal() {
- return acceptNonLocal;
- }
+ private void verifyAppFailureIfLoopback(String propertyKey) throws Exception {
+ TestAppSettings settings = newSettingsForAppNode();
+ verifyFailure(propertyKey, settings);
}
- private static class ValueAndResult {
- private final String value;
- private final String message;
+ private void verifyFailure(String propertyKey, TestAppSettings settings) {
+ settings.set(propertyKey, loopback.getHostAddress());
- private ValueAndResult(String value, String message) {
- this.value = value;
- this.message = message;
- }
+ expectedException.expect(MessageException.class);
+ expectedException.expectMessage("Property " + propertyKey + " must be a local non-loopback address: " + loopback.getHostAddress());
- public String getValue() {
- return value;
- }
+ new ClusterSettings(network).accept(settings.getProps());
+ }
- public String getMessage() {
- return message;
- }
+ private TestAppSettings newSettingsForAppNode() throws SocketException {
+ return new TestAppSettings()
+ .set(CLUSTER_ENABLED, "true")
+ .set(CLUSTER_NODE_TYPE, "application")
+ .set(CLUSTER_NODE_HOST, nonLoopbackLocal.getHostAddress())
+ .set(CLUSTER_HOSTS, nonLoopbackLocal.getHostAddress())
+ .set(CLUSTER_SEARCH_HOSTS, nonLoopbackLocal.getHostAddress())
+ .set("sonar.auth.jwtBase64Hs256Secret", "abcde")
+ .set(JDBC_URL, "jdbc:mysql://localhost:3306/sonar");
+ }
- public boolean isList() {
- return value != null && value.contains(",");
- }
+ private TestAppSettings newSettingsForSearchNode() throws SocketException {
+ return new TestAppSettings()
+ .set(CLUSTER_ENABLED, "true")
+ .set(CLUSTER_NODE_TYPE, "search")
+ .set(CLUSTER_NODE_HOST, nonLoopbackLocal.getHostAddress())
+ .set(CLUSTER_HOSTS, nonLoopbackLocal.getHostAddress())
+ .set(CLUSTER_SEARCH_HOSTS, nonLoopbackLocal.getHostAddress())
+ .set(SEARCH_HOST, nonLoopbackLocal.getHostAddress());
}
}
*/
package org.sonar.application.config;
-import java.net.Inet4Address;
import java.net.InetAddress;
-import java.net.NetworkInterface;
import java.net.SocketException;
-import java.util.Collections;
-import java.util.Enumeration;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.process.MessageException;
+import org.sonar.process.NetworkUtils;
import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_HOSTS;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_TYPE;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_SEARCH_HOSTS;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
import static org.sonar.process.ProcessId.COMPUTE_ENGINE;
import static org.sonar.process.ProcessId.ELASTICSEARCH;
import static org.sonar.process.ProcessId.WEB_SERVER;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_HOSTS;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_HOST;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_TYPE;
+import static org.sonar.process.ProcessProperties.CLUSTER_SEARCH_HOSTS;
import static org.sonar.process.ProcessProperties.JDBC_URL;
import static org.sonar.process.ProcessProperties.SEARCH_HOST;
@Rule
public ExpectedException expectedException = ExpectedException.none();
+ private InetAddress nonLoopbackLocal = InetAddress.getLoopbackAddress();
+ private NetworkUtils network = spy(NetworkUtils.INSTANCE);
+
+ @Before
+ public void setUp() throws Exception {
+ when(network.isLocalInetAddress(nonLoopbackLocal)).thenReturn(true);
+ when(network.isLoopbackInetAddress(nonLoopbackLocal)).thenReturn(false);
+ }
+
@Test
- public void test_isClusterEnabled() {
+ public void test_isClusterEnabled() throws Exception {
TestAppSettings settings = newSettingsForAppNode().set(CLUSTER_ENABLED, "true");
assertThat(ClusterSettings.isClusterEnabled(settings)).isTrue();
}
@Test
- public void getEnabledProcesses_fails_if_no_node_type_is_set_for_a_cluster_node() {
- TestAppSettings settings = new TestAppSettings();
- settings.set(CLUSTER_ENABLED, "true");
- settings.set(CLUSTER_NODE_TYPE, "foo");
-
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Invalid value for [sonar.cluster.node.type]: [foo]");
-
- ClusterSettings.getEnabledProcesses(settings);
- }
-
- @Test
- public void getEnabledProcesses_returns_configured_processes_in_cluster_mode() {
+ public void getEnabledProcesses_returns_configured_processes_in_cluster_mode() throws Exception {
TestAppSettings settings = newSettingsForAppNode();
assertThat(ClusterSettings.getEnabledProcesses(settings)).containsOnly(COMPUTE_ENGINE, WEB_SERVER);
settings.set(CLUSTER_ENABLED, "true");
expectedException.expect(MessageException.class);
- expectedException.expectMessage("Property [sonar.cluster.node.type] is mandatory");
+ expectedException.expectMessage("Property sonar.cluster.node.type is mandatory");
- new ClusterSettings().accept(settings.getProps());
+ new ClusterSettings(network).accept(settings.getProps());
}
@Test
settings.set(CLUSTER_NODE_TYPE, "bla");
expectedException.expect(MessageException.class);
- expectedException.expectMessage("Invalid value for property [sonar.cluster.node.type]: [bla], only [application, search] are allowed");
+ expectedException.expectMessage("Invalid value for property sonar.cluster.node.type: [bla], only [application, search] are allowed");
- new ClusterSettings().accept(settings.getProps());
+ new ClusterSettings(network).accept(settings.getProps());
}
@Test
- public void accept_throws_MessageException_if_internal_property_for_web_leader_is_configured() {
+ public void accept_throws_MessageException_if_internal_property_for_startup_leader_is_configured() throws Exception {
TestAppSettings settings = newSettingsForAppNode();
settings.set("sonar.cluster.web.startupLeader", "true");
expectedException.expect(MessageException.class);
expectedException.expectMessage("Property [sonar.cluster.web.startupLeader] is forbidden");
- new ClusterSettings().accept(settings.getProps());
- }
-
- @Test
- public void accept_throws_MessageException_if_search_enabled_with_loopback() {
- TestAppSettings settings = newSettingsForSearchNode();
- settings.set(CLUSTER_SEARCH_HOSTS, "192.168.1.1,192.168.1.2");
- settings.set(SEARCH_HOST, "::1");
-
- expectedException.expect(MessageException.class);
- expectedException.expectMessage("The interface address [::1] of [sonar.search.host] must not be a loopback address");
-
- new ClusterSettings().accept(settings.getProps());
+ new ClusterSettings(network).accept(settings.getProps());
}
@Test
// this property is supposed to fail if cluster is enabled
settings.set("sonar.cluster.web.startupLeader", "true");
- new ClusterSettings().accept(settings.getProps());
+ new ClusterSettings(network).accept(settings.getProps());
}
@Test
- public void accept_throws_MessageException_if_h2_on_application_node() {
+ public void accept_throws_MessageException_if_h2_on_application_node() throws Exception {
TestAppSettings settings = newSettingsForAppNode();
settings.set("sonar.jdbc.url", "jdbc:h2:mem");
expectedException.expect(MessageException.class);
expectedException.expectMessage("Embedded database is not supported in cluster mode");
- new ClusterSettings().accept(settings.getProps());
+ new ClusterSettings(network).accept(settings.getProps());
}
@Test
- public void accept_does_not_verify_h2_on_search_node() {
+ public void accept_does_not_verify_h2_on_search_node() throws Exception {
TestAppSettings settings = newSettingsForSearchNode();
settings.set("sonar.jdbc.url", "jdbc:h2:mem");
// do not fail
- new ClusterSettings().accept(settings.getProps());
+ new ClusterSettings(network).accept(settings.getProps());
}
@Test
- public void accept_throws_MessageException_on_application_node_if_default_jdbc_url() {
+ public void accept_throws_MessageException_on_application_node_if_default_jdbc_url() throws Exception {
TestAppSettings settings = newSettingsForAppNode();
settings.clearProperty(JDBC_URL);
expectedException.expect(MessageException.class);
expectedException.expectMessage("Embedded database is not supported in cluster mode");
- new ClusterSettings().accept(settings.getProps());
+ new ClusterSettings(network).accept(settings.getProps());
}
@Test
}
@Test
- public void isLocalElasticsearchEnabled_returns_true_on_search_node() {
+ public void isLocalElasticsearchEnabled_returns_true_on_search_node() throws Exception {
TestAppSettings settings = newSettingsForSearchNode();
assertThat(ClusterSettings.isLocalElasticsearchEnabled(settings)).isTrue();
}
@Test
- public void isLocalElasticsearchEnabled_returns_true_for_a_application_node() {
+ public void isLocalElasticsearchEnabled_returns_true_for_a_application_node() throws Exception {
TestAppSettings settings = newSettingsForAppNode();
assertThat(ClusterSettings.isLocalElasticsearchEnabled(settings)).isFalse();
}
@Test
- public void accept_throws_MessageException_if_searchHost_is_missing() {
+ public void accept_throws_MessageException_if_searchHost_is_missing() throws Exception {
TestAppSettings settings = newSettingsForSearchNode();
settings.clearProperty(SEARCH_HOST);
assertThatPropertyIsMandatory(settings, SEARCH_HOST);
}
@Test
- public void accept_throws_MessageException_if_searchHost_is_blank() {
+ public void accept_throws_MessageException_if_searchHost_is_empty() throws Exception {
TestAppSettings settings = newSettingsForSearchNode();
- settings.set(SEARCH_HOST, " ");
+ settings.set(SEARCH_HOST, "");
assertThatPropertyIsMandatory(settings, SEARCH_HOST);
}
@Test
- public void accept_throws_MessageException_if_clusterHosts_is_missing() {
+ public void accept_throws_MessageException_if_clusterHosts_is_missing() throws Exception {
TestAppSettings settings = newSettingsForSearchNode();
settings.clearProperty(CLUSTER_HOSTS);
assertThatPropertyIsMandatory(settings, CLUSTER_HOSTS);
}
@Test
- public void accept_throws_MessageException_if_clusterHosts_is_blank() {
- TestAppSettings settings = newSettingsForSearchNode();
- settings.set(CLUSTER_HOSTS, " ");
- assertThatPropertyIsMandatory(settings, CLUSTER_HOSTS);
- }
-
- @Test
- public void accept_throws_MessageException_if_clusterSearchHosts_is_missing() {
+ public void accept_throws_MessageException_if_clusterSearchHosts_is_missing() throws Exception {
TestAppSettings settings = newSettingsForSearchNode();
settings.clearProperty(CLUSTER_SEARCH_HOSTS);
assertThatPropertyIsMandatory(settings, CLUSTER_SEARCH_HOSTS);
}
@Test
- public void accept_throws_MessageException_if_clusterSearchHosts_is_blank() {
+ public void accept_throws_MessageException_if_clusterSearchHosts_is_empty() throws Exception {
TestAppSettings settings = newSettingsForSearchNode();
- settings.set(CLUSTER_SEARCH_HOSTS, " ");
+ settings.set(CLUSTER_SEARCH_HOSTS, "");
assertThatPropertyIsMandatory(settings, CLUSTER_SEARCH_HOSTS);
}
@Test
- public void accept_throws_MessageException_if_jwt_token_is_not_set_on_application_nodes() {
+ public void accept_throws_MessageException_if_jwt_token_is_not_set_on_application_nodes() throws Exception {
TestAppSettings settings = newSettingsForAppNode();
settings.clearProperty("sonar.auth.jwtBase64Hs256Secret");
assertThatPropertyIsMandatory(settings, "sonar.auth.jwtBase64Hs256Secret");
private void assertThatPropertyIsMandatory(TestAppSettings settings, String key) {
expectedException.expect(MessageException.class);
- expectedException.expectMessage(format("Property [%s] is mandatory", key));
+ expectedException.expectMessage(format("Property %s is mandatory", key));
- new ClusterSettings().accept(settings.getProps());
+ new ClusterSettings(network).accept(settings.getProps());
}
- private static TestAppSettings newSettingsForAppNode() {
+ private TestAppSettings newSettingsForAppNode() throws SocketException {
return new TestAppSettings()
.set(CLUSTER_ENABLED, "true")
.set(CLUSTER_NODE_TYPE, "application")
- .set(CLUSTER_SEARCH_HOSTS, "localhost")
- .set(CLUSTER_HOSTS, "192.168.233.1, 192.168.233.2,192.168.233.3")
+ .set(CLUSTER_NODE_HOST, nonLoopbackLocal.getHostAddress())
+ .set(CLUSTER_HOSTS, nonLoopbackLocal.getHostAddress())
+ .set(CLUSTER_SEARCH_HOSTS, nonLoopbackLocal.getHostAddress())
.set("sonar.auth.jwtBase64Hs256Secret", "abcde")
.set(JDBC_URL, "jdbc:mysql://localhost:3306/sonar");
}
- private static TestAppSettings newSettingsForSearchNode() {
+ private TestAppSettings newSettingsForSearchNode() throws SocketException {
return new TestAppSettings()
.set(CLUSTER_ENABLED, "true")
.set(CLUSTER_NODE_TYPE, "search")
- .set(CLUSTER_SEARCH_HOSTS, "192.168.233.1")
- .set(CLUSTER_HOSTS, "192.168.233.1, 192.168.233.2,192.168.233.3")
- .set(SEARCH_HOST, getNonLoopbackIpv4Address().getHostName());
- }
-
- private static InetAddress getNonLoopbackIpv4Address() {
- try {
- Enumeration<NetworkInterface> nets = NetworkInterface.getNetworkInterfaces();
- for (NetworkInterface networkInterface : Collections.list(nets)) {
- if (!networkInterface.isLoopback() && networkInterface.isUp()) {
- Enumeration<InetAddress> inetAddresses = networkInterface.getInetAddresses();
- while (inetAddresses.hasMoreElements()) {
- InetAddress inetAddress = inetAddresses.nextElement();
- if (inetAddress instanceof Inet4Address) {
- return inetAddress;
- }
- }
- }
- }
- } catch (SocketException se) {
- throw new RuntimeException("Cannot find a non loopback card required for tests", se);
- }
- throw new RuntimeException("Cannot find a non loopback card required for tests");
+ .set(CLUSTER_NODE_HOST, nonLoopbackLocal.getHostAddress())
+ .set(CLUSTER_HOSTS, nonLoopbackLocal.getHostAddress())
+ .set(CLUSTER_SEARCH_HOSTS, nonLoopbackLocal.getHostAddress())
+ .set(SEARCH_HOST, nonLoopbackLocal.getHostAddress());
}
}
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.sonar.application.logging.ListAppender;
-import org.sonar.process.cluster.ClusterProperties;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
import org.sonar.process.System2;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NAME;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_SEARCH_HOSTS;
+import static org.sonar.process.ProcessProperties.CLUSTER_NAME;
+import static org.sonar.process.ProcessProperties.CLUSTER_SEARCH_HOSTS;
public class EsSettingsTest {
props.set(ProcessProperties.PATH_HOME, homeDir.getAbsolutePath());
props.set(ProcessProperties.PATH_TEMP, temp.newFolder().getAbsolutePath());
props.set(ProcessProperties.PATH_LOGS, temp.newFolder().getAbsolutePath());
- props.set(ClusterProperties.CLUSTER_NAME, "sonarqube-1");
- props.set(ClusterProperties.CLUSTER_ENABLED, "true");
- props.set(ClusterProperties.CLUSTER_NODE_NAME, "node-1");
+ props.set(ProcessProperties.CLUSTER_NAME, "sonarqube-1");
+ props.set(ProcessProperties.CLUSTER_ENABLED, "true");
+ props.set(ProcessProperties.CLUSTER_NODE_NAME, "node-1");
EsSettings esSettings = new EsSettings(props, new EsFileSystem(props), System2.INSTANCE);
public void test_node_name_default_for_cluster_mode() throws Exception {
File homeDir = temp.newFolder();
Props props = new Props(new Properties());
- props.set(ClusterProperties.CLUSTER_NAME, "sonarqube");
- props.set(ClusterProperties.CLUSTER_ENABLED, "true");
+ props.set(ProcessProperties.CLUSTER_NAME, "sonarqube");
+ props.set(ProcessProperties.CLUSTER_ENABLED, "true");
props.set(ProcessProperties.SEARCH_PORT, "1234");
props.set(ProcessProperties.SEARCH_HOST, "127.0.0.1");
props.set(ProcessProperties.PATH_HOME, homeDir.getAbsolutePath());
public void test_node_name_default_for_standalone_mode() throws Exception {
File homeDir = temp.newFolder();
Props props = new Props(new Properties());
- props.set(ClusterProperties.CLUSTER_NAME, "sonarqube");
- props.set(ClusterProperties.CLUSTER_ENABLED, "false");
+ props.set(ProcessProperties.CLUSTER_NAME, "sonarqube");
+ props.set(ProcessProperties.CLUSTER_ENABLED, "false");
props.set(ProcessProperties.SEARCH_PORT, "1234");
props.set(ProcessProperties.SEARCH_HOST, "127.0.0.1");
props.set(ProcessProperties.PATH_HOME, homeDir.getAbsolutePath());
Props props = new Props(new Properties());
ProcessProperties.completeDefaults(props);
props.set(ProcessProperties.PATH_HOME, homeDir.getAbsolutePath());
- props.set(ClusterProperties.CLUSTER_ENABLED, Boolean.toString(cluster));
+ props.set(ProcessProperties.CLUSTER_ENABLED, Boolean.toString(cluster));
return props;
}
}
</properties>
<dependencies>
+ <dependency>
+ <groupId>com.hazelcast</groupId>
+ <artifactId>hazelcast</artifactId>
+ </dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
*/
package org.sonar.process;
+import java.net.Inet4Address;
import java.net.InetAddress;
+import java.net.SocketException;
+import java.net.UnknownHostException;
+import java.util.Optional;
+import java.util.function.Predicate;
public interface NetworkUtils {
NetworkUtils INSTANCE = new NetworkUtilsImpl();
* @return "ipv4_1, ipv4_2"
*/
String getIPAddresses();
+
+ /**
+ * Converts a text representation of an IP address or host name to
+ * a {@link InetAddress}.
+ * If text value references an IPv4 or IPv6 address, then DNS is
+ * not used.
+ */
+ InetAddress toInetAddress(String hostOrAddress) throws UnknownHostException;
+
+ boolean isLocalInetAddress(InetAddress address) throws SocketException;
+
+ boolean isLoopbackInetAddress(InetAddress address);
+
+ /**
+ * Returns the machine {@link InetAddress} that matches the specified
+ * predicate. If multiple addresses match then a single one
+ * is picked in a non deterministic way.
+ */
+ Optional<InetAddress> getLocalInetAddress(Predicate<InetAddress> predicate);
+
+ /**
+ * Returns a local {@link InetAddress} that is IPv4 and not
+ * loopback. If multiple addresses match then a single one
+ * is picked in a non deterministic way.
+ */
+ default Optional<InetAddress> getLocalNonLoopbackIpv4Address() {
+ return getLocalInetAddress(a -> !a.isLoopbackAddress() && a instanceof Inet4Address);
+ }
}
package org.sonar.process;
import com.google.common.annotations.VisibleForTesting;
+import com.google.common.net.InetAddresses;
import java.io.IOException;
import java.net.Inet4Address;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.SocketException;
import java.net.UnknownHostException;
+import java.util.Collections;
import java.util.HashSet;
+import java.util.Optional;
import java.util.Set;
+import java.util.function.Predicate;
import java.util.stream.Collectors;
import static java.util.Collections.list;
import static org.apache.commons.lang.StringUtils.isBlank;
-public final class NetworkUtilsImpl implements NetworkUtils {
+public class NetworkUtilsImpl implements NetworkUtils {
- private static final Set<Integer> ALREADY_ALLOCATED = new HashSet<>();
- private static final int MAX_TRIES = 50;
+ private static final Set<Integer> PORTS_ALREADY_ALLOCATED = new HashSet<>();
+ private static final int PORT_MAX_TRIES = 50;
NetworkUtilsImpl() {
// prevent instantiation
*/
@VisibleForTesting
static int getNextAvailablePort(InetAddress address, PortAllocator portAllocator) {
- for (int i = 0; i < MAX_TRIES; i++) {
+ for (int i = 0; i < PORT_MAX_TRIES; i++) {
int port = portAllocator.getAvailable(address);
if (isValidPort(port)) {
- ALREADY_ALLOCATED.add(port);
+ PORTS_ALREADY_ALLOCATED.add(port);
return port;
}
}
}
private static boolean isValidPort(int port) {
- return port > 1023 && !ALREADY_ALLOCATED.contains(port);
+ return port > 1023 && !PORTS_ALREADY_ALLOCATED.contains(port);
}
static class PortAllocator {
return ips;
}
+
+ @Override
+ public InetAddress toInetAddress(String hostOrAddress) throws UnknownHostException {
+ if (InetAddresses.isInetAddress(hostOrAddress)) {
+ return InetAddresses.forString(hostOrAddress);
+ }
+ return InetAddress.getByName(hostOrAddress);
+ }
+
+ @Override
+ public boolean isLocalInetAddress(InetAddress address) throws SocketException {
+ return NetworkInterface.getByInetAddress(address) != null ;
+ }
+
+ @Override
+ public boolean isLoopbackInetAddress(InetAddress address) {
+ return address.isLoopbackAddress();
+ }
+
+ @Override
+ public Optional<InetAddress> getLocalInetAddress(Predicate<InetAddress> predicate) {
+ try {
+ return Collections.list(NetworkInterface.getNetworkInterfaces()).stream()
+ .flatMap(ni -> Collections.list(ni.getInetAddresses()).stream())
+ .filter(a -> a.getHostAddress() != null)
+ .filter(predicate)
+ .findFirst();
+ } catch (SocketException e) {
+ throw new IllegalStateException("Can not retrieve network interfaces", e);
+ }
+ }
}
import java.net.UnknownHostException;
import java.util.Map;
import java.util.Properties;
-
-import static org.sonar.process.cluster.ClusterProperties.putClusterDefaults;
+import java.util.UUID;
/**
* Constants shared by search, web server and app processes.
public static final String HTTP_PROXY_USER = "http.proxyUser";
public static final String HTTP_PROXY_PASSWORD = "http.proxyPassword";
+ public static final String CLUSTER_ENABLED = "sonar.cluster.enabled";
+ public static final String CLUSTER_NODE_TYPE = "sonar.cluster.node.type";
+ public static final String CLUSTER_SEARCH_HOSTS = "sonar.cluster.search.hosts";
+ public static final String CLUSTER_HOSTS = "sonar.cluster.hosts";
+ public static final String CLUSTER_NODE_PORT = "sonar.cluster.node.port";
+ public static final int CLUSTER_NODE_PORT_DEFAULT_VALUE = 9003;
+ public static final String CLUSTER_NODE_HOST = "sonar.cluster.node.host";
+ public static final String CLUSTER_NODE_NAME = "sonar.cluster.node.name";
+ public static final String CLUSTER_NAME = "sonar.cluster.name";
+ public static final String CLUSTER_WEB_STARTUP_LEADER = "sonar.cluster.web.startupLeader";
+
private ProcessProperties() {
// only static stuff
}
fixPortIfZero(props, SEARCH_HOST, SEARCH_PORT);
}
- private static void fixPortIfZero(Props props, String addressPropertyKey, String portPropertyKey) {
- String port = props.value(portPropertyKey);
- if ("0".equals(port)) {
- String address = props.nonNullValue(addressPropertyKey);
- try {
- props.set(portPropertyKey, String.valueOf(NetworkUtils.INSTANCE.getNextAvailablePort(InetAddress.getByName(address))));
- } catch (UnknownHostException e) {
- throw new IllegalStateException("Cannot resolve address [" + address + "] set by property [" + addressPropertyKey + "]", e);
- }
- }
- }
-
public static Properties defaults() {
Properties defaults = new Properties();
defaults.put(SEARCH_HOST, InetAddress.getLoopbackAddress().getHostAddress());
defaults.put(JDBC_MIN_EVICTABLE_IDLE_TIME_MILLIS, "600000");
defaults.put(JDBC_TIME_BETWEEN_EVICTION_RUNS_MILLIS, "30000");
- putClusterDefaults(defaults);
+ defaults.put(CLUSTER_ENABLED, "false");
+ defaults.put(CLUSTER_NAME, "sonarqube");
+ defaults.put(CLUSTER_NODE_PORT, Integer.toString(CLUSTER_NODE_PORT_DEFAULT_VALUE));
+ defaults.put(CLUSTER_NODE_NAME, "sonarqube-" + UUID.randomUUID().toString());
return defaults;
}
+
+ private static void fixPortIfZero(Props props, String addressPropertyKey, String portPropertyKey) {
+ String port = props.value(portPropertyKey);
+ if ("0".equals(port)) {
+ String address = props.nonNullValue(addressPropertyKey);
+ try {
+ props.set(portPropertyKey, String.valueOf(NetworkUtils.INSTANCE.getNextAvailablePort(InetAddress.getByName(address))));
+ } catch (UnknownHostException e) {
+ throw new IllegalStateException("Cannot resolve address [" + address + "] set by property [" + addressPropertyKey + "]", e);
+ }
+ }
+ }
}
+++ /dev/null
-/*
- * 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.process.cluster;
-
-/**
- * This class holds all object keys accessible via Hazelcast
- */
-public final class ClusterObjectKeys {
-
- private ClusterObjectKeys() {
- // Holder for clustered objects
- }
-
- /**
- * The key of replicated map that hold all operational processes
- */
- public static final String OPERATIONAL_PROCESSES = "OPERATIONAL_PROCESSES";
- /**
- * The key of atomic reference holding the leader UUID
- */
- public static final String LEADER = "LEADER";
- /**
- * 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
- * {@link NodeType}
- */
- public static final String NODE_TYPE = "NODE_TYPE";
- /**
- * 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
- */
- public static final String LOCAL_MEMBER_UUIDS = "LOCAL_MEMBER_UUIDS";
- /**
- * The key of replicated map holding the CeWorker UUIDs
- */
- public static final String WORKER_UUIDS = "WORKER_UUIDS";
- /**
- * The key of the lock for executing CE_CLEANING_JOB
- * {@link CeCleaningSchedulerImpl}
- */
- public static final String CE_CLEANING_JOB_LOCK = "CE_CLEANING_JOB_LOCK";
- /**
- * THe key of the replicated map holding the health state information of all SQ nodes.
- */
- public static final String SQ_HEALTH_STATE = "sq_health_state";
-}
+++ /dev/null
-/*
- * 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.process.cluster;
-
-import java.util.Properties;
-import java.util.UUID;
-
-import static java.lang.String.valueOf;
-
-public final class ClusterProperties {
- public static final String CLUSTER_ENABLED = "sonar.cluster.enabled";
- public static final String CLUSTER_NODE_TYPE = "sonar.cluster.node.type";
- public static final String CLUSTER_SEARCH_HOSTS = "sonar.cluster.search.hosts";
- public static final String CLUSTER_HOSTS = "sonar.cluster.hosts";
- public static final String CLUSTER_NODE_PORT = "sonar.cluster.node.port";
- public static final String CLUSTER_NODE_HOST = "sonar.cluster.node.host";
- public static final String CLUSTER_NODE_NAME = "sonar.cluster.node.name";
- public static final String CLUSTER_NAME = "sonar.cluster.name";
- public static final String HAZELCAST_LOG_LEVEL = "sonar.log.level.app.hazelcast";
- public static final String CLUSTER_WEB_LEADER = "sonar.cluster.web.startupLeader";
- // Internal property used by sonar-application to share the local endpoint of Hazelcast
- public static final String CLUSTER_LOCALENDPOINT = "sonar.cluster.hazelcast.localEndPoint";
- // Internal property used by sonar-application to share the local UUID of the Hazelcast member
- public static final String CLUSTER_MEMBERUUID = "sonar.cluster.hazelcast.memberUUID";
-
- private ClusterProperties() {
- // prevents instantiation
- }
-
- public static void putClusterDefaults(Properties properties) {
- properties.put(CLUSTER_ENABLED, valueOf(false));
- properties.put(CLUSTER_NAME, "sonarqube");
- properties.put(CLUSTER_NODE_HOST, "");
- properties.put(CLUSTER_HOSTS, "");
- properties.put(CLUSTER_NODE_PORT, "9003");
- properties.put(CLUSTER_NODE_NAME, "sonarqube-" + UUID.randomUUID().toString());
- properties.put(HAZELCAST_LOG_LEVEL, "WARN");
- }
-}
+++ /dev/null
-/*
- * 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.process.cluster;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.locks.Lock;
-
-/**
- * The interface Hazelcast client wrapper.
- */
-public interface HazelcastClient {
- /**
- * Gets the set shared by the cluster and identified by name
- */
- <E> Set<E> getSet(String name);
-
- /**
- * Gets the list shared by the cluster and identified by name
- */
- <E> List<E> getList(String name);
-
- /**
- * Gets the map shared by the cluster and identified by name
- */
- <K, V> Map<K, V> getMap(String name);
-
- /**
- * Gets the replicated map shared by the cluster and identified by name
- */
- <K,V> Map<K,V> getReplicatedMap(String name);
-
- /**
- * The UUID of the Hazelcast client.
- *
- * <p>The uuid of the member of the current client is a member, otherwise the UUID of the client if the
- * member is a local client of one of the members.</p>
- */
- String getUUID();
-
- /**
- * The UUIDs of all the members (both members and local clients of these members) currently connected to the
- * Hazelcast cluster.
- */
- Set<String> getMemberUuids();
-
- /**
- * Gets lock among the cluster, identified by name
- */
- Lock getLock(String name);
-
- /**
- * Retrieves the cluster time which is (alsmost) identical on all members of the cluster.
- */
- long getClusterTime();
-}
package org.sonar.process.cluster;
import static java.util.Arrays.stream;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_TYPE;
public enum NodeType {
APPLICATION("application"), SEARCH("search");
return stream(values())
.filter(t -> nodeType.equals(t.value))
.findFirst()
- .orElseThrow(() -> new IllegalArgumentException("Invalid value for [" + CLUSTER_NODE_TYPE + "]: [" + nodeType + "]"));
+ .orElseThrow(() -> new IllegalArgumentException("Invalid value: " + nodeType));
}
public static boolean isValid(String nodeType) {
+++ /dev/null
-/*
- * 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.process.cluster.health;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.Future;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.ScheduledFuture;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-class DelegateHealthStateRefresherExecutorService implements HealthStateRefresherExecutorService {
- private final ScheduledExecutorService delegate;
-
- DelegateHealthStateRefresherExecutorService(ScheduledExecutorService delegate) {
- this.delegate = delegate;
- }
-
- @Override
- public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
- return delegate.schedule(command, delay, unit);
- }
-
- @Override
- public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit) {
- return delegate.schedule(callable, delay, unit);
- }
-
- @Override
- public ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) {
- return delegate.scheduleAtFixedRate(command, initialDelay, period, unit);
- }
-
- @Override
- public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) {
- return delegate.scheduleWithFixedDelay(command, initialDelay, delay, unit);
- }
-
- @Override
- public void shutdown() {
- delegate.shutdown();
- }
-
- @Override
- public List<Runnable> shutdownNow() {
- return delegate.shutdownNow();
- }
-
- @Override
- public boolean isShutdown() {
- return delegate.isShutdown();
- }
-
- @Override
- public boolean isTerminated() {
- return delegate.isTerminated();
- }
-
- @Override
- public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
- return delegate.awaitTermination(timeout, unit);
- }
-
- @Override
- public <T> Future<T> submit(Callable<T> task) {
- return delegate.submit(task);
- }
-
- @Override
- public <T> Future<T> submit(Runnable task, T result) {
- return delegate.submit(task, result);
- }
-
- @Override
- public Future<?> submit(Runnable task) {
- return delegate.submit(task);
- }
-
- @Override
- public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException {
- return delegate.invokeAll(tasks);
- }
-
- @Override
- public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException {
- return delegate.invokeAll(tasks, timeout, unit);
- }
-
- @Override
- public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
- return delegate.invokeAny(tasks);
- }
-
- @Override
- public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
- return delegate.invokeAny(tasks, timeout, unit);
- }
-
- @Override
- public void execute(Runnable command) {
- delegate.execute(command);
- }
-}
+++ /dev/null
-/*
- * 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.process.cluster.health;
-
-public interface HealthStateSharing {
- void start();
-
- void stop();
-}
+++ /dev/null
-/*
- * 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.process.cluster.health;
-
-import com.google.common.util.concurrent.ThreadFactoryBuilder;
-import java.util.concurrent.Executors;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.sonar.process.cluster.HazelcastClient;
-
-import static java.lang.String.format;
-
-public class HealthStateSharingImpl implements HealthStateSharing {
- private static final Logger LOG = LoggerFactory.getLogger(HealthStateSharingImpl.class);
-
- private final HazelcastClient hazelcastClient;
- private final NodeHealthProvider nodeHealthProvider;
- private HealthStateRefresherExecutorService executorService;
- private HealthStateRefresher healthStateRefresher;
-
- public HealthStateSharingImpl(HazelcastClient hazelcastClient, NodeHealthProvider nodeHealthProvider) {
- this.hazelcastClient = hazelcastClient;
- this.nodeHealthProvider = nodeHealthProvider;
- }
-
- @Override
- public void start() {
- executorService = new DelegateHealthStateRefresherExecutorService(
- Executors.newSingleThreadScheduledExecutor(
- new ThreadFactoryBuilder()
- .setDaemon(false)
- .setNameFormat("health_state_refresh-%d")
- .build()));
- healthStateRefresher = new HealthStateRefresher(executorService, nodeHealthProvider, new SharedHealthStateImpl(hazelcastClient));
- healthStateRefresher.start();
- }
-
- @Override
- public void stop() {
- healthStateRefresher.stop();
- stopExecutorService(executorService);
- }
-
- private static void stopExecutorService(ScheduledExecutorService executorService) {
- // Disable new tasks from being submitted
- executorService.shutdown();
- try {
- // Wait a while for existing tasks to terminate
- if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
- // Cancel currently executing tasks
- executorService.shutdownNow();
- // Wait a while for tasks to respond to being canceled
- if (!executorService.awaitTermination(5, TimeUnit.SECONDS)) {
- LOG.warn("Pool {} did not terminate", HealthStateSharingImpl.class.getSimpleName());
- }
- }
- } catch (InterruptedException ie) {
- LOG.warn(format("Termination of pool %s failed", HealthStateSharingImpl.class.getSimpleName()), ie);
- // (Re-)Cancel if current thread also interrupted
- executorService.shutdownNow();
- Thread.currentThread().interrupt();
- }
- }
-
-}
public interface NodeHealthProvider {
/**
- * Returns the {@link NodeHealth} for the current SonarQube instance.
+ * Returns the {@link NodeHealth} for the current SonarQube node.
*
* <p>Implementation must support being called very frequently and from concurrent threads</p>
*/
import java.util.stream.Collectors;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.sonar.process.cluster.ClusterObjectKeys;
-import org.sonar.process.cluster.HazelcastClient;
+import org.sonar.process.cluster.hz.HazelcastMember;
+import org.sonar.process.cluster.hz.HazelcastObjects;
import static java.util.Objects.requireNonNull;
private static final Logger LOG = LoggerFactory.getLogger(SharedHealthStateImpl.class);
private static final int TIMEOUT_30_SECONDS = 30 * 1000;
- private final HazelcastClient hazelcastClient;
+ private final HazelcastMember hzMember;
- public SharedHealthStateImpl(HazelcastClient hazelcastClient) {
- this.hazelcastClient = hazelcastClient;
+ public SharedHealthStateImpl(HazelcastMember hzMember) {
+ this.hzMember = hzMember;
}
@Override
if (LOG.isTraceEnabled()) {
LOG.trace("Reading {} and adding {}", new HashMap<>(sqHealthState), nodeHealth);
}
- sqHealthState.put(hazelcastClient.getUUID(), new TimestampedNodeHealth(nodeHealth, hazelcastClient.getClusterTime()));
+ sqHealthState.put(hzMember.getUuid(), new TimestampedNodeHealth(nodeHealth, hzMember.getClusterTime()));
}
@Override
public void clearMine() {
Map<String, TimestampedNodeHealth> sqHealthState = readReplicatedMap();
- String clientUUID = hazelcastClient.getUUID();
+ String clientUUID = hzMember.getUuid();
if (LOG.isTraceEnabled()) {
LOG.trace("Reading {} and clearing for {}", new HashMap<>(sqHealthState), clientUUID);
}
@Override
public Set<NodeHealth> readAll() {
- long clusterTime = hazelcastClient.getClusterTime();
+ long clusterTime = hzMember.getClusterTime();
long timeout = clusterTime - TIMEOUT_30_SECONDS;
Map<String, TimestampedNodeHealth> sqHealthState = readReplicatedMap();
- Set<String> hzMemberUUIDs = hazelcastClient.getMemberUuids();
+ Set<String> hzMemberUUIDs = hzMember.getMemberUuids();
Set<NodeHealth> existingNodeHealths = sqHealthState.entrySet().stream()
.filter(outOfDate(timeout))
.filter(ofNonExistentMember(hzMemberUUIDs))
}
private Map<String, TimestampedNodeHealth> readReplicatedMap() {
- return hazelcastClient.getReplicatedMap(ClusterObjectKeys.SQ_HEALTH_STATE);
+ return hzMember.getReplicatedMap(HazelcastObjects.SQ_HEALTH_STATE);
}
}
--- /dev/null
+/*
+ * 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.process.cluster.hz;
+
+import com.hazelcast.core.Cluster;
+import com.hazelcast.core.IAtomicReference;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import org.sonar.process.ProcessId;
+import org.sonar.process.cluster.NodeType;
+
+public interface HazelcastMember extends AutoCloseable {
+
+ interface Attribute {
+ /**
+ * The key of the hostname attribute of a member
+ */
+ String HOSTNAME = "HOSTNAME";
+ /**
+ * The key of the ips list attribute of a member
+ */
+ String IP_ADDRESSES = "IP_ADDRESSES";
+ /**
+ * The key of the node name attribute of a member
+ */
+ String NODE_NAME = "NODE_NAME";
+ /**
+ * The role of the sonar-application inside the SonarQube cluster
+ * {@link NodeType}
+ */
+ String NODE_TYPE = "NODE_TYPE";
+ /**
+ * Key of process as defined by {@link ProcessId#getKey()}
+ */
+ String PROCESS_KEY = "PROCESS_KEY";
+ }
+
+ <E> IAtomicReference<E> getAtomicReference(String name);
+
+ /**
+ * Gets the set shared by the cluster and identified by name
+ */
+ <E> Set<E> getSet(String name);
+
+ /**
+ * Gets the list shared by the cluster and identified by name
+ */
+ <E> List<E> getList(String name);
+
+ /**
+ * Gets the map shared by the cluster and identified by name
+ */
+ <K, V> Map<K, V> getMap(String name);
+
+ /**
+ * Gets the replicated map shared by the cluster and identified by name.
+ * Result can be casted to {@link com.hazelcast.core.ReplicatedMap} if needed to
+ * benefit from listeners.
+ */
+ <K, V> Map<K, V> getReplicatedMap(String name);
+
+ String getUuid();
+
+ /**
+ * The UUIDs of all the members (both members and local clients of these members) currently connected to the
+ * Hazelcast cluster.
+ */
+ Set<String> getMemberUuids();
+
+ /**
+ * Gets lock among the cluster, identified by name
+ */
+ Lock getLock(String name);
+
+ /**
+ * Retrieves the cluster time which is (almost) identical on all members of the cluster.
+ */
+ long getClusterTime();
+
+ Cluster getCluster();
+
+ @Override
+ void close();
+}
--- /dev/null
+/*
+ * 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.process.cluster.hz;
+
+import com.hazelcast.config.Config;
+import com.hazelcast.config.JoinConfig;
+import com.hazelcast.config.MemberAttributeConfig;
+import com.hazelcast.config.NetworkConfig;
+import com.hazelcast.core.Hazelcast;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import javax.annotation.CheckForNull;
+import org.sonar.process.NetworkUtils;
+import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.cluster.NodeType;
+import org.sonar.process.cluster.hz.HazelcastMember.Attribute;
+
+import static java.lang.String.format;
+import static java.util.Collections.singletonList;
+import static java.util.Objects.requireNonNull;
+
+public class HazelcastMemberBuilder {
+
+ private String clusterName;
+ private String nodeName;
+ private int port;
+ private NodeType nodeType;
+ private ProcessId processId;
+ private String networkInterface;
+ private List<String> members = new ArrayList<>();
+
+ public HazelcastMemberBuilder setClusterName(String s) {
+ this.clusterName = s;
+ return this;
+ }
+
+ public HazelcastMemberBuilder setNodeName(String s) {
+ this.nodeName = s;
+ return this;
+ }
+
+ public HazelcastMemberBuilder setNodeType(NodeType t) {
+ this.nodeType = t;
+ return this;
+ }
+
+ public HazelcastMemberBuilder setProcessId(ProcessId p) {
+ this.processId = p;
+ return this;
+ }
+
+ public HazelcastMemberBuilder setPort(int i) {
+ this.port = i;
+ return this;
+ }
+
+ public HazelcastMemberBuilder setNetworkInterface(String s) {
+ this.networkInterface = s;
+ return this;
+ }
+
+ @CheckForNull
+ List<String> getMembers() {
+ return members;
+ }
+
+ /**
+ * Adds references to cluster members. If port is missing, then default
+ * port is automatically added.
+ */
+ public HazelcastMemberBuilder setMembers(Collection<String> c) {
+ this.members = c.stream()
+ .map(host -> host.contains(":") ? host : format("%s:%d", host, ProcessProperties.CLUSTER_NODE_PORT_DEFAULT_VALUE))
+ .collect(Collectors.toList());
+ return this;
+ }
+
+ public HazelcastMember build() {
+ Config config = new Config();
+ config.getGroupConfig().setName(requireNonNull(clusterName, "Cluster name is missing"));
+
+ // Configure network
+ NetworkConfig netConfig = config.getNetworkConfig();
+ netConfig
+ .setPort(port)
+ .setPortAutoIncrement(false)
+ .setReuseAddress(true);
+ netConfig.getInterfaces()
+ .setEnabled(true)
+ .setInterfaces(singletonList(requireNonNull(networkInterface, "Network interface is missing")));
+
+ // Only allowing TCP/IP configuration
+ JoinConfig joinConfig = netConfig.getJoin();
+ joinConfig.getAwsConfig().setEnabled(false);
+ joinConfig.getMulticastConfig().setEnabled(false);
+ joinConfig.getTcpIpConfig().setEnabled(true);
+ joinConfig.getTcpIpConfig().setMembers(requireNonNull(members, "Members are missing"));
+ // We are not using the partition group of Hazelcast, so disabling it
+ config.getPartitionGroupConfig().setEnabled(false);
+
+ // Tweak HazelCast configuration
+ config
+ // 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");
+
+ MemberAttributeConfig attributes = config.getMemberAttributeConfig();
+ attributes.setStringAttribute(Attribute.HOSTNAME, NetworkUtils.INSTANCE.getHostname());
+ attributes.setStringAttribute(Attribute.IP_ADDRESSES, NetworkUtils.INSTANCE.getIPAddresses());
+ attributes.setStringAttribute(Attribute.NODE_NAME, requireNonNull(nodeName, "Node name is missing"));
+ attributes.setStringAttribute(Attribute.NODE_TYPE, requireNonNull(nodeType, "Node type is missing").getValue());
+ attributes.setStringAttribute(Attribute.PROCESS_KEY, requireNonNull(processId, "Process key is missing").getKey());
+
+ return new HazelcastMemberImpl(Hazelcast.newHazelcastInstance(config));
+ }
+
+}
--- /dev/null
+/*
+ * 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.process.cluster.hz;
+
+import com.hazelcast.core.Cluster;
+import com.hazelcast.core.HazelcastInstance;
+import com.hazelcast.core.HazelcastInstanceNotActiveException;
+import com.hazelcast.core.IAtomicReference;
+import com.hazelcast.core.Member;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import java.util.stream.Collectors;
+import org.slf4j.LoggerFactory;
+
+class HazelcastMemberImpl implements HazelcastMember {
+
+ private final HazelcastInstance hzInstance;
+
+ HazelcastMemberImpl(HazelcastInstance hzInstance) {
+ this.hzInstance = hzInstance;
+ }
+
+ @Override
+ public <E> IAtomicReference<E> getAtomicReference(String name) {
+ return hzInstance.getAtomicReference(name);
+ }
+
+ @Override
+ public <E> Set<E> getSet(String s) {
+ return hzInstance.getSet(s);
+ }
+
+ @Override
+ public <E> List<E> getList(String s) {
+ return hzInstance.getList(s);
+ }
+
+ @Override
+ public <K, V> Map<K, V> getMap(String s) {
+ return hzInstance.getMap(s);
+ }
+
+ @Override
+ public <K, V> Map<K, V> getReplicatedMap(String s) {
+ return hzInstance.getReplicatedMap(s);
+ }
+
+ @Override
+ public String getUuid() {
+ return hzInstance.getLocalEndpoint().getUuid();
+ }
+
+ @Override
+ public Set<String> getMemberUuids() {
+ return hzInstance.getCluster().getMembers().stream().map(Member::getUuid).collect(Collectors.toSet());
+ }
+
+ @Override
+ public Lock getLock(String s) {
+ return hzInstance.getLock(s);
+ }
+
+ @Override
+ public long getClusterTime() {
+ return hzInstance.getCluster().getClusterTime();
+ }
+
+ @Override
+ public Cluster getCluster() {
+ return hzInstance.getCluster();
+ }
+
+ @Override
+ public void close() {
+ try {
+ hzInstance.shutdown();
+ } catch (HazelcastInstanceNotActiveException e) {
+ LoggerFactory.getLogger(getClass()).debug("Unable to shutdown Hazelcast member", e);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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.process.cluster.hz;
+
+import org.sonar.process.ProcessId;
+import org.sonar.process.cluster.NodeType;
+
+/**
+ * This class holds all object keys accessible via Hazelcast
+ */
+public final class HazelcastObjects {
+
+ private HazelcastObjects() {
+ // Holder for clustered objects
+ }
+
+ /**
+ * The key of replicated map that hold all operational processes
+ */
+ public static final String OPERATIONAL_PROCESSES = "OPERATIONAL_PROCESSES";
+ /**
+ * The key of atomic reference holding the leader UUID
+ */
+ public static final String LEADER = "LEADER";
+ /**
+ * 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 replicated map holding the CeWorker UUIDs
+ */
+ public static final String WORKER_UUIDS = "WORKER_UUIDS";
+ /**
+ * The key of the lock for executing CE_CLEANING_JOB
+ * {@link CeCleaningSchedulerImpl}
+ */
+ public static final String CE_CLEANING_JOB_LOCK = "CE_CLEANING_JOB_LOCK";
+ /**
+ * THe key of the replicated map holding the health state information of all SQ nodes.
+ */
+ public static final String SQ_HEALTH_STATE = "sq_health_state";
+}
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.process.cluster.hz;
+
+import javax.annotation.ParametersAreNonnullByDefault;
--- /dev/null
+/*
+ * 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.process;
+
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
+import org.hamcrest.CoreMatchers;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assume.assumeThat;
+
+public class NetworkUtilsImplTest {
+
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private NetworkUtilsImpl underTest = new NetworkUtilsImpl();
+
+ @Test
+ public void getNextAvailablePort_returns_a_port() throws Exception {
+ int port = underTest.getNextAvailablePort(InetAddress.getLocalHost());
+ assertThat(port)
+ .isGreaterThan(1_023)
+ .isLessThanOrEqualTo(65_535);
+ }
+
+ @Test
+ public void getNextAvailablePort_does_not_return_twice_the_same_port() throws Exception {
+ Set<Integer> ports = new HashSet<>(Arrays.asList(
+ underTest.getNextAvailablePort(InetAddress.getLocalHost()),
+ underTest.getNextAvailablePort(InetAddress.getLocalHost()),
+ underTest.getNextAvailablePort(InetAddress.getLocalHost())));
+ assertThat(ports).hasSize(3);
+ }
+
+ @Test
+ public void getLocalNonLoopbackIpv4Address_returns_a_valid_local_and_non_loopback_ipv4() {
+ Optional<InetAddress> address = underTest.getLocalNonLoopbackIpv4Address();
+
+ // address is empty on offline builds
+ assumeThat(address.isPresent(), CoreMatchers.is(true));
+
+ assertThat(address.get()).isInstanceOf(Inet4Address.class);
+ assertThat(address.get().isLoopbackAddress()).isFalse();
+ }
+
+ @Test
+ public void getLocalInetAddress_filters_local_addresses() {
+ InetAddress address = underTest.getLocalInetAddress(InetAddress::isLoopbackAddress).get();
+ assertThat(address.isLoopbackAddress()).isTrue();
+ }
+
+ @Test
+ public void getLocalInetAddress_returns_empty_if_no_local_addresses_match() {
+ Optional<InetAddress> address = underTest.getLocalInetAddress(a -> false);
+ assertThat(address).isEmpty();
+ }
+
+ @Test
+ public void toInetAddress_supports_host_names() throws Exception {
+ assertThat(underTest.toInetAddress("localhost")).isNotNull();
+ // do not test values that require DNS calls. Build must support offline mode.
+ }
+
+ @Test
+ public void toInetAddress_supports_ipv4() throws Exception {
+ assertThat(underTest.toInetAddress("1.2.3.4")).isNotNull();
+ }
+
+ @Test
+ public void toInetAddress_supports_ipv6() throws Exception {
+ assertThat(underTest.toInetAddress("2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb")).isNotNull();
+ assertThat(underTest.toInetAddress("[2a01:e34:ef1f:dbb0:c2f6:a978:c5c0:9ccb]")).isNotNull();
+ }
+
+}
+++ /dev/null
-/*
- * 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.process.cluster.health;
-
-import java.util.Collection;
-import java.util.Random;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ScheduledExecutorService;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-import java.util.stream.Collectors;
-import java.util.stream.IntStream;
-import org.junit.Test;
-
-import static java.util.concurrent.TimeUnit.SECONDS;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-
-public class DelegateHealthStateRefresherExecutorServiceTest {
- private Random random = new Random();
- private Runnable runnable = mock(Runnable.class);
- private Callable callable = mock(Callable.class);
- private Collection<Callable<Object>> callables = IntStream.range(0, random.nextInt(5))
- .mapToObj(i -> (Callable<Object>) mock(Callable.class))
- .collect(Collectors.toList());
- private int initialDelay = random.nextInt(333);
- private int delay = random.nextInt(333);
- private int period = random.nextInt(333);
- private int timeout = random.nextInt(333);
- private Object result = new Object();
- private ScheduledExecutorService executorService = mock(ScheduledExecutorService.class);
- private DelegateHealthStateRefresherExecutorService underTest = new DelegateHealthStateRefresherExecutorService(executorService);
-
- @Test
- public void schedule() {
- underTest.schedule(runnable, delay, SECONDS);
-
- verify(executorService).schedule(runnable, delay, SECONDS);
- }
-
- @Test
- public void schedule1() {
- underTest.schedule(callable, delay, SECONDS);
-
- verify(executorService).schedule(callable, delay, SECONDS);
- }
-
- @Test
- public void scheduleAtFixedRate() {
- underTest.scheduleAtFixedRate(runnable, initialDelay, period, SECONDS);
- verify(executorService).scheduleAtFixedRate(runnable, initialDelay, period, SECONDS);
- }
-
- @Test
- public void scheduleWithFixeddelay() {
- underTest.scheduleWithFixedDelay(runnable, initialDelay, delay, TimeUnit.SECONDS);
- verify(executorService).scheduleWithFixedDelay(runnable, initialDelay, delay, TimeUnit.SECONDS);
- }
-
- @Test
- public void shutdown() {
- underTest.shutdown();
- verify(executorService).shutdown();
- }
-
- @Test
- public void shutdownNow() {
- underTest.shutdownNow();
- verify(executorService).shutdownNow();
- }
-
- @Test
- public void isShutdown() {
- underTest.isShutdown();
- verify(executorService).isShutdown();
- }
-
- @Test
- public void isTerminated() {
- underTest.isTerminated();
- verify(executorService).isTerminated();
- }
-
- @Test
- public void awaitTermination() throws InterruptedException {
- underTest.awaitTermination(timeout, TimeUnit.SECONDS);
-
- verify(executorService).awaitTermination(timeout, TimeUnit.SECONDS);
- }
-
- @Test
- public void submit() {
- underTest.submit(callable);
-
- verify(executorService).submit(callable);
- }
-
- @Test
- public void submit1() {
- underTest.submit(runnable, result);
-
- verify(executorService).submit(runnable, result);
- }
-
- @Test
- public void submit2() {
- underTest.submit(runnable);
- verify(executorService).submit(runnable);
- }
-
- @Test
- public void invokeAll() throws InterruptedException {
- underTest.invokeAll(callables);
- verify(executorService).invokeAll(callables);
- }
-
- @Test
- public void invokeAll1() throws InterruptedException {
- underTest.invokeAll(callables, timeout, SECONDS);
- verify(executorService).invokeAll(callables, timeout, SECONDS);
- }
-
- @Test
- public void invokeAny() throws InterruptedException, ExecutionException {
- underTest.invokeAny(callables);
- verify(executorService).invokeAny(callables);
- }
-
- @Test
- public void invokeAny2() throws InterruptedException, ExecutionException, TimeoutException {
- underTest.invokeAny(callables, timeout, SECONDS);
- verify(executorService).invokeAny(callables, timeout, SECONDS);
- }
-
-}
--- /dev/null
+/*
+ * 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.process.cluster.hz;
+
+import java.net.InetAddress;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.DisableOnDebug;
+import org.junit.rules.TestRule;
+import org.junit.rules.Timeout;
+import org.sonar.process.NetworkUtils;
+import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.cluster.NodeType;
+
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class HazelcastMemberBuilderTest {
+
+ @Rule
+ public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60));
+
+ // use loopback for support of offline builds
+ private InetAddress loopback = InetAddress.getLoopbackAddress();
+ private HazelcastMemberBuilder underTest = new HazelcastMemberBuilder();
+
+ @Test
+ public void build_member() {
+ HazelcastMember member = underTest
+ .setNodeType(NodeType.APPLICATION)
+ .setProcessId(ProcessId.COMPUTE_ENGINE)
+ .setClusterName("foo")
+ .setNodeName("bar")
+ .setPort(NetworkUtils.INSTANCE.getNextAvailablePort(loopback))
+ .setNetworkInterface(loopback.getHostAddress())
+ .build();
+
+ assertThat(member.getUuid()).isNotEmpty();
+ assertThat(member.getClusterTime()).isGreaterThan(0);
+ assertThat(member.getCluster().getMembers()).hasSize(1);
+ assertThat(member.getMemberUuids()).containsOnlyOnce(member.getUuid());
+
+ assertThat(member.getAtomicReference("baz")).isNotNull();
+ assertThat(member.getLock("baz")).isNotNull();
+ assertThat(member.getReplicatedMap("baz")).isNotNull();
+ assertThat(member.getList("baz")).isNotNull();
+ assertThat(member.getMap("baz")).isNotNull();
+ assertThat(member.getSet("baz")).isNotNull();
+
+ member.close();
+ }
+
+ @Test
+ public void default_port_is_added_when_missing() {
+ underTest.setMembers(asList("foo", "bar:9100", "1.2.3.4"));
+
+ assertThat(underTest.getMembers()).containsExactly(
+ "foo:" + ProcessProperties.CLUSTER_NODE_PORT_DEFAULT_VALUE,
+ "bar:9100",
+ "1.2.3.4:" + ProcessProperties.CLUSTER_NODE_PORT_DEFAULT_VALUE);
+ }
+}
--- /dev/null
+/*
+ * 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.server.cluster;
+
+import com.hazelcast.core.Cluster;
+import com.hazelcast.core.IAtomicReference;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.locks.Lock;
+import org.sonar.api.Startable;
+import org.sonar.api.config.Configuration;
+import org.sonar.process.NetworkUtils;
+import org.sonar.process.ProcessId;
+import org.sonar.process.ProcessProperties;
+import org.sonar.process.cluster.NodeType;
+import org.sonar.process.cluster.hz.HazelcastMember;
+import org.sonar.process.cluster.hz.HazelcastMemberBuilder;
+
+import static java.lang.String.format;
+import static java.util.Arrays.asList;
+import static java.util.Objects.requireNonNull;
+import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_KEY;
+import static org.sonar.process.ProcessProperties.CLUSTER_HOSTS;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_HOST;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_TYPE;
+
+/**
+ * Implementation of {@link HazelcastMember} as used by Compute Engine and
+ * Web Server processes. It is configured by {@link Configuration}
+ * and its lifecycle is managed by picocontainer.
+ */
+public class StartableHazelcastMember implements HazelcastMember, Startable {
+
+ private final Configuration config;
+ private final NetworkUtils networkUtils;
+ private HazelcastMember member = null;
+
+ public StartableHazelcastMember(Configuration config, NetworkUtils networkUtils) {
+ this.config = config;
+ this.networkUtils = networkUtils;
+ }
+
+ @Override
+ public <E> IAtomicReference<E> getAtomicReference(String name) {
+ return nonNullMember().getAtomicReference(name);
+ }
+
+ @Override
+ public <E> Set<E> getSet(String name) {
+ return nonNullMember().getSet(name);
+ }
+
+ @Override
+ public <E> List<E> getList(String name) {
+ return nonNullMember().getList(name);
+ }
+
+ @Override
+ public <K, V> Map<K, V> getMap(String name) {
+ return nonNullMember().getMap(name);
+ }
+
+ @Override
+ public <K, V> Map<K, V> getReplicatedMap(String name) {
+ return nonNullMember().getReplicatedMap(name);
+ }
+
+ @Override
+ public String getUuid() {
+ return nonNullMember().getUuid();
+ }
+
+ @Override
+ public Set<String> getMemberUuids() {
+ return nonNullMember().getMemberUuids();
+ }
+
+ @Override
+ public Lock getLock(String name) {
+ return nonNullMember().getLock(name);
+ }
+
+ @Override
+ public long getClusterTime() {
+ return nonNullMember().getClusterTime();
+ }
+
+ @Override
+ public Cluster getCluster() {
+ return nonNullMember().getCluster();
+ }
+
+ private HazelcastMember nonNullMember() {
+ return requireNonNull(member, "Hazelcast member not started");
+ }
+
+ @Override
+ public void close() {
+ if (member != null) {
+ member.close();
+ member = null;
+ }
+ }
+
+ @Override
+ public void start() {
+ String networkAddress = config.get(CLUSTER_NODE_HOST).orElseThrow(() -> new IllegalStateException("Missing node host"));
+ int freePort;
+ try {
+ freePort = networkUtils.getNextAvailablePort(InetAddress.getByName(networkAddress));
+ } catch (UnknownHostException e) {
+ throw new IllegalStateException(format("Can not resolve address %s", networkAddress), e);
+ }
+ this.member = new HazelcastMemberBuilder()
+ .setClusterName(config.get(ProcessProperties.CLUSTER_NAME).orElseThrow(() -> new IllegalStateException("Missing cluster name")))
+ .setNodeName(config.get(ProcessProperties.CLUSTER_NODE_NAME).orElseThrow(() -> new IllegalStateException("Missing node name")))
+ .setNodeType(NodeType.parse(config.get(CLUSTER_NODE_TYPE).orElseThrow(() -> new IllegalStateException("Missing node type"))))
+ .setPort(freePort)
+ .setProcessId(ProcessId.fromKey(config.get(PROPERTY_PROCESS_KEY).orElseThrow(() -> new IllegalStateException("Missing process key"))))
+ .setMembers(asList(config.getStringArray(CLUSTER_HOSTS)))
+ .setNetworkInterface(networkAddress)
+ .build();
+ }
+
+ @Override
+ public void stop() {
+ close();
+ }
+}
--- /dev/null
+/*
+ * 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.cluster;
+
+import javax.annotation.ParametersAreNonnullByDefault;
import org.sonar.process.ProcessProperties;
import static java.util.Collections.unmodifiableList;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NAME;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_TYPE;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_SEARCH_HOSTS;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_NAME;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_TYPE;
+import static org.sonar.process.ProcessProperties.CLUSTER_SEARCH_HOSTS;
import static org.sonar.process.cluster.NodeType.SEARCH;
@ComputeEngineSide
import static java.lang.String.format;
import static java.lang.String.valueOf;
import static java.util.Objects.requireNonNull;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
import static org.sonar.server.es.DefaultIndexSettings.ANALYZER;
import static org.sonar.server.es.DefaultIndexSettings.FIELDDATA_ENABLED;
import static org.sonar.server.es.DefaultIndexSettings.FIELD_FIELDDATA;
import org.sonar.process.cluster.health.NodeHealthProvider;
import static java.lang.String.format;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_HOST;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_NAME;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_PORT;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_HOST;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_NAME;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_PORT;
import static org.sonar.process.cluster.health.NodeDetails.newNodeDetailsBuilder;
import static org.sonar.process.cluster.health.NodeHealth.newNodeHealthBuilder;
+++ /dev/null
-/*
- * 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.server.hz;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Preconditions;
-import com.google.common.collect.ImmutableSet;
-import com.hazelcast.client.config.ClientConfig;
-import com.hazelcast.core.HazelcastInstance;
-import com.hazelcast.core.Member;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.locks.Lock;
-import org.picocontainer.Startable;
-import org.sonar.api.config.Configuration;
-import org.sonar.process.cluster.ClusterObjectKeys;
-import org.sonar.process.cluster.HazelcastClient;
-
-import static org.apache.commons.lang.StringUtils.isNotEmpty;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_LOCALENDPOINT;
-
-/**
- * This class will connect as a Hazelcast client to the local instance of Hazelcluster
- */
-public class HazelcastLocalClient implements Startable, HazelcastClient {
-
- private static final String HAZELCAST_CLUSTER_NAME = "sonarqube";
- private final ClientConfig hzConfig;
-
- @VisibleForTesting
- HazelcastInstance hzInstance;
-
- public HazelcastLocalClient(Configuration config) {
- boolean clusterEnabled = config.getBoolean(CLUSTER_ENABLED).orElse(false);
- String clusterLocalEndPoint = config.get(CLUSTER_LOCALENDPOINT).orElse(null);
-
- Preconditions.checkState(clusterEnabled, "Cluster is not enabled");
- Preconditions.checkState(isNotEmpty(clusterLocalEndPoint), "LocalEndPoint have not been set");
-
- hzConfig = new ClientConfig();
- hzConfig.getGroupConfig().setName(HAZELCAST_CLUSTER_NAME);
- hzConfig.getNetworkConfig().addAddress(clusterLocalEndPoint);
-
- // Tweak HazelCast configuration
- hzConfig
- // Increase the number of tries
- .setProperty("hazelcast.tcp.join.port.try.count", "10")
- // Don't phone home
- .setProperty("hazelcast.phone.home.enabled", "false")
- // Use slf4j for logging
- .setProperty("hazelcast.logging.type", "slf4j");
- }
-
- @Override
- public <E> Set<E> getSet(String name) {
- return hzInstance.getSet(name);
- }
-
- @Override
- public <E> List<E> getList(String name) {
- return hzInstance.getList(name);
- }
-
- @Override
- public <K, V> Map<K, V> getMap(String name) {
- return hzInstance.getMap(name);
- }
-
- @Override
- public <K, V> Map<K, V> getReplicatedMap(String name) {
- return hzInstance.getReplicatedMap(name);
- }
-
- @Override
- public String getUUID() {
- return hzInstance.getLocalEndpoint().getUuid();
- }
-
- @Override
- public Set<String> getMemberUuids() {
- ImmutableSet.Builder<String> builder = ImmutableSet.builder();
- builder.addAll(hzInstance.getSet(ClusterObjectKeys.LOCAL_MEMBER_UUIDS));
- hzInstance.getCluster().getMembers().stream().map(Member::getUuid).forEach(builder::add);
- return builder.build();
- }
-
- @Override
- public Lock getLock(String name) {
- return hzInstance.getLock(name);
- }
-
- @Override
- public long getClusterTime() {
- return hzInstance.getCluster().getClusterTime();
- }
-
- @Override
- public void start() {
- this.hzInstance = com.hazelcast.client.HazelcastClient.newHazelcastClient(hzConfig);
- }
-
- @Override
- public void stop() {
- // Shutdown Hazelcast properly
- hzInstance.shutdown();
- }
-}
+++ /dev/null
-/*
- * 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.
- */
-@ParametersAreNonnullByDefault
-package org.sonar.server.hz;
-
-import javax.annotation.ParametersAreNonnullByDefault;
*/
package org.sonar.server.platform;
-import org.sonar.process.cluster.ClusterProperties;
-
public interface WebServer {
/**
- * WebServer is standalone when property {@link ClusterProperties#CLUSTER_ENABLED} is {@code false} or
+ * WebServer is standalone when property {@link org.sonar.process.ProcessProperties#CLUSTER_ENABLED} is {@code false} or
* undefined.
*/
boolean isStandalone();
import org.sonar.api.config.Configuration;
import org.sonar.api.utils.log.Loggers;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_WEB_LEADER;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_WEB_STARTUP_LEADER;
public class WebServerImpl implements WebServer {
public WebServerImpl(Configuration config) {
this.clusterEnabled = config.getBoolean(CLUSTER_ENABLED).orElse(false);
if (this.clusterEnabled) {
- this.startupLeader = config.getBoolean(CLUSTER_WEB_LEADER).orElse(false);
+ this.startupLeader = config.getBoolean(CLUSTER_WEB_STARTUP_LEADER).orElse(false);
Loggers.get(WebServerImpl.class).info("Cluster enabled (startup {})", startupLeader ? "leader" : "follower");
} else {
this.startupLeader = true;
import org.sonar.server.batch.BatchWsModule;
import org.sonar.server.branch.BranchFeatureProxyImpl;
import org.sonar.server.ce.ws.CeWsModule;
+import org.sonar.server.cluster.StartableHazelcastMember;
import org.sonar.server.component.ComponentCleanerService;
import org.sonar.server.component.ComponentFinder;
import org.sonar.server.component.ComponentService;
import org.sonar.server.event.NewAlerts;
import org.sonar.server.favorite.FavoriteModule;
import org.sonar.server.health.NodeHealthModule;
-import org.sonar.server.hz.HazelcastLocalClient;
import org.sonar.server.issue.AddTagsAction;
import org.sonar.server.issue.AssignAction;
import org.sonar.server.issue.CommentAction;
EsDbCompatibilityImpl.class);
addIfCluster(
- HazelcastLocalClient.class,
+ StartableHazelcastMember.class,
NodeHealthModule.class);
add(
import static java.lang.String.format;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NAME;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_TYPE;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_SEARCH_HOSTS;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_NAME;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_TYPE;
+import static org.sonar.process.ProcessProperties.CLUSTER_SEARCH_HOSTS;
public class EsClientProviderTest {
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.data.MapEntry.entry;
import static org.junit.Assert.fail;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
import static org.sonar.server.es.NewIndex.SettingsConfiguration.newBuilder;
public class NewIndexTest {
import org.sonar.api.utils.System2;
import org.sonar.core.platform.ComponentContainer;
import org.sonar.process.NetworkUtils;
-import org.sonar.process.cluster.HazelcastClient;
import org.sonar.process.cluster.health.SharedHealthStateImpl;
+import org.sonar.process.cluster.hz.HazelcastMember;
import static java.lang.String.valueOf;
import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
mapSettings.asConfig(),
server,
networkUtils,
- mock(HazelcastClient.class));
+ mock(HazelcastMember.class));
// HealthAction dependencies
container.add(mock(HealthChecker.class));
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_HOST;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_NAME;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NODE_PORT;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_HOST;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_NAME;
+import static org.sonar.process.ProcessProperties.CLUSTER_NODE_PORT;
public class NodeHealthProviderImplTest {
@Rule
import static org.assertj.core.api.Assertions.assertThat;
import static org.elasticsearch.index.query.QueryBuilders.matchQuery;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_ENABLED;
+import static org.sonar.process.ProcessProperties.CLUSTER_ENABLED;
import static org.sonar.server.es.DefaultIndexSettingsElement.ENGLISH_HTML_ANALYZER;
import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_HTML_DESCRIPTION;
import static org.sonar.server.rule.index.RuleIndexDefinition.FIELD_RULE_KEY;
import org.sonar.application.command.CommandFactoryImpl;
import static org.sonar.application.config.SonarQubeVersionHelper.getSonarqubeVersion;
-import static org.sonar.process.cluster.ClusterProperties.CLUSTER_NAME;
+import static org.sonar.process.ProcessProperties.CLUSTER_NAME;
public class App {
try (AppState appState = new AppStateFactory(settings).create()) {
appState.registerSonarQubeVersion(getSonarqubeVersion());
- appState.registerClusterName(settings.getProps().value(CLUSTER_NAME, "sonarqube"));
+ appState.registerClusterName(settings.getProps().nonNullValue(CLUSTER_NAME));
AppReloader appReloader = new AppReloaderImpl(settingsLoader, fileSystem, appState, logging);
fileSystem.reset();
CommandFactory commandFactory = new CommandFactoryImpl(settings.getProps(), fileSystem.getTempDir(), System2.INSTANCE);
import org.junit.rules.TestRule;
import org.junit.rules.Timeout;
import org.sonarqube.ws.WsSystem;
+import org.sonarqube.ws.client.GetRequest;
import static com.google.common.base.Preconditions.checkState;
import static org.assertj.core.api.Assertions.assertThat;
db.stop();
}
+ /**
+ * TODO WIP
+ */
+ @Test
+ public void wip() throws Exception {
+ try (Cluster cluster = newCluster(3, 2)) {
+ cluster.getNodes().forEach(Node::start);
+
+ Node app = cluster.getAppNode(0);
+ app.waitForHealthGreen();
+
+ System.out.println("-----------------------------------------------------------------------");
+ String json = app.wsClient().wsConnector().call(new GetRequest("api/system/cluster_info")).content();
+ System.out.println(json);
+ System.out.println("-----------------------------------------------------------------------");
+ }
+ }
+
@Test
public void test_high_availability_topology() throws Exception {
try (Cluster cluster = newCluster(3, 2)) {