Skip to content

Commit b3d2aaf

Browse files
authored
Fix kubernetes client latest dep tests (#10433)
1 parent 1f5fe2f commit b3d2aaf

File tree

5 files changed

+301
-5
lines changed

5 files changed

+301
-5
lines changed

instrumentation/kubernetes-client-7.0/javaagent/build.gradle.kts

+22-2
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,29 @@ muzzle {
1414
dependencies {
1515
library("io.kubernetes:client-java-api:7.0.0")
1616

17-
implementation(project(":instrumentation:okhttp:okhttp-3.0:javaagent"))
18-
1917
testInstrumentation(project(":instrumentation:okhttp:okhttp-3.0:javaagent"))
18+
19+
latestDepTestLibrary("io.kubernetes:client-java-api:19.+")
20+
}
21+
22+
testing {
23+
suites {
24+
val version20Test by registering(JvmTestSuite::class) {
25+
dependencies {
26+
if (findProperty("testLatestDeps") as Boolean) {
27+
implementation("io.kubernetes:client-java-api:+")
28+
} else {
29+
implementation("io.kubernetes:client-java-api:20.0.0")
30+
}
31+
}
32+
}
33+
}
34+
}
35+
36+
tasks {
37+
check {
38+
dependsOn(testing.suites)
39+
}
2040
}
2141

2242
tasks.withType<Test>().configureEach {

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ public ElementMatcher<TypeDescription> typeMatcher() {
3535
@Override
3636
public void transform(TypeTransformer transformer) {
3737
transformer.applyAdviceToMethod(
38-
isPublic().and(named("buildRequest")).and(takesArguments(10)),
38+
isPublic().and(named("buildRequest")).and(takesArguments(10).or(takesArguments(11))),
3939
this.getClass().getName() + "$BuildRequestAdvice");
4040
transformer.applyAdviceToMethod(
4141
isPublic()

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public KubernetesVerb getVerb() {
8989
@Override
9090
public String toString() {
9191
if (isNonResourceRequest) {
92-
return verb.value() + ' ' + urlPath;
92+
return (verb != null ? verb.value() + ' ' : "") + urlPath;
9393
}
9494

9595
String groupVersion;

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ class KubernetesResource {
1212

1313
public static final Pattern CORE_RESOURCE_URL_PATH_PATTERN =
1414
Pattern.compile(
15-
"^/api/v1(/namespaces/(?<namespace>[\\w-]+))?/(?<resource>[\\w-]+)(/(?<name>[\\w-]+))?(/(?<subresource>[\\w-]+))?");
15+
"^/api/v1(/namespaces/(?<namespace>[\\w-]+))?/(?<resource>[\\w-]+)(/(?<name>[\\w-]+))?(/(?<subresource>[\\w-]+))?(/.*)?");
1616

1717
public static final Pattern REGULAR_RESOURCE_URL_PATH_PATTERN =
1818
Pattern.compile(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.javaagent.instrumentation.kubernetesclient;
7+
8+
import static io.opentelemetry.instrumentation.testing.util.TelemetryDataUtil.orderByRootSpanName;
9+
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo;
10+
import static org.assertj.core.api.Assertions.assertThat;
11+
12+
import io.kubernetes.client.openapi.ApiCallback;
13+
import io.kubernetes.client.openapi.ApiClient;
14+
import io.kubernetes.client.openapi.ApiException;
15+
import io.kubernetes.client.openapi.apis.CoreV1Api;
16+
import io.opentelemetry.api.common.AttributeKey;
17+
import io.opentelemetry.api.trace.SpanKind;
18+
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
19+
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
20+
import io.opentelemetry.sdk.trace.data.StatusData;
21+
import io.opentelemetry.semconv.SemanticAttributes;
22+
import io.opentelemetry.testing.internal.armeria.common.HttpResponse;
23+
import io.opentelemetry.testing.internal.armeria.common.HttpStatus;
24+
import io.opentelemetry.testing.internal.armeria.common.MediaType;
25+
import io.opentelemetry.testing.internal.armeria.testing.junit5.server.mock.MockWebServerExtension;
26+
import java.util.List;
27+
import java.util.Map;
28+
import java.util.concurrent.CountDownLatch;
29+
import java.util.concurrent.atomic.AtomicReference;
30+
import org.junit.jupiter.api.AfterEach;
31+
import org.junit.jupiter.api.BeforeEach;
32+
import org.junit.jupiter.api.Test;
33+
import org.junit.jupiter.api.extension.RegisterExtension;
34+
35+
class KubernetesClientVer20Test {
36+
37+
private static final String TEST_USER_AGENT = "test-user-agent";
38+
39+
@RegisterExtension
40+
private static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
41+
42+
private final MockWebServerExtension mockWebServer = new MockWebServerExtension();
43+
44+
private CoreV1Api coreV1Api;
45+
46+
@BeforeEach
47+
void beforeEach() {
48+
mockWebServer.start();
49+
ApiClient apiClient = new ApiClient();
50+
apiClient.setUserAgent(TEST_USER_AGENT);
51+
apiClient.setBasePath(mockWebServer.httpUri().toString());
52+
coreV1Api = new CoreV1Api(apiClient);
53+
}
54+
55+
@AfterEach
56+
void afterEach() {
57+
mockWebServer.stop();
58+
}
59+
60+
@Test
61+
void synchronousCall() throws ApiException {
62+
mockWebServer.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "42"));
63+
String response =
64+
testing.runWithSpan(
65+
"parent",
66+
() ->
67+
coreV1Api
68+
.connectGetNamespacedPodProxyWithPath("name", "namespace", "path")
69+
.execute());
70+
71+
assertThat(response).isEqualTo("42");
72+
assertThat(mockWebServer.takeRequest().request().headers().get("traceparent")).isNotBlank();
73+
74+
testing.waitAndAssertTraces(
75+
trace ->
76+
trace.hasSpansSatisfyingExactly(
77+
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
78+
span ->
79+
span.hasName("get pods/proxy")
80+
.hasKind(SpanKind.CLIENT)
81+
.hasParent(trace.getSpan(0))
82+
.hasAttributesSatisfyingExactly(
83+
equalTo(
84+
SemanticAttributes.URL_FULL,
85+
mockWebServer.httpUri()
86+
+ "/api/v1/namespaces/namespace/pods/name/proxy/path"),
87+
equalTo(SemanticAttributes.HTTP_REQUEST_METHOD, "GET"),
88+
equalTo(SemanticAttributes.HTTP_RESPONSE_STATUS_CODE, 200),
89+
equalTo(SemanticAttributes.SERVER_ADDRESS, "127.0.0.1"),
90+
equalTo(SemanticAttributes.SERVER_PORT, mockWebServer.httpPort()),
91+
equalTo(
92+
AttributeKey.stringKey("kubernetes-client.namespace"), "namespace"),
93+
equalTo(AttributeKey.stringKey("kubernetes-client.name"), "name"))));
94+
}
95+
96+
@Test
97+
void handleErrorsInSyncCall() {
98+
mockWebServer.enqueue(
99+
HttpResponse.of(HttpStatus.valueOf(451), MediaType.PLAIN_TEXT_UTF_8, "42"));
100+
ApiException exception = null;
101+
try {
102+
testing.runWithSpan(
103+
"parent",
104+
() -> {
105+
coreV1Api.connectGetNamespacedPodProxyWithPath("name", "namespace", "path").execute();
106+
});
107+
} catch (ApiException e) {
108+
exception = e;
109+
}
110+
ApiException apiException = exception;
111+
assertThat(apiException).isNotNull();
112+
assertThat(mockWebServer.takeRequest().request().headers().get("traceparent")).isNotBlank();
113+
114+
testing.waitAndAssertTraces(
115+
trace ->
116+
trace.hasSpansSatisfyingExactly(
117+
span ->
118+
span.hasName("parent")
119+
.hasKind(SpanKind.INTERNAL)
120+
.hasNoParent()
121+
.hasStatus(StatusData.error())
122+
.hasException(apiException),
123+
span ->
124+
span.hasName("get pods/proxy")
125+
.hasKind(SpanKind.CLIENT)
126+
.hasParent(trace.getSpan(0))
127+
.hasStatus(StatusData.error())
128+
.hasException(apiException)
129+
.hasAttributesSatisfyingExactly(
130+
equalTo(
131+
SemanticAttributes.URL_FULL,
132+
mockWebServer.httpUri()
133+
+ "/api/v1/namespaces/namespace/pods/name/proxy/path"),
134+
equalTo(SemanticAttributes.HTTP_REQUEST_METHOD, "GET"),
135+
equalTo(SemanticAttributes.HTTP_RESPONSE_STATUS_CODE, 451),
136+
equalTo(SemanticAttributes.SERVER_ADDRESS, "127.0.0.1"),
137+
equalTo(SemanticAttributes.SERVER_PORT, mockWebServer.httpPort()),
138+
equalTo(SemanticAttributes.ERROR_TYPE, "451"),
139+
equalTo(
140+
AttributeKey.stringKey("kubernetes-client.namespace"), "namespace"),
141+
equalTo(AttributeKey.stringKey("kubernetes-client.name"), "name"))));
142+
}
143+
144+
@Test
145+
void asynchronousCall() throws ApiException, InterruptedException {
146+
mockWebServer.enqueue(HttpResponse.of(HttpStatus.OK, MediaType.PLAIN_TEXT_UTF_8, "42"));
147+
148+
AtomicReference<String> responseBodyReference = new AtomicReference<>();
149+
CountDownLatch countDownLatch = new CountDownLatch(1);
150+
151+
testing.runWithSpan(
152+
"parent",
153+
() -> {
154+
coreV1Api
155+
.connectGetNamespacedPodProxyWithPath("name", "namespace", "path")
156+
.executeAsync(
157+
new ApiCallbackTemplate() {
158+
@Override
159+
public void onSuccess(
160+
String result, int statusCode, Map<String, List<String>> responseHeaders) {
161+
responseBodyReference.set(result);
162+
countDownLatch.countDown();
163+
testing.runWithSpan("callback", () -> {});
164+
}
165+
});
166+
});
167+
168+
countDownLatch.await();
169+
170+
assertThat(responseBodyReference.get()).isEqualTo("42");
171+
assertThat(mockWebServer.takeRequest().request().headers().get("traceparent")).isNotBlank();
172+
173+
testing.waitAndAssertSortedTraces(
174+
orderByRootSpanName("parent", "get pods/proxy", "callback"),
175+
trace ->
176+
trace.hasSpansSatisfyingExactly(
177+
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
178+
span ->
179+
span.hasName("get pods/proxy")
180+
.hasKind(SpanKind.CLIENT)
181+
.hasParent(trace.getSpan(0))
182+
.hasAttributesSatisfyingExactly(
183+
equalTo(
184+
SemanticAttributes.URL_FULL,
185+
mockWebServer.httpUri()
186+
+ "/api/v1/namespaces/namespace/pods/name/proxy/path"),
187+
equalTo(SemanticAttributes.HTTP_REQUEST_METHOD, "GET"),
188+
equalTo(SemanticAttributes.HTTP_RESPONSE_STATUS_CODE, 200),
189+
equalTo(SemanticAttributes.SERVER_ADDRESS, "127.0.0.1"),
190+
equalTo(SemanticAttributes.SERVER_PORT, mockWebServer.httpPort()),
191+
equalTo(
192+
AttributeKey.stringKey("kubernetes-client.namespace"), "namespace"),
193+
equalTo(AttributeKey.stringKey("kubernetes-client.name"), "name")),
194+
span ->
195+
span.hasName("callback")
196+
.hasKind(SpanKind.INTERNAL)
197+
.hasParent(trace.getSpan(0))));
198+
}
199+
200+
@Test
201+
void handleErrorsInAsynchronousCall() throws ApiException, InterruptedException {
202+
203+
mockWebServer.enqueue(
204+
HttpResponse.of(HttpStatus.valueOf(451), MediaType.PLAIN_TEXT_UTF_8, "42"));
205+
206+
AtomicReference<ApiException> exceptionReference = new AtomicReference<>();
207+
CountDownLatch countDownLatch = new CountDownLatch(1);
208+
209+
testing.runWithSpan(
210+
"parent",
211+
() -> {
212+
coreV1Api
213+
.connectGetNamespacedPodProxyWithPath("name", "namespace", "path")
214+
.executeAsync(
215+
new ApiCallbackTemplate() {
216+
@Override
217+
public void onFailure(
218+
ApiException e, int statusCode, Map<String, List<String>> responseHeaders) {
219+
exceptionReference.set(e);
220+
countDownLatch.countDown();
221+
testing.runWithSpan("callback", () -> {});
222+
}
223+
});
224+
});
225+
226+
countDownLatch.await();
227+
228+
assertThat(exceptionReference.get()).isNotNull();
229+
assertThat(mockWebServer.takeRequest().request().headers().get("traceparent")).isNotBlank();
230+
231+
testing.waitAndAssertSortedTraces(
232+
orderByRootSpanName("parent", "get pods/proxy", "callback"),
233+
trace ->
234+
trace.hasSpansSatisfyingExactly(
235+
span -> span.hasName("parent").hasKind(SpanKind.INTERNAL).hasNoParent(),
236+
span ->
237+
span.hasName("get pods/proxy")
238+
.hasKind(SpanKind.CLIENT)
239+
.hasParent(trace.getSpan(0))
240+
.hasStatus(StatusData.error())
241+
.hasException(exceptionReference.get())
242+
.hasAttributesSatisfyingExactly(
243+
equalTo(
244+
SemanticAttributes.URL_FULL,
245+
mockWebServer.httpUri()
246+
+ "/api/v1/namespaces/namespace/pods/name/proxy/path"),
247+
equalTo(SemanticAttributes.HTTP_REQUEST_METHOD, "GET"),
248+
equalTo(SemanticAttributes.HTTP_RESPONSE_STATUS_CODE, 451),
249+
equalTo(SemanticAttributes.SERVER_ADDRESS, "127.0.0.1"),
250+
equalTo(SemanticAttributes.SERVER_PORT, mockWebServer.httpPort()),
251+
equalTo(SemanticAttributes.ERROR_TYPE, "451"),
252+
equalTo(
253+
AttributeKey.stringKey("kubernetes-client.namespace"), "namespace"),
254+
equalTo(AttributeKey.stringKey("kubernetes-client.name"), "name")),
255+
span ->
256+
span.hasName("callback")
257+
.hasKind(SpanKind.INTERNAL)
258+
.hasParent(trace.getSpan(0))));
259+
}
260+
261+
private static class ApiCallbackTemplate implements ApiCallback<String> {
262+
@Override
263+
public void onFailure(
264+
ApiException e, int statusCode, Map<String, List<String>> responseHeaders) {}
265+
266+
@Override
267+
public void onSuccess(
268+
String result, int statusCode, Map<String, List<String>> responseHeaders) {}
269+
270+
@Override
271+
public void onUploadProgress(long bytesWritten, long contentLength, boolean done) {}
272+
273+
@Override
274+
public void onDownloadProgress(long bytesRead, long contentLength, boolean done) {}
275+
}
276+
}

0 commit comments

Comments
 (0)