diff --git a/custom/src/test/resources/sdk-config.yaml b/custom/src/test/resources/sdk-config.yaml index d69572d2..5855f556 100644 --- a/custom/src/test/resources/sdk-config.yaml +++ b/custom/src/test/resources/sdk-config.yaml @@ -29,7 +29,11 @@ attribute_limits: # Configure text map context propagators. propagator: # Configure the set of propagators to include in the composite text map propagator. - composite: [ tracecontext, baggage, swo/contextPropagator ] + composite: + - tracecontext: + - baggage: + - swo_tracestate: + - swo_xtraceoptions: # Configure tracer provider. tracer_provider: diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SolarwindsContextPropagatorProvider.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SolarwindsContextPropagatorProvider.java index a04e08ab..001f3ed0 100644 --- a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SolarwindsContextPropagatorProvider.java +++ b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SolarwindsContextPropagatorProvider.java @@ -28,7 +28,7 @@ public class SolarwindsContextPropagatorProvider implements ConfigurablePropagat @Override public TextMapPropagator getPropagator(ConfigProperties config) { - return new SolarwindsContextPropagator(); + return TextMapPropagator.composite(new TraceStatePropagator(), new TraceOptionsPropagator()); } @Override diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/TraceOptionsPropagator.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/TraceOptionsPropagator.java new file mode 100644 index 00000000..66788de1 --- /dev/null +++ b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/TraceOptionsPropagator.java @@ -0,0 +1,93 @@ +/* + * © SolarWinds Worldwide, LLC. All rights reserved. + * + * 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 com.solarwinds.opentelemetry.extensions; + +import com.solarwinds.joboe.sampling.XtraceOptions; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.context.propagation.TextMapSetter; +import java.util.Collection; +import java.util.Collections; + +public class TraceOptionsPropagator implements TextMapPropagator { + static final String X_TRACE_OPTIONS = "X-Trace-Options"; + static final String X_TRACE_OPTIONS_SIGNATURE = "X-Trace-Options-Signature"; + + @Override + public Collection fields() { + return Collections.emptyList(); + } + + /** + * Injects the x-trace-options and its signature into the carrier with values provided by current + * context + * + * @param context trace context + * @param carrier the input to the method being instrumented. Usually a request of some kind + * @param setter the object that knows how to inject data into carrier using preconfigured keys + */ + @Override + public void inject(Context context, C carrier, TextMapSetter setter) { + if (carrier == null || !Span.fromContext(context).getSpanContext().isValid()) { + return; + } + + final String traceOptions = context.get(TriggerTraceContextKey.XTRACE_OPTIONS); + final String traceOptionsSignature = + context.get(TriggerTraceContextKey.XTRACE_OPTIONS_SIGNATURE); + + if (traceOptions != null) { + setter.set(carrier, X_TRACE_OPTIONS, traceOptions); + } + + if (traceOptionsSignature != null) { + setter.set(carrier, X_TRACE_OPTIONS_SIGNATURE, traceOptionsSignature); + } + } + + /** + * Extracts x-trace-options and its signature from carrier and populates the extracted data into + * the current trace context. + * + * @param context trace context + * @param carrier the input to the method being instrumented. Usually a request of some kind + * @param getter the object that knows how to extract data from carrier using preconfigured keys + * @return updated context + */ + @Override + public Context extract(Context context, C carrier, TextMapGetter getter) { + final String traceOptions = getter.get(carrier, X_TRACE_OPTIONS); + final String traceOptionsSignature = getter.get(carrier, X_TRACE_OPTIONS_SIGNATURE); + + final XtraceOptions xTraceOptions = + XtraceOptions.getXtraceOptions(traceOptions, traceOptionsSignature); + + if (xTraceOptions != null) { + context = context.with(TriggerTraceContextKey.KEY, xTraceOptions); + context = context.with(TriggerTraceContextKey.XTRACE_OPTIONS, traceOptions); + + if (traceOptionsSignature != null) { + context = + context.with(TriggerTraceContextKey.XTRACE_OPTIONS_SIGNATURE, traceOptionsSignature); + } + } + + return context; + } +} diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SolarwindsContextPropagator.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/TraceStatePropagator.java similarity index 79% rename from libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SolarwindsContextPropagator.java rename to libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/TraceStatePropagator.java index 3d6798ea..cb1957ad 100644 --- a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/SolarwindsContextPropagator.java +++ b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/TraceStatePropagator.java @@ -18,7 +18,6 @@ import static com.solarwinds.opentelemetry.extensions.SamplingUtil.SW_XTRACE_OPTIONS_RESP_KEY; -import com.solarwinds.joboe.sampling.XtraceOptions; import io.opentelemetry.api.trace.Span; import io.opentelemetry.api.trace.SpanContext; import io.opentelemetry.api.trace.TraceState; @@ -33,13 +32,11 @@ import java.util.Map; import java.util.Set; -public class SolarwindsContextPropagator implements TextMapPropagator { +public class TraceStatePropagator implements TextMapPropagator { private static final String SWI_TRACE_STATE_KEY = "sw"; static final String TRACE_PARENT = "traceparent"; static final String TRACE_STATE = "tracestate"; static final String W3C_TRACE_CONTEXT_HEADER = "sw.trace_context"; - static final String X_TRACE_OPTIONS = "X-Trace-Options"; - static final String X_TRACE_OPTIONS_SIGNATURE = "X-Trace-Options-Signature"; private static final List FIELDS = Collections.unmodifiableList( Arrays.asList(TRACE_PARENT, TRACE_STATE, W3C_TRACE_CONTEXT_HEADER)); @@ -55,8 +52,8 @@ public Collection fields() { } /** - * Injects the both the Solarwinds x-trace ID and the updated w3c `tracestate` with our x-trace ID - * prepended into the carrier with values provided by current context + * Injects the updated w3c `tracestate` with our x-trace ID prepended into the carrier with values + * provided by current context * * @param context trace context * @param carrier the input to the method being instrumented. Usually a request of some kind @@ -75,16 +72,6 @@ public void inject(Context context, C carrier, TextMapSetter setter) { final String swTraceStateValue = spanContext.getSpanId() + "-" + spanContext.getTraceFlags().asHex(); setter.set(carrier, TRACE_STATE, updateTraceState(traceState, swTraceStateValue)); - - final String traceOptions = context.get(TriggerTraceContextKey.XTRACE_OPTIONS); - final String traceOptionsSignature = - context.get(TriggerTraceContextKey.XTRACE_OPTIONS_SIGNATURE); - if (traceOptions != null) { - setter.set(carrier, X_TRACE_OPTIONS, traceOptions); - } - if (traceOptionsSignature != null) { - setter.set(carrier, X_TRACE_OPTIONS_SIGNATURE, traceOptionsSignature); - } } /** @@ -146,12 +133,13 @@ private String updateTraceState(TraceState traceState, String swTraceStateValue) } } } + return traceStateBuilder.toString(); } /** - * Extracts x-trace-options and tracestate from carrier and populates the extracted data into the - * current trace context. + * Extracts tracestate from carrier and populates the extracted data into the current trace + * context. * * @param context trace context * @param carrier the input to the method being instrumented. Usually a request of some kind @@ -160,27 +148,16 @@ private String updateTraceState(TraceState traceState, String swTraceStateValue) */ @Override public Context extract(Context context, C carrier, TextMapGetter getter) { - final String traceOptions = getter.get(carrier, X_TRACE_OPTIONS); - final String traceOptionsSignature = getter.get(carrier, X_TRACE_OPTIONS_SIGNATURE); - final XtraceOptions xTraceOptions = - XtraceOptions.getXtraceOptions(traceOptions, traceOptionsSignature); - if (xTraceOptions != null) { - context = context.with(TriggerTraceContextKey.KEY, xTraceOptions); - context = context.with(TriggerTraceContextKey.XTRACE_OPTIONS, traceOptions); - if (traceOptionsSignature != null) { - context = - context.with(TriggerTraceContextKey.XTRACE_OPTIONS_SIGNATURE, traceOptionsSignature); - } - } - final String traceState = getter.get(carrier, TRACE_STATE); if (traceState != null) { boolean anyMatch = Arrays.stream(traceState.split(",")).anyMatch(info -> !info.startsWith("sw=")); + if (anyMatch) { context = context.with(TraceStateKey.KEY, traceState); } } + return context; } } diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProvider.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProvider.java index ffd7de00..89f838ee 100644 --- a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProvider.java +++ b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProvider.java @@ -40,7 +40,6 @@ import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.MetricReaderModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.PeriodicMetricReaderModel; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.PropagatorModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.PushMetricExporterModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SamplerModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SpanExporterModel; @@ -98,13 +97,6 @@ public void customize(DeclarativeConfigurationCustomizer customizer) { addLogExporter(configurationModel); } - PropagatorModel propagatorModel = configurationModel.getPropagator(); - if (propagatorModel == null) { - propagatorModel = new PropagatorModel(); - configurationModel.setPropagator(propagatorModel); - } - - addContextPropagators(propagatorModel); return configurationModel; }); } @@ -172,18 +164,6 @@ private void addSampler(TracerProviderModel model) { } } - private void addContextPropagators(PropagatorModel model) { - String compositeList = model.getCompositeList(); - if (compositeList != null) { - model.setCompositeList( - String.format("%s,%s", compositeList, ContextPropagatorComponentProvider.COMPONENT_NAME)); - } else { - model.setCompositeList( - String.format( - "tracecontext,baggage,%s", ContextPropagatorComponentProvider.COMPONENT_NAME)); - } - } - private void addSpanExporter(OpenTelemetryConfigurationModel model) { TracerProviderModel tracerProvider = Objects.requireNonNull(model.getTracerProvider()); List processors = tracerProvider.getProcessors(); diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/ContextPropagatorComponentProvider.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/TraceOptionsComponentProvider.java similarity index 82% rename from libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/ContextPropagatorComponentProvider.java rename to libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/TraceOptionsComponentProvider.java index f33d115c..51f61b00 100644 --- a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/ContextPropagatorComponentProvider.java +++ b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/TraceOptionsComponentProvider.java @@ -17,15 +17,15 @@ package com.solarwinds.opentelemetry.extensions.config.provider; import com.google.auto.service.AutoService; -import com.solarwinds.opentelemetry.extensions.SolarwindsContextPropagator; +import com.solarwinds.opentelemetry.extensions.TraceOptionsPropagator; import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; import io.opentelemetry.context.propagation.TextMapPropagator; import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; @AutoService(ComponentProvider.class) -public class ContextPropagatorComponentProvider implements ComponentProvider { +public class TraceOptionsComponentProvider implements ComponentProvider { - public static final String COMPONENT_NAME = "swo/contextPropagator"; + public static final String COMPONENT_NAME = "swo_xtraceoptions"; @Override public Class getType() { @@ -39,6 +39,6 @@ public String getName() { @Override public TextMapPropagator create(DeclarativeConfigProperties declarativeConfigProperties) { - return new SolarwindsContextPropagator(); + return new TraceOptionsPropagator(); } } diff --git a/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/TraceStateComponentProvider.java b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/TraceStateComponentProvider.java new file mode 100644 index 00000000..d52139fb --- /dev/null +++ b/libs/shared/src/main/java/com/solarwinds/opentelemetry/extensions/config/provider/TraceStateComponentProvider.java @@ -0,0 +1,44 @@ +/* + * © SolarWinds Worldwide, LLC. All rights reserved. + * + * 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 com.solarwinds.opentelemetry.extensions.config.provider; + +import com.google.auto.service.AutoService; +import com.solarwinds.opentelemetry.extensions.TraceStatePropagator; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import io.opentelemetry.context.propagation.TextMapPropagator; +import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider; + +@AutoService(ComponentProvider.class) +public class TraceStateComponentProvider implements ComponentProvider { + + public static final String COMPONENT_NAME = "swo_tracestate"; + + @Override + public Class getType() { + return TextMapPropagator.class; + } + + @Override + public String getName() { + return COMPONENT_NAME; + } + + @Override + public TextMapPropagator create(DeclarativeConfigProperties declarativeConfigProperties) { + return new TraceStatePropagator(); + } +} diff --git a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/SolarwindsContextPropagatorTest.java b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/TraceOptionsPropagatorTest.java similarity index 58% rename from libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/SolarwindsContextPropagatorTest.java rename to libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/TraceOptionsPropagatorTest.java index c3822164..2ed2fafb 100644 --- a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/SolarwindsContextPropagatorTest.java +++ b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/TraceOptionsPropagatorTest.java @@ -20,10 +20,8 @@ import static com.solarwinds.opentelemetry.extensions.TriggerTraceContextKey.XTRACE_OPTIONS_SIGNATURE; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.atMostOnce; import static org.mockito.Mockito.mockStatic; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; @@ -41,7 +39,6 @@ import io.opentelemetry.context.propagation.TextMapSetter; import java.util.HashMap; import java.util.Map; -import java.util.Random; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.ArgumentCaptor; @@ -52,9 +49,9 @@ import org.mockito.junit.jupiter.MockitoExtension; @ExtendWith(MockitoExtension.class) -class SolarwindsContextPropagatorTest { +class TraceOptionsPropagatorTest { - @InjectMocks private SolarwindsContextPropagator solarwindsContextPropagator; + @InjectMocks private TraceOptionsPropagator traceOptionsPropagator; @Mock private TextMapSetter> textMapSetterMock; @@ -77,59 +74,16 @@ class SolarwindsContextPropagatorTest { @Test void verifyThatInjectReturnsSuccessfullyWhenCarrierIsNull() { - solarwindsContextPropagator.inject(Context.current(), null, textMapSetterMock); + traceOptionsPropagator.inject(Context.current(), null, textMapSetterMock); verify(textMapSetterMock, never()).set(any(), anyString(), anyString()); } @Test void verifyThatInjectReturnsSuccessfullyWhenSpanIsInvalid() { - solarwindsContextPropagator.inject(Context.current(), new HashMap<>(), textMapSetterMock); + traceOptionsPropagator.inject(Context.current(), new HashMap<>(), textMapSetterMock); verify(textMapSetterMock, never()).set(any(), anyString(), anyString()); } - @Test - void verifyThatTracestateIsUpdatedAndInjectedMaintainingLeftmostInserts() { - try (MockedStatic spanMockedStatic = mockStatic(Span.class)) { - spanMockedStatic.when(() -> Span.fromContext(any())).thenReturn(spanMock); - when(spanMock.getSpanContext()).thenReturn(spanContext); - - solarwindsContextPropagator.inject(Context.current(), new HashMap<>(), textMapSetterMock); - verify(textMapSetterMock, atMostOnce()) - .set(any(), anyString(), stringArgumentCaptor.capture()); - assertEquals( - String.format("sw=%s-00,test-key=test-value", "a2d8376f3cab2837"), - stringArgumentCaptor.getValue()); - } - } - - @Test - void verifyThatTracestateThatExceeds512CharactersIsTruncatedAndInjected() { - try (MockedStatic spanMockedStatic = mockStatic(Span.class)) { - StringBuilder builder = new StringBuilder(); - new Random().ints(512L).mapToObj(Integer::toString).forEach(builder::append); - - SpanContext spanContext = - SpanContext.create( - traceId, - spanId, - TraceFlags.getDefault(), - TraceState.builder() - .put("test-key", "test-value") - .put("test-key-0", builder.toString()) - .build()); - - spanMockedStatic.when(() -> Span.fromContext(any())).thenReturn(spanMock); - when(spanMock.getSpanContext()).thenReturn(spanContext); - - solarwindsContextPropagator.inject(Context.current(), new HashMap<>(), textMapSetterMock); - verify(textMapSetterMock, atMostOnce()) - .set(any(), anyString(), stringArgumentCaptor.capture()); - assertEquals( - String.format("sw=%s-00,test-key=test-value", "a2d8376f3cab2837"), - stringArgumentCaptor.getValue()); - } - } - @Test void verifyThatXtraceOptionsIsInjected() { try (MockedStatic spanMockedStatic = mockStatic(Span.class)) { @@ -139,9 +93,9 @@ void verifyThatXtraceOptionsIsInjected() { spanMockedStatic.when(() -> Span.fromContext(context)).thenReturn(spanMock); when(spanMock.getSpanContext()).thenReturn(spanContext); - solarwindsContextPropagator.inject(context, new HashMap<>(), textMapSetterMock); - verify(textMapSetterMock, times(2)).set(any(), anyString(), stringArgumentCaptor.capture()); - assertEquals(xtraceOptions, stringArgumentCaptor.getAllValues().get(1)); + traceOptionsPropagator.inject(context, new HashMap<>(), textMapSetterMock); + verify(textMapSetterMock, times(1)).set(any(), anyString(), stringArgumentCaptor.capture()); + assertEquals(xtraceOptions, stringArgumentCaptor.getValue()); } } @@ -154,9 +108,9 @@ void verifyThatXtraceOptionsSignatureIsInjected() { spanMockedStatic.when(() -> Span.fromContext(context)).thenReturn(spanMock); when(spanMock.getSpanContext()).thenReturn(spanContext); - solarwindsContextPropagator.inject(context, new HashMap<>(), textMapSetterMock); - verify(textMapSetterMock, times(2)).set(any(), anyString(), stringArgumentCaptor.capture()); - assertEquals(xtraceOptions, stringArgumentCaptor.getAllValues().get(1)); + traceOptionsPropagator.inject(context, new HashMap<>(), textMapSetterMock); + verify(textMapSetterMock, times(1)).set(any(), anyString(), stringArgumentCaptor.capture()); + assertEquals(xtraceOptions, stringArgumentCaptor.getValue()); } } @@ -172,7 +126,7 @@ void verifyThatXtraceOptionsIsExtractedAndPutIntoContext() { }; Context newContext = - solarwindsContextPropagator.extract(Context.current(), carrier, textMapGetterStub); + traceOptionsPropagator.extract(Context.current(), carrier, textMapGetterStub); XtraceOptions xtraceOptions = newContext.get(TriggerTraceContextKey.KEY); assertEquals(String.format("%s=%s;", key, value), newContext.get(XTRACE_OPTIONS)); @@ -199,35 +153,7 @@ void verifyThatXtraceOptionsSignatureIsExtractedAndPutIntoContext() { }; Context newContext = - solarwindsContextPropagator.extract(Context.current(), carrier, textMapGetterStub); + traceOptionsPropagator.extract(Context.current(), carrier, textMapGetterStub); assertEquals("test-sig", newContext.get(XTRACE_OPTIONS_SIGNATURE)); } - - @Test - void verifyThatTracestateIsExtractedAndPutIntoContext() { - final Map carrier = - new HashMap() { - { - put("tracestate", "trigger-trace=ok"); - } - }; - - Context newContext = - solarwindsContextPropagator.extract(Context.current(), carrier, textMapGetterStub); - assertEquals("trigger-trace=ok", newContext.get(TraceStateKey.KEY)); - } - - @Test - void verifyThatTracestateIsNotPutIntoContextWhenNoVendorInfo() { - final Map carrier = - new HashMap() { - { - put("tracestate", "sw=blah"); - } - }; - - Context newContext = - solarwindsContextPropagator.extract(Context.current(), carrier, textMapGetterStub); - assertNull(newContext.get(TraceStateKey.KEY)); - } } diff --git a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/TraceStatePropagatorTest.java b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/TraceStatePropagatorTest.java new file mode 100644 index 00000000..bb9d217b --- /dev/null +++ b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/TraceStatePropagatorTest.java @@ -0,0 +1,155 @@ +/* + * © SolarWinds Worldwide, LLC. All rights reserved. + * + * 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 com.solarwinds.opentelemetry.extensions; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.atMostOnce; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.solarwinds.opentelemetry.extensions.stubs.TextMapGetterStub; +import io.opentelemetry.api.trace.Span; +import io.opentelemetry.api.trace.SpanContext; +import io.opentelemetry.api.trace.TraceFlags; +import io.opentelemetry.api.trace.TraceState; +import io.opentelemetry.context.Context; +import io.opentelemetry.context.propagation.TextMapGetter; +import io.opentelemetry.context.propagation.TextMapSetter; +import java.util.HashMap; +import java.util.Map; +import java.util.Random; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class TraceStatePropagatorTest { + + @InjectMocks private TraceStatePropagator traceStatePropagator; + + @Mock private TextMapSetter> textMapSetterMock; + + private final TextMapGetter> textMapGetterStub = new TextMapGetterStub(); + + @Mock private Span spanMock; + + @Captor private ArgumentCaptor stringArgumentCaptor; + + private static final String traceId = "80ad68e98d3449dfc54098b38fc466ec"; + + private static final String spanId = "a2d8376f3cab2837"; + + private final SpanContext spanContext = + SpanContext.create( + traceId, + spanId, + TraceFlags.getDefault(), + TraceState.builder().put("test-key", "test-value").build()); + + @Test + void verifyThatInjectReturnsSuccessfullyWhenCarrierIsNull() { + traceStatePropagator.inject(Context.current(), null, textMapSetterMock); + verify(textMapSetterMock, never()).set(any(), anyString(), anyString()); + } + + @Test + void verifyThatInjectReturnsSuccessfullyWhenSpanIsInvalid() { + traceStatePropagator.inject(Context.current(), new HashMap<>(), textMapSetterMock); + verify(textMapSetterMock, never()).set(any(), anyString(), anyString()); + } + + @Test + void verifyThatTracestateIsUpdatedAndInjectedMaintainingLeftmostInserts() { + try (MockedStatic spanMockedStatic = mockStatic(Span.class)) { + spanMockedStatic.when(() -> Span.fromContext(any())).thenReturn(spanMock); + when(spanMock.getSpanContext()).thenReturn(spanContext); + + traceStatePropagator.inject(Context.current(), new HashMap<>(), textMapSetterMock); + verify(textMapSetterMock, atMostOnce()) + .set(any(), anyString(), stringArgumentCaptor.capture()); + assertEquals( + String.format("sw=%s-00,test-key=test-value", "a2d8376f3cab2837"), + stringArgumentCaptor.getValue()); + } + } + + @Test + void verifyThatTracestateThatExceeds512CharactersIsTruncatedAndInjected() { + try (MockedStatic spanMockedStatic = mockStatic(Span.class)) { + StringBuilder builder = new StringBuilder(); + new Random().ints(512L).mapToObj(Integer::toString).forEach(builder::append); + + SpanContext spanContext = + SpanContext.create( + traceId, + spanId, + TraceFlags.getDefault(), + TraceState.builder() + .put("test-key", "test-value") + .put("test-key-0", builder.toString()) + .build()); + + spanMockedStatic.when(() -> Span.fromContext(any())).thenReturn(spanMock); + when(spanMock.getSpanContext()).thenReturn(spanContext); + + traceStatePropagator.inject(Context.current(), new HashMap<>(), textMapSetterMock); + verify(textMapSetterMock, atMostOnce()) + .set(any(), anyString(), stringArgumentCaptor.capture()); + assertEquals( + String.format("sw=%s-00,test-key=test-value", "a2d8376f3cab2837"), + stringArgumentCaptor.getValue()); + } + } + + @Test + void verifyThatTracestateIsExtractedAndPutIntoContext() { + final Map carrier = + new HashMap() { + { + put("tracestate", "trigger-trace=ok"); + } + }; + + Context newContext = + traceStatePropagator.extract(Context.current(), carrier, textMapGetterStub); + assertEquals("trigger-trace=ok", newContext.get(TraceStateKey.KEY)); + } + + @Test + void verifyThatTracestateIsNotPutIntoContextWhenNoVendorInfo() { + final Map carrier = + new HashMap() { + { + put("tracestate", "sw=blah"); + } + }; + + Context newContext = + traceStatePropagator.extract(Context.current(), carrier, textMapGetterStub); + assertNull(newContext.get(TraceStateKey.KEY)); + } +} diff --git a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProviderTest.java b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProviderTest.java index aeddd1b0..7a4285df 100644 --- a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProviderTest.java +++ b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/SharedConfigCustomizerProviderTest.java @@ -37,7 +37,6 @@ import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.MetricReaderModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.OpenTelemetryConfigurationModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.PeriodicMetricReaderModel; -import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.PropagatorModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SamplerModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SimpleLogRecordProcessorModel; import io.opentelemetry.sdk.autoconfigure.declarativeconfig.model.SimpleSpanProcessorModel; @@ -111,11 +110,6 @@ void testCustomize() { assertNotNull(sampler.getExtensionProperties().get(SamplerComponentProvider.COMPONENT_NAME)); assertEquals(3, tracerProvider.getProcessors().size()); - assertTrue( - openTelemetryConfigurationModel - .getPropagator() - .getCompositeList() - .contains(ContextPropagatorComponentProvider.COMPONENT_NAME)); PeriodicMetricReaderModel periodic = openTelemetryConfigurationModel.getMeterProvider().getReaders().get(0).getPeriodic(); @@ -179,11 +173,6 @@ void testCustomize1() { assertNotNull(sampler.getExtensionProperties().get(SamplerComponentProvider.COMPONENT_NAME)); assertEquals(3, tracerProvider.getProcessors().size()); - assertTrue( - openTelemetryConfigurationModel - .getPropagator() - .getCompositeList() - .contains(ContextPropagatorComponentProvider.COMPONENT_NAME)); PeriodicMetricReaderModel periodic = openTelemetryConfigurationModel.getMeterProvider().getReaders().get(0).getPeriodic(); @@ -711,7 +700,6 @@ void tracesNotConfiguredWhenTracerProviderAbsent() { assertNull(openTelemetryConfigurationModel.getTracerProvider()); assertNotNull(openTelemetryConfigurationModel.getLoggerProvider()); assertNotNull(openTelemetryConfigurationModel.getMeterProvider()); - assertNotNull(openTelemetryConfigurationModel.getPropagator()); } @Test @@ -785,37 +773,6 @@ void logsNotConfiguredWhenLoggerProviderAbsent() { assertNotNull(openTelemetryConfigurationModel.getMeterProvider()); } - @Test - void propagatorsAppendedToExistingCompositeList() { - OpenTelemetryConfigurationModel openTelemetryConfigurationModel = - withInstrumentation( - new OpenTelemetryConfigurationModel() - .setPropagator(new PropagatorModel().setCompositeList("tracecontext,baggage")), - new ExperimentalInstrumentationModel() - .setJava( - new ExperimentalLanguageSpecificInstrumentationModel() - .setAdditionalProperty( - "solarwinds", - new ExperimentalLanguageSpecificInstrumentationPropertyModel() - .setAdditionalProperty( - ConfigProperty.AGENT_SERVICE_KEY.getConfigFileKey(), - "token:service") - .setAdditionalProperty( - ConfigProperty.AGENT_COLLECTOR.getConfigFileKey(), - "apm.collector.com")))); - - doNothing() - .when(declarativeConfigurationCustomizerMock) - .addModelCustomizer(functionArgumentCaptor.capture()); - - tested.customize(declarativeConfigurationCustomizerMock); - functionArgumentCaptor.getValue().apply(openTelemetryConfigurationModel); - - assertEquals( - "tracecontext,baggage," + ContextPropagatorComponentProvider.COMPONENT_NAME, - openTelemetryConfigurationModel.getPropagator().getCompositeList()); - } - @Test void metricsExportDisabledWhenMeterProviderAbsent() { OpenTelemetryConfigurationModel openTelemetryConfigurationModel = diff --git a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/TraceOptionsComponentProviderTest.java b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/TraceOptionsComponentProviderTest.java new file mode 100644 index 00000000..95b1f090 --- /dev/null +++ b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/TraceOptionsComponentProviderTest.java @@ -0,0 +1,42 @@ +/* + * © SolarWinds Worldwide, LLC. All rights reserved. + * + * 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 com.solarwinds.opentelemetry.extensions.config.provider; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +import com.solarwinds.opentelemetry.extensions.TraceOptionsPropagator; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +class TraceOptionsComponentProviderTest { + + private final TraceOptionsComponentProvider tested = new TraceOptionsComponentProvider(); + + @Test + void testName() { + assertEquals("swo_xtraceoptions", tested.getName()); + } + + @Test + void createReturnsExpectedPropagator() { + assertInstanceOf( + TraceOptionsPropagator.class, + tested.create(Mockito.mock(DeclarativeConfigProperties.class))); + } +} diff --git a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/ContextPropagatorComponentProviderTest.java b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/TraceStateComponentProviderTest.java similarity index 57% rename from libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/ContextPropagatorComponentProviderTest.java rename to libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/TraceStateComponentProviderTest.java index 03589ce6..dda2e09d 100644 --- a/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/ContextPropagatorComponentProviderTest.java +++ b/libs/shared/src/test/java/com/solarwinds/opentelemetry/extensions/config/provider/TraceStateComponentProviderTest.java @@ -17,16 +17,25 @@ package com.solarwinds.opentelemetry.extensions.config.provider; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import com.solarwinds.opentelemetry.extensions.TraceStatePropagator; +import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties; import org.junit.jupiter.api.Test; +import org.mockito.Mockito; -class ContextPropagatorComponentProviderTest { +class TraceStateComponentProviderTest { - private final ContextPropagatorComponentProvider tested = - new ContextPropagatorComponentProvider(); + private final TraceStateComponentProvider tested = new TraceStateComponentProvider(); @Test void testName() { - assertEquals("swo/contextPropagator", tested.getName()); + assertEquals("swo_tracestate", tested.getName()); + } + + @Test + void createReturnsExpectedPropagator() { + assertInstanceOf( + TraceStatePropagator.class, tested.create(Mockito.mock(DeclarativeConfigProperties.class))); } } diff --git a/libs/shared/src/test/resources/sdk-config.yaml b/libs/shared/src/test/resources/sdk-config.yaml index 55b4ee4e..fc7bead7 100644 --- a/libs/shared/src/test/resources/sdk-config.yaml +++ b/libs/shared/src/test/resources/sdk-config.yaml @@ -29,7 +29,11 @@ attribute_limits: # Configure text map context propagators. propagator: # Configure the set of propagators to include in the composite text map propagator. - composite: [ tracecontext, baggage, swo/contextPropagator ] + composite: + - tracecontext: + - baggage: + - swo_tracestate: + - swo_xtraceoptions: # Configure tracer provider. tracer_provider: diff --git a/sdk-config.yaml b/sdk-config.yaml index 1a561bda..11ada32e 100644 --- a/sdk-config.yaml +++ b/sdk-config.yaml @@ -30,6 +30,15 @@ distribution: # Configure general attribute limits. See also tracer_provider.limits, logger_provider.limits. attribute_limits: +# Configure text map context propagators. +propagator: + # Configure the set of propagators to include in the composite text map propagator. + composite: + - tracecontext: + - baggage: + - swo_tracestate: + - swo_xtraceoptions: + # Configure tracer provider. tracer_provider: processors: diff --git a/smoke-tests/sdk-config.yaml b/smoke-tests/sdk-config.yaml index 583f372b..295a4689 100644 --- a/smoke-tests/sdk-config.yaml +++ b/smoke-tests/sdk-config.yaml @@ -36,6 +36,14 @@ attribute_limits: # Configure max attribute count. attribute_count_limit: 128 +# Configure text map context propagators. +propagator: + # Configure the set of propagators to include in the composite text map propagator. + composite: + - tracecontext: + - baggage: + - swo_tracestate: + - swo_xtraceoptions: # Configure tracer provider. tracer_provider: