137 lines
4.7 KiB
TypeScript
137 lines
4.7 KiB
TypeScript
import {
|
|
test,
|
|
expect,
|
|
kubernetesExecutorConfig,
|
|
kubernetesProfileConfig,
|
|
} from "../../fixtures/kubernetes-test-base";
|
|
import type { KubernetesPod } from "../../fixtures/kubernetes-tools";
|
|
import {
|
|
execInKubernetesPod,
|
|
waitForKubernetesPod,
|
|
waitForKubernetesPVC,
|
|
waitForKubernetesResourceAbsent,
|
|
waitForTaskSessionState,
|
|
} from "../../helpers/kubernetes";
|
|
import {
|
|
waitForAgentMessage,
|
|
waitForLatestSessionDone,
|
|
waitForSessionDone,
|
|
} from "../../helpers/session";
|
|
|
|
test("sessions share one task pod through stop and backend restart", async ({
|
|
apiClient,
|
|
seedData,
|
|
cluster,
|
|
backend,
|
|
}) => {
|
|
test.setTimeout(360_000);
|
|
await apiClient.updateExecutor(seedData.executorId, {
|
|
config: kubernetesExecutorConfig(cluster, { request_timeout_seconds: "120" }),
|
|
});
|
|
const profile = await apiClient.createExecutorProfile(seedData.executorId, {
|
|
name: "Shared task pod",
|
|
prepare_script: "",
|
|
cleanup_script: "",
|
|
env_vars: [],
|
|
config: kubernetesProfileConfig(cluster, {
|
|
"workspace.mode": "managed_pvc",
|
|
"workspace.size": "1Gi",
|
|
"workspace.access_modes": JSON.stringify(["ReadWriteOnce"]),
|
|
}),
|
|
});
|
|
const task = await apiClient.createTaskWithAgent(
|
|
seedData.workspaceId,
|
|
"Shared Kubernetes task",
|
|
seedData.agentProfileId,
|
|
{
|
|
description: "/e2e:simple-message",
|
|
workflow_id: seedData.workflowId,
|
|
workflow_step_id: seedData.startStepId,
|
|
executor_id: seedData.executorId,
|
|
executor_profile_id: profile.id,
|
|
},
|
|
);
|
|
await waitForLatestSessionDone(apiClient, task.id, 1, "First session ready", 180_000);
|
|
expect(task.session_id).toBeTruthy();
|
|
const pod = await waitForKubernetesPod(cluster, task.id, task.session_id!);
|
|
const claim = await waitForKubernetesPVC(cluster, task.id, task.session_id!);
|
|
execInKubernetesPod(cluster, pod.metadata.name, [
|
|
"sh",
|
|
"-c",
|
|
"printf shared > /workspace/shared-marker",
|
|
]);
|
|
const launchAdditional = () =>
|
|
apiClient.launchSession(
|
|
{
|
|
task_id: task.id,
|
|
agent_profile_id: seedData.agentProfileId,
|
|
executor_id: seedData.executorId,
|
|
executor_profile_id: profile.id,
|
|
prompt: "/e2e:simple-message",
|
|
},
|
|
90_000,
|
|
);
|
|
const [second, third] = await Promise.all([launchAdditional(), launchAdditional()]);
|
|
await waitForSessionDone(apiClient, task.id, second.session_id, "Second session ready");
|
|
await waitForSessionDone(apiClient, task.id, third.session_id, "Concurrent sibling ready");
|
|
const taskPods = () =>
|
|
cluster.json<{ items: KubernetesPod[] }>([
|
|
"-n",
|
|
cluster.namespace,
|
|
"get",
|
|
"pods",
|
|
"-l",
|
|
`kandev.ai/task-id=${task.id}`,
|
|
]).items;
|
|
expect(taskPods()).toHaveLength(1);
|
|
expect(taskPods()[0].metadata.uid).toBe(pod.metadata.uid);
|
|
const rows = (await apiClient.listKubernetesSessions(seedData.executorId)).filter(
|
|
(row) => row.task_id === task.id,
|
|
);
|
|
expect(rows).toHaveLength(3);
|
|
await test.info().attach("shared-task-session-status", {
|
|
body: JSON.stringify(rows, null, 2),
|
|
contentType: "application/json",
|
|
});
|
|
const instances = execInKubernetesPod(cluster, pod.metadata.name, [
|
|
"sh",
|
|
"-c",
|
|
"find /run/kandev/sessions -name auth.env | wc -l",
|
|
]);
|
|
expect(Number(instances.trim())).toBe(3);
|
|
await apiClient.stopSession({ session_id: task.session_id!, force: true });
|
|
await waitForTaskSessionState(apiClient, task.id, task.session_id!, "CANCELLED");
|
|
expect(taskPods()).toHaveLength(1);
|
|
await apiClient.addUserMessage(
|
|
task.id,
|
|
second.session_id,
|
|
'e2e:message("Sibling survived stop")',
|
|
);
|
|
await waitForAgentMessage(apiClient, second.session_id, "Sibling survived stop");
|
|
await waitForSessionDone(apiClient, task.id, second.session_id, "Sibling survives stop");
|
|
await backend.restart();
|
|
await apiClient.addUserMessage(
|
|
task.id,
|
|
second.session_id,
|
|
'e2e:message("Sibling survived restart")',
|
|
);
|
|
await waitForAgentMessage(apiClient, second.session_id, "Sibling survived restart");
|
|
await waitForSessionDone(
|
|
apiClient,
|
|
task.id,
|
|
second.session_id,
|
|
"Sibling survives backend restart",
|
|
);
|
|
expect(taskPods()).toHaveLength(1);
|
|
expect(taskPods()[0].metadata.uid).toBe(pod.metadata.uid);
|
|
expect(execInKubernetesPod(cluster, pod.metadata.name, ["cat", "/workspace/shared-marker"])).toBe(
|
|
"shared",
|
|
);
|
|
await apiClient.archiveTask(task.id);
|
|
await waitForKubernetesResourceAbsent(cluster, "pod", pod.metadata.name);
|
|
await waitForKubernetesResourceAbsent(cluster, "persistentvolumeclaim", claim.metadata.name);
|
|
expect(rows.map((row) => ({ pod: row.pod_name, failure: row.failure_reason ?? "" }))).toEqual(
|
|
rows.map(() => ({ pod: pod.metadata.name, failure: "" })),
|
|
);
|
|
});
|