diff --git a/dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpServerInstrumentationTest.groovy b/dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpServerInstrumentationTest.groovy index ef63868cf4..5c158ef414 100644 --- a/dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpServerInstrumentationTest.groovy +++ b/dd-java-agent/instrumentation/akka-http-10.0/src/test/groovy/AkkaHttpServerInstrumentationTest.groovy @@ -20,7 +20,7 @@ abstract class AkkaHttpServerInstrumentationTest extends HttpServerTest { + + @Shared + Vertx vertx = Vertx.vertx(new VertxOptions()) + @Shared + WebClient client = WebClient.create(vertx) + @Shared + CircuitBreaker breaker = CircuitBreaker.create("my-circuit-breaker", vertx, + new CircuitBreakerOptions() + .setTimeout(-1) // Disable the timeout otherwise it makes each test take this long. + ) + + @Override + int doRequest(String method, URI uri, Map headers, Closure callback) { + def request = client.request(HttpMethod.valueOf(method), uri.port, uri.host, "$uri") + headers.each { request.putHeader(it.key, it.value) } + Future result = breaker.execute { command -> + request.rxSend().doOnSuccess { + command.complete(it) + }.doOnError { + command.fail(it) + }.subscribe() + } + + def future = new CompletableFuture() + result.setHandler { + callback?.call() + if (it.succeeded()) { + future.complete(it.result().statusCode()) + } else { + future.completeExceptionally(it.cause()) + } + } + return future.get() + } + + @Override + NettyHttpClientDecorator decorator() { + return NettyHttpClientDecorator.DECORATE + } + + @Override + String expectedOperationName() { + return "netty.client.request" + } + + @Override + boolean testRedirects() { + false + } + + @Override + boolean testConnectionFailure() { + false + } +} diff --git a/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxWebClientTest.groovy b/dd-java-agent/instrumentation/vertx/src/test/groovy/client/VertxRxWebClientTest.groovy similarity index 98% rename from dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxWebClientTest.groovy rename to dd-java-agent/instrumentation/vertx/src/test/groovy/client/VertxRxWebClientTest.groovy index fc556865c4..c39456a82b 100644 --- a/dd-java-agent/instrumentation/vertx/src/test/groovy/VertxRxWebClientTest.groovy +++ b/dd-java-agent/instrumentation/vertx/src/test/groovy/client/VertxRxWebClientTest.groovy @@ -1,3 +1,5 @@ +package client + import datadog.trace.agent.test.base.HttpClientTest import datadog.trace.instrumentation.netty41.client.NettyHttpClientDecorator import io.vertx.core.VertxOptions diff --git a/dd-java-agent/instrumentation/vertx/src/test/groovy/server/NettyServerTestInstrumentation.java b/dd-java-agent/instrumentation/vertx/src/test/groovy/server/NettyServerTestInstrumentation.java new file mode 100644 index 0000000000..61cb150a54 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/src/test/groovy/server/NettyServerTestInstrumentation.java @@ -0,0 +1,22 @@ +package server; + +import static net.bytebuddy.matcher.ElementMatchers.named; + +import com.google.auto.service.AutoService; +import datadog.trace.agent.test.base.HttpServerTestAdvice; +import datadog.trace.agent.tooling.Instrumenter; +import net.bytebuddy.agent.builder.AgentBuilder; + +@AutoService(Instrumenter.class) +public class NettyServerTestInstrumentation implements Instrumenter { + + @Override + public AgentBuilder instrument(final AgentBuilder agentBuilder) { + return agentBuilder + .type(named("io.netty.handler.codec.ByteToMessageDecoder")) + .transform( + new AgentBuilder.Transformer.ForAdvice() + .advice( + named("channelRead"), HttpServerTestAdvice.ServerEntryAdvice.class.getName())); + } +} diff --git a/dd-java-agent/instrumentation/vertx/src/test/groovy/server/VertxHttpServerTest.groovy b/dd-java-agent/instrumentation/vertx/src/test/groovy/server/VertxHttpServerTest.groovy new file mode 100644 index 0000000000..f4f753093b --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/src/test/groovy/server/VertxHttpServerTest.groovy @@ -0,0 +1,128 @@ +package server + +import datadog.trace.agent.test.asserts.ListWriterAssert +import datadog.trace.agent.test.base.HttpServerTest +import datadog.trace.instrumentation.netty41.server.NettyHttpServerDecorator +import groovy.transform.stc.ClosureParams +import groovy.transform.stc.SimpleType +import io.vertx.core.AbstractVerticle +import io.vertx.core.DeploymentOptions +import io.vertx.core.Future +import io.vertx.core.Vertx +import io.vertx.core.VertxOptions +import io.vertx.core.json.JsonObject +import io.vertx.ext.web.Router +import spock.lang.Shared + +import java.util.concurrent.CompletableFuture + +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS + +class VertxHttpServerTest extends HttpServerTest { + public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port" + + @Shared + Vertx server + + @Override + void startServer(int port) { + server = Vertx.vertx(new VertxOptions() + // Useful for debugging: + // .setBlockedThreadCheckInterval(Integer.MAX_VALUE) + .setClusterPort(port)) + final CompletableFuture future = new CompletableFuture<>() + server.deployVerticle(verticle().name, + new DeploymentOptions() + .setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port)) + .setInstances(3)) { res -> + if (!res.succeeded()) { + throw new RuntimeException("Cannot deploy server Verticle", res.cause()) + } + future.complete(null) + } + + future.get() + } + + protected Class verticle() { + return VertxWebTestServer + } + + @Override + void stopServer() { + server.close() + } + + @Override + NettyHttpServerDecorator decorator() { + return NettyHttpServerDecorator.DECORATE + } + + @Override + String expectedOperationName() { + "netty.request" + } + + @Override + boolean testExceptionBody() { + false + } + + static class VertxWebTestServer extends AbstractVerticle { + + @Override + void start(final Future startFuture) { + final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT) + final Router router = Router.router(vertx) + + router.route(SUCCESS.path).handler { ctx -> + controller(SUCCESS) { + ctx.response().setStatusCode(SUCCESS.status).end(SUCCESS.body) + } + } + router.route(REDIRECT.path).handler { ctx -> + controller(REDIRECT) { + ctx.response().setStatusCode(REDIRECT.status).putHeader("location", REDIRECT.body) + } + } + router.route(ERROR.path).handler { ctx -> + controller(ERROR) { + ctx.response().setStatusCode(ERROR.status).end(ERROR.body) + } + } + router.route(EXCEPTION.path).handler { ctx -> + controller(EXCEPTION) { + throw new Exception(EXCEPTION.body) + } + } + + vertx.createHttpServer() + .requestHandler { router.accept(it) } + .listen(port) { startFuture.complete() } + } + } + + void cleanAndAssertTraces( + final int size, + @ClosureParams(value = SimpleType, options = "datadog.trace.agent.test.asserts.ListWriterAssert") + @DelegatesTo(value = ListWriterAssert, strategy = Closure.DELEGATE_FIRST) + final Closure spec) { + // If this is failing, make sure HttpServerTestAdvice is applied correctly. + TEST_WRITER.waitForTraces(size * 2) + + // Netty closes the parent span before the controller returns, so we need to manually reorder it. + TEST_WRITER.each { + def controllerSpan = it.find { + it.operationName == "controller" + } + if (controllerSpan) { + it.remove(controllerSpan) + it.add(controllerSpan) + } + } + super.cleanAndAssertTraces(size, spec) + } +} diff --git a/dd-java-agent/instrumentation/vertx/src/test/groovy/server/VertxRxCircuitBreakerHttpServerTest.groovy b/dd-java-agent/instrumentation/vertx/src/test/groovy/server/VertxRxCircuitBreakerHttpServerTest.groovy new file mode 100644 index 0000000000..1f944983a1 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/src/test/groovy/server/VertxRxCircuitBreakerHttpServerTest.groovy @@ -0,0 +1,99 @@ +package server + +import datadog.trace.agent.test.base.HttpServerTest +import io.vertx.circuitbreaker.CircuitBreakerOptions +import io.vertx.reactivex.circuitbreaker.CircuitBreaker +import io.vertx.reactivex.core.AbstractVerticle +import io.vertx.reactivex.core.Future +import io.vertx.reactivex.ext.web.Router + +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS + +class VertxRxCircuitBreakerHttpServerTest extends VertxHttpServerTest { + + @Override + protected Class verticle() { + return VertxRxCircuitBreakerWebTestServer + } + + static class VertxRxCircuitBreakerWebTestServer extends AbstractVerticle { + + @Override + void start(final io.vertx.core.Future startFuture) { + final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT) + final Router router = Router.router(super.@vertx) + final CircuitBreaker breaker = + CircuitBreaker.create( + "my-circuit-breaker", + super.@vertx, + new CircuitBreakerOptions() + .setTimeout(-1) // Disable the timeout otherwise it makes each test take this long. + ) + + router.route(SUCCESS.path).handler { ctx -> + def result = breaker.execute { future -> + future.complete(SUCCESS) + } + result.setHandler { + if (it.failed()) { + throw it.cause(); + } + HttpServerTest.ServerEndpoint endpoint = it.result() + controller(endpoint) { + ctx.response().setStatusCode(endpoint.status).end(endpoint.body) + } + } + } + router.route(REDIRECT.path).handler { ctx -> + def result = breaker.execute { future -> + future.complete(REDIRECT) + } + result.setHandler { + if (it.failed()) { + throw it.cause(); + } + HttpServerTest.ServerEndpoint endpoint = it.result() + controller(endpoint) { + ctx.response().setStatusCode(endpoint.status).putHeader("location", endpoint.body) + } + } + } + router.route(ERROR.path).handler { ctx -> + def result = breaker.execute { future -> + future.complete(ERROR) + } + result.setHandler { + if (it.failed()) { + throw it.cause(); + } + HttpServerTest.ServerEndpoint endpoint = it.result() + controller(endpoint) { + ctx.response().setStatusCode(endpoint.status).end(endpoint.body) + } + } + } + router.route(EXCEPTION.path).handler { ctx -> + def result = breaker.execute { future -> + future.fail(new Exception(EXCEPTION.body)) + } + result.setHandler { + try { + def cause = it.cause() + controller(EXCEPTION) { + throw cause + } + } catch (Exception ex) { + ctx.response().setStatusCode(EXCEPTION.status).end(ex.message) + } + } + } + + super.@vertx.createHttpServer() + .requestHandler { router.accept(it) } + .listen(port) { startFuture.complete() } + } + } +} diff --git a/dd-java-agent/instrumentation/vertx/src/test/groovy/server/VertxRxHttpServerTest.groovy b/dd-java-agent/instrumentation/vertx/src/test/groovy/server/VertxRxHttpServerTest.groovy new file mode 100644 index 0000000000..a271ae0e93 --- /dev/null +++ b/dd-java-agent/instrumentation/vertx/src/test/groovy/server/VertxRxHttpServerTest.groovy @@ -0,0 +1,53 @@ +package server + + +import io.vertx.core.Future +import io.vertx.reactivex.core.AbstractVerticle +import io.vertx.reactivex.ext.web.Router + +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.ERROR +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.EXCEPTION +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.REDIRECT +import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.SUCCESS + +class VertxRxHttpServerTest extends VertxHttpServerTest { + + @Override + protected Class verticle() { + return VertxRxWebTestServer + } + + static class VertxRxWebTestServer extends AbstractVerticle { + + @Override + void start(final Future startFuture) { + final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT) + final Router router = Router.router(super.@vertx) + + router.route(SUCCESS.path).handler { ctx -> + controller(SUCCESS) { + ctx.response().setStatusCode(SUCCESS.status).end(SUCCESS.body) + } + } + router.route(REDIRECT.path).handler { ctx -> + controller(REDIRECT) { + ctx.response().setStatusCode(REDIRECT.status).putHeader("location", REDIRECT.body) + } + } + router.route(ERROR.path).handler { ctx -> + controller(ERROR) { + ctx.response().setStatusCode(ERROR.status).end(ERROR.body) + } + } + router.route(EXCEPTION.path).handler { ctx -> + controller(EXCEPTION) { + throw new Exception(EXCEPTION.body) + } + } + + super.@vertx.createHttpServer() + .requestHandler { router.accept(it) } + .listen(port) { startFuture.complete() } + } + } +} diff --git a/dd-java-agent/instrumentation/vertx/src/test/java/VertxRxWebTestServer.java b/dd-java-agent/instrumentation/vertx/src/test/java/VertxRxWebTestServer.java deleted file mode 100644 index 521253c268..0000000000 --- a/dd-java-agent/instrumentation/vertx/src/test/java/VertxRxWebTestServer.java +++ /dev/null @@ -1,116 +0,0 @@ -import datadog.trace.api.Trace; -import io.vertx.circuitbreaker.CircuitBreakerOptions; -import io.vertx.core.DeploymentOptions; -import io.vertx.core.Future; -import io.vertx.core.Vertx; -import io.vertx.core.VertxOptions; -import io.vertx.core.json.JsonObject; -import io.vertx.reactivex.circuitbreaker.CircuitBreaker; -import io.vertx.reactivex.core.AbstractVerticle; -import io.vertx.reactivex.core.buffer.Buffer; -import io.vertx.reactivex.ext.web.Router; -import io.vertx.reactivex.ext.web.RoutingContext; -import io.vertx.reactivex.ext.web.client.WebClient; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -public class VertxRxWebTestServer extends AbstractVerticle { - public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port"; - - public static Vertx start(final int port) throws ExecutionException, InterruptedException { - /* This is highly against Vertx ideas, but our tests are synchronous - so we have to make sure server is up and running */ - final CompletableFuture future = new CompletableFuture<>(); - - final Vertx vertx = Vertx.vertx(new VertxOptions().setClusterPort(port)); - - vertx.deployVerticle( - VertxRxWebTestServer.class.getName(), - new DeploymentOptions() - .setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port)) - .setInstances(3), - res -> { - if (!res.succeeded()) { - throw new RuntimeException("Cannot deploy server Verticle", res.cause()); - } - future.complete(null); - }); - - future.get(); - - return vertx; - } - - @Override - public void start(final Future startFuture) { - // final io.vertx.reactivex.core.Vertx vertx = new io.vertx.reactivex.core.Vertx(this.vertx); - final WebClient client = WebClient.create(vertx); - - final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT); - - final Router router = Router.router(vertx); - final CircuitBreaker breaker = - CircuitBreaker.create( - "my-circuit-breaker", - vertx, - new CircuitBreakerOptions() - .setMaxFailures(5) // number of failure before opening the circuit - .setTimeout(2000) // consider a failure if the operation does not succeed in time - // .setFallbackOnFailure(true) // do we call the fallback on failure - .setResetTimeout(10000) // time spent in open state before attempting to re-try - ); - - router - .route("/") - .handler( - routingContext -> { - routingContext.response().putHeader("content-type", "text/html").end("Hello World"); - }); - router - .route("/error") - .handler( - routingContext -> { - routingContext.response().setStatusCode(500).end(); - }); - router - .route("/proxy") - .handler( - routingContext -> { - breaker.execute( - ctx -> { - client - .get(port, "localhost", "/test") - .rxSendBuffer( - Optional.ofNullable(routingContext.getBody()).orElse(Buffer.buffer())) - .subscribe( - response -> { - routingContext - .response() - .setStatusCode(response.statusCode()) - .end(response.body()); - }); - }); - }); - router - .route("/test") - .handler( - routingContext -> { - tracedMethod(); - routingContext.next(); - }) - .blockingHandler(RoutingContext::next) - .handler( - routingContext -> { - routingContext.response().putHeader("content-type", "text/html").end("Hello World"); - }); - - vertx - .createHttpServer() - .requestHandler(router::accept) - .listen(port, h -> startFuture.complete()); - } - - @Trace - private void tracedMethod() {} -} diff --git a/dd-java-agent/instrumentation/vertx/src/test/java/VertxWebTestServer.java b/dd-java-agent/instrumentation/vertx/src/test/java/VertxWebTestServer.java deleted file mode 100644 index 571487c9d5..0000000000 --- a/dd-java-agent/instrumentation/vertx/src/test/java/VertxWebTestServer.java +++ /dev/null @@ -1,107 +0,0 @@ -import static datadog.trace.agent.test.AgentTestRunner.blockUntilChildSpansFinished; - -import datadog.trace.api.Trace; -import io.vertx.core.AbstractVerticle; -import io.vertx.core.DeploymentOptions; -import io.vertx.core.Future; -import io.vertx.core.Vertx; -import io.vertx.core.VertxOptions; -import io.vertx.core.buffer.Buffer; -import io.vertx.core.http.HttpClient; -import io.vertx.core.json.JsonObject; -import io.vertx.ext.web.Router; -import io.vertx.ext.web.RoutingContext; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; - -public class VertxWebTestServer extends AbstractVerticle { - public static final String CONFIG_HTTP_SERVER_PORT = "http.server.port"; - - public static Vertx start(final int port) throws ExecutionException, InterruptedException { - /* This is highly against Vertx ideas, but our tests are synchronous - so we have to make sure server is up and running */ - final CompletableFuture future = new CompletableFuture<>(); - - final Vertx vertx = Vertx.vertx(new VertxOptions().setClusterPort(port)); - - vertx.deployVerticle( - VertxWebTestServer.class.getName(), - new DeploymentOptions() - .setConfig(new JsonObject().put(CONFIG_HTTP_SERVER_PORT, port)) - .setInstances(3), - res -> { - if (!res.succeeded()) { - throw new RuntimeException("Cannot deploy server Verticle", res.cause()); - } - future.complete(null); - }); - - future.get(); - - return vertx; - } - - @Override - public void start(final Future startFuture) { - final HttpClient client = vertx.createHttpClient(); - - final int port = config().getInteger(CONFIG_HTTP_SERVER_PORT); - - final Router router = Router.router(vertx); - - router - .route("/") - .handler( - routingContext -> { - routingContext.response().putHeader("content-type", "text/html").end("Hello World"); - }); - router - .route("/error") - .handler( - routingContext -> { - routingContext.response().setStatusCode(500).end(); - }); - router - .route("/proxy") - .handler( - routingContext -> { - client - .get( - port, - "localhost", - "/test", - response -> { - response.bodyHandler( - buffer -> { - routingContext - .response() - .setStatusCode(response.statusCode()) - .end(buffer); - }); - blockUntilChildSpansFinished(1); - }) - .end(Optional.ofNullable(routingContext.getBody()).orElse(Buffer.buffer())); - }); - router - .route("/test") - .handler( - routingContext -> { - tracedMethod(); - routingContext.next(); - }) - .blockingHandler(RoutingContext::next) - .handler( - routingContext -> { - routingContext.response().putHeader("content-type", "text/html").end("Hello World"); - }); - - vertx - .createHttpServer() - .requestHandler(router::accept) - .listen(port, h -> startFuture.complete()); - } - - @Trace - private void tracedMethod() {} -} diff --git a/dd-java-agent/instrumentation/vertx/vertx.gradle b/dd-java-agent/instrumentation/vertx/vertx.gradle index 95a0d298c8..9c616c7821 100644 --- a/dd-java-agent/instrumentation/vertx/vertx.gradle +++ b/dd-java-agent/instrumentation/vertx/vertx.gradle @@ -5,21 +5,6 @@ ext { apply from: "${rootDir}/gradle/java.gradle" -muzzle { - pass { - group = "io.vertx" - module = "vertx-web" - versions = "[4.1.0.Final,)" - assertInverse = true - } - pass { - group = "io.netty" - module = "netty" - versions = "[4.1.0.Final,)" - assertInverse = true - } -} - apply plugin: 'org.unbroken-dome.test-sets' testSets { diff --git a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy index 22bff2b5f1..9aa9a3fca0 100644 --- a/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy +++ b/dd-java-agent/testing/src/main/groovy/datadog/trace/agent/test/base/HttpServerTest.groovy @@ -72,15 +72,15 @@ abstract class HttpServerTest extends Age true } - boolean hasExceptionBody() { + boolean testExceptionBody() { true } public enum ServerEndpoint { SUCCESS("success", 200, "success"), + REDIRECT("redirect", 302, null), ERROR("error", 500, "controller error"), EXCEPTION("exception", 500, "controller exception"), - REDIRECT("redirect", 302, null), NOT_FOUND("notFound", 404, "not found"), AUTH_REQUIRED("authRequired", 200, null), @@ -214,7 +214,7 @@ abstract class HttpServerTest extends Age expect: response.code() == EXCEPTION.status - if (hasExceptionBody()) { + if (testExceptionBody()) { assert response.body().string() == EXCEPTION.body }