Skip to content
Open
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
25 changes: 24 additions & 1 deletion packages/grid_client/src/high_level/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,9 @@ class HighLevelBase {
}
if (network instanceof Network) {
const hasAccessPoint = network.hasAccessPoint(node_id);
if (hasAccessPoint && network.nodes.length !== 1) {
// Only skip network deletion if we have access point, multiple nodes, AND remaining workloads
// If remainingWorkloads is 0, we're deleting the entire deployment, so delete all network contracts
if (hasAccessPoint && network.nodes.length !== 1 && remainingWorkloads.length !== 0) {
console.log(
`network ${network.name} still has access point:${hasAccessPoint} and number of nodes ${network.nodes.length}`,
);
Expand Down Expand Up @@ -191,6 +193,27 @@ class HighLevelBase {
}
}
}

// Final safety: if this was a full deployment delete, make sure no
// network-node contracts are left behind (e.g., due to access-point guards).
if (network instanceof Network && remainingWorkloads.length === 0) {
const nodesSnapshot = [...network.nodes];
for (const n of nodesSnapshot) {
if (network.getNodeReservedIps(n.node_id).length !== 0) continue;
const contract_id = await network.deleteNode(n.node_id);
for (let d of network.deployments) {
d = await deploymentFactory.fromObj(d);
if (d.contract_id !== contract_id) continue;
if (d.workloads.length === 1) {
twinDeployments.push(new TwinDeployment(d, Operations.delete, 0, 0, "", network));
} else {
d.workloads = d.workloads.filter(item => item.name !== network?.name);
twinDeployments.push(new TwinDeployment(d, Operations.update, 0, 0, "", network));
}
}
}
}

return [twinDeployments, remainingWorkloads, deletedNodes, deletedIps, network];
}

Expand Down
76 changes: 56 additions & 20 deletions packages/playground/src/utils/delete_deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { GridClient } from "@threefold/grid_client";
import { ProjectName } from "@/types";

import { loadVM } from "./deploy_vm";
import { getSubdomain, loadDeploymentGateways } from "./gateway";
import { getDeploymentIps, getSubdomain, loadDeploymentGateways } from "./gateway";
import { updateGrid } from "./grid";

export interface DeleteDeploymentOptions {
Expand Down Expand Up @@ -40,15 +40,13 @@ export async function deleteDeployment(grid: GridClient, options: DeleteDeployme

/* Delete deployment */
if (options.k8s) {
const { gateways } = await loadDeploymentGateways(grid, { filter: () => true });
for (const gateway of gateways) {
try {
await grid.gateway.delete_name({ name: gateway.name });
} catch (error) {
console.error("Error while deleting k8s gateway.", error);
}
const result = await grid.k8s.delete({ name: options.name });
try {
await deleteK8sGateways(grid, options.name, options.ip);
} catch (error) {
console.error("Error during gateway cleanup after K8s deletion:", error);
}
return grid.k8s.delete({ name: options.name });
return result;
}
// if Caprover deployment should handled by machines.delete
if (options.deploymentName && !options.isCaprover) {
Expand Down Expand Up @@ -123,19 +121,57 @@ function isVm(projectName: string) {
return false;
}

async function deleteVmGateways(grid: GridClient, ips?: string[]) {
async function deleteGatewaysByIps(grid: GridClient, ips: string[]) {
if (!ips.length) return;

const { gateways } = await loadDeploymentGateways(grid, {
filter: ips ? gw => gw.backends.some(bk => ips.some(ip => bk.includes(ip))) : undefined,
filter: gw => gw.backends.some(bk => ips.some(ip => bk.includes(ip))),
});
for (const gateway of gateways) {
try {
if (gateway.type.includes("name")) {
await grid.gateway.delete_name(gateway);
} else {
await grid.gateway.delete_fqdn(gateway);
}
} catch (error) {
console.log("Error while deleting vm gateway", error);

if (gateways.length === 0) return;

const deletionPromises = gateways.map(async gateway => {
if (gateway.type.includes("name")) {
return await grid.gateway.delete_name(gateway);
} else {
return await grid.gateway.delete_fqdn(gateway);
}
});

// Wait for all gateway deletions to complete
await Promise.allSettled(deletionPromises);
}

async function deleteVmGateways(grid: GridClient, ips?: string[]) {
if (!ips || ips.length === 0) return;
await deleteGatewaysByIps(grid, ips);
}

async function deleteK8sGateways(grid: GridClient, deploymentName: string, ips?: string[]) {
// Get deployment IPs - try from deployment first, fallback to provided IPs
let deploymentIps: string[] = [];

try {
const k8sDeployment = await grid.k8s.getObj(deploymentName);
const fetchedIps = [
...(k8sDeployment.masters?.flatMap(getDeploymentIps) ?? []),
...(k8sDeployment.workers?.flatMap(getDeploymentIps) ?? []),
];

if (fetchedIps.length > 0) {
deploymentIps = fetchedIps;
} else if (ips && ips.length > 0) {
deploymentIps = ips;
}
} catch (error) {
console.error("Error while fetching K8s deployment for gateway deletion:", error);
if (ips && ips.length > 0) {
deploymentIps = ips;
}
}

if (deploymentIps.length === 0) return;

// Delete all gateways pointing to this K8s cluster
await deleteGatewaysByIps(grid, deploymentIps);
}
10 changes: 9 additions & 1 deletion packages/playground/src/weblets/tf_deployment_list.vue
Original file line number Diff line number Diff line change
Expand Up @@ -546,11 +546,19 @@ async function onDelete(k8s = false) {
item[0].workloads[0].name as string,
);
} else {
// For K8s, collect IPs from all masters and workers
let deploymentIps: string[] = [];
if (k8s && item.masters && item.workers) {
deploymentIps = [...item.masters.flatMap(getDeploymentIps), ...item.workers.flatMap(getDeploymentIps)];
} else {
deploymentIps = getDeploymentIps(item);
}

await deleteDeployment(updateGrid(grid!, { projectName: item.projectName }), {
deploymentName: item.deploymentName,
name: k8s ? item.deploymentName : item.name,
projectName: item.projectName,
ip: getDeploymentIps(item),
ip: deploymentIps,
k8s,
isCaprover: item.projectName?.toLowerCase().includes(ProjectName.Caprover.toLowerCase()),
});
Expand Down