344 lines
10 KiB
Groovy
344 lines
10 KiB
Groovy
/*
|
|
* Copyright The OpenTelemetry Authors
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import io.opentelemetry.instrumentation.test.AgentTestTrait
|
|
import io.opentelemetry.instrumentation.test.asserts.TraceAssert
|
|
import io.opentelemetry.instrumentation.test.base.HttpServerTest
|
|
import io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint
|
|
import io.opentelemetry.sdk.trace.data.SpanData
|
|
import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
|
|
import spock.lang.Unroll
|
|
|
|
import java.util.concurrent.TimeUnit
|
|
|
|
import static io.opentelemetry.api.trace.SpanKind.INTERNAL
|
|
import static io.opentelemetry.api.trace.SpanKind.SERVER
|
|
import static io.opentelemetry.api.trace.StatusCode.ERROR
|
|
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.EXCEPTION
|
|
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.PATH_PARAM
|
|
import static io.opentelemetry.instrumentation.testing.junit.http.ServerEndpoint.SUCCESS
|
|
import static java.util.concurrent.TimeUnit.SECONDS
|
|
import static org.junit.jupiter.api.Assumptions.assumeTrue
|
|
|
|
abstract class AbstractJaxRsHttpServerTest<S> extends HttpServerTest<S> implements AgentTestTrait {
|
|
|
|
abstract void awaitBarrier(int amount, TimeUnit timeUnit)
|
|
|
|
def "test super method without @Path"() {
|
|
given:
|
|
def response = client.get(address.resolve("test-resource-super").toString()).aggregate().join()
|
|
|
|
expect:
|
|
response.status().code() == SUCCESS.status
|
|
response.contentUtf8() == SUCCESS.body
|
|
|
|
assertTraces(1) {
|
|
trace(0, 2) {
|
|
span(0) {
|
|
hasNoParent()
|
|
kind SERVER
|
|
name "GET " + getContextPath() + "/test-resource-super"
|
|
}
|
|
span(1) {
|
|
name "controller"
|
|
kind INTERNAL
|
|
childOf span(0)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
def "test interface method with @Path"() {
|
|
assumeTrue(testInterfaceMethodWithPath())
|
|
|
|
given:
|
|
def response = client.get(address.resolve("test-resource-interface/call").toString()).aggregate().join()
|
|
|
|
expect:
|
|
response.status().code() == SUCCESS.status
|
|
response.contentUtf8() == SUCCESS.body
|
|
|
|
assertTraces(1) {
|
|
trace(0, 2) {
|
|
span(0) {
|
|
hasNoParent()
|
|
kind SERVER
|
|
name "GET " + getContextPath() + "/test-resource-interface/call"
|
|
}
|
|
span(1) {
|
|
name "controller"
|
|
kind INTERNAL
|
|
childOf span(0)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
def "test sub resource locator"() {
|
|
given:
|
|
def response = client.get(address.resolve("test-sub-resource-locator/call/sub").toString()).aggregate().join()
|
|
|
|
expect:
|
|
response.status().code() == SUCCESS.status
|
|
response.contentUtf8() == SUCCESS.body
|
|
|
|
assertTraces(1) {
|
|
trace(0, 5) {
|
|
span(0) {
|
|
hasNoParent()
|
|
kind SERVER
|
|
name "GET " + getContextPath() + "/test-sub-resource-locator/call/sub"
|
|
}
|
|
span(1) {
|
|
name "JaxRsSubResourceLocatorTestResource.call"
|
|
kind INTERNAL
|
|
childOf span(0)
|
|
}
|
|
span(2) {
|
|
name "controller"
|
|
kind INTERNAL
|
|
childOf span(1)
|
|
}
|
|
span(3) {
|
|
name "SubResource.call"
|
|
kind INTERNAL
|
|
childOf span(0)
|
|
}
|
|
span(4) {
|
|
name "controller"
|
|
kind INTERNAL
|
|
childOf span(3)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Unroll
|
|
def "should handle #desc AsyncResponse"() {
|
|
given:
|
|
def url = address.resolve("async?action=${action}").toString()
|
|
|
|
when: "async call is started"
|
|
def futureResponse = client.get(url).aggregate()
|
|
|
|
then: "there are no traces yet"
|
|
assertTraces(0) {
|
|
}
|
|
|
|
when: "barrier is released and resource class sends response"
|
|
awaitBarrier(1, SECONDS)
|
|
def response = futureResponse.join()
|
|
|
|
then:
|
|
response.status().code() == statusCode
|
|
bodyPredicate(response.contentUtf8())
|
|
|
|
def spanCount = 2
|
|
def hasSendError = asyncCancelHasSendError() && action == "cancel"
|
|
if (hasSendError) {
|
|
spanCount++
|
|
}
|
|
assertTraces(1) {
|
|
trace(0, spanCount) {
|
|
asyncServerSpan(it, 0, url, statusCode)
|
|
handlerSpan(it, 1, span(0), "asyncOp", isCancelled, isError, errorMessage)
|
|
if (hasSendError) {
|
|
sendErrorSpan(it, 2, span(1))
|
|
}
|
|
}
|
|
}
|
|
|
|
where:
|
|
desc | action | statusCode | bodyPredicate | isCancelled | isError | errorMessage
|
|
"successful" | "succeed" | 200 | { it == "success" } | false | false | null
|
|
"failing" | "throw" | 500 | { it == "failure" } | false | true | "failure"
|
|
"canceled" | "cancel" | 503 | { it instanceof String } | true | false | null
|
|
}
|
|
|
|
@Unroll
|
|
def "should handle #desc CompletionStage (JAX-RS 2.1+ only)"() {
|
|
assumeTrue(shouldTestCompletableStageAsync())
|
|
given:
|
|
def url = address.resolve("async-completion-stage?action=${action}").toString()
|
|
|
|
when: "async call is started"
|
|
def futureResponse = client.get(url).aggregate()
|
|
|
|
then: "there are no traces yet"
|
|
assertTraces(0) {
|
|
}
|
|
|
|
when: "barrier is released and resource class sends response"
|
|
awaitBarrier(1, SECONDS)
|
|
def response = futureResponse.join()
|
|
|
|
then:
|
|
response.status().code() == statusCode
|
|
bodyPredicate(response.contentUtf8())
|
|
|
|
assertTraces(1) {
|
|
trace(0, 2) {
|
|
asyncServerSpan(it, 0, url, statusCode)
|
|
handlerSpan(it, 1, span(0), "jaxRs21Async", false, isError, errorMessage)
|
|
}
|
|
}
|
|
|
|
where:
|
|
desc | action | statusCode | bodyPredicate | isError | errorMessage
|
|
"successful" | "succeed" | 200 | { it == "success" } | false | null
|
|
"failing" | "throw" | 500 | { it == "failure" } | true | "failure"
|
|
}
|
|
|
|
@Override
|
|
boolean hasHandlerSpan(ServerEndpoint endpoint) {
|
|
true
|
|
}
|
|
|
|
@Override
|
|
boolean testNotFound() {
|
|
false
|
|
}
|
|
|
|
@Override
|
|
boolean testPathParam() {
|
|
true
|
|
}
|
|
|
|
boolean testInterfaceMethodWithPath() {
|
|
true
|
|
}
|
|
|
|
boolean asyncCancelHasSendError() {
|
|
false
|
|
}
|
|
|
|
boolean shouldTestCompletableStageAsync() {
|
|
Boolean.getBoolean("testLatestDeps")
|
|
}
|
|
|
|
@Override
|
|
boolean hasResponseCustomizer(ServerEndpoint endpoint) {
|
|
true
|
|
}
|
|
|
|
@Override
|
|
void serverSpan(TraceAssert trace,
|
|
int index,
|
|
String traceID = null,
|
|
String parentID = null,
|
|
String method = "GET",
|
|
Long responseContentLength = null,
|
|
ServerEndpoint endpoint = SUCCESS,
|
|
String spanID = null) {
|
|
serverSpan(trace, index, traceID, parentID, spanID, method,
|
|
endpoint == PATH_PARAM ? getContextPath() + "/path/{id}/param" : endpoint.resolvePath(address).path,
|
|
endpoint.resolve(address),
|
|
endpoint.status,
|
|
endpoint.query)
|
|
}
|
|
|
|
void asyncServerSpan(TraceAssert trace,
|
|
int index,
|
|
String url,
|
|
int statusCode) {
|
|
def rawUrl = URI.create(url).toURL()
|
|
serverSpan(trace, index, null, null, null, "GET",
|
|
rawUrl.path,
|
|
rawUrl.toURI(),
|
|
statusCode,
|
|
null)
|
|
}
|
|
|
|
void serverSpan(TraceAssert trace,
|
|
int index,
|
|
String traceID,
|
|
String parentID,
|
|
String spanID,
|
|
String method,
|
|
String path,
|
|
URI fullUrl,
|
|
int statusCode,
|
|
String query) {
|
|
trace.span(index) {
|
|
name method + " " + path
|
|
kind SERVER
|
|
if (statusCode >= 500) {
|
|
status ERROR
|
|
}
|
|
if (traceID != null) {
|
|
traceId traceID
|
|
}
|
|
if (parentID != null) {
|
|
parentSpanId parentID
|
|
} else {
|
|
hasNoParent()
|
|
}
|
|
if (spanID != null) {
|
|
spanId spanID
|
|
}
|
|
attributes {
|
|
"net.protocol.name" "http"
|
|
"net.protocol.version" "1.1"
|
|
"$SemanticAttributes.NET_HOST_NAME" fullUrl.host
|
|
"$SemanticAttributes.NET_HOST_PORT" fullUrl.port
|
|
"$SemanticAttributes.NET_SOCK_PEER_ADDR" "127.0.0.1"
|
|
"$SemanticAttributes.NET_SOCK_PEER_PORT" Long
|
|
"$SemanticAttributes.NET_SOCK_HOST_ADDR" "127.0.0.1"
|
|
"$SemanticAttributes.HTTP_SCHEME" fullUrl.getScheme()
|
|
"$SemanticAttributes.HTTP_TARGET" fullUrl.getPath() + (fullUrl.getQuery() != null ? "?" + fullUrl.getQuery() : "")
|
|
"$SemanticAttributes.HTTP_METHOD" method
|
|
"$SemanticAttributes.HTTP_STATUS_CODE" statusCode
|
|
"$SemanticAttributes.USER_AGENT_ORIGINAL" TEST_USER_AGENT
|
|
"$SemanticAttributes.HTTP_CLIENT_IP" TEST_CLIENT_IP
|
|
// Optional
|
|
"$SemanticAttributes.HTTP_RESPONSE_CONTENT_LENGTH" { it == null || it instanceof Long }
|
|
"$SemanticAttributes.HTTP_ROUTE" path
|
|
if (fullUrl.getPath().endsWith(ServerEndpoint.CAPTURE_HEADERS.getPath())) {
|
|
"http.request.header.x_test_request" { it == ["test"] }
|
|
"http.response.header.x_test_response" { it == ["test"] }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
void handlerSpan(TraceAssert trace,
|
|
int index,
|
|
Object parent,
|
|
String method = "GET",
|
|
ServerEndpoint endpoint = SUCCESS) {
|
|
handlerSpan(trace, index, parent,
|
|
endpoint.name().toLowerCase(),
|
|
false,
|
|
endpoint == EXCEPTION,
|
|
EXCEPTION.body)
|
|
}
|
|
|
|
void handlerSpan(TraceAssert trace,
|
|
int index,
|
|
Object parent,
|
|
String methodName,
|
|
boolean isCancelled,
|
|
boolean isError,
|
|
String exceptionMessage = null) {
|
|
trace.span(index) {
|
|
name "JaxRsTestResource.${methodName}"
|
|
kind INTERNAL
|
|
if (isError) {
|
|
status ERROR
|
|
errorEvent(Exception, exceptionMessage)
|
|
}
|
|
childOf((SpanData) parent)
|
|
attributes {
|
|
"$SemanticAttributes.CODE_NAMESPACE" "test.JaxRsTestResource"
|
|
"$SemanticAttributes.CODE_FUNCTION" methodName
|
|
if (isCancelled) {
|
|
"jaxrs.canceled" true
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|