From e94a24e599547b156b8a45a320ef6ce63d3bddad Mon Sep 17 00:00:00 2001 From: E046899 Date: Tue, 17 Sep 2024 17:50:20 +0200 Subject: [PATCH 1/6] add namespace wildcard deletion --- .../controller/NamespaceController.java | 72 ++++++++++++++++--- .../controller/NamespaceControllerTest.java | 70 ++++++++++++++++++ 2 files changed, 134 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java b/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java index 1ee8be86..fe375754 100644 --- a/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java +++ b/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java @@ -24,9 +24,13 @@ import jakarta.validation.Valid; import java.time.Instant; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.stream.Collectors; /** * Controller to manage the namespaces. @@ -141,17 +145,69 @@ public HttpResponse delete(String namespace, @QueryValue(defaultValue = "f return HttpResponse.noContent(); } - var namespaceToDelete = optionalNamespace.get(); + performDeletion(optionalNamespace.get()); + return HttpResponse.noContent(); + } - sendEventLog( - namespaceToDelete, - ApplyStatus.deleted, - namespaceToDelete.getSpec(), - null, - EMPTY_STRING + /** + * Delete namespaces. + * + * @param dryrun Is dry run mode or not ? + * @param name The name parameter + * @return An HTTP response + */ + @Delete + public HttpResponse bulkDelete(@QueryValue(defaultValue = "*") String name, + @QueryValue(defaultValue = "false") boolean dryrun) { + List namespaces = namespaceService.findByWildcardName(name); + if (namespaces.isEmpty()) { + return HttpResponse.notFound(); + } + + Map> validationErrors = new HashMap<>(); + + namespaces.forEach(namespace -> + validationErrors.put( + namespace.getMetadata().getName(), + namespaceService.findAllResourcesByNamespace(namespace) + .stream().filter(r -> !r.isEmpty()) + .map(FormatErrorUtils::invalidNamespaceDeleteOperation) + .toList()) ); - namespaceService.delete(optionalNamespace.get()); + if (validationErrors.values().stream() + .anyMatch(list -> !list.isEmpty())) { + throw new ResourceValidationException( + NAMESPACE, + validationErrors.keySet().stream() + .map(Object::toString) + .collect(Collectors.joining("/")), + validationErrors.values().stream() + .flatMap(Collection::stream) + .collect(Collectors.toList())); + } + + if (dryrun) { + return HttpResponse.noContent(); + } + + namespaces.forEach(this::performDeletion); return HttpResponse.noContent(); } + + /** + * Perform the deletion of the namespace and send an event log. + * + * @param namespace The namespace to delete + */ + private void performDeletion(Namespace namespace) { + sendEventLog( + namespace, + ApplyStatus.deleted, + namespace.getSpec(), + null, + EMPTY_STRING + ); + namespaceService.delete(namespace); + } } diff --git a/src/test/java/com/michelin/ns4kafka/controller/NamespaceControllerTest.java b/src/test/java/com/michelin/ns4kafka/controller/NamespaceControllerTest.java index d992c1e7..f50b2daf 100644 --- a/src/test/java/com/michelin/ns4kafka/controller/NamespaceControllerTest.java +++ b/src/test/java/com/michelin/ns4kafka/controller/NamespaceControllerTest.java @@ -371,4 +371,74 @@ void shouldNotDeleteNamespaceWhenResourcesAreStillLinkedWithIt() { () -> namespaceController.delete("namespace", false)); verify(namespaceService, never()).delete(any()); } + + @Test + void shouldDeleteNamespaces() { + Namespace existing1 = Namespace.builder() + .metadata(Metadata.builder() + .name("namespace1") + .cluster("local") + .build()) + .spec(Namespace.NamespaceSpec.builder() + .kafkaUser("user") + .build()) + .build(); + Namespace existing2 = Namespace.builder() + .metadata(Metadata.builder() + .name("namespace2") + .cluster("local") + .build()) + .spec(Namespace.NamespaceSpec.builder() + .kafkaUser("user") + .build()) + .build(); + + when(namespaceService.findByWildcardName("namespace*")) + .thenReturn(List.of(existing1, existing2)); + when(namespaceService.findAllResourcesByNamespace(existing1)) + .thenReturn(List.of()); + when(namespaceService.findAllResourcesByNamespace(existing2)) + .thenReturn(List.of()); + when(securityService.username()) + .thenReturn(Optional.of("test-user")); + when(securityService.hasRole(ResourceBasedSecurityRule.IS_ADMIN)) + .thenReturn(false); + + doNothing().when(applicationEventPublisher).publishEvent(any()); + var result = namespaceController.bulkDelete("namespace*", false); + assertEquals(HttpResponse.noContent().getStatus(), result.getStatus()); + } + + @Test + void shouldNotDeleteNamespacesWhenResourcesAreStillLinkedWithIt() { + Namespace existing1 = Namespace.builder() + .metadata(Metadata.builder() + .name("namespace1") + .cluster("local") + .build()) + .spec(Namespace.NamespaceSpec.builder() + .kafkaUser("user") + .build()) + .build(); + Namespace existing2 = Namespace.builder() + .metadata(Metadata.builder() + .name("namespace2") + .cluster("local") + .build()) + .spec(Namespace.NamespaceSpec.builder() + .kafkaUser("user") + .build()) + .build(); + + when(namespaceService.findByWildcardName("namespace*")) + .thenReturn(List.of(existing1, existing2)); + when(namespaceService.findAllResourcesByNamespace(existing1)) + .thenReturn(List.of("Topic/topic1")); + when(namespaceService.findAllResourcesByNamespace(existing2)) + .thenReturn(List.of()); + + assertThrows(ResourceValidationException.class, + () -> namespaceController.bulkDelete("namespace*", false)); + verify(namespaceService, never()).delete(any()); + } } From 7a76ba28d668a1dd4e9789fa4531b7bf1a6fddee Mon Sep 17 00:00:00 2001 From: E046899 Date: Tue, 17 Sep 2024 17:59:58 +0200 Subject: [PATCH 2/6] add namespace wildcard deletion --- .../com/michelin/ns4kafka/controller/NamespaceController.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java b/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java index fe375754..6ecf61a0 100644 --- a/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java +++ b/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java @@ -184,7 +184,7 @@ public HttpResponse bulkDelete(@QueryValue(defaultValue = "*") String name .collect(Collectors.joining("/")), validationErrors.values().stream() .flatMap(Collection::stream) - .collect(Collectors.toList())); + .toList()); } if (dryrun) { From 753932597201166af5c6907d44952c1755ebebd4 Mon Sep 17 00:00:00 2001 From: E046899 Date: Wed, 18 Sep 2024 10:42:38 +0200 Subject: [PATCH 3/6] add namespace wildcard deletion --- .../controller/NamespaceController.java | 3 +- .../controller/NamespaceControllerTest.java | 43 +++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java b/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java index 6ecf61a0..5de2378b 100644 --- a/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java +++ b/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java @@ -126,6 +126,7 @@ public HttpResponse apply(@Valid @Body Namespace namespace, * @return An HTTP response */ @Delete("/{namespace}{?dryrun}") + @Deprecated(since = "1.13.0") public HttpResponse delete(String namespace, @QueryValue(defaultValue = "false") boolean dryrun) { Optional optionalNamespace = namespaceService.findByName(namespace); if (optionalNamespace.isEmpty()) { @@ -170,7 +171,7 @@ public HttpResponse bulkDelete(@QueryValue(defaultValue = "*") String name validationErrors.put( namespace.getMetadata().getName(), namespaceService.findAllResourcesByNamespace(namespace) - .stream().filter(r -> !r.isEmpty()) + .stream() .map(FormatErrorUtils::invalidNamespaceDeleteOperation) .toList()) ); diff --git a/src/test/java/com/michelin/ns4kafka/controller/NamespaceControllerTest.java b/src/test/java/com/michelin/ns4kafka/controller/NamespaceControllerTest.java index f50b2daf..ac505078 100644 --- a/src/test/java/com/michelin/ns4kafka/controller/NamespaceControllerTest.java +++ b/src/test/java/com/michelin/ns4kafka/controller/NamespaceControllerTest.java @@ -291,6 +291,7 @@ void shouldUpdateNamespaceInDryRunMode() { } @Test + @SuppressWarnings("deprecation") void shouldDeleteNamespace() { Namespace existing = Namespace.builder() .metadata(Metadata.builder() @@ -317,6 +318,7 @@ void shouldDeleteNamespace() { } @Test + @SuppressWarnings("deprecation") void shouldDeleteNamespaceInDryRunMode() { Namespace existing = Namespace.builder() .metadata(Metadata.builder() @@ -340,6 +342,7 @@ void shouldDeleteNamespaceInDryRunMode() { } @Test + @SuppressWarnings("deprecation") void shouldNotDeleteNamespaceWhenNotFound() { when(namespaceService.findByName("namespace")) .thenReturn(Optional.empty()); @@ -351,6 +354,7 @@ void shouldNotDeleteNamespaceWhenNotFound() { } @Test + @SuppressWarnings("deprecation") void shouldNotDeleteNamespaceWhenResourcesAreStillLinkedWithIt() { Namespace existing = Namespace.builder() .metadata(Metadata.builder() @@ -409,6 +413,39 @@ void shouldDeleteNamespaces() { assertEquals(HttpResponse.noContent().getStatus(), result.getStatus()); } + @Test + void shouldDeleteNamespacesInDryRunMode() { + Namespace existing1 = Namespace.builder() + .metadata(Metadata.builder() + .name("namespace1") + .cluster("local") + .build()) + .spec(Namespace.NamespaceSpec.builder() + .kafkaUser("user") + .build()) + .build(); + Namespace existing2 = Namespace.builder() + .metadata(Metadata.builder() + .name("namespace2") + .cluster("local") + .build()) + .spec(Namespace.NamespaceSpec.builder() + .kafkaUser("user") + .build()) + .build(); + + when(namespaceService.findByWildcardName("namespace*")) + .thenReturn(List.of(existing1, existing2)); + when(namespaceService.findAllResourcesByNamespace(existing1)) + .thenReturn(List.of()); + when(namespaceService.findAllResourcesByNamespace(existing2)) + .thenReturn(List.of()); + + var result = namespaceController.bulkDelete("namespace*", true); + verify(namespaceService, never()).delete(any()); + assertEquals(HttpResponse.noContent().getStatus(), result.getStatus()); + } + @Test void shouldNotDeleteNamespacesWhenResourcesAreStillLinkedWithIt() { Namespace existing1 = Namespace.builder() @@ -441,4 +478,10 @@ void shouldNotDeleteNamespacesWhenResourcesAreStillLinkedWithIt() { () -> namespaceController.bulkDelete("namespace*", false)); verify(namespaceService, never()).delete(any()); } + + @Test + void shouldNotDeleteNamespacesWhenPatternMatchesNothing() { + var result = namespaceController.bulkDelete("namespace*", false); + assertEquals(HttpResponse.notFound().getStatus(), result.getStatus()); + } } From f5e483cd258e8df9945d3b2705383d1a4d8f20ee Mon Sep 17 00:00:00 2001 From: E046899 Date: Wed, 18 Sep 2024 11:01:18 +0200 Subject: [PATCH 4/6] add namespace wildcard deletion --- .../com/michelin/ns4kafka/controller/NamespaceController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java b/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java index 5de2378b..2ed41fc1 100644 --- a/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java +++ b/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java @@ -124,6 +124,7 @@ public HttpResponse apply(@Valid @Body Namespace namespace, * @param namespace The namespace * @param dryrun Is dry run mode or not ? * @return An HTTP response + * @deprecated use bulkDelete instead. */ @Delete("/{namespace}{?dryrun}") @Deprecated(since = "1.13.0") From af7f062020b9f149e09fd30f8c22427af1ac1134 Mon Sep 17 00:00:00 2001 From: E046899 Date: Tue, 24 Sep 2024 11:00:32 +0200 Subject: [PATCH 5/6] reformat code --- .../controller/NamespaceController.java | 32 ++++++++----------- .../controller/NamespaceControllerTest.java | 1 + 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java b/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java index 2ed41fc1..f95c7b2a 100644 --- a/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java +++ b/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java @@ -166,27 +166,23 @@ public HttpResponse bulkDelete(@QueryValue(defaultValue = "*") String name return HttpResponse.notFound(); } - Map> validationErrors = new HashMap<>(); - - namespaces.forEach(namespace -> - validationErrors.put( - namespace.getMetadata().getName(), - namespaceService.findAllResourcesByNamespace(namespace) - .stream() - .map(FormatErrorUtils::invalidNamespaceDeleteOperation) - .toList()) - ); + List namespaceResources = namespaces + .stream() + .flatMap(namespace -> namespaceService.findAllResourcesByNamespace(namespace) + .stream()) + .toList(); + + if (!namespaceResources.isEmpty()) { + List validationErrors = namespaceResources + .stream() + .map(FormatErrorUtils::invalidNamespaceDeleteOperation) + .toList(); - if (validationErrors.values().stream() - .anyMatch(list -> !list.isEmpty())) { throw new ResourceValidationException( NAMESPACE, - validationErrors.keySet().stream() - .map(Object::toString) - .collect(Collectors.joining("/")), - validationErrors.values().stream() - .flatMap(Collection::stream) - .toList()); + String.join(",", namespaces.stream().map(namespace -> namespace.getMetadata().getName()).toList()), + validationErrors + ); } if (dryrun) { diff --git a/src/test/java/com/michelin/ns4kafka/controller/NamespaceControllerTest.java b/src/test/java/com/michelin/ns4kafka/controller/NamespaceControllerTest.java index ac505078..61685c15 100644 --- a/src/test/java/com/michelin/ns4kafka/controller/NamespaceControllerTest.java +++ b/src/test/java/com/michelin/ns4kafka/controller/NamespaceControllerTest.java @@ -481,6 +481,7 @@ void shouldNotDeleteNamespacesWhenResourcesAreStillLinkedWithIt() { @Test void shouldNotDeleteNamespacesWhenPatternMatchesNothing() { + when(namespaceService.findByWildcardName("namespace*")).thenReturn(List.of()); var result = namespaceController.bulkDelete("namespace*", false); assertEquals(HttpResponse.notFound().getStatus(), result.getStatus()); } From c2a53a8ac3dd2a4d724d1e593850d0ee43690dd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Greffier?= Date: Wed, 25 Sep 2024 00:05:38 +0200 Subject: [PATCH 6/6] Fix indent --- .../controller/NamespaceController.java | 34 ++-- .../controller/NamespaceControllerTest.java | 147 +++++++++--------- 2 files changed, 90 insertions(+), 91 deletions(-) diff --git a/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java b/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java index f95c7b2a..5ea2167e 100644 --- a/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java +++ b/src/main/java/com/michelin/ns4kafka/controller/NamespaceController.java @@ -24,13 +24,9 @@ import jakarta.validation.Valid; import java.time.Instant; import java.util.ArrayList; -import java.util.Collection; import java.util.Date; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.stream.Collectors; /** * Controller to manage the namespaces. @@ -167,21 +163,21 @@ public HttpResponse bulkDelete(@QueryValue(defaultValue = "*") String name } List namespaceResources = namespaces - .stream() - .flatMap(namespace -> namespaceService.findAllResourcesByNamespace(namespace) - .stream()) - .toList(); + .stream() + .flatMap(namespace -> namespaceService.findAllResourcesByNamespace(namespace) + .stream()) + .toList(); if (!namespaceResources.isEmpty()) { List validationErrors = namespaceResources - .stream() - .map(FormatErrorUtils::invalidNamespaceDeleteOperation) - .toList(); + .stream() + .map(FormatErrorUtils::invalidNamespaceDeleteOperation) + .toList(); throw new ResourceValidationException( - NAMESPACE, - String.join(",", namespaces.stream().map(namespace -> namespace.getMetadata().getName()).toList()), - validationErrors + NAMESPACE, + String.join(",", namespaces.stream().map(namespace -> namespace.getMetadata().getName()).toList()), + validationErrors ); } @@ -200,11 +196,11 @@ public HttpResponse bulkDelete(@QueryValue(defaultValue = "*") String name */ private void performDeletion(Namespace namespace) { sendEventLog( - namespace, - ApplyStatus.deleted, - namespace.getSpec(), - null, - EMPTY_STRING + namespace, + ApplyStatus.deleted, + namespace.getSpec(), + null, + EMPTY_STRING ); namespaceService.delete(namespace); } diff --git a/src/test/java/com/michelin/ns4kafka/controller/NamespaceControllerTest.java b/src/test/java/com/michelin/ns4kafka/controller/NamespaceControllerTest.java index 61685c15..078e0cb9 100644 --- a/src/test/java/com/michelin/ns4kafka/controller/NamespaceControllerTest.java +++ b/src/test/java/com/michelin/ns4kafka/controller/NamespaceControllerTest.java @@ -378,35 +378,36 @@ void shouldNotDeleteNamespaceWhenResourcesAreStillLinkedWithIt() { @Test void shouldDeleteNamespaces() { - Namespace existing1 = Namespace.builder() - .metadata(Metadata.builder() - .name("namespace1") - .cluster("local") - .build()) - .spec(Namespace.NamespaceSpec.builder() - .kafkaUser("user") - .build()) - .build(); - Namespace existing2 = Namespace.builder() - .metadata(Metadata.builder() - .name("namespace2") - .cluster("local") - .build()) - .spec(Namespace.NamespaceSpec.builder() - .kafkaUser("user") - .build()) - .build(); + Namespace namespace1 = Namespace.builder() + .metadata(Metadata.builder() + .name("namespace1") + .cluster("local") + .build()) + .spec(Namespace.NamespaceSpec.builder() + .kafkaUser("user") + .build()) + .build(); + + Namespace namespace2 = Namespace.builder() + .metadata(Metadata.builder() + .name("namespace2") + .cluster("local") + .build()) + .spec(Namespace.NamespaceSpec.builder() + .kafkaUser("user") + .build()) + .build(); when(namespaceService.findByWildcardName("namespace*")) - .thenReturn(List.of(existing1, existing2)); - when(namespaceService.findAllResourcesByNamespace(existing1)) - .thenReturn(List.of()); - when(namespaceService.findAllResourcesByNamespace(existing2)) - .thenReturn(List.of()); + .thenReturn(List.of(namespace1, namespace2)); + when(namespaceService.findAllResourcesByNamespace(namespace1)) + .thenReturn(List.of()); + when(namespaceService.findAllResourcesByNamespace(namespace2)) + .thenReturn(List.of()); when(securityService.username()) - .thenReturn(Optional.of("test-user")); + .thenReturn(Optional.of("test-user")); when(securityService.hasRole(ResourceBasedSecurityRule.IS_ADMIN)) - .thenReturn(false); + .thenReturn(false); doNothing().when(applicationEventPublisher).publishEvent(any()); var result = namespaceController.bulkDelete("namespace*", false); @@ -415,31 +416,32 @@ void shouldDeleteNamespaces() { @Test void shouldDeleteNamespacesInDryRunMode() { - Namespace existing1 = Namespace.builder() - .metadata(Metadata.builder() - .name("namespace1") - .cluster("local") - .build()) - .spec(Namespace.NamespaceSpec.builder() - .kafkaUser("user") - .build()) - .build(); - Namespace existing2 = Namespace.builder() - .metadata(Metadata.builder() - .name("namespace2") - .cluster("local") - .build()) - .spec(Namespace.NamespaceSpec.builder() - .kafkaUser("user") - .build()) - .build(); + Namespace namespace1 = Namespace.builder() + .metadata(Metadata.builder() + .name("namespace1") + .cluster("local") + .build()) + .spec(Namespace.NamespaceSpec.builder() + .kafkaUser("user") + .build()) + .build(); + + Namespace namespace2 = Namespace.builder() + .metadata(Metadata.builder() + .name("namespace2") + .cluster("local") + .build()) + .spec(Namespace.NamespaceSpec.builder() + .kafkaUser("user") + .build()) + .build(); when(namespaceService.findByWildcardName("namespace*")) - .thenReturn(List.of(existing1, existing2)); - when(namespaceService.findAllResourcesByNamespace(existing1)) - .thenReturn(List.of()); - when(namespaceService.findAllResourcesByNamespace(existing2)) - .thenReturn(List.of()); + .thenReturn(List.of(namespace1, namespace2)); + when(namespaceService.findAllResourcesByNamespace(namespace1)) + .thenReturn(List.of()); + when(namespaceService.findAllResourcesByNamespace(namespace2)) + .thenReturn(List.of()); var result = namespaceController.bulkDelete("namespace*", true); verify(namespaceService, never()).delete(any()); @@ -448,34 +450,35 @@ void shouldDeleteNamespacesInDryRunMode() { @Test void shouldNotDeleteNamespacesWhenResourcesAreStillLinkedWithIt() { - Namespace existing1 = Namespace.builder() - .metadata(Metadata.builder() - .name("namespace1") - .cluster("local") - .build()) - .spec(Namespace.NamespaceSpec.builder() - .kafkaUser("user") - .build()) - .build(); - Namespace existing2 = Namespace.builder() - .metadata(Metadata.builder() - .name("namespace2") - .cluster("local") - .build()) - .spec(Namespace.NamespaceSpec.builder() - .kafkaUser("user") - .build()) - .build(); + Namespace namespace1 = Namespace.builder() + .metadata(Metadata.builder() + .name("namespace1") + .cluster("local") + .build()) + .spec(Namespace.NamespaceSpec.builder() + .kafkaUser("user") + .build()) + .build(); + + Namespace namespace2 = Namespace.builder() + .metadata(Metadata.builder() + .name("namespace2") + .cluster("local") + .build()) + .spec(Namespace.NamespaceSpec.builder() + .kafkaUser("user") + .build()) + .build(); when(namespaceService.findByWildcardName("namespace*")) - .thenReturn(List.of(existing1, existing2)); - when(namespaceService.findAllResourcesByNamespace(existing1)) - .thenReturn(List.of("Topic/topic1")); - when(namespaceService.findAllResourcesByNamespace(existing2)) - .thenReturn(List.of()); + .thenReturn(List.of(namespace1, namespace2)); + when(namespaceService.findAllResourcesByNamespace(namespace1)) + .thenReturn(List.of("Topic/topic1")); + when(namespaceService.findAllResourcesByNamespace(namespace2)) + .thenReturn(List.of()); assertThrows(ResourceValidationException.class, - () -> namespaceController.bulkDelete("namespace*", false)); + () -> namespaceController.bulkDelete("namespace*", false)); verify(namespaceService, never()).delete(any()); }