Skip to content

Commit 921cd01

Browse files
committed
Add cluster tests against restarted stream
A stream restarts when a node it has a member goes down, which happens on every cluster upgrade.
1 parent e4bc478 commit 921cd01

File tree

3 files changed

+147
-2
lines changed

3 files changed

+147
-2
lines changed

src/test/java/com/rabbitmq/client/amqp/impl/Assertions.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,17 @@ ConnectionAssert hasNodename(String nodename) {
230230
return this;
231231
}
232232

233+
ConnectionAssert isOnLeader(Management.QueueInfo info) {
234+
Assert.notNull(info, "Queue info cannot be null");
235+
String actualLeader = info.leader();
236+
if (!actualLeader.equals(actual.connectionNodename())) {
237+
fail(
238+
"Connection is expected to be on leader node '%s' but is on '%s'",
239+
actualLeader, actual.connectionNodename());
240+
}
241+
return this;
242+
}
243+
233244
ConnectionAssert isOnFollower(Management.QueueInfo info) {
234245
Assert.notNull(info, "Queue info cannot be null");
235246
List<String> followers =

src/test/java/com/rabbitmq/client/amqp/impl/Cli.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,15 @@ static String rabbitmqQueuesCommand() {
6262
return rabbitmqctl.substring(0, lastIndex) + "rabbitmq-queues";
6363
}
6464

65+
static String rabbitmqStreamsCommand() {
66+
String rabbitmqctl = rabbitmqctlCommand();
67+
int lastIndex = rabbitmqctl.lastIndexOf("rabbitmqctl");
68+
if (lastIndex == -1) {
69+
throw new IllegalArgumentException("Not a valid rabbitqmctl command: " + rabbitmqctl);
70+
}
71+
return rabbitmqctl.substring(0, lastIndex) + "rabbitmq-streams";
72+
}
73+
6574
static ProcessState rabbitmqctl(String command) {
6675
return executeCommand(rabbitmqctlCommand() + " " + command);
6776
}
@@ -70,6 +79,10 @@ static ProcessState rabbitmqQueues(String command) {
7079
return executeCommand(rabbitmqQueuesCommand() + " " + command);
7180
}
7281

82+
static ProcessState rabbitmqStreams(String command) {
83+
return executeCommand(rabbitmqStreamsCommand() + " " + command);
84+
}
85+
7386
static ProcessState rabbitmqctlIgnoreError(String command) {
7487
return executeCommand(rabbitmqctlCommand() + " " + command, true);
7588
}
@@ -198,6 +211,18 @@ static void deleteQuorumQueueMember(String queue, String node) {
198211
rabbitmqQueues(" delete_member " + queue + " " + node);
199212
}
200213

214+
static void addStreamMember(String stream, String node) {
215+
rabbitmqStreams(" add_replica " + stream + " " + node);
216+
}
217+
218+
static void deleteStreamMember(String stream, String node) {
219+
rabbitmqStreams(" delete_replica " + stream + " " + node);
220+
}
221+
222+
static void restartStream(String stream) {
223+
rabbitmqStreams(" restart_stream " + stream);
224+
}
225+
201226
static List<ConnectionInfo> listConnections() {
202227
String output = rabbitmqctl("list_connections -q pid peer_port client_properties").output();
203228
// output (header line presence depends on broker version):

src/test/java/com/rabbitmq/client/amqp/impl/ClusterTest.java

Lines changed: 111 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import static com.rabbitmq.client.amqp.ConnectionSettings.Affinity.Operation.PUBLISH;
2222
import static com.rabbitmq.client.amqp.impl.Assertions.assertThat;
2323
import static com.rabbitmq.client.amqp.impl.TestUtils.sync;
24+
import static com.rabbitmq.client.amqp.impl.TestUtils.waitAtMost;
2425
import static java.time.Duration.ofMillis;
2526
import static org.assertj.core.api.Assertions.assertThat;
2627

@@ -211,6 +212,90 @@ void consumeFromMovingQq() {
211212
}
212213
}
213214

215+
@Test
216+
void publishToRestartedStream() {
217+
try {
218+
management.queue(q).type(Management.QueueType.STREAM).declare();
219+
220+
AmqpConnection publishConnection = connection(b -> b.affinity().queue(q).operation(PUBLISH));
221+
assertThat(publishConnection).isOnLeader(queueInfo());
222+
223+
Publisher publisher = publishConnection.publisherBuilder().queue(q).build();
224+
Sync publishSync = sync();
225+
publisher.publish(publisher.message().messageId(1L), ctx -> publishSync.down());
226+
assertThat(publishSync).completes();
227+
228+
restartStream();
229+
230+
publishSync.reset();
231+
publisher.publish(publisher.message().messageId(2L), ctx -> publishSync.down());
232+
assertThat(publishSync).completes();
233+
234+
int messageCount = 2;
235+
waitAtMost(() -> queueInfo().messageCount() == messageCount);
236+
Sync consumeSync = sync(messageCount);
237+
Set<Long> messageIds = ConcurrentHashMap.newKeySet(messageCount);
238+
connection.consumerBuilder().queue(q).stream()
239+
.offset(ConsumerBuilder.StreamOffsetSpecification.FIRST)
240+
.builder()
241+
.messageHandler(
242+
(ctx, msg) -> {
243+
messageIds.add(msg.messageIdAsLong());
244+
consumeSync.down();
245+
ctx.accept();
246+
})
247+
.build();
248+
assertThat(consumeSync).completes();
249+
assertThat(messageIds).containsExactlyInAnyOrder(1L, 2L);
250+
} finally {
251+
management.queueDeletion().delete(q);
252+
}
253+
}
254+
255+
@Test
256+
void consumeFromRestartedStream() {
257+
try {
258+
management.queue(q).type(Management.QueueType.STREAM).declare();
259+
260+
AmqpConnection consumeConnection = connection(b -> b.affinity().queue(q).operation(CONSUME));
261+
assertThat(consumeConnection).isOnFollower(queueInfo());
262+
263+
Set<Long> messageIds = ConcurrentHashMap.newKeySet();
264+
Sync consumeSync = sync();
265+
consumeConnection.consumerBuilder().queue(q).stream()
266+
.offset(ConsumerBuilder.StreamOffsetSpecification.FIRST)
267+
.builder()
268+
.messageHandler(
269+
(ctx, msg) -> {
270+
messageIds.add(msg.messageIdAsLong());
271+
consumeSync.down();
272+
ctx.accept();
273+
})
274+
.build();
275+
276+
Publisher publisher = connection.publisherBuilder().queue(q).build();
277+
Sync publishSync = sync();
278+
publisher.publish(publisher.message().messageId(1L), ctx -> publishSync.down());
279+
assertThat(publishSync).completes();
280+
publishSync.reset();
281+
282+
assertThat(consumeSync).completes();
283+
assertThat(messageIds).containsExactlyInAnyOrder(1L);
284+
consumeSync.reset();
285+
286+
restartStream();
287+
288+
publisher.publish(publisher.message().messageId(2L), ctx -> publishSync.down());
289+
assertThat(publishSync).completes();
290+
publishSync.reset();
291+
292+
assertThat(consumeSync).completes();
293+
assertThat(messageIds).containsExactlyInAnyOrder(1L, 2L);
294+
} finally {
295+
management.queueDeletion().delete(q);
296+
}
297+
}
298+
214299
String moveQqLeader() {
215300
String initialLeader = deleteQqLeader();
216301
addQqMember(initialLeader);
@@ -220,23 +305,47 @@ String moveQqLeader() {
220305
}
221306

222307
String deleteQqLeader() {
308+
return deleteLeader(this::deleteQqMember);
309+
}
310+
311+
String deleteStreamLeader() {
312+
return deleteLeader(leader -> Cli.deleteStreamMember(q, leader));
313+
}
314+
315+
String deleteLeader(Consumer<String> deleteMemberOperation) {
223316
Management.QueueInfo info = queueInfo();
224317
String initialLeader = info.leader();
225318
int initialReplicaCount = info.replicas().size();
226-
deleteQqMember(initialLeader);
319+
deleteMemberOperation.accept(initialLeader);
227320
TestUtils.waitAtMost(() -> !queueInfo().leader().equals(initialLeader));
228321
assertThat(queueInfo().replicas()).hasSize(initialReplicaCount - 1);
229322
return initialLeader;
230323
}
231324

325+
void restartStream() {
326+
Cli.restartStream(this.q);
327+
}
328+
232329
void deleteQqMember(String member) {
233330
Cli.deleteQuorumQueueMember(q, member);
234331
}
235332

333+
void deleteStreamMember(String member) {
334+
Cli.deleteStreamMember(q, member);
335+
}
336+
236337
void addQqMember(String newMember) {
338+
addMember(() -> Cli.addQuorumQueueMember(q, newMember));
339+
}
340+
341+
void addStreamMember(String newMember) {
342+
addMember(() -> Cli.addStreamMember(q, newMember));
343+
}
344+
345+
void addMember(Runnable addMemberOperation) {
237346
Management.QueueInfo info = queueInfo();
238347
int initialReplicaCount = info.replicas().size();
239-
Cli.addQuorumQueueMember(q, newMember);
348+
addMemberOperation.run();
240349
TestUtils.waitAtMost(() -> queueInfo().replicas().size() == initialReplicaCount + 1);
241350
}
242351

0 commit comments

Comments
 (0)