diff --git a/extended/src/test/java/io/kubernetes/client/extended/kubectl/KubectlDeleteTest.java b/extended/src/test/java/io/kubernetes/client/extended/kubectl/KubectlDeleteTest.java new file mode 100644 index 0000000000..ad87c53283 --- /dev/null +++ b/extended/src/test/java/io/kubernetes/client/extended/kubectl/KubectlDeleteTest.java @@ -0,0 +1,224 @@ +/* +Copyright 2020 The Kubernetes Authors. +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.kubernetes.client.extended.kubectl; + +import static com.github.tomakehurst.wiremock.client.WireMock.*; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.junit.Assert.assertThrows; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import com.github.tomakehurst.wiremock.stubbing.Scenario; +import io.kubernetes.client.extended.kubectl.Kubectl; +import io.kubernetes.client.extended.kubectl.KubectlDelete; +import io.kubernetes.client.extended.kubectl.exception.KubectlException; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.apis.BatchV1Api; +import io.kubernetes.client.openapi.models.*; +import io.kubernetes.client.util.ClientBuilder; +import io.kubernetes.client.util.ModelMapper; +import java.io.IOException; +import java.util.Objects; + +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; + +public class KubectlDeleteTest { + + private ApiClient apiClient; + + private final byte[] DISCOVERY_API; + + { + try { + DISCOVERY_API = IOUtils.toByteArray(Objects.requireNonNull(KubectlDeleteTest.class + .getClassLoader() + .getResourceAsStream("discovery-api.json"))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private final byte[] DISCOVERY_APIV1; + + { + try { + DISCOVERY_APIV1 = IOUtils.toByteArray(Objects.requireNonNull(KubectlDeleteTest.class + .getClassLoader() + .getResourceAsStream("discovery-api-v1.json"))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + + private final byte[] ADD_JOB; + + { + try { + ADD_JOB = IOUtils.toByteArray(Objects.requireNonNull(KubectlDeleteTest.class + .getClassLoader() + .getResourceAsStream("deleted-add-job.json"))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private final byte[] GET_BATCH; + + { + try { + GET_BATCH = IOUtils.toByteArray(Objects.requireNonNull(KubectlDeleteTest.class + .getClassLoader() + .getResourceAsStream("deleted-get-batch.json"))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private final byte[] DELETED_FIRST; + + { + try { + DELETED_FIRST = IOUtils.toByteArray(Objects.requireNonNull(KubectlDeleteTest.class + .getClassLoader() + .getResourceAsStream("deleted-success.json"))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private final byte[] DELETED_SECOND; + + { + try { + DELETED_SECOND = IOUtils.toByteArray(Objects.requireNonNull(KubectlDeleteTest.class + .getClassLoader() + .getResourceAsStream("deleted-not-found.json"))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private final byte[] DISCOVERY_APIS; + + { + try { + DISCOVERY_APIS = IOUtils.toByteArray(Objects.requireNonNull(KubectlDeleteTest.class + .getClassLoader() + .getResourceAsStream("discovery-apis.json"))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Rule public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort()); + + @Before + public void setup() { + apiClient = new ClientBuilder().setBasePath("http://localhost:" + wireMockRule.port()).build(); + } + + @Test + public void testKubectlDelete() throws KubectlException, IOException, ApiException { + wireMockRule.stubFor( + post(urlPathEqualTo("/apis/batch/v1/namespaces/foo/jobs")) + .willReturn( + aResponse() + .withStatus(201) + .withBody(ADD_JOB))); + wireMockRule.stubFor( + delete(urlPathEqualTo("/apis/batch%2Fv1/batch%2Fv1/namespaces/foo/jobs/bar")) + .inScenario("JobDeletionScenario") + .whenScenarioStateIs(Scenario.STARTED) + .willReturn(aResponse() + .withStatus(200) + .withBody(DELETED_FIRST) + ) + .willSetStateTo("SecondCall") + ); + + wireMockRule.stubFor( + delete(urlPathEqualTo("/apis/batch%2Fv1/batch%2Fv1/namespaces/foo/jobs/bar")) + .inScenario("JobDeletionScenario") + .whenScenarioStateIs("SecondCall") + .willReturn(aResponse() + .withStatus(404) + .withBody(DELETED_SECOND) + ) + ); + + + wireMockRule.stubFor( + get(urlPathEqualTo("/api")) + .willReturn( + aResponse() + .withStatus(200) + .withBody(DISCOVERY_API))); + wireMockRule.stubFor( + get(urlPathEqualTo("/apis")) + .willReturn( + aResponse() + .withStatus(200) + .withBody(DISCOVERY_APIS))); + wireMockRule.stubFor( + get(urlPathEqualTo("/api/v1")) + .willReturn( + aResponse() + .withStatus(200) + .withBody(DISCOVERY_APIV1))); + wireMockRule.stubFor( + get(urlPathEqualTo("/apis/batch/v1/")) + .willReturn( + aResponse() + .withStatus(200) + .withBody(GET_BATCH))); + + V1JobSpec v1JobSpec = new V1JobSpec() + .template(new V1PodTemplateSpec() + .spec(new V1PodSpec() + .containers(java.util.Arrays.asList(new V1Container() + .name("hello") + .image("busybox") + .command(java.util.Arrays.asList("sh", "-c", "echo Hello World!")))) + .restartPolicy("Never"))); + V1Job job = new V1Job() + .apiVersion("batch/v1") + .kind("Job") + .metadata(new V1ObjectMeta().name("bar")) + .spec(v1JobSpec); + BatchV1Api api = new BatchV1Api(); + api.setApiClient(apiClient); + api.createNamespacedJob("foo", job, null, null, null, "Strict"); + ModelMapper.addModelMap(api.getAPIResources().getGroupVersion(), job.getApiVersion(), job.getKind(), "jobs", true, V1Job.class); + + KubectlDelete kubectlDelete = Kubectl.delete(V1Job.class); + kubectlDelete.apiClient(apiClient); + kubectlDelete.namespace("foo").name("bar"); + kubectlDelete.execute(); + + assertThrows(KubectlException.class, () -> { + KubectlDelete kubectlDelete2 = Kubectl.delete(V1Job.class); + kubectlDelete2.apiClient(apiClient); + kubectlDelete2.namespace("foo").name("bar"); + kubectlDelete2.execute(); + }); + + KubectlDelete kubectlDelete2 = Kubectl.delete(V1Job.class); + kubectlDelete2.apiClient(apiClient); + kubectlDelete2.namespace("foo").name("bar").ignoreNotFound(true); + kubectlDelete2.execute(); + } +} diff --git a/extended/src/test/resources/deleted-add-job.json b/extended/src/test/resources/deleted-add-job.json new file mode 100644 index 0000000000..6fbd0cc42c --- /dev/null +++ b/extended/src/test/resources/deleted-add-job.json @@ -0,0 +1,106 @@ +{ + "kind": "Job", + "apiVersion": "batch/v1", + "metadata": { + "name": "bar", + "namespace": "foo", + "uid": "7f64e06e-d6a6-4598-b375-7c8773f3b0e7", + "resourceVersion": "46205", + "generation": 1, + "creationTimestamp": "2023-11-23T15:38:18Z", + "labels": { + "batch.kubernetes.io/controller-uid": "7f64e06e-d6a6-4598-b375-7c8773f3b0e7", + "batch.kubernetes.io/job-name": "bar", + "controller-uid": "7f64e06e-d6a6-4598-b375-7c8773f3b0e7", + "job-name": "bar" + }, + "annotations": { + "batch.kubernetes.io/job-tracking": "" + }, + "managedFields": [ + { + "manager": "Kubernetes Java Client", + "operation": "Update", + "apiVersion": "batch/v1", + "time": "2023-11-23T15:38:18Z", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:spec": { + "f:backoffLimit": {}, + "f:completionMode": {}, + "f:completions": {}, + "f:parallelism": {}, + "f:suspend": {}, + "f:template": { + "f:spec": { + "f:containers": { + "k:{\"name\":\"bar2\"}": { + ".": {}, + "f:command": {}, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:terminationGracePeriodSeconds": {} + } + } + } + } + } + ] + }, + "spec": { + "parallelism": 1, + "completions": 1, + "backoffLimit": 6, + "selector": { + "matchLabels": { + "batch.kubernetes.io/controller-uid": "7f64e06e-d6a6-4598-b375-7c8773f3b0e7" + } + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "batch.kubernetes.io/controller-uid": "7f64e06e-d6a6-4598-b375-7c8773f3b0e7", + "batch.kubernetes.io/job-name": "bar", + "controller-uid": "7f64e06e-d6a6-4598-b375-7c8773f3b0e7", + "job-name": "bar" + } + }, + "spec": { + "containers": [ + { + "name": "bar2", + "image": "busybox", + "command": [ + "sh", + "-c", + "echo Hello World!" + ], + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "imagePullPolicy": "Always" + } + ], + "restartPolicy": "Never", + "terminationGracePeriodSeconds": 30, + "dnsPolicy": "ClusterFirst", + "securityContext": {}, + "schedulerName": "default-scheduler" + } + }, + "completionMode": "NonIndexed", + "suspend": false + }, + "status": {} +} \ No newline at end of file diff --git a/extended/src/test/resources/deleted-get-batch.json b/extended/src/test/resources/deleted-get-batch.json new file mode 100644 index 0000000000..8ad121d77e --- /dev/null +++ b/extended/src/test/resources/deleted-get-batch.json @@ -0,0 +1,72 @@ +{ + "kind": "APIResourceList", + "apiVersion": "v1", + "groupVersion": "batch/v1", + "resources": [ + { + "name": "cronjobs", + "singularName": "cronjob", + "namespaced": true, + "kind": "CronJob", + "verbs": [ + "create", + "delete", + "deletecollection", + "get", + "list", + "patch", + "update", + "watch" + ], + "shortNames": [ + "cj" + ], + "categories": [ + "all" + ], + "storageVersionHash": "sd5LIXh4Fjs=" + }, + { + "name": "cronjobs/status", + "singularName": "", + "namespaced": true, + "kind": "CronJob", + "verbs": [ + "get", + "patch", + "update" + ] + }, + { + "name": "jobs", + "singularName": "job", + "namespaced": true, + "kind": "Job", + "verbs": [ + "create", + "delete", + "deletecollection", + "get", + "list", + "patch", + "update", + "watch" + ], + "categories": [ + "all" + ], + "storageVersionHash": "mudhfqk/qZY=" + }, + { + "name": "jobs/status", + "singularName": "", + "namespaced": true, + "kind": "Job", + "verbs": [ + "get", + "patch", + "update" + ] + } + ] +} \ No newline at end of file diff --git a/extended/src/test/resources/deleted-not-found.json b/extended/src/test/resources/deleted-not-found.json new file mode 100644 index 0000000000..71b7a1c396 --- /dev/null +++ b/extended/src/test/resources/deleted-not-found.json @@ -0,0 +1,14 @@ +{ + "kind": "Status", + "apiVersion": "v1", + "metadata": {}, + "status": "Failure", + "message": "jobs.batch \"bar\" not found", + "reason": "NotFound", + "details": { + "name": "bar", + "group": "batch", + "kind": "jobs" + }, + "code": 404 +} \ No newline at end of file diff --git a/extended/src/test/resources/deleted-success.json b/extended/src/test/resources/deleted-success.json new file mode 100644 index 0000000000..0b2a5eceb1 --- /dev/null +++ b/extended/src/test/resources/deleted-success.json @@ -0,0 +1,143 @@ +{ + "kind": "Job", + "apiVersion": "batch/v1", + "metadata": { + "name": "bar", + "namespace": "foo", + "uid": "b862e993-3828-4108-a38f-c19a602d9af6", + "resourceVersion": "82015", + "generation": 2, + "creationTimestamp": "2023-11-24T06:00:49Z", + "deletionTimestamp": "2023-11-24T06:07:44Z", + "deletionGracePeriodSeconds": 0, + "labels": { + "batch.kubernetes.io/controller-uid": "b862e993-3828-4108-a38f-c19a602d9af6", + "batch.kubernetes.io/job-name": "bar", + "controller-uid": "b862e993-3828-4108-a38f-c19a602d9af6", + "job-name": "bar" + }, + "annotations": { + "batch.kubernetes.io/job-tracking": "" + }, + "finalizers": [ + "orphan" + ], + "managedFields": [ + { + "manager": "Kubernetes Java Client", + "operation": "Update", + "apiVersion": "batch/v1", + "time": "2023-11-24T06:00:49Z", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:spec": { + "f:backoffLimit": {}, + "f:completionMode": {}, + "f:completions": {}, + "f:parallelism": {}, + "f:suspend": {}, + "f:template": { + "f:spec": { + "f:containers": { + "k:{\"name\":\"bar2\"}": { + ".": {}, + "f:command": {}, + "f:image": {}, + "f:imagePullPolicy": {}, + "f:name": {}, + "f:resources": {}, + "f:terminationMessagePath": {}, + "f:terminationMessagePolicy": {} + } + }, + "f:dnsPolicy": {}, + "f:restartPolicy": {}, + "f:schedulerName": {}, + "f:securityContext": {}, + "f:terminationGracePeriodSeconds": {} + } + } + } + } + }, + { + "manager": "kube-controller-manager", + "operation": "Update", + "apiVersion": "batch/v1", + "time": "2023-11-24T06:00:53Z", + "fieldsType": "FieldsV1", + "fieldsV1": { + "f:status": { + "f:completionTime": {}, + "f:conditions": {}, + "f:ready": {}, + "f:startTime": {}, + "f:succeeded": {}, + "f:uncountedTerminatedPods": {} + } + }, + "subresource": "status" + } + ] + }, + "spec": { + "parallelism": 1, + "completions": 1, + "backoffLimit": 6, + "selector": { + "matchLabels": { + "batch.kubernetes.io/controller-uid": "b862e993-3828-4108-a38f-c19a602d9af6" + } + }, + "template": { + "metadata": { + "creationTimestamp": null, + "labels": { + "batch.kubernetes.io/controller-uid": "b862e993-3828-4108-a38f-c19a602d9af6", + "batch.kubernetes.io/job-name": "bar", + "controller-uid": "b862e993-3828-4108-a38f-c19a602d9af6", + "job-name": "bar" + } + }, + "spec": { + "containers": [ + { + "name": "bar2", + "image": "busybox", + "command": [ + "sh", + "-c", + "echo Hello World!" + ], + "resources": {}, + "terminationMessagePath": "/dev/termination-log", + "terminationMessagePolicy": "File", + "imagePullPolicy": "Always" + } + ], + "restartPolicy": "Never", + "terminationGracePeriodSeconds": 30, + "dnsPolicy": "ClusterFirst", + "securityContext": {}, + "schedulerName": "default-scheduler" + } + }, + "completionMode": "NonIndexed", + "suspend": false + }, + "status": { + "conditions": [ + { + "type": "Complete", + "status": "True", + "lastProbeTime": "2023-11-24T06:00:53Z", + "lastTransitionTime": "2023-11-24T06:00:53Z" + } + ], + "startTime": "2023-11-24T06:00:49Z", + "completionTime": "2023-11-24T06:00:53Z", + "succeeded": 1, + "uncountedTerminatedPods": {}, + "ready": 0 + } +} \ No newline at end of file