diff --git a/CHANGELOG.md b/CHANGELOG.md index bd950b48167..bf49c4fc200 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ #### Bugs #### Improvements +* Fix #2781: RawCustomResourceOperationsImpl#delete now returns a boolean value for deletion status #### Dependency Upgrade diff --git a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/RawCustomResourceOperationsImpl.java b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/RawCustomResourceOperationsImpl.java index bcddfbc2b8d..2e6c3ea4424 100644 --- a/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/RawCustomResourceOperationsImpl.java +++ b/kubernetes-client/src/main/java/io/fabric8/kubernetes/client/dsl/internal/RawCustomResourceOperationsImpl.java @@ -30,6 +30,7 @@ import io.fabric8.kubernetes.api.model.DeletionPropagation; import io.fabric8.kubernetes.api.model.ListOptions; import io.fabric8.kubernetes.api.model.ListOptionsBuilder; +import io.fabric8.kubernetes.api.model.Status; import io.fabric8.kubernetes.client.Config; import io.fabric8.kubernetes.client.KubernetesClientException; import io.fabric8.kubernetes.client.Watch; @@ -422,10 +423,10 @@ public Map list(String namespace, Map labels) { * Delete all custom resources in a specific namespace * * @param namespace desired namespace - * @return deleted objects as HashMap + * @return a boolean value whether item was deleted or item didn't exist in server */ - public Map delete(String namespace) { - return makeCall(fetchUrl(namespace, null, null), null, HttpCallMethod.DELETE); + public boolean delete(String namespace) { + return handleDelete(namespace, null, null); } /** @@ -434,11 +435,11 @@ public Map delete(String namespace) { * @param namespace desired namespace * @param cascading whether dependent object need to be orphaned or not. If true/false, the "orphan" * finalizer will be added to/removed from the object's finalizers list. - * @return deleted objects as HashMap + * @return a boolean value whether item was deleted or item didn't exist in server * @throws IOException in case of any network/parsing exception */ - public Map delete(String namespace, boolean cascading) throws IOException { - return makeCall(fetchUrl(namespace, null, null), objectMapper.writeValueAsString(fetchDeleteOptions(cascading, null)), HttpCallMethod.DELETE); + public boolean delete(String namespace, boolean cascading) throws IOException { + return handleDelete(namespace, null, objectMapper.writeValueAsString(fetchDeleteOptions(cascading, null))); } /** @@ -447,11 +448,11 @@ public Map delete(String namespace, boolean cascading) throws IO * @param namespace desired namespace * @param deleteOptions object provided by Kubernetes API for more fine grained control over deletion. * For more information please see https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.16/#deleteoptions-v1-meta - * @return deleted object as HashMap + * @return a boolean value whether item was deleted or item didn't exist in server * @throws IOException in case of any network/object parse problems */ - public Map delete(String namespace, DeleteOptions deleteOptions) throws IOException { - return makeCall(fetchUrl(namespace, null, null), objectMapper.writeValueAsString(deleteOptions), HttpCallMethod.DELETE); + public boolean delete(String namespace, DeleteOptions deleteOptions) throws IOException { + return handleDelete(namespace, null, objectMapper.writeValueAsString(deleteOptions)); } /** @@ -459,11 +460,11 @@ public Map delete(String namespace, DeleteOptions deleteOptions) * * @param namespace desired namespace * @param name custom resource's name - * @return object as HashMap + * @return a boolean value whether item was deleted or item didn't exist in server * @throws IOException in case of any network/object parse problems */ - public Map delete(String namespace, String name) throws IOException { - return makeCall(fetchUrl(namespace, name, null), objectMapper.writeValueAsString(fetchDeleteOptions(false, DeletionPropagation.BACKGROUND.toString())), HttpCallMethod.DELETE); + public boolean delete(String namespace, String name) throws IOException { + return handleDelete(namespace, name, objectMapper.writeValueAsString(fetchDeleteOptions(false, DeletionPropagation.BACKGROUND.toString()))); } /** @@ -473,11 +474,11 @@ public Map delete(String namespace, String name) throws IOExcept * @param name required name of custom resource * @param cascading whether dependent object need to be orphaned or not. If true/false, the "orphan" * finalizer will be added to/removed from the object's finalizers list. - * @return deleted objects as HashMap + * @return a boolean value whether item was deleted or item didn't exist in server * @throws IOException exception related to network/object parsing */ - public Map delete(String namespace, String name, boolean cascading) throws IOException { - return makeCall(fetchUrl(namespace, name, null), objectMapper.writeValueAsString(fetchDeleteOptions(cascading, null)), HttpCallMethod.DELETE); + public boolean delete(String namespace, String name, boolean cascading) throws IOException { + return handleDelete(namespace, name, objectMapper.writeValueAsString(fetchDeleteOptions(cascading, null))); } /** @@ -492,11 +493,11 @@ public Map delete(String namespace, String name, boolean cascadi * 'Orphan' - orphan the dependents; * 'Background' - allow the garbage collector to delete the dependents in the background; * 'Foreground' - a cascading policy that deletes all dependents in the foreground. - * @return deleted object as HashMap + * @return a boolean value whether item was deleted or item didn't exist in server * @throws IOException in case of network/object parse exception */ - public Map delete(String namespace, String name, String propagationPolicy) throws IOException { - return makeCall(fetchUrl(namespace, name, null), objectMapper.writeValueAsString(fetchDeleteOptions(false, propagationPolicy)) , HttpCallMethod.DELETE); + public boolean delete(String namespace, String name, String propagationPolicy) throws IOException { + return handleDelete(namespace, name, objectMapper.writeValueAsString(fetchDeleteOptions(false, propagationPolicy))); } /** @@ -506,11 +507,11 @@ public Map delete(String namespace, String name, String propagat * @param name name of custom resource * @param deleteOptions object provided by Kubernetes API for more fine grained control over deletion. * For more information please see https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.16/#deleteoptions-v1-meta - * @return deleted object as HashMap + * @return a boolean value whether item was deleted or item didn't exist in server * @throws IOException in case of any network/object parse exception */ - public Map delete(String namespace, String name, DeleteOptions deleteOptions) throws IOException { - return makeCall(fetchUrl(namespace, name, null), objectMapper.writeValueAsString(deleteOptions), HttpCallMethod.DELETE); + public boolean delete(String namespace, String name, DeleteOptions deleteOptions) throws IOException { + return handleDelete(namespace, name, objectMapper.writeValueAsString(deleteOptions)); } /** @@ -760,6 +761,10 @@ private String getLabelsQueryParam(Map labels) { } private Map makeCall(String url, String body, HttpCallMethod callMethod) { + return makeCall(url, body, callMethod, true); + } + + private Map makeCall(String url, String body, HttpCallMethod callMethod, boolean shouldRequestFailure) { Request request = (body == null) ? getRequest(url, callMethod) : getRequest(url, body, callMethod); try (Response response = client.newCall(request).execute()) { if (response.isSuccessful()) { @@ -769,13 +774,31 @@ private Map makeCall(String url, String body, HttpCallMethod cal else return objectMapper.readValue(respBody, HashMap.class); } else { - throw requestFailure(request, createStatus(response)); + return handleFailure(request, response, shouldRequestFailure); } } catch(Exception e) { throw KubernetesClientException.launderThrowable(e); } } + private Map handleFailure(Request request, Response response, boolean shouldRequestFailure) throws IOException { + if (shouldRequestFailure) { + throw requestFailure(request, createStatus(response)); + } + return objectMapper.readValue(response.body().string(), HashMap.class); + } + + private boolean handleDelete(String namespace, String name, String requestBody) { + Map response = makeCall(fetchUrl(namespace, name, null), requestBody, HttpCallMethod.DELETE, false); + + // In most cases Status object is sent on deletion, but when deprecated DeleteOptions.orphanDependents + // is used; object which is being deleted is sent + if (response.get("kind").toString().equals("Status")) { + return response.get("status").toString().equals("Success"); + } + return true; + } + private Map validateAndSubmitRequest(String namespace, String name, String objectAsString, HttpCallMethod httpCallMethod) throws IOException { return validateAndSubmitRequest(fetchUrl(namespace, name, null), objectAsString, httpCallMethod); } diff --git a/kubernetes-client/src/test/java/io/fabric8/kubernetes/client/dsl/internal/RawCustomResourceOperationsImplTest.java b/kubernetes-client/src/test/java/io/fabric8/kubernetes/client/dsl/internal/RawCustomResourceOperationsImplTest.java index 06cca79f928..6130a2103ac 100644 --- a/kubernetes-client/src/test/java/io/fabric8/kubernetes/client/dsl/internal/RawCustomResourceOperationsImplTest.java +++ b/kubernetes-client/src/test/java/io/fabric8/kubernetes/client/dsl/internal/RawCustomResourceOperationsImplTest.java @@ -16,6 +16,8 @@ package io.fabric8.kubernetes.client.dsl.internal; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.Mockito.*; @@ -25,6 +27,8 @@ import java.util.HashMap; import java.util.Map; +import io.fabric8.kubernetes.api.model.DeleteOptionsBuilder; +import io.fabric8.kubernetes.api.model.DeletionPropagation; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -109,14 +113,132 @@ void testCreateOrReplaceUrl() throws IOException { assertEquals("POST", captor.getAllValues().get(3).method()); } - private Response mockResponse(int code) { - return new Response.Builder() - .request(new Request.Builder().url("http://mock").build()) - .protocol(Protocol.HTTP_1_1) - .code(code) - .body(ResponseBody.create(MediaType.get("application/json"), "")) - .message("mock") - .build(); + @Test + void testDeleteWithNamespaceAndNameForNonExistentResource() throws IOException { + // Given + RawCustomResourceOperationsImpl rawCustomResourceOperations = new RawCustomResourceOperationsImpl(mockClient, config, customResourceDefinitionContext); + mockDeletionCallWithResponse(HttpURLConnection.HTTP_NOT_FOUND, "{\"kind\":\"Status\",\"status\":\"Failure\"}"); + ArgumentCaptor captor = ArgumentCaptor.forClass(Request.class); + + // When + boolean result = rawCustomResourceOperations.delete("ns1", "foo"); + + // Then + assertFalse(result); + verify(mockClient).newCall(captor.capture()); + assertEquals("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos/foo", captor.getValue().url().encodedPath()); + } + + @Test + void testDeleteWithNamespaceForNonExistentResource() throws IOException { + // Given + RawCustomResourceOperationsImpl rawCustomResourceOperations = new RawCustomResourceOperationsImpl(mockClient, config, customResourceDefinitionContext); + mockDeletionCallWithResponse(HttpURLConnection.HTTP_NOT_FOUND, "{\"kind\":\"Status\",\"status\":\"Failure\"}"); + ArgumentCaptor captor = ArgumentCaptor.forClass(Request.class); + + // When + boolean result = rawCustomResourceOperations.delete("ns1"); + + // Then + assertFalse(result); + verify(mockClient).newCall(captor.capture()); + assertEquals("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos", captor.getValue().url().encodedPath()); + } + + @Test + void testDeleteWithNamespaceAndCascadingForNonExistentResource() throws IOException { + // Given + RawCustomResourceOperationsImpl rawCustomResourceOperations = new RawCustomResourceOperationsImpl(mockClient, config, customResourceDefinitionContext); + mockDeletionCallWithResponse(HttpURLConnection.HTTP_NOT_FOUND, "{\"kind\":\"Status\",\"status\":\"Failure\"}"); + ArgumentCaptor captor = ArgumentCaptor.forClass(Request.class); + + // When + boolean result = rawCustomResourceOperations.delete("ns1", true); + + // Then + assertFalse(result); + verify(mockClient).newCall(captor.capture()); + assertEquals("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos", captor.getValue().url().encodedPath()); + } + + @Test + void testDeleteWithNamespaceAndDeleteOptionsForNonExistentResource() throws IOException { + // Given + RawCustomResourceOperationsImpl rawCustomResourceOperations = new RawCustomResourceOperationsImpl(mockClient, config, customResourceDefinitionContext); + mockDeletionCallWithResponse(HttpURLConnection.HTTP_NOT_FOUND, "{\"kind\":\"Status\",\"status\":\"Failure\"}"); + ArgumentCaptor captor = ArgumentCaptor.forClass(Request.class); + + // When + boolean result = rawCustomResourceOperations.delete("ns1", new DeleteOptionsBuilder().withOrphanDependents(true).build()); + + // Then + assertFalse(result); + verify(mockClient).newCall(captor.capture()); + assertEquals("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos", captor.getValue().url().encodedPath()); + } + + @Test + void testDeleteWithNamespaceNameAndCascading() throws IOException { + // Given + RawCustomResourceOperationsImpl rawCustomResourceOperations = new RawCustomResourceOperationsImpl(mockClient, config, customResourceDefinitionContext); + mockDeletionCallWithResponse(HttpURLConnection.HTTP_OK, "{\"kind\":\"Hello\",\"metadata\":{\"name\":\"Failure\"}}"); + ArgumentCaptor captor = ArgumentCaptor.forClass(Request.class); + + // When + boolean result = rawCustomResourceOperations.delete("ns1", "foo", true); + + // Then + assertTrue(result); + verify(mockClient).newCall(captor.capture()); + assertEquals("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos/foo", captor.getValue().url().encodedPath()); + } + + @Test + void testDeleteWithNamespaceNameAndCascadingForNonExistentResource() throws IOException { + // Given + RawCustomResourceOperationsImpl rawCustomResourceOperations = new RawCustomResourceOperationsImpl(mockClient, config, customResourceDefinitionContext); + mockDeletionCallWithResponse(HttpURLConnection.HTTP_NOT_FOUND, "{\"kind\":\"Status\",\"status\":\"Failure\"}"); + ArgumentCaptor captor = ArgumentCaptor.forClass(Request.class); + + // When + boolean result = rawCustomResourceOperations.delete("ns1", "foo", true); + + // Then + assertFalse(result); + verify(mockClient).newCall(captor.capture()); + assertEquals("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos/foo", captor.getValue().url().encodedPath()); + } + + @Test + void testDeleteWithNamespaceNameAndPropagationPolicyForNonExistentResource() throws IOException { + // Given + RawCustomResourceOperationsImpl rawCustomResourceOperations = new RawCustomResourceOperationsImpl(mockClient, config, customResourceDefinitionContext); + mockDeletionCallWithResponse(HttpURLConnection.HTTP_NOT_FOUND, "{\"kind\":\"Status\",\"status\":\"Failure\"}"); + ArgumentCaptor captor = ArgumentCaptor.forClass(Request.class); + + // When + boolean result = rawCustomResourceOperations.delete("ns1", "foo", DeletionPropagation.BACKGROUND.toString()); + + // Then + assertFalse(result); + verify(mockClient).newCall(captor.capture()); + assertEquals("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos/foo", captor.getValue().url().encodedPath()); + } + + @Test + void testDeleteWithNamespaceNameAndDeleteOptionsForNonExistentResource() throws IOException { + // Given + RawCustomResourceOperationsImpl rawCustomResourceOperations = new RawCustomResourceOperationsImpl(mockClient, config, customResourceDefinitionContext); + mockDeletionCallWithResponse(HttpURLConnection.HTTP_NOT_FOUND, "{\"kind\":\"Status\",\"status\":\"Failure\"}"); + ArgumentCaptor captor = ArgumentCaptor.forClass(Request.class); + + // When + boolean result = rawCustomResourceOperations.delete("ns1", "foo", new DeleteOptionsBuilder().withPropagationPolicy(DeletionPropagation.FOREGROUND.toString()).build()); + + // Then + assertFalse(result); + verify(mockClient).newCall(captor.capture()); + assertEquals("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos/foo", captor.getValue().url().encodedPath()); } @Test @@ -138,11 +260,13 @@ void testDeleteUrl() throws IOException { // Given RawCustomResourceOperationsImpl rawCustomResourceOperations = new RawCustomResourceOperationsImpl(mockClient, config, customResourceDefinitionContext); ArgumentCaptor captor = ArgumentCaptor.forClass(Request.class); + mockDeletionCallWithResponse(HttpURLConnection.HTTP_OK, "{\"kind\":\"Status\",\"status\":\"Success\"}"); // When - rawCustomResourceOperations.delete("myns", "myresource"); + boolean result = rawCustomResourceOperations.delete("myns", "myresource"); // Then + assertTrue(result); verify(mockClient).newCall(captor.capture()); assertEquals("/apis/test.fabric8.io/v1alpha1/namespaces/myns/hellos/myresource", captor.getValue().url().encodedPath()); } @@ -252,4 +376,27 @@ void testGetConfigShouldNotReturnNull() { assertThat(configFromRawOp.getWatchReconnectInterval()).isEqualTo(10); assertThat(configFromRawOp.getWatchReconnectLimit()).isEqualTo(1); } + + private void mockDeletionCallWithResponse(int code, String status) throws IOException { + Call mockCall = mock(Call.class); + Response mockNotFoundResponse = mockResponse(code, status); + when(mockCall.execute()) + .thenReturn(mockNotFoundResponse); + when(mockClient.newCall(any())).thenReturn(mockCall); + } + + private Response mockResponse(int code) { + return mockResponse(code, ""); + } + + private Response mockResponse(int code, String body) { + return new Response.Builder() + .request(new Request.Builder().url("http://mock").build()) + .protocol(Protocol.HTTP_1_1) + .code(code) + .body(ResponseBody.create(MediaType.get("application/json"), body)) + .message("mock") + .build(); + } + } diff --git a/kubernetes-itests/src/test/java/io/fabric8/kubernetes/RawCustomResourceIT.java b/kubernetes-itests/src/test/java/io/fabric8/kubernetes/RawCustomResourceIT.java index f1afc60b8fe..79b4815dea7 100644 --- a/kubernetes-itests/src/test/java/io/fabric8/kubernetes/RawCustomResourceIT.java +++ b/kubernetes-itests/src/test/java/io/fabric8/kubernetes/RawCustomResourceIT.java @@ -146,8 +146,8 @@ public void testCrudUsingMap() throws IOException { assertAnimal(bison, "bison", "bison-bonasus"); // Delete - Map deletionStatusMap = client.customResource(customResourceDefinitionContext).delete(currentNamespace, name); - assertDeletionStatus(deletionStatusMap, "Success"); + boolean isDeleted = client.customResource(customResourceDefinitionContext).delete(currentNamespace, name); + assertTrue(isDeleted); } @Test @@ -163,8 +163,8 @@ public void testCreateReadDeleteUsingInputStream() throws IOException { assertAnimal(hippoCr, name, "Hippopotamidae"); // Delete - Map deletionStatusMap = client.customResource(customResourceDefinitionContext).delete(currentNamespace, name); - assertDeletionStatus(deletionStatusMap, "Success"); + boolean isDeleted = client.customResource(customResourceDefinitionContext).delete(currentNamespace, name); + assertTrue(isDeleted); } @Test @@ -189,8 +189,8 @@ public void testCrudUsingString() throws IOException { assertAnimal(rhinoCr, name, "rhinoceros"); // Delete - Map deletionStatusMap = client.customResource(customResourceDefinitionContext).delete(currentNamespace, name); - assertDeletionStatus(deletionStatusMap, "Success"); + boolean isDeleted = client.customResource(customResourceDefinitionContext).delete(currentNamespace, name); + assertTrue(isDeleted); } @Test @@ -238,6 +238,12 @@ public void testUpdateStatus() throws IOException { assertEquals("endangered", ((Map)deer.get("status")).get("conservationStatus").toString()); } + @Test + public void testDeleteNonExistingResource() throws IOException { + boolean isDeleted = client.customResource(customResourceDefinitionContext).delete(currentNamespace, "idontexist"); + assertFalse(isDeleted); + } + @AfterClass public static void cleanup() { ClusterEntity.remove(RawCustomResourceIT.class.getResourceAsStream("/test-rawcustomresource-definition.yml")); @@ -250,12 +256,6 @@ private void assertAnimal(Map animal, String name, String image) assertThat(((HashMap)animal.get("spec")).get("image")).isEqualTo(image); } - private void assertDeletionStatus(Map deletionStatusAsMap, String status) { - Status deletionStatus = Serialization.jsonMapper().convertValue(deletionStatusAsMap, Status.class); - assertNotNull(deletionStatus); - assertEquals(status, deletionStatus.getStatus()); - } - private Map createNewAnimal(String name, String image) { Map crAsMap = new HashMap<>(); crAsMap.put("apiVersion", "jungle.example.com/v1"); diff --git a/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/CustomResourceTest.java b/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/CustomResourceTest.java index 6cb5a5b00d8..9db8130631b 100644 --- a/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/CustomResourceTest.java +++ b/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/CustomResourceTest.java @@ -19,6 +19,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.jupiter.api.Assertions.assertFalse; import java.io.IOException; import java.net.HttpURLConnection; @@ -32,11 +33,13 @@ import io.fabric8.kubernetes.api.model.DeleteOptions; import io.fabric8.kubernetes.api.model.ListOptions; import io.fabric8.kubernetes.api.model.ListOptionsBuilder; +import io.fabric8.kubernetes.api.model.Status; import io.fabric8.kubernetes.api.model.StatusBuilder; import io.fabric8.kubernetes.client.Watch; import io.fabric8.kubernetes.client.Watcher; import io.fabric8.kubernetes.api.model.WatchEvent; import io.fabric8.kubernetes.client.WatcherException; +import io.fabric8.kubernetes.client.utils.Serialization; import okhttp3.mockwebserver.RecordedRequest; import org.junit.Rule; import org.junit.jupiter.api.Assertions; @@ -60,7 +63,7 @@ class CustomResourceTest { @Rule public KubernetesServer server = new KubernetesServer(); - private CustomResourceDefinitionContext customResourceDefinitionContext = new CustomResourceDefinitionContext.Builder() + private final CustomResourceDefinitionContext customResourceDefinitionContext = new CustomResourceDefinitionContext.Builder() .withGroup("test.fabric8.io") .withName("hellos.test.fabric8.io") .withPlural("hellos") @@ -198,21 +201,49 @@ void testEdit() throws IOException { @Test void testDelete() throws IOException { + // Given server.expect().delete().withPath("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos/example-hello").andReturn(HttpURLConnection.HTTP_OK, "{\"metadata\":{},\"apiVersion\":\"v1\",\"kind\":\"Status\",\"details\":{\"name\":\"prometheus-example-rules\",\"group\":\"monitoring.coreos.com\",\"kind\":\"prometheusrules\",\"uid\":\"b3d085bd-6a5c-11e9-8787-525400b18c1d\"},\"status\":\"Success\"}").once(); + KubernetesClient client = server.getClient(); + // When + boolean result = client.customResource(customResourceDefinitionContext).delete("ns1", "example-hello"); + + // Then + assertTrue(result); + } + + @Test + void testDeleteNonExistentItem() throws IOException { + // Given + Status notFoundStatus = new StatusBuilder() + .withStatus("Failure") + .withMessage("\"idontexist\" not found") + .withReason("NotFound") + .withNewDetails().withKind("pods").withName("idontexist").endDetails() + .withCode(HttpURLConnection.HTTP_NOT_FOUND) + .build(); + server.expect().delete().withPath("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos/idontexist").andReturn(HttpURLConnection.HTTP_NOT_FOUND, Serialization.jsonMapper().writeValueAsString(notFoundStatus)).once(); KubernetesClient client = server.getClient(); - Map result = client.customResource(customResourceDefinitionContext).delete("ns1", "example-hello"); - assertEquals("Success", result.get("status")); + + // When + boolean isDeleted = client.customResource(customResourceDefinitionContext).delete("ns1", "idontexist"); + + // Then + assertFalse(isDeleted); } @Test void testCascadingDeletion() throws IOException, InterruptedException { + // Given server.expect().delete().withPath("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos/example-hello").andReturn(HttpURLConnection.HTTP_OK, "{\"metadata\":{},\"apiVersion\":\"v1\",\"kind\":\"Status\",\"details\":{\"name\":\"prometheus-example-rules\",\"group\":\"monitoring.coreos.com\",\"kind\":\"prometheusrules\",\"uid\":\"b3d085bd-6a5c-11e9-8787-525400b18c1d\"},\"status\":\"Success\"}").once(); - KubernetesClient client = server.getClient(); - Map result = client.customResource(customResourceDefinitionContext) + + // When + boolean result = client.customResource(customResourceDefinitionContext) .delete("ns1", "example-hello", true); - assertEquals("Success", result.get("status")); + + // Then + assertTrue(result); RecordedRequest request = server.getLastRequest(); assertEquals("DELETE", request.getMethod()); @@ -222,13 +253,17 @@ void testCascadingDeletion() throws IOException, InterruptedException { @Test void testPropagationPolicy() throws IOException, InterruptedException { + // Given server.expect().delete().withPath("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos/example-hello").andReturn(HttpURLConnection.HTTP_OK, "{\"metadata\":{},\"apiVersion\":\"v1\",\"kind\":\"Status\",\"details\":{\"name\":\"prometheus-example-rules\",\"group\":\"monitoring.coreos.com\",\"kind\":\"prometheusrules\",\"uid\":\"b3d085bd-6a5c-11e9-8787-525400b18c1d\"},\"status\":\"Success\"}").once(); KubernetesClient client = server.getClient(); - Map result = client.customResource(customResourceDefinitionContext) + + // When + boolean result = client.customResource(customResourceDefinitionContext) .delete("ns1", "example-hello", "Orphan"); - assertEquals("Success", result.get("status")); + // Then + assertTrue(result); RecordedRequest request = server.getLastRequest(); assertEquals("DELETE", request.getMethod()); assertEquals("{\"apiVersion\":\"v1\",\"kind\":\"DeleteOptions\",\"propagationPolicy\":\"Orphan\"}", @@ -237,18 +272,20 @@ void testPropagationPolicy() throws IOException, InterruptedException { @Test void testDeleteOptions() throws InterruptedException, IOException { + // Given server.expect().delete().withPath("/apis/test.fabric8.io/v1alpha1/namespaces/ns1/hellos/example-hello").andReturn(HttpURLConnection.HTTP_OK, "{\"metadata\":{},\"apiVersion\":\"v1\",\"kind\":\"Status\",\"details\":{\"name\":\"prometheus-example-rules\",\"group\":\"monitoring.coreos.com\",\"kind\":\"prometheusrules\",\"uid\":\"b3d085bd-6a5c-11e9-8787-525400b18c1d\"},\"status\":\"Success\"}").once(); - KubernetesClient client = server.getClient(); DeleteOptions deleteOptions = new DeleteOptions(); deleteOptions.setGracePeriodSeconds(0L); deleteOptions.setPropagationPolicy("Orphan"); - Map result = client.customResource(customResourceDefinitionContext) - .delete("ns1", "example-hello", deleteOptions); - assertEquals("Success", result.get("status")); + // When + boolean result = client.customResource(customResourceDefinitionContext) + .delete("ns1", "example-hello", deleteOptions); + // Then + assertTrue(result); RecordedRequest request = server.getLastRequest(); assertEquals("DELETE", request.getMethod()); assertEquals("{\"apiVersion\":\"v1\",\"kind\":\"DeleteOptions\",\"gracePeriodSeconds\":0,\"propagationPolicy\":\"Orphan\"}", diff --git a/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/PropagationPolicyTest.java b/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/PropagationPolicyTest.java index 1437b4b6705..22c7bb5ca32 100644 --- a/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/PropagationPolicyTest.java +++ b/kubernetes-tests/src/test/java/io/fabric8/kubernetes/client/mock/PropagationPolicyTest.java @@ -391,7 +391,7 @@ void testDeleteRawCustomResource() throws InterruptedException, IOException { KubernetesClient client = server.getClient(); // When - Map result = client.customResource(new CustomResourceDefinitionContext.Builder() + boolean result = client.customResource(new CustomResourceDefinitionContext.Builder() .withGroup("test.fabric8.io") .withName("hellos.test.fabric8.io") .withPlural("hellos") @@ -401,6 +401,7 @@ void testDeleteRawCustomResource() throws InterruptedException, IOException { .delete("ns1", "example-hello"); // Then + assertTrue(result); assertDeleteOptionsInLastRecordedRequest(DeletionPropagation.BACKGROUND.toString(), server.getLastRequest()); }