Skip to content

Commit

Permalink
fix: delete knative service pods manually (#20)
Browse files Browse the repository at this point in the history
  • Loading branch information
Oleksii-Klimov authored Jan 15, 2025
1 parent 56edffd commit 42d609a
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 10 deletions.
44 changes: 37 additions & 7 deletions src/main/java/com/epam/aidial/kubernetes/KubernetesClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import io.kubernetes.client.openapi.apis.CustomObjectsApi;
import io.kubernetes.client.openapi.models.V1Job;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import io.kubernetes.client.openapi.models.V1Pod;
import io.kubernetes.client.openapi.models.V1PodList;
import io.kubernetes.client.openapi.models.V1Secret;
import io.kubernetes.client.openapi.models.V1Status;
Expand Down Expand Up @@ -44,7 +45,7 @@ public Mono<Void> createSecret(String namespace, V1Secret secret) {
String name = metadata.getName();

CoreV1Api coreApi = new CoreV1Api(apiClient);
log.info("Creating a secret {}", name);
log.info("Creating secret {}", name);
try {
coreApi.createNamespacedSecret(namespace, secret)
.executeAsync(new NoProgressApiCallback<>() {
Expand All @@ -68,7 +69,7 @@ public void onSuccess(V1Secret state, int i, Map<String, List<String>> map) {
public Mono<Boolean> deleteSecret(String namespace, String name) {
return handleMissing(Mono.create(sink -> {
CoreV1Api coreApi = new CoreV1Api(apiClient);
log.info("Deleting a secret {}", name);
log.info("Deleting secret {}", name);
try {
coreApi.deleteNamespacedSecret(name, namespace)
.executeAsync(new NoProgressApiCallback<>() {
Expand Down Expand Up @@ -102,7 +103,7 @@ public Mono<Void> createJob(String namespace, V1Job job, int imageBuildTimeoutSe
.buildCall(null);

try (Watch<V1Job> watch = Watch.createWatch(batchApi.getApiClient(), call, JOB_TYPE_TOKEN.getType())) {
log.info("Creating a job {}", name);
log.info("Creating job {}", name);
batchApi.createNamespacedJob(namespace, job)
.execute();

Expand Down Expand Up @@ -157,7 +158,11 @@ public void onFailure(ApiException e, int i, Map<String, List<String>> map) {

@Override
public void onSuccess(V1PodList state, int i, Map<String, List<String>> map) {
log.info("Received pod list with label {}", label);
if (state.getItems().isEmpty()) {
log.info("No pods with label {}", label);
} else {
log.info("Received a pod list for label {}", label);
}
sink.success(state);
}
});
Expand Down Expand Up @@ -195,7 +200,7 @@ public void onSuccess(String logs, int i, Map<String, List<String>> map) {
public Mono<Boolean> deleteJob(String namespace, String name) {
return handleMissing(Mono.create(sink -> {
BatchV1Api batchV1Api = new BatchV1Api(apiClient);
log.info("Deleting a job {}", name);
log.info("Deleting job {}", name);
try {
batchV1Api.deleteNamespacedJob(name, namespace)
.executeAsync(new NoProgressApiCallback<>() {
Expand Down Expand Up @@ -230,7 +235,7 @@ public Mono<String> createKnativeService(String namespace, V1Service service, in
.buildCall(null);
try (Watch<V1Service> watch = Watch.createWatch(
customObjectsApi.getApiClient(), call, SERVICE_TYPE_TOKEN.getType())) {
log.info("Creating a service {}", name);
log.info("Creating service {}", name);
customObjectsApi.createNamespacedCustomObject(version.group(), version.version(), namespace, SERVICES, service)
.execute();

Expand Down Expand Up @@ -259,7 +264,7 @@ public Mono<Boolean> deleteKnativeService(String namespace, String name, String
ServiceVersion version = ServiceVersion.parse(serviceVersion);

CustomObjectsApi customObjectsApi = new CustomObjectsApi(apiClient);
log.info("Deleting a service {}", name);
log.info("Deleting service {}", name);
try {
customObjectsApi.deleteNamespacedCustomObject(version.group(), version.version(), namespace, SERVICES, name)
.propagationPolicy(FOREGROUND_POLICY)
Expand All @@ -282,6 +287,31 @@ public void onSuccess(Object state, int i, Map<String, List<String>> map) {
}));
}

public Mono<Boolean> deletePod(String namespace, String name) {
return handleMissing(Mono.create(sink -> {
CoreV1Api batchV1Api = new CoreV1Api(apiClient);
log.info("Deleting pod {}", name);
try {
batchV1Api.deleteNamespacedPod(name, namespace)
.gracePeriodSeconds(0)
.executeAsync(new NoProgressApiCallback<>() {
@Override
public void onFailure(ApiException e, int i, Map<String, List<String>> map) {
sink.error(e);
}

@Override
public void onSuccess(V1Pod pod, int i, Map<String, List<String>> map) {
log.info("Pod {} has been deleted", name);
sink.success();
}
});
} catch (ApiException e) {
sink.error(e);
}
}));
}

public static void addKnativeServiceToModelMap(String serviceVersion) {
ServiceVersion version = ServiceVersion.parse(serviceVersion);
ModelMapper.addModelMap(version.group(), version.version(), "Service", SERVICES, true, V1Service.class);
Expand Down
9 changes: 8 additions & 1 deletion src/main/java/com/epam/aidial/service/DeployService.java
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,15 @@ public Mono<String> deploy(

public Mono<Boolean> undeploy(String name) {
KubernetesClient kubernetesClient = kubernetesService.deployClient();
String appName = appName(name);
return kubernetesClient.deleteKnativeService(
namespace, appName(name), kubernetesService.getKnativeServiceVersion());
namespace, appName, kubernetesService.getKnativeServiceVersion())
// Knative has a default termination grace period and ignores any configured value.
// Therefore, an extra step is performed to delete pods instantly.
.flatMap(deleted -> kubernetesClient.getKnativeServicePods(namespace, appName)
.flatMapIterable(V1PodList::getItems)
.flatMap(pod -> kubernetesClient.deletePod(namespace, pod.getMetadata().getName()))
.reduce(deleted, (a, b) -> a || b));
}

public Mono<List<GetApplicationLogsResponseDto.LogEntry>> logs(String name) {
Expand Down
36 changes: 34 additions & 2 deletions src/test/java/com/epam/aidial/service/DeployServiceTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import com.epam.aidial.kubernetes.KubernetesClient;
import com.epam.aidial.kubernetes.knative.V1Service;
import io.kubernetes.client.openapi.models.V1ObjectMeta;
import io.kubernetes.client.openapi.models.V1Pod;
import io.kubernetes.client.openapi.models.V1PodList;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
Expand Down Expand Up @@ -33,6 +35,8 @@ class DeployServiceTest {
private static final String TEST_NAME = "test-name";
private static final String TEST_URL = "url";
private static final String TEST_SERVICE_VERSION = "test-service-version";
private static final String TEST_APP = "app-ctrl-app-test-name";
private static final String TEST_POD = "test-pod";

static final String TEST_NAMESPACE = "test-namespace";

Expand All @@ -57,6 +61,12 @@ class DeployServiceTest {
@Captor
private ArgumentCaptor<String> deleteServiceCaptor;

@Captor
private ArgumentCaptor<String> getServicePodsCaptor;

@Captor
private ArgumentCaptor<String> deletePodCaptor;

@Test
@SuppressWarnings("unchecked")
void testDeploy() {
Expand Down Expand Up @@ -102,6 +112,18 @@ void testUndeploy() {
deleteServiceCaptor.capture(),
deleteServiceCaptor.capture()))
.thenReturn(Mono.just(Boolean.TRUE));
V1PodList podList = new V1PodList()
.addItemsItem(new V1Pod()
.metadata(new V1ObjectMeta()
.name(TEST_POD)));
when(kubernetesClient.getKnativeServicePods(
getServicePodsCaptor.capture(),
getServicePodsCaptor.capture()))
.thenReturn(Mono.just(podList));
when(kubernetesClient.deletePod(
deletePodCaptor.capture(),
deletePodCaptor.capture()))
.thenReturn(Mono.just(Boolean.TRUE));

// Act
Mono<Boolean> actual = deployService.undeploy(TEST_NAME);
Expand All @@ -112,7 +134,11 @@ void testUndeploy() {
.verifyComplete();

assertThat(deleteServiceCaptor.getAllValues())
.isEqualTo(List.of(TEST_NAMESPACE, "app-ctrl-app-test-name", TEST_SERVICE_VERSION));
.isEqualTo(List.of(TEST_NAMESPACE, TEST_APP, TEST_SERVICE_VERSION));
assertThat(getServicePodsCaptor.getAllValues())
.isEqualTo(List.of(TEST_NAMESPACE, TEST_APP));
assertThat(deletePodCaptor.getAllValues())
.isEqualTo(List.of(TEST_NAMESPACE, TEST_POD));
}

@Test
Expand All @@ -125,6 +151,10 @@ void testUndeployReturnsFalse() {
deleteServiceCaptor.capture(),
deleteServiceCaptor.capture()))
.thenReturn(Mono.just(Boolean.FALSE));
when(kubernetesClient.getKnativeServicePods(
getServicePodsCaptor.capture(),
getServicePodsCaptor.capture()))
.thenReturn(Mono.just(new V1PodList()));

// Act
Mono<Boolean> actual = deployService.undeploy(TEST_NAME);
Expand All @@ -135,6 +165,8 @@ void testUndeployReturnsFalse() {
.verifyComplete();

assertThat(deleteServiceCaptor.getAllValues())
.isEqualTo(List.of(TEST_NAMESPACE, "app-ctrl-app-test-name", TEST_SERVICE_VERSION));
.isEqualTo(List.of(TEST_NAMESPACE, TEST_APP, TEST_SERVICE_VERSION));
assertThat(getServicePodsCaptor.getAllValues())
.isEqualTo(List.of(TEST_NAMESPACE, TEST_APP));
}
}

0 comments on commit 42d609a

Please sign in to comment.