3 * Copyright (C) 2009-2022 SonarSource SA
4 * mailto:info AT sonarsource DOT com
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.
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.
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.
20 package org.sonar.application.cluster;
22 import com.google.common.collect.ImmutableMap;
23 import com.hazelcast.cluster.Address;
24 import com.hazelcast.cluster.Cluster;
25 import com.hazelcast.cluster.Member;
26 import com.hazelcast.cluster.MemberSelector;
27 import com.hazelcast.cp.IAtomicReference;
28 import java.net.InetAddress;
29 import java.net.UnknownHostException;
30 import java.util.Arrays;
31 import java.util.Collections;
32 import java.util.LinkedHashMap;
33 import java.util.List;
36 import java.util.UUID;
37 import java.util.concurrent.locks.Lock;
38 import java.util.function.Consumer;
39 import org.junit.After;
40 import org.junit.Before;
41 import org.junit.Test;
42 import org.sonar.application.config.TestAppSettings;
43 import org.sonar.process.cluster.hz.DistributedAnswer;
44 import org.sonar.process.cluster.hz.DistributedCall;
45 import org.sonar.process.cluster.hz.DistributedCallback;
46 import org.sonar.process.cluster.hz.HazelcastMember;
48 import static org.assertj.core.api.Assertions.assertThatThrownBy;
49 import static org.mockito.Mockito.mock;
50 import static org.mockito.Mockito.verify;
51 import static org.mockito.Mockito.verifyNoMoreInteractions;
52 import static org.mockito.Mockito.when;
53 import static org.sonar.process.ProcessProperties.Property.CLUSTER_HZ_HOSTS;
55 public class AppNodesClusterHostsConsistencyTest {
57 @SuppressWarnings("unchecked")
58 private final Consumer<String> logger = mock(Consumer.class);
63 AppNodesClusterHostsConsistency.clearInstance();
67 public void log_warning_if_configured_hosts_are_not_consistent() throws UnknownHostException {
68 Map<Member, List<String>> hostsPerMember = new LinkedHashMap<>();
69 Member m1 = newLocalHostMember(1, true);
70 Member m2 = newLocalHostMember(2);
71 Member m3 = newLocalHostMember(3);
73 hostsPerMember.put(m2, Arrays.asList("1.1.1.1:1000", "1.1.1.1:2000"));
74 hostsPerMember.put(m3, Arrays.asList("1.1.1.1:1000", "1.1.1.2:1000"));
76 TestAppSettings settings = new TestAppSettings(ImmutableMap.of(CLUSTER_HZ_HOSTS.getKey(), "1.1.1.1:1000,1.1.1.1:2000,1.1.1.2:1000"));
78 TestHazelcastMember member = new TestHazelcastMember(hostsPerMember, m1);
79 AppNodesClusterHostsConsistency underTest = AppNodesClusterHostsConsistency.setInstance(member, settings, logger);
82 verify(logger).accept("The configuration of the current node doesn't match the list of hosts configured in the application nodes that have already joined the cluster:\n" +
83 m1.getAddress().getHost() + ":" + m1.getAddress().getPort() + " : [1.1.1.1:1000, 1.1.1.1:2000, 1.1.1.2:1000] (current)\n" +
84 m2.getAddress().getHost() + ":" + m2.getAddress().getPort() + " : [1.1.1.1:1000, 1.1.1.1:2000]\n" +
85 m3.getAddress().getHost() + ":" + m3.getAddress().getPort() + " : [1.1.1.1:1000, 1.1.1.2:1000]\n" +
86 "Make sure the configuration is consistent among all application nodes before you restart any node");
87 verifyNoMoreInteractions(logger);
91 public void dont_log_if_configured_hosts_are_consistent() throws UnknownHostException {
92 Map<Member, List<String>> hostsPerMember = new LinkedHashMap<>();
93 Member m1 = newLocalHostMember(1, true);
94 Member m2 = newLocalHostMember(2);
95 Member m3 = newLocalHostMember(3);
97 hostsPerMember.put(m2, Arrays.asList("1.1.1.1:1000", "1.1.1.1:2000", "1.1.1.2:1000"));
98 hostsPerMember.put(m3, Arrays.asList("1.1.1.1:1000", "1.1.1.1:2000", "1.1.1.2:1000"));
100 TestAppSettings settings = new TestAppSettings(ImmutableMap.of(CLUSTER_HZ_HOSTS.getKey(), "1.1.1.1:1000,1.1.1.1:2000,1.1.1.2:1000"));
102 TestHazelcastMember member = new TestHazelcastMember(hostsPerMember, m1);
103 AppNodesClusterHostsConsistency underTest = AppNodesClusterHostsConsistency.setInstance(member, settings, logger);
106 verifyNoMoreInteractions(logger);
110 public void setInstance_fails_with_ISE_when_called_twice_with_same_arguments() throws UnknownHostException {
111 TestHazelcastMember member = new TestHazelcastMember(Collections.emptyMap(), newLocalHostMember(1, true));
112 TestAppSettings settings = new TestAppSettings();
113 AppNodesClusterHostsConsistency.setInstance(member, settings);
115 assertThatThrownBy(() -> AppNodesClusterHostsConsistency.setInstance(member, settings))
116 .isInstanceOf(IllegalStateException.class)
117 .hasMessage("Instance is already set");
121 public void setInstance_fails_with_ISE_when_called_twice_with_other_arguments() throws UnknownHostException {
122 TestHazelcastMember member1 = new TestHazelcastMember(Collections.emptyMap(), newLocalHostMember(1, true));
123 TestHazelcastMember member2 = new TestHazelcastMember(Collections.emptyMap(), newLocalHostMember(2, true));
124 AppNodesClusterHostsConsistency.setInstance(member1, new TestAppSettings());
126 assertThatThrownBy(() -> AppNodesClusterHostsConsistency.setInstance(member2, new TestAppSettings()))
127 .isInstanceOf(IllegalStateException.class)
128 .hasMessage("Instance is already set");
131 private Member newLocalHostMember(int port) throws UnknownHostException {
132 return newLocalHostMember(port, false);
135 private Member newLocalHostMember(int port, boolean localMember) throws UnknownHostException {
136 Member member = mock(Member.class);
137 when(member.localMember()).thenReturn(localMember);
138 Address address1 = new Address(InetAddress.getLocalHost(), port);
139 when(member.getAddress()).thenReturn(address1);
143 private static class TestHazelcastMember implements HazelcastMember {
144 private final Map<Member, List<String>> hostsPerMember;
145 private final Cluster cluster = mock(Cluster.class);
147 private TestHazelcastMember(Map<Member, List<String>> hostsPerMember, Member localMember) {
148 this.hostsPerMember = hostsPerMember;
149 when(cluster.getLocalMember()).thenReturn(localMember);
153 public <E> IAtomicReference<E> getAtomicReference(String name) {
154 throw new IllegalStateException("not expected to be called");
158 public <K, V> Map<K, V> getReplicatedMap(String name) {
159 throw new IllegalStateException("not expected to be called");
163 public UUID getUuid() {
164 throw new IllegalStateException("not expected to be called");
168 public Set<UUID> getMemberUuids() {
169 throw new IllegalStateException("not expected to be called");
173 public Lock getLock(String name) {
174 throw new IllegalStateException("not expected to be called");
178 public long getClusterTime() {
179 throw new IllegalStateException("not expected to be called");
183 public Cluster getCluster() {
188 public <T> DistributedAnswer<T> call(DistributedCall<T> callable, MemberSelector memberSelector, long timeoutMs) {
189 throw new IllegalStateException("not expected to be called");
193 public <T> void callAsync(DistributedCall<T> callable, MemberSelector memberSelector, DistributedCallback<T> callback) {
194 callback.onComplete((Map<Member, T>) hostsPerMember);
198 public void close() {