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.

SharedHealthStateImplTest.java 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  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.process.cluster.health;
  21. import com.google.common.collect.ImmutableSet;
  22. import java.util.HashMap;
  23. import java.util.Map;
  24. import java.util.Random;
  25. import java.util.UUID;
  26. import java.util.stream.IntStream;
  27. import org.junit.Rule;
  28. import org.junit.Test;
  29. import org.junit.rules.ExpectedException;
  30. import org.slf4j.event.Level;
  31. import org.sonar.process.LoggingRule;
  32. import org.sonar.process.cluster.hz.HazelcastMember;
  33. import static java.util.Collections.singleton;
  34. import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
  35. import static org.assertj.core.api.Assertions.assertThat;
  36. import static org.mockito.Mockito.doReturn;
  37. import static org.mockito.Mockito.mock;
  38. import static org.mockito.Mockito.verify;
  39. import static org.mockito.Mockito.verifyNoMoreInteractions;
  40. import static org.mockito.Mockito.when;
  41. import static org.sonar.process.cluster.health.NodeDetails.newNodeDetailsBuilder;
  42. import static org.sonar.process.cluster.health.NodeHealth.newNodeHealthBuilder;
  43. public class SharedHealthStateImplTest {
  44. private static final String MAP_SQ_HEALTH_STATE = "sq_health_state";
  45. @Rule
  46. public ExpectedException expectedException = ExpectedException.none();
  47. @Rule
  48. public LoggingRule logging = new LoggingRule(SharedHealthStateImpl.class);
  49. private final Random random = new Random();
  50. private long clusterTime = 99 + Math.abs(random.nextInt(9621));
  51. private HazelcastMember hazelcastMember = mock(HazelcastMember.class);
  52. private SharedHealthStateImpl underTest = new SharedHealthStateImpl(hazelcastMember);
  53. @Test
  54. public void write_fails_with_NPE_if_arg_is_null() {
  55. expectedException.expect(NullPointerException.class);
  56. expectedException.expectMessage("nodeHealth can't be null");
  57. underTest.writeMine(null);
  58. }
  59. @Test
  60. public void write_put_arg_into_map_sq_health_state_under_current_client_uuid() {
  61. NodeHealth nodeHealth = randomNodeHealth();
  62. Map<UUID, TimestampedNodeHealth> map = new HashMap<>();
  63. doReturn(map).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
  64. long clusterTime = random.nextLong();
  65. UUID uuid = UUID.randomUUID();
  66. when(hazelcastMember.getUuid()).thenReturn(uuid);
  67. when(hazelcastMember.getClusterTime()).thenReturn(clusterTime);
  68. underTest.writeMine(nodeHealth);
  69. assertThat(map).hasSize(1);
  70. assertThat(map.get(uuid)).isEqualTo(new TimestampedNodeHealth(nodeHealth, clusterTime));
  71. assertThat(logging.getLogs()).isEmpty();
  72. }
  73. @Test
  74. public void write_logs_map_sq_health_state_content_and_NodeHealth_to_be_added_if_TRACE() {
  75. logging.setLevel(Level.TRACE);
  76. NodeHealth newNodeHealth = randomNodeHealth();
  77. Map<String, TimestampedNodeHealth> map = new HashMap<>();
  78. map.put(randomAlphanumeric(4), new TimestampedNodeHealth(randomNodeHealth(), random.nextLong()));
  79. doReturn(new HashMap<>(map)).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
  80. UUID uuid = UUID.randomUUID();
  81. when(hazelcastMember.getUuid()).thenReturn(uuid);
  82. underTest.writeMine(newNodeHealth);
  83. assertThat(logging.getLogs()).hasSize(1);
  84. assertThat(logging.hasLog(Level.TRACE, "Reading " + map + " and adding " + newNodeHealth)).isTrue();
  85. }
  86. @Test
  87. public void readAll_returns_all_NodeHealth_in_map_sq_health_state_for_existing_client_uuids_aged_less_than_30_seconds() {
  88. NodeHealth[] nodeHealths = IntStream.range(0, 1 + random.nextInt(6)).mapToObj(i -> randomNodeHealth()).toArray(NodeHealth[]::new);
  89. Map<UUID, TimestampedNodeHealth> allNodeHealths = new HashMap<>();
  90. Map<UUID, NodeHealth> expected = new HashMap<>();
  91. for (NodeHealth nodeHealth : nodeHealths) {
  92. UUID memberUuid = UUID.randomUUID();
  93. TimestampedNodeHealth timestampedNodeHealth = new TimestampedNodeHealth(nodeHealth, clusterTime - random.nextInt(30 * 1000));
  94. allNodeHealths.put(memberUuid, timestampedNodeHealth);
  95. if (random.nextBoolean()) {
  96. expected.put(memberUuid, nodeHealth);
  97. }
  98. }
  99. doReturn(allNodeHealths).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
  100. when(hazelcastMember.getMemberUuids()).thenReturn(expected.keySet());
  101. when(hazelcastMember.getClusterTime()).thenReturn(clusterTime);
  102. assertThat(underTest.readAll())
  103. .containsOnly(expected.values().toArray(new NodeHealth[0]));
  104. assertThat(logging.getLogs()).isEmpty();
  105. }
  106. @Test
  107. public void readAll_ignores_NodeHealth_of_30_seconds_before_cluster_time() {
  108. NodeHealth nodeHealth = randomNodeHealth();
  109. Map<UUID, TimestampedNodeHealth> map = new HashMap<>();
  110. UUID memberUuid = UUID.randomUUID();
  111. TimestampedNodeHealth timestampedNodeHealth = new TimestampedNodeHealth(nodeHealth, clusterTime - 30 * 1000);
  112. map.put(memberUuid, timestampedNodeHealth);
  113. doReturn(map).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
  114. when(hazelcastMember.getMemberUuids()).thenReturn(map.keySet());
  115. when(hazelcastMember.getClusterTime()).thenReturn(clusterTime);
  116. assertThat(underTest.readAll()).isEmpty();
  117. }
  118. @Test
  119. public void readAll_ignores_NodeHealth_of_more_than_30_seconds_before_cluster_time() {
  120. NodeHealth nodeHealth = randomNodeHealth();
  121. Map<UUID, TimestampedNodeHealth> map = new HashMap<>();
  122. UUID memberUuid = UUID.randomUUID();
  123. TimestampedNodeHealth timestampedNodeHealth = new TimestampedNodeHealth(nodeHealth, clusterTime - 30 * 1000 - random.nextInt(99));
  124. map.put(memberUuid, timestampedNodeHealth);
  125. doReturn(map).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
  126. when(hazelcastMember.getMemberUuids()).thenReturn(map.keySet());
  127. when(hazelcastMember.getClusterTime()).thenReturn(clusterTime);
  128. assertThat(underTest.readAll()).isEmpty();
  129. }
  130. @Test
  131. public void readAll_logs_map_sq_health_state_content_and_the_content_effectively_returned_if_TRACE() {
  132. logging.setLevel(Level.TRACE);
  133. Map<UUID, TimestampedNodeHealth> map = new HashMap<>();
  134. UUID uuid = UUID.randomUUID();
  135. NodeHealth nodeHealth = randomNodeHealth();
  136. map.put(uuid, new TimestampedNodeHealth(nodeHealth, clusterTime - 1));
  137. when(hazelcastMember.getClusterTime()).thenReturn(clusterTime);
  138. when(hazelcastMember.getMemberUuids()).thenReturn(singleton(uuid));
  139. doReturn(map).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
  140. underTest.readAll();
  141. assertThat(logging.getLogs()).hasSize(1);
  142. assertThat(logging.hasLog(Level.TRACE, "Reading " + new HashMap<>(map) + " and keeping " + singleton(nodeHealth))).isTrue();
  143. }
  144. @Test
  145. public void readAll_logs_message_for_each_non_existing_member_ignored_if_TRACE() {
  146. logging.setLevel(Level.TRACE);
  147. Map<String, TimestampedNodeHealth> map = new HashMap<>();
  148. String memberUuid1 = randomAlphanumeric(44);
  149. String memberUuid2 = randomAlphanumeric(44);
  150. map.put(memberUuid1, new TimestampedNodeHealth(randomNodeHealth(), clusterTime - 1));
  151. map.put(memberUuid2, new TimestampedNodeHealth(randomNodeHealth(), clusterTime - 1));
  152. when(hazelcastMember.getClusterTime()).thenReturn(clusterTime);
  153. doReturn(map).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
  154. underTest.readAll();
  155. assertThat(logging.getLogs()).hasSize(3);
  156. assertThat(logging.getLogs(Level.TRACE))
  157. .containsOnly(
  158. "Reading " + new HashMap<>(map) + " and keeping []",
  159. "Ignoring NodeHealth of member " + memberUuid1 + " because it is not part of the cluster at the moment",
  160. "Ignoring NodeHealth of member " + memberUuid2 + " because it is not part of the cluster at the moment");
  161. }
  162. @Test
  163. public void readAll_logs_message_for_each_timed_out_NodeHealth_ignored_if_TRACE() {
  164. logging.setLevel(Level.TRACE);
  165. Map<UUID, TimestampedNodeHealth> map = new HashMap<>();
  166. UUID memberUuid1 = UUID.randomUUID();
  167. UUID memberUuid2 = UUID.randomUUID();
  168. map.put(memberUuid1, new TimestampedNodeHealth(randomNodeHealth(), clusterTime - 30 * 1000));
  169. map.put(memberUuid2, new TimestampedNodeHealth(randomNodeHealth(), clusterTime - 30 * 1000));
  170. doReturn(map).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
  171. when(hazelcastMember.getMemberUuids()).thenReturn(ImmutableSet.of(memberUuid1, memberUuid2));
  172. when(hazelcastMember.getClusterTime()).thenReturn(clusterTime);
  173. underTest.readAll();
  174. assertThat(logging.getLogs()).hasSize(3);
  175. assertThat(logging.getLogs(Level.TRACE))
  176. .containsOnly(
  177. "Reading " + new HashMap<>(map) + " and keeping []",
  178. "Ignoring NodeHealth of member " + memberUuid1 + " because it is too old",
  179. "Ignoring NodeHealth of member " + memberUuid2 + " because it is too old");
  180. }
  181. @Test
  182. public void clearMine_clears_entry_into_map_sq_health_state_under_current_client_uuid() {
  183. Map<UUID, TimestampedNodeHealth> map = mock(Map.class);
  184. doReturn(map).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
  185. UUID uuid = UUID.randomUUID();
  186. when(hazelcastMember.getUuid()).thenReturn(uuid);
  187. underTest.clearMine();
  188. verify(map).remove(uuid);
  189. verifyNoMoreInteractions(map);
  190. assertThat(logging.getLogs()).isEmpty();
  191. }
  192. @Test
  193. public void clearMine_logs_map_sq_health_state_and_current_client_uuid_if_TRACE() {
  194. logging.setLevel(Level.TRACE);
  195. Map<UUID, TimestampedNodeHealth> map = new HashMap<>();
  196. map.put(UUID.randomUUID(), new TimestampedNodeHealth(randomNodeHealth(), random.nextLong()));
  197. doReturn(map).when(hazelcastMember).getReplicatedMap(MAP_SQ_HEALTH_STATE);
  198. UUID uuid = UUID.randomUUID();
  199. when(hazelcastMember.getUuid()).thenReturn(uuid);
  200. underTest.clearMine();
  201. assertThat(logging.getLogs()).hasSize(1);
  202. assertThat(logging.hasLog(Level.TRACE, "Reading " + map + " and clearing for " + uuid)).isTrue();
  203. }
  204. private NodeHealth randomNodeHealth() {
  205. return newNodeHealthBuilder()
  206. .setStatus(NodeHealth.Status.values()[random.nextInt(NodeHealth.Status.values().length)])
  207. .setDetails(newNodeDetailsBuilder()
  208. .setType(random.nextBoolean() ? NodeDetails.Type.SEARCH : NodeDetails.Type.APPLICATION)
  209. .setName(randomAlphanumeric(30))
  210. .setHost(randomAlphanumeric(10))
  211. .setPort(1 + random.nextInt(666))
  212. .setStartedAt(1 + random.nextInt(852))
  213. .build())
  214. .build();
  215. }
  216. }