From b6e0f7f6f2802d50d485ce7bf1dfec3f6e27e383 Mon Sep 17 00:00:00 2001 From: Rohan Kumar Date: Tue, 22 Dec 2020 22:46:14 +0530 Subject: [PATCH] Add an Integration test for Typed CustomResource support (#2685) Added an Integration test for typed customResources() API which tests standard operations like create, createOrReplace, get, list, watch, update, updateStatus, delete * Use CustomResourceDefinitionBuilder rather thanYaml for applying CRD * Infer CRD from CustomResourceDefinitionContext Use CustomResourceDefinitionContext.v1CRDFromCustomResourceType to get CustomResourceDefinition details from POJO annotations --- .../io/fabric8/commons/ClusterEntity.java | 15 + .../src/test/java/io/fabric8/crd/Pet.java | 25 ++ .../src/test/java/io/fabric8/crd/PetSpec.java | 34 +++ .../test/java/io/fabric8/crd/PetStatus.java | 33 +++ .../kubernetes/TypedCustomResourceIT.java | 268 ++++++++++++++++++ 5 files changed, 375 insertions(+) create mode 100644 kubernetes-itests/src/test/java/io/fabric8/crd/Pet.java create mode 100644 kubernetes-itests/src/test/java/io/fabric8/crd/PetSpec.java create mode 100644 kubernetes-itests/src/test/java/io/fabric8/crd/PetStatus.java create mode 100644 kubernetes-itests/src/test/java/io/fabric8/kubernetes/TypedCustomResourceIT.java diff --git a/kubernetes-itests/src/test/java/io/fabric8/commons/ClusterEntity.java b/kubernetes-itests/src/test/java/io/fabric8/commons/ClusterEntity.java index 347e44fc3c5..6a4908ce910 100644 --- a/kubernetes-itests/src/test/java/io/fabric8/commons/ClusterEntity.java +++ b/kubernetes-itests/src/test/java/io/fabric8/commons/ClusterEntity.java @@ -46,6 +46,15 @@ public static void apply(InputStream inputStream) { } } + public static void apply(HasMetadata kubernetesResource) { + try (KubernetesClient client = new DefaultKubernetesClient()) { + String namespace = getArquillianNamespace(); + if (namespace != null) { + client.resource(kubernetesResource).inNamespace(namespace).createOrReplace(); + } + } + } + public static void remove(InputStream inputStream) { try (KubernetesClient client = new DefaultKubernetesClient()) { List items = client.load(inputStream).get(); @@ -53,6 +62,12 @@ public static void remove(InputStream inputStream) { } } + public static void remove(HasMetadata kubernetesResource) { + try (KubernetesClient client = new DefaultKubernetesClient()) { + client.resource(kubernetesResource).inNamespace(getArquillianNamespace()).withPropagationPolicy(DeletionPropagation.BACKGROUND).delete(); + } + } + public static boolean kubernetesVersionAtLeast(String majorVersion, String minorVersion) { try (KubernetesClient client = new DefaultKubernetesClient()) { VersionInfo versionInfo = client.getVersion(); diff --git a/kubernetes-itests/src/test/java/io/fabric8/crd/Pet.java b/kubernetes-itests/src/test/java/io/fabric8/crd/Pet.java new file mode 100644 index 00000000000..5397660e16d --- /dev/null +++ b/kubernetes-itests/src/test/java/io/fabric8/crd/Pet.java @@ -0,0 +1,25 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crd; + +import io.fabric8.kubernetes.api.model.Namespaced; +import io.fabric8.kubernetes.client.CustomResource; +import io.fabric8.kubernetes.model.annotation.Group; +import io.fabric8.kubernetes.model.annotation.Version; + +@Version("v1alpha1") +@Group("testing.fabric8.io") +public class Pet extends CustomResource implements Namespaced { } diff --git a/kubernetes-itests/src/test/java/io/fabric8/crd/PetSpec.java b/kubernetes-itests/src/test/java/io/fabric8/crd/PetSpec.java new file mode 100644 index 00000000000..90a130106dc --- /dev/null +++ b/kubernetes-itests/src/test/java/io/fabric8/crd/PetSpec.java @@ -0,0 +1,34 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crd; + +public class PetSpec { + private String type; + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + @Override + public String toString() { + return "PetSpec{type=" + type + "}"; + } +} + diff --git a/kubernetes-itests/src/test/java/io/fabric8/crd/PetStatus.java b/kubernetes-itests/src/test/java/io/fabric8/crd/PetStatus.java new file mode 100644 index 00000000000..bcb08f76f31 --- /dev/null +++ b/kubernetes-itests/src/test/java/io/fabric8/crd/PetStatus.java @@ -0,0 +1,33 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.crd; + +public class PetStatus { + private String currentStatus; + + public String getCurrentStatus() { + return currentStatus; + } + + public void setCurrentStatus(String currentStatus) { + this.currentStatus = currentStatus; + } + + @Override + public String toString() { + return "PetStatus{currentStatus=" + currentStatus + "}"; + } +} diff --git a/kubernetes-itests/src/test/java/io/fabric8/kubernetes/TypedCustomResourceIT.java b/kubernetes-itests/src/test/java/io/fabric8/kubernetes/TypedCustomResourceIT.java new file mode 100644 index 00000000000..f3d8fd1c7b0 --- /dev/null +++ b/kubernetes-itests/src/test/java/io/fabric8/kubernetes/TypedCustomResourceIT.java @@ -0,0 +1,268 @@ +/** + * Copyright (C) 2015 Red Hat, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.fabric8.kubernetes; + +import io.fabric8.commons.AssumingK8sVersionAtLeast; +import io.fabric8.commons.ClusterEntity; +import io.fabric8.crd.Pet; +import io.fabric8.crd.PetSpec; +import io.fabric8.crd.PetStatus; +import io.fabric8.kubernetes.api.model.KubernetesResourceList; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.apiextensions.v1.CustomResourceDefinition; +import io.fabric8.kubernetes.api.model.apiextensions.v1.JSONSchemaPropsBuilder; +import io.fabric8.kubernetes.client.KubernetesClient; +import io.fabric8.kubernetes.client.Watch; +import io.fabric8.kubernetes.client.Watcher; +import io.fabric8.kubernetes.client.WatcherException; +import io.fabric8.kubernetes.client.dsl.MixedOperation; +import io.fabric8.kubernetes.client.dsl.Resource; +import io.fabric8.kubernetes.client.dsl.base.CustomResourceDefinitionContext; +import org.arquillian.cube.kubernetes.api.Session; +import org.arquillian.cube.kubernetes.impl.requirement.RequiresKubernetes; +import org.arquillian.cube.requirement.ArquillianConditionalRunner; +import org.jboss.arquillian.test.api.ArquillianResource; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.Collections; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import static org.awaitility.Awaitility.await; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +@RunWith(ArquillianConditionalRunner.class) +@RequiresKubernetes +public class TypedCustomResourceIT { + @ArquillianResource + KubernetesClient client; + + @ArquillianResource + Session session; + + private String currentNamespace; + + private static final CustomResourceDefinition petCrd = CustomResourceDefinitionContext.v1CRDFromCustomResourceType(Pet.class) + .editSpec().editVersion(0) + .withNewSubresources() + .withNewStatus().endStatus() + .endSubresources() + .withNewSchema() + .withNewOpenAPIV3Schema() + .withType("object") + .addToProperties(Collections.singletonMap("spec", new JSONSchemaPropsBuilder() + .withType("object") + .withProperties(Collections.singletonMap("type", new JSONSchemaPropsBuilder() + .withType("string") + .build())) + .build())) + .addToProperties(Collections.singletonMap("status", new JSONSchemaPropsBuilder() + .withType("object") + .withProperties(Collections.singletonMap("currentStatus", new JSONSchemaPropsBuilder() + .withType("string") + .build())) + .build())) + .endOpenAPIV3Schema() + .endSchema() + .endVersion() + .endSpec() + .build(); + + private MixedOperation, Resource> petClient; + + @ClassRule + public static final AssumingK8sVersionAtLeast assumingK8sVersion = new AssumingK8sVersionAtLeast("1", "16"); + + @BeforeClass + public static void init() { + ClusterEntity.apply(petCrd); + } + + @Before + public void initPetClientAndCurrentNamespace() { + petClient = client.customResources(Pet.class); + currentNamespace = session.getNamespace(); + } + + @Test + public void create() { + // Given + Pet pet = createNewPet("pet-create", "Dog", null); + + // When + Pet createdPet = petClient.inNamespace(currentNamespace).create(pet); + + // Then + assertPet(createdPet, "pet-create", "Dog", null); + } + + @Test + public void createOrReplace() { + // Given + Pet pet = createNewPet("pet-createorreplace", "Dog", null); + + // When + Pet createdPet = petClient.inNamespace(currentNamespace).create(pet); + createdPet.getSpec().setType("Buffalo"); + Pet replacedPet = petClient.inNamespace(currentNamespace).createOrReplace(createdPet); + + // Then + assertPet(replacedPet, "pet-createorreplace", "Buffalo", null); + } + + @Test + public void get() { + // Given + Pet pet = createNewPet("pet-get", "Cow", null); + + // When + petClient.inNamespace(currentNamespace).create(pet); + Pet petFromServer = petClient.inNamespace(currentNamespace).withName("pet-get").get(); + + // Then + assertPet(petFromServer, "pet-get", "Cow", null); + } + + @Test + public void list() { + // Given + Pet pet = createNewPet("pet-list", "Parrot", null); + + // When + petClient.inNamespace(currentNamespace).create(pet); + KubernetesResourceList petList = petClient.inNamespace(currentNamespace).list(); + + // Then + assertNotNull(petList); + assertNotNull(petList.getItems()); + assertTrue(petList.getItems().size() >= 1); + } + + @Test + public void update() { + // Given + Pet pet = createNewPet("pet-update", "Pigeon", null); + + // When + petClient.inNamespace(currentNamespace).create(pet); + await().atMost(5, TimeUnit.SECONDS) + .until(() -> petClient.inNamespace(currentNamespace).withName("pet-update").get() != null); + Pet updatedPet = petClient.inNamespace(currentNamespace).withName("pet-update").edit(pet1 -> { + pet1.getMetadata().setAnnotations(Collections.singletonMap("first", "1")); + return pet1; + }); + + // Then + assertPet(updatedPet, "pet-update", "Pigeon", null); + assertNotNull(updatedPet.getMetadata().getAnnotations()); + assertEquals(1, updatedPet.getMetadata().getAnnotations().size()); + assertEquals("1", updatedPet.getMetadata().getAnnotations().get("first")); + } + + @Test + public void updateStatusSubresource() { + // Given + Pet pet = createNewPet("pet-updatestatus", "Pigeon", null); + PetStatus petStatusToUpdate = new PetStatus(); + petStatusToUpdate.setCurrentStatus("Sleeping"); + + // When + pet = petClient.inNamespace(currentNamespace).create(pet); + await().atMost(5, TimeUnit.SECONDS) + .until(() -> petClient.inNamespace(currentNamespace).withName("pet-updatestatus").get() != null); + pet.setStatus(petStatusToUpdate); + Pet updatedPet = petClient.inNamespace(currentNamespace).updateStatus(pet); + + // Then + assertPet(updatedPet, "pet-updatestatus", "Pigeon", "Sleeping"); + } + + @Test + public void watch() throws InterruptedException { + // Given + Pet pet = createNewPet("pet-watch", "Hamster", null); + + // When + CountDownLatch creationEventReceived = new CountDownLatch(1); + Watch petWatch = petClient.inNamespace(currentNamespace).watch(new Watcher() { + @Override + public void eventReceived(Action action, Pet resource) { + if (resource.getMetadata().getName().equals("pet-watch")) { + creationEventReceived.countDown(); + } + } + + @Override + public void onClose(WatcherException cause) { } + }); + petClient.inNamespace(currentNamespace).createOrReplace(pet); + + // Then + assertTrue(creationEventReceived.await(1, TimeUnit.SECONDS)); + petWatch.close(); + } + + @Test + public void delete() { + // Given + Pet pet = createNewPet("pet-delete", "Cow", "Eating"); + + // When + petClient.inNamespace(currentNamespace).create(pet); + Boolean isDeleted = petClient.inNamespace(currentNamespace).withName("pet-delete").delete(); + + // Then + assertNotNull(isDeleted); + assertTrue(isDeleted); + } + + private void assertPet(Pet pet, String name, String type, String currentStatus) { + assertNotNull(pet); + assertEquals(name, pet.getMetadata().getName()); + assertEquals(type, pet.getSpec().getType()); + if (currentStatus != null) { + assertEquals(currentStatus, pet.getStatus().getCurrentStatus()); + } + } + + @AfterClass + public static void cleanup() { + ClusterEntity.remove(petCrd); + } + + private Pet createNewPet(String name, String type, String currentStatus) { + Pet pet = new Pet(); + + PetSpec petSpec = new PetSpec(); + petSpec.setType(type); + pet.setSpec(petSpec); + + if (currentStatus != null) { + PetStatus petStatus = new PetStatus(); + petStatus.setCurrentStatus(currentStatus); + pet.setStatus(petStatus); + } + pet.setMetadata(new ObjectMetaBuilder().withName(name).build()); + return pet; + } +}