Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
093f1c9
commit 1163c97
Showing
11 changed files
with
935 additions
and
0 deletions.
There are no files selected for viewing
155 changes: 155 additions & 0 deletions
155
.../src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/rest/PropagatorSpiTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
/* | ||
* Copyright (c) 2023 Contributors to the Eclipse Foundation | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information regarding copyright ownership. | ||
* | ||
* 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 org.eclipse.microprofile.telemetry.tracing.tck.rest; | ||
|
||
import static java.net.HttpURLConnection.HTTP_OK; | ||
import static org.eclipse.microprofile.telemetry.tracing.tck.rest.PropagationHelper.SpanResourceClient; | ||
|
||
import java.net.URL; | ||
|
||
import org.eclipse.microprofile.telemetry.tracing.tck.TestLibraries; | ||
import org.eclipse.microprofile.telemetry.tracing.tck.exporter.InMemorySpanExporter; | ||
import org.eclipse.microprofile.telemetry.tracing.tck.exporter.InMemorySpanExporterProvider; | ||
import org.jboss.arquillian.container.test.api.Deployment; | ||
import org.jboss.arquillian.test.api.ArquillianResource; | ||
import org.jboss.arquillian.testng.Arquillian; | ||
import org.jboss.shrinkwrap.api.ShrinkWrap; | ||
import org.jboss.shrinkwrap.api.asset.EmptyAsset; | ||
import org.jboss.shrinkwrap.api.asset.StringAsset; | ||
import org.jboss.shrinkwrap.api.spec.WebArchive; | ||
import org.testng.Assert; | ||
import org.testng.annotations.BeforeMethod; | ||
import org.testng.annotations.Test; | ||
|
||
import io.opentelemetry.api.baggage.Baggage; | ||
import io.opentelemetry.api.common.AttributeKey; | ||
import io.opentelemetry.api.trace.Span; | ||
import io.opentelemetry.api.trace.SpanKind; | ||
import io.opentelemetry.context.Scope; | ||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider; | ||
import io.opentelemetry.sdk.autoconfigure.spi.traces.ConfigurableSpanExporterProvider; | ||
import io.opentelemetry.sdk.trace.data.SpanData; | ||
import jakarta.inject.Inject; | ||
import jakarta.ws.rs.ApplicationPath; | ||
import jakarta.ws.rs.GET; | ||
import jakarta.ws.rs.Path; | ||
import jakarta.ws.rs.client.ClientBuilder; | ||
import jakarta.ws.rs.client.WebTarget; | ||
import jakarta.ws.rs.core.Application; | ||
import jakarta.ws.rs.core.Context; | ||
import jakarta.ws.rs.core.HttpHeaders; | ||
import jakarta.ws.rs.core.Response; | ||
|
||
public class PropagatorSpiTest extends Arquillian { | ||
public static final String TEST_VALUE = "test-value"; | ||
|
||
public static final String TEST_KEY = "test-key"; | ||
|
||
@Deployment | ||
public static WebArchive createDeployment() { | ||
return ShrinkWrap.create(WebArchive.class) | ||
.addClasses(TestPropagator.class, TestPropagatorProvider.class, InMemorySpanExporter.class, | ||
InMemorySpanExporterProvider.class, PropagationHelper.class, | ||
SpanResourceClient.class) | ||
.addAsServiceProvider(ConfigurableSpanExporterProvider.class, InMemorySpanExporterProvider.class) | ||
.addAsServiceProvider(ConfigurablePropagatorProvider.class, TestPropagatorProvider.class) | ||
.addAsLibrary(TestLibraries.AWAITILITY_LIB) | ||
.addAsResource( | ||
new StringAsset("otel.sdk.disabled=false\notel.propagators=" + TestPropagatorProvider.NAME | ||
+ "\notel.traces.exporter=in-memory"), | ||
"META-INF/microprofile-config.properties") | ||
.addAsManifestResource(EmptyAsset.INSTANCE, "beans.xml"); | ||
|
||
} | ||
|
||
SpanResourceClient client; | ||
|
||
@ArquillianResource | ||
URL url; | ||
|
||
@Inject | ||
InMemorySpanExporter exporter; | ||
|
||
@Inject | ||
Baggage baggage; | ||
|
||
@BeforeMethod | ||
void setUp() { | ||
// Only want to run on server | ||
if (exporter != null) { | ||
exporter.reset(); | ||
} | ||
} | ||
|
||
@Test | ||
void testSPIPropagator() { | ||
try (Scope s = baggage.toBuilder().put(TEST_KEY, TEST_VALUE).build().makeCurrent()) { | ||
WebTarget target = ClientBuilder.newClient().target(url.toString()).path("baggage"); | ||
Response response = target.request().get(); | ||
Assert.assertEquals(response.getStatus(), HTTP_OK); | ||
} | ||
|
||
exporter.assertSpanCount(2); | ||
|
||
SpanData server = exporter.getFirst(SpanKind.SERVER); | ||
Assert.assertEquals(TEST_VALUE, server.getAttributes().get(AttributeKey.stringKey(TEST_KEY))); | ||
|
||
SpanData client = exporter.getFirst(SpanKind.CLIENT); | ||
// Check that trace context propagation worked by checking that the parent was set correctly | ||
Assert.assertEquals(server.getParentSpanId(), client.getSpanId());; | ||
} | ||
|
||
@Path("/baggage") | ||
public static class BaggageResource { | ||
@Inject | ||
Baggage baggage; | ||
|
||
@Inject | ||
private Span span; | ||
|
||
@GET | ||
public Response get(@Context HttpHeaders headers) { | ||
try { | ||
// Check the TestPropagator headers were used | ||
Assert.assertNotNull(headers.getHeaderString(TestPropagator.TRACE_KEY)); | ||
Assert.assertNotNull(headers.getHeaderString(TestPropagator.BAGGAGE_KEY)); | ||
|
||
// Test that the default W3C headers were not used | ||
Assert.assertNull(headers.getHeaderString("traceparent")); | ||
Assert.assertNull(headers.getHeaderString("tracestate")); | ||
Assert.assertNull(headers.getHeaderString("baggage")); | ||
|
||
// Copy TEST_KEY from baggage into a span attribute | ||
span.setAttribute(TEST_KEY, baggage.getEntryValue(TEST_KEY)); | ||
return Response.ok().build(); | ||
} catch (Throwable e) { | ||
// An error here won't get reported back fully, so output it to the log as well | ||
System.err.println("Baggage Resource Exception:"); | ||
e.printStackTrace(); | ||
throw e; | ||
} | ||
} | ||
} | ||
|
||
@ApplicationPath("/") | ||
public static class RestApplication extends Application { | ||
|
||
} | ||
} |
189 changes: 189 additions & 0 deletions
189
...tck/src/main/java/org/eclipse/microprofile/telemetry/tracing/tck/rest/TestPropagator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,189 @@ | ||
/* | ||
* Copyright (c) 2023 Contributors to the Eclipse Foundation | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information regarding copyright ownership. | ||
* | ||
* 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 org.eclipse.microprofile.telemetry.tracing.tck.rest; | ||
|
||
import static java.util.Collections.unmodifiableList; | ||
|
||
import java.net.URLDecoder; | ||
import java.net.URLEncoder; | ||
import java.nio.charset.StandardCharsets; | ||
import java.util.Arrays; | ||
import java.util.Collection; | ||
import java.util.List; | ||
import java.util.Map.Entry; | ||
|
||
import io.opentelemetry.api.baggage.Baggage; | ||
import io.opentelemetry.api.baggage.BaggageBuilder; | ||
import io.opentelemetry.api.baggage.BaggageEntry; | ||
import io.opentelemetry.api.baggage.BaggageEntryMetadata; | ||
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.api.trace.TraceStateBuilder; | ||
import io.opentelemetry.context.Context; | ||
import io.opentelemetry.context.propagation.TextMapGetter; | ||
import io.opentelemetry.context.propagation.TextMapPropagator; | ||
import io.opentelemetry.context.propagation.TextMapSetter; | ||
|
||
/** | ||
* A basic propagator for span context and baggage | ||
* <p> | ||
* Span information is passed in the TEST-SPAN key with format | ||
* {@code traceId;spanId;flags;statekey1=value,statekey2=value} | ||
* <p> | ||
* Baggage information is passed in the TEST-BAGGAGE key with format | ||
* {@code key,value,metadata;key2,value,metadata;key3,value,metadata;...} | ||
* <p> | ||
* All individual values are urlencoded to make parsing easy (don't have to worry about values containing separator | ||
* characters) | ||
*/ | ||
public class TestPropagator implements TextMapPropagator { | ||
|
||
public static final String BAGGAGE_KEY = "TEST-BAGGAGE"; | ||
public static final String TRACE_KEY = "TEST-SPAN"; | ||
|
||
private static final List<String> FIELDS = unmodifiableList(Arrays.asList(BAGGAGE_KEY, TRACE_KEY)); | ||
|
||
/** {@inheritDoc} */ | ||
@Override | ||
public Collection<String> fields() { | ||
return FIELDS; | ||
} | ||
|
||
/** {@inheritDoc} */ | ||
@Override | ||
public <C> Context extract(Context context, C carrier, TextMapGetter<C> getter) { | ||
// extract data from carrier using getter and put it into context | ||
|
||
String baggageString = getter.get(carrier, BAGGAGE_KEY); | ||
if (baggageString != null && !baggageString.isEmpty()) { | ||
Baggage baggage = deserializeBaggage(baggageString); | ||
context = context.with(baggage); | ||
} | ||
|
||
String traceString = getter.get(carrier, TRACE_KEY); | ||
if (traceString != null && !traceString.isEmpty()) { | ||
Span span = deserializeSpan(traceString); | ||
context = context.with(span); | ||
} | ||
|
||
return context; | ||
} | ||
|
||
/** {@inheritDoc} */ | ||
@Override | ||
public <C> void inject(Context context, C carrier, TextMapSetter<C> setter) { | ||
// take data from context and inject it into carrier using setter | ||
Baggage baggage = Baggage.fromContextOrNull(context); | ||
if (baggage != null && !baggage.isEmpty()) { | ||
setter.set(carrier, BAGGAGE_KEY, serializeBaggage(baggage)); | ||
} | ||
|
||
Span span = Span.fromContextOrNull(context); | ||
if (span != null && span.getSpanContext().isValid()) { | ||
setter.set(carrier, TRACE_KEY, serializeSpan(span.getSpanContext())); | ||
} | ||
} | ||
|
||
private String serializeBaggage(Baggage baggage) { | ||
StringBuffer baggageString = new StringBuffer(); | ||
boolean first = true; | ||
for (Entry<String, BaggageEntry> entry : baggage.asMap().entrySet()) { | ||
if (!first) { | ||
baggageString.append(';'); | ||
first = false; | ||
} | ||
baggageString.append(encode(entry.getKey())) | ||
.append(',') | ||
.append(encode(entry.getValue().getValue())) | ||
.append(',') | ||
.append(encode(entry.getValue().getMetadata().getValue())); | ||
} | ||
return baggageString.toString(); | ||
} | ||
|
||
private Baggage deserializeBaggage(String string) { | ||
BaggageBuilder builder = Baggage.empty().toBuilder(); | ||
for (String entry : string.split(";")) { | ||
if (entry.isEmpty()) { | ||
continue; | ||
} | ||
String[] parts = entry.split(",", -1); // -1 -> keep trailing empty strings | ||
builder.put(decode(parts[0]), | ||
decode(parts[1]), | ||
BaggageEntryMetadata.create(decode(parts[2]))); | ||
} | ||
return builder.build(); | ||
} | ||
|
||
private String serializeSpan(SpanContext span) { | ||
StringBuffer spanString = new StringBuffer(); | ||
spanString.append(span.getTraceId()) | ||
.append(';') | ||
.append(span.getSpanId()) | ||
.append(';') | ||
.append(span.getTraceFlags().asHex()) | ||
.append(';'); | ||
boolean first = true; | ||
for (Entry<String, String> entry : span.getTraceState().asMap().entrySet()) { | ||
if (first) { | ||
spanString.append(','); | ||
first = false; | ||
} | ||
|
||
spanString.append(encode(entry.getKey())) | ||
.append('=') | ||
.append(encode(entry.getValue())); | ||
} | ||
|
||
return spanString.toString(); | ||
} | ||
|
||
private Span deserializeSpan(String string) { | ||
String[] parts = string.split(";", -1); | ||
String traceId = decode(parts[0]); | ||
String spanId = decode(parts[1]); | ||
TraceFlags flags = TraceFlags.fromHex(decode(parts[2]), 0); | ||
|
||
TraceStateBuilder stateBuilder = TraceState.builder(); | ||
for (String entry : parts[3].split(",")) { | ||
if (entry.isEmpty()) { | ||
continue; | ||
} | ||
String[] entryParts = entry.split("="); | ||
stateBuilder.put(decode(entryParts[0]), | ||
decode(entryParts[1])); | ||
} | ||
|
||
SpanContext spanContext = SpanContext.create(traceId, spanId, flags, stateBuilder.build()); | ||
return Span.wrap(spanContext); | ||
} | ||
|
||
private String encode(String s) { | ||
return URLEncoder.encode(s, StandardCharsets.UTF_8); | ||
} | ||
|
||
private String decode(String s) { | ||
return URLDecoder.decode(s, StandardCharsets.UTF_8); | ||
} | ||
|
||
} |
43 changes: 43 additions & 0 deletions
43
...main/java/org/eclipse/microprofile/telemetry/tracing/tck/rest/TestPropagatorProvider.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/* | ||
* Copyright (c) 2023 Contributors to the Eclipse Foundation | ||
* | ||
* See the NOTICE file(s) distributed with this work for additional | ||
* information regarding copyright ownership. | ||
* | ||
* 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 org.eclipse.microprofile.telemetry.tracing.tck.rest; | ||
|
||
import io.opentelemetry.context.propagation.TextMapPropagator; | ||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; | ||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurablePropagatorProvider; | ||
|
||
public class TestPropagatorProvider implements ConfigurablePropagatorProvider { | ||
|
||
public static final String NAME = "test-propagator"; | ||
|
||
/** {@inheritDoc} */ | ||
@Override | ||
public String getName() { | ||
return NAME; | ||
} | ||
|
||
/** {@inheritDoc} */ | ||
@Override | ||
public TextMapPropagator getPropagator(ConfigProperties arg0) { | ||
return new TestPropagator(); | ||
} | ||
|
||
} |
Oops, something went wrong.