Convert pulsar 2.8 groovy test to Java (#10176)
Co-authored-by: Lauri Tulmin <ltulmin@splunk.com>
This commit is contained in:
parent
37cb7c61fd
commit
7d544f1476
|
@ -1,671 +0,0 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.pulsar.v2_8
|
||||
|
||||
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
|
||||
import io.opentelemetry.instrumentation.test.asserts.TraceAssert
|
||||
import io.opentelemetry.semconv.SemanticAttributes
|
||||
import org.apache.pulsar.client.admin.PulsarAdmin
|
||||
import org.apache.pulsar.client.api.Consumer
|
||||
import org.apache.pulsar.client.api.Message
|
||||
import org.apache.pulsar.client.api.MessageListener
|
||||
import org.apache.pulsar.client.api.Messages
|
||||
import org.apache.pulsar.client.api.Producer
|
||||
import org.apache.pulsar.client.api.PulsarClient
|
||||
import org.apache.pulsar.client.api.Schema
|
||||
import org.apache.pulsar.client.api.SubscriptionInitialPosition
|
||||
import org.slf4j.Logger
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.testcontainers.containers.PulsarContainer
|
||||
import org.testcontainers.containers.output.Slf4jLogConsumer
|
||||
import org.testcontainers.utility.DockerImageName
|
||||
import spock.lang.Shared
|
||||
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.regex.Pattern
|
||||
|
||||
import static io.opentelemetry.api.trace.SpanKind.CONSUMER
|
||||
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
|
||||
import static io.opentelemetry.api.trace.SpanKind.PRODUCER
|
||||
|
||||
class PulsarClientTest extends AgentInstrumentationSpecification {
|
||||
private static final Logger logger = LoggerFactory.getLogger(PulsarClientTest)
|
||||
|
||||
private static final DockerImageName DEFAULT_IMAGE_NAME =
|
||||
DockerImageName.parse("apachepulsar/pulsar:2.8.0")
|
||||
|
||||
@Shared
|
||||
private PulsarContainer pulsar
|
||||
@Shared
|
||||
private PulsarClient client
|
||||
@Shared
|
||||
private PulsarAdmin admin
|
||||
@Shared
|
||||
private Producer<String> producer
|
||||
@Shared
|
||||
private Consumer<String> consumer
|
||||
@Shared
|
||||
private Producer<String> producer2
|
||||
|
||||
@Shared
|
||||
private String brokerHost
|
||||
@Shared
|
||||
private int brokerPort
|
||||
|
||||
@Override
|
||||
def setupSpec() {
|
||||
pulsar = new PulsarContainer(DEFAULT_IMAGE_NAME)
|
||||
.withEnv("PULSAR_MEM", "-Xmx128m")
|
||||
.withLogConsumer(new Slf4jLogConsumer(logger))
|
||||
.withStartupTimeout(Duration.ofMinutes(2))
|
||||
pulsar.start()
|
||||
|
||||
brokerHost = pulsar.host
|
||||
brokerPort = pulsar.getMappedPort(6650)
|
||||
client = PulsarClient.builder().serviceUrl(pulsar.pulsarBrokerUrl).build()
|
||||
admin = PulsarAdmin.builder().serviceHttpUrl(pulsar.httpServiceUrl).build()
|
||||
}
|
||||
|
||||
@Override
|
||||
def cleanupSpec() {
|
||||
producer?.close()
|
||||
consumer?.close()
|
||||
producer2?.close()
|
||||
client?.close()
|
||||
admin?.close()
|
||||
pulsar.close()
|
||||
}
|
||||
|
||||
def "test send non-partitioned topic"() {
|
||||
setup:
|
||||
def topic = "persistent://public/default/testSendNonPartitionedTopic"
|
||||
admin.topics().createNonPartitionedTopic(topic)
|
||||
producer =
|
||||
client.newProducer(Schema.STRING).topic(topic)
|
||||
.enableBatching(false).create()
|
||||
|
||||
when:
|
||||
String msg = "test"
|
||||
def msgId = runWithSpan("parent") {
|
||||
producer.send(msg)
|
||||
}
|
||||
|
||||
then:
|
||||
assertTraces(1) {
|
||||
trace(0, 2) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
}
|
||||
producerSpan(it, 1, span(0), topic, msgId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test consume non-partitioned topic"() {
|
||||
setup:
|
||||
def topic = "persistent://public/default/testConsumeNonPartitionedTopic"
|
||||
def latch = new CountDownLatch(1)
|
||||
admin.topics().createNonPartitionedTopic(topic)
|
||||
consumer = client.newConsumer(Schema.STRING)
|
||||
.subscriptionName("test_sub")
|
||||
.topic(topic)
|
||||
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
|
||||
.messageListener(new MessageListener<String>() {
|
||||
@Override
|
||||
void received(Consumer<String> consumer, Message<String> msg) {
|
||||
consumer.acknowledge(msg)
|
||||
latch.countDown()
|
||||
}
|
||||
})
|
||||
.subscribe()
|
||||
|
||||
producer = client.newProducer(Schema.STRING)
|
||||
.topic(topic)
|
||||
.enableBatching(false)
|
||||
.create()
|
||||
|
||||
when:
|
||||
def msg = "test"
|
||||
def msgId = runWithSpan("parent") {
|
||||
producer.send(msg)
|
||||
}
|
||||
|
||||
latch.await(1, TimeUnit.MINUTES)
|
||||
|
||||
then:
|
||||
assertTraces(1) {
|
||||
trace(0, 4) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
}
|
||||
producerSpan(it, 1, span(0), topic, msgId)
|
||||
receiveSpan(it, 2, span(1), topic, msgId)
|
||||
processSpan(it, 3, span(2), topic, msgId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test consume non-partitioned topic using receive"() {
|
||||
setup:
|
||||
def topic = "persistent://public/default/testConsumeNonPartitionedTopicCallReceive"
|
||||
admin.topics().createNonPartitionedTopic(topic)
|
||||
consumer = client.newConsumer(Schema.STRING)
|
||||
.subscriptionName("test_sub")
|
||||
.topic(topic)
|
||||
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
|
||||
.subscribe()
|
||||
|
||||
producer = client.newProducer(Schema.STRING)
|
||||
.topic(topic)
|
||||
.enableBatching(false)
|
||||
.create()
|
||||
|
||||
when:
|
||||
def msg = "test"
|
||||
def msgId = runWithSpan("parent") {
|
||||
producer.send(msg)
|
||||
}
|
||||
|
||||
def receivedMsg = consumer.receive()
|
||||
consumer.acknowledge(receivedMsg)
|
||||
|
||||
then:
|
||||
assertTraces(1) {
|
||||
trace(0, 3) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
}
|
||||
producerSpan(it, 1, span(0), topic, msgId)
|
||||
receiveSpan(it, 2, span(1), topic, msgId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test consume non-partitioned topic using receiveAsync"() {
|
||||
setup:
|
||||
def topic = "persistent://public/default/testConsumeNonPartitionedTopicCallReceiveAsync"
|
||||
admin.topics().createNonPartitionedTopic(topic)
|
||||
consumer = client.newConsumer(Schema.STRING)
|
||||
.subscriptionName("test_sub")
|
||||
.topic(topic)
|
||||
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
|
||||
.subscribe()
|
||||
|
||||
producer = client.newProducer(Schema.STRING)
|
||||
.topic(topic)
|
||||
.enableBatching(false)
|
||||
.create()
|
||||
|
||||
when:
|
||||
CompletableFuture<Message<String>> result = consumer.receiveAsync().whenComplete { receivedMsg, throwable ->
|
||||
runWithSpan("callback") {
|
||||
consumer.acknowledge(receivedMsg)
|
||||
}
|
||||
}
|
||||
|
||||
def msg = "test"
|
||||
def msgId = runWithSpan("parent") {
|
||||
producer.send(msg)
|
||||
}
|
||||
|
||||
result.get(1, TimeUnit.MINUTES)
|
||||
|
||||
then:
|
||||
assertTraces(1) {
|
||||
trace(0, 4) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
}
|
||||
producerSpan(it, 1, span(0), topic, msgId)
|
||||
receiveSpan(it, 2, span(1), topic, msgId)
|
||||
span(3) {
|
||||
name "callback"
|
||||
kind INTERNAL
|
||||
childOf span(2)
|
||||
attributes {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test consume non-partitioned topic using receive with timeout"() {
|
||||
setup:
|
||||
def topic = "persistent://public/default/testConsumeNonPartitionedTopicCallReceiveWithTimeout"
|
||||
admin.topics().createNonPartitionedTopic(topic)
|
||||
consumer = client.newConsumer(Schema.STRING)
|
||||
.subscriptionName("test_sub")
|
||||
.topic(topic)
|
||||
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
|
||||
.subscribe()
|
||||
|
||||
producer = client.newProducer(Schema.STRING)
|
||||
.topic(topic)
|
||||
.enableBatching(false)
|
||||
.create()
|
||||
|
||||
when:
|
||||
def msg = "test"
|
||||
def msgId = runWithSpan("parent") {
|
||||
producer.send(msg)
|
||||
}
|
||||
|
||||
def receivedMsg = consumer.receive(1, TimeUnit.MINUTES)
|
||||
consumer.acknowledge(receivedMsg)
|
||||
|
||||
then:
|
||||
assertTraces(1) {
|
||||
trace(0, 3) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
}
|
||||
producerSpan(it, 1, span(0), topic, msgId)
|
||||
receiveSpan(it, 2, span(1), topic, msgId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test consume non-partitioned topic using batchReceive"() {
|
||||
setup:
|
||||
def topic = "persistent://public/default/testConsumeNonPartitionedTopicCallBatchReceive"
|
||||
admin.topics().createNonPartitionedTopic(topic)
|
||||
consumer = client.newConsumer(Schema.STRING)
|
||||
.subscriptionName("test_sub")
|
||||
.topic(topic)
|
||||
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
|
||||
.subscribe()
|
||||
|
||||
producer = client.newProducer(Schema.STRING)
|
||||
.topic(topic)
|
||||
.enableBatching(false)
|
||||
.create()
|
||||
|
||||
when:
|
||||
def msg = "test"
|
||||
def msgId = runWithSpan("parent") {
|
||||
producer.send(msg)
|
||||
}
|
||||
|
||||
runWithSpan("receive-parent") {
|
||||
def receivedMsg = consumer.batchReceive()
|
||||
consumer.acknowledge(receivedMsg)
|
||||
}
|
||||
|
||||
then:
|
||||
def producer
|
||||
assertTraces(2) {
|
||||
trace(0, 2) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
}
|
||||
producerSpan(it, 1, span(0), topic, msgId)
|
||||
producer = span(1)
|
||||
}
|
||||
trace(1, 2) {
|
||||
span(0) {
|
||||
name "receive-parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
}
|
||||
receiveSpan(it, 1, span(0), topic, null, producer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test consume non-partitioned topic using batchReceiveAsync"() {
|
||||
setup:
|
||||
def topic = "persistent://public/default/testConsumeNonPartitionedTopicCallBatchReceiveAsync"
|
||||
admin.topics().createNonPartitionedTopic(topic)
|
||||
consumer = client.newConsumer(Schema.STRING)
|
||||
.subscriptionName("test_sub")
|
||||
.topic(topic)
|
||||
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
|
||||
.subscribe()
|
||||
|
||||
producer = client.newProducer(Schema.STRING)
|
||||
.topic(topic)
|
||||
.enableBatching(false)
|
||||
.create()
|
||||
|
||||
when:
|
||||
def msg = "test"
|
||||
def msgId = runWithSpan("parent") {
|
||||
producer.send(msg)
|
||||
}
|
||||
|
||||
CompletableFuture<Messages<String>> result = runWithSpan("receive-parent") {
|
||||
consumer.batchReceiveAsync().whenComplete { receivedMsg, throwable ->
|
||||
runWithSpan("callback") {
|
||||
consumer.acknowledge(receivedMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
result.get(1, TimeUnit.MINUTES).size() == 1
|
||||
|
||||
then:
|
||||
def producer
|
||||
assertTraces(2) {
|
||||
trace(0, 2) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
}
|
||||
producerSpan(it, 1, span(0), topic, msgId)
|
||||
producer = span(1)
|
||||
}
|
||||
trace(1, 3) {
|
||||
span(0) {
|
||||
name "receive-parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
}
|
||||
receiveSpan(it, 1, span(0), topic, null, producer)
|
||||
span(2) {
|
||||
name "callback"
|
||||
kind INTERNAL
|
||||
childOf span(1)
|
||||
attributes {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "capture message header as span attribute"() {
|
||||
setup:
|
||||
def topic = "persistent://public/default/testCaptureMessageHeaderTopic"
|
||||
def latch = new CountDownLatch(1)
|
||||
admin.topics().createNonPartitionedTopic(topic)
|
||||
consumer = client.newConsumer(Schema.STRING)
|
||||
.subscriptionName("test_sub")
|
||||
.topic(topic)
|
||||
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
|
||||
.messageListener(new MessageListener<String>() {
|
||||
@Override
|
||||
void received(Consumer<String> consumer, Message<String> msg) {
|
||||
consumer.acknowledge(msg)
|
||||
latch.countDown()
|
||||
}
|
||||
})
|
||||
.subscribe()
|
||||
|
||||
producer = client.newProducer(Schema.STRING)
|
||||
.topic(topic)
|
||||
.enableBatching(false)
|
||||
.create()
|
||||
|
||||
when:
|
||||
def msg = "test"
|
||||
def msgId = runWithSpan("parent") {
|
||||
producer.newMessage().value(msg).property("test-message-header", "test").send()
|
||||
}
|
||||
|
||||
latch.await(1, TimeUnit.MINUTES)
|
||||
|
||||
then:
|
||||
assertTraces(1) {
|
||||
trace(0, 4) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
}
|
||||
producerSpan(it, 1, span(0), topic, msgId, true)
|
||||
receiveSpan(it, 2, span(1), topic, msgId, null, true)
|
||||
processSpan(it, 3, span(2), topic, msgId, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test send partitioned topic"() {
|
||||
setup:
|
||||
def topic = "persistent://public/default/testSendPartitionedTopic"
|
||||
admin.topics().createPartitionedTopic(topic, 2)
|
||||
producer =
|
||||
client.newProducer(Schema.STRING).topic(topic)
|
||||
.enableBatching(false).create()
|
||||
|
||||
when:
|
||||
String msg = "test"
|
||||
def msgId = runWithSpan("parent") {
|
||||
producer.send(msg)
|
||||
}
|
||||
|
||||
then:
|
||||
assertTraces(1) {
|
||||
trace(0, 2) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
}
|
||||
producerSpan(it, 1, span(0), topic, ~/${topic}-partition-.*publish/, { it.startsWith(topic) }, msgId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test consume partitioned topic"() {
|
||||
setup:
|
||||
def topic = "persistent://public/default/testConsumePartitionedTopic"
|
||||
admin.topics().createPartitionedTopic(topic, 2)
|
||||
|
||||
def latch = new CountDownLatch(1)
|
||||
consumer = client.newConsumer(Schema.STRING)
|
||||
.subscriptionName("test_sub")
|
||||
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
|
||||
.topic(topic)
|
||||
.messageListener(new MessageListener<String>() {
|
||||
@Override
|
||||
void received(Consumer<String> consumer, Message<String> msg) {
|
||||
consumer.acknowledge(msg)
|
||||
latch.countDown()
|
||||
}
|
||||
})
|
||||
.subscribe()
|
||||
|
||||
producer = client.newProducer(Schema.STRING)
|
||||
.topic(topic)
|
||||
.enableBatching(false)
|
||||
.create()
|
||||
|
||||
when:
|
||||
def msg = "test"
|
||||
def msgId = runWithSpan("parent") {
|
||||
producer.send(msg)
|
||||
}
|
||||
|
||||
latch.await(1, TimeUnit.MINUTES)
|
||||
|
||||
then:
|
||||
assertTraces(1) {
|
||||
trace(0, 4) {
|
||||
span(0) {
|
||||
name "parent"
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
}
|
||||
producerSpan(it, 1, span(0), topic, ~/${topic}-partition-.*publish/, { it.startsWith(topic) }, msgId)
|
||||
receiveSpan(it, 2, span(1), topic, ~/${topic}-partition-.*receive/, { it.startsWith(topic) }, msgId)
|
||||
processSpan(it, 3, span(2), topic, ~/${topic}-partition-.*process/, { it.startsWith(topic) }, msgId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def "test consume multi-topics"() {
|
||||
setup:
|
||||
|
||||
def topicNamePrefix = "persistent://public/default/testConsumeMulti_"
|
||||
def topic1 = topicNamePrefix + "1"
|
||||
def topic2 = topicNamePrefix + "2"
|
||||
|
||||
def latch = new CountDownLatch(2)
|
||||
producer = client.newProducer(Schema.STRING)
|
||||
.topic(topic1)
|
||||
.enableBatching(false)
|
||||
.create()
|
||||
producer2 = client.newProducer(Schema.STRING)
|
||||
.topic(topic2)
|
||||
.enableBatching(false)
|
||||
.create()
|
||||
|
||||
when:
|
||||
runWithSpan("parent1") {
|
||||
producer.send("test1")
|
||||
}
|
||||
runWithSpan("parent2") {
|
||||
producer2.send("test2")
|
||||
}
|
||||
|
||||
consumer = client.newConsumer(Schema.STRING)
|
||||
.topic(topic2, topic1)
|
||||
.subscriptionName("test_sub")
|
||||
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
|
||||
.messageListener(new MessageListener<String>() {
|
||||
@Override
|
||||
void received(Consumer<String> consumer, Message<String> msg) {
|
||||
consumer.acknowledge(msg)
|
||||
latch.countDown()
|
||||
}
|
||||
})
|
||||
.subscribe()
|
||||
|
||||
latch.await(1, TimeUnit.MINUTES)
|
||||
|
||||
then:
|
||||
assertTraces(2) {
|
||||
traces.sort(orderByRootSpanName("parent1", "parent2"))
|
||||
for (int i in 1..2) {
|
||||
def topic = i == 1 ? topic1 : topic2
|
||||
trace(i - 1, 4) {
|
||||
span(0) {
|
||||
name "parent" + i
|
||||
kind INTERNAL
|
||||
hasNoParent()
|
||||
}
|
||||
producerSpan(it, 1, span(0), topic, null, { it.startsWith(topicNamePrefix) }, String)
|
||||
receiveSpan(it, 2, span(1), topic, null, { it.startsWith(topicNamePrefix) }, String)
|
||||
processSpan(it, 3, span(2), topic, null, { it.startsWith(topicNamePrefix) }, String)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def producerSpan(TraceAssert trace, int index, Object parentSpan, String topic, Object msgId, boolean headers = false) {
|
||||
producerSpan(trace, index, parentSpan, topic, null, { it == topic }, msgId, headers)
|
||||
}
|
||||
|
||||
def producerSpan(TraceAssert trace, int index, Object parentSpan, String topic, Pattern namePattern, Closure destination, Object msgId, boolean headers = false) {
|
||||
trace.span(index) {
|
||||
if (namePattern != null) {
|
||||
name namePattern
|
||||
} else {
|
||||
name "$topic publish"
|
||||
}
|
||||
kind PRODUCER
|
||||
childOf parentSpan
|
||||
attributes {
|
||||
"$SemanticAttributes.MESSAGING_SYSTEM" "pulsar"
|
||||
"$SemanticAttributes.SERVER_ADDRESS" brokerHost
|
||||
"$SemanticAttributes.SERVER_PORT" brokerPort
|
||||
"$SemanticAttributes.MESSAGING_DESTINATION_NAME" destination
|
||||
"$SemanticAttributes.MESSAGING_OPERATION" "publish"
|
||||
if (msgId == String) {
|
||||
"$SemanticAttributes.MESSAGING_MESSAGE_ID" String
|
||||
} else if (msgId != null) {
|
||||
"$SemanticAttributes.MESSAGING_MESSAGE_ID" msgId.toString()
|
||||
}
|
||||
"$SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES" Long
|
||||
"messaging.pulsar.message.type" "normal"
|
||||
if (headers) {
|
||||
"messaging.header.test_message_header" { it == ["test"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def receiveSpan(TraceAssert trace, int index, Object parentSpan, String topic, Object msgId, Object linkedSpan = null, boolean headers = false) {
|
||||
receiveSpan(trace, index, parentSpan, topic, null, { it == topic }, msgId, linkedSpan, headers)
|
||||
}
|
||||
|
||||
def receiveSpan(TraceAssert trace, int index, Object parentSpan, String topic, Pattern namePattern, Closure destination, Object msgId, Object linkedSpan = null, boolean headers = false) {
|
||||
trace.span(index) {
|
||||
if (namePattern != null) {
|
||||
name namePattern
|
||||
} else {
|
||||
name "$topic receive"
|
||||
}
|
||||
kind CONSUMER
|
||||
childOf parentSpan
|
||||
if (linkedSpan == null) {
|
||||
hasNoLinks()
|
||||
} else {
|
||||
hasLink linkedSpan
|
||||
}
|
||||
attributes {
|
||||
"$SemanticAttributes.MESSAGING_SYSTEM" "pulsar"
|
||||
"$SemanticAttributes.SERVER_ADDRESS" brokerHost
|
||||
"$SemanticAttributes.SERVER_PORT" brokerPort
|
||||
"$SemanticAttributes.MESSAGING_DESTINATION_NAME" destination
|
||||
if (msgId == String) {
|
||||
"$SemanticAttributes.MESSAGING_MESSAGE_ID" String
|
||||
} else if (msgId != null) {
|
||||
"$SemanticAttributes.MESSAGING_MESSAGE_ID" msgId.toString()
|
||||
}
|
||||
"$SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES" Long
|
||||
"$SemanticAttributes.MESSAGING_OPERATION" "receive"
|
||||
if (headers) {
|
||||
"messaging.header.test_message_header" { it == ["test"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def processSpan(TraceAssert trace, int index, Object parentSpan, String topic, Object msgId, boolean headers = false) {
|
||||
processSpan(trace, index, parentSpan, topic, null, { it == topic }, msgId, headers)
|
||||
}
|
||||
|
||||
def processSpan(TraceAssert trace, int index, Object parentSpan, String topic, Pattern namePattern, Closure destination, Object msgId, boolean headers = false) {
|
||||
trace.span(index) {
|
||||
if (namePattern != null) {
|
||||
name namePattern
|
||||
} else {
|
||||
name "$topic process"
|
||||
}
|
||||
kind INTERNAL
|
||||
childOf parentSpan
|
||||
attributes {
|
||||
"$SemanticAttributes.MESSAGING_SYSTEM" "pulsar"
|
||||
"$SemanticAttributes.MESSAGING_DESTINATION_NAME" destination
|
||||
if (msgId == String) {
|
||||
"$SemanticAttributes.MESSAGING_MESSAGE_ID" String
|
||||
} else if (msgId != null) {
|
||||
"$SemanticAttributes.MESSAGING_MESSAGE_ID" msgId.toString()
|
||||
}
|
||||
"$SemanticAttributes.MESSAGING_OPERATION" "process"
|
||||
"$SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES" Long
|
||||
if (headers) {
|
||||
"messaging.header.test_message_header" { it == ["test"] }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,699 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.javaagent.instrumentation.pulsar.v2_8;
|
||||
|
||||
import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanName;
|
||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
|
||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.satisfies;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import io.opentelemetry.api.common.AttributeKey;
|
||||
import io.opentelemetry.api.trace.SpanKind;
|
||||
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
|
||||
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
|
||||
import io.opentelemetry.sdk.testing.assertj.AttributeAssertion;
|
||||
import io.opentelemetry.sdk.trace.data.LinkData;
|
||||
import io.opentelemetry.sdk.trace.data.SpanData;
|
||||
import io.opentelemetry.semconv.SemanticAttributes;
|
||||
import java.time.Duration;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import org.apache.pulsar.client.admin.PulsarAdmin;
|
||||
import org.apache.pulsar.client.api.Consumer;
|
||||
import org.apache.pulsar.client.api.Message;
|
||||
import org.apache.pulsar.client.api.MessageId;
|
||||
import org.apache.pulsar.client.api.MessageListener;
|
||||
import org.apache.pulsar.client.api.Messages;
|
||||
import org.apache.pulsar.client.api.Producer;
|
||||
import org.apache.pulsar.client.api.PulsarClient;
|
||||
import org.apache.pulsar.client.api.PulsarClientException;
|
||||
import org.apache.pulsar.client.api.Schema;
|
||||
import org.apache.pulsar.client.api.SubscriptionInitialPosition;
|
||||
import org.assertj.core.api.AbstractLongAssert;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.testcontainers.containers.PulsarContainer;
|
||||
import org.testcontainers.containers.output.Slf4jLogConsumer;
|
||||
import org.testcontainers.utility.DockerImageName;
|
||||
|
||||
class PulsarClientTest {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(PulsarClientTest.class);
|
||||
|
||||
private static final DockerImageName DEFAULT_IMAGE_NAME =
|
||||
DockerImageName.parse("apachepulsar/pulsar:2.8.0");
|
||||
|
||||
@RegisterExtension
|
||||
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
|
||||
|
||||
private static PulsarContainer pulsar;
|
||||
private static PulsarClient client;
|
||||
private static PulsarAdmin admin;
|
||||
private static Producer<String> producer;
|
||||
private static Consumer<String> consumer;
|
||||
private static Producer<String> producer2;
|
||||
|
||||
private static String brokerHost;
|
||||
private static int brokerPort;
|
||||
|
||||
private static final AttributeKey<String> MESSAGE_TYPE =
|
||||
AttributeKey.stringKey("messaging.pulsar.message.type");
|
||||
|
||||
@BeforeAll
|
||||
static void beforeAll() throws PulsarClientException {
|
||||
pulsar =
|
||||
new PulsarContainer(DEFAULT_IMAGE_NAME)
|
||||
.withEnv("PULSAR_MEM", "-Xmx128m")
|
||||
.withLogConsumer(new Slf4jLogConsumer(logger))
|
||||
.withStartupTimeout(Duration.ofMinutes(2));
|
||||
pulsar.start();
|
||||
|
||||
brokerHost = pulsar.getHost();
|
||||
brokerPort = pulsar.getMappedPort(6650);
|
||||
client = PulsarClient.builder().serviceUrl(pulsar.getPulsarBrokerUrl()).build();
|
||||
admin = PulsarAdmin.builder().serviceHttpUrl(pulsar.getHttpServiceUrl()).build();
|
||||
}
|
||||
|
||||
@AfterAll
|
||||
static void afterAll() throws PulsarClientException {
|
||||
if (producer != null) {
|
||||
producer.close();
|
||||
}
|
||||
if (consumer != null) {
|
||||
consumer.close();
|
||||
}
|
||||
if (producer2 != null) {
|
||||
producer2.close();
|
||||
}
|
||||
if (client != null) {
|
||||
client.close();
|
||||
}
|
||||
if (admin != null) {
|
||||
admin.close();
|
||||
}
|
||||
pulsar.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSendNonPartitionedTopic() throws Exception {
|
||||
String topic = "persistent://public/default/testSendNonPartitionedTopic";
|
||||
admin.topics().createNonPartitionedTopic(topic);
|
||||
producer = client.newProducer(Schema.STRING).topic(topic).enableBatching(false).create();
|
||||
|
||||
String msg = "test";
|
||||
MessageId msgId = testing.runWithSpan("parent", () -> producer.send(msg));
|
||||
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
|
||||
span ->
|
||||
span.hasName(topic + " publish")
|
||||
.hasKind(SpanKind.PRODUCER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
sendAttributes(topic, msgId.toString(), false))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConsumeNonPartitionedTopic() throws Exception {
|
||||
String topic = "persistent://public/default/testConsumeNonPartitionedTopic";
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
admin.topics().createNonPartitionedTopic(topic);
|
||||
consumer =
|
||||
client
|
||||
.newConsumer(Schema.STRING)
|
||||
.subscriptionName("test_sub")
|
||||
.topic(topic)
|
||||
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
|
||||
.messageListener(
|
||||
(MessageListener<String>)
|
||||
(consumer, msg) -> {
|
||||
acknowledgeMessage(consumer, msg);
|
||||
latch.countDown();
|
||||
})
|
||||
.subscribe();
|
||||
|
||||
producer = client.newProducer(Schema.STRING).topic(topic).enableBatching(false).create();
|
||||
|
||||
String msg = "test";
|
||||
MessageId msgId = testing.runWithSpan("parent", () -> producer.send(msg));
|
||||
|
||||
latch.await(1, TimeUnit.MINUTES);
|
||||
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
|
||||
span ->
|
||||
span.hasName(topic + " publish")
|
||||
.hasKind(SpanKind.PRODUCER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
sendAttributes(topic, msgId.toString(), false)),
|
||||
span ->
|
||||
span.hasName(topic + " receive")
|
||||
.hasKind(SpanKind.CONSUMER)
|
||||
.hasParent(trace.getSpan(1))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
receiveAttributes(topic, msgId.toString(), false)),
|
||||
span ->
|
||||
span.hasName(topic + " process")
|
||||
.hasKind(SpanKind.INTERNAL)
|
||||
.hasParent(trace.getSpan(2))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
processAttributes(topic, msgId.toString(), false))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConsumeNonPartitionedTopicUsingReceive() throws Exception {
|
||||
String topic = "persistent://public/default/testConsumeNonPartitionedTopicCallReceive";
|
||||
admin.topics().createNonPartitionedTopic(topic);
|
||||
consumer =
|
||||
client
|
||||
.newConsumer(Schema.STRING)
|
||||
.subscriptionName("test_sub")
|
||||
.topic(topic)
|
||||
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
|
||||
.subscribe();
|
||||
producer = client.newProducer(Schema.STRING).topic(topic).enableBatching(false).create();
|
||||
|
||||
String msg = "test";
|
||||
MessageId msgId = testing.runWithSpan("parent", () -> producer.send(msg));
|
||||
|
||||
Message<String> receivedMsg = consumer.receive();
|
||||
consumer.acknowledge(receivedMsg);
|
||||
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
|
||||
span ->
|
||||
span.hasName(topic + " publish")
|
||||
.hasKind(SpanKind.PRODUCER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
sendAttributes(topic, msgId.toString(), false)),
|
||||
span ->
|
||||
span.hasName(topic + " receive")
|
||||
.hasKind(SpanKind.CONSUMER)
|
||||
.hasParent(trace.getSpan(1))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
receiveAttributes(topic, msgId.toString(), false))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConsumeNonPartitionedTopicUsingReceiveAsync() throws Exception {
|
||||
String topic = "persistent://public/default/testConsumeNonPartitionedTopicCallReceiveAsync";
|
||||
admin.topics().createNonPartitionedTopic(topic);
|
||||
consumer =
|
||||
client
|
||||
.newConsumer(Schema.STRING)
|
||||
.subscriptionName("test_sub")
|
||||
.topic(topic)
|
||||
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
|
||||
.subscribe();
|
||||
|
||||
producer = client.newProducer(Schema.STRING).topic(topic).enableBatching(false).create();
|
||||
|
||||
CompletableFuture<Message<String>> result =
|
||||
consumer
|
||||
.receiveAsync()
|
||||
.whenComplete(
|
||||
(message, throwable) -> {
|
||||
if (message != null) {
|
||||
testing.runWithSpan("callback", () -> acknowledgeMessage(consumer, message));
|
||||
}
|
||||
});
|
||||
|
||||
String msg = "test";
|
||||
MessageId msgId = testing.runWithSpan("parent", () -> producer.send(msg));
|
||||
|
||||
result.get(1, TimeUnit.MINUTES);
|
||||
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
|
||||
span ->
|
||||
span.hasName(topic + " publish")
|
||||
.hasKind(SpanKind.PRODUCER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
sendAttributes(topic, msgId.toString(), false)),
|
||||
span ->
|
||||
span.hasName(topic + " receive")
|
||||
.hasKind(SpanKind.CONSUMER)
|
||||
.hasParent(trace.getSpan(1))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
receiveAttributes(topic, msgId.toString(), false)),
|
||||
span ->
|
||||
span.hasName("callback")
|
||||
.hasKind(SpanKind.INTERNAL)
|
||||
.hasParent(trace.getSpan(2))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConsumeNonPartitionedTopicUsingReceiveWithTimeout() throws Exception {
|
||||
String topic =
|
||||
"persistent://public/default/testConsumeNonPartitionedTopicCallReceiveWithTimeout";
|
||||
admin.topics().createNonPartitionedTopic(topic);
|
||||
consumer =
|
||||
client
|
||||
.newConsumer(Schema.STRING)
|
||||
.subscriptionName("test_sub")
|
||||
.topic(topic)
|
||||
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
|
||||
.subscribe();
|
||||
producer = client.newProducer(Schema.STRING).topic(topic).enableBatching(false).create();
|
||||
|
||||
String msg = "test";
|
||||
MessageId msgId = testing.runWithSpan("parent", () -> producer.send(msg));
|
||||
|
||||
Message<String> receivedMsg = consumer.receive(1, TimeUnit.MINUTES);
|
||||
consumer.acknowledge(receivedMsg);
|
||||
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
|
||||
span ->
|
||||
span.hasName(topic + " publish")
|
||||
.hasKind(SpanKind.PRODUCER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
sendAttributes(topic, msgId.toString(), false)),
|
||||
span ->
|
||||
span.hasName(topic + " receive")
|
||||
.hasKind(SpanKind.CONSUMER)
|
||||
.hasParent(trace.getSpan(1))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
receiveAttributes(topic, msgId.toString(), false))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConsumeNonPartitionedTopicUsingBatchReceive() throws Exception {
|
||||
String topic = "persistent://public/default/testConsumeNonPartitionedTopicCallBatchReceive";
|
||||
admin.topics().createNonPartitionedTopic(topic);
|
||||
consumer =
|
||||
client
|
||||
.newConsumer(Schema.STRING)
|
||||
.subscriptionName("test_sub")
|
||||
.topic(topic)
|
||||
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
|
||||
.subscribe();
|
||||
|
||||
producer = client.newProducer(Schema.STRING).topic(topic).enableBatching(false).create();
|
||||
|
||||
String msg = "test";
|
||||
MessageId msgId = testing.runWithSpan("parent", () -> producer.send(msg));
|
||||
|
||||
testing.runWithSpan(
|
||||
"receive-parent",
|
||||
() -> {
|
||||
Messages<String> receivedMsg = consumer.batchReceive();
|
||||
consumer.acknowledge(receivedMsg);
|
||||
});
|
||||
AtomicReference<SpanData> producerSpan = new AtomicReference<>();
|
||||
|
||||
testing.waitAndAssertTraces(
|
||||
trace -> {
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
|
||||
span ->
|
||||
span.hasName(topic + " publish")
|
||||
.hasKind(SpanKind.PRODUCER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
sendAttributes(topic, msgId.toString(), false)));
|
||||
producerSpan.set(trace.getSpan(1));
|
||||
},
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("receive-parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
|
||||
span ->
|
||||
span.hasName(topic + " receive")
|
||||
.hasKind(SpanKind.CONSUMER)
|
||||
.hasLinks(LinkData.create(producerSpan.get().getSpanContext()))
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(receiveAttributes(topic, null, false))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConsumeNonPartitionedTopicUsingBatchReceiveAsync() throws Exception {
|
||||
String topic =
|
||||
"persistent://public/default/testConsumeNonPartitionedTopicCallBatchReceiveAsync";
|
||||
admin.topics().createNonPartitionedTopic(topic);
|
||||
consumer =
|
||||
client
|
||||
.newConsumer(Schema.STRING)
|
||||
.subscriptionName("test_sub")
|
||||
.topic(topic)
|
||||
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
|
||||
.subscribe();
|
||||
|
||||
producer = client.newProducer(Schema.STRING).topic(topic).enableBatching(false).create();
|
||||
|
||||
String msg = "test";
|
||||
MessageId msgId = testing.runWithSpan("parent", () -> producer.send(msg));
|
||||
|
||||
CompletableFuture<Messages<String>> result =
|
||||
testing.runWithSpan(
|
||||
"receive-parent",
|
||||
() ->
|
||||
consumer
|
||||
.batchReceiveAsync()
|
||||
.whenComplete(
|
||||
(messages, throwable) -> {
|
||||
if (messages != null) {
|
||||
testing.runWithSpan(
|
||||
"callback", () -> acknowledgeMessages(consumer, messages));
|
||||
}
|
||||
}));
|
||||
|
||||
assertThat(result.get(1, TimeUnit.MINUTES).size()).isEqualTo(1);
|
||||
|
||||
AtomicReference<SpanData> producerSpan = new AtomicReference<>();
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
|
||||
span -> {
|
||||
span.hasName(topic + " publish")
|
||||
.hasKind(SpanKind.PRODUCER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
sendAttributes(topic, msgId.toString(), false));
|
||||
producerSpan.set(trace.getSpan(1));
|
||||
}),
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("receive-parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
|
||||
span ->
|
||||
span.hasName(topic + " receive")
|
||||
.hasKind(SpanKind.CONSUMER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasLinks(LinkData.create(producerSpan.get().getSpanContext()))
|
||||
.hasAttributesSatisfyingExactly(receiveAttributes(topic, null, false)),
|
||||
span ->
|
||||
span.hasName("callback")
|
||||
.hasKind(SpanKind.INTERNAL)
|
||||
.hasParent(trace.getSpan(1))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void captureMessageHeaderAsSpanAttribute() throws Exception {
|
||||
String topic = "persistent://public/default/testCaptureMessageHeaderTopic";
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
admin.topics().createNonPartitionedTopic(topic);
|
||||
consumer =
|
||||
client
|
||||
.newConsumer(Schema.STRING)
|
||||
.subscriptionName("test_sub")
|
||||
.topic(topic)
|
||||
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
|
||||
.messageListener(
|
||||
(MessageListener<String>)
|
||||
(consumer, msg) -> {
|
||||
acknowledgeMessage(consumer, msg);
|
||||
latch.countDown();
|
||||
})
|
||||
.subscribe();
|
||||
|
||||
producer = client.newProducer(Schema.STRING).topic(topic).enableBatching(false).create();
|
||||
|
||||
String msg = "test";
|
||||
MessageId msgId =
|
||||
testing.runWithSpan(
|
||||
"parent",
|
||||
() -> producer.newMessage().value(msg).property("test-message-header", "test").send());
|
||||
|
||||
latch.await(1, TimeUnit.MINUTES);
|
||||
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
|
||||
span ->
|
||||
span.hasName(topic + " publish")
|
||||
.hasKind(SpanKind.PRODUCER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
sendAttributes(topic, msgId.toString(), true)),
|
||||
span ->
|
||||
span.hasName(topic + " receive")
|
||||
.hasKind(SpanKind.CONSUMER)
|
||||
.hasParent(trace.getSpan(1))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
receiveAttributes(topic, msgId.toString(), true)),
|
||||
span ->
|
||||
span.hasName(topic + " process")
|
||||
.hasKind(SpanKind.INTERNAL)
|
||||
.hasParent(trace.getSpan(2))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
processAttributes(topic, msgId.toString(), true))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testSendPartitionedTopic() throws Exception {
|
||||
String topic = "persistent://public/default/testSendPartitionedTopic";
|
||||
admin.topics().createPartitionedTopic(topic, 1);
|
||||
producer = client.newProducer(Schema.STRING).topic(topic).enableBatching(false).create();
|
||||
|
||||
String msg = "test";
|
||||
MessageId msgId = testing.runWithSpan("parent", () -> producer.send(msg));
|
||||
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
|
||||
span ->
|
||||
span.hasName(topic + "-partition-0 publish")
|
||||
.hasKind(SpanKind.PRODUCER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
sendAttributes(topic + "-partition-0", msgId.toString(), false))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConsumePartitionedTopic() throws Exception {
|
||||
String topic = "persistent://public/default/testConsumePartitionedTopic";
|
||||
admin.topics().createPartitionedTopic(topic, 1);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
|
||||
consumer =
|
||||
client
|
||||
.newConsumer(Schema.STRING)
|
||||
.subscriptionName("test_sub")
|
||||
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
|
||||
.topic(topic)
|
||||
.messageListener(
|
||||
(MessageListener<String>)
|
||||
(consumer, msg) -> {
|
||||
acknowledgeMessage(consumer, msg);
|
||||
latch.countDown();
|
||||
})
|
||||
.subscribe();
|
||||
|
||||
producer = client.newProducer(Schema.STRING).topic(topic).enableBatching(false).create();
|
||||
|
||||
String msg = "test";
|
||||
MessageId msgId = testing.runWithSpan("parent", () -> producer.send(msg));
|
||||
|
||||
latch.await(1, TimeUnit.MINUTES);
|
||||
|
||||
testing.waitAndAssertTraces(
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
|
||||
span ->
|
||||
span.hasName(topic + "-partition-0 publish")
|
||||
.hasKind(SpanKind.PRODUCER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
sendAttributes(topic + "-partition-0", msgId.toString(), false)),
|
||||
span ->
|
||||
span.hasName(topic + "-partition-0 receive")
|
||||
.hasKind(SpanKind.CONSUMER)
|
||||
.hasParent(trace.getSpan(1))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
receiveAttributes(topic + "-partition-0", msgId.toString(), false)),
|
||||
span ->
|
||||
span.hasName(topic + "-partition-0 process")
|
||||
.hasKind(SpanKind.INTERNAL)
|
||||
.hasParent(trace.getSpan(2))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
processAttributes(topic + "-partition-0", msgId.toString(), false))));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testConsumeMultiTopics() throws Exception {
|
||||
String topicNamePrefix = "persistent://public/default/testConsumeMulti_";
|
||||
String topic1 = topicNamePrefix + "1";
|
||||
String topic2 = topicNamePrefix + "2";
|
||||
CountDownLatch latch = new CountDownLatch(2);
|
||||
producer = client.newProducer(Schema.STRING).topic(topic1).enableBatching(false).create();
|
||||
producer2 = client.newProducer(Schema.STRING).topic(topic2).enableBatching(false).create();
|
||||
|
||||
MessageId msgId1 = testing.runWithSpan("parent1", () -> producer.send("test1"));
|
||||
MessageId msgId2 = testing.runWithSpan("parent2", () -> producer2.send("test2"));
|
||||
|
||||
consumer =
|
||||
client
|
||||
.newConsumer(Schema.STRING)
|
||||
.topic(topic2, topic1)
|
||||
.subscriptionName("test_sub")
|
||||
.subscriptionInitialPosition(SubscriptionInitialPosition.Earliest)
|
||||
.messageListener(
|
||||
(MessageListener<String>)
|
||||
(consumer, msg) -> {
|
||||
acknowledgeMessage(consumer, msg);
|
||||
latch.countDown();
|
||||
})
|
||||
.subscribe();
|
||||
|
||||
latch.await(1, TimeUnit.MINUTES);
|
||||
|
||||
testing.waitAndAssertSortedTraces(
|
||||
orderByRootSpanName("parent1", "parent2"),
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("parent1").hasKind(SpanKind.INTERNAL).hasNoParent(),
|
||||
span ->
|
||||
span.hasName(topic1 + " publish")
|
||||
.hasKind(SpanKind.PRODUCER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
sendAttributes(topic1, msgId1.toString(), false)),
|
||||
span ->
|
||||
span.hasName(topic1 + " receive")
|
||||
.hasKind(SpanKind.CONSUMER)
|
||||
.hasParent(trace.getSpan(1))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
receiveAttributes(topic1, msgId1.toString(), false)),
|
||||
span ->
|
||||
span.hasName(topic1 + " process")
|
||||
.hasKind(SpanKind.INTERNAL)
|
||||
.hasParent(trace.getSpan(2))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
processAttributes(topic1, msgId1.toString(), false))),
|
||||
trace ->
|
||||
trace.hasSpansSatisfyingExactly(
|
||||
span -> span.hasName("parent2").hasKind(SpanKind.INTERNAL).hasNoParent(),
|
||||
span ->
|
||||
span.hasName(topic2 + " publish")
|
||||
.hasKind(SpanKind.PRODUCER)
|
||||
.hasParent(trace.getSpan(0))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
sendAttributes(topic2, msgId2.toString(), false)),
|
||||
span ->
|
||||
span.hasName(topic2 + " receive")
|
||||
.hasKind(SpanKind.CONSUMER)
|
||||
.hasParent(trace.getSpan(1))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
receiveAttributes(topic2, msgId2.toString(), false)),
|
||||
span ->
|
||||
span.hasName(topic2 + " process")
|
||||
.hasKind(SpanKind.INTERNAL)
|
||||
.hasParent(trace.getSpan(2))
|
||||
.hasAttributesSatisfyingExactly(
|
||||
processAttributes(topic2, msgId2.toString(), false))));
|
||||
}
|
||||
|
||||
private static List<AttributeAssertion> sendAttributes(
|
||||
String destination, String messageId, boolean testHeaders) {
|
||||
List<AttributeAssertion> assertions =
|
||||
new ArrayList<>(
|
||||
Arrays.asList(
|
||||
equalTo(SemanticAttributes.MESSAGING_SYSTEM, "pulsar"),
|
||||
equalTo(SemanticAttributes.SERVER_ADDRESS, brokerHost),
|
||||
equalTo(SemanticAttributes.SERVER_PORT, brokerPort),
|
||||
equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, destination),
|
||||
equalTo(SemanticAttributes.MESSAGING_OPERATION, "publish"),
|
||||
equalTo(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId),
|
||||
satisfies(
|
||||
SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES,
|
||||
AbstractLongAssert::isNotNegative),
|
||||
equalTo(MESSAGE_TYPE, "normal")));
|
||||
if (testHeaders) {
|
||||
assertions.add(
|
||||
equalTo(
|
||||
AttributeKey.stringArrayKey("messaging.header.test_message_header"),
|
||||
Collections.singletonList("test")));
|
||||
}
|
||||
return assertions;
|
||||
}
|
||||
|
||||
private static List<AttributeAssertion> receiveAttributes(
|
||||
String destination, String messageId, boolean testHeaders) {
|
||||
List<AttributeAssertion> assertions =
|
||||
new ArrayList<>(
|
||||
Arrays.asList(
|
||||
equalTo(SemanticAttributes.MESSAGING_SYSTEM, "pulsar"),
|
||||
equalTo(SemanticAttributes.SERVER_ADDRESS, brokerHost),
|
||||
equalTo(SemanticAttributes.SERVER_PORT, brokerPort),
|
||||
equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, destination),
|
||||
equalTo(SemanticAttributes.MESSAGING_OPERATION, "receive"),
|
||||
equalTo(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId),
|
||||
satisfies(
|
||||
SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES,
|
||||
AbstractLongAssert::isNotNegative)));
|
||||
if (testHeaders) {
|
||||
assertions.add(
|
||||
equalTo(
|
||||
AttributeKey.stringArrayKey("messaging.header.test_message_header"),
|
||||
Collections.singletonList("test")));
|
||||
}
|
||||
return assertions;
|
||||
}
|
||||
|
||||
private static List<AttributeAssertion> processAttributes(
|
||||
String destination, String messageId, boolean testHeaders) {
|
||||
List<AttributeAssertion> assertions =
|
||||
new ArrayList<>(
|
||||
Arrays.asList(
|
||||
equalTo(SemanticAttributes.MESSAGING_SYSTEM, "pulsar"),
|
||||
equalTo(SemanticAttributes.MESSAGING_DESTINATION_NAME, destination),
|
||||
equalTo(SemanticAttributes.MESSAGING_OPERATION, "process"),
|
||||
equalTo(SemanticAttributes.MESSAGING_MESSAGE_ID, messageId),
|
||||
satisfies(
|
||||
SemanticAttributes.MESSAGING_MESSAGE_PAYLOAD_SIZE_BYTES,
|
||||
AbstractLongAssert::isNotNegative)));
|
||||
if (testHeaders) {
|
||||
assertions.add(
|
||||
equalTo(
|
||||
AttributeKey.stringArrayKey("messaging.header.test_message_header"),
|
||||
Collections.singletonList("test")));
|
||||
}
|
||||
return assertions;
|
||||
}
|
||||
|
||||
private static void acknowledgeMessage(Consumer<String> consumer, Message<String> message) {
|
||||
try {
|
||||
consumer.acknowledge(message);
|
||||
} catch (PulsarClientException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
private static void acknowledgeMessages(Consumer<String> consumer, Messages<String> messages) {
|
||||
try {
|
||||
consumer.acknowledge(messages);
|
||||
} catch (PulsarClientException exception) {
|
||||
throw new RuntimeException(exception);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue