diff --git a/instrumentation/netty/netty-4.1/javaagent/src/test/groovy/Netty41ClientTest.groovy b/instrumentation/netty/netty-4.1/javaagent/src/test/groovy/Netty41ClientTest.groovy index 9e47ed6c1062..e937779dd614 100644 --- a/instrumentation/netty/netty-4.1/javaagent/src/test/groovy/Netty41ClientTest.groovy +++ b/instrumentation/netty/netty-4.1/javaagent/src/test/groovy/Netty41ClientTest.groovy @@ -27,6 +27,7 @@ import io.netty.handler.codec.http.HttpMethod import io.netty.handler.codec.http.HttpVersion import io.opentelemetry.instrumentation.test.AgentTestTrait import io.opentelemetry.instrumentation.test.base.HttpClientTest +import io.opentelemetry.instrumentation.test.base.SingleConnection import io.opentelemetry.javaagent.instrumentation.netty.v4_1.client.HttpClientTracingHandler import java.util.concurrent.CompletableFuture import java.util.concurrent.ExecutionException @@ -374,4 +375,9 @@ class Netty41ClientTest extends HttpClientTest implements AgentTestTrait { ch.pipeline().addLast("added_in_initializer", new HttpClientCodec()) } } + + @Override + SingleConnection createSingleConnection(String host, int port) { + return new SingleNettyConnection(host, port) + } } diff --git a/instrumentation/netty/netty-4.1/javaagent/src/test/groovy/SingleNettyConnection.java b/instrumentation/netty/netty-4.1/javaagent/src/test/groovy/SingleNettyConnection.java new file mode 100644 index 000000000000..5434cbca1963 --- /dev/null +++ b/instrumentation/netty/netty-4.1/javaagent/src/test/groovy/SingleNettyConnection.java @@ -0,0 +1,94 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelOption; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.SocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.codec.http.DefaultFullHttpRequest; +import io.netty.handler.codec.http.HttpClientCodec; +import io.netty.handler.codec.http.HttpHeaderNames; +import io.netty.handler.codec.http.HttpMethod; +import io.netty.handler.codec.http.HttpRequest; +import io.netty.handler.codec.http.HttpVersion; +import io.opentelemetry.instrumentation.test.base.SingleConnection; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/* +Netty does not actually support proper http pipelining and has no way to correlate incoming response +message with some sent request. This means that without some support from the higher level protocol +we cannot concurrently send several requests across the same channel. Thus doRequest method of this +class is synchronised. Yes, it seems kinda pointless, but at least we test that our instrumentation +does not wreak havoc on Netty channel. + */ +public class SingleNettyConnection implements SingleConnection { + private final String host; + private final int port; + private final Channel channel; + + public SingleNettyConnection(String host, int port) { + this.host = host; + this.port = port; + EventLoopGroup group = new NioEventLoopGroup(); + Bootstrap bootstrap = new Bootstrap(); + bootstrap + .group(group) + .channel(NioSocketChannel.class) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10000) + .handler( + new ChannelInitializer() { + @Override + protected void initChannel(SocketChannel socketChannel) { + ChannelPipeline pipeline = socketChannel.pipeline(); + pipeline.addLast(new HttpClientCodec()); + } + }); + + ChannelFuture channelFuture = bootstrap.connect(host, port); + channelFuture.awaitUninterruptibly(); + if (!channelFuture.isSuccess()) { + throw new IllegalStateException(channelFuture.cause()); + } else { + channel = channelFuture.channel(); + } + } + + @Override + public synchronized int doRequest(String path, Map headers) + throws ExecutionException, InterruptedException, TimeoutException { + CompletableFuture result = new CompletableFuture<>(); + + channel.pipeline().addLast(new ClientHandler(null, result)); + + String url; + try { + url = new URL("http", host, port, path).toString(); + } catch (MalformedURLException e) { + throw new ExecutionException(e); + } + + HttpRequest request = + new DefaultFullHttpRequest( + HttpVersion.HTTP_1_1, HttpMethod.GET, url, Unpooled.EMPTY_BUFFER); + request.headers().set(HttpHeaderNames.HOST, host); + headers.forEach((k, v) -> request.headers().set(k, v)); + + channel.writeAndFlush(request).get(); + return result.get(20, TimeUnit.SECONDS); + } +} diff --git a/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/base/SingleConnection.java b/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/base/SingleConnection.java index d254aa73fe08..a074ced36a60 100644 --- a/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/base/SingleConnection.java +++ b/testing-common/src/main/groovy/io/opentelemetry/instrumentation/test/base/SingleConnection.java @@ -7,6 +7,7 @@ import java.util.Map; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; /** * Helper class for http client tests which require a single connection. @@ -24,5 +25,5 @@ public interface SingleConnection { String REQUEST_ID_HEADER = "test-request-id"; int doRequest(String path, Map headers) - throws ExecutionException, InterruptedException; + throws ExecutionException, InterruptedException, TimeoutException; }