Skip to content

Commit 967c6cd

Browse files
committed
feat(cli): add cleanup subcommand to remove run leftovers from the namespace
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]': - takes --spec like every other subcommand and renders the scenario, so resource names come from the plan config rather than from constants: harness.podLabel, harness.name (the '*-profiles' ConfigMap), labels.app (the harness service) and storage.workloadPvc.name - resolution follows the precedence the run steps use (executor/step.py): CLI flag > scenario > default - 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', so a single namespace cannot reach everything - also deletes the resources with no scenario key of their own (the data-access pod label and the llm-d-benchmark-* / harness-scripts ConfigMaps), which stay matched by name - pods are deleted before the PVC so the pvc-protection finalizer can clear - idempotent: missing resources are skipped and a namespace with no benchmark resources exits 0 - --keep-pvc preserves the workload PVC so workload data survives between runs; --namespace and --pvc-name override the scenario Fixes: #1789 Signed-off-by: Himanshu Prajapati <himanshuprajapati15072003@gmail.com>
1 parent e525662 commit 967c6cd

6 files changed

Lines changed: 737 additions & 2 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,7 @@ llmdbenchmark/ Python package
648648
plan.py Plan subcommand
649649
standup.py Standup subcommand
650650
teardown.py Teardown subcommand
651+
cleanup.py Cleanup subcommand (remove run leftovers)
651652
run.py Run subcommand
652653
experiment.py Experiment subcommand (DoE orchestration)
653654

docs/lifecycle.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,3 +120,13 @@ llmdbenchmark teardown
120120
121121
> [!NOTE]
122122
> The scenario can also be indicated as part of the command line options for `llmdbenchmark teardown` (e.g., `llmdbenchmark teardown --spec kubernetes_H200_modelservice_llama-8b`)
123+
124+
`teardown` removes the deployed llm-d stack, but the harness namespace still holds what benchmark runs created there: the data-access pod, the harness service, the benchmark ConfigMaps and the workload PVC (which keeps billing against its storage backend until deleted). Remove all of it with
125+
126+
```
127+
llmdbenchmark --spec <specification> cleanup
128+
```
129+
130+
`cleanup` takes the same `--spec` as every other subcommand and reads the resource names from the scenario, so a scenario that overrides `harness.podLabel`, `harness.name`, `labels.app` or `storage.workloadPvc.name` is cleaned correctly. It also reaches both namespaces when the scenario sets `harness.namespace` separately from `namespace.name`.
131+
132+
The command is idempotent -- resources that no longer exist are skipped, and a namespace with no benchmark resources exits 0. Pass `--keep-pvc` to preserve the workload PVC so workload data survives for the next run. `--namespace` overrides the namespaces from the scenario (comma-separated `model,harness`), and `--pvc-name` overrides `storage.workloadPvc.name`.

llmdbenchmark/cli.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
from llmdbenchmark.interface import smoketest as smoketest_interface
3535
from llmdbenchmark.interface import experiment as experiment_interface
3636
from llmdbenchmark.interface import results
37+
from llmdbenchmark.interface import cleanup as cleanup_interface
3738
from llmdbenchmark.parser.cli_overrides import (
3839
GLOBAL_SELECTOR,
3940
REDACTED,
@@ -93,6 +94,7 @@ def dispatch_cli(args: argparse.Namespace, logger: logging.Logger) -> None:
9394
Command.SMOKETEST.value,
9495
Command.TEARDOWN.value,
9596
Command.RUN.value,
97+
Command.CLEANUP.value,
9698
):
9799
# Resolve templates, scenarios, and values into the workspace
98100
try:
@@ -164,7 +166,10 @@ def dispatch_cli(args: argparse.Namespace, logger: logging.Logger) -> None:
164166
# This enables kustomize overlays and full manifest inspection.
165167
# Runs even in dry-run mode - helmfile template is purely local
166168
# and does not touch the cluster.
167-
_render_helm_manifests(config.plan_dir, logger)
169+
# Cleanup deletes by name and label and never reads the manifests,
170+
# so it skips the helmfile shell-outs.
171+
if args.command != Command.CLEANUP.value:
172+
_render_helm_manifests(config.plan_dir, logger)
168173

169174
if args.command == Command.STANDUP.value:
170175
_execute_standup(args, logger, render_plan_errors)
@@ -178,6 +183,9 @@ def dispatch_cli(args: argparse.Namespace, logger: logging.Logger) -> None:
178183
if args.command == Command.RUN.value:
179184
_execute_run(args, logger, render_plan_errors)
180185

186+
if args.command == Command.CLEANUP.value:
187+
cleanup_interface.execute(args, logger)
188+
181189

182190
def _render_helm_manifests(plan_dir: Path, logger) -> None:
183191
"""Pre-render modelservice Helm chart manifests into each stack's plan directory.
@@ -2039,6 +2047,7 @@ def cli() -> None:
20392047
run.add_subcommands(subparsers, parents=[benchmark_parser])
20402048
experiment_interface.add_subcommands(subparsers, parents=[benchmark_parser])
20412049
results.add_subcommands(subparsers, parents=[])
2050+
cleanup_interface.add_subcommands(subparsers, parents=[benchmark_parser])
20422051
args = parser.parse_args()
20432052

20442053
# Merge env vars for boolean flags (store_true can't use default=)
@@ -2067,7 +2076,7 @@ def cli() -> None:
20672076
"the following arguments are required: --specification_file/--spec"
20682077
)
20692078

2070-
# Results command is handled separately
2079+
# Results reads the local store only -- no specification, no plans
20712080
if args.command == Command.RESULTS.value:
20722081
temp_dir = Path(tempfile.gettempdir()) / "llmdbenchmark" / "logs"
20732082
temp_dir.mkdir(parents=True, exist_ok=True)

0 commit comments

Comments
 (0)