From 44a2089c139328603662ca270cc80a4ad3743e3f Mon Sep 17 00:00:00 2001 From: Carsten Lohmann Date: Wed, 20 Dec 2023 13:39:55 +0100 Subject: [PATCH] [#3597] Add more generic AMQP trace context propagation format. Writing trace-context information in the AMQP message application-properties instead of the message-annotations, if isUseLegacyTraceContextFormat has been set to false. --- .../hono/client/amqp/GenericSenderLink.java | 5 +- .../client/amqp/RequestResponseClient.java | 5 +- .../amqp/config/ClientConfigProperties.java | 29 +++- .../client/amqp/config/ClientOptions.java | 11 +- .../client/amqp/connection/AmqpUtils.java | 24 ++-- .../tracing/AmqpMessageExtractAdapter.java | 106 +++++++++++++++ .../tracing/AmqpMessageInjectAdapter.java | 59 +++++++++ .../MessageAnnotationsExtractAdapter.java | 14 +- .../MessageAnnotationsInjectAdapter.java | 4 +- .../AmqpMessageInjectExtractAdapterTest.java | 124 ++++++++++++++++++ .../admin-guide/hono-client-configuration.md | 1 + site/homepage/content/release-notes.md | 6 + 12 files changed, 370 insertions(+), 18 deletions(-) create mode 100644 clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/tracing/AmqpMessageExtractAdapter.java create mode 100644 clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/tracing/AmqpMessageInjectAdapter.java create mode 100644 clients/amqp-connection/src/test/java/org/eclipse/hono/client/amqp/tracing/AmqpMessageInjectExtractAdapterTest.java diff --git a/clients/amqp-common/src/main/java/org/eclipse/hono/client/amqp/GenericSenderLink.java b/clients/amqp-common/src/main/java/org/eclipse/hono/client/amqp/GenericSenderLink.java index a24af57ecf..4e1680c1fc 100644 --- a/clients/amqp-common/src/main/java/org/eclipse/hono/client/amqp/GenericSenderLink.java +++ b/clients/amqp-common/src/main/java/org/eclipse/hono/client/amqp/GenericSenderLink.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2016 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -340,7 +340,8 @@ private Future checkForCreditAndSend( TracingHelper.TAG_QOS.set(currentSpan, sender.getQoS().toString()); Tags.SPAN_KIND.set(currentSpan, Tags.SPAN_KIND_PRODUCER); TracingHelper.setDeviceTags(currentSpan, tenantId, AmqpUtils.getDeviceId(message)); - AmqpUtils.injectSpanContext(connection.getTracer(), currentSpan.context(), message); + AmqpUtils.injectSpanContext(connection.getTracer(), currentSpan.context(), message, + connection.getConfig().isUseLegacyTraceContextFormat()); return connection.executeOnContext(result -> { if (sender.sendQueueFull()) { diff --git a/clients/amqp-common/src/main/java/org/eclipse/hono/client/amqp/RequestResponseClient.java b/clients/amqp-common/src/main/java/org/eclipse/hono/client/amqp/RequestResponseClient.java index ee0411b18e..06db4ae2f0 100644 --- a/clients/amqp-common/src/main/java/org/eclipse/hono/client/amqp/RequestResponseClient.java +++ b/clients/amqp-common/src/main/java/org/eclipse/hono/client/amqp/RequestResponseClient.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2016 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -638,7 +638,8 @@ private Future sendRequest( currentSpan.log(details); final TriTuple, BiFunction, Span> handler = TriTuple.of(res, responseMapper, currentSpan); - AmqpUtils.injectSpanContext(connection.getTracer(), currentSpan.context(), request); + AmqpUtils.injectSpanContext(connection.getTracer(), currentSpan.context(), request, + connection.getConfig().isUseLegacyTraceContextFormat()); replyMap.put(correlationId, handler); final SendMessageSampler.Sample sample = sampler.start(tenantId); diff --git a/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/config/ClientConfigProperties.java b/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/config/ClientConfigProperties.java index c4c128bee2..b013f30919 100644 --- a/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/config/ClientConfigProperties.java +++ b/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/config/ClientConfigProperties.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2016 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -90,6 +90,10 @@ public class ClientConfigProperties extends AuthenticatingClientConfigProperties * size. */ public static final long MIN_MAX_MESSAGE_SIZE_NONE = 0; + /** + * The default value for deciding whether to use the legacy trace context format. + */ + public static final boolean DEFAULT_USE_LEGACY_TRACE_CONTEXT_FORMAT = true; private Pattern addressRewritePattern; private String addressRewriteReplacement; @@ -111,6 +115,7 @@ public class ClientConfigProperties extends AuthenticatingClientConfigProperties private long reconnectDelayIncrement = DEFAULT_RECONNECT_DELAY_INCREMENT; private long requestTimeout = DEFAULT_REQUEST_TIMEOUT; private long sendMessageTimeout = DEFAULT_SEND_MESSAGE_TIMEOUT; + private boolean useLegacyTraceContextFormat = DEFAULT_USE_LEGACY_TRACE_CONTEXT_FORMAT; /** * Creates new properties using default values. @@ -147,6 +152,7 @@ public ClientConfigProperties(final ClientConfigProperties otherProperties) { this.reconnectDelayIncrement = otherProperties.reconnectDelayIncrement; this.requestTimeout = otherProperties.requestTimeout; this.sendMessageTimeout = otherProperties.sendMessageTimeout; + this.useLegacyTraceContextFormat = otherProperties.useLegacyTraceContextFormat; } /** @@ -174,6 +180,7 @@ public ClientConfigProperties(final ClientOptions options) { setReconnectDelayIncrement(options.reconnectDelayIncrement()); setRequestTimeout(options.requestTimeout()); setSendMessageTimeout(options.sendMessageTimeout()); + setUseLegacyTraceContextFormat(options.useLegacyTraceContextFormat()); } /** @@ -802,4 +809,24 @@ public final int getMaxSessionWindowSize() { return 0; } } + + /** + * Checks whether the legacy trace context format shall be used, writing to the message annotations instead of the + * application properties. + * + * @return {@code true} if the legacy format shall be used. + */ + public boolean isUseLegacyTraceContextFormat() { + return useLegacyTraceContextFormat; + } + + /** + * Sets whether the legacy trace context format shall be used, writing to the message annotations instead of the + * application properties. + * + * @param useLegacyTraceContextFormat {@code true} if the legacy format shall be used. + */ + public void setUseLegacyTraceContextFormat(final boolean useLegacyTraceContextFormat) { + this.useLegacyTraceContextFormat = useLegacyTraceContextFormat; + } } diff --git a/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/config/ClientOptions.java b/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/config/ClientOptions.java index 1c3b1bda0f..7ca85a35d1 100644 --- a/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/config/ClientOptions.java +++ b/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/config/ClientOptions.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2021, 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2021 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -217,4 +217,13 @@ public interface ClientOptions { */ @WithDefault("-1") int maxSessionFrames(); + + /** + * Checks whether the legacy trace context format shall be used, writing to the message annotations instead of the + * application properties. + * + * @return {@code true} if the legacy format shall be used. + */ + @WithDefault("true") + boolean useLegacyTraceContextFormat(); } diff --git a/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/connection/AmqpUtils.java b/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/connection/AmqpUtils.java index 8f03254682..7fbf4c0236 100644 --- a/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/connection/AmqpUtils.java +++ b/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/connection/AmqpUtils.java @@ -34,7 +34,8 @@ import org.eclipse.hono.auth.Authorities; import org.eclipse.hono.auth.HonoUser; import org.eclipse.hono.auth.HonoUserAdapter; -import org.eclipse.hono.client.amqp.tracing.MessageAnnotationsExtractAdapter; +import org.eclipse.hono.client.amqp.tracing.AmqpMessageExtractAdapter; +import org.eclipse.hono.client.amqp.tracing.AmqpMessageInjectAdapter; import org.eclipse.hono.client.amqp.tracing.MessageAnnotationsInjectAdapter; import org.eclipse.hono.util.CacheDirective; import org.eclipse.hono.util.CommandConstants; @@ -45,6 +46,7 @@ import io.opentracing.Tracer; import io.opentracing.noop.NoopSpanContext; import io.opentracing.propagation.Format; +import io.opentracing.propagation.TextMap; import io.vertx.core.buffer.Buffer; import io.vertx.core.json.JsonObject; import io.vertx.proton.ProtonConnection; @@ -117,7 +119,7 @@ public Authorities getAuthorities() { } }; - private static final String AMQP_ANNOTATION_NAME_TRACE_CONTEXT = "x-opt-trace-context"; + private static final String LEGACY_AMQP_ANNOTATION_NAME_TRACE_CONTEXT = "x-opt-trace-context"; private AmqpUtils() { // prevent instantiation @@ -170,28 +172,34 @@ public static void setClientPrincipal(final ProtonConnection con, final HonoUser /** * Injects a {@code SpanContext} into an AMQP {@code Message}. *

- * The span context will be written to the message annotations of the given message. + * The span context will be written either to the message annotations (if {@code useLegacyTraceContextFormat} is + * {@code true}) or the application properties of the given message. * * @param tracer The Tracer to use for injecting the context. * @param spanContext The context to inject or {@code null} if no context is available. * @param message The AMQP {@code Message} object to inject the context into. + * @param useLegacyTraceContextFormat If {@code true}, the legacy trace context format will be used (writing to + * a map in the message annotation properties). * @throws NullPointerException if tracer or message is {@code null}. */ - public static void injectSpanContext(final Tracer tracer, final SpanContext spanContext, final Message message) { + public static void injectSpanContext(final Tracer tracer, final SpanContext spanContext, final Message message, + final boolean useLegacyTraceContextFormat) { Objects.requireNonNull(tracer); Objects.requireNonNull(message); if (spanContext != null && !(spanContext instanceof NoopSpanContext)) { - tracer.inject(spanContext, Format.Builtin.TEXT_MAP, - new MessageAnnotationsInjectAdapter(message, AMQP_ANNOTATION_NAME_TRACE_CONTEXT)); + final TextMap injectAdapter = useLegacyTraceContextFormat + ? new MessageAnnotationsInjectAdapter(message, LEGACY_AMQP_ANNOTATION_NAME_TRACE_CONTEXT) + : new AmqpMessageInjectAdapter(message); + tracer.inject(spanContext, Format.Builtin.TEXT_MAP, injectAdapter); } } /** * Extracts a {@code SpanContext} from an AMQP {@code Message}. *

- * The span context will be read from the message annotations of the given message. + * The span context will be read from the message annotations or the application properties of the given message. * * @param tracer The Tracer to use for extracting the context. * @param message The AMQP {@code Message} to extract the context from. @@ -204,7 +212,7 @@ public static SpanContext extractSpanContext(final Tracer tracer, final Message Objects.requireNonNull(message); return tracer.extract(Format.Builtin.TEXT_MAP, - new MessageAnnotationsExtractAdapter(message, AMQP_ANNOTATION_NAME_TRACE_CONTEXT)); + new AmqpMessageExtractAdapter(message, LEGACY_AMQP_ANNOTATION_NAME_TRACE_CONTEXT)); } /** diff --git a/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/tracing/AmqpMessageExtractAdapter.java b/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/tracing/AmqpMessageExtractAdapter.java new file mode 100644 index 0000000000..b816430ffa --- /dev/null +++ b/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/tracing/AmqpMessageExtractAdapter.java @@ -0,0 +1,106 @@ +/******************************************************************************* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package org.eclipse.hono.client.amqp.tracing; + +import java.util.AbstractMap; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.Optional; + +import org.apache.qpid.proton.amqp.messaging.ApplicationProperties; +import org.apache.qpid.proton.message.Message; + +import io.opentracing.propagation.TextMap; + +/** + * An adapter for extracting trace context information from the message annotations (representing the legacy format) + * or application properties of an AMQP 1.0 message. + */ +public class AmqpMessageExtractAdapter implements TextMap { + + private final Message message; + private final MessageAnnotationsExtractAdapter legacyAdapter; + + /** + * Creates an adapter for a message. + *

+ * Trace context information will be extracted from the application properties of the message. + * + * @param message The message. + * @throws NullPointerException if message is {@code null}. + */ + public AmqpMessageExtractAdapter(final Message message) { + this(message, null); + } + + /** + * Creates an adapter for a message. + *

+ * If the {@code legacyMessageAnnotationsPropertiesMapName} constructor parameter is not {@code null}, the + * information is extracted from the corresponding map in the message annotations, if that map exists. + * Otherwise, the application properties of the message will be used for extracting the trace context information. + * + * @param message The message. + * @param legacyMessageAnnotationsPropertiesMapName The name of the message annotation of type map from where to + * extract the trace properties (if that map exists). + * @throws NullPointerException if message is {@code null}. + */ + public AmqpMessageExtractAdapter(final Message message, final String legacyMessageAnnotationsPropertiesMapName) { + this.message = Objects.requireNonNull(message); + this.legacyAdapter = Optional.ofNullable(legacyMessageAnnotationsPropertiesMapName) + .map(mapName -> new MessageAnnotationsExtractAdapter(message, mapName)) + .orElse(null); + } + + @Override + public Iterator> iterator() { + + if (legacyAdapter != null) { + final Map legacyPropertiesMap = legacyAdapter.getMessageAnnotationsPropertiesMap(); + if (!legacyPropertiesMap.isEmpty()) { + return mapEntriesIterator(legacyPropertiesMap.entrySet().iterator()); + } + } + + final ApplicationProperties applicationProperties = message.getApplicationProperties(); + if (applicationProperties == null || applicationProperties.getValue() == null) { + return Collections.emptyIterator(); + } + return mapEntriesIterator(applicationProperties.getValue().entrySet().iterator()); + } + + private static Iterator> mapEntriesIterator(final Iterator> entriesIterator) { + return new Iterator<>() { + + @Override + public boolean hasNext() { + return entriesIterator.hasNext(); + } + + @Override + public Entry next() { + final Entry nextEntry = entriesIterator.next(); + return new AbstractMap.SimpleEntry<>(nextEntry.getKey().toString(), nextEntry.getValue().toString()); + } + }; + } + + @Override + public void put(final String key, final String value) { + throw new UnsupportedOperationException(); + } +} diff --git a/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/tracing/AmqpMessageInjectAdapter.java b/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/tracing/AmqpMessageInjectAdapter.java new file mode 100644 index 0000000000..74062e76c3 --- /dev/null +++ b/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/tracing/AmqpMessageInjectAdapter.java @@ -0,0 +1,59 @@ +/******************************************************************************* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.hono.client.amqp.tracing; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map.Entry; +import java.util.Objects; + +import org.apache.qpid.proton.amqp.messaging.ApplicationProperties; +import org.apache.qpid.proton.message.Message; + +import io.opentracing.propagation.TextMap; + +/** + * An adapter for injecting trace context information in the application properties of an AMQP 1.0 message. + * + */ +public class AmqpMessageInjectAdapter implements TextMap { + + private final Message message; + + /** + * Creates an adapter for a message. + * + * @param message The message. + * @throws NullPointerException if message is {@code null}. + */ + public AmqpMessageInjectAdapter(final Message message) { + this.message = Objects.requireNonNull(message); + } + + @Override + public Iterator> iterator() { + throw new UnsupportedOperationException(); + } + + @Override + public void put(final String key, final String value) { + final ApplicationProperties applicationProperties; + if (message.getApplicationProperties() != null && message.getApplicationProperties().getValue() != null) { + applicationProperties = message.getApplicationProperties(); + } else { + applicationProperties = new ApplicationProperties(new HashMap<>()); + message.setApplicationProperties(applicationProperties); + } + applicationProperties.getValue().put(key, value); + } +} diff --git a/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/tracing/MessageAnnotationsExtractAdapter.java b/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/tracing/MessageAnnotationsExtractAdapter.java index 05e0f0790e..5d9c638b09 100644 --- a/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/tracing/MessageAnnotationsExtractAdapter.java +++ b/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/tracing/MessageAnnotationsExtractAdapter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2016 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -31,7 +31,9 @@ /** * An adapter for extracting properties from an AMQP 1.0 message's message annotations. * + * @deprecated Use {@link AmqpMessageExtractAdapter} instead. */ +@Deprecated public class MessageAnnotationsExtractAdapter implements TextMap { private static final Logger LOG = LoggerFactory.getLogger(MessageAnnotationsExtractAdapter.class); @@ -54,7 +56,7 @@ public MessageAnnotationsExtractAdapter(final Message message, final String prop @Override public Iterator> iterator() { - final Map propertiesMap = getPropertiesMap(); + final Map propertiesMap = getMessageAnnotationsPropertiesMap(); if (propertiesMap.isEmpty()) { return Collections.emptyIterator(); } @@ -80,7 +82,13 @@ public void put(final String key, final String value) { throw new UnsupportedOperationException(); } - private Map getPropertiesMap() { + /** + * Gets the properties map stored in a message annotation entry named according to the + * propertiesMapName constructor parameter. + * + * @return The properties map or an empty map, if no corresponding message annotations map entry was found. + */ + Map getMessageAnnotationsPropertiesMap() { final MessageAnnotations messageAnnotations = message.getMessageAnnotations(); if (messageAnnotations == null || messageAnnotations.getValue() == null) { return Collections.emptyMap(); diff --git a/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/tracing/MessageAnnotationsInjectAdapter.java b/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/tracing/MessageAnnotationsInjectAdapter.java index 5e5a233c31..0cbcea1f23 100644 --- a/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/tracing/MessageAnnotationsInjectAdapter.java +++ b/clients/amqp-connection/src/main/java/org/eclipse/hono/client/amqp/tracing/MessageAnnotationsInjectAdapter.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016, 2022 Contributors to the Eclipse Foundation + * Copyright (c) 2016, 2023 Contributors to the Eclipse Foundation * * See the NOTICE file(s) distributed with this work for additional * information regarding copyright ownership. @@ -27,7 +27,9 @@ /** * An adapter for injecting properties into an AMQP 1.0 message's message annotations. * + * @deprecated Use {@link AmqpMessageInjectAdapter} instead. */ +@Deprecated public class MessageAnnotationsInjectAdapter implements TextMap { private final Message message; diff --git a/clients/amqp-connection/src/test/java/org/eclipse/hono/client/amqp/tracing/AmqpMessageInjectExtractAdapterTest.java b/clients/amqp-connection/src/test/java/org/eclipse/hono/client/amqp/tracing/AmqpMessageInjectExtractAdapterTest.java new file mode 100644 index 0000000000..7af24e8726 --- /dev/null +++ b/clients/amqp-connection/src/test/java/org/eclipse/hono/client/amqp/tracing/AmqpMessageInjectExtractAdapterTest.java @@ -0,0 +1,124 @@ +/******************************************************************************* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ + +package org.eclipse.hono.client.amqp.tracing; + +import static com.google.common.truth.Truth.assertThat; + +import java.util.HashMap; +import java.util.Map; + +import org.apache.qpid.proton.codec.WritableBuffer; +import org.apache.qpid.proton.message.Message; +import org.junit.jupiter.api.Test; + +import io.opentelemetry.api.OpenTelemetry; +import io.opentelemetry.api.trace.propagation.W3CTraceContextPropagator; +import io.opentelemetry.context.propagation.ContextPropagators; +import io.opentelemetry.opentracingshim.OpenTracingShim; +import io.opentelemetry.sdk.OpenTelemetrySdk; +import io.opentracing.Span; +import io.opentracing.SpanContext; +import io.opentracing.Tracer; +import io.opentracing.propagation.Format; +import io.vertx.proton.ProtonHelper; + +/** + * Tests verifying the behavior of {@link AmqpMessageInjectAdapter} and {@link AmqpMessageExtractAdapter}. + * + */ +public class AmqpMessageInjectExtractAdapterTest { + + /** + * Verifies that the same entries injected via the {@code AmqpMessageInjectAdapter} are extracted via the + * {@code AmqpMessageExtractAdapter}. + * Also verifies that there are no errors during encoding/decoding of the message with the injected entries. + */ + @Test + public void testInjectAndExtract() { + final Map testEntries = new HashMap<>(); + testEntries.put("key1", "value1"); + testEntries.put("key2", "value2"); + + final Message message = ProtonHelper.message(); + // inject the properties + final AmqpMessageInjectAdapter injectAdapter = new AmqpMessageInjectAdapter(message); + testEntries.forEach(injectAdapter::put); + + // encode the message + final WritableBuffer.ByteBufferWrapper buffer = WritableBuffer.ByteBufferWrapper.allocate(100); + message.encode(buffer); + + // decode the message + final Message decodedMessage = ProtonHelper.message(); + decodedMessage.decode(buffer.toReadableBuffer()); + // extract the properties from the decoded message + final AmqpMessageExtractAdapter extractAdapter = new AmqpMessageExtractAdapter(decodedMessage); + extractAdapter.iterator().forEachRemaining(extractedEntry -> { + assertThat(extractedEntry.getValue()).isEqualTo(testEntries.get(extractedEntry.getKey())); + }); + } + + /** + * Verifies that the same entries injected via the {@code MessageAnnotationsInjectAdapter} are extracted via the + * {@code AmqpMessageExtractAdapter}, when having provided the name of the message annotations map. + * Also verifies that there are no errors during encoding/decoding of the message with the injected entries. + */ + @Test + public void testInjectAndExtractUsingLegacyFormat() { + final String legacyMessageAnnotationsPropertiesMapName = "map"; + final Map testEntries = new HashMap<>(); + testEntries.put("key1", "value1"); + testEntries.put("key2", "value2"); + + final Message message = ProtonHelper.message(); + // inject the properties using the legacy format + final MessageAnnotationsInjectAdapter injectAdapter = new MessageAnnotationsInjectAdapter(message, + legacyMessageAnnotationsPropertiesMapName); + testEntries.forEach(injectAdapter::put); + + // encode the message + final WritableBuffer.ByteBufferWrapper buffer = WritableBuffer.ByteBufferWrapper.allocate(100); + message.encode(buffer); + + // decode the message + final Message decodedMessage = ProtonHelper.message(); + decodedMessage.decode(buffer.toReadableBuffer()); + // extract the properties from the decoded message + final AmqpMessageExtractAdapter extractAdapter = new AmqpMessageExtractAdapter(decodedMessage, + legacyMessageAnnotationsPropertiesMapName); + extractAdapter.iterator().forEachRemaining(extractedEntry -> { + assertThat(extractedEntry.getValue()).isEqualTo(testEntries.get(extractedEntry.getKey())); + }); + } + + /** + * Verifies that the OpenTelemetry Tracer shim can successfully use the adapter to inject and extract + * a SpanContext. + */ + @Test + public void testTracerShimCanUseAdapter() { + final OpenTelemetry openTelemetry = OpenTelemetrySdk.builder() + .setPropagators(ContextPropagators.create(W3CTraceContextPropagator.getInstance())) + .build(); + final Tracer tracer = OpenTracingShim.createTracerShim(openTelemetry); + final Span span = tracer.buildSpan("do").start(); + + final Message message = ProtonHelper.message(); + final AmqpMessageInjectAdapter injectAdapter = new AmqpMessageInjectAdapter(message); + tracer.inject(span.context(), Format.Builtin.TEXT_MAP, injectAdapter); + + final SpanContext context = tracer.extract(Format.Builtin.TEXT_MAP, new AmqpMessageExtractAdapter(message)); + assertThat(context.toSpanId()).isEqualTo(span.context().toSpanId()); + } +} diff --git a/site/documentation/content/admin-guide/hono-client-configuration.md b/site/documentation/content/admin-guide/hono-client-configuration.md index cba69c535e..3ccab9d0cf 100644 --- a/site/documentation/content/admin-guide/hono-client-configuration.md +++ b/site/documentation/content/admin-guide/hono-client-configuration.md @@ -60,6 +60,7 @@ property prefix `hono.credentials`. | `${PREFIX}_TLSENABLED`
`${prefix}.tlsEnabled` | no | `false` | If set to `true` the connection to the peer will be encrypted using TLS and the peer's identity will be verified using the JVM's configured standard trust store.
This variable only needs to be set to enable TLS explicitly if no specific trust store is configured using the `${PREFIX}_TRUSTSTOREPATH` variable. | | `${PREFIX}_TRUSTSTOREPATH`
`${prefix}.trustStorePath` | no | - | The absolute path to the Java key store containing the CA certificates the client uses for authenticating to the service. This property **must** be set if the service has been configured to support TLS. The key store format can be either `JKS`, `PKCS12` or `PEM` indicated by a `.jks`, `.p12` or `.pem` file suffix respectively. | | `${PREFIX}_TRUSTSTOREPASSWORD`
`${prefix}.trustStorePassword` | no | - | The password required to read the contents of the trust store. If the value starts with `file:` then the string after the prefix is interpreted as the path to a file to read the password from. | +| `${PREFIX}_USELEGACYTRACECONTEXTFORMAT`
`${prefix}.useLegacyTraceContextFormat` | no | `true` | This flag determines where OpenTelemetry trace context information will be stored in an AMQP 1.0 message sent via the client. If set to `true`, the legacy format will be used, writing trace context information as `traceparent` and `tracestate` properties in an `x-opt-trace-context` map in the message-annotations of the message.
When set to `false`, the tracing properties will be written to the application properties of the message instead. The latter is a more generic approach, adhering to the [Trace Context: AMQP protocol](https://w3c.github.io/trace-context-amqp/) specification draft and being compatible with Eclipse Ditto, for example. It will be the default in Hono 3.0. | | `${PREFIX}_USERNAME`
`${prefix}.username` | no | - | The username to use for authenticating to the service. This property (and the corresponding *password*) needs to be set in order to enable *SASL Plain* based authentication to the service.| ## Response Caching diff --git a/site/homepage/content/release-notes.md b/site/homepage/content/release-notes.md index 6ce77daffe..ebc60be839 100644 --- a/site/homepage/content/release-notes.md +++ b/site/homepage/content/release-notes.md @@ -19,6 +19,12 @@ description = "Information about changes in recent Hono releases. Includes new f * The command line client was still trying to connect to the insecure ports of the Sandbox. This has been changed so that the client now uses the TLS endpoints and requires the user to specify a trust store for validating the server certificate. * Updated to Quarkus 3.2.6.Final +* A more generic format for storing the OpenTelemetry trace context information in an AMQP 1.0 message can now be + configured, writing the corresponding properties in the message application properties. This is for example relevant + when using AMQP 1.0 messaging in connection with Eclipse Ditto, resulting in combined traces of Hono and Ditto. + Please refer to the `${prefix}.useLegacyTraceContextFormat` connection property documentation in the + [Hono Client Configuration Guide]({{% doclink "/admin-guide/hono-client-configuration/#connection-properties" %}}) + for additional information. ### Deprecations