|
20 | 20 |
|
21 | 21 | import static org.apache.pulsar.common.naming.SystemTopicNames.TRANSACTION_COORDINATOR_ASSIGN;
|
22 | 22 | import static org.apache.pulsar.common.naming.SystemTopicNames.TRANSACTION_COORDINATOR_LOG;
|
| 23 | +import static org.mockito.ArgumentMatchers.anyString; |
23 | 24 | import static org.mockito.Mockito.any;
|
24 | 25 | import static org.mockito.Mockito.doReturn;
|
| 26 | +import static org.mockito.Mockito.mock; |
25 | 27 | import static org.mockito.Mockito.spy;
|
| 28 | +import static org.mockito.Mockito.when; |
26 | 29 | import static org.testng.Assert.assertEquals;
|
27 | 30 | import static org.testng.Assert.assertFalse;
|
28 | 31 | import static org.testng.Assert.assertNotNull;
|
29 | 32 | import static org.testng.Assert.assertNull;
|
30 | 33 | import static org.testng.Assert.assertTrue;
|
31 | 34 | import static org.testng.Assert.fail;
|
| 35 | +import com.google.common.collect.Multimap; |
32 | 36 | import com.google.common.collect.Sets;
|
33 | 37 | import com.google.gson.Gson;
|
34 | 38 | import com.google.gson.JsonArray;
|
35 | 39 | import com.google.gson.JsonObject;
|
36 |
| -import com.google.gson.JsonPrimitive; |
37 | 40 | import io.netty.buffer.ByteBuf;
|
38 | 41 | import io.netty.channel.EventLoopGroup;
|
39 | 42 | import io.netty.util.concurrent.DefaultThreadFactory;
|
|
79 | 82 | import org.apache.pulsar.broker.service.BrokerServiceException.PersistenceException;
|
80 | 83 | import org.apache.pulsar.broker.service.persistent.PersistentSubscription;
|
81 | 84 | import org.apache.pulsar.broker.service.persistent.PersistentTopic;
|
| 85 | +import org.apache.pulsar.broker.stats.prometheus.PrometheusMetricsClient; |
82 | 86 | import org.apache.pulsar.broker.stats.prometheus.PrometheusRawMetricsProvider;
|
83 | 87 | import org.apache.pulsar.client.admin.BrokerStats;
|
84 | 88 | import org.apache.pulsar.client.admin.PulsarAdminException;
|
|
113 | 117 | import org.apache.pulsar.common.protocol.Commands;
|
114 | 118 | import org.apache.pulsar.common.util.netty.EventLoopUtil;
|
115 | 119 | import org.apache.pulsar.compaction.Compactor;
|
| 120 | +import org.apache.zookeeper.KeeperException; |
| 121 | +import org.apache.zookeeper.MockZooKeeper; |
116 | 122 | import org.awaitility.Awaitility;
|
| 123 | +import org.glassfish.jersey.client.JerseyClient; |
| 124 | +import org.glassfish.jersey.client.JerseyClientBuilder; |
117 | 125 | import org.mockito.Mockito;
|
118 | 126 | import org.testng.Assert;
|
119 | 127 | import org.testng.annotations.AfterClass;
|
@@ -1589,82 +1597,93 @@ public void testDynamicConfigurationsForceDeleteTenantAllowed() throws Exception
|
1589 | 1597 | });
|
1590 | 1598 | }
|
1591 | 1599 |
|
1592 |
| - // this test is disabled since it is flaky |
1593 |
| - @Test(enabled = false) |
1594 |
| - public void testBrokerStatsTopicLoadFailed() throws Exception { |
1595 |
| - admin.namespaces().createNamespace("prop/ns-test"); |
1596 |
| - |
1597 |
| - String persistentTopic = "persistent://prop/ns-test/topic1_" + UUID.randomUUID(); |
1598 |
| - String nonPersistentTopic = "non-persistent://prop/ns-test/topic2_" + UUID.randomUUID(); |
1599 |
| - |
1600 |
| - BrokerService brokerService = pulsar.getBrokerService(); |
1601 |
| - brokerService = Mockito.spy(brokerService); |
1602 |
| - // mock create persistent topic failed |
1603 |
| - Mockito |
1604 |
| - .doAnswer(invocation -> { |
1605 |
| - CompletableFuture<ManagedLedgerConfig> f = new CompletableFuture<>(); |
1606 |
| - f.completeExceptionally(new RuntimeException("This is an exception")); |
1607 |
| - return f; |
1608 |
| - }) |
1609 |
| - .when(brokerService).getManagedLedgerConfig(Mockito.eq(TopicName.get(persistentTopic))); |
1610 |
| - |
1611 |
| - // mock create non-persistent topic failed |
1612 |
| - Mockito |
1613 |
| - .doAnswer(inv -> { |
1614 |
| - CompletableFuture<Void> f = new CompletableFuture<>(); |
1615 |
| - f.completeExceptionally(new RuntimeException("This is an exception")); |
1616 |
| - return f; |
1617 |
| - }) |
1618 |
| - .when(brokerService).checkTopicNsOwnership(Mockito.eq(nonPersistentTopic)); |
1619 |
| - |
1620 |
| - |
1621 |
| - PulsarService pulsarService = pulsar; |
1622 |
| - Field field = PulsarService.class.getDeclaredField("brokerService"); |
1623 |
| - field.setAccessible(true); |
1624 |
| - field.set(pulsarService, brokerService); |
1625 |
| - |
1626 |
| - CompletableFuture<Producer<String>> producer = pulsarClient.newProducer(Schema.STRING) |
1627 |
| - .topic(persistentTopic) |
1628 |
| - .createAsync(); |
1629 |
| - CompletableFuture<Producer<String>> producer1 = pulsarClient.newProducer(Schema.STRING) |
1630 |
| - .topic(nonPersistentTopic) |
1631 |
| - .createAsync(); |
1632 |
| - |
1633 |
| - producer.whenComplete((v, t) -> { |
1634 |
| - if (t == null) { |
1635 |
| - try { |
1636 |
| - v.close(); |
1637 |
| - } catch (PulsarClientException e) { |
1638 |
| - // ignore |
1639 |
| - } |
| 1600 | + @Test |
| 1601 | + public void testMetricsPersistentTopicLoadFails() throws Exception { |
| 1602 | + final String namespace = "prop/" + UUID.randomUUID().toString().replaceAll("-", ""); |
| 1603 | + String topic = "persistent://" + namespace + "/topic1_" + UUID.randomUUID(); |
| 1604 | + admin.namespaces().createNamespace(namespace); |
| 1605 | + admin.topics().createNonPartitionedTopic(topic); |
| 1606 | + admin.topics().unload(topic); |
| 1607 | + |
| 1608 | + // Inject an error that makes the topic load fails. |
| 1609 | + AtomicBoolean failMarker = new AtomicBoolean(true); |
| 1610 | + mockZooKeeper.failConditional(KeeperException.Code.NODEEXISTS, (op, path) -> { |
| 1611 | + if (failMarker.get() && op.equals(MockZooKeeper.Op.SET) && |
| 1612 | + path.endsWith(TopicName.get(topic).getPersistenceNamingEncoding())) { |
| 1613 | + return true; |
1640 | 1614 | }
|
| 1615 | + return false; |
1641 | 1616 | });
|
1642 |
| - producer1.whenComplete((v, t) -> { |
1643 |
| - if (t == null) { |
1644 |
| - try { |
1645 |
| - v.close(); |
1646 |
| - } catch (PulsarClientException e) { |
1647 |
| - // ignore |
1648 |
| - } |
| 1617 | + |
| 1618 | + // Do test |
| 1619 | + CompletableFuture<Producer<byte[]>> producer = pulsarClient.newProducer().topic(topic).createAsync(); |
| 1620 | + JerseyClient httpClient = JerseyClientBuilder.createClient(); |
| 1621 | + Awaitility.await().until(() -> { |
| 1622 | + String response = httpClient.target(pulsar.getWebServiceAddress()).path("/metrics/") |
| 1623 | + .request().get(String.class); |
| 1624 | + Multimap<String, PrometheusMetricsClient.Metric> metricMap = PrometheusMetricsClient.parseMetrics(response); |
| 1625 | + if (!metricMap.containsKey("pulsar_topic_load_failed_count")) { |
| 1626 | + return false; |
| 1627 | + } |
| 1628 | + double topic_load_failed_count = 0; |
| 1629 | + for (PrometheusMetricsClient.Metric metric : metricMap.get("pulsar_topic_load_failed_count")) { |
| 1630 | + topic_load_failed_count += metric.value; |
1649 | 1631 | }
|
| 1632 | + return topic_load_failed_count >= 1D; |
1650 | 1633 | });
|
1651 | 1634 |
|
1652 |
| - Awaitility.waitAtMost(2, TimeUnit.MINUTES).until(() -> { |
1653 |
| - String json = admin.brokerStats().getMetrics(); |
1654 |
| - JsonArray metrics = new Gson().fromJson(json, JsonArray.class); |
1655 |
| - AtomicBoolean flag = new AtomicBoolean(false); |
1656 |
| - |
1657 |
| - metrics.forEach(ele -> { |
1658 |
| - JsonObject obj = ((JsonObject) ele); |
1659 |
| - JsonObject metrics0 = (JsonObject) obj.get("metrics"); |
1660 |
| - JsonPrimitive v = (JsonPrimitive) metrics0.get("brk_topic_load_failed_count"); |
1661 |
| - if (null != v && v.getAsDouble() >= 2D) { |
1662 |
| - flag.set(true); |
1663 |
| - } |
1664 |
| - }); |
| 1635 | + // Remove the injection. |
| 1636 | + failMarker.set(false); |
| 1637 | + // cleanup. |
| 1638 | + httpClient.close(); |
| 1639 | + producer.join().close(); |
| 1640 | + admin.topics().delete(topic); |
| 1641 | + admin.namespaces().deleteNamespace(namespace); |
| 1642 | + } |
1665 | 1643 |
|
1666 |
| - return flag.get(); |
| 1644 | + @Test |
| 1645 | + public void testMetricsNonPersistentTopicLoadFails() throws Exception { |
| 1646 | + final String namespace = "prop/" + UUID.randomUUID().toString().replaceAll("-", ""); |
| 1647 | + String topic = "non-persistent://" + namespace + "/topic1_" + UUID.randomUUID(); |
| 1648 | + admin.namespaces().createNamespace(namespace); |
| 1649 | + |
| 1650 | + // Inject an error that makes the topic load fails. |
| 1651 | + // Since we did not set a topic factory name, the "topicFactory" variable is null, inject a mocked |
| 1652 | + // "topicFactory". |
| 1653 | + Field fieldTopicFactory = BrokerService.class.getDeclaredField("topicFactory"); |
| 1654 | + fieldTopicFactory.setAccessible(true); |
| 1655 | + TopicFactory originalTopicFactory = (TopicFactory) fieldTopicFactory.get(pulsar.getBrokerService()); |
| 1656 | + assertNull(originalTopicFactory); |
| 1657 | + TopicFactory mockedTopicFactory = mock(TopicFactory.class); |
| 1658 | + when(mockedTopicFactory.create(anyString(), any(), any(), any())) |
| 1659 | + .thenThrow(new RuntimeException("mocked error")); |
| 1660 | + fieldTopicFactory.set(pulsar.getBrokerService(), mockedTopicFactory); |
| 1661 | + |
| 1662 | + // Do test. |
| 1663 | + CompletableFuture<Producer<byte[]>> producer = pulsarClient.newProducer().topic(topic).createAsync(); |
| 1664 | + JerseyClient httpClient = JerseyClientBuilder.createClient(); |
| 1665 | + Awaitility.await().until(() -> { |
| 1666 | + String response = httpClient.target(pulsar.getWebServiceAddress()).path("/metrics/") |
| 1667 | + .request().get(String.class); |
| 1668 | + Multimap<String, PrometheusMetricsClient.Metric> metricMap = PrometheusMetricsClient.parseMetrics(response); |
| 1669 | + if (!metricMap.containsKey("pulsar_topic_load_failed_count")) { |
| 1670 | + return false; |
| 1671 | + } |
| 1672 | + double topic_load_failed_count = 0; |
| 1673 | + for (PrometheusMetricsClient.Metric metric : metricMap.get("pulsar_topic_load_failed_count")) { |
| 1674 | + topic_load_failed_count += metric.value; |
| 1675 | + } |
| 1676 | + return topic_load_failed_count >= 1D; |
1667 | 1677 | });
|
| 1678 | + |
| 1679 | + // Remove the injection. |
| 1680 | + fieldTopicFactory.set(pulsar.getBrokerService(), null); |
| 1681 | + |
| 1682 | + // cleanup. |
| 1683 | + httpClient.close(); |
| 1684 | + producer.join().close(); |
| 1685 | + admin.topics().delete(topic); |
| 1686 | + admin.namespaces().deleteNamespace(namespace); |
1668 | 1687 | }
|
1669 | 1688 |
|
1670 | 1689 | @Test
|
|
0 commit comments