Skip to content

Commit 0a23618

Browse files
[release/v1.32.x] Fix kubernetes client latest dep tests (#10584)
Co-authored-by: Lauri Tulmin <[email protected]>
1 parent 9286aba commit 0a23618

File tree

5 files changed

+314
-5
lines changed

5 files changed

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

0 commit comments

Comments
 (0)