Skip to content

Commit

Permalink
test: added tests for KubernetesClient serialization/deserialization
Browse files Browse the repository at this point in the history
Should ensure that (de)serialization works as expected both in
JVM and Native modes.

Signed-off-by: Marc Nuri <marc@marcnuri.com>
  • Loading branch information
manusa committed Jun 18, 2024
1 parent 50431f0 commit f4ae263
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,29 @@
import java.util.List;
import java.util.UUID;

import jakarta.inject.Inject;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.DELETE;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;

import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.PodBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.dsl.NonDeletingOperation;

@Path("/pod")
public class Pods {

private final KubernetesClient kubernetesClient;

@Inject
public Pods(KubernetesClient kubernetesClient) {
this.kubernetesClient = kubernetesClient;
}
Expand All @@ -38,7 +44,7 @@ public Response deleteFirst(@PathParam("namespace") String namespace) {
return Response.status(404).build();
}

kubernetesClient.pods().inNamespace(namespace).delete(pods.get(0));
kubernetesClient.pods().inNamespace(namespace).resource(pods.get(0)).delete();
return Response.noContent().build();
}

Expand All @@ -53,12 +59,13 @@ public Response updateFirst(@PathParam("namespace") String namespace) {
final Pod pod = pods.get(0);
final String podName = pod.getMetadata().getName();
// would normally do some kind of meaningful update here
Pod updatedPod = new PodBuilder().withNewMetadata().withName(podName)
Pod updatedPod = new PodBuilder().withNewMetadata()
.withName(podName)
.addToAnnotations("test-reference", "12345")
.addToLabels("key1", "value1").endMetadata()
.build();

updatedPod = kubernetesClient.pods().withName(podName).createOrReplace(updatedPod);
updatedPod = kubernetesClient.pods().resource(updatedPod).createOr(NonDeletingOperation::update);
return Response.ok(updatedPod).build();
}

Expand All @@ -67,10 +74,29 @@ public Response updateFirst(@PathParam("namespace") String namespace) {
public Response createNew(@PathParam("namespace") String namespace) {
return Response
.ok(kubernetesClient.pods().inNamespace(namespace)
.create(new PodBuilder().withNewMetadata()
.resource(new PodBuilder().withNewMetadata()
.withName(UUID.randomUUID().toString())
.addToAnnotations("test-reference", "12345")
.endMetadata().build()))
.endMetadata().build())
.create())
.build();
}

@GET
@Path("/{namespace}/{podName}")
@Produces(MediaType.APPLICATION_JSON)
public Response get(@PathParam("namespace") String namespace, @PathParam("podName") String name) {
return Response.ok(kubernetesClient.pods().inNamespace(namespace).withName(name).get()).build();
}

@PUT
@Path("/{namespace}/{podName}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response upsert(@PathParam("namespace") String namespace, @PathParam("podName") String name, Pod pod) {
final Pod updatedPod = kubernetesClient.pods().inNamespace(namespace)
.resource(new PodBuilder(pod).editMetadata().withName(name).endMetadata().build())
.createOr(NonDeletingOperation::update);
return Response.ok(updatedPod).build();
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.quarkus.it.kubernetes.client;

import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Response;
Expand All @@ -11,6 +12,7 @@ public class Version {

private final KubernetesClient kubernetesClient;

@Inject
public Version(KubernetesClient kubernetesClient) {
this.kubernetesClient = kubernetesClient;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.quarkus.it.kubernetes.client;

import io.quarkus.test.junit.QuarkusIntegrationTest;

@QuarkusIntegrationTest
public class KubernetesClientSerializationIT extends KubernetesClientSerializationTest {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package io.quarkus.it.kubernetes.client;

import static io.restassured.RestAssured.given;
import static io.restassured.RestAssured.when;
import static org.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.nullValue;

import java.util.function.Consumer;

import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.ContainerBuilder;
import io.fabric8.kubernetes.api.model.ContainerPortBuilder;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.fabric8.kubernetes.api.model.PodBuilder;
import io.fabric8.kubernetes.api.model.PodSpecBuilder;
import io.fabric8.kubernetes.api.model.SecretBuilder;
import io.fabric8.kubernetes.api.model.VolumeMount;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.fabric8.kubernetes.client.server.mock.KubernetesServer;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.kubernetes.client.KubernetesTestServer;
import io.quarkus.test.kubernetes.client.WithKubernetesTestServer;
import io.restassured.filter.log.LogDetail;

@WithKubernetesTestServer(setup = KubernetesClientSerializationTest.CrudEnvironmentPreparation.class)
@QuarkusTest
public class KubernetesClientSerializationTest {

@KubernetesTestServer
private KubernetesServer mockServer;

@Test
@DisplayName("PUT with String body - Should serialize and persist the Pod")
void serialization() {
given()
.contentType("application/json")
.body("""
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "serialization-test"
},
"spec": {
"containers": [
{
"name": "test",
"image": "busybox",
"volumeMounts": [
{
"name": "test",
"mountPath": "/test"
}
]
}
]
}
}
""")
.when()
.put("/pod/{namespace}/{podName}", mockServer.getClient().getNamespace(), "serialization-test")
.then()
.statusCode(200);
// JSON data is persisted into the mock server
assertThat(mockServer.getClient().pods().withName("serialization-test").get())
.hasFieldOrPropertyWithValue("metadata.name", "serialization-test")
.extracting("spec.containers").asInstanceOf(InstanceOfAssertFactories.list(Container.class))
.singleElement()
.hasFieldOrPropertyWithValue("name", "test")
.hasFieldOrPropertyWithValue("image", "busybox")
.extracting(Container::getVolumeMounts)
.asInstanceOf(InstanceOfAssertFactories.list(VolumeMount.class))
.singleElement()
.hasFieldOrPropertyWithValue("name", "test")
.hasFieldOrPropertyWithValue("mountPath", "/test");
}

@Test
@DisplayName("GET - Should deserialize the Pod into valid JSON")
void deserialization() {
mockServer.getClient().pods().resource(new PodBuilder()
.withMetadata(new ObjectMetaBuilder()
.withName("deserialization-test")
.build())
.withSpec(new PodSpecBuilder()
.addToContainers(new ContainerBuilder()
.withName("deserialization-test-container")
.addToPorts(new ContainerPortBuilder()
.withContainerPort(8080)
.build())
.build())
.build())
.build())
.create();
// JSON data is retrieved from the mock server
when()
.get("/pod/{namespace}/{podName}", mockServer.getClient().getNamespace(), "deserialization-test")
.then()
.statusCode(200)
.body(
"metadata.name", is("deserialization-test"),
"spec.containers[0].name", is("deserialization-test-container"),
"spec.containers[0].ports[0].containerPort", is(8080),
"metadata.annotations", nullValue(),
// https://github.com/quarkusio/quarkus/issues/39934
"spec.overhead", nullValue())
.log().ifValidationFails(LogDetail.BODY);
}

public static final class CrudEnvironmentPreparation implements Consumer<KubernetesServer> {

@Override
public void accept(KubernetesServer kubernetesServer) {
final KubernetesClient kc = kubernetesServer.getClient();
kc.configMaps().resource(new ConfigMapBuilder()
.withNewMetadata().withName("cmap1").endMetadata()
.addToData("dummy", "I'm required")
.build()).create();
kc.configMaps().resource(new ConfigMapBuilder()
.withNewMetadata().withName("cmap2").endMetadata()
.addToData("dummysecret", "dumb")
.addToData("overridden.secret", "Alex")
.addToData("some.prop1", "I'm required")
.addToData("some.prop2", "I'm required (2)")
.addToData("some.prop3", "I'm required (3)")
.addToData("some.prop4", "I'm required (4)")
.addToData("some.prop5", "I'm required (5)")
.build()).create();
kc.secrets().resource(new SecretBuilder()
.withNewMetadata().withName("s1").endMetadata()
.addToData("secret.prop1", "c2VjcmV0")
.addToData("secret.prop2", "c2VjcmV0")
.addToData("secret.prop3", "c2VjcmV0")
.addToData("secret.prop4", "c2VjcmV0")
.build()).create();
}
}
}

0 comments on commit f4ae263

Please sign in to comment.