From 0b2d27c8105da8f98121063ea68e09dd2272a49f Mon Sep 17 00:00:00 2001 From: Colin Sullivan Date: Fri, 3 May 2019 09:21:00 -0600 Subject: [PATCH] Binary format implementation (#613) Signed-off-by: Colin Sullivan --- .../java/io/jaegertracing/Configuration.java | 36 +++ .../internal/JaegerSpanContext.java | 7 + .../jaegertracing/internal/JaegerTracer.java | 8 +- .../internal/propagation/BinaryCodec.java | 270 ++++++++++++++++++ .../io/jaegertracing/ConfigurationTest.java | 51 +++- .../internal/AdhocHeadersTest.java | 1 - .../internal/propagation/BinaryCodecTest.java | 151 ++++++++++ .../propagation/TestBinaryCarrier.java | 48 ++++ 8 files changed, 569 insertions(+), 3 deletions(-) create mode 100644 jaeger-core/src/main/java/io/jaegertracing/internal/propagation/BinaryCodec.java create mode 100644 jaeger-core/src/test/java/io/jaegertracing/internal/propagation/BinaryCodecTest.java create mode 100644 jaeger-core/src/test/java/io/jaegertracing/internal/propagation/TestBinaryCarrier.java diff --git a/jaeger-core/src/main/java/io/jaegertracing/Configuration.java b/jaeger-core/src/main/java/io/jaegertracing/Configuration.java index b75bc77e4..99d761f28 100644 --- a/jaeger-core/src/main/java/io/jaegertracing/Configuration.java +++ b/jaeger-core/src/main/java/io/jaegertracing/Configuration.java @@ -19,6 +19,7 @@ import io.jaegertracing.internal.metrics.Metrics; import io.jaegertracing.internal.metrics.NoopMetricsFactory; import io.jaegertracing.internal.propagation.B3TextMapCodec; +import io.jaegertracing.internal.propagation.BinaryCodec; import io.jaegertracing.internal.propagation.CompositeCodec; import io.jaegertracing.internal.propagation.TextMapCodec; import io.jaegertracing.internal.reporters.CompositeReporter; @@ -36,6 +37,7 @@ import io.jaegertracing.spi.Sampler; import io.jaegertracing.spi.Sender; import io.jaegertracing.spi.SenderFactory; +import io.opentracing.propagation.Binary; import io.opentracing.propagation.Format; import io.opentracing.propagation.TextMap; import java.text.NumberFormat; @@ -424,9 +426,11 @@ public SamplerConfiguration withManagerHostPort(String managerHostPort) { */ public static class CodecConfiguration { private final Map, List>> codecs; + private final Map, List>> binaryCodecs; public CodecConfiguration() { codecs = new HashMap, List>>(); + binaryCodecs = new HashMap, List>>(); } public static CodecConfiguration fromEnv() { @@ -457,6 +461,7 @@ public CodecConfiguration withPropagation(Propagation propagation) { case JAEGER: addCodec(codecs, Format.Builtin.HTTP_HEADERS, new TextMapCodec(true)); addCodec(codecs, Format.Builtin.TEXT_MAP, new TextMapCodec(false)); + addBinaryCodec(binaryCodecs, Format.Builtin.BINARY, new BinaryCodec()); break; case B3: addCodec(codecs, Format.Builtin.HTTP_HEADERS, new B3TextMapCodec.Builder().build()); @@ -473,10 +478,19 @@ public CodecConfiguration withCodec(Format format, Codec codec) { return this; } + public CodecConfiguration withBinaryCodec(Format format, Codec codec) { + addBinaryCodec(binaryCodecs, format, codec); + return this; + } + public Map, List>> getCodecs() { return Collections.unmodifiableMap(codecs); } + public Map, List>> getBinaryCodecs() { + return Collections.unmodifiableMap(binaryCodecs); + } + private static void addCodec(Map, List>> codecs, Format format, Codec codec) { List> codecList = codecs.get(format); if (codecList == null) { @@ -486,11 +500,23 @@ private static void addCodec(Map, List>> codecs, Format codecList.add(codec); } + private static void addBinaryCodec(Map, List>> codecs, + Format format, Codec codec) { + + List> codecList = codecs.get(format); + if (codecList == null) { + codecList = new LinkedList>(); + codecs.put(format, codecList); + } + codecList.add(codec); + } + public void apply(JaegerTracer.Builder builder) { // Replace existing TEXT_MAP and HTTP_HEADERS codec with one that represents the // configured propagation formats registerCodec(builder, Format.Builtin.HTTP_HEADERS); registerCodec(builder, Format.Builtin.TEXT_MAP); + registerBinaryCodec(builder, Format.Builtin.BINARY); } protected void registerCodec(JaegerTracer.Builder builder, Format format) { @@ -502,6 +528,16 @@ protected void registerCodec(JaegerTracer.Builder builder, Format forma builder.registerExtractor(format, codec); } } + + protected void registerBinaryCodec(JaegerTracer.Builder builder, Format format) { + if (codecs.containsKey(format)) { + List> codecsForFormat = binaryCodecs.get(format); + Codec codec = codecsForFormat.size() == 1 + ? codecsForFormat.get(0) : new CompositeCodec(codecsForFormat); + builder.registerInjector(format, codec); + builder.registerExtractor(format, codec); + } + } } public static class ReporterConfiguration { diff --git a/jaeger-core/src/main/java/io/jaegertracing/internal/JaegerSpanContext.java b/jaeger-core/src/main/java/io/jaegertracing/internal/JaegerSpanContext.java index 806a05420..1742587ac 100644 --- a/jaeger-core/src/main/java/io/jaegertracing/internal/JaegerSpanContext.java +++ b/jaeger-core/src/main/java/io/jaegertracing/internal/JaegerSpanContext.java @@ -133,6 +133,13 @@ public boolean isDebug() { return (flags & flagDebug) == flagDebug; } + /** + * @return the number of items in baggage. + */ + public int baggageCount() { + return baggage.size(); + } + @Override public String toString() { return TextMapCodec.contextAsString(this); diff --git a/jaeger-core/src/main/java/io/jaegertracing/internal/JaegerTracer.java b/jaeger-core/src/main/java/io/jaegertracing/internal/JaegerTracer.java index c4f8392c3..6303b5b71 100644 --- a/jaeger-core/src/main/java/io/jaegertracing/internal/JaegerTracer.java +++ b/jaeger-core/src/main/java/io/jaegertracing/internal/JaegerTracer.java @@ -24,6 +24,7 @@ import io.jaegertracing.internal.exceptions.UnsupportedFormatException; import io.jaegertracing.internal.metrics.Metrics; import io.jaegertracing.internal.metrics.NoopMetricsFactory; +import io.jaegertracing.internal.propagation.BinaryCodec; import io.jaegertracing.internal.propagation.TextMapCodec; import io.jaegertracing.internal.reporters.RemoteReporter; import io.jaegertracing.internal.samplers.RemoteControlledSampler; @@ -538,7 +539,12 @@ protected Builder(String serviceName, JaegerObjectFactory objectFactory) { .build(); this.registerInjector(Format.Builtin.HTTP_HEADERS, httpCodec); this.registerExtractor(Format.Builtin.HTTP_HEADERS, httpCodec); - // TODO binary codec not implemented + BinaryCodec binaryCodec = + BinaryCodec.builder() + .withObjectFactory(this.objectFactory) + .build(); + this.registerInjector(Format.Builtin.BINARY, binaryCodec); + this.registerExtractor(Format.Builtin.BINARY, binaryCodec); } /** diff --git a/jaeger-core/src/main/java/io/jaegertracing/internal/propagation/BinaryCodec.java b/jaeger-core/src/main/java/io/jaegertracing/internal/propagation/BinaryCodec.java new file mode 100644 index 000000000..3eb19353e --- /dev/null +++ b/jaeger-core/src/main/java/io/jaegertracing/internal/propagation/BinaryCodec.java @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2019, The Jaeger Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package io.jaegertracing.internal.propagation; + +import io.jaegertracing.internal.JaegerObjectFactory; +import io.jaegertracing.internal.JaegerSpanContext; +import io.jaegertracing.spi.Codec; +import io.opentracing.propagation.Binary; + +import java.io.ByteArrayOutputStream; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map; + +/** + * This class is a simple binary codec, mirroring the wire format of + * the golang binary injector/extractor to be compatible. The go client + * codec that has defined the wire format used in this module can be found + * here: + * + * https://github.com/jaegertracing/jaeger-client-go/blob/v2.16.0/propagation.go#L177 + * + * For reference, the binary format is: + * | IDs | flags | baggage count | key len | key | value len | value | ... + * + * The baggage count and lengths are 32 bit integers (int). + * + * IDs are 64 bits integers (long) serialized as: + * | TraceID high | TraceID low | SpanID | Parent ID | + */ +public class BinaryCodec implements Codec { + + /** + * Explicitly define the charset we will use. + */ + private static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8"); + + /** + * Object factory used to construct JaegerSpanContext subclass instances. + */ + private final JaegerObjectFactory objectFactory; + + /** + * Constructor for a Binary Codec. + */ + public BinaryCodec() { + this(builder()); + } + + private BinaryCodec(Builder builder) { + this.objectFactory = builder.objectFactory; + } + + /** + * Write a long into a stream in network order + * + * @param stream Stream to write the integer into + * @param value long to write + * @param buf buffer to use to write to the stream. + */ + private static void writeLong(ByteArrayOutputStream stream, long value) { + stream.write((byte) (value >> 56)); + stream.write((byte) (value >> 48)); + stream.write((byte) (value >> 40)); + stream.write((byte) (value >> 32)); + stream.write((byte) (value >> 24)); + stream.write((byte) (value >> 16)); + stream.write((byte) (value >> 8)); + stream.write((byte) (value)); + } + + /** + * Write an integer into a stream in network order + * + * @param stream Stream to write the integer into + * @param value integer to write + */ + private static void writeInt(ByteArrayOutputStream stream, int value) { + stream.write((byte) (value >> 24)); + stream.write((byte) (value >> 16)); + stream.write((byte) (value >> 8)); + stream.write((byte) (value)); + } + + /** + * Writes a String Key/Value pair into a ByteArrayOutputStream + * + * @param stream Stream to write the integer into + * @param key key of the KV pair + * @param value value of the KV pair + */ + private void writeKvPair(ByteArrayOutputStream stream, String key, String value) { + byte[] buf; + + int keyLen; + buf = key.getBytes(DEFAULT_CHARSET); + keyLen = buf.length; + writeInt(stream, keyLen); + stream.write(buf, 0, keyLen); + + int valLen; + buf = value.getBytes(DEFAULT_CHARSET); + valLen = value.length(); + writeInt(stream, valLen); + stream.write(buf, 0, valLen); + } + + /** + * Convenience method to check a buffer for size and reallocate if necessary. + * + * @param len the length required + * @param bytes the buffer of bytes to be used + * @return a byte array of the correct size. + */ + private static byte[] checkBuf(int len, byte[] bytes) { + return len <= bytes.length ? bytes : new byte[len]; + } + + @Override + public void inject(JaegerSpanContext spanContext, Binary carrier) { + + // Because we need to know the size of a ByteBuffer a priori, we'll + // use a stream to serialize and then copy the stream into the + // ByteBuffer of the carrier. The double allocation isn't ideal, but + // these should be small and the GC will return this memory very fast. + ByteArrayOutputStream stream = new ByteArrayOutputStream(64); + + // Write the IDs + writeLong(stream, spanContext.getTraceIdHigh()); + writeLong(stream, spanContext.getTraceIdLow()); + writeLong(stream, spanContext.getSpanId()); + writeLong(stream, spanContext.getParentId()); + + // Write the flags (byte) + stream.write(spanContext.getFlags()); + + // write the baggage count. + writeInt(stream, spanContext.baggageCount()); + + // write the kv/pars into the stream + for (Map.Entry entry : spanContext.baggageItems()) { + writeKvPair(stream, entry.getKey(), entry.getValue()); + } + + // Now we have a stream and a size, and we'll copy it into the byte + // buffer. + int size = stream.size(); + ByteBuffer buf = carrier.injectionBuffer(size); + + // Java defaults to big endian (network order), but enforce it just + // in case the carrier set the wrong byte order before passing it in. + if (buf.order() != ByteOrder.BIG_ENDIAN) { + throw new IllegalStateException("Carrier byte order must be big endian."); + } + buf.put(stream.toByteArray(), 0, size); + } + + @Override + public JaegerSpanContext extract(Binary carrier) { + Map baggage = null; + + ByteBuffer buf = carrier.extractionBuffer(); + + // Do not require the carrier implemention to rewind. + buf.rewind(); + + // Java defaults to big endian (network order), but enforce it just + // in case the carrier is using the wrong byte order. + if (buf.order() != ByteOrder.BIG_ENDIAN) { + throw new IllegalStateException("Carrier byte order must be big endian."); + } + + long traceIdHigh = buf.getLong(); + long traceIdLow = buf.getLong(); + long spanId = buf.getLong(); + long parentId = buf.getLong(); + byte flags = buf.get(); + int count = buf.getInt(); + + // This is optimized to reduce allocations. A decent + // buffer is allocated to read strings, and reused for + // keys and values. It will be expanded as necessary. + if (count > 0) { + baggage = new HashMap(count); + // Choose a size that we guess would fit most baggage k/v lengths. + byte[] tmp = new byte[32]; + + for (int i = 0; i < count; i++) { + int len = buf.getInt(); + tmp = checkBuf(len, tmp); + buf.get(tmp, 0, len); + final String key = new String(tmp, 0, len, DEFAULT_CHARSET); + + len = buf.getInt(); + tmp = checkBuf(len, tmp); + buf.get(tmp, 0, len); + final String value = new String(tmp, 0, len, DEFAULT_CHARSET); + + baggage.put(key, value); + } + } + + return objectFactory.createSpanContext( + traceIdHigh, + traceIdLow, + spanId, + parentId, + flags, + baggage, + null); + } + + @Override + public String toString() { + StringBuilder buffer = new StringBuilder(); + buffer + .append("BinaryCodec{") + .append("ObjectFactory=" + objectFactory.getClass().getName()) + .append('}'); + return buffer.toString(); + } + + /** + * Returns a builder for BinaryCodec. + * + * @return Builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * This class is the builder for the BinaryCodec. + */ + public static class Builder { + + private JaegerObjectFactory objectFactory = new JaegerObjectFactory(); + + /** + * Set object factory to use for construction of JaegerSpanContext subclass instances. + * + * @param objectFactory JaegerObjectFactory subclass instance. + */ + public Builder withObjectFactory(JaegerObjectFactory objectFactory) { + this.objectFactory = objectFactory; + return this; + } + + /** + * Builds a BinaryCodec object. + */ + public BinaryCodec build() { + return new BinaryCodec(this); + } + } +} diff --git a/jaeger-core/src/test/java/io/jaegertracing/ConfigurationTest.java b/jaeger-core/src/test/java/io/jaegertracing/ConfigurationTest.java index 4b4cf8c51..6e0a1ab7a 100644 --- a/jaeger-core/src/test/java/io/jaegertracing/ConfigurationTest.java +++ b/jaeger-core/src/test/java/io/jaegertracing/ConfigurationTest.java @@ -31,12 +31,15 @@ import io.jaegertracing.internal.metrics.Metrics; import io.jaegertracing.internal.metrics.MockMetricsFactory; import io.jaegertracing.internal.propagation.B3TextMapCodec; +import io.jaegertracing.internal.propagation.BinaryCodec; +import io.jaegertracing.internal.propagation.TestBinaryCarrier; import io.jaegertracing.internal.propagation.TextMapCodec; import io.jaegertracing.internal.samplers.ConstSampler; import io.jaegertracing.internal.samplers.ProbabilisticSampler; import io.jaegertracing.internal.samplers.RateLimitingSampler; import io.jaegertracing.spi.Codec; import io.jaegertracing.spi.Sampler; +import io.opentracing.propagation.Binary; import io.opentracing.propagation.Format; import io.opentracing.propagation.Format.Builtin; import io.opentracing.propagation.TextMap; @@ -257,6 +260,25 @@ public void testPropagationJaegerAndB3() { assertEquals(spanId, extractedContext.getSpanId()); } + @Test + public void testPropagationBinary() { + System.setProperty(Configuration.JAEGER_PROPAGATION, "jaeger"); + System.setProperty(Configuration.JAEGER_SERVICE_NAME, "Test"); + + long traceIdLow = 1234L; + long spanId = 5678L; + + TestBinaryCarrier buffer = new TestBinaryCarrier(); + JaegerSpanContext spanContext = new JaegerSpanContext(0, traceIdLow, spanId, 0, (byte)0); + + JaegerTracer tracer = Configuration.fromEnv().getTracer(); + tracer.inject(spanContext, Format.Builtin.BINARY, buffer); + JaegerSpanContext extractedContext = tracer.extract(Format.Builtin.BINARY, buffer); + assertEquals(traceIdLow, extractedContext.getTraceIdLow()); + assertEquals(0, extractedContext.getTraceIdHigh()); + assertEquals(spanId, extractedContext.getSpanId()); + } + @Test public void testPropagationDefault() { System.setProperty(Configuration.JAEGER_SERVICE_NAME, "Test"); @@ -385,12 +407,24 @@ public void inject(JaegerSpanContext spanContext, TextMap carrier) { } }; + Codec codec3 = new Codec() { + @Override + public JaegerSpanContext extract(Binary carrier) { + return null; + } + + @Override + public void inject(JaegerSpanContext spanContext, Binary carrier) { + } + }; CodecConfiguration codecConfiguration = new CodecConfiguration() .withCodec(Builtin.HTTP_HEADERS, codec1) - .withCodec(Builtin.HTTP_HEADERS, codec2); + .withCodec(Builtin.HTTP_HEADERS, codec2) + .withBinaryCodec(Builtin.BINARY, codec3); assertEquals(2, codecConfiguration.getCodecs().get(Builtin.HTTP_HEADERS).size()); assertEquals(codec1, codecConfiguration.getCodecs().get(Builtin.HTTP_HEADERS).get(0)); assertEquals(codec2, codecConfiguration.getCodecs().get(Builtin.HTTP_HEADERS).get(1)); + assertEquals(codec3, codecConfiguration.getBinaryCodecs().get(Builtin.BINARY).get(0)); Configuration configuration = new Configuration("foo") .withCodec(codecConfiguration); @@ -412,6 +446,7 @@ public void testDefaultCodecs() { JaegerSpanContext spanContext = new JaegerSpanContext(0, traceIdLow, spanId, parentId, (byte) 0); assertInjectExtract(configuration.getTracer(), Builtin.TEXT_MAP, spanContext, false); assertInjectExtract(configuration.getTracer(), Builtin.HTTP_HEADERS, spanContext, false); + assertBinaryInjectExtract(configuration.getTracer(), spanContext); } @Test @@ -424,6 +459,7 @@ public void testDefaultCodecsWith128BitTraceId() { JaegerSpanContext spanContext = new JaegerSpanContext(traceIdHigh, traceIdLow, spanId, parentId, (byte) 0); assertInjectExtract(configuration.getTracer(), Builtin.TEXT_MAP, spanContext, false); assertInjectExtract(configuration.getTracer(), Builtin.HTTP_HEADERS, spanContext, false); + assertBinaryInjectExtract(configuration.getTracer(), spanContext); } @Test @@ -438,6 +474,7 @@ public void testB3CodecsWith128BitTraceId() { JaegerSpanContext spanContext = new JaegerSpanContext(traceIdHigh, traceIdLow, spanId, parentId, (byte) 0); assertInjectExtract(configuration.getTracer(), Builtin.TEXT_MAP, spanContext, false); assertInjectExtract(configuration.getTracer(), Builtin.HTTP_HEADERS, spanContext, false); + assertBinaryInjectExtract(configuration.getTracer(), spanContext); } @Test @@ -447,10 +484,12 @@ public void testCodecFromString() { assertEquals(2, codecConfiguration.getCodecs().size()); assertEquals(2, codecConfiguration.getCodecs().get(Builtin.HTTP_HEADERS).size()); assertEquals(2, codecConfiguration.getCodecs().get(Builtin.TEXT_MAP).size()); + assertEquals(1, codecConfiguration.getBinaryCodecs().get(Builtin.BINARY).size()); assertTrue(codecConfiguration.getCodecs().get(Builtin.HTTP_HEADERS).get(0) instanceof B3TextMapCodec); assertTrue(codecConfiguration.getCodecs().get(Builtin.HTTP_HEADERS).get(1) instanceof TextMapCodec); assertTrue(codecConfiguration.getCodecs().get(Builtin.TEXT_MAP).get(0) instanceof B3TextMapCodec); assertTrue(codecConfiguration.getCodecs().get(Builtin.TEXT_MAP).get(1) instanceof TextMapCodec); + assertTrue(codecConfiguration.getBinaryCodecs().get(Builtin.BINARY).get(0) instanceof BinaryCodec); } @Test @@ -462,6 +501,8 @@ public void testCodecWithPropagationJaeger() { assertEquals(1, codecConfiguration.getCodecs().get(Builtin.TEXT_MAP).size()); assertTrue(codecConfiguration.getCodecs().get(Builtin.HTTP_HEADERS).get(0) instanceof TextMapCodec); assertTrue(codecConfiguration.getCodecs().get(Builtin.TEXT_MAP).get(0) instanceof TextMapCodec); + assertEquals(1, codecConfiguration.getBinaryCodecs().size()); + assertTrue(codecConfiguration.getBinaryCodecs().get(Builtin.BINARY).get(0) instanceof BinaryCodec); } @Test @@ -490,6 +531,14 @@ private void assertInjectExtract(JaegerTracer tracer, Format format, Jaeg assertEquals(contextToInject.getSpanId(), extractedContext.getSpanId()); } + private void assertBinaryInjectExtract(JaegerTracer tracer, JaegerSpanContext contextToInject) { + TestBinaryCarrier carrier = new TestBinaryCarrier(); + tracer.inject(contextToInject, Format.Builtin.BINARY, carrier); + JaegerSpanContext extractedContext = tracer.extract(Format.Builtin.BINARY, carrier); + assertEquals(contextToInject.getTraceId(), extractedContext.getTraceId()); + assertEquals(contextToInject.getSpanId(), extractedContext.getSpanId()); + } + @Test public void testMetricsFactoryFromServiceLoader() { System.setProperty(Configuration.JAEGER_SERVICE_NAME, "Test"); diff --git a/jaeger-core/src/test/java/io/jaegertracing/internal/AdhocHeadersTest.java b/jaeger-core/src/test/java/io/jaegertracing/internal/AdhocHeadersTest.java index 5bcf4814e..db2fad0db 100644 --- a/jaeger-core/src/test/java/io/jaegertracing/internal/AdhocHeadersTest.java +++ b/jaeger-core/src/test/java/io/jaegertracing/internal/AdhocHeadersTest.java @@ -63,7 +63,6 @@ public void testDebugCorrelationId() { assertEquals("Coraline", span.getTags().get(Constants.DEBUG_ID_HEADER_KEY)); } - @Test public void testStartTraceWithAdhocBaggage() { traceWithAdhocBaggage(new HashMap()); diff --git a/jaeger-core/src/test/java/io/jaegertracing/internal/propagation/BinaryCodecTest.java b/jaeger-core/src/test/java/io/jaegertracing/internal/propagation/BinaryCodecTest.java new file mode 100644 index 000000000..1004c537e --- /dev/null +++ b/jaeger-core/src/test/java/io/jaegertracing/internal/propagation/BinaryCodecTest.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2019, The Jaeger Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package io.jaegertracing.internal.propagation; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import io.jaegertracing.internal.JaegerSpanContext; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.util.HashMap; +import java.util.Map; + +import org.junit.Test; + +public class BinaryCodecTest { + + @Test + public void testBuilder() { + BinaryCodec codec = BinaryCodec.builder() + .build(); + assertNotNull(codec); + } + + @Test + public void testWithoutBuilder() { + BinaryCodec codec = new BinaryCodec(); + String str = codec.toString(); + assertTrue(str.contains("BinaryCodec")); + } + + /** + * Tests that the codec will include baggage from header "jaeger-baggage". + */ + @Test + public void testContextFieldsWithNoBaggage() { + final long traceIdLow = 42; + final long traceIdHigh = 2; + final long spanId = 1; + final long parentId = 22; + final byte flags = (byte)1; + BinaryCodec codec = new BinaryCodec(); + TestBinaryCarrier carrier = new TestBinaryCarrier(); + codec.inject(new JaegerSpanContext(traceIdHigh, traceIdLow, spanId, parentId, flags), carrier); + JaegerSpanContext context = codec.extract(carrier); + assertTrue(carrier.buffer.remaining() == 0); + assertEquals("must have trace ID low", traceIdLow, context.getTraceIdLow()); + assertEquals("must have trace ID high", traceIdHigh, context.getTraceIdHigh()); + assertEquals("must have span ID", spanId, context.getSpanId()); + assertEquals("must have parent ID", parentId, context.getParentId()); + assertEquals("must have flags", flags, context.getFlags()); + } + + @Test + public void testInvalidByteOrder() { + BinaryCodec codec = new BinaryCodec(); + ByteBuffer buf = ByteBuffer.allocate(128); + buf.order(ByteOrder.LITTLE_ENDIAN); + TestBinaryCarrier carrier = new TestBinaryCarrier(buf); + try { + codec.inject(new JaegerSpanContext(0L, 0L, 0L, 0L, (byte) 0), carrier); + fail("Exception not thrown."); + } catch (IllegalStateException expected) { + assertEquals("Carrier byte order must be big endian.", expected.getMessage()); + } + try { + codec.extract(carrier); + fail("Exception not thrown."); + } catch (IllegalStateException expected) { + assertEquals("Carrier byte order must be big endian.", expected.getMessage()); + } + } + + /** + * Tests that the codec will return non-null SpanContext even if the only header + * present is "jaeger-baggage". + */ + @Test + public void testBaggage() { + Map baggage = new HashMap(); + for (int i = 0; i < 200; i++) { + baggage.put("k" + i, "v" + i); + } + + BinaryCodec codec = new BinaryCodec(); + JaegerSpanContext inContext = new JaegerSpanContext(0L, 42L, 1L, 1L, (byte)1) + .withBaggage(baggage); + + TestBinaryCarrier carrier = new TestBinaryCarrier(); + codec.inject(inContext, carrier); + + // check with a new carrier just to make sure testing is accurate. + byte[] raw = new byte[carrier.buffer.capacity()]; + carrier.buffer.rewind(); + carrier.buffer.get(raw); + TestBinaryCarrier carrier2 = new TestBinaryCarrier(ByteBuffer.wrap(raw)); + + JaegerSpanContext outContext = codec.extract(carrier2); + assertTrue(carrier2.buffer.remaining() == 0); + for (int i = 0; i < 200; i++) { + assertEquals("v" + i, outContext.getBaggageItem("k" + i)); + } + } + + @Test + public void testBaggageWithLargeValues() { + String key1 = ""; + String val1 = ""; + for (int i = 0; i < 256; i++) { + key1 += "A"; + val1 += "B"; + } + + String key2 = ""; + String val2 = ""; + for (int i = 0; i < 1024; i++) { + key2 += "C"; + val2 += "D"; + } + + Map baggage = new HashMap(); + baggage.put(key1, val1); + baggage.put(key2, val2); + + JaegerSpanContext inContext = new JaegerSpanContext(0L, 1L, 1L, 1L, (byte)1) + .withBaggage(baggage); + + BinaryCodec codec = new BinaryCodec(); + TestBinaryCarrier carrier = new TestBinaryCarrier(); + codec.inject(inContext, carrier); + + JaegerSpanContext outContext = codec.extract(carrier); + assertEquals(val1, outContext.getBaggageItem(key1)); + assertEquals(val2, outContext.getBaggageItem(key2)); + } +} diff --git a/jaeger-core/src/test/java/io/jaegertracing/internal/propagation/TestBinaryCarrier.java b/jaeger-core/src/test/java/io/jaegertracing/internal/propagation/TestBinaryCarrier.java new file mode 100644 index 000000000..f9456ccc1 --- /dev/null +++ b/jaeger-core/src/test/java/io/jaegertracing/internal/propagation/TestBinaryCarrier.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2019, The Jaeger Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package io.jaegertracing.internal.propagation; + +import io.opentracing.propagation.Binary; +import java.nio.ByteBuffer; + +/** + * Test implemention of a binary carrier. + */ +public class TestBinaryCarrier implements Binary { + + ByteBuffer buffer = null; + + public TestBinaryCarrier(ByteBuffer b) { + buffer = b; + } + + public TestBinaryCarrier() { } + + @Override + public ByteBuffer injectionBuffer(int length) { + if (length <= 0) { + throw new IllegalArgumentException("length must be greater than zero"); + } + if (buffer == null) { + buffer = ByteBuffer.allocate(length); + } + return buffer; + } + + @Override + public ByteBuffer extractionBuffer() { + return buffer; + } +} \ No newline at end of file