Rewrite kubernetes-client instrumentation so that it works
* Rewrite kubernetes-client instrumentation so that it works
(and add tests)
* spotless
* Make Kubernetes propagation work
* spotless
MODIFY
instrumentation/kubernetes-client-7.0/javaagent/kubernetes-client-7.0-javaagent.gradle
instrumentation/kubernetes-client-7.0/javaagent/kubernetes-client-7.0-javaagent.gradle
@@ -11,5 +11,13 @@ muzzle {
dependencies {
library(group: 'io.kubernetes', name: 'client-java-api', version: '7.0.0')
+
+ implementation(project(':instrumentation:okhttp:okhttp-3.0:javaagent'))
+
+ testInstrumentation(project(':instrumentation:okhttp:okhttp-3.0:javaagent'))
}
+tasks.withType(Test) {
+ // TODO run tests both with and without experimental span attributes
+ jvmArgs "-Dotel.instrumentation.kubernetes-client.experimental-span-attributes=true"
+}

ADD
instrumentation/kubernetes-client-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/CurrentContextAndScope.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.kubernetesclient;
+
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import org.checkerframework.checker.nullness.qual.Nullable;
+
+/**
+ * Kubernetes instrumentation starts and ends spans in two different methods - there only way to
+ * pass {@link Scope} between them is to use a thread local.
+ */
+public final class CurrentContextAndScope {
+ private static final ThreadLocal<CurrentContextAndScope> CURRENT = new ThreadLocal<>();
+
+ private final Context parentContext;
+ private final Context context;
+ private final Scope scope;
+
+ private CurrentContextAndScope(Context parentContext, Context context, Scope scope) {
+ this.parentContext = parentContext;
+ this.context = context;
+ this.scope = scope;
+ }
+
+ public static void set(Context parentContext, Context context) {
+ CURRENT.set(new CurrentContextAndScope(parentContext, context, context.makeCurrent()));
+ }
+
+ @Nullable
+ public static CurrentContextAndScope remove() {
+ CurrentContextAndScope contextAndScope = CURRENT.get();
+ CURRENT.remove();
+ return contextAndScope;
+ }
+
+ @Nullable
+ public static Context removeAndClose() {
+ CurrentContextAndScope contextAndScope = remove();
+ if (contextAndScope == null) {
+ return null;
+ }
+ contextAndScope.scope.close();
+ return contextAndScope.context;
+ }
+
+ public Context getParentContext() {
+ return parentContext;
+ }
+
+ public Context getContext() {
+ return context;
+ }
+
+ public Scope getScope() {
+ return scope;
+ }
+}

MODIFY
instrumentation/kubernetes-client-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/KubernetesClientInstrumentationModule.java
instrumentation/kubernetes-client-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/KubernetesClientInstrumentationModule.java
package io.opentelemetry.javaagent.instrumentation.kubernetesclient;
-import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.AgentElementMatchers.extendsClass;
-import static io.opentelemetry.javaagent.tooling.bytebuddy.matcher.ClassLoaderMatcher.hasClassesNamed;
+import static io.opentelemetry.javaagent.instrumentation.kubernetesclient.KubernetesClientTracer.tracer;
import static java.util.Collections.singletonList;
-import static java.util.Collections.singletonMap;
+import static net.bytebuddy.matcher.ElementMatchers.isPublic;
import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
import com.google.auto.service.AutoService;
-import io.kubernetes.client.openapi.ApiClient;
+import io.kubernetes.client.openapi.ApiCallback;
+import io.kubernetes.client.openapi.ApiResponse;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import io.opentelemetry.javaagent.instrumentation.api.Java8BytecodeBridge;
import io.opentelemetry.javaagent.tooling.InstrumentationModule;
import io.opentelemetry.javaagent.tooling.TypeInstrumentation;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
-import net.bytebuddy.matcher.ElementMatchers;
-import okhttp3.Interceptor;
+import okhttp3.Call;
+import okhttp3.Request;
@AutoService(InstrumentationModule.class)
public class KubernetesClientInstrumentationModule extends InstrumentationModule {
public KubernetesClientInstrumentationModule() {
- super("kubernetes-client", "kubernetes-client-3.0");
+ super("kubernetes-client", "kubernetes-client-7.0");
}
@Override
@@ -37,40 +43,95 @@ public class KubernetesClientInstrumentationModule extends InstrumentationModule
}
public static class ApiClientInstrumentation implements TypeInstrumentation {
- @Override
- public ElementMatcher<ClassLoader> classLoaderOptimization() {
- return hasClassesNamed("io.kubernetes.client.openapi.ApiClient");
- }
-
@Override
public ElementMatcher<TypeDescription> typeMatcher() {
- return extendsClass(named("io.kubernetes.client.openapi.ApiClient"));
+ return named("io.kubernetes.client.openapi.ApiClient");
}
@Override
public Map<? extends ElementMatcher<? super MethodDescription>, String> transformers() {
- return singletonMap(
- ElementMatchers.isMethod()
- .and(named("initHttpClient"))
- .and(ElementMatchers.takesArguments(1))
- .and(ElementMatchers.takesArgument(0, named("java.util.List"))),
- KubernetesClientInstrumentationModule.class.getName() + "$KubernetesAdvice");
+ Map<ElementMatcher<MethodDescription>, String> transformers = new HashMap<>();
+ transformers.put(
+ isPublic().and(named("buildRequest")).and(takesArguments(10)),
+ KubernetesClientInstrumentationModule.class.getName() + "$BuildRequestAdvice");
+ transformers.put(
+ isPublic()
+ .and(named("execute"))
+ .and(takesArguments(2))
+ .and(takesArgument(0, named("okhttp3.Call"))),
+ KubernetesClientInstrumentationModule.class.getName() + "$ExecuteAdvice");
+ transformers.put(
+ isPublic()
+ .and(named("executeAsync"))
+ .and(takesArguments(3))
+ .and(takesArgument(0, named("okhttp3.Call")))
+ .and(takesArgument(2, named("io.kubernetes.client.openapi.ApiCallback"))),
+ KubernetesClientInstrumentationModule.class.getName() + "$ExecuteAsyncAdvice");
+ return transformers;
}
}
- public static class KubernetesAdvice {
+ public static class BuildRequestAdvice {
@Advice.OnMethodExit(suppress = Throwable.class)
- public static void addTracingInterceptor(
- @Advice.This ApiClient apiClient, @Advice.Argument(0) List<Interceptor> interceptors) {
+ public static void onExit(@Advice.Return(readOnly = false) Request request) {
+ Context parentContext = Java8BytecodeBridge.currentContext();
+ if (!tracer().shouldStartSpan(parentContext)) {
+ return;
+ }
- for (Interceptor interceptor : interceptors) {
- if (interceptor instanceof TracingInterceptor) {
+ Request.Builder requestWithPropagation = request.newBuilder();
+ Context context = tracer().startSpan(parentContext, request, requestWithPropagation);
+ CurrentContextAndScope.set(parentContext, context);
+ request = requestWithPropagation.build();
+ }
+ }
+
+ public static class ExecuteAdvice {
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
+ public static void onExit(
+ @Advice.Return ApiResponse<?> response, @Advice.Thrown Throwable throwable) {
+ Context context = CurrentContextAndScope.removeAndClose();
+ if (context == null) {
return;
}
+ if (throwable == null) {
+ tracer().end(context, response);
+ } else {
+ tracer().endExceptionally(context, response, throwable);
+ }
+ }
+ }
+
+ public static class ExecuteAsyncAdvice {
+ @Advice.OnMethodEnter(suppress = Throwable.class)
+ public static void onEnter(
+ @Advice.Argument(0) Call httpCall,
+ @Advice.Argument(value = 2, readOnly = false) ApiCallback<?> callback,
+ @Advice.Local("otelContext") Context context,
+ @Advice.Local("otelScope") Scope scope) {
+
+ CurrentContextAndScope current = CurrentContextAndScope.remove();
+ if (current != null) {
+ context = current.getContext();
+ scope = current.getScope();
+ callback = new TracingApiCallback<>(callback, current.getParentContext(), context);
+ }
}
- apiClient.setHttpClient(
- apiClient.getHttpClient().newBuilder().addInterceptor(new TracingInterceptor()).build());
+ @Advice.OnMethodExit(suppress = Throwable.class, onThrowable = Throwable.class)
+ public static void onExit(
+ @Advice.Thrown Throwable throwable,
+ @Advice.Local("otelContext") Context context,
+ @Advice.Local("otelScope") Scope scope) {
+ if (scope == null) {
+ return;
+ }
+ scope.close();
+
+ if (throwable != null) {
+ tracer().endExceptionally(context, throwable);
+ }
+ // else span will be ended in the TracingApiCallback
}
}
}

MODIFY
instrumentation/kubernetes-client-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/KubernetesClientTracer.java
instrumentation/kubernetes-client-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/KubernetesClientTracer.java
package io.opentelemetry.javaagent.instrumentation.kubernetesclient;
-import static io.opentelemetry.api.trace.SpanKind.CLIENT;
-
-import io.opentelemetry.api.GlobalOpenTelemetry;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.openapi.ApiResponse;
import io.opentelemetry.api.trace.Span;
-import io.opentelemetry.api.trace.SpanBuilder;
-import io.opentelemetry.context.Context;
import io.opentelemetry.context.propagation.TextMapSetter;
import io.opentelemetry.instrumentation.api.config.Config;
import io.opentelemetry.instrumentation.api.tracer.HttpClientTracer;
+import io.opentelemetry.javaagent.instrumentation.okhttp.v3_0.RequestBuilderInjectAdapter;
+import io.opentelemetry.semconv.trace.attributes.SemanticAttributes;
import java.net.URI;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
import okhttp3.Request;
-import okhttp3.Response;
-public class KubernetesClientTracer extends HttpClientTracer<Request, Request, Response> {
+public class KubernetesClientTracer
+ extends HttpClientTracer<Request, Request.Builder, ApiResponse<?>> {
private static final boolean CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES =
Config.get()
@@ -31,40 +33,24 @@ public class KubernetesClientTracer extends HttpClientTracer<Request, Request, R
return TRACER;
}
- /**
- * This method is used to generate an acceptable CLIENT span (operation) name based on a given
- * KubernetesRequestDigest.
- */
- public Context startSpan(Context parentContext, Request request) {
- KubernetesRequestDigest digest = KubernetesRequestDigest.parse(request);
- SpanBuilder spanBuilder =
- tracer.spanBuilder(digest.toString()).setSpanKind(CLIENT).setParent(parentContext);
- if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
- spanBuilder
- .setAttribute("kubernetes-client.namespace", digest.getResourceMeta().getNamespace())
- .setAttribute("kubernetes-client.name", digest.getResourceMeta().getName());
- }
- Span span = spanBuilder.startSpan();
- Context context = withClientSpan(parentContext, span);
- GlobalOpenTelemetry.getPropagators()
- .getTextMapPropagator()
- .inject(context, request, getSetter());
- return context;
+ @Override
+ protected String getInstrumentationName() {
+ return "io.opentelemetry.javaagent.kubernetes-client";
}
@Override
- protected String method(Request httpRequest) {
- return httpRequest.method();
+ protected String method(Request request) {
+ return request.method();
}
@Override
- protected URI url(Request httpRequest) {
- return httpRequest.url().uri();
+ protected URI url(Request request) {
+ return request.url().uri();
}
@Override
- protected Integer status(Response httpResponse) {
- return httpResponse.code();
+ protected Integer status(ApiResponse<?> response) {
+ return response.getStatusCode();
}
@Override
@@ -73,24 +59,42 @@ public class KubernetesClientTracer extends HttpClientTracer<Request, Request, R
}
@Override
- protected String responseHeader(Response response, String name) {
- return response.header(name);
+ protected String responseHeader(ApiResponse<?> response, String name) {
+ Map<String, List<String>> responseHeaders =
+ response.getHeaders() == null ? Collections.emptyMap() : response.getHeaders();
+ return responseHeaders.getOrDefault(name, Collections.emptyList()).stream()
+ .findFirst()
+ .orElse(null);
}
@Override
- protected TextMapSetter<Request> getSetter() {
- // TODO (trask) no propagation implemented yet?
- return null;
+ protected TextMapSetter<Request.Builder> getSetter() {
+ return RequestBuilderInjectAdapter.SETTER;
}
@Override
- protected String getInstrumentationName() {
- return "io.opentelemetry.javaagent.kubernetes-client";
+ protected void onError(Span span, Throwable throwable) {
+ super.onError(span, throwable);
+ if (throwable instanceof ApiException) {
+ int status = ((ApiException) throwable).getCode();
+ if (status != 0) {
+ span.setAttribute(SemanticAttributes.HTTP_STATUS_CODE, status);
+ }
+ }
}
- /** This method is overridden to allow other classes in this package to call it. */
@Override
protected void onRequest(Span span, Request request) {
super.onRequest(span, request);
+ if (CAPTURE_EXPERIMENTAL_SPAN_ATTRIBUTES) {
+ KubernetesRequestDigest digest = KubernetesRequestDigest.parse(request);
+ span.setAttribute("kubernetes-client.namespace", digest.getResourceMeta().getNamespace())
+ .setAttribute("kubernetes-client.name", digest.getResourceMeta().getName());
+ }
+ }
+
+ @Override
+ protected String spanNameForRequest(Request request) {
+ return KubernetesRequestDigest.parse(request).toString();
}
}

ADD
instrumentation/kubernetes-client-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/TracingApiCallback.java
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.kubernetesclient;
+
+import static io.opentelemetry.javaagent.instrumentation.kubernetesclient.KubernetesClientTracer.tracer;
+
+import io.kubernetes.client.openapi.ApiCallback;
+import io.kubernetes.client.openapi.ApiException;
+import io.kubernetes.client.openapi.ApiResponse;
+import io.opentelemetry.context.Context;
+import io.opentelemetry.context.Scope;
+import java.util.List;
+import java.util.Map;
+
+public class TracingApiCallback<T> implements ApiCallback<T> {
+ private final ApiCallback<T> delegate;
+ private final Context parentContext;
+ private final Context context;
+
+ public TracingApiCallback(ApiCallback<T> delegate, Context parentContext, Context context) {
+ this.delegate = delegate;
+ this.parentContext = parentContext;
+ this.context = context;
+ }
+
+ @Override
+ public void onFailure(ApiException e, int status, Map<String, List<String>> headers) {
+ tracer().endExceptionally(context, new ApiResponse<>(status, headers), e);
+ if (delegate != null) {
+ try (Scope ignored = parentContext.makeCurrent()) {
+ delegate.onFailure(e, status, headers);
+ }
+ }
+ }
+
+ @Override
+ public void onSuccess(T t, int status, Map<String, List<String>> headers) {
+ tracer().end(context, new ApiResponse<>(status, headers));
+ if (delegate != null) {
+ try (Scope ignored = parentContext.makeCurrent()) {
+ delegate.onSuccess(t, status, headers);
+ }
+ }
+ }
+
+ @Override
+ public void onUploadProgress(long bytesWritten, long contentLength, boolean done) {
+ if (delegate != null) {
+ try (Scope ignored = parentContext.makeCurrent()) {
+ delegate.onUploadProgress(bytesWritten, contentLength, done);
+ }
+ }
+ }
+
+ @Override
+ public void onDownloadProgress(long bytesRead, long contentLength, boolean done) {
+ if (delegate != null) {
+ try (Scope ignored = parentContext.makeCurrent()) {
+ delegate.onDownloadProgress(bytesRead, contentLength, done);
+ }
+ }
+ }
+}

DELETE
instrumentation/kubernetes-client-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/TracingInterceptor.java
-/*
- * Copyright The OpenTelemetry Authors
- * SPDX-License-Identifier: Apache-2.0
- */
-
-package io.opentelemetry.javaagent.instrumentation.kubernetesclient;
-
-import static io.opentelemetry.javaagent.instrumentation.kubernetesclient.KubernetesClientTracer.tracer;
-
-import io.opentelemetry.api.trace.Span;
-import io.opentelemetry.context.Context;
-import io.opentelemetry.context.Scope;
-import java.io.IOException;
-import okhttp3.Interceptor;
-import okhttp3.Response;
-
-public class TracingInterceptor implements Interceptor {
-
- @Override
- public Response intercept(Chain chain) throws IOException {
-
- Context context = tracer().startSpan(Context.current(), chain.request());
- tracer().onRequest(Span.fromContext(context), chain.request());
-
- Response response;
- try (Scope ignored = context.makeCurrent()) {
- response = chain.proceed(chain.request());
- } catch (Exception e) {
- tracer().endExceptionally(context, e);
- throw e;
- }
-
- tracer().end(context, response);
- return response;
- }
-}

ADD
instrumentation/kubernetes-client-7.0/javaagent/src/test/groovy/KubernetesClientTest.groovy
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+import static io.opentelemetry.api.trace.SpanKind.CLIENT
+import static io.opentelemetry.instrumentation.test.server.http.TestHttpServer.distributedRequestSpan
+import static io.opentelemetry.instrumentation.test.server.http.TestHttpServer.httpServer
+import static io.opentelemetry.instrumentation.test.utils.TraceUtils.basicSpan
+import static io.opentelemetry.instrumentation.test.utils.TraceUtils.runInternalSpan
+import static io.opentelemetry.instrumentation.test.utils.TraceUtils.runUnderTrace
+
+import io.kubernetes.client.openapi.ApiCallback
+import io.kubernetes.client.openapi.ApiClient
+import io.kubernetes.client.openapi.ApiException
+import io.kubernetes.client.openapi.apis.CoreV1Api
+import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
+import io.opentelemetry.instrumentation.test.asserts.TraceAssert
+import io.opentelemetry.instrumentation.test.server.http.TestHttpServer
+import io.opentelemetry.semconv.trace.attributes.SemanticAttributes
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.atomic.AtomicInteger
+import java.util.concurrent.atomic.AtomicReference
+import spock.lang.AutoCleanup
+import spock.lang.Shared
+
+class KubernetesClientTest extends AgentInstrumentationSpecification {
+ @Shared
+ def responseStatus = new AtomicInteger()
+
+ @AutoCleanup
+ @Shared
+ TestHttpServer server
+
+ @Shared
+ CoreV1Api api
+
+ def setup() {
+ // Lazy-load server to allow traits to initialize first.
+ if (server == null) {
+ server = httpServer {
+ handlers {
+ all {
+ handleDistributedRequest()
+ response.status(responseStatus.get()).send("42")
+ }
+ }
+ }
+ }
+
+ if (api == null) {
+ def apiClient = new ApiClient()
+ apiClient.basePath = server.address.toString()
+ api = new CoreV1Api(apiClient)
+ }
+ }
+
+ def "Kubernetes span is registered on a synchronous call"() {
+ given:
+ responseStatus.set(200)
+
+ when:
+ def response = runUnderTrace("parent") {
+ api.connectGetNamespacedPodProxy("name", "namespace", "path")
+ }
+
+ then:
+ response == "42"
+
+ assertTraces(1) {
+ trace(0, 3) {
+ basicSpan(it, 0, "parent")
+ apiClientSpan(it, 1, "get pods/proxy", "${server.address}/api/v1/namespaces/namespace/pods/name/proxy?path=path")
+ distributedRequestSpan(it, 2, span(1))
+ }
+ }
+ }
+
+ def "Kubernetes instrumentation handles errors on a synchronous call"() {
+ given:
+ responseStatus.set(451)
+
+ when:
+ runUnderTrace("parent") {
+ api.connectGetNamespacedPodProxy("name", "namespace", "path")
+ }
+
+ then:
+ def exception = thrown(ApiException)
+
+ assertTraces(1) {
+ trace(0, 3) {
+ basicSpan(it, 0, "parent", null, exception)
+ apiClientSpan(it, 1, "get pods/proxy", "${server.address}/api/v1/namespaces/namespace/pods/name/proxy?path=path", exception)
+ distributedRequestSpan(it, 2, span(1))
+ }
+ }
+ }
+
+ def "Kubernetes span is registered on an asynchronous call"() {
+ given:
+ responseStatus.set(200)
+
+ when:
+ def responseBody = new AtomicReference<String>()
+ def latch = new CountDownLatch(1)
+
+ runUnderTrace("parent") {
+ api.connectGetNamespacedPodProxyAsync("name", "namespace", "path", new ApiCallbackTemplate() {
+ @Override
+ void onSuccess(String result, int statusCode, Map<String, List<String>> responseHeaders) {
+ responseBody.set(result)
+ latch.countDown()
+ runInternalSpan("callback")
+ }
+ })
+ }
+
+ then:
+ latch.await()
+ responseBody.get() == "42"
+
+ assertTraces(1) {
+ trace(0, 4) {
+ basicSpan(it, 0, "parent")
+ apiClientSpan(it, 1, "get pods/proxy", "${server.address}/api/v1/namespaces/namespace/pods/name/proxy?path=path")
+ distributedRequestSpan(it, 2, span(1))
+ basicSpan(it, 3, "callback", span(0))
+ }
+ }
+ }
+
+ def "Kubernetes instrumentation handles errors on an asynchronous call"() {
+ given:
+ responseStatus.set(451)
+
+ when:
+ def exception = new AtomicReference<Exception>()
+ def latch = new CountDownLatch(1)
+
+ runUnderTrace("parent") {
+ api.connectGetNamespacedPodProxyAsync("name", "namespace", "path", new ApiCallbackTemplate() {
+ @Override
+ void onFailure(ApiException e, int statusCode, Map<String, List<String>> responseHeaders) {
+ exception.set(e)
+ latch.countDown()
+ runInternalSpan("callback")
+ }
+ })
+ }
+
+ then:
+ latch.await()
+ exception.get() != null
+
+ assertTraces(1) {
+ trace(0, 4) {
+ basicSpan(it, 0, "parent")
+ apiClientSpan(it, 1, "get pods/proxy", "${server.address}/api/v1/namespaces/namespace/pods/name/proxy?path=path", exception.get())
+ distributedRequestSpan(it, 2, span(1))
+ basicSpan(it, 3, "callback", span(0))
+ }
+ }
+ }
+
+ private void apiClientSpan(TraceAssert trace, int index, String spanName, String url, Throwable exception = null) {
+ boolean hasFailed = exception != null
+ trace.span(index) {
+ name spanName
+ kind CLIENT
+ childOf trace.span(0)
+ errored hasFailed
+ if (hasFailed) {
+ errorEvent exception.class, exception.message
+ }
+ attributes {
+ "$SemanticAttributes.HTTP_URL.key" url
+ "$SemanticAttributes.HTTP_FLAVOR.key" "1.1"
+ "$SemanticAttributes.HTTP_METHOD.key" "GET"
+ "$SemanticAttributes.HTTP_USER_AGENT" "OpenAPI-Generator/1.0-SNAPSHOT/java"
+ "$SemanticAttributes.HTTP_STATUS_CODE" responseStatus.get()
+ "$SemanticAttributes.NET_TRANSPORT" "IP.TCP"
+ "$SemanticAttributes.NET_PEER_NAME" server.address.host
+ "$SemanticAttributes.NET_PEER_PORT" server.address.port
+ "kubernetes-client.namespace" "namespace"
+ "kubernetes-client.name" "name"
+ }
+ }
+ }
+
+ static class ApiCallbackTemplate implements ApiCallback<String> {
+ @Override
+ void onFailure(ApiException e, int statusCode, Map<String, List<String>> responseHeaders) {}
+
+ @Override
+ void onSuccess(String result, int statusCode, Map<String, List<String>> responseHeaders) {}
+
+ @Override
+ void onUploadProgress(long bytesWritten, long contentLength, boolean done) {}
+
+ @Override
+ void onDownloadProgress(long bytesRead, long contentLength, boolean done) {}
+ }
+}

Rewrite kubernetes-client instrumentation so that it works (#2416)

* Rewrite kubernetes-client instrumentation so that it works

(and add tests)

* spotless

* Make Kubernetes propagation work

* spotless