Skip to content

feat(cli): add cleanup subcommand to remove run leftovers from the namespace - #1801

Open
himanshu1573 wants to merge 1 commit into
llm-d:mainfrom
himanshu1573:feat/cleanup-subcommand
Open

feat(cli): add cleanup subcommand to remove run leftovers from the namespace#1801
himanshu1573 wants to merge 1 commit into
llm-d:mainfrom
himanshu1573:feat/cleanup-subcommand

Conversation

@himanshu1573

Copy link
Copy Markdown

What

Adds llmdbenchmark cleanup --namespace <ns> [--keep-pvc], which removes everything a benchmark run leaves behind in the harness namespace:

  • leftover harness launcher pods (app=llmdbench-harness-launcher)
  • the data-access pod (role=llm-d-benchmark-data-access)
  • service/llm-d-benchmark-harness
  • ConfigMaps: llm-d-benchmark-preprocesses, llm-d-benchmark-run-parameters, llm-d-benchmark-standup-parameters, llmdbench-harness-scripts, and any *-profiles workload profile ConfigMaps
  • pvc/workload-pvc (skipped with --keep-pvc; --pvc-name covers scenarios that override storage.workloadPvc.name)

Why

Closes #1789. run tears down the launcher pod but leaves the rest in the namespace: the PVC bills against its storage backend until deleted, the resource names are CLI internals that consuming docs have to hardcode, and a stale claim wedges retries after a wrong-StorageClass failure.

Design notes

  • Idempotent: resources that no longer exist are skipped; a namespace with no benchmark resources (or a namespace that doesn't exist) exits 0, matching the acceptance criteria in the issue.
  • Pods are deleted before the PVC so the pvc-protection finalizer can clear instead of leaving the claim stuck in Terminating.
  • No spec/plan required: unlike the other subcommands, cleanup only needs --namespace (env: LLMDBENCH_NAMESPACE) — it deletes by name/label, so it works after the user has deleted their scenario or stack. It is dispatched like results, before plan rendering.
  • The deletion set lives in one place (llmdbenchmark/interface/cleanup.py), so llm-d's helpers/benchmark.md can replace its hand-maintained kubectl delete list with this command. The issue also floats labeling every CLI-created resource (app.kubernetes.io/managed-by) and deleting purely by selector; happy to follow up with that if preferred, but name/label-based deletion keeps this PR small and works for resources created by older CLI versions too.

Testing

  • New unit tests in tests/test_cleanup_command.py (full deletion set, --keep-pvc, empty-namespace no-op, pod-before-PVC ordering, custom PVC name) following the existing fake-executor style.
  • Full suite: 1141 passed, 32 skipped.
  • ruff check / ruff format --check (0.15.11, as CI runs) clean.
  • Manual --dry-run exercises the exact kubectl commands end-to-end.

Docs: added a section to docs/lifecycle.md after teardown, and listed cleanup.py in the README package layout.

@Vezio

Vezio commented Aug 18, 2026

Copy link
Copy Markdown
Collaborator

This looks and sounds like a great and well needed addition. Before we continue, please address the erroring CI

CICD is failing on multiple jobs with similar error:

[3](https://github.com/llm-d/llm-d-benchmark/actions/runs/31824123563/job/94871535295?pr=1801#step:32:204)
2026-08-14 19:22:46,369 - INFO    - ✅ [05] Stack 'kind-sim-cicd': Completed: render_profiles
2026-08-14 19:22:46,369 - INFO    - >> [06] Stack 'kind-sim-cicd': Create profile and harness-scripts ConfigMaps
2026-08-14 19:22:46,431 - INFO    -     | Creating ConfigMap 'guidellm-profiles' with 1 profile(s) in ns=llmdbenchcicd-sa-1801...
2026-08-14 19:22:46,569 - INFO    -     | ConfigMap 'guidellm-profiles' created with 1 profile(s)
2026-08-14 19:22:46,570 - INFO    -     | Creating ConfigMap 'llmdbench-harness-scripts' with 26 harness script(s) in ns=llmdbenchcicd-sa-1801...
2026-08-14 19:22:46,774 - INFO    -     | ConfigMap 'llmdbench-harness-scripts' created with 26 script(s)
2026-08-14 19:22:46,774 - INFO    - ✅ [06] Stack 'kind-sim-cicd': Completed: create_profile_configmap
2026-08-14 19:22:46,774 - INFO    - >> [07] Stack 'kind-sim-cicd': Deploy harness pod(s) for benchmark execution
2026-08-14 19:22:46,831 - INFO    -     | Running 1 treatment(s) x 1 parallel pod(s) for 'guidellm' (sequential per treatment)...
2026-08-14 19:22:46,831 - INFO    - 🚀     | [1/1] Treatment 'default': deploying 1 pod(s)...
2026-08-14 19:22:47,015 - INFO    -     | Deployed pod 'guidellm-puok3wqk' (experiment=guidellm-1786735366-50ym4v, parallel=1/1)
2026-08-14 19:22:47,015 - INFO    -     | Waiting for pods (label=app=llmdbench-harness-launcher) to start (timeout=1200s)...
2026-08-14 19:22:52,144 - INFO    -     | All pods are running
2026-08-14 19:22:52,144 - INFO    -     | Waiting for pods (label=app=llmdbench-harness-launcher) to complete (timeout=1200s)...
2026-08-14 19:25:24,175 - INFO    -     | Collecting results for 1 dir(s): guidellm-1786735366-50ym4v_1
2026-08-14 19:25:24,268 - INFO    -     | Collected 4 file(s) for guidellm-1786735366-50ym4v_1
2026-08-14 19:25:24,329 - INFO    -     | Deleted pod 'guidellm-puok3wqk'
2026-08-14 19:25:24,329 - ERROR   - ❌     | [1/1] Treatment 'default' failed (157s): 1 error(s)
2026-08-14 19:25:24,330 - ERROR   - ❌ [07] Stack 'kind-sim-cicd': FAILED: [07] deploy_harness (kind-sim-cicd): FAILED - Some treatments had errors



2026-08-14 19:25:24,330 - INFO    - 📋 Executing 3 global step(s)...

2026-08-14 19:25:24,330 - INFO    - -- [10] Skipping: upload_results (Upload results to cloud storage)
2026-08-14 19:25:24,330 - INFO    - >> [11] Clean up harness pods and ConfigMaps
2026-08-14 19:25:24,389 - INFO    -     | Cleaning up harness resources in ns=llmdbenchcicd-sa-1801...
2026-08-14 19:25:24,445 - INFO    -     | Harness pods deleted
2026-08-14 19:25:24,501 - INFO    -     | ConfigMap 'guidellm-profiles' deleted
2026-08-14 19:25:24,559 - INFO    -     | ConfigMap 'llmdbench-harness-scripts' deleted
2026-08-14 19:25:24,559 - ERROR   - ❌ Run failed:
2026-08-14 19:25:24,559 - INFO    - ✅ [11] Completed: run_cleanup_post
Phase: run
Stack 'kind-sim-cicd' failures:
  - [07] deploy_harness (kind-sim-cicd): FAILED - Some treatments had errors
    * Error: Found pods in error state: guidellm-puok3wqk/harness (terminated: Error, exit_code=1)
2026-08-14 19:25:24,559 - INFO    - -- [12] Skipping: analyze_results (Run local analysis on collected results)

Can you try to reproduce this locally and resolve this issue?

I will re kick off the CICD now just in the event that this was a transient issue...just in case

Comment thread llmdbenchmark/interface/cleanup.py Outdated
# at one command instead of maintaining their own `kubectl delete` list.
DATA_ACCESS_POD_LABEL = "role=llm-d-benchmark-data-access"
HARNESS_LAUNCHER_POD_LABEL = "app=llmdbench-harness-launcher"
HARNESS_SERVICE = "llm-d-benchmark-harness"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe a lot of these parameters (from line 18 to 27) today are able to be configured from the scenario file we specify for run.

This should also be the case here. Having them purely hardcoded will not be feasible. I do see a cli option, but having consistency with how we specify parameters for commans is key.

Can you confirm?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed — you're right for most of this block. Checked each name for an actual config knob:

Scenario-configurable (fixing these):

Constant Config path Rendered at
HARNESS_LAUNCHER_POD_LABEL harness.podLabel 20_harness_pod.yaml.j2:12
WORKLOAD_PVC storage.workloadPvc.name 01_pvc_workload-pvc.yaml.j2:4
HARNESS_SERVICE labels.app 07_service_access_to_harness_data.yaml.j2:4
*-profiles CM harness.name step_11_cleanup_post.py:63

No knob today (literal in the templates/code, so matching them by name is correct): role=llm-d-benchmark-data-access (06_*.j2:7), the three llm-d-benchmark-*-parameters/-preprocesses CMs (cli.py:1254), and llmdbench-harness-scripts (step_06_create_profile_configmap.py:9). Can make those configurable too, but that's a render-side change — happier keeping it out of this PR.

Also worth flagging: the resources split across namespaces. Launcher pods render into harness.namespace | default(namespace.name) (20_harness_pod.yaml.j2:10), while the PVC/service/data-access pod go to namespace.name (01_*.j2:5, 06_*.j2:8, 07_*.j2:5) — so a single --namespace can't reach everything when a scenario sets harness.namespace. Another reason to read this from the scenario.

Plan: cleanup takes --spec (already a top-level arg), resolves the merged config via RenderSpecification.eval(), and derives namespaces + those four values from it — same three-tier precedence step_11_cleanup_post.py already uses (executor/step.py:157): CLI flag > scenario > default. Plus tests for the overrides and precedence.

One question before I push: required or optional --spec? I left it out originally for the recovery case in #1789 — cleaning up after the scenario is gone, or after a standup failed partway. Required is more consistent with the other subcommands; optional keeps that path working. I lean optional, but your call.

@Vezio Vezio Aug 21, 2026

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@himanshu1573 it should be required and used just as it is used in other subcommands. If you migrate those global variabels to the scenario or cli, that would be needed to be referenced in the actual scenario.

Unless did you somehow make cleanup not need --spec if so, how does that work?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Vezio Nothing clever — cleanup doesn't need --spec today because it never resolves anything. It's a flat kubectl pass over hardcoded values: pods by literal label (app=llmdbench-harness-launcher, role=llm-d-benchmark-data-access), the service and the four llm-d-benchmark-* / llmdbench-harness-scripts ConfigMaps by literal name, the profile CM by suffix scan (delete any CM ending in -profiles), and pvc/workload-pvc overridable only via --pvc-name. Each is a get --ignore-not-found then delete, so it no-ops on anything absent. That only "works" for scenarios that override none of those names — one that sets harness.podLabel, storage.workloadPvc.name, labels.app or harness.name silently leaves resources behind. Which is your point.

So: agreed, --spec becomes required and is used as the other subcommands use it. I'll:

  • resolve via RenderSpecification.eval() and derive harness.podLabel, storage.workloadPvc.name, labels.app and {harness.name}-profiles from it, with the same precedence step_11_cleanup_post.py uses (executor/step.py:157): CLI flag > scenario > default
  • replace the -profiles suffix scan with the exact name
  • clean both namespaces — launcher pods render into harness.namespace | default(namespace.name) (20_harness_pod.yaml.j2:10), the PVC/service/data-access pod into namespace.name, so one --namespace can't reach both
  • add tests for the overrides and the precedence

On role=llm-d-benchmark-data-access (06_*.j2:7), the three llm-d-benchmark-*-parameters/-preprocesses CMs (cli.py:1254) and llmdbench-harness-scripts (step_06_create_profile_configmap.py:9): no scenario key exists for these anywhere — they're literal in the templates and render code, so adding keys touches the render path for every command, not just cleanup. I'd keep matching them by name here and do that separately; say the word if you want it in this PR.

One consequence: requiring --spec closes the recovery case from #1789 (cleaning up after the scenario is gone, or a standup that died partway) — that path now needs the original scenario in hand. I'll note it in the subcommand help.

CI is green on the current head after the rebase onto e525662.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Vezio Pushed. --spec is required now and cleanup goes through the same path as the other subcommands: it renders the scenario and reads the names from the plan config.

What comes from the scenario now: harness.podLabel, harness.name (for the *-profiles ConfigMap), labels.app (the service) and storage.workloadPvc.name. Precedence is CLI flag > scenario > default, same as executor/step.py. I also dropped the old -profiles suffix scan, it uses the exact name now.

It also cleans both namespaces. Launcher pods and their ConfigMaps go to harness.namespace | default(namespace.name), the PVC, service and data-access pod go to namespace.name, so one namespace was not enough. -p model-ns,harness-ns works like it does for teardown.

The data-access pod label and the llm-d-benchmark-* / llmdbench-harness-scripts ConfigMaps are still matched by name, since there is no scenario key for them anywhere yet. Happy to add keys for those in a follow-up if you want them configurable.

Tests: 14 cases covering the scenario overrides, the defaults, the CLI override and the two-namespace case. Full suite and ruff are green locally.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've added a new round of comments - several items from the previous review are still outstanding.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Vezio Pushed. cleanup is now a proper Step, same as teardown and run.

What changed

  • interface/cleanup.py is argparse only (404 → 47 lines), like every other interface module.
  • The logic moved to cleanup/steps/step_00_cleanup_resources.py as CleanupResourcesStep(Step), dispatched through StepExecutor from a new _do_cleanup() in cli.py (copied from _do_teardown()).
  • No fallbacks. Every name comes from the rendered config.yaml via Step._require_config(): namespace.name, harness.podLabel, harness.name, labels.app, storage.workloadPvc.name. A missing key raises the framework's existing error instead of guessing.
  • No duplicated loaders. load_stack_configs, _resolve, _delete_by_label are gone — the step uses Step._load_stack_config() and kube_helpers.delete_pods_by_label() / find_data_access_pod() / delete_pods_by_names().
  • Reads context.rendered_stacks like the other commands, instead of rescanning the plan directory.
  • Dropped --pvc-name. --set storage.workloadPvc.name=... already does this on every subcommand.

The llm-d-benchmark-* / llmdbench-harness-scripts ConfigMaps are still matched by name — they're created by name in the standup/run code and have no scenario key (same list teardown/step_02 and run/step_11 use).

The diff got larger than the last round because the restructure touches cli.py, executor/step.py (Phase.CLEANUP) and executor/context.py (keep_pvc), but net it's −42 lines. Tests rewritten around the step (16 cases), full suite and ruff green locally.

@maugustosilva

Copy link
Copy Markdown
Collaborator

@himanshu1573 The breakage on the CI/CD was fixed, please rebase so we can continue with the review. Thanks.

@himanshu1573
himanshu1573 force-pushed the feat/cleanup-subcommand branch from 00e966e to b5cf445 Compare August 20, 2026 16:16
@himanshu1573

Copy link
Copy Markdown
Author

@maugustosilva Rebased onto main (now at e525662) and force-pushed — the branch is up to date and the stale failures are cleared. DCO, signed-commits, License Compliance, Dependency Quality and Security Analysis are green; the Kind benchmark jobs are still queuing.

@Vezio still keen on your take on the --spec question in the thread above (required vs optional for cleanup) before I push the config-driven changes.

@Vezio

Vezio commented Aug 21, 2026

Copy link
Copy Markdown
Collaborator

@himanshu1573 just responded above

@himanshu1573
himanshu1573 force-pushed the feat/cleanup-subcommand branch from b5cf445 to 967c6cd Compare August 21, 2026 16:35
Comment thread llmdbenchmark/interface/cleanup.py Outdated
HARNESS_SCRIPTS_CONFIGMAP,
)

# Fallbacks for the scenario keys below, mirroring config/templates/values/

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mentioned that these sections, namely the hardcoded fall backs, shouldn't exist here. They should be determined based on the scenario provided in the specification.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. The DEFAULT_* constants are gone; every name is now read from the rendered config.yaml with Step._require_config(), and a missing key raises instead of falling back.

Comment thread llmdbenchmark/interface/cleanup.py Outdated
)


def load_stack_configs(plan_dir: Path | None) -> list[dict]:

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems duplicated since we already handle scenario parsing in the cli and many other utilities exist today to load in scenarios.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed. load_stack_configs() is gone — the step now uses Step._load_stack_config() on context.rendered_stacks, the same as the teardown steps.

@himanshu1573
himanshu1573 force-pushed the feat/cleanup-subcommand branch from 967c6cd to f2b31b1 Compare August 24, 2026 17:33
Comment thread llmdbenchmark/cli.py Outdated
)

# Results command is handled separately
# Results reads the local store only -- no specification, no plans

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused, why this comment change?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No good reason — leftover noise from an earlier iteration, unrelated to cleanup. Reverted, the comment is back to the original.

The PR is now purely additive: 693 insertions, 0 deletions. Nothing existing is touched.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😂

…mespace

llmdbenchmark run tears down the harness launcher pod when a run
finishes, but leaves the data-access pod, the harness service, the
benchmark ConfigMaps and the workload PVC behind. The PVC keeps billing
against its storage backend, a stale claim wedges retries after a
wrong-StorageClass failure, and the only way out was a hand-written
kubectl delete naming CLI-internal resource names.

Add 'llmdbenchmark --spec <specification> cleanup [--keep-pvc]',
implemented as a phase like teardown and run:

- interface/cleanup.py is argparse only; the logic lives in
  cleanup/steps/step_00_cleanup_resources.py as CleanupResourcesStep
  and is dispatched through StepExecutor from _do_cleanup() in cli.py
- every resource name is read from the rendered config.yaml with
  Step._require_config(): namespace.name, harness.podLabel,
  harness.name (the '*-profiles' ConfigMap), labels.app (the harness
  service) and storage.workloadPvc.name. There are no hardcoded
  fallbacks; a missing key raises. Overrides go through the same
  --set/--namespace path every other subcommand uses
- reuses the framework helpers instead of re-implementing them:
  Step._load_stack_config(), kube_helpers.delete_pods_by_label(),
  find_data_access_pod() and delete_pods_by_names()
- operates on context.rendered_stacks -- the stacks this invocation
  rendered -- rather than rescanning the plan directory
- cleans both namespaces: launcher pods and their ConfigMaps render
  into 'harness.namespace | default(namespace.name)' while the PVC,
  service and data-access pod render into 'namespace.name'
- the llm-d-benchmark-* and llmdbench-harness-scripts ConfigMaps are
  created by name in the standup/run code and have no scenario key, so
  they stay matched by name (same list teardown step_02 and run
  step_11 use)
- the data-access pod is deleted before the PVC so the pvc-protection
  finalizer can clear
- idempotent: every delete uses --ignore-not-found, and a namespace
  with no benchmark resources exits 0
- --keep-pvc preserves the workload PVC so workload data survives
  between runs

Fixes: llm-d#1789
Signed-off-by: Himanshu Prajapati <himanshuprajapati15072003@gmail.com>
@himanshu1573
himanshu1573 force-pushed the feat/cleanup-subcommand branch from f2b31b1 to 25e37f5 Compare August 24, 2026 19:27
@Vezio Vezio added the do-not-merge Indicates that a PR should not merge label Aug 24, 2026
@Vezio

Vezio commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Marked as Do Not Merge for now since a large portion of the discourse and code written is AI generated.

Only tests completed have been static as documented in the PR:

New unit tests in tests/test_cleanup_command.py (full deletion set, --keep-pvc, empty-namespace no-op, pod-before-PVC ordering, custom PVC name) following the existing fake-executor style.
Full suite: 1141 passed, 32 skipped.
ruff check / ruff format --check (0.15.11, as CI runs) clean.
Manual --dry-run exercises the exact kubectl commands end-to-end.

So, the code has not been tested on a real cluster (or simulation in CICD).

At some point this week, I will take liberty to test these newly added code paths.

@himanshu1573

Copy link
Copy Markdown
Author

Fair point on the cluster testing. You're right that I had only done static tests.

I ran it on a kind cluster today. v1.35.0, default standard storageclass, WaitForFirstConsumer.

I rendered the cicd/kind spec, then created the leftovers a real run leaves behind: the workload PVC, the data-access pod that mounts it, a harness pod, the harness service, and the five configmaps. All five names come from the rendered config, nothing hardcoded:

namespace: llmdbench-cleanup-test
podLabel:  llmdbench-harness-launcher
harness:   inference-perf
service:   llm-d-benchmark-harness
pvc:       workload-pvc

First, does the delete order actually matter?

In a throwaway namespace I deleted the PVC first, while the pod still had it mounted:

$ kubectl delete pvc workload-pvc -n llmdbench-order-control --ignore-not-found --timeout=15s
persistentvolumeclaim "workload-pvc" deleted from llmdbench-order-control namespace
error: timed out waiting for the condition on persistentvolumeclaims/workload-pvc
exit code: 1

$ kubectl get pvc workload-pvc -n llmdbench-order-control
NAME           STATUS        VOLUME                                     CAPACITY   ACCESS MODES
workload-pvc   Terminating   pvc-5d0b7f64-5e3c-4f94-a17f-e69a28feeb7e   128Mi      RWO
finalizers still: ["kubernetes.io/pvc-protection"]

So it hangs. Deleting the pod cleared it right away, which shows the pod was the thing holding it:

$ kubectl delete pod llm-d-benchmark-data-access -n llmdbench-order-control
pod "llm-d-benchmark-data-access" deleted
$ kubectl get pvc workload-pvc -n llmdbench-order-control
Error from server (NotFound): persistentvolumeclaims "workload-pvc" not found

Then the actual command

$ llmdbenchmark --spec cicd/kind --workspace $WS cleanup --namespace llmdbench-cleanup-test
13:42:44,046 |     | Cleaning harness resources in "llmdbench-cleanup-test"...
13:42:45,081 |     | Harness pods deleted
13:42:45,153 | 🗑️  | Deleted configmap/inference-perf-profiles
13:42:45,215 | 🗑️  | Deleted configmap/llm-d-benchmark-preprocesses
13:42:45,281 | 🗑️  | Deleted configmap/llm-d-benchmark-run-parameters
13:42:45,343 | 🗑️  | Deleted configmap/llm-d-benchmark-standup-parameters
13:42:45,405 | 🗑️  | Deleted configmap/llmdbench-harness-scripts
13:42:45,405 |     | Cleaning workload resources in "llmdbench-cleanup-test"...
13:42:46,085 |     | Deleted pod 'llm-d-benchmark-data-access'
13:42:46,164 | 🗑️  | Deleted service/llm-d-benchmark-harness
13:42:46,348 | 🗑️  | Deleted pvc/workload-pvc
13:42:46,349 | ✅  | Cleanup complete.
exit code: 0

Pod at 46,085, PVC at 46,348. That is 0.26s apart, against the 15s timeout in the control run above. That gap is the hang the ordering avoids.

After it ran, the cluster agrees. kubectl get pod,svc,pvc returns nothing, only kube-root-ca.crt is left in configmaps, kubectl get pv is empty, and the namespace is still Active. Cleanup is not teardown.

Also checked

  • Running it again on the now empty namespace: exit 0, no Deleted lines at all.
  • --keep-pvc: logs Keeping PVC "workload-pvc" (--keep-pvc) and the PVC stays Bound.
  • ruff check clean, 298 files already formatted, 1187 passed, 32 skipped. I deselected tests/test_cli_set_overrides.py, it fails on my machine because it writes to an unwritable ~/data. That is pre-existing and unrelated to this PR.

One gap I should flag

cleanup on a namespace that does not exist exits 0 silently. That is because --ignore-not-found hides a missing namespace, not just a missing resource:

$ kubectl delete configmap foo -n does-not-exist --ignore-not-found ; echo $?
0
$ kubectl delete configmap foo -n does-not-exist ; echo $?
Error from server (NotFound): configmaps "foo" not found
1

This matches teardown and the rest of the repo, so I left it alone. Happy to make it error if you would rather.

Scope of what I tested

I built the leftovers by hand in a test namespace rather than running a full standup. So this proves the finalizer hang is real and that deleting pods before PVCs avoids it. It does not prove cleanup handles every kind of leftover a full run produces. The kind-sim CI job would cover that.

On the AI note, that is fair. I have been leaning on it for the write-ups. This one is mine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge Indicates that a PR should not merge

Projects

None yet

Development

Successfully merging this pull request may close these issues.

llmdbenchmark cleanup subcommand to remove run leftovers from the namespace

3 participants