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

fix: delete knative service pods manually #20

Merged
merged 2 commits into from
Jan 15, 2025
Merged
Show file tree
Hide file tree
Changes from all 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
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));
}
}
Loading