Skip to content

Implement list and delete snapshot functionality in Python SDK#448

Open
shrutiyam-glitch wants to merge 3 commits intokubernetes-sigs:mainfrom
shrutiyam-glitch:pss-list-delete
Open

Implement list and delete snapshot functionality in Python SDK#448
shrutiyam-glitch wants to merge 3 commits intokubernetes-sigs:mainfrom
shrutiyam-glitch:pss-list-delete

Conversation

@shrutiyam-glitch
Copy link
Contributor

This PR implements the ability to list and delete Pod Snapshots within the PodSnapshotSandboxClient.

Core Logic Implementation:
* list_snapshots(grouping_labels, ready_only):
-- Fetches PodSnapshots using Kubernetes label selectors.
-- Returns a list of snapshots sorted by creation timestamp (newest first).
-- Supports filtering for snapshots in the Ready state.
*delete_snapshots(grouping_labels, snapshot_uid):
-- Allows deleting a specific snapshot by UID.
-- Supports bulk deletion based on grouping labels or deleting all snapshots associated with the pod.

Testing Done:

  • Integration Test: Added test_podsnapshot_extension.py which verifies the full E2E flow:
    -- creating multiple snapshots, listing them, and performing a cleanup deletion

  • Unit tests are added

Output:

  • Integration Test (clients/python/agentic-sandbox-client/test_podsnapshot_extension.py):
$ python3 test_podsnapshot_extension.py --template-name python-counter-template --namespace sandbox-test
--- Starting Sandbox Client Test (Namespace: sandbox-test, Port: 8888) ---

***** Phase 1: Starting Counter *****

======= Testing Pod Snapshot Extension =======
Creating first pod snapshot 'test-snapshot-10' after 10 seconds...
Trigger Name: test-snapshot-10-20260320-075957-3f239b0f
Snapshot UID: fdb1a93a-37ca-4ac2-b339-3cf2025b2eb0
Success: True
Error Code: 0
Error Reason: 

Creating second pod snapshot 'test-snapshot-20' after 10 seconds...
Trigger Name: test-snapshot-20-20260320-080011-b3a7cf3d
Snapshot UID: ea7b86eb-2813-430e-bd2e-95fac639a720
Success: True
Error Code: 0
Error Reason: 
Recent snapshot UID: ea7b86eb-2813-430e-bd2e-95fac639a720

***** List all existing ready snapshots associated with the sandbox. *****
Snapshot ID: ea7b86eb-2813-430e-bd2e-95fac639a720, Source Pod: sandbox-claim-79aee963, Creation Time: 2026-03-20T08:00:11Z
Snapshot ID: fdb1a93a-37ca-4ac2-b339-3cf2025b2eb0, Source Pod: sandbox-claim-79aee963, Creation Time: 2026-03-20T07:59:58Z

***** Phase 2: Restoring from most recent snapshot & Verifying *****
Pod was restored from the most recent snapshot.

**** Deleting snapshots *****
Deleted Snapshots: ['ea7b86eb-2813-430e-bd2e-95fac639a720', 'fdb1a93a-37ca-4ac2-b339-3cf2025b2eb0']
--- Pod Snapshot Test Passed! ---

--- Sandbox Client Test Finished ---

@netlify
Copy link

netlify bot commented Mar 20, 2026

Deploy Preview for agent-sandbox canceled.

Name Link
🔨 Latest commit ed5d1de
🔍 Latest deploy log https://app.netlify.com/projects/agent-sandbox/deploys/69bd6dbe489b420008fc8465

@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: shrutiyam-glitch
Once this PR has been reviewed and has the lgtm label, please assign justinsb for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot requested review from igooch and justinsb March 20, 2026 15:54
@k8s-ci-robot k8s-ci-robot added cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Mar 20, 2026
"""Result of a list snapshots operation."""

success: bool
snapshots: list[dict[str, str]]
Copy link
Member

Choose a reason for hiding this comment

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

Instead of returning a list[dict[str, str]], wdyt about creating a dedicated dataclass (e.g., SnapshotInfo or SnapshotDetail) for the snapshot representation ?


valid_snapshots.append(
{
"snapshot_id": metadata.get("name"),
Copy link
Member

Choose a reason for hiding this comment

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

We are mapping metadata.get("name") to snapshot_id here. However, down below in delete_snapshots, the parameter is named snapshot_uid. Please add a small docstring note or standardizing the terminology across the client to snapshot_name to prevent confusion.

)

# Sort snapshots by creation timestamp descending
valid_snapshots.sort(key=lambda x: x["creationTimestamp"], reverse=True)
Copy link
Member

Choose a reason for hiding this comment

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

If a snapshot object is missing creationTimestamp or it explicitly evaluates to None for any unforeseen reason, sorting might throw a TypeError. To be safe, you can use key=lambda x: x["creationTimestamp"] or "".

- If snapshot_uid is provided, deletes that specific snapshot.
- If grouping_labels is provided, deletes all snapshots matching the grouping labels.
- If not provided, deletes ALL snapshots for this pod.
Returns a DeleteSnapshotResult containing the list of successfully deleted snapshots.
Copy link
Member

Choose a reason for hiding this comment

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

Just to confirm my understanding here. If a user passes both snapshot_uid and grouping_labels to this method, grouping_labels will be silently ignored, right ? It would be safer to either explicitly enforce mutual exclusivity by raising a ValueError, or at least log a warning that the labels are being ignored when a UID is specified or document this case...

snapshots_to_delete.append(snapshot_uid)
else:
logger.info(
"No snapshot_uid provided. Deleting ALL snapshots for this pod."
Copy link
Member

Choose a reason for hiding this comment

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

This log message says "Deleting ALL snapshots for this pod." But, if grouping_labels is provided, it's actually filtering and only deleting a matching subset. Should we reword this to something like: "No snapshot_uid provided. Fetching snapshots to delete based on pod name and labels."

error_code=SNAPSHOT_ERROR_CODE,
)
if (
snapshots_result
Copy link
Member

Choose a reason for hiding this comment

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

snapshots_result is a ListSnapshotResult dataclass instance, so shouldn't this always be "true" ? . Maybe you can simplify this condition to:
if snapshots_result.success and snapshots_result.snapshots:

name=uid,
)
logger.info(f"PodSnapshot '{uid}' deleted.")
deleted_snapshots.append(uid)
Copy link
Member

Choose a reason for hiding this comment

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

When deleting a list of snapshots (batch deletion), if one deletion throws an ApiException (other than 404), the function returns immediately. This leaves the remaining targeted snapshots undeleted. Let's accumulate the errors in a list and continuing the loop, then returning an aggregated failure response at the end so it attempts to delete as many as they were intended.

Copy link
Contributor

Choose a reason for hiding this comment

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

If one deletion fails in the middle, there is another case where it returns success=False, but partial delete happened

and snapshots_result.snapshots
):
snapshots_to_delete = [
s["snapshot_id"] for s in snapshots_result.snapshots
Copy link
Contributor

@tomergee tomergee Mar 21, 2026

Choose a reason for hiding this comment

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

is it the snapshot name or uid here? not clear, if the name is needed Rename the parameter to snapshot_name, could be confusing

)
if (
snapshots_result
and snapshots_result.success
Copy link
Contributor

Choose a reason for hiding this comment

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

You already checked not snapshots_result.success above

@@ -19,11 +19,13 @@
PodSnapshotSandboxClient,
Copy link
Contributor

Choose a reason for hiding this comment

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

Missing Tests
test for list_snapshots with ready_only=False

There is no unit test verifying that passing ready_only=False includes non-ready snapshots. This is a key feature path.

test for delete_snapshots with snapshot_uid and grouping_labels both provided

The docstring implies only snapshot_uid is used when both are provided — but there's no test asserting that grouping_labels is ignored in that case.

test for delete_snapshots with a generic Exception

delete_snapshots loop catches Exception in addition to ApiException, but there's no unit test for this code path.


# Sort snapshots by creation timestamp descending
valid_snapshots.sort(key=lambda x: x["creationTimestamp"], reverse=True)
logger.info(f"Found {len(valid_snapshots)} ready snapshots.")
Copy link
Contributor

Choose a reason for hiding this comment

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

This will print also when ready_only=False, just change the wording

)
else:
print(
f"No ready snapshots found or failed: {list_result.error_reason if list_result else ''}"
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this check complete?, it just check the object type, will not reach else

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

Labels

cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants