|
40 | 40 | import java.nio.charset.StandardCharsets; |
41 | 41 | import java.util.ArrayList; |
42 | 42 | import java.util.Collection; |
| 43 | +import java.util.Collections; |
| 44 | +import java.util.HashSet; |
43 | 45 | import java.util.List; |
| 46 | +import java.util.Set; |
44 | 47 | import java.util.UUID; |
45 | 48 | import java.util.concurrent.CompletableFuture; |
46 | 49 | import java.util.concurrent.CountDownLatch; |
47 | 50 | import java.util.concurrent.ExecutionException; |
48 | 51 | import java.util.concurrent.TimeUnit; |
49 | 52 | import java.util.concurrent.atomic.AtomicBoolean; |
| 53 | +import java.util.function.Supplier; |
50 | 54 | import lombok.Cleanup; |
51 | 55 | import org.apache.bookkeeper.client.LedgerHandle; |
| 56 | +import org.apache.bookkeeper.mledger.ManagedCursor; |
52 | 57 | import org.apache.bookkeeper.mledger.ManagedLedger; |
53 | 58 | import org.apache.pulsar.broker.service.BrokerService; |
54 | 59 | import org.apache.pulsar.broker.service.BrokerTestBase; |
|
57 | 62 | import org.apache.pulsar.client.admin.PulsarAdminException; |
58 | 63 | import org.apache.pulsar.client.api.Consumer; |
59 | 64 | import org.apache.pulsar.client.api.Message; |
| 65 | +import org.apache.pulsar.client.api.MessageId; |
60 | 66 | import org.apache.pulsar.client.api.MessageListener; |
61 | 67 | import org.apache.pulsar.client.api.MessageRoutingMode; |
62 | 68 | import org.apache.pulsar.client.api.Producer; |
|
66 | 72 | import org.apache.pulsar.client.api.SubscriptionType; |
67 | 73 | import org.apache.pulsar.common.naming.NamespaceBundle; |
68 | 74 | import org.apache.pulsar.common.naming.TopicName; |
| 75 | +import org.apache.pulsar.common.policies.data.ClusterData; |
69 | 76 | import org.apache.pulsar.common.policies.data.Policies; |
| 77 | +import org.apache.pulsar.common.policies.data.TenantInfo; |
70 | 78 | import org.apache.pulsar.common.policies.data.TopicStats; |
71 | 79 | import org.awaitility.Awaitility; |
72 | 80 | import org.junit.Assert; |
@@ -525,4 +533,59 @@ public void testDeleteTopicFail() throws Exception { |
525 | 533 | makeDeletedFailed.set(false); |
526 | 534 | persistentTopic.delete().get(); |
527 | 535 | } |
| 536 | + |
| 537 | + @DataProvider(name = "topicLevelPolicy") |
| 538 | + public static Object[][] topicLevelPolicy() { |
| 539 | + return new Object[][] { { true }, { false } }; |
| 540 | + } |
| 541 | + |
| 542 | + @Test(dataProvider = "topicLevelPolicy") |
| 543 | + public void testCreateTopicWithZombieReplicatorCursor(boolean topicLevelPolicy) throws Exception { |
| 544 | + final String namespace = "prop/ns-abc"; |
| 545 | + final String topicName = "persistent://" + namespace |
| 546 | + + "/testCreateTopicWithZombieReplicatorCursor" + topicLevelPolicy; |
| 547 | + final String remoteCluster = "remote"; |
| 548 | + admin.topics().createNonPartitionedTopic(topicName); |
| 549 | + admin.topics().createSubscription(topicName, conf.getReplicatorPrefix() + "." + remoteCluster, |
| 550 | + MessageId.earliest, true); |
| 551 | + |
| 552 | + admin.clusters().createCluster(remoteCluster, ClusterData.builder() |
| 553 | + .serviceUrl("http://localhost:11112") |
| 554 | + .brokerServiceUrl("pulsar://localhost:11111") |
| 555 | + .build()); |
| 556 | + TenantInfo tenantInfo = admin.tenants().getTenantInfo("prop"); |
| 557 | + tenantInfo.getAllowedClusters().add(remoteCluster); |
| 558 | + admin.tenants().updateTenant("prop", tenantInfo); |
| 559 | + |
| 560 | + if (topicLevelPolicy) { |
| 561 | + admin.topics().setReplicationClusters(topicName, Collections.singletonList(remoteCluster)); |
| 562 | + } else { |
| 563 | + admin.namespaces().setNamespaceReplicationClustersAsync( |
| 564 | + namespace, Collections.singleton(remoteCluster)).get(); |
| 565 | + } |
| 566 | + |
| 567 | + final PersistentTopic topic = (PersistentTopic) pulsar.getBrokerService().getTopic(topicName, false) |
| 568 | + .get(3, TimeUnit.SECONDS).orElse(null); |
| 569 | + assertNotNull(topic); |
| 570 | + |
| 571 | + final Supplier<Set<String>> getCursors = () -> { |
| 572 | + final Set<String> cursors = new HashSet<>(); |
| 573 | + final Iterable<ManagedCursor> iterable = topic.getManagedLedger().getCursors(); |
| 574 | + iterable.forEach(c -> cursors.add(c.getName())); |
| 575 | + return cursors; |
| 576 | + }; |
| 577 | + assertEquals(getCursors.get(), Collections.singleton(conf.getReplicatorPrefix() + "." + remoteCluster)); |
| 578 | + |
| 579 | + if (topicLevelPolicy) { |
| 580 | + admin.topics().setReplicationClusters(topicName, Collections.emptyList()); |
| 581 | + } else { |
| 582 | + admin.namespaces().setNamespaceReplicationClustersAsync(namespace, Collections.emptySet()).get(); |
| 583 | + } |
| 584 | + admin.clusters().deleteCluster(remoteCluster); |
| 585 | + // Now the cluster and its related policy has been removed but the replicator cursor still exists |
| 586 | + |
| 587 | + topic.initialize().get(3, TimeUnit.SECONDS); |
| 588 | + Awaitility.await().atMost(3, TimeUnit.SECONDS) |
| 589 | + .until(() -> !topic.getManagedLedger().getCursors().iterator().hasNext()); |
| 590 | + } |
528 | 591 | } |
0 commit comments