feat(api): introduce RevertActor lifecycle RPC - #1675
Conversation
RevertActor is a new actor lifecycle RPC, so the model needs a permission for it. Give it the same shape as can_suspend: the editor tier of the parent atespace, plus a direct grant so a machine identity can revert the actor it drives without holding an atespace role. Revert is the recovery counterpart of suspend and discards execution the same way, so anyone trusted to suspend an actor is trusted to revert it. Viewers and cluster viewers are asserted false, matching the other data plane verbs. Nothing in Go reads these relations yet. This is the model and its test suite only, as with can_suspend and can_resume.
The workflow existed but nothing reached it: Control.RevertActor was still the generated stub answering Unimplemented. Wire the service method to the workflow and add "ate revert actor", so the feature is usable end to end. The fake atelet gained a Terminate implementation. It had none, because no functional test had ever reached that call: delete is the only other caller and every delete test runs against an actor with no worker assignment, which skips the step. Revert from RUNNING is the first path that gets there, and without the method the embedded UnimplementedAteomHerderServer answers Unimplemented, which revert treats as a real failure.
RevertActor's unit and functional tests run against a fake atelet, so nothing exercises the verb against a real control plane. This script reverts from CRASHED, RUNNING and PAUSED, and checks that a SUSPENDED actor is rejected. The crash is produced by deleting the worker pod the actor runs on, which is the case that leaves revert with no worker to terminate through. It needs a live cluster and an existing template, so it is not wired into make verify.
f38cc9a to
7200dde
Compare
Add TestRevertActor_FromPaused and TestRevertActor_FromCrashed to verify RevertActor recovers paused and crashed actors to SUSPENDED at their last external snapshot and allows subsequent resumes
| } | ||
|
|
||
| // TODO: Check if local checkpoints need to be pruned #641 | ||
| // w.ensureLocalCheckpointsPruned(leaseCtx, actor) |
There was a problem hiding this comment.
The local checkpoints should be pruned by ensureWorkerDiscarded -> ensureAteletTerminated above. is that not the case?
There was a problem hiding this comment.
Currently, ensureAteletTerminated runs pruneLocalCheckpoints(actorUID) only for RUNNING actors
For PAUSED actors, worker_assignment is nil (
substrate/cmd/ateapi/internal/controlapi/workflow_delete.go
Lines 122 to 125 in 85ce8ed
), so ensureAteletTerminated skips.
so, I guessed it will be a separate step itself and get called from
deleteActor flow and revertActor flow.
There was a problem hiding this comment.
I see, we should be able to call pruneLocalCheckpoints for paused actors as well, after #1742 is submitted, we can do it in a follow up PR.
| return nil, nil, fmt.Errorf("while fetching actor: %w", err) | ||
| } | ||
|
|
||
| actorTemplate, err := resolveActorTemplate(ctx, w.store, actor) |
There was a problem hiding this comment.
I think there is an issue with the sequence:
- suspended actor on actor template v1 , take a full snapshot, actor.status.current_actor_template_uid == v1
- UpdateActor to v2, ResumeActor, now current_actor_template_uid updated to v2
- Call RevertActor, now the snapshot is reverted to the one taken on v1, but the actor's current_actor_template_uid is still v2.
Can we move the actor_template_uid as a field of ExternalSnapshot, so the revert will take effect together?
There was a problem hiding this comment.
handled this case in a follow up PR - #1713
…iscarded Align function, trace span, and test names with revert semantics of discarding rather than collecting in-progress snapshots, and clarify local checkpoint pruning behavior during revert.
The in-progress snapshot is now recorded as a full URI rather than a name derived from the actor's template, so revert reads the URI directly and no longer needs the template to find the objects. Check that the URI is owned by the actor before deleting its prefix. Derivation used to make that structural; a stored URI can name anything, and this step deletes what it is pointed at.
Adding RevertActor left both unupdated. releaseBoundActor labelled the crash metric with the unknown operation, hiding that a revert was in flight when the worker pod went away. The golden snapshot reconciler fell through to the default branch and requeued forever instead of failing the template. Nothing in the golden flow reverts its actor, so that state means someone else interfered.
|
The FGA part looks good to me. |
| } | ||
|
|
||
| // TODO: Check if local checkpoints need to be pruned #641 | ||
| // w.ensureLocalCheckpointsPruned(leaseCtx, actor) |
There was a problem hiding this comment.
I see, we should be able to call pruneLocalCheckpoints for paused actors as well, after #1742 is submitted, we can do it in a follow up PR.
| ctx, done := stepSpan(ctx, "FinalizeReverted") | ||
| defer func() { err = done(err) }() | ||
|
|
||
| latestActor, err := w.store.GetActor(ctx, actorRef) |
There was a problem hiding this comment.
check the latestActor is in Reverting state first?
| ctx, done := stepSpan(ctx, "DiscardWorker") | ||
| defer func() { err = done(err) }() | ||
|
|
||
| if actor.GetStatus().GetWorkerAssignment() != nil { |
There was a problem hiding this comment.
We should check if the worker is still assigned to the same Actor.
| } | ||
| // A suspend records the in-progress URI under the actor's own prefix | ||
| // before atelet writes the first object, so a URI owned by anything else | ||
| // is a corrupted record: deleting it would collect another actor's data. |
There was a problem hiding this comment.
| // is a corrupted record: deleting it would collect another actor's data. | |
| // is a corrupted record. |
It shouldn't be possible that another actor owns this snapshot.
Summary
This PR introduces the
RevertActorRPC for actor lifecycle management. It includes the API definition, the corresponding workflow execution logic, observability metrics, and updates to the authorization model to support reverting actors.Fixes #1556
Docs updated in #1711
Commit-wise Changes
1. Add RevertActor RPC (
1145d99)RevertActorRPC to the API definitions.ateapi.pb.goandateapi_pb2.py).2. Add the revertActor workflow and observability metrics (
dcb1fae)revertActorworkflow logic, designed to be idempotent and re-enterable. It progresses through the following steps:RUNNING,PAUSED, orCRASHED) and transitions its state toREVERTING.SUSPENDEDand strips all node-local and in-progress state pointers (clearingWorkerAssignment,LocalSnapshotInfo, etc.), returning the actor to its untouched external snapshot.ate.actor.lifecycle.operation.durationto trackrevertoperations).3. Add
can_revertto the authorization model (2fc53ae)can_revertpermission to the auth model, mirroring the shape ofcan_suspend(editor tier of the parent atespace, plus a direct grant so a machine identity can revert the actor it drives without holding an atespace role).4. Serve RevertActor and add the CLI verb (
f04fab4)Control.RevertActorservice method to the workflow (replacing the generated stub that previously answeredUnimplemented)."ate revert actor"CLI command, making the feature usable end-to-end.Terminatefor the fake atelet. This was necessary because reverting an actor from theRUNNINGstate is the first path to reach this call in functional tests (previously, delete tests skipped this step as they ran against actors with no worker assignment).5. Add a manual verify script for RevertActor (
8022db3)RevertActoragainst a real control plane, since unit and functional tests only run against a fake atelet.CRASHED,RUNNING, andPAUSEDstates, and verifies that attempting to revert aSUSPENDEDactor is properly rejected.