diff --git a/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go b/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go index 1b82acf0c..5198f5ed6 100644 --- a/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go +++ b/cmd/ateapi/internal/controlapi/functionaltest/actor_test.go @@ -1610,7 +1610,7 @@ func TestResumeActor(t *testing.T) { }, }, } - if diff := cmp.Diff(want, getResp, protocmp.Transform(), ignoreUID, ignoreVersion, ignoreTimestamps, protocmp.IgnoreFields(&ateapipb.WorkerAssignment{}, "worker_pod_uid")); diff != "" { + if diff := cmp.Diff(want, getResp, protocmp.Transform(), ignoreUID, ignoreVersion, ignoreTimestamps, ignoreResumeTime, protocmp.IgnoreFields(&ateapipb.WorkerAssignment{}, "worker_pod_uid")); diff != "" { t.Errorf("GetActor response mismatch (-want +got):\n%s", diff) } @@ -2108,7 +2108,7 @@ func TestSuspendActor(t *testing.T) { ignoreUID, ignoreVersion, ignoreTimestamps, - protocmp.IgnoreFields(&ateapipb.ActorStatus{}, "latest_snapshot"), + protocmp.IgnoreFields(&ateapipb.ActorStatus{}, "latest_snapshot", "last_resume_time"), ); diff != "" { t.Errorf("GetActor response mismatch (-want +got):\n%s", diff) } @@ -2202,6 +2202,7 @@ func TestPauseActor(t *testing.T) { ignoreUID, ignoreVersion, ignoreTimestamps, + ignoreResumeTime, protocmp.IgnoreFields(&ateapipb.WorkerAssignment{}, "worker_pod_uid"), protocmp.IgnoreFields(&ateapipb.LocalSnapshotInfo{}, "snapshot_name"), ); diff != "" { diff --git a/cmd/ateapi/internal/controlapi/functionaltest/common_test.go b/cmd/ateapi/internal/controlapi/functionaltest/common_test.go index e7c144000..a09d1088e 100644 --- a/cmd/ateapi/internal/controlapi/functionaltest/common_test.go +++ b/cmd/ateapi/internal/controlapi/functionaltest/common_test.go @@ -70,6 +70,7 @@ var ( ignoreUID = protocmp.IgnoreFields(&ateapipb.ResourceMetadata{}, "uid") ignoreVersion = protocmp.IgnoreFields(&ateapipb.ResourceMetadata{}, "version") ignoreTimestamps = protocmp.IgnoreFields(&ateapipb.ResourceMetadata{}, "create_time", "update_time") + ignoreResumeTime = protocmp.IgnoreFields(&ateapipb.ActorStatus{}, "last_resume_time") ) type testContext struct { diff --git a/cmd/ateapi/internal/controlapi/workflow_resume.go b/cmd/ateapi/internal/controlapi/workflow_resume.go index 5dfce905f..229d6fdd7 100644 --- a/cmd/ateapi/internal/controlapi/workflow_resume.go +++ b/cmd/ateapi/internal/controlapi/workflow_resume.go @@ -31,6 +31,7 @@ import ( "google.golang.org/grpc/codes" "google.golang.org/grpc/status" "google.golang.org/protobuf/proto" + "google.golang.org/protobuf/types/known/timestamppb" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/util/wait" @@ -764,6 +765,7 @@ func (w *ActorWorkflow) finalizeRunning(ctx context.Context, actorRef resources. storedActor, err := w.store.UpdateActor(ctx, actorRef, store.WithPrecondition(latestActor, func(toUpdate *ateapipb.Actor) error { toUpdate.Status.State = ateapipb.ActorState_ACTOR_STATE_RUNNING + toUpdate.Status.LastResumeTime = timestamppb.Now() return nil })) if err != nil { diff --git a/cmd/ateapi/internal/controlapi/workflow_resume_test.go b/cmd/ateapi/internal/controlapi/workflow_resume_test.go index 0439571ee..0c35a085d 100644 --- a/cmd/ateapi/internal/controlapi/workflow_resume_test.go +++ b/cmd/ateapi/internal/controlapi/workflow_resume_test.go @@ -611,6 +611,36 @@ func TestResumeActor_CrashesOnMissingWorkerAssignment(t *testing.T) { } } +func TestFinalizeRunning_SetsLastResumeTime(t *testing.T) { + ctx := context.Background() + st, cleanup := storetest.SetupTestStore(t) + defer cleanup() + w := newTestActorWorkflow(t, st, "ns", "tmpl1") + + ref := resources.ActorRef{Atespace: "team-a", Name: "id1"} + seedWorkflowActor(t, ctx, st, ref, "ns", "tmpl1", ateapipb.ActorState_ACTOR_STATE_RESUMING) + + before := time.Now() + if _, err := w.finalizeRunning(ctx, ref); err != nil { + t.Fatalf("finalizeRunning: %v", err) + } + + got, err := st.GetActor(ctx, ref) + if err != nil { + t.Fatalf("GetActor: %v", err) + } + if got.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_RUNNING { + t.Errorf("state = %v, want RUNNING", got.GetStatus().GetState()) + } + lrt := got.GetStatus().GetLastResumeTime() + if lrt == nil { + t.Fatal("LastResumeTime not set") + } + if ts := lrt.AsTime(); ts.Before(before.Add(-time.Second)) || ts.After(time.Now().Add(time.Second)) { + t.Errorf("LastResumeTime = %v, want within test window", ts) + } +} + // TestValidateAssignedWorker_WorkerOwnership verifies that RESUMING recovery // only proceeds on a worker whose assignment still names this actor: the // recovery path loads the worker by pod name only, so the assignment may have diff --git a/cmd/ateapi/internal/controlapi/workflow_suspend_test.go b/cmd/ateapi/internal/controlapi/workflow_suspend_test.go index 659b4694b..ccbc82200 100644 --- a/cmd/ateapi/internal/controlapi/workflow_suspend_test.go +++ b/cmd/ateapi/internal/controlapi/workflow_suspend_test.go @@ -18,6 +18,7 @@ import ( "context" "errors" "testing" + "time" "github.com/agent-substrate/substrate/cmd/ateapi/internal/store" "github.com/agent-substrate/substrate/cmd/ateapi/internal/store/ateredis" @@ -30,6 +31,7 @@ import ( "github.com/redis/go-redis/v9" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" + "google.golang.org/protobuf/types/known/timestamppb" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/tools/cache" ) @@ -372,6 +374,41 @@ func TestEnsureSuspendedFinalized_NoAssignment(t *testing.T) { } } +// LastResumeTime records the last transition into RUNNING, so suspending must +// leave it alone: it stays queryable while the actor is suspended. +func TestEnsureSuspendedFinalized_KeepsLastResumeTime(t *testing.T) { + ctx := context.Background() + persistence := newTestPersistence(t) + + resumedAt := timestamppb.New(time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)) + actor := &ateapipb.Actor{ + Metadata: &ateapipb.ResourceMetadata{Atespace: "team-a", Name: "actor-1"}, + Status: &ateapipb.ActorStatus{ + State: ateapipb.ActorState_ACTOR_STATE_SUSPENDING, + InProgressSnapshotName: "2026-01-01t00-00-00z-abc", + InProgressSnapshotSourceActorVersion: 1, + LastResumeTime: resumedAt, + }, + } + if _, err := persistence.CreateActor(ctx, actor); err != nil { + t.Fatalf("CreateActor: %v", err) + } + + w := &ActorWorkflow{store: persistence} + tmpl := &atev1alpha1.ActorTemplate{Spec: atev1alpha1.ActorTemplateSpec{SnapshotsConfig: atev1alpha1.SnapshotsConfig{Location: "gs://snapshots"}}} + stored, err := w.ensureSuspendedFinalized(ctx, resources.ActorRef{Atespace: "team-a", Name: "actor-1"}, tmpl) + if err != nil { + t.Fatalf("ensureSuspendedFinalized: %v", err) + } + + if stored.GetStatus().GetState() != ateapipb.ActorState_ACTOR_STATE_SUSPENDED { + t.Errorf("state = %v, want SUSPENDED", stored.GetStatus().GetState()) + } + if got := stored.GetStatus().GetLastResumeTime(); !got.AsTime().Equal(resumedAt.AsTime()) { + t.Errorf("LastResumeTime = %v, want %v preserved through suspend", got.AsTime(), resumedAt.AsTime()) + } +} + func TestEnsureSuspendedFinalized_ReleasesOnlyOwnWorker(t *testing.T) { tests := []struct { name string diff --git a/pkg/proto/ateapipb/ateapi.pb.go b/pkg/proto/ateapipb/ateapi.pb.go index 79c9b6ef4..7f964b932 100644 --- a/pkg/proto/ateapipb/ateapi.pb.go +++ b/pkg/proto/ateapipb/ateapi.pb.go @@ -931,6 +931,8 @@ type ActorStatus struct { // How this Actor was seeded from an ActorSnapshot at CreateActor. Unset if // the Actor was not created from a snapshot. SourceSnapshot *ActorSourceSnapshotStatus `protobuf:"bytes,9,opt,name=source_snapshot,json=sourceSnapshot,proto3" json:"source_snapshot,omitempty"` + // Time of the most recent transition into ACTOR_STATE_RUNNING. + LastResumeTime *timestamppb.Timestamp `protobuf:"bytes,10,opt,name=last_resume_time,json=lastResumeTime,proto3" json:"last_resume_time,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -1028,6 +1030,13 @@ func (x *ActorStatus) GetSourceSnapshot() *ActorSourceSnapshotStatus { return nil } +func (x *ActorStatus) GetLastResumeTime() *timestamppb.Timestamp { + if x != nil { + return x.LastResumeTime + } + return nil +} + // ActorSourceSnapshotStatus records what an Actor's source_snapshot_tag // resolved to at CreateActor time. Set once, immutable afterward. type ActorSourceSnapshotStatus struct { @@ -4873,7 +4882,7 @@ const file_ateapi_proto_rawDesc = "" + "\x0eactor_template\x18\x04 \x01(\v2\x11.ateapi.ObjectRefR\ractorTemplate\x129\n" + "\x0fworker_selector\x18\x05 \x01(\v2\x10.ateapi.SelectorR\x0eworkerSelector\x12A\n" + "\x13source_snapshot_tag\x18\x06 \x01(\v2\x11.ateapi.ObjectRefR\x11sourceSnapshotTag\x12+\n" + - "\x06status\x18\a \x01(\v2\x13.ateapi.ActorStatusR\x06status\"\xe8\x04\n" + + "\x06status\x18\a \x01(\v2\x13.ateapi.ActorStatusR\x06status\"\xae\x05\n" + "\vActorStatus\x12(\n" + "\x05state\x18\x01 \x01(\x0e2\x12.ateapi.ActorStateR\x05state\x12E\n" + "\x11worker_assignment\x18\x02 \x01(\v2\x18.ateapi.WorkerAssignmentR\x10workerAssignment\x129\n" + @@ -4883,7 +4892,9 @@ const file_ateapi_proto_rawDesc = "" + ")in_progress_snapshot_source_actor_version\x18\x06 \x01(\x03R$inProgressSnapshotSourceActorVersion\x12;\n" + "\ractor_volumes\x18\a \x03(\v2\x16.ateapi.ExternalVolumeR\factorVolumes\x12D\n" + "\x1fin_progress_local_snapshot_name\x18\b \x01(\tR\x1binProgressLocalSnapshotName\x12J\n" + - "\x0fsource_snapshot\x18\t \x01(\v2!.ateapi.ActorSourceSnapshotStatusR\x0esourceSnapshot\"m\n" + + "\x0fsource_snapshot\x18\t \x01(\v2!.ateapi.ActorSourceSnapshotStatusR\x0esourceSnapshot\x12D\n" + + "\x10last_resume_time\x18\n" + + " \x01(\v2\x1a.google.protobuf.TimestampR\x0elastResumeTime\"m\n" + "\x19ActorSourceSnapshotStatus\x12-\n" + "\bsnapshot\x18\x01 \x01(\v2\x11.ateapi.ObjectRefR\bsnapshot\x12!\n" + "\fsnapshot_uid\x18\x02 \x01(\tR\vsnapshotUid\"\xc7\x01\n" + @@ -5332,136 +5343,137 @@ var file_ateapi_proto_depIdxs = []int32{ 9, // 14: ateapi.ActorStatus.local_snapshot_info:type_name -> ateapi.LocalSnapshotInfo 12, // 15: ateapi.ActorStatus.actor_volumes:type_name -> ateapi.ExternalVolume 15, // 16: ateapi.ActorStatus.source_snapshot:type_name -> ateapi.ActorSourceSnapshotStatus - 21, // 17: ateapi.ActorSourceSnapshotStatus.snapshot:type_name -> ateapi.ObjectRef - 11, // 18: ateapi.ActorSnapshot.metadata:type_name -> ateapi.ResourceMetadata - 18, // 19: ateapi.ActorSnapshot.status:type_name -> ateapi.ActorSnapshotStatus - 21, // 20: ateapi.ActorSnapshotStatus.source_actor:type_name -> ateapi.ObjectRef - 0, // 21: ateapi.ActorSnapshotStatus.content_scope:type_name -> ateapi.SnapshotContentScope - 21, // 22: ateapi.ActorSnapshotStatus.actor_template:type_name -> ateapi.ObjectRef - 11, // 23: ateapi.ActorSnapshotTag.metadata:type_name -> ateapi.ResourceMetadata - 21, // 24: ateapi.ActorSnapshotTag.snapshot:type_name -> ateapi.ObjectRef - 1, // 25: ateapi.ActorSnapshotTag.scope:type_name -> ateapi.ActorSnapshotTagScope - 11, // 26: ateapi.Atespace.metadata:type_name -> ateapi.ResourceMetadata - 11, // 27: ateapi.ActorTemplate.metadata:type_name -> ateapi.ResourceMetadata - 10, // 28: ateapi.ActorTemplate.worker_selector:type_name -> ateapi.Selector - 29, // 29: ateapi.ActorTemplate.containers:type_name -> ateapi.Container - 33, // 30: ateapi.ActorTemplate.volumes:type_name -> ateapi.Volume - 27, // 31: ateapi.ActorTemplate.snapshots_config:type_name -> ateapi.SnapshotsConfig - 26, // 32: ateapi.ActorTemplate.sandbox_config:type_name -> ateapi.SandboxConfig - 23, // 33: ateapi.ActorTemplate.resources:type_name -> ateapi.Resources - 25, // 34: ateapi.ActorTemplate.status:type_name -> ateapi.ActorTemplateStatus - 24, // 35: ateapi.Resources.limits:type_name -> ateapi.Limits - 4, // 36: ateapi.ActorTemplateStatus.phase:type_name -> ateapi.ActorTemplatePhase - 21, // 37: ateapi.ActorTemplateStatus.golden_snapshot:type_name -> ateapi.ObjectRef - 37, // 38: ateapi.ActorTemplateStatus.sandbox_assets:type_name -> ateapi.SandboxAssets - 3, // 39: ateapi.SandboxConfig.sandbox_class:type_name -> ateapi.SandboxClass - 0, // 40: ateapi.SnapshotsConfig.on_pause:type_name -> ateapi.SnapshotContentScope - 0, // 41: ateapi.SnapshotsConfig.on_commit:type_name -> ateapi.SnapshotContentScope - 28, // 42: ateapi.SnapshotsConfig.on_resume:type_name -> ateapi.OnResumeConfig - 5, // 43: ateapi.OnResumeConfig.from_data:type_name -> ateapi.ResumeSource - 30, // 44: ateapi.Container.env:type_name -> ateapi.EnvVar - 31, // 45: ateapi.Container.readyz:type_name -> ateapi.ContainerReadyz - 36, // 46: ateapi.Container.volume_mounts:type_name -> ateapi.VolumeMount - 32, // 47: ateapi.ContainerReadyz.http_get:type_name -> ateapi.HTTPGetAction - 34, // 48: ateapi.Volume.durable_dir:type_name -> ateapi.DurableDirVolumeSource - 35, // 49: ateapi.Volume.external_volume_template:type_name -> ateapi.ExternalVolumeTemplate - 3, // 50: ateapi.SandboxAssets.sandbox_class:type_name -> ateapi.SandboxClass - 83, // 51: ateapi.SandboxAssets.assets:type_name -> ateapi.SandboxAssets.AssetsEntry - 84, // 52: ateapi.ArchAssets.files:type_name -> ateapi.ArchAssets.FilesEntry - 20, // 53: ateapi.CreateAtespaceRequest.atespace:type_name -> ateapi.Atespace - 21, // 54: ateapi.GetAtespaceRequest.atespace:type_name -> ateapi.ObjectRef - 20, // 55: ateapi.ListAtespacesResponse.atespaces:type_name -> ateapi.Atespace - 21, // 56: ateapi.DeleteAtespaceRequest.atespace:type_name -> ateapi.ObjectRef - 22, // 57: ateapi.CreateActorTemplateRequest.actor_template:type_name -> ateapi.ActorTemplate - 21, // 58: ateapi.GetActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef - 22, // 59: ateapi.ListActorTemplatesResponse.actor_templates:type_name -> ateapi.ActorTemplate - 21, // 60: ateapi.DeleteActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef - 21, // 61: ateapi.GetActorRequest.actor:type_name -> ateapi.ObjectRef - 13, // 62: ateapi.CreateActorRequest.actor:type_name -> ateapi.Actor - 13, // 63: ateapi.UpdateActorRequest.actor:type_name -> ateapi.Actor - 87, // 64: ateapi.UpdateActorRequest.update_mask:type_name -> google.protobuf.FieldMask - 21, // 65: ateapi.SuspendActorRequest.actor:type_name -> ateapi.ObjectRef - 13, // 66: ateapi.SuspendActorResponse.actor:type_name -> ateapi.Actor - 21, // 67: ateapi.PauseActorRequest.actor:type_name -> ateapi.ObjectRef - 13, // 68: ateapi.PauseActorResponse.actor:type_name -> ateapi.Actor - 21, // 69: ateapi.ResumeActorRequest.actor:type_name -> ateapi.ObjectRef - 13, // 70: ateapi.ResumeActorResponse.actor:type_name -> ateapi.Actor - 21, // 71: ateapi.DeleteActorRequest.actor:type_name -> ateapi.ObjectRef - 21, // 72: ateapi.GetActorSnapshotRequest.snapshot:type_name -> ateapi.ObjectRef - 21, // 73: ateapi.GetActorSnapshotTagRequest.tag:type_name -> ateapi.ObjectRef - 17, // 74: ateapi.ListActorSnapshotsResponse.snapshots:type_name -> ateapi.ActorSnapshot - 19, // 75: ateapi.CreateActorSnapshotTagRequest.actor_snapshot_tag:type_name -> ateapi.ActorSnapshotTag - 19, // 76: ateapi.UpdateActorSnapshotTagRequest.tag:type_name -> ateapi.ActorSnapshotTag - 87, // 77: ateapi.UpdateActorSnapshotTagRequest.update_mask:type_name -> google.protobuf.FieldMask - 21, // 78: ateapi.DeleteActorSnapshotTagRequest.tag:type_name -> ateapi.ObjectRef - 71, // 79: ateapi.ListWorkersResponse.workers:type_name -> ateapi.Worker - 13, // 80: ateapi.ListActorsResponse.actors:type_name -> ateapi.Actor - 73, // 81: ateapi.Worker.assignment:type_name -> ateapi.Assignment - 85, // 82: ateapi.Worker.labels:type_name -> ateapi.Worker.LabelsEntry - 8, // 83: ateapi.Worker.state:type_name -> ateapi.Worker.State - 72, // 84: ateapi.Worker.capacity:type_name -> ateapi.WorkerCapacity - 74, // 85: ateapi.Assignment.actor_template:type_name -> ateapi.KubeNamespacedObjectRef - 21, // 86: ateapi.Assignment.actor:type_name -> ateapi.ObjectRef - 6, // 87: ateapi.MintCertRequest.purpose:type_name -> ateapi.ActorCertificatePurpose - 38, // 88: ateapi.SandboxAssets.AssetsEntry.value:type_name -> ateapi.ArchAssets - 39, // 89: ateapi.ArchAssets.FilesEntry.value:type_name -> ateapi.AssetFile - 50, // 90: ateapi.Control.GetActor:input_type -> ateapi.GetActorRequest - 51, // 91: ateapi.Control.CreateActor:input_type -> ateapi.CreateActorRequest - 52, // 92: ateapi.Control.UpdateActor:input_type -> ateapi.UpdateActorRequest - 53, // 93: ateapi.Control.SuspendActor:input_type -> ateapi.SuspendActorRequest - 55, // 94: ateapi.Control.PauseActor:input_type -> ateapi.PauseActorRequest - 57, // 95: ateapi.Control.ResumeActor:input_type -> ateapi.ResumeActorRequest - 59, // 96: ateapi.Control.DeleteActor:input_type -> ateapi.DeleteActorRequest - 60, // 97: ateapi.Control.GetActorSnapshot:input_type -> ateapi.GetActorSnapshotRequest - 61, // 98: ateapi.Control.GetActorSnapshotTag:input_type -> ateapi.GetActorSnapshotTagRequest - 62, // 99: ateapi.Control.ListActorSnapshots:input_type -> ateapi.ListActorSnapshotsRequest - 64, // 100: ateapi.Control.CreateActorSnapshotTag:input_type -> ateapi.CreateActorSnapshotTagRequest - 65, // 101: ateapi.Control.UpdateActorSnapshotTag:input_type -> ateapi.UpdateActorSnapshotTagRequest - 66, // 102: ateapi.Control.DeleteActorSnapshotTag:input_type -> ateapi.DeleteActorSnapshotTagRequest - 67, // 103: ateapi.Control.ListWorkers:input_type -> ateapi.ListWorkersRequest - 69, // 104: ateapi.Control.ListActors:input_type -> ateapi.ListActorsRequest - 40, // 105: ateapi.Control.CreateAtespace:input_type -> ateapi.CreateAtespaceRequest - 41, // 106: ateapi.Control.GetAtespace:input_type -> ateapi.GetAtespaceRequest - 42, // 107: ateapi.Control.ListAtespaces:input_type -> ateapi.ListAtespacesRequest - 44, // 108: ateapi.Control.DeleteAtespace:input_type -> ateapi.DeleteAtespaceRequest - 45, // 109: ateapi.Control.CreateActorTemplate:input_type -> ateapi.CreateActorTemplateRequest - 46, // 110: ateapi.Control.GetActorTemplate:input_type -> ateapi.GetActorTemplateRequest - 47, // 111: ateapi.Control.ListActorTemplates:input_type -> ateapi.ListActorTemplatesRequest - 49, // 112: ateapi.Control.DeleteActorTemplate:input_type -> ateapi.DeleteActorTemplateRequest - 75, // 113: ateapi.Debug.DebugClear:input_type -> ateapi.DebugClearRequest - 77, // 114: ateapi.ActorIdentity.MintJWT:input_type -> ateapi.MintJWTRequest - 79, // 115: ateapi.ActorIdentity.MintCert:input_type -> ateapi.MintCertRequest - 13, // 116: ateapi.Control.GetActor:output_type -> ateapi.Actor - 13, // 117: ateapi.Control.CreateActor:output_type -> ateapi.Actor - 13, // 118: ateapi.Control.UpdateActor:output_type -> ateapi.Actor - 54, // 119: ateapi.Control.SuspendActor:output_type -> ateapi.SuspendActorResponse - 56, // 120: ateapi.Control.PauseActor:output_type -> ateapi.PauseActorResponse - 58, // 121: ateapi.Control.ResumeActor:output_type -> ateapi.ResumeActorResponse - 13, // 122: ateapi.Control.DeleteActor:output_type -> ateapi.Actor - 17, // 123: ateapi.Control.GetActorSnapshot:output_type -> ateapi.ActorSnapshot - 19, // 124: ateapi.Control.GetActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag - 63, // 125: ateapi.Control.ListActorSnapshots:output_type -> ateapi.ListActorSnapshotsResponse - 19, // 126: ateapi.Control.CreateActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag - 19, // 127: ateapi.Control.UpdateActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag - 19, // 128: ateapi.Control.DeleteActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag - 68, // 129: ateapi.Control.ListWorkers:output_type -> ateapi.ListWorkersResponse - 70, // 130: ateapi.Control.ListActors:output_type -> ateapi.ListActorsResponse - 20, // 131: ateapi.Control.CreateAtespace:output_type -> ateapi.Atespace - 20, // 132: ateapi.Control.GetAtespace:output_type -> ateapi.Atespace - 43, // 133: ateapi.Control.ListAtespaces:output_type -> ateapi.ListAtespacesResponse - 20, // 134: ateapi.Control.DeleteAtespace:output_type -> ateapi.Atespace - 22, // 135: ateapi.Control.CreateActorTemplate:output_type -> ateapi.ActorTemplate - 22, // 136: ateapi.Control.GetActorTemplate:output_type -> ateapi.ActorTemplate - 48, // 137: ateapi.Control.ListActorTemplates:output_type -> ateapi.ListActorTemplatesResponse - 22, // 138: ateapi.Control.DeleteActorTemplate:output_type -> ateapi.ActorTemplate - 76, // 139: ateapi.Debug.DebugClear:output_type -> ateapi.DebugClearResponse - 78, // 140: ateapi.ActorIdentity.MintJWT:output_type -> ateapi.MintJWTResponse - 80, // 141: ateapi.ActorIdentity.MintCert:output_type -> ateapi.MintCertResponse - 116, // [116:142] is the sub-list for method output_type - 90, // [90:116] is the sub-list for method input_type - 90, // [90:90] is the sub-list for extension type_name - 90, // [90:90] is the sub-list for extension extendee - 0, // [0:90] is the sub-list for field type_name + 86, // 17: ateapi.ActorStatus.last_resume_time:type_name -> google.protobuf.Timestamp + 21, // 18: ateapi.ActorSourceSnapshotStatus.snapshot:type_name -> ateapi.ObjectRef + 11, // 19: ateapi.ActorSnapshot.metadata:type_name -> ateapi.ResourceMetadata + 18, // 20: ateapi.ActorSnapshot.status:type_name -> ateapi.ActorSnapshotStatus + 21, // 21: ateapi.ActorSnapshotStatus.source_actor:type_name -> ateapi.ObjectRef + 0, // 22: ateapi.ActorSnapshotStatus.content_scope:type_name -> ateapi.SnapshotContentScope + 21, // 23: ateapi.ActorSnapshotStatus.actor_template:type_name -> ateapi.ObjectRef + 11, // 24: ateapi.ActorSnapshotTag.metadata:type_name -> ateapi.ResourceMetadata + 21, // 25: ateapi.ActorSnapshotTag.snapshot:type_name -> ateapi.ObjectRef + 1, // 26: ateapi.ActorSnapshotTag.scope:type_name -> ateapi.ActorSnapshotTagScope + 11, // 27: ateapi.Atespace.metadata:type_name -> ateapi.ResourceMetadata + 11, // 28: ateapi.ActorTemplate.metadata:type_name -> ateapi.ResourceMetadata + 10, // 29: ateapi.ActorTemplate.worker_selector:type_name -> ateapi.Selector + 29, // 30: ateapi.ActorTemplate.containers:type_name -> ateapi.Container + 33, // 31: ateapi.ActorTemplate.volumes:type_name -> ateapi.Volume + 27, // 32: ateapi.ActorTemplate.snapshots_config:type_name -> ateapi.SnapshotsConfig + 26, // 33: ateapi.ActorTemplate.sandbox_config:type_name -> ateapi.SandboxConfig + 23, // 34: ateapi.ActorTemplate.resources:type_name -> ateapi.Resources + 25, // 35: ateapi.ActorTemplate.status:type_name -> ateapi.ActorTemplateStatus + 24, // 36: ateapi.Resources.limits:type_name -> ateapi.Limits + 4, // 37: ateapi.ActorTemplateStatus.phase:type_name -> ateapi.ActorTemplatePhase + 21, // 38: ateapi.ActorTemplateStatus.golden_snapshot:type_name -> ateapi.ObjectRef + 37, // 39: ateapi.ActorTemplateStatus.sandbox_assets:type_name -> ateapi.SandboxAssets + 3, // 40: ateapi.SandboxConfig.sandbox_class:type_name -> ateapi.SandboxClass + 0, // 41: ateapi.SnapshotsConfig.on_pause:type_name -> ateapi.SnapshotContentScope + 0, // 42: ateapi.SnapshotsConfig.on_commit:type_name -> ateapi.SnapshotContentScope + 28, // 43: ateapi.SnapshotsConfig.on_resume:type_name -> ateapi.OnResumeConfig + 5, // 44: ateapi.OnResumeConfig.from_data:type_name -> ateapi.ResumeSource + 30, // 45: ateapi.Container.env:type_name -> ateapi.EnvVar + 31, // 46: ateapi.Container.readyz:type_name -> ateapi.ContainerReadyz + 36, // 47: ateapi.Container.volume_mounts:type_name -> ateapi.VolumeMount + 32, // 48: ateapi.ContainerReadyz.http_get:type_name -> ateapi.HTTPGetAction + 34, // 49: ateapi.Volume.durable_dir:type_name -> ateapi.DurableDirVolumeSource + 35, // 50: ateapi.Volume.external_volume_template:type_name -> ateapi.ExternalVolumeTemplate + 3, // 51: ateapi.SandboxAssets.sandbox_class:type_name -> ateapi.SandboxClass + 83, // 52: ateapi.SandboxAssets.assets:type_name -> ateapi.SandboxAssets.AssetsEntry + 84, // 53: ateapi.ArchAssets.files:type_name -> ateapi.ArchAssets.FilesEntry + 20, // 54: ateapi.CreateAtespaceRequest.atespace:type_name -> ateapi.Atespace + 21, // 55: ateapi.GetAtespaceRequest.atespace:type_name -> ateapi.ObjectRef + 20, // 56: ateapi.ListAtespacesResponse.atespaces:type_name -> ateapi.Atespace + 21, // 57: ateapi.DeleteAtespaceRequest.atespace:type_name -> ateapi.ObjectRef + 22, // 58: ateapi.CreateActorTemplateRequest.actor_template:type_name -> ateapi.ActorTemplate + 21, // 59: ateapi.GetActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef + 22, // 60: ateapi.ListActorTemplatesResponse.actor_templates:type_name -> ateapi.ActorTemplate + 21, // 61: ateapi.DeleteActorTemplateRequest.actor_template:type_name -> ateapi.ObjectRef + 21, // 62: ateapi.GetActorRequest.actor:type_name -> ateapi.ObjectRef + 13, // 63: ateapi.CreateActorRequest.actor:type_name -> ateapi.Actor + 13, // 64: ateapi.UpdateActorRequest.actor:type_name -> ateapi.Actor + 87, // 65: ateapi.UpdateActorRequest.update_mask:type_name -> google.protobuf.FieldMask + 21, // 66: ateapi.SuspendActorRequest.actor:type_name -> ateapi.ObjectRef + 13, // 67: ateapi.SuspendActorResponse.actor:type_name -> ateapi.Actor + 21, // 68: ateapi.PauseActorRequest.actor:type_name -> ateapi.ObjectRef + 13, // 69: ateapi.PauseActorResponse.actor:type_name -> ateapi.Actor + 21, // 70: ateapi.ResumeActorRequest.actor:type_name -> ateapi.ObjectRef + 13, // 71: ateapi.ResumeActorResponse.actor:type_name -> ateapi.Actor + 21, // 72: ateapi.DeleteActorRequest.actor:type_name -> ateapi.ObjectRef + 21, // 73: ateapi.GetActorSnapshotRequest.snapshot:type_name -> ateapi.ObjectRef + 21, // 74: ateapi.GetActorSnapshotTagRequest.tag:type_name -> ateapi.ObjectRef + 17, // 75: ateapi.ListActorSnapshotsResponse.snapshots:type_name -> ateapi.ActorSnapshot + 19, // 76: ateapi.CreateActorSnapshotTagRequest.actor_snapshot_tag:type_name -> ateapi.ActorSnapshotTag + 19, // 77: ateapi.UpdateActorSnapshotTagRequest.tag:type_name -> ateapi.ActorSnapshotTag + 87, // 78: ateapi.UpdateActorSnapshotTagRequest.update_mask:type_name -> google.protobuf.FieldMask + 21, // 79: ateapi.DeleteActorSnapshotTagRequest.tag:type_name -> ateapi.ObjectRef + 71, // 80: ateapi.ListWorkersResponse.workers:type_name -> ateapi.Worker + 13, // 81: ateapi.ListActorsResponse.actors:type_name -> ateapi.Actor + 73, // 82: ateapi.Worker.assignment:type_name -> ateapi.Assignment + 85, // 83: ateapi.Worker.labels:type_name -> ateapi.Worker.LabelsEntry + 8, // 84: ateapi.Worker.state:type_name -> ateapi.Worker.State + 72, // 85: ateapi.Worker.capacity:type_name -> ateapi.WorkerCapacity + 74, // 86: ateapi.Assignment.actor_template:type_name -> ateapi.KubeNamespacedObjectRef + 21, // 87: ateapi.Assignment.actor:type_name -> ateapi.ObjectRef + 6, // 88: ateapi.MintCertRequest.purpose:type_name -> ateapi.ActorCertificatePurpose + 38, // 89: ateapi.SandboxAssets.AssetsEntry.value:type_name -> ateapi.ArchAssets + 39, // 90: ateapi.ArchAssets.FilesEntry.value:type_name -> ateapi.AssetFile + 50, // 91: ateapi.Control.GetActor:input_type -> ateapi.GetActorRequest + 51, // 92: ateapi.Control.CreateActor:input_type -> ateapi.CreateActorRequest + 52, // 93: ateapi.Control.UpdateActor:input_type -> ateapi.UpdateActorRequest + 53, // 94: ateapi.Control.SuspendActor:input_type -> ateapi.SuspendActorRequest + 55, // 95: ateapi.Control.PauseActor:input_type -> ateapi.PauseActorRequest + 57, // 96: ateapi.Control.ResumeActor:input_type -> ateapi.ResumeActorRequest + 59, // 97: ateapi.Control.DeleteActor:input_type -> ateapi.DeleteActorRequest + 60, // 98: ateapi.Control.GetActorSnapshot:input_type -> ateapi.GetActorSnapshotRequest + 61, // 99: ateapi.Control.GetActorSnapshotTag:input_type -> ateapi.GetActorSnapshotTagRequest + 62, // 100: ateapi.Control.ListActorSnapshots:input_type -> ateapi.ListActorSnapshotsRequest + 64, // 101: ateapi.Control.CreateActorSnapshotTag:input_type -> ateapi.CreateActorSnapshotTagRequest + 65, // 102: ateapi.Control.UpdateActorSnapshotTag:input_type -> ateapi.UpdateActorSnapshotTagRequest + 66, // 103: ateapi.Control.DeleteActorSnapshotTag:input_type -> ateapi.DeleteActorSnapshotTagRequest + 67, // 104: ateapi.Control.ListWorkers:input_type -> ateapi.ListWorkersRequest + 69, // 105: ateapi.Control.ListActors:input_type -> ateapi.ListActorsRequest + 40, // 106: ateapi.Control.CreateAtespace:input_type -> ateapi.CreateAtespaceRequest + 41, // 107: ateapi.Control.GetAtespace:input_type -> ateapi.GetAtespaceRequest + 42, // 108: ateapi.Control.ListAtespaces:input_type -> ateapi.ListAtespacesRequest + 44, // 109: ateapi.Control.DeleteAtespace:input_type -> ateapi.DeleteAtespaceRequest + 45, // 110: ateapi.Control.CreateActorTemplate:input_type -> ateapi.CreateActorTemplateRequest + 46, // 111: ateapi.Control.GetActorTemplate:input_type -> ateapi.GetActorTemplateRequest + 47, // 112: ateapi.Control.ListActorTemplates:input_type -> ateapi.ListActorTemplatesRequest + 49, // 113: ateapi.Control.DeleteActorTemplate:input_type -> ateapi.DeleteActorTemplateRequest + 75, // 114: ateapi.Debug.DebugClear:input_type -> ateapi.DebugClearRequest + 77, // 115: ateapi.ActorIdentity.MintJWT:input_type -> ateapi.MintJWTRequest + 79, // 116: ateapi.ActorIdentity.MintCert:input_type -> ateapi.MintCertRequest + 13, // 117: ateapi.Control.GetActor:output_type -> ateapi.Actor + 13, // 118: ateapi.Control.CreateActor:output_type -> ateapi.Actor + 13, // 119: ateapi.Control.UpdateActor:output_type -> ateapi.Actor + 54, // 120: ateapi.Control.SuspendActor:output_type -> ateapi.SuspendActorResponse + 56, // 121: ateapi.Control.PauseActor:output_type -> ateapi.PauseActorResponse + 58, // 122: ateapi.Control.ResumeActor:output_type -> ateapi.ResumeActorResponse + 13, // 123: ateapi.Control.DeleteActor:output_type -> ateapi.Actor + 17, // 124: ateapi.Control.GetActorSnapshot:output_type -> ateapi.ActorSnapshot + 19, // 125: ateapi.Control.GetActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag + 63, // 126: ateapi.Control.ListActorSnapshots:output_type -> ateapi.ListActorSnapshotsResponse + 19, // 127: ateapi.Control.CreateActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag + 19, // 128: ateapi.Control.UpdateActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag + 19, // 129: ateapi.Control.DeleteActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag + 68, // 130: ateapi.Control.ListWorkers:output_type -> ateapi.ListWorkersResponse + 70, // 131: ateapi.Control.ListActors:output_type -> ateapi.ListActorsResponse + 20, // 132: ateapi.Control.CreateAtespace:output_type -> ateapi.Atespace + 20, // 133: ateapi.Control.GetAtespace:output_type -> ateapi.Atespace + 43, // 134: ateapi.Control.ListAtespaces:output_type -> ateapi.ListAtespacesResponse + 20, // 135: ateapi.Control.DeleteAtespace:output_type -> ateapi.Atespace + 22, // 136: ateapi.Control.CreateActorTemplate:output_type -> ateapi.ActorTemplate + 22, // 137: ateapi.Control.GetActorTemplate:output_type -> ateapi.ActorTemplate + 48, // 138: ateapi.Control.ListActorTemplates:output_type -> ateapi.ListActorTemplatesResponse + 22, // 139: ateapi.Control.DeleteActorTemplate:output_type -> ateapi.ActorTemplate + 76, // 140: ateapi.Debug.DebugClear:output_type -> ateapi.DebugClearResponse + 78, // 141: ateapi.ActorIdentity.MintJWT:output_type -> ateapi.MintJWTResponse + 80, // 142: ateapi.ActorIdentity.MintCert:output_type -> ateapi.MintCertResponse + 117, // [117:143] is the sub-list for method output_type + 91, // [91:117] is the sub-list for method input_type + 91, // [91:91] is the sub-list for extension type_name + 91, // [91:91] is the sub-list for extension extendee + 0, // [0:91] is the sub-list for field type_name } func init() { file_ateapi_proto_init() } diff --git a/pkg/proto/ateapipb/ateapi.proto b/pkg/proto/ateapipb/ateapi.proto index 433b65c3e..6951994c8 100644 --- a/pkg/proto/ateapipb/ateapi.proto +++ b/pkg/proto/ateapipb/ateapi.proto @@ -244,6 +244,9 @@ message ActorStatus { // How this Actor was seeded from an ActorSnapshot at CreateActor. Unset if // the Actor was not created from a snapshot. ActorSourceSnapshotStatus source_snapshot = 9; + + // Time of the most recent transition into ACTOR_STATE_RUNNING. + google.protobuf.Timestamp last_resume_time = 10; } // ActorSourceSnapshotStatus records what an Actor's source_snapshot_tag