Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions dd-java-agent/instrumentation/netty-3.8/netty-3.8.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Set properties before any plugins get loaded
ext {
minJavaVersionForTests = JavaVersion.VERSION_1_8
maxJavaVersionForTests = JavaVersion.VERSION_1_8
}

apply from: "${rootDir}/gradle/java.gradle"

muzzle {
pass {
group = "io.netty"
module = "netty"
versions = "[3.8.0.Final,4)"
assertInverse = true
}
fail {
group = "io.netty"
module = "netty-all"
versions = "[,]"
}
}

apply plugin: 'org.unbroken-dome.test-sets'

testSets {
latestDepTest
}

dependencies {
compileOnly group: 'io.netty', name: 'netty', version: '3.8.0.Final'

testCompile group: 'io.netty', name: 'netty', version: '3.8.0.Final'
testCompile group: 'com.ning', name: 'async-http-client', version: '1.8.0'

latestDepTestCompile group: 'io.netty', name: 'netty', version: '3.10.+'
latestDepTestCompile group: 'com.ning', name: 'async-http-client', version: '1.9.+'
}

// We need to force the dependency to the earliest supported version because other libraries declare newer versions.
configurations.testCompile {
resolutionStrategy {
eachDependency { DependencyResolveDetails details ->
//specifying a fixed version for all libraries with io.netty' group
if (details.requested.group == 'io.netty') {
details.useVersion "3.8.0.Final"
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import com.ning.http.client.AsyncCompletionHandler
import com.ning.http.client.AsyncHttpClient
import com.ning.http.client.AsyncHttpClientConfig
import com.ning.http.client.Response
import datadog.trace.agent.test.base.HttpClientTest
import datadog.trace.bootstrap.instrumentation.api.Tags
import datadog.trace.instrumentation.netty38.client.NettyHttpClientDecorator
import spock.lang.AutoCleanup
import spock.lang.Shared

import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit

import static datadog.trace.agent.test.utils.PortUtils.UNUSABLE_PORT
import static datadog.trace.agent.test.utils.TraceUtils.basicSpan
import static datadog.trace.agent.test.utils.TraceUtils.runUnderTrace

class Netty38ClientTest extends HttpClientTest {

@Shared
def clientConfig = new AsyncHttpClientConfig.Builder()
.setRequestTimeout(TimeUnit.SECONDS.toMillis(10).toInteger())
.build()

@Shared
@AutoCleanup
AsyncHttpClient asyncHttpClient = new AsyncHttpClient(clientConfig)

@Override
int doRequest(String method, URI uri, Map<String, String> headers, Closure callback) {
def methodName = "prepare" + method.toLowerCase().capitalize()
def requestBuilder = asyncHttpClient."$methodName"(uri.toString())
headers.each { requestBuilder.setHeader(it.key, it.value) }
def response = requestBuilder.execute(new AsyncCompletionHandler() {
@Override
Object onCompleted(Response response) throws Exception {
callback?.call()
return response
}
}).get()
blockUntilChildSpansFinished(1)
return response.statusCode
}

@Override
String component() {
return NettyHttpClientDecorator.DECORATE.component()
}

@Override
String expectedOperationName() {
return "netty.client.request"
}

@Override
boolean testRedirects() {
false
}

@Override
boolean testConnectionFailure() {
false
}

def "connection error (unopened port)"() {
given:
def uri = new URI("http://localhost:$UNUSABLE_PORT/")

when:
runUnderTrace("parent") {
doRequest(method, uri)
}

then:
def ex = thrown(Exception)
def thrownException = ex instanceof ExecutionException ? ex.cause : ex

and:
assertTraces(1) {
trace(0, 2) {
basicSpan(it, 0, "parent", null, thrownException)

span(1) {
operationName "netty.connect"
resourceName "netty.connect"
childOf span(0)
errored true
tags {
"$Tags.COMPONENT" "netty"
Class errorClass = ConnectException
try {
errorClass = Class.forName('io.netty.channel.AbstractChannel$AnnotatedConnectException')
} catch (ClassNotFoundException e) {
// Older versions use 'java.net.ConnectException' and do not have 'io.netty.channel.AbstractChannel$AnnotatedConnectException'
}
errorTags errorClass, "Connection refused: localhost/127.0.0.1:$UNUSABLE_PORT"
defaultTags()
}
}
}
}

where:
method = "GET"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import datadog.trace.agent.test.base.HttpServerTest
import datadog.trace.instrumentation.netty38.server.NettyHttpServerDecorator
import org.jboss.netty.bootstrap.ServerBootstrap
import org.jboss.netty.buffer.ChannelBuffer
import org.jboss.netty.buffer.ChannelBuffers
import org.jboss.netty.channel.*
import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory
import org.jboss.netty.handler.codec.http.*
import org.jboss.netty.handler.logging.LoggingHandler
import org.jboss.netty.logging.InternalLogLevel
import org.jboss.netty.util.CharsetUtil

import static datadog.trace.agent.test.base.HttpServerTest.ServerEndpoint.*
import static org.jboss.netty.handler.codec.http.HttpHeaders.Names.*
import static org.jboss.netty.handler.codec.http.HttpVersion.HTTP_1_1

class Netty38ServerTest extends HttpServerTest<Channel> {

ChannelPipeline channelPipeline() {
ChannelPipeline channelPipeline = new DefaultChannelPipeline()

channelPipeline.addLast("http-codec", new HttpServerCodec())
channelPipeline.addLast("controller", new SimpleChannelHandler() {
@Override
void messageReceived(ChannelHandlerContext ctx, MessageEvent msg) throws Exception {
if (msg.getMessage() instanceof HttpRequest) {
def uri = URI.create((msg.getMessage() as HttpRequest).getUri())
HttpServerTest.ServerEndpoint endpoint = forPath(uri.path)
ctx.sendDownstream controller(endpoint) {
HttpResponse response
ChannelBuffer responseContent = null
switch (endpoint) {
case SUCCESS:
case ERROR:
responseContent = ChannelBuffers.copiedBuffer(endpoint.body, CharsetUtil.UTF_8)
response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status))
response.setContent(responseContent)
break
case QUERY_PARAM:
responseContent = ChannelBuffers.copiedBuffer(uri.query, CharsetUtil.UTF_8)
response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status))
response.setContent(responseContent)
break
case REDIRECT:
response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status))
response.headers().set(LOCATION, endpoint.body)
break
case EXCEPTION:
throw new Exception(endpoint.body)
default:
responseContent = ChannelBuffers.copiedBuffer(NOT_FOUND.body, CharsetUtil.UTF_8)
response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.valueOf(endpoint.status))
response.setContent(responseContent)
break
}
response.headers().set(CONTENT_TYPE, "text/plain")
if (responseContent) {
response.headers().set(CONTENT_LENGTH, responseContent.readableBytes())
}
return new DownstreamMessageEvent(
ctx.getChannel(),
new SucceededChannelFuture(ctx.getChannel()),
response,
ctx.getChannel().getRemoteAddress())
}
}
}

@Override
void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent ex) throws Exception {
ChannelBuffer buffer = ChannelBuffers.copiedBuffer(ex.getCause().getMessage(), CharsetUtil.UTF_8)
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, HttpResponseStatus.INTERNAL_SERVER_ERROR)
response.setContent(buffer)
response.headers().set(CONTENT_TYPE, "text/plain")
response.headers().set(CONTENT_LENGTH, buffer.readableBytes())
ctx.sendDownstream(new DownstreamMessageEvent(
ctx.getChannel(),
new FailedChannelFuture(ctx.getChannel(), ex.getCause()),
response,
ctx.getChannel().getRemoteAddress()))
}
})

return channelPipeline
}

@Override
Channel startServer(int port) {
ServerBootstrap bootstrap = new ServerBootstrap(new NioServerSocketChannelFactory())
bootstrap.setParentHandler(new LoggingHandler(InternalLogLevel.INFO))
bootstrap.setPipeline(channelPipeline())

InetSocketAddress address = new InetSocketAddress(port)
return bootstrap.bind(address)
}

@Override
void stopServer(Channel server) {
server?.disconnect()
}

@Override
String component() {
NettyHttpServerDecorator.DECORATE.component()
}

@Override
String expectedOperationName() {
"netty.request"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
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("org.jboss.netty.handler.codec.http.HttpRequestDecoder"))
.transform(
new AgentBuilder.Transformer.ForAdvice()
.advice(
named("createMessage"),
HttpServerTestAdvice.ServerEntryAdvice.class.getName()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package datadog.trace.instrumentation.netty38;

import org.jboss.netty.handler.codec.http.HttpHeaders;
import org.jboss.netty.handler.codec.http.HttpRequest;

public class AbstractNettyAdvice {
public static void muzzleCheck(final HttpRequest httpRequest) {
final HttpHeaders headers = httpRequest.headers();
}
}
Loading