You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ClusterAppStateImpl.java 9.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2021 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.application.cluster;
  21. import com.hazelcast.cluster.Member;
  22. import com.hazelcast.cluster.MembershipEvent;
  23. import com.hazelcast.cluster.MembershipListener;
  24. import com.hazelcast.core.EntryEvent;
  25. import com.hazelcast.core.EntryListener;
  26. import com.hazelcast.core.HazelcastInstanceNotActiveException;
  27. import com.hazelcast.cp.IAtomicReference;
  28. import com.hazelcast.map.MapEvent;
  29. import com.hazelcast.replicatedmap.ReplicatedMap;
  30. import java.util.ArrayList;
  31. import java.util.EnumMap;
  32. import java.util.List;
  33. import java.util.Map;
  34. import java.util.Optional;
  35. import java.util.UUID;
  36. import org.elasticsearch.cluster.health.ClusterHealthStatus;
  37. import org.slf4j.Logger;
  38. import org.slf4j.LoggerFactory;
  39. import org.sonar.application.AppStateListener;
  40. import org.sonar.application.cluster.health.HealthStateSharing;
  41. import org.sonar.application.cluster.health.HealthStateSharingImpl;
  42. import org.sonar.application.cluster.health.SearchNodeHealthProvider;
  43. import org.sonar.application.config.AppSettings;
  44. import org.sonar.application.config.ClusterSettings;
  45. import org.sonar.application.es.EsConnector;
  46. import org.sonar.process.MessageException;
  47. import org.sonar.process.NetworkUtilsImpl;
  48. import org.sonar.process.ProcessId;
  49. import org.sonar.process.cluster.hz.HazelcastMember;
  50. import static java.lang.String.format;
  51. import static org.sonar.process.cluster.hz.HazelcastObjects.CLUSTER_NAME;
  52. import static org.sonar.process.cluster.hz.HazelcastObjects.LEADER;
  53. import static org.sonar.process.cluster.hz.HazelcastObjects.OPERATIONAL_PROCESSES;
  54. import static org.sonar.process.cluster.hz.HazelcastObjects.SONARQUBE_VERSION;
  55. public class ClusterAppStateImpl implements ClusterAppState {
  56. private static final Logger LOGGER = LoggerFactory.getLogger(ClusterAppStateImpl.class);
  57. private final HazelcastMember hzMember;
  58. private final List<AppStateListener> listeners = new ArrayList<>();
  59. private final Map<ProcessId, Boolean> operationalLocalProcesses = new EnumMap<>(ProcessId.class);
  60. private final ReplicatedMap<ClusterProcess, Boolean> operationalProcesses;
  61. private final UUID operationalProcessListenerUUID;
  62. private final UUID nodeDisconnectedListenerUUID;
  63. private final EsConnector esConnector;
  64. private HealthStateSharing healthStateSharing = null;
  65. public ClusterAppStateImpl(AppSettings settings, HazelcastMember hzMember, EsConnector esConnector, AppNodesClusterHostsConsistency appNodesClusterHostsConsistency) {
  66. this.hzMember = hzMember;
  67. // Get or create the replicated map
  68. operationalProcesses = (ReplicatedMap) hzMember.getReplicatedMap(OPERATIONAL_PROCESSES);
  69. operationalProcessListenerUUID = operationalProcesses.addEntryListener(new OperationalProcessListener());
  70. nodeDisconnectedListenerUUID = hzMember.getCluster().addMembershipListener(new NodeDisconnectedListener());
  71. appNodesClusterHostsConsistency.check();
  72. if (ClusterSettings.isLocalElasticsearchEnabled(settings)) {
  73. this.healthStateSharing = new HealthStateSharingImpl(hzMember, new SearchNodeHealthProvider(settings.getProps(), this, NetworkUtilsImpl.INSTANCE));
  74. this.healthStateSharing.start();
  75. }
  76. this.esConnector = esConnector;
  77. }
  78. @Override
  79. public HazelcastMember getHazelcastMember() {
  80. return hzMember;
  81. }
  82. @Override
  83. public void addListener(AppStateListener listener) {
  84. listeners.add(listener);
  85. }
  86. @Override
  87. public boolean isOperational(ProcessId processId, boolean local) {
  88. if (local) {
  89. return operationalLocalProcesses.computeIfAbsent(processId, p -> false);
  90. }
  91. if (processId.equals(ProcessId.ELASTICSEARCH)) {
  92. return isElasticSearchAvailable();
  93. }
  94. for (Map.Entry<ClusterProcess, Boolean> entry : operationalProcesses.entrySet()) {
  95. if (entry.getKey().getProcessId().equals(processId) && entry.getValue()) {
  96. return true;
  97. }
  98. }
  99. return false;
  100. }
  101. @Override
  102. public void setOperational(ProcessId processId) {
  103. operationalLocalProcesses.put(processId, true);
  104. operationalProcesses.put(new ClusterProcess(hzMember.getUuid(), processId), Boolean.TRUE);
  105. }
  106. @Override
  107. public boolean tryToLockWebLeader() {
  108. IAtomicReference<UUID> leader = hzMember.getAtomicReference(LEADER);
  109. return leader.compareAndSet(null, hzMember.getUuid());
  110. }
  111. @Override
  112. public void reset() {
  113. throw new IllegalStateException("state reset is not supported in cluster mode");
  114. }
  115. @Override
  116. public void registerSonarQubeVersion(String sonarqubeVersion) {
  117. IAtomicReference<String> sqVersion = hzMember.getAtomicReference(SONARQUBE_VERSION);
  118. boolean wasSet = sqVersion.compareAndSet(null, sonarqubeVersion);
  119. if (!wasSet) {
  120. String clusterVersion = sqVersion.get();
  121. if (!sqVersion.get().equals(sonarqubeVersion)) {
  122. throw new IllegalStateException(
  123. format("The local version %s is not the same as the cluster %s", sonarqubeVersion, clusterVersion));
  124. }
  125. }
  126. }
  127. @Override
  128. public void registerClusterName(String clusterName) {
  129. IAtomicReference<String> property = hzMember.getAtomicReference(CLUSTER_NAME);
  130. boolean wasSet = property.compareAndSet(null, clusterName);
  131. if (!wasSet) {
  132. String clusterValue = property.get();
  133. if (!property.get().equals(clusterName)) {
  134. throw new MessageException(
  135. format("This node has a cluster name [%s], which does not match [%s] from the cluster", clusterName, clusterValue));
  136. }
  137. }
  138. }
  139. @Override
  140. public Optional<String> getLeaderHostName() {
  141. UUID leaderUuid = (UUID) hzMember.getAtomicReference(LEADER).get();
  142. if (leaderUuid != null) {
  143. Optional<Member> leader = hzMember.getCluster().getMembers().stream().filter(m -> m.getUuid().equals(leaderUuid)).findFirst();
  144. if (leader.isPresent()) {
  145. return Optional.of(leader.get().getAddress().getHost());
  146. }
  147. }
  148. return Optional.empty();
  149. }
  150. @Override
  151. public void close() {
  152. esConnector.stop();
  153. if (hzMember != null) {
  154. if (healthStateSharing != null) {
  155. healthStateSharing.stop();
  156. }
  157. try {
  158. // Removing listeners
  159. operationalProcesses.removeEntryListener(operationalProcessListenerUUID);
  160. hzMember.getCluster().removeMembershipListener(nodeDisconnectedListenerUUID);
  161. // Removing the operationalProcess from the replicated map
  162. operationalProcesses.keySet().forEach(
  163. clusterNodeProcess -> {
  164. if (clusterNodeProcess.getNodeUuid().equals(hzMember.getUuid())) {
  165. operationalProcesses.remove(clusterNodeProcess);
  166. }
  167. });
  168. // Shutdown Hazelcast properly
  169. hzMember.close();
  170. } catch (HazelcastInstanceNotActiveException e) {
  171. // hazelcastCluster may be already closed by the shutdown hook
  172. LOGGER.debug("Unable to close Hazelcast cluster", e);
  173. }
  174. }
  175. }
  176. private boolean isElasticSearchAvailable() {
  177. return esConnector.getClusterHealthStatus()
  178. .filter(t -> ClusterHealthStatus.GREEN.equals(t) || ClusterHealthStatus.YELLOW.equals(t))
  179. .isPresent();
  180. }
  181. private class OperationalProcessListener implements EntryListener<ClusterProcess, Boolean> {
  182. @Override
  183. public void entryAdded(EntryEvent<ClusterProcess, Boolean> event) {
  184. if (event.getValue()) {
  185. listeners.forEach(appStateListener -> appStateListener.onAppStateOperational(event.getKey().getProcessId()));
  186. }
  187. }
  188. @Override
  189. public void entryRemoved(EntryEvent<ClusterProcess, Boolean> event) {
  190. // Ignore it
  191. }
  192. @Override
  193. public void entryUpdated(EntryEvent<ClusterProcess, Boolean> event) {
  194. if (event.getValue()) {
  195. listeners.forEach(appStateListener -> appStateListener.onAppStateOperational(event.getKey().getProcessId()));
  196. }
  197. }
  198. @Override
  199. public void entryEvicted(EntryEvent<ClusterProcess, Boolean> event) {
  200. // Ignore it
  201. }
  202. @Override
  203. public void mapCleared(MapEvent event) {
  204. // Ignore it
  205. }
  206. @Override
  207. public void mapEvicted(MapEvent event) {
  208. // Ignore it
  209. }
  210. @Override
  211. public void entryExpired(EntryEvent<ClusterProcess, Boolean> event) {
  212. // Ignore it
  213. }
  214. }
  215. private class NodeDisconnectedListener implements MembershipListener {
  216. @Override
  217. public void memberAdded(MembershipEvent membershipEvent) {
  218. // Nothing to do
  219. }
  220. @Override
  221. public void memberRemoved(MembershipEvent membershipEvent) {
  222. removeOperationalProcess(membershipEvent.getMember().getUuid());
  223. }
  224. private void removeOperationalProcess(UUID uuid) {
  225. for (ClusterProcess clusterProcess : operationalProcesses.keySet()) {
  226. if (clusterProcess.getNodeUuid().equals(uuid)) {
  227. LOGGER.debug("Set node process off for [{}:{}] : ", clusterProcess.getNodeUuid(), clusterProcess.getProcessId());
  228. hzMember.getReplicatedMap(OPERATIONAL_PROCESSES).put(clusterProcess, Boolean.FALSE);
  229. }
  230. }
  231. }
  232. }
  233. }