Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle wildcard parameter in ResourceQuota list API #427

Merged
merged 3 commits into from
Aug 9, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,11 @@ public class ResourceQuotaController extends NamespacedResourceController {
* @return A list of quotas
*/
@Get
public List<ResourceQuotaResponse> list(String namespace) {
return List.of(resourceQuotaService.getUsedResourcesByQuotaByNamespace(getNamespace(namespace),
resourceQuotaService.findByNamespace(namespace)));
public List<ResourceQuotaResponse> list(String namespace, @QueryValue(defaultValue = "*") String name) {
return resourceQuotaService.findByWildcardName(namespace, name)
.stream()
.map(o -> resourceQuotaService.getUsedResourcesByQuotaByNamespace(getNamespace(namespace), Optional.of(o)))
ThomasCAI-mlv marked this conversation as resolved.
Show resolved Hide resolved
.toList();
}

/**
Expand All @@ -54,8 +56,10 @@ public List<ResourceQuotaResponse> list(String namespace) {
* @param namespace The name
* @param quota The quota name
* @return A quota
* @deprecated use list(String, String name) instead.
*/
@Get("/{quota}")
@Deprecated(since = "1.12.0")
public Optional<ResourceQuotaResponse> get(String namespace, String quota) {
Optional<ResourceQuota> resourceQuota = resourceQuotaService.findByName(namespace, quota);
if (resourceQuota.isEmpty()) {
Expand Down Expand Up @@ -87,7 +91,7 @@ public HttpResponse<ResourceQuota> apply(String namespace, @Body @Valid Resource
throw new ResourceValidationException(quota, validationErrors);
}

Optional<ResourceQuota> resourceQuotaOptional = resourceQuotaService.findByNamespace(namespace);
Optional<ResourceQuota> resourceQuotaOptional = resourceQuotaService.findForNamespace(namespace);
if (resourceQuotaOptional.isPresent() && resourceQuotaOptional.get().equals(quota)) {
return formatHttpResponse(quota, ApplyStatus.unchanged);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public List<ResourceQuota> findAll() {
}

/**
* Get resource quota by namespace.
* Get resource quota of a given namespace.
*
* @param namespace The namespace used to research
* @return A resource quota
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ public List<String> findAllResourcesByNamespace(Namespace namespace) {
.map(connectCluster -> CONNECT_CLUSTER + "/" + connectCluster.getMetadata().getName()),
aclService.findAllForNamespace(namespace).stream()
.map(ace -> ACCESS_CONTROL_ENTRY + "/" + ace.getMetadata().getName()),
resourceQuotaService.findByNamespace(namespace.getMetadata().getName()).stream()
resourceQuotaService.findForNamespace(namespace.getMetadata().getName()).stream()
.map(resourceQuota -> RESOURCE_QUOTA + "/" + resourceQuota.getMetadata().getName()),
roleBindingService.findAllForNamespace(namespace.getMetadata().getName()).stream()
.map(roleBinding -> ROLE_BINDING + "/" + roleBinding.getMetadata().getName())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.michelin.ns4kafka.repository.ResourceQuotaRepository;
import com.michelin.ns4kafka.service.executor.UserAsyncExecutor;
import com.michelin.ns4kafka.util.BytesUtils;
import com.michelin.ns4kafka.util.RegexUtils;
import io.micronaut.core.util.StringUtils;
import jakarta.inject.Inject;
import jakarta.inject.Singleton;
Expand Down Expand Up @@ -54,15 +55,43 @@ public class ResourceQuotaService {
ConnectorService connectorService;

/**
* Find a resource quota by namespace.
* Find a resource quota of a given namespace.
*
* @param namespace The namespace used to research
* @return The researched resource quota
*/
public Optional<ResourceQuota> findByNamespace(String namespace) {
public Optional<ResourceQuota> findForNamespace(String namespace) {
return resourceQuotaRepository.findForNamespace(namespace);
}

/**
* Find all the resource quota of a given namespace.
*
* @param namespace The namespace used to research
* @return The researched resource quota
*/
public List<ResourceQuota> findAllForNamespace(String namespace) {
ThomasCAI-mlv marked this conversation as resolved.
Show resolved Hide resolved
return resourceQuotaRepository.findAll()
.stream()
.filter(resourceQuota -> resourceQuota.getMetadata().getNamespace().equals(namespace))
.toList();
}

/**
* Find a resource quota of a given namespace, filtered by name.
*
* @param namespace The namespace
* @param name The name parameter
* @return The researched resource quota
*/
public List<ResourceQuota> findByWildcardName(String namespace, String name) {
List<String> nameFilterPatterns = RegexUtils.wildcardStringsToRegexPatterns(List.of(name));
return findAllForNamespace(namespace)
.stream()
.filter(quota -> RegexUtils.filterByPattern(quota.getMetadata().getName(), nameFilterPatterns))
.toList();
}

/**
* Find a resource quota by namespace and name.
*
Expand All @@ -71,7 +100,7 @@ public Optional<ResourceQuota> findByNamespace(String namespace) {
* @return The researched resource quota
*/
public Optional<ResourceQuota> findByName(String namespace, String quota) {
return findByNamespace(namespace)
return findForNamespace(namespace)
.stream()
.filter(resourceQuota -> resourceQuota.getMetadata().getName().equals(quota))
.findFirst();
Expand Down Expand Up @@ -227,7 +256,7 @@ public long getCurrentCountConnectorsByNamespace(Namespace namespace) {
* @return A list of errors
*/
public List<String> validateTopicQuota(Namespace namespace, Optional<Topic> existingTopic, Topic newTopic) {
Optional<ResourceQuota> resourceQuotaOptional = findByNamespace(namespace.getMetadata().getName());
Optional<ResourceQuota> resourceQuotaOptional = findForNamespace(namespace.getMetadata().getName());
if (resourceQuotaOptional.isEmpty()) {
return List.of();
}
Expand Down Expand Up @@ -284,7 +313,7 @@ public List<String> validateTopicQuota(Namespace namespace, Optional<Topic> exis
* @return A list of errors
*/
public List<String> validateConnectorQuota(Namespace namespace) {
Optional<ResourceQuota> resourceQuotaOptional = findByNamespace(namespace.getMetadata().getName());
Optional<ResourceQuota> resourceQuotaOptional = findForNamespace(namespace.getMetadata().getName());
if (resourceQuotaOptional.isEmpty()) {
return List.of();
}
Expand Down Expand Up @@ -312,7 +341,7 @@ public List<ResourceQuotaResponse> getUsedQuotaByNamespaces(List<Namespace> name
return namespaces
.stream()
.map(namespace -> getUsedResourcesByQuotaByNamespace(namespace,
findByNamespace(namespace.getMetadata().getName())))
findForNamespace(namespace.getMetadata().getName())))
.toList();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,52 @@ class ResourceQuotaControllerTest {
ApplicationEventPublisher<AuditLog> applicationEventPublisher;

@Test
void list() {
void shouldListQuotaWithoutNameParameter() {
Namespace ns = Namespace.builder()
.metadata(Metadata.builder()
.name("test")
.cluster("local")
.build())
.build();

ResourceQuota quota = ResourceQuota.builder()
.metadata(Metadata.builder()
.cluster("local")
.name("test")
.build())
.build();

ResourceQuotaResponse response = ResourceQuotaResponse.builder()
.spec(ResourceQuotaResponse.ResourceQuotaResponseSpec.builder()
.countTopic("0/INF")
.countPartition("0/INF")
.countConnector("0/INF")
.build())
.build();

when(namespaceService.findByName("test")).thenReturn(Optional.of(ns));
when(resourceQuotaService.findByWildcardName("test", "*")).thenReturn(List.of(quota));
when(resourceQuotaService.getUsedResourcesByQuotaByNamespace(ns, Optional.of(quota))).thenReturn(response);

assertEquals(List.of(response), resourceQuotaController.list("test", "*"));
}

@Test
void shouldListQuotaWithNameParameter() {
Namespace ns = Namespace.builder()
.metadata(Metadata.builder()
.name("test")
.cluster("local")
.build())
.build();

ResourceQuota quota = ResourceQuota.builder()
.metadata(Metadata.builder()
.cluster("local")
.name("quotaName")
.build())
.build();

ResourceQuotaResponse response = ResourceQuotaResponse.builder()
.spec(ResourceQuotaResponse.ResourceQuotaResponseSpec.builder()
.countTopic("0/INF")
Expand All @@ -70,16 +108,17 @@ void list() {
.build();

when(namespaceService.findByName("test")).thenReturn(Optional.of(ns));
when(resourceQuotaService.findByNamespace(ns.getMetadata().getName())).thenReturn(Optional.empty());
when(resourceQuotaService.getUsedResourcesByQuotaByNamespace(ns, Optional.empty())).thenReturn(response);
when(resourceQuotaService.findByWildcardName("test", "quotaName")).thenReturn(List.of(quota));
when(resourceQuotaService.findByWildcardName("test", "not-found")).thenReturn(List.of());
when(resourceQuotaService.getUsedResourcesByQuotaByNamespace(ns, Optional.of(quota))).thenReturn(response);

List<ResourceQuotaResponse> actual = resourceQuotaController.list("test");
assertEquals(1, actual.size());
assertEquals(response, actual.get(0));
assertEquals(List.of(response), resourceQuotaController.list("test", "quotaName"));
assertTrue(resourceQuotaController.list("test", "not-found").isEmpty());
}

@Test
void getEmpty() {
@SuppressWarnings("deprecation")
void shouldGetQuotaWhenEmpty() {
Namespace ns = Namespace.builder()
.metadata(Metadata.builder()
.name("test")
Expand All @@ -94,7 +133,8 @@ void getEmpty() {
}

@Test
void getPresent() {
@SuppressWarnings("deprecation")
void shouldGetQuotaWhenPresent() {
Namespace ns = Namespace.builder()
.metadata(Metadata.builder()
.name("test")
Expand Down Expand Up @@ -128,7 +168,7 @@ void getPresent() {
}

@Test
void applyValidationErrors() {
void shouldApplyValidationErrors() {
Namespace ns = Namespace.builder()
.metadata(Metadata.builder()
.name("test")
Expand Down Expand Up @@ -157,7 +197,7 @@ void applyValidationErrors() {
}

@Test
void applyUnchanged() {
void shouldApplyUnchanged() {
Namespace ns = Namespace.builder()
.metadata(Metadata.builder()
.name("test")
Expand All @@ -175,7 +215,7 @@ void applyUnchanged() {

when(namespaceService.findByName("test")).thenReturn(Optional.of(ns));
when(resourceQuotaService.validateNewResourceQuota(ns, resourceQuota)).thenReturn(List.of());
when(resourceQuotaService.findByNamespace(ns.getMetadata().getName())).thenReturn(Optional.of(resourceQuota));
when(resourceQuotaService.findForNamespace(ns.getMetadata().getName())).thenReturn(Optional.of(resourceQuota));

var response = resourceQuotaController.apply("test", resourceQuota, false);
assertEquals("unchanged", response.header("X-Ns4kafka-Result"));
Expand All @@ -184,7 +224,7 @@ void applyUnchanged() {
}

@Test
void applyDryRun() {
void shouldApplyDryRun() {
Namespace ns = Namespace.builder()
.metadata(Metadata.builder()
.name("test")
Expand All @@ -202,15 +242,15 @@ void applyDryRun() {

when(namespaceService.findByName("test")).thenReturn(Optional.of(ns));
when(resourceQuotaService.validateNewResourceQuota(ns, resourceQuota)).thenReturn(List.of());
when(resourceQuotaService.findByNamespace(ns.getMetadata().getName())).thenReturn(Optional.empty());
when(resourceQuotaService.findForNamespace(ns.getMetadata().getName())).thenReturn(Optional.empty());

var response = resourceQuotaController.apply("test", resourceQuota, true);
assertEquals("created", response.header("X-Ns4kafka-Result"));
verify(resourceQuotaService, never()).create(ArgumentMatchers.any());
}

@Test
void applyCreated() {
void shouldApplyCreated() {
Namespace ns = Namespace.builder()
.metadata(Metadata.builder()
.name("test")
Expand All @@ -228,7 +268,7 @@ void applyCreated() {

when(namespaceService.findByName("test")).thenReturn(Optional.of(ns));
when(resourceQuotaService.validateNewResourceQuota(ns, resourceQuota)).thenReturn(List.of());
when(resourceQuotaService.findByNamespace(ns.getMetadata().getName())).thenReturn(Optional.empty());
when(resourceQuotaService.findForNamespace(ns.getMetadata().getName())).thenReturn(Optional.empty());
when(securityService.username()).thenReturn(Optional.of("test-user"));
when(securityService.hasRole(ResourceBasedSecurityRule.IS_ADMIN)).thenReturn(false);
doNothing().when(applicationEventPublisher).publishEvent(any());
Expand All @@ -241,7 +281,7 @@ void applyCreated() {
}

@Test
void applyUpdated() {
void shouldApplyUpdated() {
Namespace ns = Namespace.builder()
.metadata(Metadata.builder()
.name("test")
Expand All @@ -267,7 +307,7 @@ void applyUpdated() {

when(namespaceService.findByName("test")).thenReturn(Optional.of(ns));
when(resourceQuotaService.validateNewResourceQuota(ns, resourceQuota)).thenReturn(List.of());
when(resourceQuotaService.findByNamespace(ns.getMetadata().getName())).thenReturn(
when(resourceQuotaService.findForNamespace(ns.getMetadata().getName())).thenReturn(
Optional.of(resourceQuotaExisting));
when(securityService.username()).thenReturn(Optional.of("test-user"));
when(securityService.hasRole(ResourceBasedSecurityRule.IS_ADMIN)).thenReturn(false);
Expand All @@ -282,15 +322,15 @@ void applyUpdated() {
}

@Test
void deleteNotFound() {
void shouldDeleteWhenNotFound() {
when(resourceQuotaService.findByName("test", "quota")).thenReturn(Optional.empty());
HttpResponse<Void> actual = resourceQuotaController.delete("test", "quota", false);
assertEquals(HttpStatus.NOT_FOUND, actual.getStatus());
verify(resourceQuotaService, never()).delete(ArgumentMatchers.any());
}

@Test
void deleteDryRun() {
void shouldDeleteWhenDryRun() {
ResourceQuota resourceQuota = ResourceQuota.builder()
.metadata(Metadata.builder()
.cluster("local")
Expand All @@ -306,7 +346,7 @@ void deleteDryRun() {
}

@Test
void delete() {
void shouldDelete() {
ResourceQuota resourceQuota = ResourceQuota.builder()
.metadata(Metadata.builder()
.cluster("local")
Expand Down
Loading