|
21 | 21 | import static java.util.concurrent.TimeUnit.MINUTES;
|
22 | 22 | import static org.apache.commons.lang3.StringUtils.isBlank;
|
23 | 23 | import static org.apache.pulsar.broker.BrokerTestUtil.newUniqueName;
|
| 24 | +import static org.apache.pulsar.common.policies.data.NamespaceIsolationPolicyUnloadScope.*; |
24 | 25 | import static org.mockito.ArgumentMatchers.any;
|
25 | 26 | import static org.mockito.Mockito.doAnswer;
|
26 | 27 | import static org.mockito.Mockito.spy;
|
|
52 | 53 | import java.util.TreeSet;
|
53 | 54 | import java.util.UUID;
|
54 | 55 | import java.util.concurrent.CompletableFuture;
|
| 56 | +import java.util.concurrent.ExecutionException; |
55 | 57 | import java.util.concurrent.TimeUnit;
|
56 | 58 | import java.util.concurrent.atomic.AtomicInteger;
|
57 | 59 | import javax.ws.rs.NotAcceptableException;
|
|
108 | 110 | import org.apache.pulsar.common.naming.NamespaceName;
|
109 | 111 | import org.apache.pulsar.common.naming.TopicDomain;
|
110 | 112 | import org.apache.pulsar.common.naming.TopicName;
|
111 |
| -import org.apache.pulsar.common.policies.data.AutoFailoverPolicyData; |
112 |
| -import org.apache.pulsar.common.policies.data.AutoFailoverPolicyType; |
113 |
| -import org.apache.pulsar.common.policies.data.AutoTopicCreationOverride; |
114 |
| -import org.apache.pulsar.common.policies.data.BacklogQuota; |
115 |
| -import org.apache.pulsar.common.policies.data.BrokerNamespaceIsolationData; |
116 |
| -import org.apache.pulsar.common.policies.data.BrokerNamespaceIsolationDataImpl; |
117 |
| -import org.apache.pulsar.common.policies.data.BundlesData; |
118 |
| -import org.apache.pulsar.common.policies.data.ClusterData; |
119 |
| -import org.apache.pulsar.common.policies.data.ConsumerStats; |
120 |
| -import org.apache.pulsar.common.policies.data.EntryFilters; |
121 |
| -import org.apache.pulsar.common.policies.data.FailureDomain; |
122 |
| -import org.apache.pulsar.common.policies.data.NamespaceIsolationData; |
123 |
| -import org.apache.pulsar.common.policies.data.NonPersistentTopicStats; |
124 |
| -import org.apache.pulsar.common.policies.data.PartitionedTopicStats; |
125 |
| -import org.apache.pulsar.common.policies.data.PersistencePolicies; |
126 |
| -import org.apache.pulsar.common.policies.data.PersistentTopicInternalStats; |
127 |
| -import org.apache.pulsar.common.policies.data.RetentionPolicies; |
128 |
| -import org.apache.pulsar.common.policies.data.SubscriptionStats; |
129 |
| -import org.apache.pulsar.common.policies.data.TenantInfoImpl; |
130 |
| -import org.apache.pulsar.common.policies.data.TopicStats; |
131 |
| -import org.apache.pulsar.common.policies.data.TopicType; |
| 113 | +import org.apache.pulsar.common.policies.data.*; |
132 | 114 | import org.apache.pulsar.common.policies.data.impl.BacklogQuotaImpl;
|
133 | 115 | import org.apache.pulsar.common.protocol.schema.SchemaData;
|
134 | 116 | import org.apache.pulsar.common.util.collections.ConcurrentOpenHashMap;
|
@@ -3494,4 +3476,188 @@ public void testGetStatsIfPartitionNotExists() throws Exception {
|
3494 | 3476 | // cleanup.
|
3495 | 3477 | admin.topics().deletePartitionedTopic(partitionedTp);
|
3496 | 3478 | }
|
| 3479 | + |
| 3480 | + private NamespaceIsolationData createPolicyData(NamespaceIsolationPolicyUnloadScope scope, List<String> namespaces, |
| 3481 | + List<String> primaryBrokers |
| 3482 | + ) { |
| 3483 | + // setup ns-isolation-policy in both the clusters. |
| 3484 | + Map<String, String> parameters1 = new HashMap<>(); |
| 3485 | + parameters1.put("min_limit", "1"); |
| 3486 | + parameters1.put("usage_threshold", "100"); |
| 3487 | + List<String> nsRegexList = new ArrayList<>(namespaces); |
| 3488 | + |
| 3489 | + return NamespaceIsolationData.builder() |
| 3490 | + // "prop-ig/ns1" is present in test cluster, policy set on test2 should work |
| 3491 | + .namespaces(nsRegexList) |
| 3492 | + .primary(primaryBrokers) |
| 3493 | + .secondary(Collections.singletonList("")) |
| 3494 | + .autoFailoverPolicy(AutoFailoverPolicyData.builder() |
| 3495 | + .policyType(AutoFailoverPolicyType.min_available) |
| 3496 | + .parameters(parameters1) |
| 3497 | + .build()) |
| 3498 | + .unloadScope(scope) |
| 3499 | + .build(); |
| 3500 | + } |
| 3501 | + |
| 3502 | + private boolean allTopicsUnloaded(List<String> topics) { |
| 3503 | + for (String topic : topics) { |
| 3504 | + if (pulsar.getBrokerService().getTopicReference(topic).isPresent()) { |
| 3505 | + return false; |
| 3506 | + } |
| 3507 | + } |
| 3508 | + return true; |
| 3509 | + } |
| 3510 | + |
| 3511 | + private void loadTopics(List<String> topics) throws PulsarClientException, ExecutionException, InterruptedException { |
| 3512 | + // create a topic by creating a producer so that the topic is present on the broker |
| 3513 | + for (String topic : topics) { |
| 3514 | + Producer<byte[]> producer = pulsarClient.newProducer().topic(topic).create(); |
| 3515 | + producer.close(); |
| 3516 | + pulsar.getBrokerService().getTopicIfExists(topic).get(); |
| 3517 | + } |
| 3518 | + |
| 3519 | + // All namespaces are loaded onto broker. Assert that |
| 3520 | + for (String topic : topics) { |
| 3521 | + assertTrue(pulsar.getBrokerService().getTopicReference(topic).isPresent()); |
| 3522 | + } |
| 3523 | + } |
| 3524 | + |
| 3525 | + /** |
| 3526 | + * Validates that the namespace isolation policy set and update is unloading only the relevant namespaces based on |
| 3527 | + * the unload scope provided. |
| 3528 | + * |
| 3529 | + * @param topicType persistent or non persistent. |
| 3530 | + * @param policyName policy name. |
| 3531 | + * @param nsPrefix unique namespace prefix. |
| 3532 | + * @param totalNamespaces total namespaces to create. Only the end part. Each namespace also gets a topic t1. |
| 3533 | + * @param initialScope unload scope while creating the policy. |
| 3534 | + * @param initialNamespaceRegex namespace regex while creating the policy. |
| 3535 | + * @param initialLoadedNS expected namespaces to be still loaded after the policy create call. Remaining namespaces |
| 3536 | + * will be asserted to be unloaded within 20 seconds. |
| 3537 | + * @param updatedScope unload scope while updating the policy. |
| 3538 | + * @param updatedNamespaceRegex namespace regex while updating the policy. |
| 3539 | + * @param updatedLoadedNS expected namespaces to be loaded after policy update call. Remaining namespaces will be |
| 3540 | + * asserted to be unloaded within 20 seconds. |
| 3541 | + * @throws PulsarAdminException |
| 3542 | + * @throws PulsarClientException |
| 3543 | + * @throws ExecutionException |
| 3544 | + * @throws InterruptedException |
| 3545 | + */ |
| 3546 | + private void testIsolationPolicyUnloadsNSWithScope(String topicType, String policyName, String nsPrefix, |
| 3547 | + List<String> totalNamespaces, |
| 3548 | + NamespaceIsolationPolicyUnloadScope initialScope, |
| 3549 | + List<String> initialNamespaceRegex, List<String> initialLoadedNS, |
| 3550 | + NamespaceIsolationPolicyUnloadScope updatedScope, |
| 3551 | + List<String> updatedNamespaceRegex, List<String> updatedLoadedNS, |
| 3552 | + List<String> updatedBrokerRegex) |
| 3553 | + throws PulsarAdminException, PulsarClientException, ExecutionException, InterruptedException { |
| 3554 | + |
| 3555 | + // Create all namespaces |
| 3556 | + List<String> allTopics = new ArrayList<>(); |
| 3557 | + for (String namespacePart: totalNamespaces) { |
| 3558 | + admin.namespaces().createNamespace(nsPrefix + namespacePart, Set.of("test")); |
| 3559 | + allTopics.add(topicType + "://" + nsPrefix + namespacePart + "/t1"); |
| 3560 | + } |
| 3561 | + // Load all topics so that they are present. Assume topic t1 under each namespace |
| 3562 | + loadTopics(allTopics); |
| 3563 | + |
| 3564 | + // Create the policy |
| 3565 | + NamespaceIsolationData nsPolicyData1 = createPolicyData( |
| 3566 | + initialScope, initialNamespaceRegex, Collections.singletonList(".*") |
| 3567 | + ); |
| 3568 | + admin.clusters().createNamespaceIsolationPolicy("test", policyName, nsPolicyData1); |
| 3569 | + |
| 3570 | + List<String> initialLoadedTopics = new ArrayList<>(); |
| 3571 | + for (String namespacePart: initialLoadedNS) { |
| 3572 | + initialLoadedTopics.add(topicType + "://" + nsPrefix + namespacePart + "/t1"); |
| 3573 | + } |
| 3574 | + |
| 3575 | + List<String> initialUnloadedTopics = new ArrayList<>(allTopics); |
| 3576 | + initialUnloadedTopics.removeAll(initialLoadedTopics); |
| 3577 | + |
| 3578 | + // Assert that all topics (and thus ns) not under initialLoadedNS namespaces are unloaded |
| 3579 | + if (initialUnloadedTopics.isEmpty()) { |
| 3580 | + // Just wait a bit to ensure we don't miss lazy unloading of topics we expect not to unload |
| 3581 | + TimeUnit.SECONDS.sleep(5); |
| 3582 | + } else { |
| 3583 | + Awaitility.await() |
| 3584 | + .atMost(10, TimeUnit.SECONDS) |
| 3585 | + .until(() -> allTopicsUnloaded(initialUnloadedTopics)); |
| 3586 | + } |
| 3587 | + // Assert that all topics under initialLoadedNS are still present |
| 3588 | + initialLoadedTopics.forEach(t -> assertTrue(pulsar.getBrokerService().getTopicReference(t).isPresent())); |
| 3589 | + |
| 3590 | + // Load the topics again |
| 3591 | + loadTopics(allTopics); |
| 3592 | + |
| 3593 | + // Update policy using updatedScope with updated namespace regex |
| 3594 | + nsPolicyData1 = createPolicyData(updatedScope, updatedNamespaceRegex, updatedBrokerRegex); |
| 3595 | + admin.clusters().updateNamespaceIsolationPolicy("test", policyName, nsPolicyData1); |
| 3596 | + |
| 3597 | + List<String> updatedLoadedTopics = new ArrayList<>(); |
| 3598 | + for (String namespacePart : updatedLoadedNS) { |
| 3599 | + updatedLoadedTopics.add(topicType + "://" + nsPrefix + namespacePart + "/t1"); |
| 3600 | + } |
| 3601 | + |
| 3602 | + List<String> updatedUnloadedTopics = new ArrayList<>(allTopics); |
| 3603 | + updatedUnloadedTopics.removeAll(updatedLoadedTopics); |
| 3604 | + |
| 3605 | + // Assert that all topics (and thus ns) not under updatedLoadedNS namespaces are unloaded |
| 3606 | + if (updatedUnloadedTopics.isEmpty()) { |
| 3607 | + // Just wait a bit to ensure we don't miss lazy unloading of topics we expect not to unload |
| 3608 | + TimeUnit.SECONDS.sleep(5); |
| 3609 | + } else { |
| 3610 | + Awaitility.await() |
| 3611 | + .atMost(10, TimeUnit.SECONDS) |
| 3612 | + .until(() -> allTopicsUnloaded(updatedUnloadedTopics)); |
| 3613 | + } |
| 3614 | + // Assert that all topics under updatedLoadedNS are still present |
| 3615 | + updatedLoadedTopics.forEach(t -> assertTrue(pulsar.getBrokerService().getTopicReference(t).isPresent())); |
| 3616 | + |
| 3617 | + } |
| 3618 | + |
| 3619 | + @Test(dataProvider = "topicType") |
| 3620 | + public void testIsolationPolicyUnloadsNSWithAllScope(final String topicType) throws Exception { |
| 3621 | + String nsPrefix = newUniqueName(defaultTenant + "/") + "-unload-test-"; |
| 3622 | + testIsolationPolicyUnloadsNSWithScope( |
| 3623 | + topicType, "policy-all", nsPrefix, List.of("a1", "a2", "b1", "b2", "c1"), |
| 3624 | + all_matching, List.of(".*-unload-test-a.*"), List.of("b1", "b2", "c1"), |
| 3625 | + all_matching, List.of(".*-unload-test-a.*", ".*-unload-test-c.*"), List.of("b1", "b2"), |
| 3626 | + Collections.singletonList(".*") |
| 3627 | + ); |
| 3628 | + } |
| 3629 | + |
| 3630 | + @Test(dataProvider = "topicType") |
| 3631 | + public void testIsolationPolicyUnloadsNSWithChangedScope(final String topicType) throws Exception { |
| 3632 | + String nsPrefix = newUniqueName(defaultTenant + "/") + "-unload-test-"; |
| 3633 | + testIsolationPolicyUnloadsNSWithScope( |
| 3634 | + topicType, "policy-changed", nsPrefix, List.of("a1", "a2", "b1", "b2", "c1"), |
| 3635 | + all_matching, List.of(".*-unload-test-a.*"), List.of("b1", "b2", "c1"), |
| 3636 | + changed, List.of(".*-unload-test-a.*", ".*-unload-test-c.*"), List.of("a1", "a2", "b1", "b2"), |
| 3637 | + Collections.singletonList(".*") |
| 3638 | + ); |
| 3639 | + } |
| 3640 | + |
| 3641 | + @Test(dataProvider = "topicType") |
| 3642 | + public void testIsolationPolicyUnloadsNSWithNoneScope(final String topicType) throws Exception { |
| 3643 | + String nsPrefix = newUniqueName(defaultTenant + "/") + "-unload-test-"; |
| 3644 | + testIsolationPolicyUnloadsNSWithScope( |
| 3645 | + topicType, "policy-none", nsPrefix, List.of("a1", "a2", "b1", "b2", "c1"), |
| 3646 | + all_matching, List.of(".*-unload-test-a.*"), List.of("b1", "b2", "c1"), |
| 3647 | + none, List.of(".*-unload-test-a.*", ".*-unload-test-c.*"), List.of("a1", "a2", "b1", "b2", "c1"), |
| 3648 | + Collections.singletonList(".*") |
| 3649 | + ); |
| 3650 | + } |
| 3651 | + |
| 3652 | + @Test(dataProvider = "topicType") |
| 3653 | + public void testIsolationPolicyUnloadsNSWithPrimaryChanged(final String topicType) throws Exception { |
| 3654 | + String nsPrefix = newUniqueName(defaultTenant + "/") + "-unload-test-"; |
| 3655 | + // As per changed flag, only c1 should unload, but due to primary change, both a* and c* will. |
| 3656 | + testIsolationPolicyUnloadsNSWithScope( |
| 3657 | + topicType, "policy-primary-changed", nsPrefix, List.of("a1", "a2", "b1", "b2", "c1"), |
| 3658 | + all_matching, List.of(".*-unload-test-a.*"), List.of("b1", "b2", "c1"), |
| 3659 | + changed, List.of(".*-unload-test-a.*", ".*-unload-test-c.*"), List.of("b1", "b2"), |
| 3660 | + List.of(".*", "broker.*") |
| 3661 | + ); |
| 3662 | + } |
3497 | 3663 | }
|
0 commit comments