Skip to content

Commit 2b806d3

Browse files
Unit tests for Heart
ref: #249
1 parent d6a8eb4 commit 2b806d3

File tree

6 files changed

+341
-5
lines changed

6 files changed

+341
-5
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,4 @@ src/packaging/data
3232

3333
# SSL stores directory
3434
src/packaging/ssl-stores
35+
/src/server/nbproject/

src/server/pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -275,6 +275,7 @@
275275
<argLine>${failsafeArgLine}</argLine>
276276
<failIfNoTests>false</failIfNoTests>
277277
<includes>
278+
<include>**/AddClusterIT.java</include>
278279
<include>**/ReaperIT.java</include>
279280
</includes>
280281
</configuration>

src/server/src/main/java/io/cassandrareaper/service/Heart.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ private void updateRequestedNodeMetrics() {
112112
.filter(nodeMetrics -> nodeMetrics.isRequested())
113113
.forEach(req -> {
114114

115-
116115
LOG.info("Got metric request for node {} in {}", req.getNode(), req.getCluster());
117116
try (Timer.Context t1 = timer(context, req.getCluster(), req.getNode())) {
118117

@@ -122,16 +121,14 @@ private void updateRequestedNodeMetrics() {
122121
storage.storeNodeMetrics(
123122
runId,
124123
NodeMetrics.builder()
125-
126124
.withNode(req.getNode())
127125
.withCluster(req.getCluster())
128-
.withDatacenter(nodeProxy.getDataCenter())
126+
.withDatacenter(req.getDatacenter())
129127
.withPendingCompactions(nodeProxy.getPendingCompactions())
130128
.withHasRepairRunning(nodeProxy.isRepairRunning())
131129
.withActiveAnticompactions(0) // for future use
132130
.build());
133131

134-
135132
LOG.info("Responded to metric request for node {}", req.getNode());
136133
} catch (ReaperException | RuntimeException | InterruptedException ex) {
137134
LOG.debug("failed seed connection in cluster " + req.getCluster(), ex);

src/server/src/test/java/io/cassandrareaper/acceptance/AddClusterTest.java renamed to src/server/src/test/java/io/cassandrareaper/acceptance/AddClusterIT.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,6 @@
2222
@CucumberOptions(
2323
features = "classpath:io.cassandrareaper.acceptance/basic_reaper_functionality.feature"
2424
)
25-
public final class AddClusterTest {
25+
public final class AddClusterIT {
2626
// Required only to get the Cucumber acceptance tests actually run.
2727
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,324 @@
1+
/*
2+
* Licensed under the Apache License, Version 2.0 (the "License");
3+
* you may not use this file except in compliance with the License.
4+
* You may obtain a copy of the License at
5+
*
6+
* http://www.apache.org/licenses/LICENSE-2.0
7+
*
8+
* Unless required by applicable law or agreed to in writing, software
9+
* distributed under the License is distributed on an "AS IS" BASIS,
10+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
* See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
15+
package io.cassandrareaper.service;
16+
17+
import io.cassandrareaper.AppContext;
18+
import io.cassandrareaper.ReaperApplicationConfiguration;
19+
import io.cassandrareaper.ReaperException;
20+
import io.cassandrareaper.core.NodeMetrics;
21+
import io.cassandrareaper.jmx.HostConnectionCounters;
22+
import io.cassandrareaper.jmx.JmxConnectionFactory;
23+
import io.cassandrareaper.jmx.JmxProxy;
24+
import io.cassandrareaper.storage.CassandraStorage;
25+
import io.cassandrareaper.storage.MemoryStorage;
26+
27+
import java.util.Collections;
28+
import java.util.UUID;
29+
import java.util.concurrent.TimeUnit;
30+
import javax.management.JMException;
31+
32+
import org.assertj.core.api.Assertions;
33+
import org.awaitility.Awaitility;
34+
import org.junit.Test;
35+
import org.mockito.Mockito;
36+
37+
import static org.mockito.ArgumentMatchers.any;
38+
import static org.mockito.ArgumentMatchers.anyInt;
39+
import static org.mockito.ArgumentMatchers.eq;
40+
41+
public final class HeartTest {
42+
43+
@Test
44+
public void testBeat_nullStorage() {
45+
AppContext context = new AppContext();
46+
try (Heart heart = Heart.create(context)) {
47+
heart.beat();
48+
Assertions.assertThat(heart.isCurrentlyUpdatingNodeMetrics().get()).isFalse();
49+
}
50+
}
51+
52+
@Test
53+
public void testBeat_memoryStorage() {
54+
AppContext context = new AppContext();
55+
context.storage = new MemoryStorage();
56+
try (Heart heart = Heart.create(context)) {
57+
heart.beat();
58+
Assertions.assertThat(heart.isCurrentlyUpdatingNodeMetrics().get()).isFalse();
59+
}
60+
}
61+
62+
@Test
63+
public void testBeat_distributedStorage_noDatacenterAvailability() throws InterruptedException {
64+
65+
AppContext context = new AppContext();
66+
context.config = new ReaperApplicationConfiguration();
67+
context.storage = Mockito.mock(CassandraStorage.class);
68+
69+
try (Heart heart = Heart.create(context)) {
70+
heart.beat();
71+
Awaitility.await().until(() -> {
72+
try {
73+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(1)).saveHeartbeat();
74+
return true;
75+
} catch (AssertionError ex) {
76+
return false;
77+
}
78+
});
79+
Assertions.assertThat(heart.isCurrentlyUpdatingNodeMetrics().get()).isFalse();
80+
Thread.sleep(500);
81+
}
82+
83+
Mockito.verify(context.storage, Mockito.times(0)).getClusters();
84+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(0)).storeNodeMetrics(any(), any());
85+
}
86+
87+
@Test
88+
public void testBeat_distributedStorage_allDatacenterAvailability() throws InterruptedException {
89+
90+
AppContext context = new AppContext();
91+
context.config = new ReaperApplicationConfiguration();
92+
context.config.setDatacenterAvailability(ReaperApplicationConfiguration.DatacenterAvailability.ALL);
93+
context.storage = Mockito.mock(CassandraStorage.class);
94+
95+
try (Heart heart = Heart.create(context)) {
96+
heart.beat();
97+
Awaitility.await().until(() -> {
98+
try {
99+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(1)).saveHeartbeat();
100+
return true;
101+
} catch (AssertionError ex) {
102+
return false;
103+
}
104+
});
105+
Assertions.assertThat(heart.isCurrentlyUpdatingNodeMetrics().get()).isFalse();
106+
Thread.sleep(500);
107+
}
108+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(1)).saveHeartbeat();
109+
Mockito.verify(context.storage, Mockito.times(0)).getClusters();
110+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(0)).storeNodeMetrics(any(), any());
111+
}
112+
113+
@Test
114+
public void testBeat_distributedStorage_eachDatacenterAvailability() throws InterruptedException {
115+
116+
AppContext context = new AppContext();
117+
context.config = new ReaperApplicationConfiguration();
118+
context.config.setDatacenterAvailability(ReaperApplicationConfiguration.DatacenterAvailability.EACH);
119+
context.storage = Mockito.mock(CassandraStorage.class);
120+
context.jmxConnectionFactory = new JmxConnectionFactory(context.metricRegistry);
121+
122+
try (Heart heart = Heart.create(context)) {
123+
heart.beat();
124+
Thread.sleep(500);
125+
}
126+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(1)).saveHeartbeat();
127+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(0)).storeNodeMetrics(any(), any());
128+
}
129+
130+
@Test
131+
public void testBeat_distributedStorage_eachDatacenterAvailability_repairs() throws InterruptedException {
132+
133+
AppContext context = new AppContext();
134+
context.config = new ReaperApplicationConfiguration();
135+
context.config.setDatacenterAvailability(ReaperApplicationConfiguration.DatacenterAvailability.EACH);
136+
137+
context.repairManager = RepairManager.create(context);
138+
context.repairManager.repairRunners.put(UUID.randomUUID(), Mockito.mock(RepairRunner.class));
139+
context.repairManager.repairRunners.put(UUID.randomUUID(), Mockito.mock(RepairRunner.class));
140+
141+
context.storage = Mockito.mock(CassandraStorage.class);
142+
context.jmxConnectionFactory = new JmxConnectionFactory(context.metricRegistry);
143+
144+
try (Heart heart = Heart.create(context)) {
145+
heart.beat();
146+
Thread.sleep(500);
147+
}
148+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(1)).saveHeartbeat();
149+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(0)).storeNodeMetrics(any(), any());
150+
}
151+
152+
@Test
153+
public void testBeat_distributedStorage_eachDatacenterAvailability_repairs_noMetrics()
154+
throws InterruptedException, ReaperException {
155+
156+
AppContext context = new AppContext();
157+
context.config = new ReaperApplicationConfiguration();
158+
context.config.setDatacenterAvailability(ReaperApplicationConfiguration.DatacenterAvailability.EACH);
159+
160+
context.repairManager = RepairManager.create(context);
161+
context.repairManager.repairRunners.put(UUID.randomUUID(), Mockito.mock(RepairRunner.class));
162+
context.repairManager.repairRunners.put(UUID.randomUUID(), Mockito.mock(RepairRunner.class));
163+
164+
context.storage = Mockito.mock(CassandraStorage.class);
165+
context.jmxConnectionFactory = Mockito.mock(JmxConnectionFactory.class);
166+
167+
Mockito
168+
.when(((CassandraStorage)context.storage).getNodeMetrics(any()))
169+
.thenReturn(Collections.emptyList());
170+
171+
try (Heart heart = Heart.create(context)) {
172+
heart.beat();
173+
Thread.sleep(500);
174+
}
175+
176+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(1)).saveHeartbeat();
177+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(2)).getNodeMetrics(any());
178+
Mockito.verify(context.jmxConnectionFactory, Mockito.times(0)).connect(any(), anyInt());
179+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(0)).storeNodeMetrics(any(), any());
180+
}
181+
182+
@Test
183+
public void testBeat_distributedStorage_eachDatacenterAvailability_repairs_noRequests()
184+
throws InterruptedException, ReaperException {
185+
186+
AppContext context = new AppContext();
187+
context.config = new ReaperApplicationConfiguration();
188+
context.config.setDatacenterAvailability(ReaperApplicationConfiguration.DatacenterAvailability.EACH);
189+
context.storage = Mockito.mock(CassandraStorage.class);
190+
191+
context.repairManager = RepairManager.create(context);
192+
context.repairManager.repairRunners.put(UUID.randomUUID(), Mockito.mock(RepairRunner.class));
193+
context.repairManager.repairRunners.put(UUID.randomUUID(), Mockito.mock(RepairRunner.class));
194+
195+
context.storage = Mockito.mock(CassandraStorage.class);
196+
context.jmxConnectionFactory = Mockito.mock(JmxConnectionFactory.class);
197+
198+
Mockito
199+
.when(((CassandraStorage)context.storage).getNodeMetrics(any()))
200+
.thenReturn(
201+
Collections.singleton(
202+
NodeMetrics.builder()
203+
.withNode("test")
204+
.withDatacenter("dc1")
205+
.withCluster("cluster1")
206+
.build()));
207+
208+
JmxProxy nodeProxy = Mockito.mock(JmxProxy.class);
209+
210+
Mockito
211+
.when(context.jmxConnectionFactory.connect(any(), eq(context.config.getJmxConnectionTimeoutInSeconds())))
212+
.thenReturn(nodeProxy);
213+
214+
HostConnectionCounters hostConnectionCounters = Mockito.mock(HostConnectionCounters.class);
215+
Mockito.when(context.jmxConnectionFactory.getHostConnectionCounters()).thenReturn(hostConnectionCounters);
216+
217+
Mockito
218+
.when(context.jmxConnectionFactory.connect(any(), eq(context.config.getJmxConnectionTimeoutInSeconds())))
219+
.thenThrow(InterruptedException.class);
220+
221+
try (Heart heart = Heart.create(context)) {
222+
heart.beat();
223+
Thread.sleep(500);
224+
}
225+
226+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(1)).saveHeartbeat();
227+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(2)).getNodeMetrics(any());
228+
Mockito.verify(context.jmxConnectionFactory, Mockito.times(0)).connect(any(), anyInt());
229+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(0)).storeNodeMetrics(any(), any());
230+
}
231+
232+
@Test
233+
public void testBeat_distributedStorage_eachDatacenterAvailability_repairs_requests()
234+
throws InterruptedException, ReaperException {
235+
236+
AppContext context = new AppContext();
237+
context.config = new ReaperApplicationConfiguration();
238+
context.config.setDatacenterAvailability(ReaperApplicationConfiguration.DatacenterAvailability.EACH);
239+
context.storage = Mockito.mock(CassandraStorage.class);
240+
241+
context.repairManager = RepairManager.create(context);
242+
context.repairManager.repairRunners.put(UUID.randomUUID(), Mockito.mock(RepairRunner.class));
243+
context.repairManager.repairRunners.put(UUID.randomUUID(), Mockito.mock(RepairRunner.class));
244+
245+
context.storage = Mockito.mock(CassandraStorage.class);
246+
context.jmxConnectionFactory = Mockito.mock(JmxConnectionFactory.class);
247+
248+
Mockito
249+
.when(((CassandraStorage)context.storage).getNodeMetrics(any()))
250+
.thenReturn(
251+
Collections.singleton(
252+
NodeMetrics.builder()
253+
.withNode("test")
254+
.withDatacenter("dc1")
255+
.withCluster("cluster1")
256+
.withRequested(true)
257+
.build()));
258+
259+
JmxProxy nodeProxy = Mockito.mock(JmxProxy.class);
260+
261+
Mockito.when(context.jmxConnectionFactory.connect(any(), anyInt())).thenReturn(nodeProxy);
262+
263+
try (Heart heart = Heart.create(context)) {
264+
heart.beat();
265+
Thread.sleep(500);
266+
}
267+
268+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(1)).saveHeartbeat();
269+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(2)).getNodeMetrics(any());
270+
Mockito.verify(context.jmxConnectionFactory, Mockito.times(2)).connect(any(), anyInt());
271+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(2)).storeNodeMetrics(any(), any());
272+
}
273+
274+
@Test
275+
public void testBeat_distributedStorage_eachDatacenterAvailability_repairs_requests_queued()
276+
throws InterruptedException, ReaperException, JMException {
277+
278+
AppContext context = new AppContext();
279+
context.config = new ReaperApplicationConfiguration();
280+
context.config.setDatacenterAvailability(ReaperApplicationConfiguration.DatacenterAvailability.EACH);
281+
context.storage = Mockito.mock(CassandraStorage.class);
282+
283+
context.repairManager = RepairManager.create(context);
284+
context.repairManager.repairRunners.put(UUID.randomUUID(), Mockito.mock(RepairRunner.class));
285+
context.repairManager.repairRunners.put(UUID.randomUUID(), Mockito.mock(RepairRunner.class));
286+
287+
context.storage = Mockito.mock(CassandraStorage.class);
288+
context.jmxConnectionFactory = Mockito.mock(JmxConnectionFactory.class);
289+
290+
Mockito
291+
.when(((CassandraStorage)context.storage).getNodeMetrics(any()))
292+
.thenReturn(
293+
Collections.singleton(
294+
NodeMetrics.builder()
295+
.withNode("test")
296+
.withDatacenter("dc1")
297+
.withCluster("cluster1")
298+
.withRequested(true)
299+
.build()));
300+
301+
JmxProxy nodeProxy = Mockito.mock(JmxProxy.class);
302+
Mockito.when(context.jmxConnectionFactory.connect(any(), anyInt())).thenReturn(nodeProxy);
303+
304+
Mockito.when(nodeProxy.getPendingCompactions())
305+
.then(a -> {
306+
// delay the call so force the forkJoinPool queue
307+
Thread.sleep(2501);
308+
return 10;
309+
});
310+
311+
try (Heart heart = Heart.create(context, TimeUnit.SECONDS.toMillis(2))) {
312+
heart.beat();
313+
Assertions.assertThat(heart.isCurrentlyUpdatingNodeMetrics().get()).isTrue();
314+
Thread.sleep(2100);
315+
heart.beat();
316+
Thread.sleep(500);
317+
}
318+
319+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(2)).saveHeartbeat();
320+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(2)).getNodeMetrics(any());
321+
Mockito.verify(context.jmxConnectionFactory, Mockito.times(2)).connect(any(), anyInt());
322+
Mockito.verify((CassandraStorage)context.storage, Mockito.times(2)).storeNodeMetrics(any(), any());
323+
}
324+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<configuration>
3+
<appender name="file" class="ch.qos.logback.core.FileAppender">
4+
<file>tests.log</file>
5+
<encoder>
6+
<pattern>%d %-5p [%c{0}] %m%n</pattern>
7+
</encoder>
8+
</appender>
9+
<root>
10+
<level value="DEBUG" />
11+
<appender-ref ref="file"/>
12+
</root>
13+
</configuration>

0 commit comments

Comments
 (0)