Skip to content

Commit

Permalink
BC-6618 automatically delete forgotten namespaces
Browse files Browse the repository at this point in the history
  • Loading branch information
Loki-Afro committed Aug 21, 2024
1 parent b680517 commit 1d9e96c
Show file tree
Hide file tree
Showing 8 changed files with 199 additions and 10 deletions.
13 changes: 13 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@
<version>1.18.34</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-scheduler</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kubernetes-client</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-kubernetes-client</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-mongodb-client</artifactId>
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/de/svs/Namespace.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import org.bson.types.ObjectId;

import java.time.Instant;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

Expand All @@ -16,10 +17,21 @@ public class Namespace extends PanacheMongoEntityBase {
public String name;
public Instant activatedUntil;

public static Namespace create(String name, Instant activatedUntil) {
Namespace namespace = new Namespace();
namespace.name = name;
namespace.activatedUntil = activatedUntil;
return namespace;
}

public static Optional<Namespace> findByName(String name) {
return find("name", name).singleResultOptional();
}

public static List<Namespace> findByActivatedUntilOlderThan(Instant activatedUntil) {
return find("activatedUntil < ?1", activatedUntil).list();
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
Expand Down
1 change: 0 additions & 1 deletion src/main/java/de/svs/NamespaceController.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ public class NamespaceController {

private static final Logger logger = Logger.getLogger(NamespaceController.class);


@ConfigProperty(name = "namespace.activationHours", defaultValue = "48")
int activationHours;

Expand Down
47 changes: 47 additions & 0 deletions src/main/java/de/svs/scheduling/DeleteDeactivatedNamespaces.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package de.svs.scheduling;

import de.svs.Namespace;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.quarkus.scheduler.Scheduled;
import io.quarkus.scheduler.ScheduledExecution;
import jakarta.enterprise.context.ApplicationScoped;
import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.jboss.logging.Logger;

import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.List;

@ApplicationScoped
public class DeleteDeactivatedNamespaces {

private static final Logger logger = Logger.getLogger(DeleteDeactivatedNamespaces.class);
private final KubernetesClient kubernetesClient;

@ConfigProperty(name = "namespace.deletion.afterDaysOfInactivity", defaultValue = "30")
int afterDaysOfInactivity;

public DeleteDeactivatedNamespaces(KubernetesClient kubernetesClient) {
this.kubernetesClient = kubernetesClient;
}

@Scheduled(cron = "{namespace.deletion.cron}", concurrentExecution = Scheduled.ConcurrentExecution.SKIP)
void deleteDeactivatedNamespaces(ScheduledExecution execution) {
Instant thirtyDaysAgo = Instant.now().minus(afterDaysOfInactivity, ChronoUnit.DAYS);
List<Namespace> namespacesToDelete = Namespace.findByActivatedUntilOlderThan(thirtyDaysAgo);
logger.info("found namespaces to delete: " + namespacesToDelete.stream().map(ns -> ns.name).toList());
for (Namespace namespace : namespacesToDelete) {
logger.info("deleting namespace: " + namespace.name);
io.fabric8.kubernetes.api.model.Namespace k8sNamespace = kubernetesClient.namespaces().withName(namespace.name).get();
if (k8sNamespace != null) {
kubernetesClient.namespaces().withName(namespace.name).delete();
namespace.delete();
logger.info("deleted namespace in k8s and db: " + namespace.name);
} else {
namespace.delete();
logger.info("namespace " + namespace.name + " not found, deleted it only from db");
}
}
logger.info("finished deleting namespaces");
}
}
2 changes: 2 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
quarkus.mongodb.connection-string=mongodb://localhost:27017/keda
baseDomain=.brb.dbildungscloud.dev
quarkus.mongodb.database=keda
namespace.deletion.cron=30 * * * * ?
# to set a kubeconfig you have to set the kubeconfig variable, yes lowercase
40 changes: 32 additions & 8 deletions src/test/java/de/svs/NamespaceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import io.quarkus.test.junit.QuarkusTest;
import org.junit.jupiter.api.Test;

import java.time.Instant;
import java.util.List;

import static java.time.temporal.ChronoUnit.MINUTES;
import static org.assertj.core.api.Assertions.assertThat;

@QuarkusTest
Expand All @@ -12,23 +16,43 @@ class NamespaceTest {

@Test
public void shouldNotBeFoundByNameTest() {
Namespace namespace = new Namespace();

namespace.name = "hello";
namespace.persist();
persistNamespace("buh");

assertThat(Namespace.findByName("hello2")).isEmpty();
}

@Test
public void findByNameTest() {
Namespace namespace = new Namespace();

String name = "hello";
namespace.name = name;
namespace.persist();
Namespace namespace = persistNamespace(name);

assertThat(Namespace.findByName(name)).contains(namespace);
}

@Test
public void findByActivatedUntilOlderThanTest() {
Instant threeMinutesAgo = Instant.now().minus(3, MINUTES);
Instant fourMinutesAgo = Instant.now().minus(4, MINUTES);
Instant fiveMinutesAgo = Instant.now().minus(5, MINUTES);

String fiveMinutesAgoNamespace = "fiveMinutesAgoNamespace";
persistNamespace("threeMinutesAgoNamespace", threeMinutesAgo);
persistNamespace(fiveMinutesAgoNamespace, fiveMinutesAgo);

List<Namespace> byActivatedUntilOlderThan = Namespace.findByActivatedUntilOlderThan(fourMinutesAgo);

assertThat(byActivatedUntilOlderThan).extracting(ns -> ns.name).containsOnly(fiveMinutesAgoNamespace);
}

private static Namespace persistNamespace(String name, Instant activatedUntil) {
Namespace namespace = Namespace.create(name, activatedUntil);

namespace.persist();
return namespace;
}

private static Namespace persistNamespace(String name) {
return persistNamespace(name, Instant.now());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package de.svs.scheduling;

import de.svs.Namespace;
import de.svs.QuarkusMongoDbTestResource;
import io.fabric8.kubernetes.api.model.NamespaceBuilder;
import io.fabric8.kubernetes.client.KubernetesClient;
import io.quarkus.scheduler.ScheduledExecution;
import io.quarkus.scheduler.Trigger;
import io.quarkus.test.common.WithTestResource;
import io.quarkus.test.junit.QuarkusTest;
import io.quarkus.test.kubernetes.client.WithKubernetesTestServer;
import jakarta.inject.Inject;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.Test;

import java.time.Instant;

import static java.time.temporal.ChronoUnit.DAYS;
import static org.assertj.core.api.Assertions.assertThat;

@WithKubernetesTestServer
@QuarkusTest
@WithTestResource(QuarkusMongoDbTestResource.ContainerResource.class)
class DeleteDeactivatedNamespacesTest {

@Inject
DeleteDeactivatedNamespaces deleteDeactivatedNamespaces;

@Inject
KubernetesClient k8sClient;

@Test
void deleteDeactivatedNamespaces() {
Instant instantToBeDeleted = Instant.now().minus(300, DAYS);
Instant justNow = Instant.now();

Namespace namespaceThatDoesNotExistInK8s = persistNamespace("namespaceThatDoesNotExistInK8s", instantToBeDeleted);
Namespace namespaceThatExistInK8s = persistNamespace("namespaceThatExistInK8s", instantToBeDeleted);
Namespace freshNamespace = persistNamespace("freshNamespace", justNow);

createK8sNamespace(namespaceThatExistInK8s);
createK8sNamespace(freshNamespace);

deleteDeactivatedNamespaces.deleteDeactivatedNamespaces(scheduledExecution());

assertThat(Namespace.findByIdOptional(namespaceThatDoesNotExistInK8s.id)).isEmpty();
assertThat(Namespace.findByIdOptional(namespaceThatExistInK8s.id)).isEmpty();
assertThat(Namespace.findByIdOptional(freshNamespace.id)).isNotEmpty();

assertThat(k8sClient.namespaces().withName(namespaceThatExistInK8s.name).get()).isNull();
assertThat(k8sClient.namespaces().withName(freshNamespace.name).get()).isNotNull();

}

private void createK8sNamespace(Namespace namespaceThatExistInK8s) {
io.fabric8.kubernetes.api.model.Namespace k8sNamespace = new NamespaceBuilder()
.withNewMetadata()
.withName(namespaceThatExistInK8s.name)
.and()
.build();

k8sClient.resource(k8sNamespace).create();
}

private static @NotNull ScheduledExecution scheduledExecution() {
return new ScheduledExecution() {
@Override
public Trigger getTrigger() {
return null;
}

@Override
public Instant getFireTime() {
return null;
}

@Override
public Instant getScheduledFireTime() {
return null;
}
};
}

private static Namespace persistNamespace(String name, Instant activatedUntil) {
Namespace namespace = Namespace.create(name, activatedUntil);

namespace.persist();
return namespace;
}
}
4 changes: 3 additions & 1 deletion src/test/resources/application.properties
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
quarkus.mongodb.database=keda-test
quarkus.mongodb.database=keda-test
# basically never
namespace.deletion.cron=0/5 14,18,3-39,52 * ? JAN,MAR,SEP MON-FRI 2002-2010

0 comments on commit 1d9e96c

Please sign in to comment.