Browse Source

Fix kubernetes client latest dep tests (#10433)

Lauri Tulmin 1 year ago
parent
commit
b3d2aaf855

+ 22 - 2
instrumentation/kubernetes-client-7.0/javaagent/build.gradle.kts

@@ -14,9 +14,29 @@ muzzle {
 dependencies {
   library("io.kubernetes:client-java-api:7.0.0")
 
-  implementation(project(":instrumentation:okhttp:okhttp-3.0:javaagent"))
-
   testInstrumentation(project(":instrumentation:okhttp:okhttp-3.0:javaagent"))
+
+  latestDepTestLibrary("io.kubernetes:client-java-api:19.+")
+}
+
+testing {
+  suites {
+    val version20Test by registering(JvmTestSuite::class) {
+      dependencies {
+        if (findProperty("testLatestDeps") as Boolean) {
+          implementation("io.kubernetes:client-java-api:+")
+        } else {
+          implementation("io.kubernetes:client-java-api:20.0.0")
+        }
+      }
+    }
+  }
+}
+
+tasks {
+  check {
+    dependsOn(testing.suites)
+  }
 }
 
 tasks.withType<Test>().configureEach {

+ 1 - 1
instrumentation/kubernetes-client-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/ApiClientInstrumentation.java

@@ -35,7 +35,7 @@ public class ApiClientInstrumentation implements TypeInstrumentation {
   @Override
   public void transform(TypeTransformer transformer) {
     transformer.applyAdviceToMethod(
-        isPublic().and(named("buildRequest")).and(takesArguments(10)),
+        isPublic().and(named("buildRequest")).and(takesArguments(10).or(takesArguments(11))),
         this.getClass().getName() + "$BuildRequestAdvice");
     transformer.applyAdviceToMethod(
         isPublic()

+ 1 - 1
instrumentation/kubernetes-client-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/KubernetesRequestDigest.java

@@ -89,7 +89,7 @@ class KubernetesRequestDigest {
   @Override
   public String toString() {
     if (isNonResourceRequest) {
-      return verb.value() + ' ' + urlPath;
+      return (verb != null ? verb.value() + ' ' : "") + urlPath;
     }
 
     String groupVersion;

+ 1 - 1
instrumentation/kubernetes-client-7.0/javaagent/src/main/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/KubernetesResource.java

@@ -12,7 +12,7 @@ class KubernetesResource {
 
   public static final Pattern CORE_RESOURCE_URL_PATH_PATTERN =
       Pattern.compile(
-          "^/api/v1(/namespaces/(?<namespace>[\\w-]+))?/(?<resource>[\\w-]+)(/(?<name>[\\w-]+))?(/(?<subresource>[\\w-]+))?");
+          "^/api/v1(/namespaces/(?<namespace>[\\w-]+))?/(?<resource>[\\w-]+)(/(?<name>[\\w-]+))?(/(?<subresource>[\\w-]+))?(/.*)?");
 
   public static final Pattern REGULAR_RESOURCE_URL_PATH_PATTERN =
       Pattern.compile(

+ 276 - 0
instrumentation/kubernetes-client-7.0/javaagent/src/version20Test/java/io/opentelemetry/javaagent/instrumentation/kubernetesclient/KubernetesClientVer20Test.java

@@ -0,0 +1,276 @@
+/*
+ * Copyright The OpenTelemetry Authors
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+package io.opentelemetry.javaagent.instrumentation.kubernetesclient;
+
+import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanName;
+import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
+import static org.assertj.core.api.Assertions.assertThat;
+
+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.api.common.AttributeKey;
+import io.opentelemetry.api.trace.SpanKind;
+import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
+import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
+import io.opentelemetry.sdk.trace.data.StatusData;
+import io.opentelemetry.semconv.SemanticAttributes;
+import io.opentelemetry.testing.internal.armeria.common.HttpResponse;
+import io.opentelemetry.testing.internal.armeria.common.HttpStatus;
+import io.opentelemetry.testing.internal.armeria.common.MediaType;
+import io.opentelemetry.testing.internal.armeria.testing.junit5.server.mock.MockWebServerExtension;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.RegisterExtension;
+
+class KubernetesClientVer20Test {
+
+  private static final String TEST_USER_AGENT = "test-user-agent";
+
+  @RegisterExtension
+  private static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
+
+  private final MockWebServerExtension mockWebServer = new MockWebServerExtension();
+
+  private CoreV1Api coreV1Api;
+
+  @BeforeEach
+  void beforeEach() {
+    mockWebServer.start();
+    ApiClient apiClient = new ApiClient();
+    apiClient.setUserAgent(TEST_USER_AGENT);
+    apiClient.setBasePath(mockWebServer.httpUri().toString());
+    coreV1Api = new CoreV1Api(apiClient);
+  }
+
+  @AfterEach
+  void afterEach() {
+    mockWebServer.stop();
+  }
+
+  @Test
+  void synchronousCall() throws ApiException {
+    mockWebServer.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "42"));
+    String response =
+        testing.runWithSpan(
+            "parent",
+            () ->
+                coreV1Api
+                    .connectGetNamespacedPodProxyWithPath("name", "namespace", "path")
+                    .execute());
+
+    assertThat(response).isEqualTo("42");
+    assertThat(mockWebServer.takeRequest().request().headers().get("traceparent")).isNotBlank();
+
+    testing.waitAndAssertTraces(
+        trace ->
+            trace.hasSpansSatisfyingExactly(
+                span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
+                span ->
+                    span.hasName("get pods/proxy")
+                        .hasKind(SpanKind.CLIENT)
+                        .hasParent(trace.getSpan(0))
+                        .hasAttributesSatisfyingExactly(
+                            equalTo(
+                                SemanticAttributes.URL_FULL,
+                                mockWebServer.httpUri()
+                                    + "/api/v1/namespaces/namespace/pods/name/proxy/path"),
+                            equalTo(SemanticAttributes.HTTP_REQUEST_METHOD, "GET"),
+                            equalTo(SemanticAttributes.HTTP_RESPONSE_STATUS_CODE, 200),
+                            equalTo(SemanticAttributes.SERVER_ADDRESS, "127.0.0.1"),
+                            equalTo(SemanticAttributes.SERVER_PORT, mockWebServer.httpPort()),
+                            equalTo(
+                                AttributeKey.stringKey("kubernetes-client.namespace"), "namespace"),
+                            equalTo(AttributeKey.stringKey("kubernetes-client.name"), "name"))));
+  }
+
+  @Test
+  void handleErrorsInSyncCall() {
+    mockWebServer.enqueue(
+        HttpResponse.of(HttpStatus.valueOf(451), MediaType.PLAIN_TEXT_UTF_8, "42"));
+    ApiException exception = null;
+    try {
+      testing.runWithSpan(
+          "parent",
+          () -> {
+            coreV1Api.connectGetNamespacedPodProxyWithPath("name", "namespace", "path").execute();
+          });
+    } catch (ApiException e) {
+      exception = e;
+    }
+    ApiException apiException = exception;
+    assertThat(apiException).isNotNull();
+    assertThat(mockWebServer.takeRequest().request().headers().get("traceparent")).isNotBlank();
+
+    testing.waitAndAssertTraces(
+        trace ->
+            trace.hasSpansSatisfyingExactly(
+                span ->
+                    span.hasName("parent")
+                        .hasKind(SpanKind.INTERNAL)
+                        .hasNoParent()
+                        .hasStatus(StatusData.error())
+                        .hasException(apiException),
+                span ->
+                    span.hasName("get pods/proxy")
+                        .hasKind(SpanKind.CLIENT)
+                        .hasParent(trace.getSpan(0))
+                        .hasStatus(StatusData.error())
+                        .hasException(apiException)
+                        .hasAttributesSatisfyingExactly(
+                            equalTo(
+                                SemanticAttributes.URL_FULL,
+                                mockWebServer.httpUri()
+                                    + "/api/v1/namespaces/namespace/pods/name/proxy/path"),
+                            equalTo(SemanticAttributes.HTTP_REQUEST_METHOD, "GET"),
+                            equalTo(SemanticAttributes.HTTP_RESPONSE_STATUS_CODE, 451),
+                            equalTo(SemanticAttributes.SERVER_ADDRESS, "127.0.0.1"),
+                            equalTo(SemanticAttributes.SERVER_PORT, mockWebServer.httpPort()),
+                            equalTo(SemanticAttributes.ERROR_TYPE, "451"),
+                            equalTo(
+                                AttributeKey.stringKey("kubernetes-client.namespace"), "namespace"),
+                            equalTo(AttributeKey.stringKey("kubernetes-client.name"), "name"))));
+  }
+
+  @Test
+  void asynchronousCall() throws ApiException, InterruptedException {
+    mockWebServer.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "42"));
+
+    AtomicReference<String> responseBodyReference = new AtomicReference<>();
+    CountDownLatch countDownLatch = new CountDownLatch(1);
+
+    testing.runWithSpan(
+        "parent",
+        () -> {
+          coreV1Api
+              .connectGetNamespacedPodProxyWithPath("name", "namespace", "path")
+              .executeAsync(
+                  new ApiCallbackTemplate() {
+                    @Override
+                    public void onSuccess(
+                        String result, int statusCode, Map<String, List<String>> responseHeaders) {
+                      responseBodyReference.set(result);
+                      countDownLatch.countDown();
+                      testing.runWithSpan("callback", () -> {});
+                    }
+                  });
+        });
+
+    countDownLatch.await();
+
+    assertThat(responseBodyReference.get()).isEqualTo("42");
+    assertThat(mockWebServer.takeRequest().request().headers().get("traceparent")).isNotBlank();
+
+    testing.waitAndAssertSortedTraces(
+        orderByRootSpanName("parent", "get pods/proxy", "callback"),
+        trace ->
+            trace.hasSpansSatisfyingExactly(
+                span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
+                span ->
+                    span.hasName("get pods/proxy")
+                        .hasKind(SpanKind.CLIENT)
+                        .hasParent(trace.getSpan(0))
+                        .hasAttributesSatisfyingExactly(
+                            equalTo(
+                                SemanticAttributes.URL_FULL,
+                                mockWebServer.httpUri()
+                                    + "/api/v1/namespaces/namespace/pods/name/proxy/path"),
+                            equalTo(SemanticAttributes.HTTP_REQUEST_METHOD, "GET"),
+                            equalTo(SemanticAttributes.HTTP_RESPONSE_STATUS_CODE, 200),
+                            equalTo(SemanticAttributes.SERVER_ADDRESS, "127.0.0.1"),
+                            equalTo(SemanticAttributes.SERVER_PORT, mockWebServer.httpPort()),
+                            equalTo(
+                                AttributeKey.stringKey("kubernetes-client.namespace"), "namespace"),
+                            equalTo(AttributeKey.stringKey("kubernetes-client.name"), "name")),
+                span ->
+                    span.hasName("callback")
+                        .hasKind(SpanKind.INTERNAL)
+                        .hasParent(trace.getSpan(0))));
+  }
+
+  @Test
+  void handleErrorsInAsynchronousCall() throws ApiException, InterruptedException {
+
+    mockWebServer.enqueue(
+        HttpResponse.of(HttpStatus.valueOf(451), MediaType.PLAIN_TEXT_UTF_8, "42"));
+
+    AtomicReference<ApiException> exceptionReference = new AtomicReference<>();
+    CountDownLatch countDownLatch = new CountDownLatch(1);
+
+    testing.runWithSpan(
+        "parent",
+        () -> {
+          coreV1Api
+              .connectGetNamespacedPodProxyWithPath("name", "namespace", "path")
+              .executeAsync(
+                  new ApiCallbackTemplate() {
+                    @Override
+                    public void onFailure(
+                        ApiException e, int statusCode, Map<String, List<String>> responseHeaders) {
+                      exceptionReference.set(e);
+                      countDownLatch.countDown();
+                      testing.runWithSpan("callback", () -> {});
+                    }
+                  });
+        });
+
+    countDownLatch.await();
+
+    assertThat(exceptionReference.get()).isNotNull();
+    assertThat(mockWebServer.takeRequest().request().headers().get("traceparent")).isNotBlank();
+
+    testing.waitAndAssertSortedTraces(
+        orderByRootSpanName("parent", "get pods/proxy", "callback"),
+        trace ->
+            trace.hasSpansSatisfyingExactly(
+                span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
+                span ->
+                    span.hasName("get pods/proxy")
+                        .hasKind(SpanKind.CLIENT)
+                        .hasParent(trace.getSpan(0))
+                        .hasStatus(StatusData.error())
+                        .hasException(exceptionReference.get())
+                        .hasAttributesSatisfyingExactly(
+                            equalTo(
+                                SemanticAttributes.URL_FULL,
+                                mockWebServer.httpUri()
+                                    + "/api/v1/namespaces/namespace/pods/name/proxy/path"),
+                            equalTo(SemanticAttributes.HTTP_REQUEST_METHOD, "GET"),
+                            equalTo(SemanticAttributes.HTTP_RESPONSE_STATUS_CODE, 451),
+                            equalTo(SemanticAttributes.SERVER_ADDRESS, "127.0.0.1"),
+                            equalTo(SemanticAttributes.SERVER_PORT, mockWebServer.httpPort()),
+                            equalTo(SemanticAttributes.ERROR_TYPE, "451"),
+                            equalTo(
+                                AttributeKey.stringKey("kubernetes-client.namespace"), "namespace"),
+                            equalTo(AttributeKey.stringKey("kubernetes-client.name"), "name")),
+                span ->
+                    span.hasName("callback")
+                        .hasKind(SpanKind.INTERNAL)
+                        .hasParent(trace.getSpan(0))));
+  }
+
+  private static class ApiCallbackTemplate implements ApiCallback<String> {
+    @Override
+    public void onFailure(
+        ApiException e, int statusCode, Map<String, List<String>> responseHeaders) {}
+
+    @Override
+    public void onSuccess(
+        String result, int statusCode, Map<String, List<String>> responseHeaders) {}
+
+    @Override
+    public void onUploadProgress(long bytesWritten, long contentLength, boolean done) {}
+
+    @Override
+    public void onDownloadProgress(long bytesRead, long contentLength, boolean done) {}
+  }
+}