|
31 | 31 | import java.util.Arrays;
|
32 | 32 | import java.util.List;
|
33 | 33 | import java.util.UUID;
|
| 34 | +import java.util.concurrent.CompletableFuture; |
34 | 35 | import java.util.concurrent.ExecutionException;
|
35 | 36 | import java.util.concurrent.TimeUnit;
|
36 | 37 | import lombok.AllArgsConstructor;
|
|
42 | 43 | import org.apache.avro.Schema.Parser;
|
43 | 44 | import org.apache.avro.reflect.ReflectData;
|
44 | 45 | import org.apache.pulsar.TestNGInstanceOrder;
|
| 46 | +import org.apache.pulsar.broker.BrokerTestUtil; |
| 47 | +import org.apache.pulsar.broker.service.persistent.PersistentTopic; |
45 | 48 | import org.apache.pulsar.client.admin.PulsarAdminException;
|
46 | 49 | import org.apache.pulsar.client.api.PulsarClientException.IncompatibleSchemaException;
|
47 | 50 | import org.apache.pulsar.client.api.PulsarClientException.InvalidMessageException;
|
48 | 51 | import org.apache.pulsar.client.api.schema.GenericRecord;
|
49 | 52 | import org.apache.pulsar.client.impl.BinaryProtoLookupService;
|
| 53 | +import org.apache.pulsar.client.impl.ClientBuilderImpl; |
| 54 | +import org.apache.pulsar.client.impl.ClientCnx; |
50 | 55 | import org.apache.pulsar.client.impl.HttpLookupService;
|
51 | 56 | import org.apache.pulsar.client.impl.LookupService;
|
52 | 57 | import org.apache.pulsar.client.impl.MessageImpl;
|
53 | 58 | import org.apache.pulsar.client.impl.PulsarClientImpl;
|
| 59 | +import org.apache.pulsar.client.impl.metrics.InstrumentProvider; |
54 | 60 | import org.apache.pulsar.client.impl.schema.KeyValueSchemaImpl;
|
55 | 61 | import org.apache.pulsar.client.impl.schema.SchemaInfoImpl;
|
56 | 62 | import org.apache.pulsar.client.impl.schema.reader.AvroReader;
|
57 | 63 | import org.apache.pulsar.client.impl.schema.writer.AvroWriter;
|
| 64 | +import org.apache.pulsar.common.api.proto.CommandGetOrCreateSchemaResponse; |
58 | 65 | import org.apache.pulsar.common.naming.TopicName;
|
| 66 | +import org.apache.pulsar.common.policies.data.SchemaCompatibilityStrategy; |
59 | 67 | import org.apache.pulsar.common.schema.KeyValue;
|
60 | 68 | import org.apache.pulsar.common.schema.KeyValueEncodingType;
|
61 | 69 | import org.apache.pulsar.common.schema.LongSchemaVersion;
|
62 | 70 | import org.apache.pulsar.common.schema.SchemaInfo;
|
63 | 71 | import org.apache.pulsar.common.schema.SchemaType;
|
| 72 | +import org.awaitility.Awaitility; |
64 | 73 | import org.testng.Assert;
|
65 | 74 | import org.testng.annotations.AfterClass;
|
66 | 75 | import org.testng.annotations.AfterMethod;
|
|
76 | 85 | public class SimpleSchemaTest extends ProducerConsumerBase {
|
77 | 86 |
|
78 | 87 | private static final String NAMESPACE = "my-property/my-ns";
|
| 88 | + private static final String NAMESPACE_ALWAYS_COMPATIBLE = "my-property/always-compatible"; |
| 89 | + private static final String NAMESPACE_NEVER_COMPATIBLE = "my-property/never-compatible"; |
79 | 90 |
|
80 | 91 | @DataProvider(name = "batchingModes")
|
81 | 92 | public static Object[][] batchingModes() {
|
@@ -124,6 +135,12 @@ protected void setup() throws Exception {
|
124 | 135 | this.isTcpLookup = true;
|
125 | 136 | super.internalSetup();
|
126 | 137 | super.producerBaseSetup();
|
| 138 | + admin.namespaces().createNamespace(NAMESPACE_ALWAYS_COMPATIBLE); |
| 139 | + admin.namespaces().setSchemaCompatibilityStrategy(NAMESPACE_ALWAYS_COMPATIBLE, |
| 140 | + SchemaCompatibilityStrategy.ALWAYS_COMPATIBLE); |
| 141 | + admin.namespaces().createNamespace(NAMESPACE_NEVER_COMPATIBLE); |
| 142 | + admin.namespaces().setSchemaCompatibilityStrategy(NAMESPACE_NEVER_COMPATIBLE, |
| 143 | + SchemaCompatibilityStrategy.ALWAYS_INCOMPATIBLE); |
127 | 144 | }
|
128 | 145 |
|
129 | 146 | @AfterClass(alwaysRun = true)
|
@@ -340,6 +357,78 @@ public void newProducerForMessageSchemaOnTopicWithMultiVersionSchema() throws Ex
|
340 | 357 | }
|
341 | 358 | }
|
342 | 359 |
|
| 360 | + @Test |
| 361 | + public void testProducerConnectStateWhenRegisteringSchema() throws Exception { |
| 362 | + final String topic = BrokerTestUtil.newUniqueName(NAMESPACE_ALWAYS_COMPATIBLE + "/tp"); |
| 363 | + final String subscription = "s1"; |
| 364 | + admin.topics().createNonPartitionedTopic(topic); |
| 365 | + admin.topics().createSubscription(topic, subscription, MessageId.earliest); |
| 366 | + |
| 367 | + // Create a pulsar client with a delayed response of "getOrCreateSchemaResponse" |
| 368 | + CompletableFuture<Void> responseSignal = new CompletableFuture<>(); |
| 369 | + ClientBuilderImpl clientBuilder = (ClientBuilderImpl) PulsarClient.builder().serviceUrl(lookupUrl.toString()); |
| 370 | + PulsarClient client = InjectedClientCnxClientBuilder.create(clientBuilder, (conf, eventLoopGroup) -> |
| 371 | + new ClientCnx(InstrumentProvider.NOOP, conf, eventLoopGroup) { |
| 372 | + protected void handleGetOrCreateSchemaResponse( |
| 373 | + CommandGetOrCreateSchemaResponse commandGetOrCreateSchemaResponse) { |
| 374 | + responseSignal.join(); |
| 375 | + super.handleGetOrCreateSchemaResponse(commandGetOrCreateSchemaResponse); |
| 376 | + } |
| 377 | + }); |
| 378 | + Producer producer = client.newProducer(Schema.AUTO_PRODUCE_BYTES()).enableBatching(false).topic(topic).create(); |
| 379 | + producer.newMessage(Schema.STRING).value("msg").sendAsync(); |
| 380 | + |
| 381 | + PersistentTopic persistentTopic = |
| 382 | + (PersistentTopic) pulsar.getBrokerService().getTopic(topic, false).get().get(); |
| 383 | + assertEquals(persistentTopic.getProducers().size(), 1); |
| 384 | + assertTrue(producer.isConnected()); |
| 385 | + |
| 386 | + // cleanup. |
| 387 | + responseSignal.complete(null); |
| 388 | + producer.close(); |
| 389 | + client.close(); |
| 390 | + Awaitility.await().untilAsserted(() -> { |
| 391 | + assertEquals(persistentTopic.getProducers().size(), 0); |
| 392 | + }); |
| 393 | + admin.topics().delete(topic); |
| 394 | + } |
| 395 | + |
| 396 | + @Test |
| 397 | + public void testNoMemoryLeakIfSchemaIncompatible() throws Exception { |
| 398 | + final String topic = BrokerTestUtil.newUniqueName(NAMESPACE_NEVER_COMPATIBLE + "/tp"); |
| 399 | + final String subscription = "s1"; |
| 400 | + admin.topics().createNonPartitionedTopic(topic); |
| 401 | + admin.topics().createSubscription(topic, subscription, MessageId.earliest); |
| 402 | + |
| 403 | + // Create a pulsar client with a delayed response of "getOrCreateSchemaResponse" |
| 404 | + CompletableFuture<Void> responseSignal = new CompletableFuture<>(); |
| 405 | + ClientBuilderImpl clientBuilder = (ClientBuilderImpl) PulsarClient.builder().serviceUrl(lookupUrl.toString()); |
| 406 | + PulsarClient client = InjectedClientCnxClientBuilder.create(clientBuilder, (conf, eventLoopGroup) -> |
| 407 | + new ClientCnx(InstrumentProvider.NOOP, conf, eventLoopGroup) { |
| 408 | + protected void handleGetOrCreateSchemaResponse( |
| 409 | + CommandGetOrCreateSchemaResponse commandGetOrCreateSchemaResponse) { |
| 410 | + responseSignal.join(); |
| 411 | + super.handleGetOrCreateSchemaResponse(commandGetOrCreateSchemaResponse); |
| 412 | + } |
| 413 | + }); |
| 414 | + Producer producer = client.newProducer(Schema.AUTO_PRODUCE_BYTES()).enableBatching(false).topic(topic).create(); |
| 415 | + producer.newMessage(Schema.STRING).value("msg").sendAsync(); |
| 416 | + |
| 417 | + PersistentTopic persistentTopic = |
| 418 | + (PersistentTopic) pulsar.getBrokerService().getTopic(topic, false).get().get(); |
| 419 | + assertEquals(persistentTopic.getProducers().size(), 1); |
| 420 | + assertTrue(producer.isConnected()); |
| 421 | + |
| 422 | + // cleanup. |
| 423 | + responseSignal.complete(null); |
| 424 | + producer.close(); |
| 425 | + client.close(); |
| 426 | + Awaitility.await().untilAsserted(() -> { |
| 427 | + assertEquals(persistentTopic.getProducers().size(), 0); |
| 428 | + }); |
| 429 | + admin.topics().delete(topic); |
| 430 | + } |
| 431 | + |
343 | 432 | @Test
|
344 | 433 | public void newNativeAvroProducerForMessageSchemaOnTopicWithMultiVersionSchema() throws Exception {
|
345 | 434 | String topic = NAMESPACE + "/schema-test";
|
|
0 commit comments