Rename ActorSnapshotTag to Tag (#1491)

Renames the proto message, its status message and scope enum, the five
RPCs and their request and response messages, the actor_snapshot_tag
request fields, and Actor.source_snapshot_tag to source_tag. The store
interface, its Postgres table, the object-storage prefix segment and the
kubectl-ate verbs follow, so nothing keeps the old spelling.

https://github.com/agent-substrate/substrate/issues/664

> It's a good idea to open an issue first for discussion.

- [x] Tests pass
- [x] Appropriate changes to documentation are included in the PR
This commit is contained in:
Luiz Oliveira
2026-09-04 18:00:36 -04:00
committed by GitHub
parent 6378b707e3
commit 1e56e66b25
45 changed files with 3141 additions and 3151 deletions
File diff suppressed because one or more lines are too long
+72 -73
View File
@@ -104,30 +104,30 @@ class ControlStub:
request_serializer=ateapi__pb2.DeleteActorEgressPolicyRequest.SerializeToString,
response_deserializer=ateapi__pb2.EgressPolicy.FromString,
_registered_method=True)
self.CreateActorSnapshotTag = channel.unary_unary(
'/ateapi.Control/CreateActorSnapshotTag',
request_serializer=ateapi__pb2.CreateActorSnapshotTagRequest.SerializeToString,
response_deserializer=ateapi__pb2.ActorSnapshotTag.FromString,
self.CreateTag = channel.unary_unary(
'/ateapi.Control/CreateTag',
request_serializer=ateapi__pb2.CreateTagRequest.SerializeToString,
response_deserializer=ateapi__pb2.Tag.FromString,
_registered_method=True)
self.GetActorSnapshotTag = channel.unary_unary(
'/ateapi.Control/GetActorSnapshotTag',
request_serializer=ateapi__pb2.GetActorSnapshotTagRequest.SerializeToString,
response_deserializer=ateapi__pb2.ActorSnapshotTag.FromString,
self.GetTag = channel.unary_unary(
'/ateapi.Control/GetTag',
request_serializer=ateapi__pb2.GetTagRequest.SerializeToString,
response_deserializer=ateapi__pb2.Tag.FromString,
_registered_method=True)
self.ListActorSnapshotTags = channel.unary_unary(
'/ateapi.Control/ListActorSnapshotTags',
request_serializer=ateapi__pb2.ListActorSnapshotTagsRequest.SerializeToString,
response_deserializer=ateapi__pb2.ListActorSnapshotTagsResponse.FromString,
self.ListTags = channel.unary_unary(
'/ateapi.Control/ListTags',
request_serializer=ateapi__pb2.ListTagsRequest.SerializeToString,
response_deserializer=ateapi__pb2.ListTagsResponse.FromString,
_registered_method=True)
self.UpdateActorSnapshotTag = channel.unary_unary(
'/ateapi.Control/UpdateActorSnapshotTag',
request_serializer=ateapi__pb2.UpdateActorSnapshotTagRequest.SerializeToString,
response_deserializer=ateapi__pb2.ActorSnapshotTag.FromString,
self.UpdateTag = channel.unary_unary(
'/ateapi.Control/UpdateTag',
request_serializer=ateapi__pb2.UpdateTagRequest.SerializeToString,
response_deserializer=ateapi__pb2.Tag.FromString,
_registered_method=True)
self.DeleteActorSnapshotTag = channel.unary_unary(
'/ateapi.Control/DeleteActorSnapshotTag',
request_serializer=ateapi__pb2.DeleteActorSnapshotTagRequest.SerializeToString,
response_deserializer=ateapi__pb2.ActorSnapshotTag.FromString,
self.DeleteTag = channel.unary_unary(
'/ateapi.Control/DeleteTag',
request_serializer=ateapi__pb2.DeleteTagRequest.SerializeToString,
response_deserializer=ateapi__pb2.Tag.FromString,
_registered_method=True)
self.ListWorkers = channel.unary_unary(
'/ateapi.Control/ListWorkers',
@@ -294,7 +294,7 @@ class ControlServicer:
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def CreateActorSnapshotTag(self, request, context):
def CreateTag(self, request, context):
"""Tag the external snapshot a suspended Actor holds. The tag gets its own
copy of that snapshot, so suspending or deleting the Actor afterwards
cannot collect it.
@@ -303,32 +303,31 @@ class ControlServicer:
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def GetActorSnapshotTag(self, request, context):
"""Get an ActorSnapshot tag.
def GetTag(self, request, context):
"""Get a Tag.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def ListActorSnapshotTags(self, request, context):
"""List ActorSnapshot tags.
def ListTags(self, request, context):
"""List Tags.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def UpdateActorSnapshotTag(self, request, context):
"""Publish or unpublish an ActorSnapshot tag without changing its address.
def UpdateTag(self, request, context):
"""Publish or unpublish a Tag without changing its address.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
raise NotImplementedError('Method not implemented!')
def DeleteActorSnapshotTag(self, request, context):
"""Delete an ActorSnapshot tag and the external snapshot it owns. Actors
created from the tag that have not yet been suspended still point at that
external snapshot and become unrecoverable, so do not delete a tag while
such Actors exist.
def DeleteTag(self, request, context):
"""Delete a Tag and the external snapshot it owns. Actors created from the
tag that have not yet been suspended still point at that external snapshot
and become unrecoverable, so do not delete a tag while such Actors exist.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
@@ -416,7 +415,7 @@ class ControlServicer:
def DeleteAtespace(self, request, context):
"""Delete an empty Atespace. Rejects (FailedPrecondition) if any Actors or
ActorSnapshotTags remain.
Tags remain.
"""
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
context.set_details('Method not implemented!')
@@ -506,30 +505,30 @@ def add_ControlServicer_to_server(servicer, server):
request_deserializer=ateapi__pb2.DeleteActorEgressPolicyRequest.FromString,
response_serializer=ateapi__pb2.EgressPolicy.SerializeToString,
),
'CreateActorSnapshotTag': grpc.unary_unary_rpc_method_handler(
servicer.CreateActorSnapshotTag,
request_deserializer=ateapi__pb2.CreateActorSnapshotTagRequest.FromString,
response_serializer=ateapi__pb2.ActorSnapshotTag.SerializeToString,
'CreateTag': grpc.unary_unary_rpc_method_handler(
servicer.CreateTag,
request_deserializer=ateapi__pb2.CreateTagRequest.FromString,
response_serializer=ateapi__pb2.Tag.SerializeToString,
),
'GetActorSnapshotTag': grpc.unary_unary_rpc_method_handler(
servicer.GetActorSnapshotTag,
request_deserializer=ateapi__pb2.GetActorSnapshotTagRequest.FromString,
response_serializer=ateapi__pb2.ActorSnapshotTag.SerializeToString,
'GetTag': grpc.unary_unary_rpc_method_handler(
servicer.GetTag,
request_deserializer=ateapi__pb2.GetTagRequest.FromString,
response_serializer=ateapi__pb2.Tag.SerializeToString,
),
'ListActorSnapshotTags': grpc.unary_unary_rpc_method_handler(
servicer.ListActorSnapshotTags,
request_deserializer=ateapi__pb2.ListActorSnapshotTagsRequest.FromString,
response_serializer=ateapi__pb2.ListActorSnapshotTagsResponse.SerializeToString,
'ListTags': grpc.unary_unary_rpc_method_handler(
servicer.ListTags,
request_deserializer=ateapi__pb2.ListTagsRequest.FromString,
response_serializer=ateapi__pb2.ListTagsResponse.SerializeToString,
),
'UpdateActorSnapshotTag': grpc.unary_unary_rpc_method_handler(
servicer.UpdateActorSnapshotTag,
request_deserializer=ateapi__pb2.UpdateActorSnapshotTagRequest.FromString,
response_serializer=ateapi__pb2.ActorSnapshotTag.SerializeToString,
'UpdateTag': grpc.unary_unary_rpc_method_handler(
servicer.UpdateTag,
request_deserializer=ateapi__pb2.UpdateTagRequest.FromString,
response_serializer=ateapi__pb2.Tag.SerializeToString,
),
'DeleteActorSnapshotTag': grpc.unary_unary_rpc_method_handler(
servicer.DeleteActorSnapshotTag,
request_deserializer=ateapi__pb2.DeleteActorSnapshotTagRequest.FromString,
response_serializer=ateapi__pb2.ActorSnapshotTag.SerializeToString,
'DeleteTag': grpc.unary_unary_rpc_method_handler(
servicer.DeleteTag,
request_deserializer=ateapi__pb2.DeleteTagRequest.FromString,
response_serializer=ateapi__pb2.Tag.SerializeToString,
),
'ListWorkers': grpc.unary_unary_rpc_method_handler(
servicer.ListWorkers,
@@ -921,7 +920,7 @@ class Control:
_registered_method=True)
@staticmethod
def CreateActorSnapshotTag(request,
def CreateTag(request,
target,
options=(),
channel_credentials=None,
@@ -934,9 +933,9 @@ class Control:
return grpc.experimental.unary_unary(
request,
target,
'/ateapi.Control/CreateActorSnapshotTag',
ateapi__pb2.CreateActorSnapshotTagRequest.SerializeToString,
ateapi__pb2.ActorSnapshotTag.FromString,
'/ateapi.Control/CreateTag',
ateapi__pb2.CreateTagRequest.SerializeToString,
ateapi__pb2.Tag.FromString,
options,
channel_credentials,
insecure,
@@ -948,7 +947,7 @@ class Control:
_registered_method=True)
@staticmethod
def GetActorSnapshotTag(request,
def GetTag(request,
target,
options=(),
channel_credentials=None,
@@ -961,9 +960,9 @@ class Control:
return grpc.experimental.unary_unary(
request,
target,
'/ateapi.Control/GetActorSnapshotTag',
ateapi__pb2.GetActorSnapshotTagRequest.SerializeToString,
ateapi__pb2.ActorSnapshotTag.FromString,
'/ateapi.Control/GetTag',
ateapi__pb2.GetTagRequest.SerializeToString,
ateapi__pb2.Tag.FromString,
options,
channel_credentials,
insecure,
@@ -975,7 +974,7 @@ class Control:
_registered_method=True)
@staticmethod
def ListActorSnapshotTags(request,
def ListTags(request,
target,
options=(),
channel_credentials=None,
@@ -988,9 +987,9 @@ class Control:
return grpc.experimental.unary_unary(
request,
target,
'/ateapi.Control/ListActorSnapshotTags',
ateapi__pb2.ListActorSnapshotTagsRequest.SerializeToString,
ateapi__pb2.ListActorSnapshotTagsResponse.FromString,
'/ateapi.Control/ListTags',
ateapi__pb2.ListTagsRequest.SerializeToString,
ateapi__pb2.ListTagsResponse.FromString,
options,
channel_credentials,
insecure,
@@ -1002,7 +1001,7 @@ class Control:
_registered_method=True)
@staticmethod
def UpdateActorSnapshotTag(request,
def UpdateTag(request,
target,
options=(),
channel_credentials=None,
@@ -1015,9 +1014,9 @@ class Control:
return grpc.experimental.unary_unary(
request,
target,
'/ateapi.Control/UpdateActorSnapshotTag',
ateapi__pb2.UpdateActorSnapshotTagRequest.SerializeToString,
ateapi__pb2.ActorSnapshotTag.FromString,
'/ateapi.Control/UpdateTag',
ateapi__pb2.UpdateTagRequest.SerializeToString,
ateapi__pb2.Tag.FromString,
options,
channel_credentials,
insecure,
@@ -1029,7 +1028,7 @@ class Control:
_registered_method=True)
@staticmethod
def DeleteActorSnapshotTag(request,
def DeleteTag(request,
target,
options=(),
channel_credentials=None,
@@ -1042,9 +1041,9 @@ class Control:
return grpc.experimental.unary_unary(
request,
target,
'/ateapi.Control/DeleteActorSnapshotTag',
ateapi__pb2.DeleteActorSnapshotTagRequest.SerializeToString,
ateapi__pb2.ActorSnapshotTag.FromString,
'/ateapi.Control/DeleteTag',
ateapi__pb2.DeleteTagRequest.SerializeToString,
ateapi__pb2.Tag.FromString,
options,
channel_credentials,
insecure,
+16 -16
View File
@@ -77,11 +77,11 @@ func (s *ServiceImpl) CreateActor(ctx context.Context, inActor *ateapipb.Actor)
return nil, err
}
// If a source snapshot tag is requested, resolve it to the external
// If a source tag is requested, resolve it to the external
// snapshot the new Actor starts from.
var sourceTag *ateapipb.ActorSnapshotTag
if tagRef := inActor.GetSourceSnapshotTag(); tagRef != nil {
sourceTag, err = s.resolveSnapshotTagSource(ctx, inActor.GetMetadata().GetAtespace(), tagRef, template)
var sourceTag *ateapipb.Tag
if tagRef := inActor.GetSourceTag(); tagRef != nil {
sourceTag, err = s.resolveTagSource(ctx, inActor.GetMetadata().GetAtespace(), tagRef, template)
if err != nil {
return nil, err
}
@@ -135,38 +135,38 @@ func (s *ServiceImpl) CreateActor(ctx context.Context, inActor *ateapipb.Actor)
return stored, nil
}
// resolveSnapshotTagSource resolves a CreateActor request's source snapshot tag
// resolveTagSource resolves a CreateActor request's source tag
// and checks that the tag is usable for creating an Actor in actorAtespace
// from template.
func (s *ServiceImpl) resolveSnapshotTagSource(ctx context.Context, actorAtespace string, tagRef *ateapipb.ObjectRef, template *ateapipb.ActorTemplate) (*ateapipb.ActorSnapshotTag, error) {
tag, err := s.store.GetActorSnapshotTag(ctx, resources.ActorSnapshotTagRefFromObjectRef(tagRef))
func (s *ServiceImpl) resolveTagSource(ctx context.Context, actorAtespace string, tagRef *ateapipb.ObjectRef, template *ateapipb.ActorTemplate) (*ateapipb.Tag, error) {
tag, err := s.store.GetTag(ctx, resources.TagRefFromObjectRef(tagRef))
if errors.Is(err, store.ErrNotFound) {
return nil, status.Error(codes.NotFound, "ActorSnapshotTag not found")
return nil, status.Error(codes.NotFound, "Tag not found")
}
if err != nil {
return nil, fmt.Errorf("while getting actor snapshot tag: %w", err)
return nil, fmt.Errorf("while getting tag: %w", err)
}
switch tag.GetScope() {
case ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE:
case ateapipb.TagScope_TAG_SCOPE_ATESPACE:
if tag.GetMetadata().GetAtespace() != actorAtespace {
return nil, status.Error(codes.FailedPrecondition, "ActorSnapshotTag is not published outside its Atespace")
return nil, status.Error(codes.FailedPrecondition, "Tag is not published outside its Atespace")
}
case ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED:
case ateapipb.TagScope_TAG_SCOPE_PUBLISHED:
default:
return nil, status.Error(codes.FailedPrecondition, "source ActorSnapshotTag has an invalid scope")
return nil, status.Error(codes.FailedPrecondition, "source Tag has an invalid scope")
}
// A tag might have an empty Snapshot URI if the tag creation failed or is ongoing.
if tag.GetStatus().GetSnapshot().GetSnapshotUri() == "" {
return nil, status.Error(codes.FailedPrecondition, "source ActorSnapshotTag is still being created or failed creation")
return nil, status.Error(codes.FailedPrecondition, "source Tag is still being created or failed creation")
}
// TODO: Permit compatible DATA snapshots when runtimes can extract portable data.
if tag.GetStatus().GetActorTemplateUid() != template.GetMetadata().GetUid() {
return nil, status.Errorf(codes.FailedPrecondition, "source ActorSnapshotTag must be taken from an actor with ActorTemplate uid %q", tag.GetStatus().GetActorTemplateUid())
return nil, status.Errorf(codes.FailedPrecondition, "source Tag must be taken from an actor with ActorTemplate uid %q", tag.GetStatus().GetActorTemplateUid())
}
for _, volume := range template.GetVolumes() {
if volume.GetExternalVolumeTemplate() != nil {
// TODO: Permit cloning after CSI volume snapshots are supported.
return nil, status.Error(codes.FailedPrecondition, "ActorSnapshotTag cloning does not support ActorTemplates with external volumes")
return nil, status.Error(codes.FailedPrecondition, "Tag cloning does not support ActorTemplates with external volumes")
}
}
return tag, nil
File diff suppressed because it is too large Load Diff
+54 -54
View File
@@ -48,7 +48,7 @@ func TestValidateCreateActorRequest(t *testing.T) {
withStatus := withActorStatus
withMetadata := withActorMetadata
withActorTemplate := withActorActorTemplate
withSourceSnapshotTag := withActorSourceSnapshotTag
withSourceTag := withActorSourceTag
withWorkerSelector := withActorWorkerSelector
tests := []struct {
@@ -140,25 +140,25 @@ func TestValidateCreateActorRequest(t *testing.T) {
validReq(validActor(withWorkerSelector(map[string]string{"tier": "not valid!"}))),
field.ErrorList{field.Invalid(field.NewPath("actor", "worker_selector", "match_labels").Key("tier"), "not valid!", "").WithOrigin("format=k8s-label-value")},
}, {
"valid actor.source_snapshot_tag",
validReq(validActor(withSourceSnapshotTag("as", "tag"))),
"valid actor.source_tag",
validReq(validActor(withSourceTag("as", "tag"))),
nil,
}, {
"missing actor.source_snapshot_tag.atespace",
validReq(validActor(withSourceSnapshotTag("", "tag"))),
field.ErrorList{field.Required(field.NewPath("actor", "source_snapshot_tag", "atespace"), "")},
"missing actor.source_tag.atespace",
validReq(validActor(withSourceTag("", "tag"))),
field.ErrorList{field.Required(field.NewPath("actor", "source_tag", "atespace"), "")},
}, {
"invalid actor.source_snapshot_tag.atespace",
validReq(validActor(withSourceSnapshotTag("invalid value", "tag"))),
field.ErrorList{field.Invalid(field.NewPath("actor", "source_snapshot_tag", "atespace"), nil, "").WithOrigin("format=k8s-short-name")},
"invalid actor.source_tag.atespace",
validReq(validActor(withSourceTag("invalid value", "tag"))),
field.ErrorList{field.Invalid(field.NewPath("actor", "source_tag", "atespace"), nil, "").WithOrigin("format=k8s-short-name")},
}, {
"missing actor.source_snapshot_tag.name",
validReq(validActor(withSourceSnapshotTag("as", ""))),
field.ErrorList{field.Required(field.NewPath("actor", "source_snapshot_tag", "name"), "")},
"missing actor.source_tag.name",
validReq(validActor(withSourceTag("as", ""))),
field.ErrorList{field.Required(field.NewPath("actor", "source_tag", "name"), "")},
}, {
"invalid actor.source_snapshot_tag.name",
validReq(validActor(withSourceSnapshotTag("as", "invalid value"))),
field.ErrorList{field.Invalid(field.NewPath("actor", "source_snapshot_tag", "name"), nil, "").WithOrigin("format=k8s-short-name")},
"invalid actor.source_tag.name",
validReq(validActor(withSourceTag("as", "invalid value"))),
field.ErrorList{field.Invalid(field.NewPath("actor", "source_tag", "name"), nil, "").WithOrigin("format=k8s-short-name")},
}}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
@@ -182,7 +182,7 @@ func TestValidateActorUpdate(t *testing.T) {
withMetadata := withActorMetadata
withWorkerSelector := withActorWorkerSelector
withActorTemplate := withActorActorTemplate
withSourceSnapshotTag := withActorSourceSnapshotTag
withSourceTag := withActorSourceTag
withWorkerAssignment := withActorWorkerAssignment
tests := []struct {
@@ -237,20 +237,20 @@ func TestValidateActorUpdate(t *testing.T) {
validOutput(func(a *ateapipb.Actor) { a.ActorTemplate = nil }),
field.ErrorList{field.Required(field.NewPath("actor_template"), "")},
}, {
"add actor.source_snapshot_tag",
"add actor.source_tag",
validInput(),
validOutput(withSourceSnapshotTag("as", "nm")),
field.ErrorList{field.Invalid(field.NewPath("source_snapshot_tag"), nil, "").WithOrigin("immutable")},
validOutput(withSourceTag("as", "nm")),
field.ErrorList{field.Invalid(field.NewPath("source_tag"), nil, "").WithOrigin("immutable")},
}, {
"clear actor.source_snapshot_tag",
validInput(withSourceSnapshotTag("as", "nm")),
validOutput(func(a *ateapipb.Actor) { a.SourceSnapshotTag = nil }),
field.ErrorList{field.Invalid(field.NewPath("source_snapshot_tag"), nil, "").WithOrigin("immutable")},
"clear actor.source_tag",
validInput(withSourceTag("as", "nm")),
validOutput(func(a *ateapipb.Actor) { a.SourceTag = nil }),
field.ErrorList{field.Invalid(field.NewPath("source_tag"), nil, "").WithOrigin("immutable")},
}, {
"change actor.source_snapshot_tag",
validInput(withSourceSnapshotTag("as1", "nm1")),
validOutput(withSourceSnapshotTag("as2", "nm2")),
field.ErrorList{field.Invalid(field.NewPath("source_snapshot_tag"), nil, "").WithOrigin("immutable")},
"change actor.source_tag",
validInput(withSourceTag("as1", "nm1")),
validOutput(withSourceTag("as2", "nm2")),
field.ErrorList{field.Invalid(field.NewPath("source_tag"), nil, "").WithOrigin("immutable")},
}, {
"set valid worker_selector",
validInput(),
@@ -287,20 +287,20 @@ func TestValidateActorUpdate(t *testing.T) {
validOutput(withWorkerSelector(selectorLabelsOfSize(11))),
field.ErrorList{field.TooMany(field.NewPath("worker_selector", "match_labels"), 11, 10).WithOrigin("maxProperties")},
}, {
"add actor.source_snapshot_tag",
"add actor.source_tag",
validInput(),
validOutput(withSourceSnapshotTag("as", "nm")),
field.ErrorList{field.Invalid(field.NewPath("source_snapshot_tag"), nil, "").WithOrigin("immutable")},
validOutput(withSourceTag("as", "nm")),
field.ErrorList{field.Invalid(field.NewPath("source_tag"), nil, "").WithOrigin("immutable")},
}, {
"clear actor.source_snapshot_tag",
validInput(withSourceSnapshotTag("as", "nm")),
validOutput(func(a *ateapipb.Actor) { a.SourceSnapshotTag = nil }),
field.ErrorList{field.Invalid(field.NewPath("source_snapshot_tag"), nil, "").WithOrigin("immutable")},
"clear actor.source_tag",
validInput(withSourceTag("as", "nm")),
validOutput(func(a *ateapipb.Actor) { a.SourceTag = nil }),
field.ErrorList{field.Invalid(field.NewPath("source_tag"), nil, "").WithOrigin("immutable")},
}, {
"change actor.source_snapshot_tag",
validInput(withSourceSnapshotTag("as1", "nm1")),
validOutput(withSourceSnapshotTag("as2", "nm2")),
field.ErrorList{field.Invalid(field.NewPath("source_snapshot_tag"), nil, "").WithOrigin("immutable")},
"change actor.source_tag",
validInput(withSourceTag("as1", "nm1")),
validOutput(withSourceTag("as2", "nm2")),
field.ErrorList{field.Invalid(field.NewPath("source_tag"), nil, "").WithOrigin("immutable")},
}, {
"unspecified actor.status",
validInput(withStatus()),
@@ -748,16 +748,16 @@ func TestUpdateActor(t *testing.T) {
want: &ateapipb.Actor{},
},
{
name: "SourceSnapshotTag immutable field is kept",
stored: &ateapipb.Actor{SourceSnapshotTag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tag1"}},
name: "SourceTag immutable field is kept",
stored: &ateapipb.Actor{SourceTag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tag1"}},
req: &ateapipb.Actor{
ActorTemplate: &ateapipb.ObjectRef{Atespace: templateNS, Name: templateName},
SourceSnapshotTag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tag1"},
WorkerSelector: &ateapipb.Selector{MatchLabels: map[string]string{"tier": "paid"}},
ActorTemplate: &ateapipb.ObjectRef{Atespace: templateNS, Name: templateName},
SourceTag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tag1"},
WorkerSelector: &ateapipb.Selector{MatchLabels: map[string]string{"tier": "paid"}},
},
want: &ateapipb.Actor{
SourceSnapshotTag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tag1"},
WorkerSelector: &ateapipb.Selector{MatchLabels: map[string]string{"tier": "paid"}},
SourceTag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tag1"},
WorkerSelector: &ateapipb.Selector{MatchLabels: map[string]string{"tier": "paid"}},
},
},
{
@@ -771,19 +771,19 @@ func TestUpdateActor(t *testing.T) {
},
{
name: "an omitted immutable field is rejected",
stored: &ateapipb.Actor{SourceSnapshotTag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tag1"}},
stored: &ateapipb.Actor{SourceTag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tag1"}},
req: &ateapipb.Actor{
ActorTemplate: &ateapipb.ObjectRef{Atespace: templateNS, Name: templateName},
// Omitted SourceSnapshotTag
// Omitted SourceTag
},
wantCode: codes.InvalidArgument,
},
{
name: "an immutable field the request rewrites is rejected",
stored: &ateapipb.Actor{SourceSnapshotTag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tag1"}},
stored: &ateapipb.Actor{SourceTag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tag1"}},
req: &ateapipb.Actor{
ActorTemplate: &ateapipb.ObjectRef{Atespace: "attacker-ns", Name: "attacker-tmpl"},
SourceSnapshotTag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tag2"},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "attacker-ns", Name: "attacker-tmpl"},
SourceTag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tag2"},
},
wantCode: codes.InvalidArgument,
},
@@ -1254,10 +1254,10 @@ func withActorActorTemplate(atespace, name string) func(*ateapipb.Actor) {
return func(a *ateapipb.Actor) { a.ActorTemplate = &ateapipb.ObjectRef{Atespace: atespace, Name: name} }
}
// withActorSourceSnapshotTag returns a modifier func (see validActor) which sets
// the actor's source_snapshot_tag to a valid value.
func withActorSourceSnapshotTag(atespace, name string) func(*ateapipb.Actor) {
return func(a *ateapipb.Actor) { a.SourceSnapshotTag = &ateapipb.ObjectRef{Atespace: atespace, Name: name} }
// withActorSourceTag returns a modifier func (see validActor) which sets
// the actor's source_tag to a valid value.
func withActorSourceTag(atespace, name string) func(*ateapipb.Actor) {
return func(a *ateapipb.Actor) { a.SourceTag = &ateapipb.ObjectRef{Atespace: atespace, Name: name} }
}
// withActorWorkerAssignment returns a modifier func (see validActor) which sets
@@ -282,16 +282,16 @@ func TestCreateActor_RejectsDifferentTemplateForDataSnapshot(t *testing.T) {
tmpl := createTemplate(t, tc, ns)
createTemplateWithSelector(t, tc, "tmpl2", nil)
seedActorSnapshotTag(t, tc, "data-source", "data-snapshot", func(tag *ateapipb.ActorSnapshotTag) {
seedTag(t, tc, "data-source", "data-snapshot", func(tag *ateapipb.Tag) {
tag.Status.Snapshot.ContentScope = ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA
tag.Status.ActorTemplateUid = tmpl.GetMetadata().GetUid()
})
_, err := tc.service.CreateActor(context.Background(), &ateapipb.CreateActorRequest{
Actor: &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: "clone"},
ActorTemplate: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tmpl2"},
SourceSnapshotTag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "data-snapshot"},
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: "clone"},
ActorTemplate: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tmpl2"},
SourceTag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "data-snapshot"},
},
})
if status.Code(err) != codes.FailedPrecondition {
@@ -326,16 +326,16 @@ func TestCreateActor_RejectsSnapshotWithExternalVolumes(t *testing.T) {
if err != nil {
t.Fatalf("Create ActorTemplate: %v", err)
}
seedActorSnapshotTag(t, tc, "external-volume-source", "external-volume-snapshot", func(tag *ateapipb.ActorSnapshotTag) {
seedTag(t, tc, "external-volume-source", "external-volume-snapshot", func(tag *ateapipb.Tag) {
tag.Status.ActorTemplateUid = template.GetMetadata().GetUid()
})
tagRef := &ateapipb.ObjectRef{Atespace: testAtespace, Name: "external-volume-snapshot"}
_, err = tc.service.CreateActor(context.Background(), &ateapipb.CreateActorRequest{
Actor: &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: "clone"},
ActorTemplate: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tmpl1"},
SourceSnapshotTag: tagRef,
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: "clone"},
ActorTemplate: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tmpl1"},
SourceTag: tagRef,
},
})
if status.Code(err) != codes.FailedPrecondition {
@@ -343,11 +343,11 @@ func TestCreateActor_RejectsSnapshotWithExternalVolumes(t *testing.T) {
}
}
// TestCreateActor_PendingSnapshotTag verifies an Actor cannot be created from a
// TestCreateActor_PendingTag verifies an Actor cannot be created from a
// tag whose create never finished: the tag names a copy that may be partial, so
// it only becomes a source once the create completes.
func TestCreateActor_PendingSnapshotTag(t *testing.T) {
ns := namespaceForTest("ns-pending-snapshot-tag")
func TestCreateActor_PendingTag(t *testing.T) {
ns := namespaceForTest("ns-pending-tag")
tc := setupTest(t, ns)
defer tc.cleanup()
ctx := context.Background()
@@ -355,7 +355,7 @@ func TestCreateActor_PendingSnapshotTag(t *testing.T) {
// Simulates a tag creation that failed in while writing the snapshot to
// external storage.
pending := seedActorSnapshotTag(t, tc, "pending-source", "pending", func(tag *ateapipb.ActorSnapshotTag) {
pending := seedTag(t, tc, "pending-source", "pending", func(tag *ateapipb.Tag) {
tag.Status.InProgressSnapshotUri = tag.GetStatus().GetSnapshot().GetSnapshotUri()
tag.Status.Snapshot = nil
tag.Status.ActorTemplateUid = tmpl.GetMetadata().GetUid()
@@ -364,17 +364,17 @@ func TestCreateActor_PendingSnapshotTag(t *testing.T) {
// seeding an actor from the pending tag must fail.
_, err := tc.client.CreateActor(ctx, &ateapipb.CreateActorRequest{Actor: &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: "clone"},
ActorTemplate: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tmpl1"},
SourceSnapshotTag: tagRef,
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: "clone"},
ActorTemplate: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tmpl1"},
SourceTag: tagRef,
}})
assertGrpcError(t, err, codes.FailedPrecondition, "source ActorSnapshotTag is still being created or failed creation")
assertGrpcError(t, err, codes.FailedPrecondition, "source Tag is still being created or failed creation")
// Finishing the tag creation, so now the tag is qualified to be a tag source.
snapshotURI := pending.GetStatus().GetInProgressSnapshotUri()
if _, err := tc.persistence.UpdateActorSnapshotTag(ctx,
resources.ActorSnapshotTagRefFromActorSnapshotTag(pending), store.PreconditionFrom(pending),
func(toUpdate *ateapipb.ActorSnapshotTag) error {
if _, err := tc.persistence.UpdateTag(ctx,
resources.TagRefFromTag(pending), store.PreconditionFrom(pending),
func(toUpdate *ateapipb.Tag) error {
toUpdate.Status.Snapshot = &ateapipb.ExternalSnapshot{
SnapshotUri: snapshotURI,
ContentScope: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL,
@@ -387,9 +387,9 @@ func TestCreateActor_PendingSnapshotTag(t *testing.T) {
// The tag was finalized. Now, actor creation should succeed.
clone, err := tc.client.CreateActor(ctx, &ateapipb.CreateActorRequest{Actor: &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: "clone"},
ActorTemplate: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tmpl1"},
SourceSnapshotTag: tagRef,
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: "clone"},
ActorTemplate: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tmpl1"},
SourceTag: tagRef,
}})
if err != nil {
t.Fatalf("CreateActor from the finished tag failed: %v", err)
@@ -1348,8 +1348,8 @@ func TestValidation_Actor(t *testing.T) {
assertGrpcError(t, err, codes.InvalidArgument, "invalid page_token")
})
t.Run("ListActorSnapshotTags invalid token", func(t *testing.T) {
_, err := tc.client.ListActorSnapshotTags(context.Background(), &ateapipb.ListActorSnapshotTagsRequest{PageToken: "%%%"})
t.Run("ListTags invalid token", func(t *testing.T) {
_, err := tc.client.ListTags(context.Background(), &ateapipb.ListTagsRequest{PageToken: "%%%"})
assertGrpcError(t, err, codes.InvalidArgument, "invalid page_token")
})
}
@@ -2185,15 +2185,15 @@ func TestSuspendActor(t *testing.T) {
// which here is the one the suspend above left behind.
const tagName = "before-upgrade"
tagRef := &ateapipb.ObjectRef{Atespace: testAtespace, Name: tagName}
tagged, err := tc.client.CreateActorSnapshotTag(context.Background(), &ateapipb.CreateActorSnapshotTagRequest{
ActorSnapshotTag: &ateapipb.ActorSnapshotTag{
tagged, err := tc.client.CreateTag(context.Background(), &ateapipb.CreateTagRequest{
Tag: &ateapipb.Tag{
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: tagName},
Scope: ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE,
Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE,
SourceActor: &ateapipb.ObjectRef{Atespace: testAtespace, Name: name},
},
})
if err != nil {
t.Fatalf("CreateActorSnapshotTag failed: %v", err)
t.Fatalf("CreateTag failed: %v", err)
}
// The tag owns a copy of its own, so the Actor's later suspends and its
@@ -2205,26 +2205,26 @@ func TestSuspendActor(t *testing.T) {
assertSnapshotPresent(t, tc, snapshotURI)
assertSnapshotPresent(t, tc, tagSnapshotURI)
wantTag := &ateapipb.ActorSnapshotTag{
wantTag := &ateapipb.Tag{
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: tagName},
Scope: ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE,
Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE,
SourceActor: &ateapipb.ObjectRef{Atespace: testAtespace, Name: name},
Status: &ateapipb.ActorSnapshotTagStatus{
Status: &ateapipb.TagStatus{
Snapshot: &ateapipb.ExternalSnapshot{SnapshotUri: tagSnapshotURI, ContentScope: sourceActor.GetStatus().GetExternalSnapshot().GetContentScope()},
ActorTemplateUid: tmpl.GetMetadata().GetUid(),
SourceActorUid: sourceActor.GetMetadata().GetUid(),
},
}
stored, err := tc.client.GetActorSnapshotTag(context.Background(), &ateapipb.GetActorSnapshotTagRequest{ActorSnapshotTag: tagRef})
stored, err := tc.client.GetTag(context.Background(), &ateapipb.GetTagRequest{Tag: tagRef})
if err != nil {
t.Fatalf("GetActorSnapshotTag failed: %v", err)
t.Fatalf("GetTag failed: %v", err)
}
if diff := cmp.Diff(wantTag, stored, protocmp.Transform(), ignoreUID, ignoreVersion, ignoreTimestamps); diff != "" {
t.Errorf("stored tag mismatch (-want +got):\n%s", diff)
}
listed, err := tc.client.ListActorSnapshotTags(context.Background(), &ateapipb.ListActorSnapshotTagsRequest{Atespace: testAtespace, PageSize: 1})
if err != nil || len(listed.GetActorSnapshotTags()) != 1 {
t.Fatalf("ListActorSnapshotTags = (%v, %v), want one", listed, err)
listed, err := tc.client.ListTags(context.Background(), &ateapipb.ListTagsRequest{Atespace: testAtespace, PageSize: 1})
if err != nil || len(listed.GetTags()) != 1 {
t.Fatalf("ListTags = (%v, %v), want one", listed, err)
}
// A tag is born ATESPACE-scoped, so it cannot seed an Actor elsewhere until
@@ -2232,20 +2232,20 @@ func TestSuspendActor(t *testing.T) {
createAtespace(t, tc, "other")
crossAtespaceClone := &ateapipb.CreateActorRequest{
Actor: &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Atespace: "other", Name: "cross-atespace"},
ActorTemplate: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tmpl1"},
SourceSnapshotTag: tagRef,
Metadata: &ateapipb.ResourceMetadata{Atespace: "other", Name: "cross-atespace"},
ActorTemplate: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tmpl1"},
SourceTag: tagRef,
},
}
if _, err := tc.client.CreateActor(context.Background(), crossAtespaceClone); status.Code(err) != codes.FailedPrecondition {
t.Fatalf("cross-atespace CreateActor status = %v, want FailedPrecondition", status.Code(err))
}
tagged.Scope = ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED
updated, err := tc.client.UpdateActorSnapshotTag(context.Background(), &ateapipb.UpdateActorSnapshotTagRequest{
ActorSnapshotTag: tagged,
tagged.Scope = ateapipb.TagScope_TAG_SCOPE_PUBLISHED
updated, err := tc.client.UpdateTag(context.Background(), &ateapipb.UpdateTagRequest{
Tag: tagged,
})
if err != nil || updated.GetScope() != ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED {
t.Fatalf("UpdateActorSnapshotTag = (%v, %v), want published", updated, err)
if err != nil || updated.GetScope() != ateapipb.TagScope_TAG_SCOPE_PUBLISHED {
t.Fatalf("UpdateTag = (%v, %v), want published", updated, err)
}
if updated.GetStatus().GetSnapshot().GetSnapshotUri() != tagSnapshotURI {
t.Errorf("tag snapshot uri after publication = %q, want %q", updated.GetStatus().GetSnapshot().GetSnapshotUri(), tagSnapshotURI)
@@ -2259,9 +2259,9 @@ func TestSuspendActor(t *testing.T) {
// lifecycle from ever releasing it.
clone, err := tc.client.CreateActor(context.Background(), &ateapipb.CreateActorRequest{
Actor: &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: "clone"},
ActorTemplate: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tmpl1"},
SourceSnapshotTag: tagRef,
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: "clone"},
ActorTemplate: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tmpl1"},
SourceTag: tagRef,
},
})
if err != nil {
@@ -2273,8 +2273,8 @@ func TestSuspendActor(t *testing.T) {
if snapshotOwnedByActor(t, clone, tagSnapshotURI) {
t.Errorf("clone snapshot %s sits under the clone's own prefix, want it left under the tag's", tagSnapshotURI)
}
if !proto.Equal(clone.GetSourceSnapshotTag(), tagRef) {
t.Errorf("clone source snapshot tag = %v, want %v", clone.GetSourceSnapshotTag(), tagRef)
if !proto.Equal(clone.GetSourceTag(), tagRef) {
t.Errorf("clone source tag = %v, want %v", clone.GetSourceTag(), tagRef)
}
if _, err := tc.client.ResumeActor(context.Background(), &ateapipb.ResumeActorRequest{Actor: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "clone"}}); err != nil {
t.Fatalf("ResumeActor clone failed: %v", err)
@@ -2298,9 +2298,9 @@ func TestSuspendActor(t *testing.T) {
assertSnapshotPresent(t, tc, tagSnapshotURI)
assertSnapshotPresent(t, tc, cloneSnapshotURI)
// The untagged suspend created no tag.
listed, err = tc.client.ListActorSnapshotTags(context.Background(), &ateapipb.ListActorSnapshotTagsRequest{Atespace: testAtespace})
if err != nil || len(listed.GetActorSnapshotTags()) != 1 {
t.Fatalf("ListActorSnapshotTags after clone suspension = (%v, %v), want one", listed, err)
listed, err = tc.client.ListTags(context.Background(), &ateapipb.ListTagsRequest{Atespace: testAtespace})
if err != nil || len(listed.GetTags()) != 1 {
t.Fatalf("ListTags after clone suspension = (%v, %v), want one", listed, err)
}
getResp, err := tc.client.GetActor(context.Background(), &ateapipb.GetActorRequest{
@@ -2332,17 +2332,17 @@ func TestSuspendActor(t *testing.T) {
if _, err := tc.client.DeleteActor(context.Background(), &ateapipb.DeleteActorRequest{Actor: &ateapipb.ObjectRef{Atespace: testAtespace, Name: name}}); err != nil {
t.Fatalf("DeleteActor source failed: %v", err)
}
if _, err := tc.client.GetActorSnapshotTag(context.Background(), &ateapipb.GetActorSnapshotTagRequest{ActorSnapshotTag: tagRef}); err != nil {
t.Fatalf("source snapshot tag disappeared with source Actor: %v", err)
if _, err := tc.client.GetTag(context.Background(), &ateapipb.GetTagRequest{Tag: tagRef}); err != nil {
t.Fatalf("source tag disappeared with source Actor: %v", err)
}
// The Actor took only what it owned with it.
assertSnapshotCollected(t, tc, snapshotURI)
assertSnapshotPresent(t, tc, tagSnapshotURI)
if deleted, err := tc.client.DeleteActorSnapshotTag(context.Background(), &ateapipb.DeleteActorSnapshotTagRequest{ActorSnapshotTag: tagRef}); err != nil || deleted.GetMetadata().GetName() != tagRef.GetName() {
t.Fatalf("DeleteActorSnapshotTag = (%v, %v)", deleted, err)
if deleted, err := tc.client.DeleteTag(context.Background(), &ateapipb.DeleteTagRequest{Tag: tagRef}); err != nil || deleted.GetMetadata().GetName() != tagRef.GetName() {
t.Fatalf("DeleteTag = (%v, %v)", deleted, err)
}
if _, err := tc.client.GetActorSnapshotTag(context.Background(), &ateapipb.GetActorSnapshotTagRequest{ActorSnapshotTag: tagRef}); status.Code(err) != codes.NotFound {
if _, err := tc.client.GetTag(context.Background(), &ateapipb.GetTagRequest{Tag: tagRef}); status.Code(err) != codes.NotFound {
t.Fatalf("deleted tag status = %v, want NotFound", status.Code(err))
}
// Deleting the tag is what ends its snapshot's life. The clone that
@@ -2352,7 +2352,7 @@ func TestSuspendActor(t *testing.T) {
}
// TestResumeActor_RepointTemplateBeforeResume checks what an Actor cloned from
// a snapshot tag asks atelet to restore on its first resume.
// a tag asks atelet to restore on its first resume.
//
// The clone borrows a snapshot taken under the tag's template. Left on that
// template, it restores in Full: memory and filesystem come back as they were.
@@ -2416,14 +2416,14 @@ func TestResumeActor_RepointTemplateBeforeResume(t *testing.T) {
waitForWorkerAvailable(t, tc, worker)
const tagName = "before-upgrade"
if _, err := tc.client.CreateActorSnapshotTag(ctx, &ateapipb.CreateActorSnapshotTagRequest{
ActorSnapshotTag: &ateapipb.ActorSnapshotTag{
if _, err := tc.client.CreateTag(ctx, &ateapipb.CreateTagRequest{
Tag: &ateapipb.Tag{
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: tagName},
Scope: ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE,
Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE,
SourceActor: actorRef,
},
}); err != nil {
t.Fatalf("CreateActorSnapshotTag failed: %v", err)
t.Fatalf("CreateTag failed: %v", err)
}
const cloneActorName = "clone"
@@ -2431,7 +2431,7 @@ func TestResumeActor_RepointTemplateBeforeResume(t *testing.T) {
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: cloneActorName},
ActorTemplate: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "tmpl1"},
// Seeded from the tag.
SourceSnapshotTag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: tagName},
SourceTag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: tagName},
}})
if err != nil {
t.Fatalf("CreateActor from tag failed: %v", err)
@@ -28,11 +28,11 @@ import (
"google.golang.org/protobuf/testing/protocmp"
)
// seedActorSnapshotTag seeds a suspended actor holding an external snapshot plus
// a finished tag over a copy of it: the state CreateActorSnapshotTag leaves
// seedTag seeds a suspended actor holding an external snapshot plus
// a finished tag over a copy of it: the state CreateTag leaves
// behind. actorName must be unique within the test, since a tag records the
// actor it was taken from; opts adjust the tag before it is written.
func seedActorSnapshotTag(t *testing.T, tc *testContext, actorName, tagName string, opts ...func(*ateapipb.ActorSnapshotTag)) *ateapipb.ActorSnapshotTag {
func seedTag(t *testing.T, tc *testContext, actorName, tagName string, opts ...func(*ateapipb.Tag)) *ateapipb.Tag {
t.Helper()
ctx := context.Background()
actor := storetest.MustCreateActor(t, ctx, tc.persistence, &ateapipb.Actor{
@@ -58,11 +58,11 @@ func seedActorSnapshotTag(t *testing.T, tc *testContext, actorName, tagName stri
if err != nil {
t.Fatalf("NewTagSnapshotURI: %v", err)
}
tag := &ateapipb.ActorSnapshotTag{
tag := &ateapipb.Tag{
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: tagName},
Scope: ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE,
Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE,
SourceActor: resources.ActorRefFromActor(actor).ToObjectRef(),
Status: &ateapipb.ActorSnapshotTagStatus{
Status: &ateapipb.TagStatus{
Snapshot: &ateapipb.ExternalSnapshot{SnapshotUri: tagSnapshotURI.String(), ContentScope: actor.GetStatus().GetExternalSnapshot().GetContentScope()},
SourceActorUid: actor.GetMetadata().GetUid(),
},
@@ -70,14 +70,14 @@ func seedActorSnapshotTag(t *testing.T, tc *testContext, actorName, tagName stri
for _, opt := range opts {
opt(tag)
}
return storetest.MustCreateActorSnapshotTag(t, ctx, tc.persistence, tag)
return storetest.MustCreateTag(t, ctx, tc.persistence, tag)
}
// TestCreateActorSnapshotTag_ReusedTagName tests that a tag does not move
// TestCreateTag_ReusedTagName tests that a tag does not move
// between external snapshots. A tag name that is already taken must be reported
// as taken, and the objects the existing tag names must be exactly what its own
// creator wrote, because the rejected create reserves the name before it copies anything.
func TestCreateActorSnapshotTag_ReusedTagName(t *testing.T) {
func TestCreateTag_ReusedTagName(t *testing.T) {
ns := namespaceForTest("ns-tag-reused-name")
tc := setupTest(t, ns)
defer tc.cleanup()
@@ -88,15 +88,15 @@ func TestCreateActorSnapshotTag_ReusedTagName(t *testing.T) {
// Actor A is suspended and tagged, the ordinary way.
const tagName = "before-upgrade"
actorFirstSnapshotURI := suspendActorForTest(t, tc, workerName, "actor-a")
first, err := tc.client.CreateActorSnapshotTag(context.Background(), &ateapipb.CreateActorSnapshotTagRequest{
ActorSnapshotTag: &ateapipb.ActorSnapshotTag{
first, err := tc.client.CreateTag(context.Background(), &ateapipb.CreateTagRequest{
Tag: &ateapipb.Tag{
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: tagName},
Scope: ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE,
Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE,
SourceActor: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "actor-a"},
},
})
if err != nil {
t.Fatalf("CreateActorSnapshotTag(actor-a) failed: %v", err)
t.Fatalf("CreateTag(actor-a) failed: %v", err)
}
tagSnapshotURI := first.GetStatus().GetSnapshot().GetSnapshotUri()
// Tag owns a snapshot. So it must not be empty.
@@ -111,22 +111,22 @@ func TestCreateActorSnapshotTag_ReusedTagName(t *testing.T) {
if secondSnapshotURI == actorFirstSnapshotURI {
t.Fatalf("both actors suspended to %s, want distinct external snapshots", secondSnapshotURI)
}
_, err = tc.client.CreateActorSnapshotTag(context.Background(), &ateapipb.CreateActorSnapshotTagRequest{
ActorSnapshotTag: &ateapipb.ActorSnapshotTag{
_, err = tc.client.CreateTag(context.Background(), &ateapipb.CreateTagRequest{
Tag: &ateapipb.Tag{
// Same tagName that was written before.
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: tagName},
Scope: ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE,
Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE,
SourceActor: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "actor-b"},
},
})
assertGrpcError(t, err, codes.AlreadyExists, fmt.Sprintf("ActorSnapshotTag %s/%s already exists; delete it and create it again to retry", testAtespace, tagName))
assertGrpcError(t, err, codes.AlreadyExists, fmt.Sprintf("Tag %s/%s already exists; delete it and create it again to retry", testAtespace, tagName))
// The row still contains actor A's tag
stored, err := tc.client.GetActorSnapshotTag(context.Background(), &ateapipb.GetActorSnapshotTagRequest{
ActorSnapshotTag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: tagName},
stored, err := tc.client.GetTag(context.Background(), &ateapipb.GetTagRequest{
Tag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: tagName},
})
if err != nil {
t.Fatalf("GetActorSnapshotTag failed: %v", err)
t.Fatalf("GetTag failed: %v", err)
}
if diff := cmp.Diff(first, stored, protocmp.Transform()); diff != "" {
t.Errorf("the rejected create modified the tag (-before +after):\n%s", diff)
@@ -139,14 +139,14 @@ func TestCreateActorSnapshotTag_ReusedTagName(t *testing.T) {
assertSnapshotPresent(t, tc, secondSnapshotURI)
// The collision is not a dead end: a free name works.
if _, err := tc.client.CreateActorSnapshotTag(context.Background(), &ateapipb.CreateActorSnapshotTagRequest{
ActorSnapshotTag: &ateapipb.ActorSnapshotTag{
if _, err := tc.client.CreateTag(context.Background(), &ateapipb.CreateTagRequest{
Tag: &ateapipb.Tag{
Metadata: &ateapipb.ResourceMetadata{Atespace: testAtespace, Name: "after-upgrade"},
Scope: ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE,
Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE,
SourceActor: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "actor-b"},
},
}); err != nil {
t.Fatalf("CreateActorSnapshotTag under a free name failed: %v", err)
t.Fatalf("CreateTag under a free name failed: %v", err)
}
}
@@ -183,9 +183,9 @@ func suspendActorForTest(t *testing.T, tc *testContext, workerName, name string)
return uri
}
// TestUpdateActorSnapshotTag_Preconditions verifies the required version and uid
// TestUpdateTag_Preconditions verifies the required version and uid
// guards carried in the tag's metadata.
func TestUpdateActorSnapshotTag_Preconditions(t *testing.T) {
func TestUpdateTag_Preconditions(t *testing.T) {
ns := namespaceForTest("ns-update-tag-preconditions")
tc := setupTest(t, ns)
defer tc.cleanup()
@@ -199,10 +199,10 @@ func TestUpdateActorSnapshotTag_Preconditions(t *testing.T) {
// observable write that bumps the version.
// The tag under test is seeded from actor-2; source_actor is immutable, so
// every update has to echo it back.
update := func(meta *ateapipb.ResourceMetadata, scope ateapipb.ActorSnapshotTagScope) (*ateapipb.ActorSnapshotTag, error) {
update := func(meta *ateapipb.ResourceMetadata, scope ateapipb.TagScope) (*ateapipb.Tag, error) {
meta.Atespace, meta.Name = testAtespace, tagName
return tc.client.UpdateActorSnapshotTag(context.Background(), &ateapipb.UpdateActorSnapshotTagRequest{
ActorSnapshotTag: &ateapipb.ActorSnapshotTag{
return tc.client.UpdateTag(context.Background(), &ateapipb.UpdateTagRequest{
Tag: &ateapipb.Tag{
Metadata: meta,
Scope: scope,
SourceActor: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "actor-2"},
@@ -212,54 +212,54 @@ func TestUpdateActorSnapshotTag_Preconditions(t *testing.T) {
// Delete and recreate the same atespace/name tag, so the first lifecycle's
// uid becomes stale.
staleUID := seedActorSnapshotTag(t, tc, "actor-1", tagName).GetMetadata().GetUid()
if _, err := tc.client.DeleteActorSnapshotTag(ctx, &ateapipb.DeleteActorSnapshotTagRequest{
ActorSnapshotTag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: tagName},
staleUID := seedTag(t, tc, "actor-1", tagName).GetMetadata().GetUid()
if _, err := tc.client.DeleteTag(ctx, &ateapipb.DeleteTagRequest{
Tag: &ateapipb.ObjectRef{Atespace: testAtespace, Name: tagName},
}); err != nil {
t.Fatalf("DeleteActorSnapshotTag failed: %v", err)
t.Fatalf("DeleteTag failed: %v", err)
}
tagged := seedActorSnapshotTag(t, tc, "actor-2", tagName)
tagged := seedTag(t, tc, "actor-2", tagName)
staleVersion := tagged.GetMetadata().GetVersion()
uid := tagged.GetMetadata().GetUid()
if uid == staleUID {
t.Fatalf("recreated tag reused uid %s, want a fresh one", uid)
}
// No preconditions
_, err := update(&ateapipb.ResourceMetadata{}, ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED)
assertGrpcError(t, err, codes.InvalidArgument, fmt.Sprintf("while updating actor snapshot tag %s/%s: persistence: precondition required: uid", testAtespace, tagName))
_, err := update(&ateapipb.ResourceMetadata{}, ateapipb.TagScope_TAG_SCOPE_PUBLISHED)
assertGrpcError(t, err, codes.InvalidArgument, fmt.Sprintf("while updating tag %s/%s: persistence: precondition required: uid", testAtespace, tagName))
// The uid from the deleted lifecycle must be rejected, even though the
// atespace/name it was observed under still resolves and the version it
// guards on matches the recreated tag's.
_, err = update(&ateapipb.ResourceMetadata{Uid: staleUID, Version: staleVersion}, ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED)
assertGrpcError(t, err, codes.Aborted, fmt.Sprintf("ActorSnapshotTag %s/%s not found with uid %s", testAtespace, tagName, staleUID))
_, err = update(&ateapipb.ResourceMetadata{Uid: staleUID, Version: staleVersion}, ateapipb.TagScope_TAG_SCOPE_PUBLISHED)
assertGrpcError(t, err, codes.Aborted, fmt.Sprintf("Tag %s/%s not found with uid %s", testAtespace, tagName, staleUID))
// Both guards matching the observed state: the update goes through, and
// moves the tag past the version observed above.
first, err := update(&ateapipb.ResourceMetadata{Uid: uid, Version: staleVersion}, ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED)
first, err := update(&ateapipb.ResourceMetadata{Uid: uid, Version: staleVersion}, ateapipb.TagScope_TAG_SCOPE_PUBLISHED)
if err != nil {
t.Fatalf("UpdateActorSnapshotTag(matching guards) failed: %v", err)
t.Fatalf("UpdateTag(matching guards) failed: %v", err)
}
currentVersion := first.GetMetadata().GetVersion()
if currentVersion <= staleVersion {
t.Fatalf("version = %d, want greater than %d after an update", currentVersion, staleVersion)
}
if got, want := first.GetScope(), ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED; got != want {
if got, want := first.GetScope(), ateapipb.TagScope_TAG_SCOPE_PUBLISHED; got != want {
t.Errorf("scope = %v, want %v", got, want)
}
// The version observed before that write is now stale: rejected rather than
// silently overwriting the concurrent change.
_, err = update(&ateapipb.ResourceMetadata{Uid: uid, Version: staleVersion}, ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE)
_, err = update(&ateapipb.ResourceMetadata{Uid: uid, Version: staleVersion}, ateapipb.TagScope_TAG_SCOPE_ATESPACE)
assertGrpcError(t, err, codes.Aborted, "concurrent update conflict, please retry")
// Guarding on the version the last write produced succeeds again.
updated, err := update(&ateapipb.ResourceMetadata{Uid: uid, Version: currentVersion}, ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE)
updated, err := update(&ateapipb.ResourceMetadata{Uid: uid, Version: currentVersion}, ateapipb.TagScope_TAG_SCOPE_ATESPACE)
if err != nil {
t.Fatalf("UpdateActorSnapshotTag(matching guards) failed: %v", err)
t.Fatalf("UpdateTag(matching guards) failed: %v", err)
}
if got, want := updated.GetScope(), ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE; got != want {
if got, want := updated.GetScope(), ateapipb.TagScope_TAG_SCOPE_ATESPACE; got != want {
t.Errorf("scope = %v, want %v", got, want)
}
if updated.GetMetadata().GetVersion() <= currentVersion {
@@ -267,17 +267,17 @@ func TestUpdateActorSnapshotTag_Preconditions(t *testing.T) {
}
// The guard the client just satisfied is now stale in turn.
_, err = update(&ateapipb.ResourceMetadata{Uid: uid, Version: currentVersion}, ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED)
_, err = update(&ateapipb.ResourceMetadata{Uid: uid, Version: currentVersion}, ateapipb.TagScope_TAG_SCOPE_PUBLISHED)
assertGrpcError(t, err, codes.Aborted, "concurrent update conflict, please retry")
}
func TestUpdateActorSnapshotTag_NotFound(t *testing.T) {
func TestUpdateTag_NotFound(t *testing.T) {
ns := namespaceForTest("ns-update-tag-notfound")
tc := setupTest(t, ns)
defer tc.cleanup()
_, err := tc.client.UpdateActorSnapshotTag(context.Background(), &ateapipb.UpdateActorSnapshotTagRequest{
ActorSnapshotTag: &ateapipb.ActorSnapshotTag{
_, err := tc.client.UpdateTag(context.Background(), &ateapipb.UpdateTagRequest{
Tag: &ateapipb.Tag{
Metadata: &ateapipb.ResourceMetadata{
Atespace: testAtespace,
Name: "does-not-exist",
@@ -285,9 +285,9 @@ func TestUpdateActorSnapshotTag_NotFound(t *testing.T) {
Uid: "9a2b1c3d-4e5f-6a7b-8c9d-0e1f2a3b4c5d",
Version: 1,
},
Scope: ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED,
Scope: ateapipb.TagScope_TAG_SCOPE_PUBLISHED,
SourceActor: &ateapipb.ObjectRef{Atespace: testAtespace, Name: "actor-1"},
},
})
assertGrpcError(t, err, codes.NotFound, "ActorSnapshotTag test-atespace/does-not-exist not found")
assertGrpcError(t, err, codes.NotFound, "Tag test-atespace/does-not-exist not found")
}
+4 -4
View File
@@ -105,10 +105,10 @@ type serviceStore interface {
GetEgressPolicy(ctx context.Context, actorRef resources.ActorRef) (*ateapipb.EgressPolicy, error)
UpdateEgressPolicy(ctx context.Context, actorRef resources.ActorRef, precondition store.Precondition, mutate func(*ateapipb.EgressPolicy) error) (*ateapipb.EgressPolicy, error)
DeleteEgressPolicy(ctx context.Context, actorRef resources.ActorRef) (*ateapipb.EgressPolicy, error)
GetActorSnapshotTag(ctx context.Context, tagRef resources.ActorSnapshotTagRef) (*ateapipb.ActorSnapshotTag, error)
ListActorSnapshotTags(ctx context.Context, atespace string, opts store.ListOptions) (store.ListResponse[*ateapipb.ActorSnapshotTag], error)
UpdateActorSnapshotTag(ctx context.Context, tagRef resources.ActorSnapshotTagRef, precondition store.Precondition, mutate func(toUpdate *ateapipb.ActorSnapshotTag) error) (*ateapipb.ActorSnapshotTag, error)
DeleteActorSnapshotTag(ctx context.Context, tagRef resources.ActorSnapshotTagRef) (*ateapipb.ActorSnapshotTag, error)
GetTag(ctx context.Context, tagRef resources.TagRef) (*ateapipb.Tag, error)
ListTags(ctx context.Context, atespace string, opts store.ListOptions) (store.ListResponse[*ateapipb.Tag], error)
UpdateTag(ctx context.Context, tagRef resources.TagRef, precondition store.Precondition, mutate func(toUpdate *ateapipb.Tag) error) (*ateapipb.Tag, error)
DeleteTag(ctx context.Context, tagRef resources.TagRef) (*ateapipb.Tag, error)
CreateAtespace(ctx context.Context, atespace *ateapipb.Atespace) (*ateapipb.Atespace, error)
GetAtespace(ctx context.Context, name string) (*ateapipb.Atespace, error)
ListAtespaces(ctx context.Context, opts store.ListOptions) (store.ListResponse[*ateapipb.Atespace], error)
@@ -31,25 +31,25 @@ import (
"k8s.io/apimachinery/pkg/util/validation/field"
)
// CreateActorSnapshotTag tags the external snapshot a suspended Actor holds,
// giving the tag its own copy of that snapshot so the Actor being suspended
// CreateTag tags the external snapshot a suspended Actor holds, giving the
// tag its own copy of that snapshot so the Actor being suspended
// again or deleted cannot collect it. The work is a workflow because it spans
// two transactions around an object copy; see TagActorSnapshot.
func (s *RPCService) CreateActorSnapshotTag(ctx context.Context, req *ateapipb.CreateActorSnapshotTagRequest) (*ateapipb.ActorSnapshotTag, error) {
func (s *RPCService) CreateTag(ctx context.Context, req *ateapipb.CreateTagRequest) (*ateapipb.Tag, error) {
// First scrub any fields that users are not allowed to set.
inTag := req.ActorSnapshotTag
inTag := req.Tag
if inTag != nil { // otherwise validation will flag it
scrubResourceMetadataForCreate(inTag.Metadata)
inTag.Status = nil
}
if errs := validateCreateActorSnapshotTagRequest(ctx, req); len(errs) > 0 {
if errs := validateCreateTagRequest(ctx, req); len(errs) > 0 {
return nil, toGRPCStatusError(errs)
}
actorRef := resources.ActorRefFromObjectRef(req.GetActorSnapshotTag().GetSourceActor())
actorRef := resources.ActorRefFromObjectRef(req.GetTag().GetSourceActor())
setSpanActorRefAttributes(ctx, actorRef)
tag, err := s.actorWorkflow.TagActorSnapshot(ctx, req.GetActorSnapshotTag())
tag, err := s.actorWorkflow.TagActorSnapshot(ctx, req.GetTag())
if err != nil {
if errors.Is(err, store.ErrNotFound) {
return nil, status.Errorf(codes.NotFound, "Actor %s not found", actorRef)
@@ -59,18 +59,18 @@ func (s *RPCService) CreateActorSnapshotTag(ctx context.Context, req *ateapipb.C
return tag, nil
}
func (s *ServiceImpl) CreateActorSnapshotTag(ctx context.Context, tag *ateapipb.ActorSnapshotTag) (*ateapipb.ActorSnapshotTag, error) {
func (s *ServiceImpl) CreateTag(ctx context.Context, tag *ateapipb.Tag) (*ateapipb.Tag, error) {
// TODO: implement this
return s.store.CreateActorSnapshotTag(ctx, tag)
return s.store.CreateTag(ctx, tag)
}
func validateCreateActorSnapshotTagRequest(ctx context.Context, req *ateapipb.CreateActorSnapshotTagRequest) field.ErrorList {
func validateCreateTagRequest(ctx context.Context, req *ateapipb.CreateTagRequest) field.ErrorList {
op := operation.Operation{Type: operation.Create}
return Validate_CreateActorSnapshotTagRequest(ctx, op, nil, req, nil)
return Validate_CreateTagRequest(ctx, op, nil, req, nil)
}
func ValidateCustom_CreateActorSnapshotTagRequest(_ context.Context, _ operation.Operation, p *field.Path, req, _ *ateapipb.CreateActorSnapshotTagRequest) field.ErrorList {
tag := req.GetActorSnapshotTag()
func ValidateCustom_CreateTagRequest(_ context.Context, _ operation.Operation, p *field.Path, req, _ *ateapipb.CreateTagRequest) field.ErrorList {
tag := req.GetTag()
sourceActorAtespace := tag.GetSourceActor().GetAtespace()
tagAtespace := tag.GetMetadata().GetAtespace()
if sourceActorAtespace == "" || tagAtespace == "" {
@@ -78,77 +78,77 @@ func ValidateCustom_CreateActorSnapshotTagRequest(_ context.Context, _ operation
}
if tagAtespace != sourceActorAtespace {
return field.ErrorList{
field.Invalid(p.Child("actor_snapshot_tag", "metadata", "atespace"), tagAtespace, "must match source_actor.atespace"),
field.Invalid(p.Child("tag", "metadata", "atespace"), tagAtespace, "must match source_actor.atespace"),
}
}
return nil
}
func (s *RPCService) GetActorSnapshotTag(ctx context.Context, req *ateapipb.GetActorSnapshotTagRequest) (*ateapipb.ActorSnapshotTag, error) {
if errs := validateGetActorSnapshotTagRequest(ctx, req); len(errs) > 0 {
func (s *RPCService) GetTag(ctx context.Context, req *ateapipb.GetTagRequest) (*ateapipb.Tag, error) {
if errs := validateGetTagRequest(ctx, req); len(errs) > 0 {
return nil, toGRPCStatusError(errs)
}
tag, err := s.impl.GetActorSnapshotTag(ctx, resources.ActorSnapshotTagRefFromObjectRef(req.GetActorSnapshotTag()))
tag, err := s.impl.GetTag(ctx, resources.TagRefFromObjectRef(req.GetTag()))
if errors.Is(err, store.ErrNotFound) {
return nil, status.Error(codes.NotFound, "ActorSnapshotTag not found")
return nil, status.Error(codes.NotFound, "Tag not found")
}
if err != nil {
return nil, fmt.Errorf("while getting actor snapshot tag: %w", err)
return nil, fmt.Errorf("while getting tag: %w", err)
}
return tag, nil
}
func (s *ServiceImpl) GetActorSnapshotTag(ctx context.Context, tagRef resources.ActorSnapshotTagRef) (*ateapipb.ActorSnapshotTag, error) {
func (s *ServiceImpl) GetTag(ctx context.Context, tagRef resources.TagRef) (*ateapipb.Tag, error) {
// TODO: implement this
return s.store.GetActorSnapshotTag(ctx, tagRef)
return s.store.GetTag(ctx, tagRef)
}
func validateGetActorSnapshotTagRequest(ctx context.Context, req *ateapipb.GetActorSnapshotTagRequest) field.ErrorList {
func validateGetTagRequest(ctx context.Context, req *ateapipb.GetTagRequest) field.ErrorList {
op := operation.Operation{Type: operation.Create}
return Validate_GetActorSnapshotTagRequest(ctx, op, nil, req, nil)
return Validate_GetTagRequest(ctx, op, nil, req, nil)
}
func (s *RPCService) ListActorSnapshotTags(ctx context.Context, req *ateapipb.ListActorSnapshotTagsRequest) (*ateapipb.ListActorSnapshotTagsResponse, error) {
if errs := validateListActorSnapshotTagsRequest(ctx, req); len(errs) > 0 {
func (s *RPCService) ListTags(ctx context.Context, req *ateapipb.ListTagsRequest) (*ateapipb.ListTagsResponse, error) {
if errs := validateListTagsRequest(ctx, req); len(errs) > 0 {
return nil, toGRPCStatusError(errs)
}
page, err := s.impl.ListActorSnapshotTags(ctx, req.GetAtespace(), store.ListOptions{PageSize: effectivePageSize(req.GetPageSize()), PageToken: req.GetPageToken()})
page, err := s.impl.ListTags(ctx, req.GetAtespace(), store.ListOptions{PageSize: effectivePageSize(req.GetPageSize()), PageToken: req.GetPageToken()})
if err != nil {
return nil, mapListError(fmt.Errorf("while listing actor snapshot tags: %w", err))
return nil, mapListError(fmt.Errorf("while listing tags: %w", err))
}
return &ateapipb.ListActorSnapshotTagsResponse{ActorSnapshotTags: page.Items, NextPageToken: page.NextPageToken}, nil
return &ateapipb.ListTagsResponse{Tags: page.Items, NextPageToken: page.NextPageToken}, nil
}
func (s *ServiceImpl) ListActorSnapshotTags(ctx context.Context, atespace string, opts store.ListOptions) (store.ListResponse[*ateapipb.ActorSnapshotTag], error) {
func (s *ServiceImpl) ListTags(ctx context.Context, atespace string, opts store.ListOptions) (store.ListResponse[*ateapipb.Tag], error) {
// TODO: implement this
return s.store.ListActorSnapshotTags(ctx, atespace, opts)
return s.store.ListTags(ctx, atespace, opts)
}
func validateListActorSnapshotTagsRequest(ctx context.Context, req *ateapipb.ListActorSnapshotTagsRequest) field.ErrorList {
func validateListTagsRequest(ctx context.Context, req *ateapipb.ListTagsRequest) field.ErrorList {
op := operation.Operation{Type: operation.Create}
return Validate_ListActorSnapshotTagsRequest(ctx, op, nil, req, nil)
return Validate_ListTagsRequest(ctx, op, nil, req, nil)
}
// errTagPending is what the update's mutate closure returns when the stored
// tag's create never finished, so the caller can tell it apart from a store
// failure and answer FAILED_PRECONDITION.
var errTagPending = errors.New("ActorSnapshotTag is still being created")
var errTagPending = errors.New("tag is still being created")
func (s *RPCService) UpdateActorSnapshotTag(ctx context.Context, req *ateapipb.UpdateActorSnapshotTagRequest) (*ateapipb.ActorSnapshotTag, error) {
func (s *RPCService) UpdateTag(ctx context.Context, req *ateapipb.UpdateTagRequest) (*ateapipb.Tag, error) {
// First scrub any fields that users are not allowed to set.
inTag := req.ActorSnapshotTag
inTag := req.Tag
if inTag != nil { // otherwise validation will flag it
scrubResourceMetadataForUpdate(inTag.Metadata)
inTag.Status = nil
}
if errs := validateUpdateActorSnapshotTagRequest(ctx, req); len(errs) > 0 {
if errs := validateUpdateTagRequest(ctx, req); len(errs) > 0 {
return nil, toGRPCStatusError(errs)
}
in := req.GetActorSnapshotTag()
tagRef := resources.ActorSnapshotTagRefFromActorSnapshotTag(in)
in := req.GetTag()
tagRef := resources.TagRefFromTag(in)
storedTag, err := s.impl.UpdateActorSnapshotTag(ctx, tagRef, store.PreconditionFrom(in), func(toUpdate *ateapipb.ActorSnapshotTag) error {
storedTag, err := s.impl.UpdateTag(ctx, tagRef, store.PreconditionFrom(in), func(toUpdate *ateapipb.Tag) error {
// A tag whose create never finished names a partial copy. Publishing it
// — or changing its scope at all — would hand out content that is still
// being written, or may never be.
@@ -172,30 +172,30 @@ func (s *RPCService) UpdateActorSnapshotTag(ctx context.Context, req *ateapipb.U
})
if err != nil {
if errors.Is(err, errTagPending) {
return nil, status.Errorf(codes.FailedPrecondition, "ActorSnapshotTag %s/%s is still being created", tagRef.Atespace, tagRef.Name)
return nil, status.Errorf(codes.FailedPrecondition, "Tag %s/%s is still being created", tagRef.Atespace, tagRef.Name)
}
if errors.Is(err, store.ErrImmutableField) {
return nil, status.Errorf(codes.InvalidArgument, "while updating actor snapshot tag %s/%s: %v", tagRef.Atespace, tagRef.Name, err)
return nil, status.Errorf(codes.InvalidArgument, "while updating tag %s/%s: %v", tagRef.Atespace, tagRef.Name, err)
}
if errors.Is(err, store.ErrVersionConflict) {
return nil, status.Error(codes.Aborted, "concurrent update conflict, please retry")
}
if errors.Is(err, store.ErrUIDConflict) {
return nil, status.Errorf(codes.Aborted, "ActorSnapshotTag %s/%s not found with uid %s", tagRef.Atespace, tagRef.Name, in.GetMetadata().GetUid())
return nil, status.Errorf(codes.Aborted, "Tag %s/%s not found with uid %s", tagRef.Atespace, tagRef.Name, in.GetMetadata().GetUid())
}
if errors.Is(err, store.ErrNotFound) {
return nil, status.Errorf(codes.NotFound, "ActorSnapshotTag %s/%s not found", tagRef.Atespace, tagRef.Name)
return nil, status.Errorf(codes.NotFound, "Tag %s/%s not found", tagRef.Atespace, tagRef.Name)
}
if errors.Is(err, store.ErrPreconditionRequired) {
return nil, status.Errorf(codes.InvalidArgument, "while updating actor snapshot tag %s/%s: %v", tagRef.Atespace, tagRef.Name, err)
return nil, status.Errorf(codes.InvalidArgument, "while updating tag %s/%s: %v", tagRef.Atespace, tagRef.Name, err)
}
return nil, fmt.Errorf("while updating actor snapshot tag: %w", err)
return nil, fmt.Errorf("while updating tag: %w", err)
}
return storedTag, nil
}
func (s *ServiceImpl) UpdateActorSnapshotTag(ctx context.Context, tagRef resources.ActorSnapshotTagRef, precondition store.Precondition, mutate func(toUpdate *ateapipb.ActorSnapshotTag) error) (*ateapipb.ActorSnapshotTag, error) {
return s.store.UpdateActorSnapshotTag(ctx, tagRef, precondition, func(toUpdate *ateapipb.ActorSnapshotTag) error {
func (s *ServiceImpl) UpdateTag(ctx context.Context, tagRef resources.TagRef, precondition store.Precondition, mutate func(toUpdate *ateapipb.Tag) error) (*ateapipb.Tag, error) {
return s.store.UpdateTag(ctx, tagRef, precondition, func(toUpdate *ateapipb.Tag) error {
// Apply the mutation function to the stored value.
oldVal := proto.CloneOf(toUpdate)
if err := mutate(toUpdate); err != nil {
@@ -205,29 +205,29 @@ func (s *ServiceImpl) UpdateActorSnapshotTag(ctx context.Context, tagRef resourc
// Validate the merged tag against the one it replaces. This is where
// the rules the request could not be checked against land: scope, and
// the immutability of metadata and source_actor.
if errs := validateActorSnapshotTagUpdate(ctx, field.NewPath("actor_snapshot_tag"), toUpdate, oldVal); len(errs) > 0 {
if errs := validateTagUpdate(ctx, field.NewPath("tag"), toUpdate, oldVal); len(errs) > 0 {
return toGRPCStatusError(errs)
}
return nil
})
}
func validateUpdateActorSnapshotTagRequest(ctx context.Context, req *ateapipb.UpdateActorSnapshotTagRequest) field.ErrorList {
func validateUpdateTagRequest(ctx context.Context, req *ateapipb.UpdateTagRequest) field.ErrorList {
// We model this as a create rather than an update because updates assume
// the existence of a "current" value, which we do not have yet. This is
// validating the request itself. The result will be validated later, after
// we have a current value to compare against.
op := operation.Operation{Type: operation.Create}
return Validate_UpdateActorSnapshotTagRequest(ctx, op, nil, req, nil)
return Validate_UpdateTagRequest(ctx, op, nil, req, nil)
}
func validateActorSnapshotTagUpdate(ctx context.Context, fldPath *field.Path, newVal, oldVal *ateapipb.ActorSnapshotTag) field.ErrorList {
func validateTagUpdate(ctx context.Context, fldPath *field.Path, newVal, oldVal *ateapipb.Tag) field.ErrorList {
op := operation.Operation{Type: operation.Update}
return Validate_ActorSnapshotTag(ctx, op, fldPath, newVal, oldVal)
return Validate_Tag(ctx, op, fldPath, newVal, oldVal)
}
// This exists only because nested subfield tags are not supported yet.
func ValidateCustom_UpdateActorSnapshotTagRequest_ActorSnapshotTag(ctx context.Context, op operation.Operation, fldPath *field.Path, tag, _ *ateapipb.ActorSnapshotTag) field.ErrorList {
func ValidateCustom_UpdateTagRequest_Tag(ctx context.Context, op operation.Operation, fldPath *field.Path, tag, _ *ateapipb.Tag) field.ErrorList {
if tag == nil || tag.Metadata == nil {
return nil // handled by DV
}
@@ -242,7 +242,7 @@ func ValidateCustom_UpdateActorSnapshotTagRequest_ActorSnapshotTag(ctx context.C
return errs
}
// DeleteActorSnapshotTag releases the external snapshot the tag owns and then
// DeleteTag releases the external snapshot the tag owns and then
// removes the row, in that order: the row is the only handle on that snapshot,
// so dropping it first would leak. A failure at any point fails the whole RPC;
// the client retries the same delete, which rediscovers the work from the row
@@ -255,38 +255,38 @@ func ValidateCustom_UpdateActorSnapshotTagRequest_ActorSnapshotTag(ctx context.C
// Note that this destroys the external snapshot: an Actor created from the tag
// and never suspended is still borrowing it and becomes unrecoverable. Do not
// delete a tag while clones of it exist.
func (s *RPCService) DeleteActorSnapshotTag(ctx context.Context, req *ateapipb.DeleteActorSnapshotTagRequest) (*ateapipb.ActorSnapshotTag, error) {
func (s *RPCService) DeleteTag(ctx context.Context, req *ateapipb.DeleteTagRequest) (*ateapipb.Tag, error) {
// TODO: mode delete orchestration to a workflow.
if errs := validateDeleteActorSnapshotTagRequest(ctx, req); len(errs) > 0 {
if errs := validateDeleteTagRequest(ctx, req); len(errs) > 0 {
return nil, toGRPCStatusError(errs)
}
tagRef := resources.ActorSnapshotTagRefFromObjectRef(req.GetActorSnapshotTag())
tagRef := resources.TagRefFromObjectRef(req.GetTag())
// Serializes against a create of the same tag, whose copy would otherwise
// keep writing into the prefix this is collecting.
ctx, lease, err := acquireActorSnapshotTagLease(ctx, s.impl, tagRef)
ctx, lease, err := acquireTagLease(ctx, s.impl, tagRef)
if err != nil {
return nil, err
}
defer lease.Close()
stored, err := s.impl.GetActorSnapshotTag(ctx, tagRef)
stored, err := s.impl.GetTag(ctx, tagRef)
if err != nil {
if errors.Is(err, store.ErrNotFound) {
return nil, status.Errorf(codes.NotFound, "ActorSnapshotTag %s/%s not found", tagRef.Atespace, tagRef.Name)
return nil, status.Errorf(codes.NotFound, "Tag %s/%s not found", tagRef.Atespace, tagRef.Name)
}
return nil, fmt.Errorf("while getting actor snapshot tag: %w", err)
return nil, fmt.Errorf("while getting tag: %w", err)
}
if err := s.releaseTagSnapshot(ctx, stored); err != nil {
return nil, err
}
tag, err := s.impl.DeleteActorSnapshotTag(ctx, tagRef)
tag, err := s.impl.DeleteTag(ctx, tagRef)
if errors.Is(err, store.ErrNotFound) {
return nil, status.Errorf(codes.NotFound, "ActorSnapshotTag %s/%s not found", tagRef.Atespace, tagRef.Name)
return nil, status.Errorf(codes.NotFound, "Tag %s/%s not found", tagRef.Atespace, tagRef.Name)
}
if err != nil {
return nil, fmt.Errorf("while deleting actor snapshot tag: %w", err)
return nil, fmt.Errorf("while deleting tag: %w", err)
}
return tag, nil
}
@@ -294,7 +294,7 @@ func (s *RPCService) DeleteActorSnapshotTag(ctx context.Context, req *ateapipb.D
// releaseTagSnapshot deletes the objects the tag's external snapshot is made
// of. It tolerates a partly-collected snapshot, so a retry finishes cleanly.
// It collects the in-progress snapshot too.
func (s *RPCService) releaseTagSnapshot(ctx context.Context, tag *ateapipb.ActorSnapshotTag) error {
func (s *RPCService) releaseTagSnapshot(ctx context.Context, tag *ateapipb.Tag) error {
if s.objectStore == nil {
return nil
}
@@ -317,12 +317,12 @@ func (s *RPCService) releaseTagSnapshot(ctx context.Context, tag *ateapipb.Actor
return nil
}
func (s *ServiceImpl) DeleteActorSnapshotTag(ctx context.Context, tagRef resources.ActorSnapshotTagRef) (*ateapipb.ActorSnapshotTag, error) {
func (s *ServiceImpl) DeleteTag(ctx context.Context, tagRef resources.TagRef) (*ateapipb.Tag, error) {
// TODO: implement this
return s.store.DeleteActorSnapshotTag(ctx, tagRef)
return s.store.DeleteTag(ctx, tagRef)
}
func validateDeleteActorSnapshotTagRequest(ctx context.Context, req *ateapipb.DeleteActorSnapshotTagRequest) field.ErrorList {
func validateDeleteTagRequest(ctx context.Context, req *ateapipb.DeleteTagRequest) field.ErrorList {
op := operation.Operation{Type: operation.Create}
return Validate_DeleteActorSnapshotTagRequest(ctx, op, nil, req, nil)
return Validate_DeleteTagRequest(ctx, op, nil, req, nil)
}
File diff suppressed because it is too large Load Diff
@@ -1198,14 +1198,14 @@ func TestValidateNestedExternalSnapshot(t *testing.T) {
},
},
{
name: "actor_snapshot_tag.status.snapshot",
name: "tag.status.snapshot",
path: field.NewPath("status", "snapshot"),
validate: func(ctx context.Context) field.ErrorList {
op := operation.Operation{Type: operation.Create}
obj := validActorSnapshotTag(func(tag *ateapipb.ActorSnapshotTag) {
obj := validTag(func(tag *ateapipb.Tag) {
tag.Status.Snapshot = badExternalSnapshot()
})
return Validate_ActorSnapshotTag(ctx, op, nil, obj, nil)
return Validate_Tag(ctx, op, nil, obj, nil)
},
},
{
@@ -1233,11 +1233,11 @@ func TestValidateNestedExternalSnapshot(t *testing.T) {
}
}
func validActorSnapshotTag(mutate ...func(*ateapipb.ActorSnapshotTag)) *ateapipb.ActorSnapshotTag {
tag := &ateapipb.ActorSnapshotTag{
func validTag(mutate ...func(*ateapipb.Tag)) *ateapipb.Tag {
tag := &ateapipb.Tag{
Metadata: validResourceMetadata(),
Status: &ateapipb.ActorSnapshotTagStatus{Snapshot: validExternalSnapshot()},
Scope: ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE,
Status: &ateapipb.TagStatus{Snapshot: validExternalSnapshot()},
Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE,
SourceActor: &ateapipb.ObjectRef{Atespace: "as", Name: "nm"},
}
for _, m := range mutate {
@@ -1246,15 +1246,15 @@ func validActorSnapshotTag(mutate ...func(*ateapipb.ActorSnapshotTag)) *ateapipb
return tag
}
func TestValidateActorSnapshotTag(t *testing.T) {
valid := validActorSnapshotTag
func TestValidateTag(t *testing.T) {
valid := validTag
metadataPath := field.NewPath("metadata")
scopePath := field.NewPath("scope")
sourceActorPath := field.NewPath("source_actor")
tests := []struct {
name string
obj *ateapipb.ActorSnapshotTag
obj *ateapipb.Tag
want field.ErrorList
}{
{
@@ -1263,74 +1263,74 @@ func TestValidateActorSnapshotTag(t *testing.T) {
},
{
name: "valid scope: published",
obj: valid(func(tag *ateapipb.ActorSnapshotTag) {
tag.Scope = ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED
obj: valid(func(tag *ateapipb.Tag) {
tag.Scope = ateapipb.TagScope_TAG_SCOPE_PUBLISHED
}),
},
{
name: "missing status",
obj: valid(func(tag *ateapipb.ActorSnapshotTag) { tag.Status = nil }),
obj: valid(func(tag *ateapipb.Tag) { tag.Status = nil }),
},
{
name: "missing metadata",
obj: valid(func(tag *ateapipb.ActorSnapshotTag) { tag.Metadata = nil }),
obj: valid(func(tag *ateapipb.Tag) { tag.Metadata = nil }),
want: field.ErrorList{field.Required(metadataPath, "")},
},
{
// A tag is addressed through the atespace that owns it, so unlike a
// global-scoped resource it always has to name one.
name: "missing metadata.atespace",
obj: valid(func(tag *ateapipb.ActorSnapshotTag) { tag.Metadata.Atespace = "" }),
obj: valid(func(tag *ateapipb.Tag) { tag.Metadata.Atespace = "" }),
want: field.ErrorList{field.Required(metadataPath.Child("atespace"), "")},
},
{
name: "invalid nested metadata.name",
obj: valid(func(tag *ateapipb.ActorSnapshotTag) { tag.Metadata.Name = "" }),
obj: valid(func(tag *ateapipb.Tag) { tag.Metadata.Name = "" }),
want: field.ErrorList{field.Required(metadataPath.Child("name"), "")},
},
{
name: "unspecified scope",
obj: valid(func(tag *ateapipb.ActorSnapshotTag) {
tag.Scope = ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_UNSPECIFIED
obj: valid(func(tag *ateapipb.Tag) {
tag.Scope = ateapipb.TagScope_TAG_SCOPE_UNSPECIFIED
}),
want: field.ErrorList{field.Required(scopePath, "")},
},
{
name: "scope above the enum",
obj: valid(func(tag *ateapipb.ActorSnapshotTag) { tag.Scope = ateapipb.ActorSnapshotTagScope(3) }),
obj: valid(func(tag *ateapipb.Tag) { tag.Scope = ateapipb.TagScope(3) }),
want: field.ErrorList{field.Invalid(scopePath, nil, "").WithOrigin("maximum")},
},
{
name: "negative scope",
obj: valid(func(tag *ateapipb.ActorSnapshotTag) { tag.Scope = ateapipb.ActorSnapshotTagScope(-1) }),
obj: valid(func(tag *ateapipb.Tag) { tag.Scope = ateapipb.TagScope(-1) }),
want: field.ErrorList{field.Invalid(scopePath, nil, "").WithOrigin("minimum")},
},
{
name: "missing source_actor",
obj: valid(func(tag *ateapipb.ActorSnapshotTag) { tag.SourceActor = nil }),
obj: valid(func(tag *ateapipb.Tag) { tag.SourceActor = nil }),
want: field.ErrorList{field.Required(sourceActorPath, "")},
},
{
name: "missing source_actor.atespace",
obj: valid(func(tag *ateapipb.ActorSnapshotTag) { tag.SourceActor.Atespace = "" }),
obj: valid(func(tag *ateapipb.Tag) { tag.SourceActor.Atespace = "" }),
want: field.ErrorList{field.Required(sourceActorPath.Child("atespace"), "")},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
op := operation.Operation{Type: operation.Create}
assertValidateErr(t, Validate_ActorSnapshotTag(context.Background(), op, nil, tt.obj, nil), tt.want)
assertValidateErr(t, Validate_Tag(context.Background(), op, nil, tt.obj, nil), tt.want)
})
}
}
func TestValidateActorSnapshotTagUpdate(t *testing.T) {
valid := validActorSnapshotTag
func TestValidateTagUpdate(t *testing.T) {
valid := validTag
tests := []struct {
name string
oldObj *ateapipb.ActorSnapshotTag // should always be valid
newObj *ateapipb.ActorSnapshotTag
oldObj *ateapipb.Tag // should always be valid
newObj *ateapipb.Tag
want field.ErrorList
}{
{
@@ -1343,39 +1343,39 @@ func TestValidateActorSnapshotTagUpdate(t *testing.T) {
// every reference to it.
name: "metadata.atespace changed",
oldObj: valid(),
newObj: valid(func(tag *ateapipb.ActorSnapshotTag) { tag.Metadata.Atespace = "other" }),
newObj: valid(func(tag *ateapipb.Tag) { tag.Metadata.Atespace = "other" }),
want: field.ErrorList{field.Invalid(field.NewPath("metadata", "atespace"), nil, "").WithOrigin("immutable")},
},
{
name: "source_actor changed",
oldObj: valid(),
newObj: valid(func(tag *ateapipb.ActorSnapshotTag) { tag.SourceActor.Name = "other" }),
newObj: valid(func(tag *ateapipb.Tag) { tag.SourceActor.Name = "other" }),
want: field.ErrorList{field.Invalid(field.NewPath("source_actor"), nil, "").WithOrigin("immutable")},
},
{
name: "scope changed",
oldObj: valid(),
newObj: valid(func(tag *ateapipb.ActorSnapshotTag) {
tag.Scope = ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED
newObj: valid(func(tag *ateapipb.Tag) {
tag.Scope = ateapipb.TagScope_TAG_SCOPE_PUBLISHED
}),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
op := operation.Operation{Type: operation.Update}
assertValidateErr(t, Validate_ActorSnapshotTag(context.Background(), op, nil, tt.newObj, tt.oldObj), tt.want)
assertValidateErr(t, Validate_Tag(context.Background(), op, nil, tt.newObj, tt.oldObj), tt.want)
})
}
}
// TestValidateActorSnapshotTagRequestPayloads covers the generated rules on the
// TestValidateTagRequestPayloads covers the generated rules on the
// tag requests. The RPCs call these directly (see
// validateCreateActorSnapshotTagRequest), so these guard the schema against
// drift; actor_snapshot_tag_test.go covers what the handlers make of it.
func TestValidateActorSnapshotTagRequestPayloads(t *testing.T) {
validTag := validActorSnapshotTag
// validateCreateTagRequest), so these guard the schema against
// drift; tag_test.go covers what the handlers make of it.
func TestValidateTagRequestPayloads(t *testing.T) {
validTag := validTag
validRef := func() *ateapipb.ObjectRef { return &ateapipb.ObjectRef{Atespace: "as", Name: "nm"} }
tagPath := field.NewPath("actor_snapshot_tag")
tagPath := field.NewPath("tag")
tests := []struct {
name string
@@ -1385,15 +1385,15 @@ func TestValidateActorSnapshotTagRequestPayloads(t *testing.T) {
{
name: "create: valid",
validate: func(ctx context.Context, op operation.Operation) field.ErrorList {
req := &ateapipb.CreateActorSnapshotTagRequest{ActorSnapshotTag: validTag()}
return Validate_CreateActorSnapshotTagRequest(ctx, op, nil, req, nil)
req := &ateapipb.CreateTagRequest{Tag: validTag()}
return Validate_CreateTagRequest(ctx, op, nil, req, nil)
},
},
{
name: "create: missing actor_snapshot_tag",
name: "create: missing tag",
validate: func(ctx context.Context, op operation.Operation) field.ErrorList {
req := &ateapipb.CreateActorSnapshotTagRequest{}
return Validate_CreateActorSnapshotTagRequest(ctx, op, nil, req, nil)
req := &ateapipb.CreateTagRequest{}
return Validate_CreateTagRequest(ctx, op, nil, req, nil)
},
want: field.ErrorList{field.Required(tagPath, "")},
},
@@ -1402,8 +1402,8 @@ func TestValidateActorSnapshotTagRequestPayloads(t *testing.T) {
validate: func(ctx context.Context, op operation.Operation) field.ErrorList {
tag := validTag()
tag.Status.Snapshot = badExternalSnapshot()
req := &ateapipb.CreateActorSnapshotTagRequest{ActorSnapshotTag: tag}
return Validate_CreateActorSnapshotTagRequest(ctx, op, nil, req, nil)
req := &ateapipb.CreateTagRequest{Tag: tag}
return Validate_CreateTagRequest(ctx, op, nil, req, nil)
},
want: field.ErrorList{
field.Required(tagPath.Child("status", "snapshot", "snapshot_uri"), ""),
@@ -1413,15 +1413,15 @@ func TestValidateActorSnapshotTagRequestPayloads(t *testing.T) {
{
name: "update: valid",
validate: func(ctx context.Context, op operation.Operation) field.ErrorList {
req := &ateapipb.UpdateActorSnapshotTagRequest{ActorSnapshotTag: validTag()}
return Validate_UpdateActorSnapshotTagRequest(ctx, op, nil, req, nil)
req := &ateapipb.UpdateTagRequest{Tag: validTag()}
return Validate_UpdateTagRequest(ctx, op, nil, req, nil)
},
},
{
name: "update: missing actor_snapshot_tag",
name: "update: missing tag",
validate: func(ctx context.Context, op operation.Operation) field.ErrorList {
req := &ateapipb.UpdateActorSnapshotTagRequest{}
return Validate_UpdateActorSnapshotTagRequest(ctx, op, nil, req, nil)
req := &ateapipb.UpdateTagRequest{}
return Validate_UpdateTagRequest(ctx, op, nil, req, nil)
},
want: field.ErrorList{field.Required(tagPath, "")},
},
@@ -1432,63 +1432,63 @@ func TestValidateActorSnapshotTagRequestPayloads(t *testing.T) {
validate: func(ctx context.Context, op operation.Operation) field.ErrorList {
tag := validTag()
tag.Status.Snapshot = badExternalSnapshot()
req := &ateapipb.UpdateActorSnapshotTagRequest{ActorSnapshotTag: tag}
return Validate_UpdateActorSnapshotTagRequest(ctx, op, nil, req, nil)
req := &ateapipb.UpdateTagRequest{Tag: tag}
return Validate_UpdateTagRequest(ctx, op, nil, req, nil)
},
},
{
name: "get: valid",
validate: func(ctx context.Context, op operation.Operation) field.ErrorList {
req := &ateapipb.GetActorSnapshotTagRequest{ActorSnapshotTag: validRef()}
return Validate_GetActorSnapshotTagRequest(ctx, op, nil, req, nil)
req := &ateapipb.GetTagRequest{Tag: validRef()}
return Validate_GetTagRequest(ctx, op, nil, req, nil)
},
},
{
name: "get: missing actor_snapshot_tag",
name: "get: missing tag",
validate: func(ctx context.Context, op operation.Operation) field.ErrorList {
req := &ateapipb.GetActorSnapshotTagRequest{}
return Validate_GetActorSnapshotTagRequest(ctx, op, nil, req, nil)
req := &ateapipb.GetTagRequest{}
return Validate_GetTagRequest(ctx, op, nil, req, nil)
},
want: field.ErrorList{field.Required(tagPath, "")},
},
{
// A tag is addressed through the atespace that owns it, so a ref
// naming only a name does not name a tag.
name: "get: missing actor_snapshot_tag.atespace",
name: "get: missing tag.atespace",
validate: func(ctx context.Context, op operation.Operation) field.ErrorList {
req := &ateapipb.GetActorSnapshotTagRequest{ActorSnapshotTag: &ateapipb.ObjectRef{Name: "nm"}}
return Validate_GetActorSnapshotTagRequest(ctx, op, nil, req, nil)
req := &ateapipb.GetTagRequest{Tag: &ateapipb.ObjectRef{Name: "nm"}}
return Validate_GetTagRequest(ctx, op, nil, req, nil)
},
want: field.ErrorList{field.Required(tagPath.Child("atespace"), "")},
},
{
name: "get: invalid actor_snapshot_tag.name",
name: "get: invalid tag.name",
validate: func(ctx context.Context, op operation.Operation) field.ErrorList {
req := &ateapipb.GetActorSnapshotTagRequest{ActorSnapshotTag: &ateapipb.ObjectRef{Atespace: "as", Name: "NM"}}
return Validate_GetActorSnapshotTagRequest(ctx, op, nil, req, nil)
req := &ateapipb.GetTagRequest{Tag: &ateapipb.ObjectRef{Atespace: "as", Name: "NM"}}
return Validate_GetTagRequest(ctx, op, nil, req, nil)
},
want: field.ErrorList{field.Invalid(tagPath.Child("name"), nil, "").WithOrigin("format=k8s-short-name")},
},
{
name: "delete: valid",
validate: func(ctx context.Context, op operation.Operation) field.ErrorList {
req := &ateapipb.DeleteActorSnapshotTagRequest{ActorSnapshotTag: validRef()}
return Validate_DeleteActorSnapshotTagRequest(ctx, op, nil, req, nil)
req := &ateapipb.DeleteTagRequest{Tag: validRef()}
return Validate_DeleteTagRequest(ctx, op, nil, req, nil)
},
},
{
name: "delete: missing actor_snapshot_tag",
name: "delete: missing tag",
validate: func(ctx context.Context, op operation.Operation) field.ErrorList {
req := &ateapipb.DeleteActorSnapshotTagRequest{}
return Validate_DeleteActorSnapshotTagRequest(ctx, op, nil, req, nil)
req := &ateapipb.DeleteTagRequest{}
return Validate_DeleteTagRequest(ctx, op, nil, req, nil)
},
want: field.ErrorList{field.Required(tagPath, "")},
},
{
name: "delete: missing actor_snapshot_tag.atespace",
name: "delete: missing tag.atespace",
validate: func(ctx context.Context, op operation.Operation) field.ErrorList {
req := &ateapipb.DeleteActorSnapshotTagRequest{ActorSnapshotTag: &ateapipb.ObjectRef{Name: "nm"}}
return Validate_DeleteActorSnapshotTagRequest(ctx, op, nil, req, nil)
req := &ateapipb.DeleteTagRequest{Tag: &ateapipb.ObjectRef{Name: "nm"}}
return Validate_DeleteTagRequest(ctx, op, nil, req, nil)
},
want: field.ErrorList{field.Required(tagPath.Child("atespace"), "")},
},
@@ -1497,31 +1497,31 @@ func TestValidateActorSnapshotTagRequestPayloads(t *testing.T) {
// how a client asks for every atespace at once.
name: "list: empty request",
validate: func(ctx context.Context, op operation.Operation) field.ErrorList {
req := &ateapipb.ListActorSnapshotTagsRequest{}
return Validate_ListActorSnapshotTagsRequest(ctx, op, nil, req, nil)
req := &ateapipb.ListTagsRequest{}
return Validate_ListTagsRequest(ctx, op, nil, req, nil)
},
},
{
name: "list: invalid atespace",
validate: func(ctx context.Context, op operation.Operation) field.ErrorList {
req := &ateapipb.ListActorSnapshotTagsRequest{Atespace: "AS"}
return Validate_ListActorSnapshotTagsRequest(ctx, op, nil, req, nil)
req := &ateapipb.ListTagsRequest{Atespace: "AS"}
return Validate_ListTagsRequest(ctx, op, nil, req, nil)
},
want: field.ErrorList{field.Invalid(field.NewPath("atespace"), nil, "").WithOrigin("format=k8s-short-name")},
},
{
name: "list: negative page_size",
validate: func(ctx context.Context, op operation.Operation) field.ErrorList {
req := &ateapipb.ListActorSnapshotTagsRequest{PageSize: -1}
return Validate_ListActorSnapshotTagsRequest(ctx, op, nil, req, nil)
req := &ateapipb.ListTagsRequest{PageSize: -1}
return Validate_ListTagsRequest(ctx, op, nil, req, nil)
},
want: field.ErrorList{field.Invalid(field.NewPath("page_size"), nil, "").WithOrigin("minimum")},
},
{
name: "list: over-long page_token",
validate: func(ctx context.Context, op operation.Operation) field.ErrorList {
req := &ateapipb.ListActorSnapshotTagsRequest{PageToken: strings.Repeat("t", 257)}
return Validate_ListActorSnapshotTagsRequest(ctx, op, nil, req, nil)
req := &ateapipb.ListTagsRequest{PageToken: strings.Repeat("t", 257)}
return Validate_ListTagsRequest(ctx, op, nil, req, nil)
},
want: field.ErrorList{field.TooLong(field.NewPath("page_token"), nil, 256).WithOrigin("maxLength")},
},
+5 -5
View File
@@ -126,9 +126,9 @@ type actorWorkflowStore interface {
// Read from the records rather than the Worker's status: only the service
// layer attaches assignments on read, and this workflow holds the store.
ListWorkerAssignments(ctx context.Context, workerName string, opts store.ListOptions) (store.ListResponse[*ateapipb.ActorAssignment], error)
CreateActorSnapshotTag(ctx context.Context, tag *ateapipb.ActorSnapshotTag) (*ateapipb.ActorSnapshotTag, error)
GetActorSnapshotTag(ctx context.Context, tagRef resources.ActorSnapshotTagRef) (*ateapipb.ActorSnapshotTag, error)
UpdateActorSnapshotTag(ctx context.Context, tagRef resources.ActorSnapshotTagRef, precondition store.Precondition, mutate func(toUpdate *ateapipb.ActorSnapshotTag) error) (*ateapipb.ActorSnapshotTag, error)
CreateTag(ctx context.Context, tag *ateapipb.Tag) (*ateapipb.Tag, error)
GetTag(ctx context.Context, tagRef resources.TagRef) (*ateapipb.Tag, error)
UpdateTag(ctx context.Context, tagRef resources.TagRef, precondition store.Precondition, mutate func(toUpdate *ateapipb.Tag) error) (*ateapipb.Tag, error)
GetActorTemplate(ctx context.Context, templateRef resources.ActorTemplateRef) (*ateapipb.ActorTemplate, error)
AcquireLease(ctx context.Context, key string) (*store.Lease, error)
}
@@ -183,6 +183,6 @@ func (w *ActorWorkflow) acquireActorLease(ctx context.Context, actorRef resource
return acquireLease(ctx, w.store, "lease:actor:"+actorRef.Atespace+":"+actorRef.Name, "actor")
}
func acquireActorSnapshotTagLease(ctx context.Context, holder leaseHolder, tagRef resources.ActorSnapshotTagRef) (context.Context, *store.Lease, error) {
return acquireLease(ctx, holder, "lease:actor-snapshot-tag:"+tagRef.Atespace+":"+tagRef.Name, "ActorSnapshotTag")
func acquireTagLease(ctx context.Context, holder leaseHolder, tagRef resources.TagRef) (context.Context, *store.Lease, error) {
return acquireLease(ctx, holder, "lease:tag:"+tagRef.Atespace+":"+tagRef.Name, "Tag")
}
@@ -441,9 +441,9 @@ func (c *conflictInjectingStore) UpdateActor(ctx context.Context, actorRef resou
return c.Interface.UpdateActor(ctx, actorRef, precondition, mutate)
}
func (c *conflictInjectingStore) UpdateActorSnapshotTag(ctx context.Context, tagRef resources.ActorSnapshotTagRef, precondition store.Precondition, mutate func(*ateapipb.ActorSnapshotTag) error) (*ateapipb.ActorSnapshotTag, error) {
func (c *conflictInjectingStore) UpdateTag(ctx context.Context, tagRef resources.TagRef, precondition store.Precondition, mutate func(*ateapipb.Tag) error) (*ateapipb.Tag, error) {
c.once.Do(c.inject)
return c.Interface.UpdateActorSnapshotTag(ctx, tagRef, precondition, mutate)
return c.Interface.UpdateTag(ctx, tagRef, precondition, mutate)
}
// seedAssignFixture stores one free gvisor worker and a SUSPENDED actor and
@@ -46,7 +46,7 @@ import (
// dies after it leaves a pending tag and every later create under that name is
// AlreadyExists. To retry, delete the tag, which collects whatever the failed
// attempt stranded, and create it again.
func (w *ActorWorkflow) TagActorSnapshot(ctx context.Context, tag *ateapipb.ActorSnapshotTag) (*ateapipb.ActorSnapshotTag, error) {
func (w *ActorWorkflow) TagActorSnapshot(ctx context.Context, tag *ateapipb.Tag) (*ateapipb.Tag, error) {
actorRef := resources.ActorRefFromObjectRef(tag.GetSourceActor())
// Serializes against a suspend of the same actor, which would otherwise
@@ -59,8 +59,8 @@ func (w *ActorWorkflow) TagActorSnapshot(ctx context.Context, tag *ateapipb.Acto
// Serializes against a delete of the tag this creates, which would
// otherwise collect the copy while it is being written.
tagRef := resources.ActorSnapshotTagRef{Atespace: actorRef.Atespace, Name: tag.GetMetadata().GetName()}
leaseCtx, tagLease, err := acquireActorSnapshotTagLease(leaseCtx, w.store, tagRef)
tagRef := resources.TagRef{Atespace: actorRef.Atespace, Name: tag.GetMetadata().GetName()}
leaseCtx, tagLease, err := acquireTagLease(leaseCtx, w.store, tagRef)
if err != nil {
return nil, err
}
@@ -123,19 +123,19 @@ func (w *ActorWorkflow) loadActorForTag(ctx context.Context, actorRef resources.
// deciding whether the objects under it still belong to the snapshot being
// tagged, and the row may not even be this actor's; deleting the tag collects
// them and frees the name, so a retry is a delete followed by a create.
func (w *ActorWorkflow) ensureTagReserved(ctx context.Context, tagRef resources.ActorSnapshotTagRef, actor *ateapipb.Actor, actorTemplate *ateapipb.ActorTemplate, tag *ateapipb.ActorSnapshotTag) (_ *ateapipb.ActorSnapshotTag, err error) {
ctx, done := stepSpan(ctx, "ReserveSnapshotTag")
func (w *ActorWorkflow) ensureTagReserved(ctx context.Context, tagRef resources.TagRef, actor *ateapipb.Actor, actorTemplate *ateapipb.ActorTemplate, tag *ateapipb.Tag) (_ *ateapipb.Tag, err error) {
ctx, done := stepSpan(ctx, "ReserveTag")
defer func() { err = done(err) }()
dst, err := resources.NewTagSnapshotURI(actorTemplate.GetSnapshotsConfig().GetStorageLocation(), tagRef.Atespace, resources.NewSnapshotName())
if err != nil {
return nil, fmt.Errorf("while building the snapshot URI for tag %s: %w", tagRef, err)
}
tagToCreate := &ateapipb.ActorSnapshotTag{
tagToCreate := &ateapipb.Tag{
Metadata: &ateapipb.ResourceMetadata{Atespace: tagRef.Atespace, Name: tagRef.Name},
Scope: tag.GetScope(),
SourceActor: resources.ActorRefFromActor(actor).ToObjectRef(),
Status: &ateapipb.ActorSnapshotTagStatus{
Status: &ateapipb.TagStatus{
// The tag records the template the snapshot's guest state was built under, not
// the one the actor currently points at. A suspended actor can be repointed,
// and a tag that claimed the new template would hand clones the old template's
@@ -147,23 +147,23 @@ func (w *ActorWorkflow) ensureTagReserved(ctx context.Context, tagRef resources.
},
}
stored, err := w.store.CreateActorSnapshotTag(ctx, tagToCreate)
stored, err := w.store.CreateTag(ctx, tagToCreate)
switch {
case err == nil:
return stored, nil
case errors.Is(err, store.ErrFailedPrecondition):
return nil, status.Errorf(codes.FailedPrecondition, "Atespace %s not found", tagRef.Atespace)
case errors.Is(err, store.ErrAlreadyExists):
return nil, status.Errorf(codes.AlreadyExists, "ActorSnapshotTag %s already exists; delete it and create it again to retry", tagRef)
return nil, status.Errorf(codes.AlreadyExists, "Tag %s already exists; delete it and create it again to retry", tagRef)
}
return nil, fmt.Errorf("while reserving actor snapshot tag %s: %w", tagRef, err)
return nil, fmt.Errorf("while reserving tag %s: %w", tagRef, err)
}
// ensureTagSnapshotCopied copies the actor's external snapshot to the tag's own
// prefix, the one the reserved row names. That prefix is freshly minted, so it
// is empty by construction and the copy never blends with objects some other
// attempt left.
func (w *ActorWorkflow) ensureTagSnapshotCopied(ctx context.Context, tag *ateapipb.ActorSnapshotTag, snapshot *ateapipb.ExternalSnapshot) (err error) {
func (w *ActorWorkflow) ensureTagSnapshotCopied(ctx context.Context, tag *ateapipb.Tag, snapshot *ateapipb.ExternalSnapshot) (err error) {
ctx, done := stepSpan(ctx, "CopyTagSnapshot")
defer func() { err = done(err) }()
@@ -171,7 +171,7 @@ func (w *ActorWorkflow) ensureTagSnapshotCopied(ctx context.Context, tag *ateapi
markSkipped(ctx, "no object store configured")
return nil
}
tagRef := resources.ActorSnapshotTagRefFromActorSnapshotTag(tag)
tagRef := resources.TagRefFromTag(tag)
src, err := resources.ParseSnapshotURI(snapshot.GetSnapshotUri())
if err != nil {
return fmt.Errorf("while parsing the external snapshot %q of the source actor: %w", snapshot.GetSnapshotUri(), err)
@@ -190,17 +190,17 @@ func (w *ActorWorkflow) ensureTagSnapshotCopied(ctx context.Context, tag *ateapi
// row already names and clears status.in_progress_snapshot_uri. Until this
// lands the tag is pending — visible, unusable, and naming exactly the objects
// an unfinished create stranded, so deleting it collects them.
func (w *ActorWorkflow) ensureTagFinalized(ctx context.Context, tag *ateapipb.ActorSnapshotTag, snapshot *ateapipb.ExternalSnapshot) (_ *ateapipb.ActorSnapshotTag, err error) {
ctx, done := stepSpan(ctx, "FinalizeSnapshotTag")
func (w *ActorWorkflow) ensureTagFinalized(ctx context.Context, tag *ateapipb.Tag, snapshot *ateapipb.ExternalSnapshot) (_ *ateapipb.Tag, err error) {
ctx, done := stepSpan(ctx, "FinalizeTag")
defer func() { err = done(err) }()
tagRef := resources.ActorSnapshotTagRefFromActorSnapshotTag(tag)
tagRef := resources.TagRefFromTag(tag)
// The copy is byte-identical to the source, so it carries the same content.
finalSnapshot := &ateapipb.ExternalSnapshot{
SnapshotUri: tag.GetStatus().GetInProgressSnapshotUri(),
ContentScope: snapshot.GetContentScope(),
}
stored, err := w.store.UpdateActorSnapshotTag(ctx, tagRef, store.PreconditionFrom(tag), func(toUpdate *ateapipb.ActorSnapshotTag) error {
stored, err := w.store.UpdateTag(ctx, tagRef, store.PreconditionFrom(tag), func(toUpdate *ateapipb.Tag) error {
toUpdate.Status.Snapshot = finalSnapshot
toUpdate.Status.InProgressSnapshotUri = ""
return nil
@@ -210,9 +210,9 @@ func (w *ActorWorkflow) ensureTagFinalized(ctx context.Context, tag *ateapipb.Ac
return nil, status.Error(codes.Aborted, "concurrent update conflict, please retry")
}
if errors.Is(err, store.ErrNotFound) || errors.Is(err, store.ErrUIDConflict) {
return nil, status.Errorf(codes.Aborted, "ActorSnapshotTag %s was deleted while it was being created, please retry", tagRef)
return nil, status.Errorf(codes.Aborted, "Tag %s was deleted while it was being created, please retry", tagRef)
}
return nil, fmt.Errorf("while finalizing actor snapshot tag %s: %w", tagRef, err)
return nil, fmt.Errorf("while finalizing tag %s: %w", tagRef, err)
}
return stored, nil
}
@@ -50,11 +50,11 @@ func seedTagSource(t *testing.T, ctx context.Context, persistence store.Interfac
return actor, uri
}
// tagToCreate is the part of an ActorSnapshotTag a client owns on create.
func tagToCreate(sourceActor resources.ActorRef, name string) *ateapipb.ActorSnapshotTag {
return &ateapipb.ActorSnapshotTag{
// tagToCreate is the part of a Tag a client owns on create.
func tagToCreate(sourceActor resources.ActorRef, name string) *ateapipb.Tag {
return &ateapipb.Tag{
Metadata: &ateapipb.ResourceMetadata{Name: name},
Scope: ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE,
Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE,
SourceActor: sourceActor.ToObjectRef(),
}
}
@@ -209,8 +209,8 @@ func TestTagActorSnapshot_Preconditions(t *testing.T) {
t.Fatalf("TagActorSnapshot error = %v (code %v), want code %v", err, code, tt.wantCode)
}
// A rejected create takes the name with it.
if _, err := persistence.GetActorSnapshotTag(ctx, resources.ActorSnapshotTagRef{Atespace: "team-a", Name: "v1"}); !errors.Is(err, store.ErrNotFound) {
t.Errorf("GetActorSnapshotTag = %v, want ErrNotFound: the rejected create reserved the name", err)
if _, err := persistence.GetTag(ctx, resources.TagRef{Atespace: "team-a", Name: "v1"}); !errors.Is(err, store.ErrNotFound) {
t.Errorf("GetTag = %v, want ErrNotFound: the rejected create reserved the name", err)
}
})
}
@@ -230,7 +230,7 @@ func TestTagActorSnapshot_RecreateAfterCopyFailure(t *testing.T) {
actor, _ := seedTagSource(t, ctx, persistence, objects, template, "actor-1", "manifest.json", "memory.zst")
actorRef := resources.ActorRefFromActor(actor)
tagRef := resources.ActorSnapshotTagRef{Atespace: "team-a", Name: "v1"}
tagRef := resources.TagRef{Atespace: "team-a", Name: "v1"}
// The copy dies halfway through: the first object lands, the second does not.
objects.OnCopy = func(_, srcObject, _, _ string) error {
@@ -244,9 +244,9 @@ func TestTagActorSnapshot_RecreateAfterCopyFailure(t *testing.T) {
}
objects.OnCopy = nil
pending, err := persistence.GetActorSnapshotTag(ctx, tagRef)
pending, err := persistence.GetTag(ctx, tagRef)
if err != nil {
t.Fatalf("GetActorSnapshotTag after the external store failure: %v", err)
t.Fatalf("GetTag after the external store failure: %v", err)
}
if got := pending.GetStatus().GetSnapshot().GetSnapshotUri(); got != "" {
t.Errorf("snapshot uri after the failure = %q, want unset: the copy never finished", got)
@@ -266,9 +266,9 @@ func TestTagActorSnapshot_RecreateAfterCopyFailure(t *testing.T) {
if code := status.Code(err); code != codes.AlreadyExists {
t.Fatalf("TagActorSnapshot over the pending tag = %v (code %v), want code AlreadyExists", err, code)
}
stillPending, err := persistence.GetActorSnapshotTag(ctx, tagRef)
stillPending, err := persistence.GetTag(ctx, tagRef)
if err != nil {
t.Fatalf("GetActorSnapshotTag after the refused create: %v", err)
t.Fatalf("GetTag after the refused create: %v", err)
}
if got := stillPending.GetStatus().GetInProgressSnapshotUri(); got != stranded {
t.Errorf("in-progress snapshot uri after the refused create = %q, want the prefix the row already named, %q", got, stranded)
@@ -278,8 +278,8 @@ func TestTagActorSnapshot_RecreateAfterCopyFailure(t *testing.T) {
}
// Deleting the tag collects the partial copy and frees the name.
if _, err := svc.DeleteActorSnapshotTag(ctx, &ateapipb.DeleteActorSnapshotTagRequest{ActorSnapshotTag: tagRef.ToObjectRef()}); err != nil {
t.Fatalf("DeleteActorSnapshotTag on the pending tag: %v", err)
if _, err := svc.DeleteTag(ctx, &ateapipb.DeleteTagRequest{Tag: tagRef.ToObjectRef()}); err != nil {
t.Fatalf("DeleteTag on the pending tag: %v", err)
}
if got := objects.Snapshot(t, strandedURI); len(got) != 0 {
t.Errorf("the stranded prefix after the delete = %v, want collected", got)
@@ -319,7 +319,7 @@ func TestTagActorSnapshot_RacesDelete(t *testing.T) {
actor, _ := seedTagSource(t, ctx, persistence, objects, template, "actor-1", "manifest.json", "memory.zst")
actorRef := resources.ActorRefFromActor(actor)
tagRef := resources.ActorSnapshotTagRef{Atespace: "team-a", Name: "v1"}
tagRef := resources.TagRef{Atespace: "team-a", Name: "v1"}
var once sync.Once
var pendingURI string
@@ -328,13 +328,13 @@ func TestTagActorSnapshot_RacesDelete(t *testing.T) {
// the file copy to GCS/S3.
objects.OnCopy = func(_, _, _, _ string) error {
once.Do(func() {
reserved, err := persistence.GetActorSnapshotTag(ctx, tagRef)
reserved, err := persistence.GetTag(ctx, tagRef)
if err != nil {
t.Errorf("GetActorSnapshotTag during the copy: %v", err)
t.Errorf("GetTag during the copy: %v", err)
return
}
pendingURI = reserved.GetStatus().GetInProgressSnapshotUri()
_, deleteErr = svc.DeleteActorSnapshotTag(ctx, &ateapipb.DeleteActorSnapshotTagRequest{ActorSnapshotTag: tagRef.ToObjectRef()})
_, deleteErr = svc.DeleteTag(ctx, &ateapipb.DeleteTagRequest{Tag: tagRef.ToObjectRef()})
})
return nil
}
@@ -349,7 +349,7 @@ func TestTagActorSnapshot_RacesDelete(t *testing.T) {
// The delete is refused rather than queued behind the copy: the create
// holds the tag's lease until it has finished writing.
if code := status.Code(deleteErr); code != codes.Aborted {
t.Errorf("DeleteActorSnapshotTag during the copy = %v (code %v), want code Aborted", deleteErr, code)
t.Errorf("DeleteTag during the copy = %v (code %v), want code Aborted", deleteErr, code)
}
// Nothing was collected out from under the copy, and the row in the store it
@@ -360,8 +360,8 @@ func TestTagActorSnapshot_RacesDelete(t *testing.T) {
if diff := cmp.Diff([]string{"manifest.json", "memory.zst"}, objects.Snapshot(t, mustParseSnapshotURI(t, pendingURI))); diff != "" {
t.Errorf("the tag's external snapshot mismatch (-want +got):\n%s", diff)
}
if _, err := persistence.GetActorSnapshotTag(ctx, tagRef); err != nil {
t.Errorf("GetActorSnapshotTag after the race: %v", err)
if _, err := persistence.GetTag(ctx, tagRef); err != nil {
t.Errorf("GetTag after the race: %v", err)
}
}
@@ -377,7 +377,7 @@ func TestTagActorSnapshot_NameTakenByAnotherActor(t *testing.T) {
first, _ := seedTagSource(t, ctx, persistence, objects, template, "actor-1", "manifest.json", "memory.zst")
second, secondSnapshot := seedTagSource(t, ctx, persistence, objects, template, "actor-2", "other.json")
tagRef := resources.ActorSnapshotTagRef{Atespace: "team-a", Name: "v1"}
tagRef := resources.TagRef{Atespace: "team-a", Name: "v1"}
// The first actor reserves the name, then dies in the copy.
objects.OnCopy = func(_, srcObject, _, _ string) error {
@@ -389,9 +389,9 @@ func TestTagActorSnapshot_NameTakenByAnotherActor(t *testing.T) {
if _, err := w.TagActorSnapshot(ctx, tagToCreate(resources.ActorRefFromActor(first), "v1")); !errors.Is(err, errObjectStore) {
t.Fatalf("TagActorSnapshot(actor-1) = %v, want an error wrapping %v", err, errObjectStore)
}
pending, err := persistence.GetActorSnapshotTag(ctx, tagRef)
pending, err := persistence.GetTag(ctx, tagRef)
if err != nil {
t.Fatalf("GetActorSnapshotTag: %v", err)
t.Fatalf("GetTag: %v", err)
}
strandedURI := mustParseSnapshotURI(t, pending.GetStatus().GetInProgressSnapshotUri())
@@ -405,9 +405,9 @@ func TestTagActorSnapshot_NameTakenByAnotherActor(t *testing.T) {
if diff := cmp.Diff([]string{"manifest.json"}, objects.Snapshot(t, strandedURI)); diff != "" {
t.Errorf("the rejected create wrote into the pending tag's prefix (-want +got):\n%s", diff)
}
stored, err := persistence.GetActorSnapshotTag(ctx, tagRef)
stored, err := persistence.GetTag(ctx, tagRef)
if err != nil {
t.Fatalf("GetActorSnapshotTag after the rejected create: %v", err)
t.Fatalf("GetTag after the rejected create: %v", err)
}
if got, want := stored.GetStatus().GetSourceActorUid(), first.GetMetadata().GetUid(); got != want {
t.Errorf("source actor uid = %q, want the first actor's %q", got, want)
File diff suppressed because it is too large Load Diff
+57 -57
View File
@@ -986,46 +986,46 @@ func unmarshalEgressPolicy(uid string, version int64, protoBytes []byte) (*ateap
return policy, nil
}
// --- Actor snapshot tags ---
// --- Actor tags ---
func (p *Persistence) GetActorSnapshotTag(ctx context.Context, tagRef resources.ActorSnapshotTagRef) (*ateapipb.ActorSnapshotTag, error) {
func (p *Persistence) GetTag(ctx context.Context, tagRef resources.TagRef) (*ateapipb.Tag, error) {
atespace, name := tagRef.Atespace, tagRef.Name
var protoBytes []byte
if err := p.pool.QueryRow(ctx, `
SELECT proto FROM actor_snapshot_tags
SELECT proto FROM tags
WHERE atespace = $1 AND name = $2`, atespace, name).Scan(&protoBytes); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, store.ErrNotFound
}
return nil, fmt.Errorf("getting actor snapshot tag %s/%s: %w", atespace, name, err)
return nil, fmt.Errorf("getting tag %s/%s: %w", atespace, name, err)
}
tag := &ateapipb.ActorSnapshotTag{}
tag := &ateapipb.Tag{}
if err := unmarshalStored(protoBytes, tag); err != nil {
return nil, fmt.Errorf("unmarshaling actor snapshot tag: %w", err)
return nil, fmt.Errorf("unmarshaling tag: %w", err)
}
return tag, nil
}
func (p *Persistence) ListActorSnapshotTags(ctx context.Context, atespace string, opts store.ListOptions) (store.ListResponse[*ateapipb.ActorSnapshotTag], error) {
func (p *Persistence) ListTags(ctx context.Context, atespace string, opts store.ListOptions) (store.ListResponse[*ateapipb.Tag], error) {
opts, err := store.NormalizeListOptions(opts)
if err != nil {
return store.ListResponse[*ateapipb.ActorSnapshotTag]{}, err
return store.ListResponse[*ateapipb.Tag]{}, err
}
var items []*ateapipb.ActorSnapshotTag
var items []*ateapipb.Tag
var nextToken string
if atespace != "" {
items, nextToken, err = p.listActorSnapshotTagsScoped(ctx, atespace, opts.PageSize, opts.PageToken)
items, nextToken, err = p.listTagsScoped(ctx, atespace, opts.PageSize, opts.PageToken)
} else {
items, nextToken, err = p.listActorSnapshotTagsGlobal(ctx, opts.PageSize, opts.PageToken)
items, nextToken, err = p.listTagsGlobal(ctx, opts.PageSize, opts.PageToken)
}
if err != nil {
return store.ListResponse[*ateapipb.ActorSnapshotTag]{}, err
return store.ListResponse[*ateapipb.Tag]{}, err
}
return store.ListResponse[*ateapipb.ActorSnapshotTag]{Items: items, NextPageToken: nextToken}, nil
return store.ListResponse[*ateapipb.Tag]{Items: items, NextPageToken: nextToken}, nil
}
func (p *Persistence) listActorSnapshotTagsScoped(ctx context.Context, atespace string, pageSize int32, pageTokenStr string) ([]*ateapipb.ActorSnapshotTag, string, error) {
token, err := decodePageToken(pageTokenStr, kindSnapshotTag, atespace, 1)
func (p *Persistence) listTagsScoped(ctx context.Context, atespace string, pageSize int32, pageTokenStr string) ([]*ateapipb.Tag, string, error) {
token, err := decodePageToken(pageTokenStr, kindTag, atespace, 1)
if err != nil {
return nil, "", err
}
@@ -1034,43 +1034,43 @@ func (p *Persistence) listActorSnapshotTagsScoped(ctx context.Context, atespace
last = &token.Last[0]
}
rows, err := p.pool.Query(ctx, `
SELECT name, proto FROM actor_snapshot_tags
SELECT name, proto FROM tags
WHERE atespace = $1 AND ($2::text IS NULL OR name > $2)
ORDER BY name
LIMIT $3`, atespace, last, int64(pageSize)+1)
if err != nil {
return nil, "", fmt.Errorf("listing actor snapshot tags in %q: %w", atespace, err)
return nil, "", fmt.Errorf("listing tags in %q: %w", atespace, err)
}
defer rows.Close()
var names []string
var result []*ateapipb.ActorSnapshotTag
var result []*ateapipb.Tag
for rows.Next() {
var name string
var protoBytes []byte
if err := rows.Scan(&name, &protoBytes); err != nil {
return nil, "", fmt.Errorf("scanning actor snapshot tag row: %w", err)
return nil, "", fmt.Errorf("scanning tag row: %w", err)
}
tag := &ateapipb.ActorSnapshotTag{}
tag := &ateapipb.Tag{}
if err := unmarshalStored(protoBytes, tag); err != nil {
return nil, "", fmt.Errorf("unmarshaling actor snapshot tag: %w", err)
return nil, "", fmt.Errorf("unmarshaling tag: %w", err)
}
result = append(result, tag)
names = append(names, name)
}
if err := rows.Err(); err != nil {
return nil, "", fmt.Errorf("listing actor snapshot tags in %q: %w", atespace, err)
return nil, "", fmt.Errorf("listing tags in %q: %w", atespace, err)
}
var nextToken string
if len(result) > int(pageSize) {
result = result[:pageSize]
nextToken = encodePageToken(kindSnapshotTag, atespace, []string{names[pageSize-1]})
nextToken = encodePageToken(kindTag, atespace, []string{names[pageSize-1]})
}
return result, nextToken, nil
}
func (p *Persistence) listActorSnapshotTagsGlobal(ctx context.Context, pageSize int32, pageTokenStr string) ([]*ateapipb.ActorSnapshotTag, string, error) {
token, err := decodePageToken(pageTokenStr, kindSnapshotTag, "", 2)
func (p *Persistence) listTagsGlobal(ctx context.Context, pageSize int32, pageTokenStr string) ([]*ateapipb.Tag, string, error) {
token, err := decodePageToken(pageTokenStr, kindTag, "", 2)
if err != nil {
return nil, "", err
}
@@ -1079,55 +1079,55 @@ func (p *Persistence) listActorSnapshotTagsGlobal(ctx context.Context, pageSize
lastAtespace, lastName = &token.Last[0], &token.Last[1]
}
rows, err := p.pool.Query(ctx, `
SELECT atespace, name, proto FROM actor_snapshot_tags
SELECT atespace, name, proto FROM tags
WHERE $1::text IS NULL OR (atespace, name) > ($1, $2)
ORDER BY atespace, name
LIMIT $3`, lastAtespace, lastName, int64(pageSize)+1)
if err != nil {
return nil, "", fmt.Errorf("listing actor snapshot tags: %w", err)
return nil, "", fmt.Errorf("listing tags: %w", err)
}
defer rows.Close()
type key struct{ atespace, name string }
var keys []key
var result []*ateapipb.ActorSnapshotTag
var result []*ateapipb.Tag
for rows.Next() {
var k key
var protoBytes []byte
if err := rows.Scan(&k.atespace, &k.name, &protoBytes); err != nil {
return nil, "", fmt.Errorf("scanning actor snapshot tag row: %w", err)
return nil, "", fmt.Errorf("scanning tag row: %w", err)
}
tag := &ateapipb.ActorSnapshotTag{}
tag := &ateapipb.Tag{}
if err := unmarshalStored(protoBytes, tag); err != nil {
return nil, "", fmt.Errorf("unmarshaling actor snapshot tag: %w", err)
return nil, "", fmt.Errorf("unmarshaling tag: %w", err)
}
result = append(result, tag)
keys = append(keys, k)
}
if err := rows.Err(); err != nil {
return nil, "", fmt.Errorf("listing actor snapshot tags: %w", err)
return nil, "", fmt.Errorf("listing tags: %w", err)
}
var nextToken string
if len(result) > int(pageSize) {
result = result[:pageSize]
last := keys[pageSize-1]
nextToken = encodePageToken(kindSnapshotTag, "", []string{last.atespace, last.name})
nextToken = encodePageToken(kindTag, "", []string{last.atespace, last.name})
}
return result, nextToken, nil
}
func (p *Persistence) CreateActorSnapshotTag(ctx context.Context, tag *ateapipb.ActorSnapshotTag) (*ateapipb.ActorSnapshotTag, error) {
func (p *Persistence) CreateTag(ctx context.Context, tag *ateapipb.Tag) (*ateapipb.Tag, error) {
atespace := tag.GetMetadata().GetAtespace()
name := tag.GetMetadata().GetName()
dbTag := proto.CloneOf(tag)
dbTag.Metadata = newCreateMetadata(atespace, name)
protoBytes, err := proto.Marshal(dbTag)
if err != nil {
return nil, fmt.Errorf("marshaling actor snapshot tag: %w", err)
return nil, fmt.Errorf("marshaling tag: %w", err)
}
_, err = p.pool.Exec(ctx, `
INSERT INTO actor_snapshot_tags (atespace, name, uid, version, proto)
INSERT INTO tags (atespace, name, uid, version, proto)
VALUES ($1, $2, $3, $4, $5)`, atespace, name,
dbTag.GetMetadata().GetUid(), dbTag.GetMetadata().GetVersion(), protoBytes)
if err != nil {
@@ -1136,18 +1136,18 @@ func (p *Persistence) CreateActorSnapshotTag(ctx context.Context, tag *ateapipb.
}
if isForeignKeyViolation(err) {
switch pgErrConstraint(err) {
case "actor_snapshot_tags_atespace_fk":
case "tags_atespace_fk":
return nil, store.ErrFailedPrecondition
default:
return nil, fmt.Errorf("inserting actor snapshot tag %s/%s violated unknown foreign key %q: %w", atespace, name, pgErrConstraint(err), err)
return nil, fmt.Errorf("inserting tag %s/%s violated unknown foreign key %q: %w", atespace, name, pgErrConstraint(err), err)
}
}
return nil, fmt.Errorf("inserting actor snapshot tag %s/%s: %w", atespace, name, err)
return nil, fmt.Errorf("inserting tag %s/%s: %w", atespace, name, err)
}
return dbTag, nil
}
func validateUpdateActorSnapshotTagMutation(storedTag, mutatedTag *ateapipb.ActorSnapshotTag) error {
func validateUpdateTagMutation(storedTag, mutatedTag *ateapipb.Tag) error {
if stored, mutated := storedTag.GetMetadata().GetAtespace(), mutatedTag.GetMetadata().GetAtespace(); stored != mutated {
return fmt.Errorf("metadata.atespace is immutable: mutation changed it from %q to %q", stored, mutated)
}
@@ -1169,7 +1169,7 @@ func validateUpdateActorSnapshotTagMutation(storedTag, mutatedTag *ateapipb.Acto
return nil
}
func (p *Persistence) UpdateActorSnapshotTag(ctx context.Context, tagRef resources.ActorSnapshotTagRef, precondition store.Precondition, mutate func(*ateapipb.ActorSnapshotTag) error) (*ateapipb.ActorSnapshotTag, error) {
func (p *Persistence) UpdateTag(ctx context.Context, tagRef resources.TagRef, precondition store.Precondition, mutate func(*ateapipb.Tag) error) (*ateapipb.Tag, error) {
if err := precondition.Validate(); err != nil {
return nil, err
}
@@ -1178,29 +1178,29 @@ func (p *Persistence) UpdateActorSnapshotTag(ctx context.Context, tagRef resourc
var currentVersion int64
var currentBytes []byte
if err := p.pool.QueryRow(ctx, `
SELECT uid, version, proto FROM actor_snapshot_tags
SELECT uid, version, proto FROM tags
WHERE atespace = $1 AND name = $2`, atespace, name).Scan(&currentUID, &currentVersion, &currentBytes); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, store.ErrNotFound
}
return nil, fmt.Errorf("getting actor snapshot tag %s/%s for update: %w", atespace, name, err)
return nil, fmt.Errorf("getting tag %s/%s for update: %w", atespace, name, err)
}
dbTag := &ateapipb.ActorSnapshotTag{}
dbTag := &ateapipb.Tag{}
if err := unmarshalStored(currentBytes, dbTag); err != nil {
return nil, fmt.Errorf("unmarshaling actor snapshot tag: %w", err)
return nil, fmt.Errorf("unmarshaling tag: %w", err)
}
if err := validateProtoMetadataMatchesColumns(fmt.Sprintf("actor snapshot tag %s/%s", atespace, name), dbTag.GetMetadata(), currentUID, currentVersion); err != nil {
if err := validateProtoMetadataMatchesColumns(fmt.Sprintf("tag %s/%s", atespace, name), dbTag.GetMetadata(), currentUID, currentVersion); err != nil {
return nil, err
}
if err := precondition.Check(dbTag.GetMetadata()); err != nil {
return nil, err
}
tagBeforeMutation := proto.Clone(dbTag).(*ateapipb.ActorSnapshotTag)
tagBeforeMutation := proto.Clone(dbTag).(*ateapipb.Tag)
if err := mutate(dbTag); err != nil {
return nil, err
}
if err := validateUpdateActorSnapshotTagMutation(tagBeforeMutation, dbTag); err != nil {
if err := validateUpdateTagMutation(tagBeforeMutation, dbTag); err != nil {
return nil, fmt.Errorf("%w: %w", store.ErrImmutableField, err)
}
// Stored metadata is authoritative; discard any metadata edits made by the
@@ -1209,40 +1209,40 @@ func (p *Persistence) UpdateActorSnapshotTag(ctx context.Context, tagRef resourc
updatedBytes, err := proto.Marshal(dbTag)
if err != nil {
return nil, fmt.Errorf("marshaling actor snapshot tag: %w", err)
return nil, fmt.Errorf("marshaling tag: %w", err)
}
commandTag, err := p.pool.Exec(ctx, `
UPDATE actor_snapshot_tags
UPDATE tags
SET version = $1, proto = $2
WHERE atespace = $3 AND name = $4 AND uid = $5 AND version = $6`,
dbTag.GetMetadata().GetVersion(), updatedBytes, atespace, name, currentUID, currentVersion)
if err != nil {
return nil, fmt.Errorf("updating actor snapshot tag %s/%s: %w", atespace, name, err)
return nil, fmt.Errorf("updating tag %s/%s: %w", atespace, name, err)
}
if commandTag.RowsAffected() == 0 {
return nil, store.ErrVersionConflict
}
if commandTag.RowsAffected() != 1 {
return nil, fmt.Errorf("updating actor snapshot tag %s/%s affected %d rows, want 1", atespace, name, commandTag.RowsAffected())
return nil, fmt.Errorf("updating tag %s/%s affected %d rows, want 1", atespace, name, commandTag.RowsAffected())
}
return dbTag, nil
}
func (p *Persistence) DeleteActorSnapshotTag(ctx context.Context, tagRef resources.ActorSnapshotTagRef) (*ateapipb.ActorSnapshotTag, error) {
func (p *Persistence) DeleteTag(ctx context.Context, tagRef resources.TagRef) (*ateapipb.Tag, error) {
atespace, name := tagRef.Atespace, tagRef.Name
var protoBytes []byte
if err := p.pool.QueryRow(ctx, `
DELETE FROM actor_snapshot_tags
DELETE FROM tags
WHERE atespace = $1 AND name = $2
RETURNING proto`, atespace, name).Scan(&protoBytes); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return nil, store.ErrNotFound
}
return nil, fmt.Errorf("deleting actor snapshot tag %s/%s: %w", atespace, name, err)
return nil, fmt.Errorf("deleting tag %s/%s: %w", atespace, name, err)
}
tag := &ateapipb.ActorSnapshotTag{}
tag := &ateapipb.Tag{}
if err := unmarshalStored(protoBytes, tag); err != nil {
return nil, fmt.Errorf("unmarshaling deleted actor snapshot tag: %w", err)
return nil, fmt.Errorf("unmarshaling deleted tag: %w", err)
}
return tag, nil
}
+23 -23
View File
@@ -464,7 +464,7 @@ func appliedMigrationVersions(t *testing.T, pool *pgxpool.Pool) []int64 {
// state, so the statement lives here rather than on Persistence.
func clearAll(t *testing.T, p *Persistence) {
t.Helper()
if _, err := p.pool.Exec(context.Background(), `TRUNCATE atespaces, actors, actor_egress_policies, actor_templates, actor_snapshot_tags, workers, worker_assignments, leases, worker_outbox, worker_outbox_trim`); err != nil {
if _, err := p.pool.Exec(context.Background(), `TRUNCATE atespaces, actors, actor_egress_policies, actor_templates, tags, workers, worker_assignments, leases, worker_outbox, worker_outbox_trim`); err != nil {
t.Fatalf("truncating tables: %v", err)
}
}
@@ -597,7 +597,7 @@ func TestUpdateActorTemplate_ConcurrentWriteReturnsConflict(t *testing.T) {
}
// createTestSuspendedActor seeds an actor holding an external snapshot, which
// is what CreateActorSnapshotTag tags.
// is what CreateTag tags.
func createTestSuspendedActor(t *testing.T, s *Persistence, atespace, name string) *ateapipb.Actor {
t.Helper()
created, err := s.CreateActor(context.Background(), &ateapipb.Actor{
@@ -614,69 +614,69 @@ func createTestSuspendedActor(t *testing.T, s *Persistence, atespace, name strin
return created
}
// createTestActorSnapshotTag creates tagName over its own copy of actor's
// createTestTag creates tagName over its own copy of actor's
// external snapshot, already finalized.
func createTestActorSnapshotTag(t *testing.T, s *Persistence, actor *ateapipb.Actor, tagAtespace, tagName string) *ateapipb.ActorSnapshotTag {
func createTestTag(t *testing.T, s *Persistence, actor *ateapipb.Actor, tagAtespace, tagName string) *ateapipb.Tag {
t.Helper()
tag, err := s.CreateActorSnapshotTag(context.Background(), &ateapipb.ActorSnapshotTag{
tag, err := s.CreateTag(context.Background(), &ateapipb.Tag{
Metadata: &ateapipb.ResourceMetadata{Atespace: tagAtespace, Name: tagName},
Scope: ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE,
Status: &ateapipb.ActorSnapshotTagStatus{
Snapshot: &ateapipb.ExternalSnapshot{SnapshotUri: "gs://bucket/atespaces/" + tagAtespace + "/actor-snapshot-tags/" + tagName},
Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE,
Status: &ateapipb.TagStatus{
Snapshot: &ateapipb.ExternalSnapshot{SnapshotUri: "gs://bucket/atespaces/" + tagAtespace + "/tags/" + tagName},
SourceActorUid: actor.GetMetadata().GetUid(),
},
})
if err != nil {
t.Fatalf("CreateActorSnapshotTag(%s/%s) failed: %v", tagAtespace, tagName, err)
t.Fatalf("CreateTag(%s/%s) failed: %v", tagAtespace, tagName, err)
}
return tag
}
func TestUpdateActorSnapshotTag_CASPreventsDeleteRecreateABA(t *testing.T) {
func TestUpdateTag_CASPreventsDeleteRecreateABA(t *testing.T) {
s := setupPostgresPersistence(t)
ctx := context.Background()
createTestAtespace(t, s, "team-a")
actorA := createTestSuspendedActor(t, s, "team-a", "actor-a")
actorB := createTestSuspendedActor(t, s, "team-a", "actor-b")
original := createTestActorSnapshotTag(t, s, actorA, "team-a", "tag-a")
original := createTestTag(t, s, actorA, "team-a", "tag-a")
mutations := 0
var recreated *ateapipb.ActorSnapshotTag
_, err := s.UpdateActorSnapshotTag(ctx, resources.ActorSnapshotTagRef{Atespace: "team-a", Name: "tag-a"}, store.PreconditionFrom(original), func(toUpdate *ateapipb.ActorSnapshotTag) error {
var recreated *ateapipb.Tag
_, err := s.UpdateTag(ctx, resources.TagRef{Atespace: "team-a", Name: "tag-a"}, store.PreconditionFrom(original), func(toUpdate *ateapipb.Tag) error {
mutations++
if _, err := s.DeleteActorSnapshotTag(ctx, resources.ActorSnapshotTagRef{Atespace: "team-a", Name: "tag-a"}); err != nil {
if _, err := s.DeleteTag(ctx, resources.TagRef{Atespace: "team-a", Name: "tag-a"}); err != nil {
return fmt.Errorf("deleting original tag: %w", err)
}
recreated = createTestActorSnapshotTag(t, s, actorB, "team-a", "tag-a")
toUpdate.Scope = ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED
recreated = createTestTag(t, s, actorB, "team-a", "tag-a")
toUpdate.Scope = ateapipb.TagScope_TAG_SCOPE_PUBLISHED
return nil
})
if !errors.Is(err, store.ErrVersionConflict) {
t.Fatalf("UpdateActorSnapshotTag error = %v, want ErrVersionConflict", err)
t.Fatalf("UpdateTag error = %v, want ErrVersionConflict", err)
}
if mutations != 1 {
t.Errorf("guarded mutation ran %d times, want 1", mutations)
}
stored, err := s.GetActorSnapshotTag(ctx, resources.ActorSnapshotTagRef{Atespace: "team-a", Name: "tag-a"})
stored, err := s.GetTag(ctx, resources.TagRef{Atespace: "team-a", Name: "tag-a"})
if err != nil {
t.Fatalf("GetActorSnapshotTag failed: %v", err)
t.Fatalf("GetTag failed: %v", err)
}
if diff := cmp.Diff(recreated, stored, protocmp.Transform()); diff != "" {
t.Errorf("recreated tag was overwritten (-want +got):\n%s", diff)
}
}
func TestCreateActorSnapshotTag_TagForeignKeyErrors(t *testing.T) {
func TestCreateTag_TagForeignKeyErrors(t *testing.T) {
s := setupPostgresPersistence(t)
ctx := context.Background()
createTestAtespace(t, s, "team-a")
actor := createTestSuspendedActor(t, s, "team-a", "actor-a")
// A tag in an atespace that does not exist trips the tag's atespace FK.
_, err := s.CreateActorSnapshotTag(ctx, &ateapipb.ActorSnapshotTag{
_, err := s.CreateTag(ctx, &ateapipb.Tag{
Metadata: &ateapipb.ResourceMetadata{Atespace: "gone", Name: "latest"},
Status: &ateapipb.ActorSnapshotTagStatus{
InProgressSnapshotUri: "gs://bucket/atespaces/gone/actor-snapshot-tags/latest",
Status: &ateapipb.TagStatus{
InProgressSnapshotUri: "gs://bucket/atespaces/gone/tags/latest",
SourceActorUid: actor.GetMetadata().GetUid(),
},
})
@@ -52,14 +52,14 @@ CREATE TABLE actor_templates (
PRIMARY KEY (atespace, name)
);
CREATE TABLE actor_snapshot_tags (
CREATE TABLE tags (
atespace text NOT NULL,
name text NOT NULL,
uid text NOT NULL,
version bigint NOT NULL,
proto bytea NOT NULL,
PRIMARY KEY (atespace, name),
CONSTRAINT actor_snapshot_tags_atespace_fk
CONSTRAINT tags_atespace_fk
FOREIGN KEY (atespace) REFERENCES atespaces(name) ON DELETE RESTRICT
);
+1 -1
View File
@@ -34,7 +34,7 @@ const (
kindAtespace resourceKind = "atespace"
kindActor resourceKind = "actor"
kindActorTemplate resourceKind = "actor-template"
kindSnapshotTag resourceKind = "snapshot-tag"
kindTag resourceKind = "tag"
kindWorker resourceKind = "worker"
kindWorkerAssign resourceKind = "worker-assignment"
)
+9 -9
View File
@@ -114,22 +114,22 @@ type Interface interface {
// Deletes and returns an Actor's policy subresource.
DeleteEgressPolicy(ctx context.Context, actorRef resources.ActorRef) (*ateapipb.EgressPolicy, error)
// CreateActorSnapshotTag creates an immutable tag to an actor snapshot.
// CreateTag creates an immutable tag to an actor snapshot.
//
// Returns ErrAlreadyExists if the name is taken — including by the caller's
// own unfinished attempt, which it can then read back and resume — or
// ErrFailedPrecondition if the tag's atespace does not exist.
CreateActorSnapshotTag(ctx context.Context, tag *ateapipb.ActorSnapshotTag) (*ateapipb.ActorSnapshotTag, error)
CreateTag(ctx context.Context, tag *ateapipb.Tag) (*ateapipb.Tag, error)
// Fetches an Atespace-owned tag by reference. Returns ErrNotFound if
// missing.
GetActorSnapshotTag(ctx context.Context, tagRef resources.ActorSnapshotTagRef) (*ateapipb.ActorSnapshotTag, error)
GetTag(ctx context.Context, tagRef resources.TagRef) (*ateapipb.Tag, error)
// Lists ActorSnapshotTags in one atespace, or all atespaces when empty.
ListActorSnapshotTags(ctx context.Context, atespace string, opts ListOptions) (ListResponse[*ateapipb.ActorSnapshotTag], error)
// Lists Tags in one atespace, or all atespaces when empty.
ListTags(ctx context.Context, atespace string, opts ListOptions) (ListResponse[*ateapipb.Tag], error)
// UpdateActorSnapshotTag performs a transactional read-modify-write on the tag
// addressed by tagRef, and returns the stored ActorSnapshotTag with
// UpdateTag performs a transactional read-modify-write on the tag
// addressed by tagRef, and returns the stored Tag with
// advanced metadata (version, update_time).
//
// precondition guards the write against landing on unexpected state: it is
@@ -149,10 +149,10 @@ type Interface interface {
// immutable for its lifetime, or the mutate's error verbatim otherwise.
//
// status.snapshot is immutable once set
UpdateActorSnapshotTag(ctx context.Context, tagRef resources.ActorSnapshotTagRef, precondition Precondition, mutate func(toUpdate *ateapipb.ActorSnapshotTag) error) (*ateapipb.ActorSnapshotTag, error)
UpdateTag(ctx context.Context, tagRef resources.TagRef, precondition Precondition, mutate func(toUpdate *ateapipb.Tag) error) (*ateapipb.Tag, error)
// Deletes and returns a tag.
DeleteActorSnapshotTag(ctx context.Context, tagRef resources.ActorSnapshotTagRef) (*ateapipb.ActorSnapshotTag, error)
DeleteTag(ctx context.Context, tagRef resources.TagRef) (*ateapipb.Tag, error)
// Stores a new atespace and returns the stored resource with server-assigned
// metadata (uid, version, timestamps). The input is not mutated. Returns
@@ -56,7 +56,7 @@ func testActorSnapshotURI(location, atespace, name string) string {
}
func testTagSnapshotURI(location, atespace, name string) string {
return location + "/atespaces/" + atespace + "/actor-snapshot-tags/" + name
return location + "/atespaces/" + atespace + "/tags/" + name
}
// Worker resource names. They are opaque to the store, which only ever uses
@@ -206,7 +206,7 @@ func RunContractTests(t *testing.T, setup func(t *testing.T) store.Interface) {
runWorkerAssignmentContractTests(t, setup)
runAtespaceContractTests(t, setup)
runActorTemplateContractTests(t, setup)
runActorSnapshotTagContractTests(t, setup)
runTagContractTests(t, setup)
runLeaseContractTests(t, setup)
runListOptionsContractTests(t, setup)
runUnknownFieldContractTests(t, setup)
@@ -329,7 +329,7 @@ func runListOptionsContractTests(t *testing.T, setup func(t *testing.T) store.In
{"atespaces", func(opts store.ListOptions) error { _, err := s.ListAtespaces(ctx, opts); return err }},
{"actors", func(opts store.ListOptions) error { _, err := s.ListActors(ctx, "", opts); return err }},
{"actor templates", func(opts store.ListOptions) error { _, err := s.ListActorTemplates(ctx, "", opts); return err }},
{"actor snapshot tags", func(opts store.ListOptions) error { _, err := s.ListActorSnapshotTags(ctx, "", opts); return err }},
{"tags", func(opts store.ListOptions) error { _, err := s.ListTags(ctx, "", opts); return err }},
{"workers", func(opts store.ListOptions) error { _, err := s.ListWorkers(ctx, opts); return err }},
}
for _, call := range calls {
@@ -354,7 +354,7 @@ func runListOptionsContractTests(t *testing.T, setup func(t *testing.T) store.In
{"atespaces", func(opts store.ListOptions) error { _, err := s.ListAtespaces(ctx, opts); return err }},
{"actors", func(opts store.ListOptions) error { _, err := s.ListActors(ctx, "", opts); return err }},
{"actor templates", func(opts store.ListOptions) error { _, err := s.ListActorTemplates(ctx, "", opts); return err }},
{"actor snapshot tags", func(opts store.ListOptions) error { _, err := s.ListActorSnapshotTags(ctx, "", opts); return err }},
{"tags", func(opts store.ListOptions) error { _, err := s.ListTags(ctx, "", opts); return err }},
{"workers", func(opts store.ListOptions) error { _, err := s.ListWorkers(ctx, opts); return err }},
}
for _, call := range calls {
@@ -981,7 +981,7 @@ func runActorTemplateContractTests(t *testing.T, setup func(t *testing.T) store.
}
// newTestSuspendedActor builds an actor that already holds an external
// snapshot, the state CreateActorSnapshotTag tags from.
// snapshot, the state CreateTag tags from.
func newTestSuspendedActor(atespace, name string) *ateapipb.Actor {
return &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Atespace: atespace, Name: name},
@@ -996,15 +996,15 @@ func newTestSuspendedActor(atespace, name string) *ateapipb.Actor {
}
}
// newTestInProgressSnapshotTag builds the row CreateActorSnapshotTag reserves for a
// newTestInProgressTag builds the row CreateTag reserves for a
// tag of actor: ATESPACE-scoped, with no snapshot yet and the destination of
// the copy still to come recorded in status.in_progress_snapshot_uri.
func newTestInProgressSnapshotTag(name string, actor *ateapipb.Actor) *ateapipb.ActorSnapshotTag {
func newTestInProgressTag(name string, actor *ateapipb.Actor) *ateapipb.Tag {
atespace := actor.GetMetadata().GetAtespace()
return &ateapipb.ActorSnapshotTag{
return &ateapipb.Tag{
Metadata: &ateapipb.ResourceMetadata{Atespace: atespace, Name: name},
Scope: ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE,
Status: &ateapipb.ActorSnapshotTagStatus{
Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE,
Status: &ateapipb.TagStatus{
ActorTemplateUid: "template-uid",
InProgressSnapshotUri: testTagSnapshotURI("gs://private", atespace, name),
SourceActorUid: actor.GetMetadata().GetUid(),
@@ -1012,7 +1012,7 @@ func newTestInProgressSnapshotTag(name string, actor *ateapipb.Actor) *ateapipb.
}
}
func runActorSnapshotTagContractTests(t *testing.T, setup func(t *testing.T) store.Interface) {
func runTagContractTests(t *testing.T, setup func(t *testing.T) store.Interface) {
t.Helper()
// seedSuspendedActor seeds an atespace and an actor holding an external
@@ -1027,26 +1027,26 @@ func runActorSnapshotTagContractTests(t *testing.T, setup func(t *testing.T) sto
return actor
}
t.Run("ActorSnapshotTag_Lifecycle", func(t *testing.T) {
t.Run("Tag_Lifecycle", func(t *testing.T) {
s := setup(t)
ctx := context.Background()
actor := seedSuspendedActor(t, s, "team-a", "actor-1")
inProgressTag := newTestInProgressSnapshotTag("production", actor)
pending, err := s.CreateActorSnapshotTag(ctx, inProgressTag)
inProgressTag := newTestInProgressTag("production", actor)
pending, err := s.CreateTag(ctx, inProgressTag)
if err != nil {
t.Fatalf("CreateActorSnapshotTag failed: %v", err)
t.Fatalf("CreateTag failed: %v", err)
}
if pending.GetMetadata().GetVersion() != 1 || pending.GetMetadata().GetUid() == "" {
t.Errorf("created tag metadata = %v, want server-owned uid and version 1", pending.GetMetadata())
}
if inProgressTag.GetMetadata().GetUid() != "" || inProgressTag.GetMetadata().GetVersion() != 0 {
t.Errorf("CreateActorSnapshotTag mutated its input metadata: %v", inProgressTag.GetMetadata())
t.Errorf("CreateTag mutated its input metadata: %v", inProgressTag.GetMetadata())
}
tag, err := s.GetActorSnapshotTag(ctx, resources.ActorSnapshotTagRef{Atespace: "team-a", Name: "production"})
tag, err := s.GetTag(ctx, resources.TagRef{Atespace: "team-a", Name: "production"})
if err != nil {
t.Fatalf("GetActorSnapshotTag failed: %v", err)
t.Fatalf("GetTag failed: %v", err)
}
// A reserved tag names where its copy is going and nothing else: it is
// not usable until the copy lands and finalize names it.
@@ -1058,15 +1058,15 @@ func runActorSnapshotTagContractTests(t *testing.T, setup func(t *testing.T) sto
}
// Try to get a tag that doens't exist
if _, err := s.GetActorSnapshotTag(ctx, resources.ActorSnapshotTagRef{Atespace: "team-a", Name: "missing"}); !errors.Is(err, store.ErrNotFound) {
t.Errorf("missing GetActorSnapshotTag = %v, want ErrNotFound", err)
if _, err := s.GetTag(ctx, resources.TagRef{Atespace: "team-a", Name: "missing"}); !errors.Is(err, store.ErrNotFound) {
t.Errorf("missing GetTag = %v, want ErrNotFound", err)
}
// Finalize: naming the snapshot and clearing the in-progress URI
// together is the one transition status.snapshot is allowed.
ready, err := s.UpdateActorSnapshotTag(ctx, resources.ActorSnapshotTagRef{Atespace: "team-a", Name: "production"}, store.PreconditionFrom(tag), finalizeTag)
ready, err := s.UpdateTag(ctx, resources.TagRef{Atespace: "team-a", Name: "production"}, store.PreconditionFrom(tag), finalizeTag)
if err != nil {
t.Fatalf("finalizing actor snapshot tag failed: %v", err)
t.Fatalf("finalizing tag failed: %v", err)
}
// Check if the pending URI was promoted to the Snapshot field.
if ready.GetStatus().GetSnapshot().GetSnapshotUri() != tag.GetStatus().GetInProgressSnapshotUri() {
@@ -1076,133 +1076,133 @@ func runActorSnapshotTagContractTests(t *testing.T, setup func(t *testing.T) sto
t.Errorf("finalized tag in-progress uri = %q, want it cleared", ready.GetStatus().GetInProgressSnapshotUri())
}
updated, err := s.UpdateActorSnapshotTag(ctx, resources.ActorSnapshotTagRef{Atespace: "team-a", Name: "production"}, store.PreconditionFrom(ready), func(toUpdate *ateapipb.ActorSnapshotTag) error {
toUpdate.Scope = ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED
updated, err := s.UpdateTag(ctx, resources.TagRef{Atespace: "team-a", Name: "production"}, store.PreconditionFrom(ready), func(toUpdate *ateapipb.Tag) error {
toUpdate.Scope = ateapipb.TagScope_TAG_SCOPE_PUBLISHED
return nil
})
if err != nil {
t.Fatalf("UpdateActorSnapshotTag failed: %v", err)
t.Fatalf("UpdateTag failed: %v", err)
}
if updated.GetScope() != ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED || updated.GetMetadata().GetVersion() != ready.GetMetadata().GetVersion()+1 {
if updated.GetScope() != ateapipb.TagScope_TAG_SCOPE_PUBLISHED || updated.GetMetadata().GetVersion() != ready.GetMetadata().GetVersion()+1 {
t.Errorf("updated tag = %v, want published scope and advanced version", updated)
}
if _, err := s.UpdateActorSnapshotTag(ctx, resources.ActorSnapshotTagRef{Atespace: "team-a", Name: "production"}, store.PreconditionFrom(ready), func(toUpdate *ateapipb.ActorSnapshotTag) error {
if _, err := s.UpdateTag(ctx, resources.TagRef{Atespace: "team-a", Name: "production"}, store.PreconditionFrom(ready), func(toUpdate *ateapipb.Tag) error {
toUpdate.Scope = ready.GetScope()
return nil
}); !errors.Is(err, store.ErrVersionConflict) {
t.Errorf("stale UpdateActorSnapshotTag = %v, want ErrVersionConflict", err)
t.Errorf("stale UpdateTag = %v, want ErrVersionConflict", err)
}
if _, err := s.DeleteAtespace(ctx, "team-a"); !errors.Is(err, store.ErrFailedPrecondition) {
t.Errorf("DeleteAtespace with tag = %v, want ErrFailedPrecondition", err)
}
deleted, err := s.DeleteActorSnapshotTag(ctx, resources.ActorSnapshotTagRef{Atespace: "team-a", Name: "production"})
deleted, err := s.DeleteTag(ctx, resources.TagRef{Atespace: "team-a", Name: "production"})
if err != nil || !proto.Equal(deleted, updated) {
t.Errorf("DeleteActorSnapshotTag = (%v, %v), want updated tag", deleted, err)
t.Errorf("DeleteTag = (%v, %v), want updated tag", deleted, err)
}
if _, err := s.GetActorSnapshotTag(ctx, resources.ActorSnapshotTagRef{Atespace: "team-a", Name: "production"}); !errors.Is(err, store.ErrNotFound) {
t.Errorf("deleted GetActorSnapshotTag = %v, want ErrNotFound", err)
if _, err := s.GetTag(ctx, resources.TagRef{Atespace: "team-a", Name: "production"}); !errors.Is(err, store.ErrNotFound) {
t.Errorf("deleted GetTag = %v, want ErrNotFound", err)
}
})
t.Run("ActorSnapshotTag_ImmutableFields", func(t *testing.T) {
t.Run("Tag_ImmutableFields", func(t *testing.T) {
s := setup(t)
ctx := context.Background()
actor := seedSuspendedActor(t, s, "team-a", "actor-1")
tag := storeTag(t, s, newTestInProgressSnapshotTag("production", actor))
tag := storeTag(t, s, newTestInProgressTag("production", actor))
tests := []struct {
name string
mutate func(*ateapipb.ActorSnapshotTag)
mutate func(*ateapipb.Tag)
}{
{
name: "snapshot uri",
mutate: func(toUpdate *ateapipb.ActorSnapshotTag) {
mutate: func(toUpdate *ateapipb.Tag) {
toUpdate.Status.Snapshot.SnapshotUri = "gs://private/elsewhere"
},
},
{
name: "snapshot content scope",
mutate: func(toUpdate *ateapipb.ActorSnapshotTag) {
mutate: func(toUpdate *ateapipb.Tag) {
toUpdate.Status.Snapshot.ContentScope = ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_DATA
},
},
{
name: "clearing the snapshot",
mutate: func(toUpdate *ateapipb.ActorSnapshotTag) {
mutate: func(toUpdate *ateapipb.Tag) {
toUpdate.Status.Snapshot = nil
},
},
{
name: "reopening the in-progress snapshot uri",
mutate: func(toUpdate *ateapipb.ActorSnapshotTag) {
mutate: func(toUpdate *ateapipb.Tag) {
toUpdate.Status.InProgressSnapshotUri = testTagSnapshotURI("gs://private", "team-a", "again")
},
},
{
name: "actor template uid",
mutate: func(toUpdate *ateapipb.ActorSnapshotTag) { toUpdate.Status.ActorTemplateUid = "other-template-uid" },
mutate: func(toUpdate *ateapipb.Tag) { toUpdate.Status.ActorTemplateUid = "other-template-uid" },
},
{
name: "source actor uid",
mutate: func(toUpdate *ateapipb.ActorSnapshotTag) { toUpdate.Status.SourceActorUid = "other-actor-uid" },
mutate: func(toUpdate *ateapipb.Tag) { toUpdate.Status.SourceActorUid = "other-actor-uid" },
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := s.UpdateActorSnapshotTag(ctx, resources.ActorSnapshotTagRef{Atespace: "team-a", Name: "production"}, store.PreconditionFrom(tag), func(toUpdate *ateapipb.ActorSnapshotTag) error {
_, err := s.UpdateTag(ctx, resources.TagRef{Atespace: "team-a", Name: "production"}, store.PreconditionFrom(tag), func(toUpdate *ateapipb.Tag) error {
tt.mutate(toUpdate)
return nil
})
if !errors.Is(err, store.ErrImmutableField) {
t.Errorf("UpdateActorSnapshotTag error = %v, want one matching store.ErrImmutableField", err)
t.Errorf("UpdateTag error = %v, want one matching store.ErrImmutableField", err)
}
})
}
})
t.Run("CreateActorSnapshotTag_ReusedTagName", func(t *testing.T) {
t.Run("CreateTag_ReusedTagName", func(t *testing.T) {
s := setup(t)
ctx := context.Background()
actor := seedSuspendedActor(t, s, "team-a", "actor-1")
tagInput := newTestInProgressSnapshotTag("production", actor)
if _, err := s.CreateActorSnapshotTag(ctx, tagInput); err != nil {
t.Fatalf("CreateActorSnapshotTag failed: %v", err)
tagInput := newTestInProgressTag("production", actor)
if _, err := s.CreateTag(ctx, tagInput); err != nil {
t.Fatalf("CreateTag failed: %v", err)
}
// Create the same tag
if _, err := s.CreateActorSnapshotTag(ctx, tagInput); !errors.Is(err, store.ErrAlreadyExists) {
t.Errorf("duplicate CreateActorSnapshotTag = %v, want ErrAlreadyExists", err)
if _, err := s.CreateTag(ctx, tagInput); !errors.Is(err, store.ErrAlreadyExists) {
t.Errorf("duplicate CreateTag = %v, want ErrAlreadyExists", err)
}
// Stored row is untouched
stored, err := s.GetActorSnapshotTag(ctx, resources.ActorSnapshotTagRef{Atespace: "team-a", Name: "production"})
stored, err := s.GetTag(ctx, resources.TagRef{Atespace: "team-a", Name: "production"})
if err != nil {
t.Fatalf("GetActorSnapshotTag failed: %v", err)
t.Fatalf("GetTag failed: %v", err)
}
if stored.GetMetadata().GetVersion() != 1 {
t.Errorf("tag version after rejected create = %d, want 1", stored.GetMetadata().GetVersion())
}
})
t.Run("CreateActorSnapshotTag_UnknownAtespace", func(t *testing.T) {
t.Run("CreateTag_UnknownAtespace", func(t *testing.T) {
s := setup(t)
ctx := context.Background()
actor := seedSuspendedActor(t, s, "team-a", "actor-1")
tag := newTestInProgressSnapshotTag("production", actor)
tag := newTestInProgressTag("production", actor)
tag.Metadata.Atespace = "team-missing"
if _, err := s.CreateActorSnapshotTag(ctx, tag); !errors.Is(err, store.ErrFailedPrecondition) {
t.Errorf("CreateActorSnapshotTag in an unknown atespace = %v, want ErrFailedPrecondition", err)
if _, err := s.CreateTag(ctx, tag); !errors.Is(err, store.ErrFailedPrecondition) {
t.Errorf("CreateTag in an unknown atespace = %v, want ErrFailedPrecondition", err)
}
})
t.Run("UpdateActorSnapshotTag_MissingPrecondition", func(t *testing.T) {
t.Run("UpdateTag_MissingPrecondition", func(t *testing.T) {
s := setup(t)
ctx := context.Background()
actor := seedSuspendedActor(t, s, "team-a", "actor-1")
created := storeTag(t, s, newTestInProgressSnapshotTag("production", actor))
created := storeTag(t, s, newTestInProgressTag("production", actor))
tests := []struct {
name string
@@ -1224,32 +1224,32 @@ func runActorSnapshotTagContractTests(t *testing.T, setup func(t *testing.T) sto
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
_, err := s.UpdateActorSnapshotTag(ctx, resources.ActorSnapshotTagRef{Atespace: "team-a", Name: "production"}, tt.precondition, func(toUpdate *ateapipb.ActorSnapshotTag) error {
_, err := s.UpdateTag(ctx, resources.TagRef{Atespace: "team-a", Name: "production"}, tt.precondition, func(toUpdate *ateapipb.Tag) error {
t.Fatal("mutate ran for a blind write")
return nil
})
if !errors.Is(err, store.ErrPreconditionRequired) {
t.Errorf("UpdateActorSnapshotTag error = %v, want one matching store.ErrPreconditionRequired", err)
t.Errorf("UpdateTag error = %v, want one matching store.ErrPreconditionRequired", err)
}
})
}
})
t.Run("ListActorSnapshotTags_PaginationAndScope", func(t *testing.T) {
t.Run("ListTags_PaginationAndScope", func(t *testing.T) {
s := setup(t)
ctx := context.Background()
for _, atespace := range []string{"team-a", "team-b"} {
actor := seedSuspendedActor(t, s, atespace, "actor-1")
for i := 0; i < 3; i++ {
storeTag(t, s, newTestInProgressSnapshotTag(fmt.Sprintf("tag-%d", i), actor))
storeTag(t, s, newTestInProgressTag(fmt.Sprintf("tag-%d", i), actor))
}
}
var scoped []*ateapipb.ActorSnapshotTag
var scoped []*ateapipb.Tag
for token := ""; ; {
page, err := s.ListActorSnapshotTags(ctx, "team-a", store.ListOptions{PageSize: 2, PageToken: token})
page, err := s.ListTags(ctx, "team-a", store.ListOptions{PageSize: 2, PageToken: token})
if err != nil {
t.Fatalf("scoped ListActorSnapshotTags failed: %v", err)
t.Fatalf("scoped ListTags failed: %v", err)
}
scoped = append(scoped, page.Items...)
if page.NextPageToken == "" {
@@ -1258,14 +1258,14 @@ func runActorSnapshotTagContractTests(t *testing.T, setup func(t *testing.T) sto
token = page.NextPageToken
}
if len(scoped) != 3 {
t.Errorf("scoped ListActorSnapshotTags returned %d tags, want 3", len(scoped))
t.Errorf("scoped ListTags returned %d tags, want 3", len(scoped))
}
var global []*ateapipb.ActorSnapshotTag
var global []*ateapipb.Tag
for token := ""; ; {
page, err := s.ListActorSnapshotTags(ctx, "", store.ListOptions{PageSize: 2, PageToken: token})
page, err := s.ListTags(ctx, "", store.ListOptions{PageSize: 2, PageToken: token})
if err != nil {
t.Fatalf("global ListActorSnapshotTags failed: %v", err)
t.Fatalf("global ListTags failed: %v", err)
}
global = append(global, page.Items...)
if page.NextPageToken == "" {
@@ -1274,7 +1274,7 @@ func runActorSnapshotTagContractTests(t *testing.T, setup func(t *testing.T) sto
token = page.NextPageToken
}
if len(global) != 6 {
t.Errorf("global ListActorSnapshotTags returned %d tags, want 6", len(global))
t.Errorf("global ListTags returned %d tags, want 6", len(global))
}
})
}
@@ -1282,7 +1282,7 @@ func runActorSnapshotTagContractTests(t *testing.T, setup func(t *testing.T) sto
// finalizeTag mutates a reserved tag the way the tag workflow's second
// transaction does: it names the copy that landed and clears the in-progress
// URI, in one update.
func finalizeTag(toUpdate *ateapipb.ActorSnapshotTag) error {
func finalizeTag(toUpdate *ateapipb.Tag) error {
toUpdate.Status.Snapshot = &ateapipb.ExternalSnapshot{
SnapshotUri: toUpdate.GetStatus().GetInProgressSnapshotUri(),
ContentScope: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL,
@@ -1293,15 +1293,15 @@ func finalizeTag(toUpdate *ateapipb.ActorSnapshotTag) error {
// storeTag reserves and finalizes tag, for tests whose subject is a usable tag
// rather than the two-phase create itself.
func storeTag(t *testing.T, s store.Interface, tag *ateapipb.ActorSnapshotTag) *ateapipb.ActorSnapshotTag {
func storeTag(t *testing.T, s store.Interface, tag *ateapipb.Tag) *ateapipb.Tag {
t.Helper()
reserved, err := s.CreateActorSnapshotTag(context.Background(), tag)
reserved, err := s.CreateTag(context.Background(), tag)
if err != nil {
t.Fatalf("CreateActorSnapshotTag(%q) failed: %v", tag.GetMetadata().GetName(), err)
t.Fatalf("CreateTag(%q) failed: %v", tag.GetMetadata().GetName(), err)
}
stored, err := s.UpdateActorSnapshotTag(context.Background(), resources.ActorSnapshotTagRefFromActorSnapshotTag(reserved), store.PreconditionFrom(reserved), finalizeTag)
stored, err := s.UpdateTag(context.Background(), resources.TagRefFromTag(reserved), store.PreconditionFrom(reserved), finalizeTag)
if err != nil {
t.Fatalf("finalizing actor snapshot tag %q failed: %v", tag.GetMetadata().GetName(), err)
t.Fatalf("finalizing tag %q failed: %v", tag.GetMetadata().GetName(), err)
}
return stored
}
@@ -2889,7 +2889,7 @@ func runUnknownFieldContractTests(t *testing.T, setup func(t *testing.T) store.I
assertPruned(t, "DeleteEgressPolicy", created, deleted)
})
t.Run("UnknownFields_ActorSnapshotTag", func(t *testing.T) {
t.Run("UnknownFields_Tag", func(t *testing.T) {
s := setup(t)
ctx := context.Background()
mustCreateAtespace(t, s, testAtespace)
@@ -2898,41 +2898,41 @@ func runUnknownFieldContractTests(t *testing.T, setup func(t *testing.T) store.I
t.Fatalf("CreateActor failed: %v", err)
}
tagRef := resources.ActorSnapshotTagRef{Atespace: testAtespace, Name: "production"}
tag := withUnknownField(newTestInProgressSnapshotTag(tagRef.Name, actor))
tagRef := resources.TagRef{Atespace: testAtespace, Name: "production"}
tag := withUnknownField(newTestInProgressTag(tagRef.Name, actor))
withUnknownField(tag.Metadata)
withUnknownField(tag.Status)
created, err := s.CreateActorSnapshotTag(ctx, tag)
created, err := s.CreateTag(ctx, tag)
if err != nil {
t.Fatalf("CreateActorSnapshotTag failed: %v", err)
t.Fatalf("CreateTag failed: %v", err)
}
got, err := s.GetActorSnapshotTag(ctx, tagRef)
got, err := s.GetTag(ctx, tagRef)
if err != nil {
t.Fatalf("GetActorSnapshotTag failed: %v", err)
t.Fatalf("GetTag failed: %v", err)
}
assertPruned(t, "GetActorSnapshotTag", created, got)
assertPruned(t, "GetTag", created, got)
list, err := s.ListActorSnapshotTags(ctx, testAtespace, store.ListOptions{})
list, err := s.ListTags(ctx, testAtespace, store.ListOptions{})
if err != nil || len(list.Items) != 1 {
t.Fatalf("ListActorSnapshotTags = %d items, %v; want 1", len(list.Items), err)
t.Fatalf("ListTags = %d items, %v; want 1", len(list.Items), err)
}
assertPruned(t, "ListActorSnapshotTags", created, list.Items[0])
assertPruned(t, "ListTags", created, list.Items[0])
updated, err := s.UpdateActorSnapshotTag(ctx, tagRef, store.PreconditionFrom(got), func(toUpdate *ateapipb.ActorSnapshotTag) error {
requireNoUnknownFields(t, "UpdateActorSnapshotTag mutation input", toUpdate)
updated, err := s.UpdateTag(ctx, tagRef, store.PreconditionFrom(got), func(toUpdate *ateapipb.Tag) error {
requireNoUnknownFields(t, "UpdateTag mutation input", toUpdate)
return nil
})
if err != nil {
t.Fatalf("UpdateActorSnapshotTag failed: %v", err)
t.Fatalf("UpdateTag failed: %v", err)
}
assertPruned(t, "UpdateActorSnapshotTag", created, updated)
assertPruned(t, "UpdateTag", created, updated)
deleted, err := s.DeleteActorSnapshotTag(ctx, tagRef)
deleted, err := s.DeleteTag(ctx, tagRef)
if err != nil {
t.Fatalf("DeleteActorSnapshotTag failed: %v", err)
t.Fatalf("DeleteTag failed: %v", err)
}
assertPruned(t, "DeleteActorSnapshotTag", created, deleted)
assertPruned(t, "DeleteTag", created, deleted)
})
t.Run("UnknownFields_Atespace", func(t *testing.T) {
@@ -108,18 +108,18 @@ func MustCreateActor(t *testing.T, ctx context.Context, s store.Interface, actor
return created
}
// MustCreateActorSnapshotTag ensures tag's parent atespace exists, then stores
// MustCreateTag ensures tag's parent atespace exists, then stores
// tag as given — ready or still pending, whichever state the test needs to
// start from. Use the store method directly only in tests that exercise
// missing-parent behavior.
func MustCreateActorSnapshotTag(t *testing.T, ctx context.Context, s store.Interface, tag *ateapipb.ActorSnapshotTag) *ateapipb.ActorSnapshotTag {
func MustCreateTag(t *testing.T, ctx context.Context, s store.Interface, tag *ateapipb.Tag) *ateapipb.Tag {
t.Helper()
atespace := tag.GetMetadata().GetAtespace()
name := tag.GetMetadata().GetName()
MustCreateAtespace(t, ctx, s, atespace)
created, err := s.CreateActorSnapshotTag(ctx, tag)
created, err := s.CreateTag(ctx, tag)
if err != nil {
t.Fatalf("creating test actor snapshot tag %q/%q: %v", atespace, name, err)
t.Fatalf("creating test tag %q/%q: %v", atespace, name, err)
}
return created
}
+9 -9
View File
@@ -143,7 +143,7 @@ kubectl ate get atespace <atespace>
kubectl ate delete atespace <atespace>
```
> **Note:** `create actor … -a <atespace>` requires the atespace to already exist, otherwise it fails with `FailedPrecondition`. `delete atespace` only removes an **empty** atespace; delete its actors and snapshot tags first (cascade delete is not yet supported).
> **Note:** `create actor … -a <atespace>` requires the atespace to already exist, otherwise it fails with `FailedPrecondition`. `delete atespace` only removes an **empty** atespace; delete its actors and tags first (cascade delete is not yet supported).
#### `kubectl ate get atespace` output columns
@@ -220,18 +220,18 @@ so suspending or deleting the actor afterwards cannot collect it.
```bash
# List an Atespace's tags, all Atespaces' tags, or resolve tags by name.
kubectl ate get snapshot-tags -a <atespace>
kubectl ate get snapshot-tags -A
kubectl ate get snapshot-tag <tag-name> [<tag-name> ...] -a <atespace>
kubectl ate get tags -a <atespace>
kubectl ate get tags -A
kubectl ate get tag <tag-name> [<tag-name> ...] -a <atespace>
# Tag the snapshot a suspended actor holds, then publish or unpublish the tag.
kubectl ate create snapshot-tag <tag-name> -a <atespace> --actor <actor-name> [--scope published]
kubectl ate update snapshot-tag <tag-name> -a <atespace> --scope published
kubectl ate update snapshot-tag <tag-name> -a <atespace> --scope atespace
kubectl ate create tag <tag-name> -a <atespace> --actor <actor-name> [--scope published]
kubectl ate update tag <tag-name> -a <atespace> --scope published
kubectl ate update tag <tag-name> -a <atespace> --scope atespace
# Create an actor from a tag and remove the tag when it is no longer needed.
kubectl ate create actor <actor-name> -a <atespace> --template-ref <template-name> --snapshot-tag <tag-atespace/tag-name>
kubectl ate delete snapshot-tag <tag-name> -a <atespace>
kubectl ate create actor <actor-name> -a <atespace> --template-ref <template-name> --tag <tag-atespace/tag-name>
kubectl ate delete tag <tag-name> -a <atespace>
```
### Logs
@@ -1,235 +0,0 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"fmt"
"strings"
"github.com/agent-substrate/substrate/cmd/kubectl-ate/internal/printer"
"github.com/agent-substrate/substrate/internal/ateclient"
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
"github.com/spf13/cobra"
"google.golang.org/grpc"
)
var (
tagAtespaceFlag string
tagAllAtespacesFlag bool
createTagAtespaceFlag string
createTagActorFlag string
createTagScopeFlag string
updateTagAtespaceFlag string
updateTagScopeFlag string
deleteTagAtespaceFlag string
)
var updateCmd = &cobra.Command{Use: "update", Short: "Update a resource"}
var getActorSnapshotTagsCmd = &cobra.Command{
Use: "actor-snapshot-tags [tag-name ...]",
Aliases: []string{"actor-snapshot-tag", "snapshot-tags", "snapshot-tag"},
Short: "List or get actor snapshot tags",
RunE: func(cmd *cobra.Command, args []string) error {
if tagAllAtespacesFlag && tagAtespaceFlag != "" {
return fmt.Errorf("--atespace and -A/--all-atespaces are mutually exclusive")
}
if len(args) > 0 && tagAtespaceFlag == "" {
return fmt.Errorf("--atespace is required when getting snapshot tags")
}
if len(args) == 0 && !tagAllAtespacesFlag && tagAtespaceFlag == "" {
return fmt.Errorf("specify --atespace <name>, or -A/--all-atespaces")
}
ctx := cmd.Context()
client, err := ateclient.NewClient(ctx, kubeconfig, k8sContext, endpoint, tokenFile, traceEnabled)
if err != nil {
return fmt.Errorf("failed to connect to ate-api-server: %w", err)
}
defer client.Close()
var tags []*ateapipb.ActorSnapshotTag
if len(args) > 0 {
for _, name := range args {
tag, err := client.GetActorSnapshotTag(ctx, &ateapipb.GetActorSnapshotTagRequest{
ActorSnapshotTag: &ateapipb.ObjectRef{Atespace: tagAtespaceFlag, Name: name},
})
if err != nil {
return fmt.Errorf("failed to get actor snapshot tag %q: %w", name, err)
}
tags = append(tags, tag)
}
} else {
pageToken := ""
for {
resp, err := client.ListActorSnapshotTags(ctx, &ateapipb.ListActorSnapshotTagsRequest{Atespace: tagAtespaceFlag, PageSize: 1000, PageToken: pageToken})
if err != nil {
return fmt.Errorf("failed to list actor snapshot tags: %w", err)
}
tags = append(tags, resp.GetActorSnapshotTags()...)
pageToken = resp.GetNextPageToken()
if pageToken == "" {
break
}
}
}
return printer.PrintActorSnapshotTags(tags, outputFmt)
},
}
var createActorSnapshotTagCmd = &cobra.Command{
Use: "actor-snapshot-tag <tag-name>",
Aliases: []string{"snapshot-tag"},
Short: "Tag the external snapshot a suspended actor holds",
Long: "Tag the external snapshot a suspended actor holds.\n\n" +
"The tag gets its own copy of that snapshot, so suspending the actor\n" +
"again or deleting it cannot collect what the tag names.",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
scope, err := parseActorSnapshotTagScope(createTagScopeFlag)
if err != nil {
return err
}
ctx := cmd.Context()
client, err := ateclient.NewClient(ctx, kubeconfig, k8sContext, endpoint, tokenFile, traceEnabled)
if err != nil {
return fmt.Errorf("failed to connect to ate-api-server: %w", err)
}
defer client.Close()
tag, err := client.CreateActorSnapshotTag(ctx, &ateapipb.CreateActorSnapshotTagRequest{
ActorSnapshotTag: &ateapipb.ActorSnapshotTag{
Metadata: &ateapipb.ResourceMetadata{Atespace: createTagAtespaceFlag, Name: args[0]},
Scope: scope,
SourceActor: &ateapipb.ObjectRef{Atespace: createTagAtespaceFlag, Name: createTagActorFlag},
},
})
if err != nil {
return fmt.Errorf("failed to create actor snapshot tag: %w", err)
}
return printer.PrintActorSnapshotTag(tag, outputFmt)
},
}
var updateActorSnapshotTagCmd = &cobra.Command{
Use: "actor-snapshot-tag <tag-name>",
Aliases: []string{"snapshot-tag"},
Short: "Publish or unpublish an actor snapshot tag",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
scope, err := parseActorSnapshotTagScope(updateTagScopeFlag)
if err != nil {
return err
}
ctx := cmd.Context()
client, err := ateclient.NewClient(ctx, kubeconfig, k8sContext, endpoint, tokenFile, traceEnabled)
if err != nil {
return fmt.Errorf("failed to connect to ate-api-server: %w", err)
}
defer client.Close()
ref := &ateapipb.ObjectRef{Atespace: updateTagAtespaceFlag, Name: args[0]}
resp, err := updateActorSnapshotTagScope(ctx, client, ref, scope)
if err != nil {
return err
}
return printer.PrintActorSnapshotTag(resp, outputFmt)
},
}
var deleteActorSnapshotTagCmd = &cobra.Command{
Use: "actor-snapshot-tag <tag-name>",
Aliases: []string{"snapshot-tag"},
Short: "Delete an actor snapshot tag",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
client, err := ateclient.NewClient(ctx, kubeconfig, k8sContext, endpoint, tokenFile, traceEnabled)
if err != nil {
return fmt.Errorf("failed to connect to ate-api-server: %w", err)
}
defer client.Close()
_, err = client.DeleteActorSnapshotTag(ctx, &ateapipb.DeleteActorSnapshotTagRequest{ActorSnapshotTag: &ateapipb.ObjectRef{Atespace: deleteTagAtespaceFlag, Name: args[0]}})
if err != nil {
return fmt.Errorf("failed to delete actor snapshot tag: %w", err)
}
fmt.Printf("actor snapshot tag %q deleted\n", args[0])
return nil
},
}
type actorSnapshotTagClient interface {
GetActorSnapshotTag(ctx context.Context, in *ateapipb.GetActorSnapshotTagRequest, opts ...grpc.CallOption) (*ateapipb.ActorSnapshotTag, error)
UpdateActorSnapshotTag(ctx context.Context, in *ateapipb.UpdateActorSnapshotTagRequest, opts ...grpc.CallOption) (*ateapipb.ActorSnapshotTag, error)
}
func updateActorSnapshotTagScope(ctx context.Context, client actorSnapshotTagClient, ref *ateapipb.ObjectRef, scope ateapipb.ActorSnapshotTagScope) (*ateapipb.ActorSnapshotTag, error) {
tag, err := client.GetActorSnapshotTag(ctx, &ateapipb.GetActorSnapshotTagRequest{ActorSnapshotTag: ref})
if err != nil {
return nil, fmt.Errorf("failed to get actor snapshot tag %q: %w", ref.GetName(), err)
}
tag.Scope = scope
resp, err := client.UpdateActorSnapshotTag(ctx, &ateapipb.UpdateActorSnapshotTagRequest{ActorSnapshotTag: tag})
if err != nil {
return nil, fmt.Errorf("failed to update actor snapshot tag: %w", err)
}
return resp, nil
}
func parseActorSnapshotTagScope(value string) (ateapipb.ActorSnapshotTagScope, error) {
switch strings.ToLower(value) {
case "atespace":
return ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE, nil
case "published":
return ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED, nil
default:
return ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_UNSPECIFIED, fmt.Errorf("invalid scope %q; must be atespace or published", value)
}
}
func parseNamespacedName(value string) (*ateapipb.ObjectRef, error) {
atespace, name, ok := strings.Cut(value, "/")
if !ok || atespace == "" || name == "" || strings.Contains(name, "/") {
return nil, fmt.Errorf("malformed reference %q (expected <atespace>/<name>)", value)
}
return &ateapipb.ObjectRef{Atespace: atespace, Name: name}, nil
}
func init() {
getActorSnapshotTagsCmd.Flags().StringVarP(&tagAtespaceFlag, "atespace", "a", "", "Atespace to list/get snapshot tags in")
getActorSnapshotTagsCmd.Flags().BoolVarP(&tagAllAtespacesFlag, "all-atespaces", "A", false, "List snapshot tags across all atespaces")
getCmd.AddCommand(getActorSnapshotTagsCmd)
createActorSnapshotTagCmd.Flags().StringVarP(&createTagAtespaceFlag, "atespace", "a", "", "Atespace the actor lives in; the tag is created here (required)")
_ = createActorSnapshotTagCmd.MarkFlagRequired("atespace")
createActorSnapshotTagCmd.Flags().StringVar(&createTagActorFlag, "actor", "", "Name of the suspended actor whose external snapshot to tag (required)")
_ = createActorSnapshotTagCmd.MarkFlagRequired("actor")
createActorSnapshotTagCmd.Flags().StringVar(&createTagScopeFlag, "scope", "atespace", "Tag scope: atespace or published")
createCmd.AddCommand(createActorSnapshotTagCmd)
rootCmd.AddCommand(updateCmd)
updateActorSnapshotTagCmd.Flags().StringVarP(&updateTagAtespaceFlag, "atespace", "a", "", "Atespace owning the tag (required)")
updateActorSnapshotTagCmd.Flags().StringVar(&updateTagScopeFlag, "scope", "", "Tag scope: atespace or published (required)")
_ = updateActorSnapshotTagCmd.MarkFlagRequired("atespace")
_ = updateActorSnapshotTagCmd.MarkFlagRequired("scope")
updateCmd.AddCommand(updateActorSnapshotTagCmd)
deleteActorSnapshotTagCmd.Flags().StringVarP(&deleteTagAtespaceFlag, "atespace", "a", "", "Atespace owning the tag (required)")
_ = deleteActorSnapshotTagCmd.MarkFlagRequired("atespace")
deleteCmd.AddCommand(deleteActorSnapshotTagCmd)
}
+7 -7
View File
@@ -25,14 +25,14 @@ import (
var templateRefFlag string
var atespaceFlag string
var sourceSnapshotTagFlag string
var sourceTagFlag string
var createActorCmd = &cobra.Command{
Use: "actor <actor-name>",
Short: "Create an actor",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
request, err := buildCreateActorRequest(args[0], atespaceFlag, templateRefFlag, sourceSnapshotTagFlag)
request, err := buildCreateActorRequest(args[0], atespaceFlag, templateRefFlag, sourceTagFlag)
if err != nil {
return err
}
@@ -55,7 +55,7 @@ var createActorCmd = &cobra.Command{
// buildCreateActorRequest assembles the CreateActor request from the command
// flags.
func buildCreateActorRequest(actorName, atespace, templateName, snapshotTag string) (*ateapipb.CreateActorRequest, error) {
func buildCreateActorRequest(actorName, atespace, templateName, tag string) (*ateapipb.CreateActorRequest, error) {
actor := &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{
Atespace: atespace,
@@ -67,12 +67,12 @@ func buildCreateActorRequest(actorName, atespace, templateName, snapshotTag stri
},
}
if snapshotTag != "" {
ref, err := parseNamespacedName(snapshotTag)
if tag != "" {
ref, err := parseNamespacedName(tag)
if err != nil {
return nil, err
}
actor.SourceSnapshotTag = ref
actor.SourceTag = ref
}
return &ateapipb.CreateActorRequest{Actor: actor}, nil
}
@@ -83,6 +83,6 @@ func init() {
_ = createActorCmd.MarkFlagRequired("template-ref")
createActorCmd.Flags().StringVarP(&atespaceFlag, "atespace", "a", "", "Atespace to create the actor in (required)")
_ = createActorCmd.MarkFlagRequired("atespace")
createActorCmd.Flags().StringVar(&sourceSnapshotTagFlag, "snapshot-tag", "", "Initialize from an ActorSnapshotTag in <atespace>/<name> format")
createActorCmd.Flags().StringVar(&sourceTagFlag, "tag", "", "Initialize from a Tag in <atespace>/<name> format")
createCmd.AddCommand(createActorCmd)
}
@@ -26,7 +26,7 @@ func TestBuildCreateActorRequest(t *testing.T) {
tests := []struct {
name string
templateRef string
snapshotTag string
tag string
want *ateapipb.Actor
wantErr bool
}{
@@ -39,21 +39,21 @@ func TestBuildCreateActorRequest(t *testing.T) {
},
},
{
name: "template ref with snapshot tag",
name: "template ref with tag",
templateRef: "counter",
snapshotTag: "demo/before-upgrade",
tag: "demo/before-upgrade",
want: &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Atespace: "demo", Name: "my-counter"},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "demo", Name: "counter"},
SourceSnapshotTag: &ateapipb.ObjectRef{Atespace: "demo", Name: "before-upgrade"},
Metadata: &ateapipb.ResourceMetadata{Atespace: "demo", Name: "my-counter"},
ActorTemplate: &ateapipb.ObjectRef{Atespace: "demo", Name: "counter"},
SourceTag: &ateapipb.ObjectRef{Atespace: "demo", Name: "before-upgrade"},
},
},
{name: "malformed snapshot tag", templateRef: "counter", snapshotTag: "before-upgrade", wantErr: true},
{name: "malformed tag", templateRef: "counter", tag: "before-upgrade", wantErr: true},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := buildCreateActorRequest("my-counter", "demo", test.templateRef, test.snapshotTag)
got, err := buildCreateActorRequest("my-counter", "demo", test.templateRef, test.tag)
if (err != nil) != test.wantErr {
t.Fatalf("buildCreateActorRequest error = %v, wantErr %t", err, test.wantErr)
}
+4 -4
View File
@@ -57,11 +57,11 @@ func TestGetCommandArgs(t *testing.T) {
}
func TestParseActorSnapshotFlags(t *testing.T) {
if got, err := parseActorSnapshotTagScope("published"); err != nil || got != ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED {
t.Fatalf("parseActorSnapshotTagScope(published) = (%v, %v)", got, err)
if got, err := parseTagScope("published"); err != nil || got != ateapipb.TagScope_TAG_SCOPE_PUBLISHED {
t.Fatalf("parseTagScope(published) = (%v, %v)", got, err)
}
if _, err := parseActorSnapshotTagScope("global"); err == nil {
t.Fatal("parseActorSnapshotTagScope(global) succeeded")
if _, err := parseTagScope("global"); err == nil {
t.Fatal("parseTagScope(global) succeeded")
}
ref, err := parseNamespacedName("team-a/before-upgrade")
if err != nil || ref.GetAtespace() != "team-a" || ref.GetName() != "before-upgrade" {
+235
View File
@@ -0,0 +1,235 @@
// Copyright 2026 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd
import (
"context"
"fmt"
"strings"
"github.com/agent-substrate/substrate/cmd/kubectl-ate/internal/printer"
"github.com/agent-substrate/substrate/internal/ateclient"
"github.com/agent-substrate/substrate/pkg/proto/ateapipb"
"github.com/spf13/cobra"
"google.golang.org/grpc"
)
var (
tagAtespaceFlag string
tagAllAtespacesFlag bool
createTagAtespaceFlag string
createTagActorFlag string
createTagScopeFlag string
updateTagAtespaceFlag string
updateTagScopeFlag string
deleteTagAtespaceFlag string
)
var updateCmd = &cobra.Command{Use: "update", Short: "Update a resource"}
var getTagsCmd = &cobra.Command{
Use: "tags [tag-name ...]",
Aliases: []string{"tag"},
Short: "List or get tags",
RunE: func(cmd *cobra.Command, args []string) error {
if tagAllAtespacesFlag && tagAtespaceFlag != "" {
return fmt.Errorf("--atespace and -A/--all-atespaces are mutually exclusive")
}
if len(args) > 0 && tagAtespaceFlag == "" {
return fmt.Errorf("--atespace is required when getting tags")
}
if len(args) == 0 && !tagAllAtespacesFlag && tagAtespaceFlag == "" {
return fmt.Errorf("specify --atespace <name>, or -A/--all-atespaces")
}
ctx := cmd.Context()
client, err := ateclient.NewClient(ctx, kubeconfig, k8sContext, endpoint, tokenFile, traceEnabled)
if err != nil {
return fmt.Errorf("failed to connect to ate-api-server: %w", err)
}
defer client.Close()
var tags []*ateapipb.Tag
if len(args) > 0 {
for _, name := range args {
tag, err := client.GetTag(ctx, &ateapipb.GetTagRequest{
Tag: &ateapipb.ObjectRef{Atespace: tagAtespaceFlag, Name: name},
})
if err != nil {
return fmt.Errorf("failed to get tag %q: %w", name, err)
}
tags = append(tags, tag)
}
} else {
pageToken := ""
for {
resp, err := client.ListTags(ctx, &ateapipb.ListTagsRequest{Atespace: tagAtespaceFlag, PageSize: 1000, PageToken: pageToken})
if err != nil {
return fmt.Errorf("failed to list tags: %w", err)
}
tags = append(tags, resp.GetTags()...)
pageToken = resp.GetNextPageToken()
if pageToken == "" {
break
}
}
}
return printer.PrintTags(tags, outputFmt)
},
}
var createTagCmd = &cobra.Command{
Use: "tag <tag-name>",
Aliases: []string{"tags"},
Short: "Tag the external snapshot a suspended actor holds",
Long: "Tag the external snapshot a suspended actor holds.\n\n" +
"The tag gets its own copy of that snapshot, so suspending the actor\n" +
"again or deleting it cannot collect what the tag names.",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
scope, err := parseTagScope(createTagScopeFlag)
if err != nil {
return err
}
ctx := cmd.Context()
client, err := ateclient.NewClient(ctx, kubeconfig, k8sContext, endpoint, tokenFile, traceEnabled)
if err != nil {
return fmt.Errorf("failed to connect to ate-api-server: %w", err)
}
defer client.Close()
tag, err := client.CreateTag(ctx, &ateapipb.CreateTagRequest{
Tag: &ateapipb.Tag{
Metadata: &ateapipb.ResourceMetadata{Atespace: createTagAtespaceFlag, Name: args[0]},
Scope: scope,
SourceActor: &ateapipb.ObjectRef{Atespace: createTagAtespaceFlag, Name: createTagActorFlag},
},
})
if err != nil {
return fmt.Errorf("failed to create tag: %w", err)
}
return printer.PrintTag(tag, outputFmt)
},
}
var updateTagCmd = &cobra.Command{
Use: "tag <tag-name>",
Aliases: []string{"tags"},
Short: "Publish or unpublish a tag",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
scope, err := parseTagScope(updateTagScopeFlag)
if err != nil {
return err
}
ctx := cmd.Context()
client, err := ateclient.NewClient(ctx, kubeconfig, k8sContext, endpoint, tokenFile, traceEnabled)
if err != nil {
return fmt.Errorf("failed to connect to ate-api-server: %w", err)
}
defer client.Close()
ref := &ateapipb.ObjectRef{Atespace: updateTagAtespaceFlag, Name: args[0]}
resp, err := updateTagScope(ctx, client, ref, scope)
if err != nil {
return err
}
return printer.PrintTag(resp, outputFmt)
},
}
var deleteTagCmd = &cobra.Command{
Use: "tag <tag-name>",
Aliases: []string{"tags"},
Short: "Delete a tag",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
ctx := cmd.Context()
client, err := ateclient.NewClient(ctx, kubeconfig, k8sContext, endpoint, tokenFile, traceEnabled)
if err != nil {
return fmt.Errorf("failed to connect to ate-api-server: %w", err)
}
defer client.Close()
_, err = client.DeleteTag(ctx, &ateapipb.DeleteTagRequest{Tag: &ateapipb.ObjectRef{Atespace: deleteTagAtespaceFlag, Name: args[0]}})
if err != nil {
return fmt.Errorf("failed to delete tag: %w", err)
}
fmt.Printf("tag %q deleted\n", args[0])
return nil
},
}
type tagClient interface {
GetTag(ctx context.Context, in *ateapipb.GetTagRequest, opts ...grpc.CallOption) (*ateapipb.Tag, error)
UpdateTag(ctx context.Context, in *ateapipb.UpdateTagRequest, opts ...grpc.CallOption) (*ateapipb.Tag, error)
}
func updateTagScope(ctx context.Context, client tagClient, ref *ateapipb.ObjectRef, scope ateapipb.TagScope) (*ateapipb.Tag, error) {
tag, err := client.GetTag(ctx, &ateapipb.GetTagRequest{Tag: ref})
if err != nil {
return nil, fmt.Errorf("failed to get tag %q: %w", ref.GetName(), err)
}
tag.Scope = scope
resp, err := client.UpdateTag(ctx, &ateapipb.UpdateTagRequest{Tag: tag})
if err != nil {
return nil, fmt.Errorf("failed to update tag: %w", err)
}
return resp, nil
}
func parseTagScope(value string) (ateapipb.TagScope, error) {
switch strings.ToLower(value) {
case "atespace":
return ateapipb.TagScope_TAG_SCOPE_ATESPACE, nil
case "published":
return ateapipb.TagScope_TAG_SCOPE_PUBLISHED, nil
default:
return ateapipb.TagScope_TAG_SCOPE_UNSPECIFIED, fmt.Errorf("invalid scope %q; must be atespace or published", value)
}
}
func parseNamespacedName(value string) (*ateapipb.ObjectRef, error) {
atespace, name, ok := strings.Cut(value, "/")
if !ok || atespace == "" || name == "" || strings.Contains(name, "/") {
return nil, fmt.Errorf("malformed reference %q (expected <atespace>/<name>)", value)
}
return &ateapipb.ObjectRef{Atespace: atespace, Name: name}, nil
}
func init() {
getTagsCmd.Flags().StringVarP(&tagAtespaceFlag, "atespace", "a", "", "Atespace to list/get tags in")
getTagsCmd.Flags().BoolVarP(&tagAllAtespacesFlag, "all-atespaces", "A", false, "List tags across all atespaces")
getCmd.AddCommand(getTagsCmd)
createTagCmd.Flags().StringVarP(&createTagAtespaceFlag, "atespace", "a", "", "Atespace the actor lives in; the tag is created here (required)")
_ = createTagCmd.MarkFlagRequired("atespace")
createTagCmd.Flags().StringVar(&createTagActorFlag, "actor", "", "Name of the suspended actor whose external snapshot to tag (required)")
_ = createTagCmd.MarkFlagRequired("actor")
createTagCmd.Flags().StringVar(&createTagScopeFlag, "scope", "atespace", "Tag scope: atespace or published")
createCmd.AddCommand(createTagCmd)
rootCmd.AddCommand(updateCmd)
updateTagCmd.Flags().StringVarP(&updateTagAtespaceFlag, "atespace", "a", "", "Atespace owning the tag (required)")
updateTagCmd.Flags().StringVar(&updateTagScopeFlag, "scope", "", "Tag scope: atespace or published (required)")
_ = updateTagCmd.MarkFlagRequired("atespace")
_ = updateTagCmd.MarkFlagRequired("scope")
updateCmd.AddCommand(updateTagCmd)
deleteTagCmd.Flags().StringVarP(&deleteTagAtespaceFlag, "atespace", "a", "", "Atespace owning the tag (required)")
_ = deleteTagCmd.MarkFlagRequired("atespace")
deleteCmd.AddCommand(deleteTagCmd)
}
@@ -25,56 +25,56 @@ import (
"google.golang.org/protobuf/testing/protocmp"
)
// fakeTagClient serves GetActorSnapshotTag from a stored tag and records the
// fakeTagClient serves GetTag from a stored tag and records the
// update it is asked to make.
type fakeTagClient struct {
tag *ateapipb.ActorSnapshotTag
tag *ateapipb.Tag
getCalls int
updateReq *ateapipb.UpdateActorSnapshotTagRequest
updateReq *ateapipb.UpdateTagRequest
}
func (f *fakeTagClient) GetActorSnapshotTag(_ context.Context, _ *ateapipb.GetActorSnapshotTagRequest, _ ...grpc.CallOption) (*ateapipb.ActorSnapshotTag, error) {
func (f *fakeTagClient) GetTag(_ context.Context, _ *ateapipb.GetTagRequest, _ ...grpc.CallOption) (*ateapipb.Tag, error) {
f.getCalls++
return proto.Clone(f.tag).(*ateapipb.ActorSnapshotTag), nil
return proto.Clone(f.tag).(*ateapipb.Tag), nil
}
func (f *fakeTagClient) UpdateActorSnapshotTag(_ context.Context, in *ateapipb.UpdateActorSnapshotTagRequest, _ ...grpc.CallOption) (*ateapipb.ActorSnapshotTag, error) {
func (f *fakeTagClient) UpdateTag(_ context.Context, in *ateapipb.UpdateTagRequest, _ ...grpc.CallOption) (*ateapipb.Tag, error) {
f.updateReq = in
// Bump the version the way a real server does, so the returned tag is
// distinguishable from the one the caller sent.
updated := proto.Clone(in.GetActorSnapshotTag()).(*ateapipb.ActorSnapshotTag)
updated := proto.Clone(in.GetTag()).(*ateapipb.Tag)
updated.Metadata.Version++
return updated, nil
}
// TestUpdateActorSnapshotTagScope checks the read-modify-write wiring: the scope
// TestUpdateTagScope checks the read-modify-write wiring: the scope
// change is written against the uid and version that were just read.
func TestUpdateActorSnapshotTagScope(t *testing.T) {
testTag := &ateapipb.ActorSnapshotTag{
func TestUpdateTagScope(t *testing.T) {
testTag := &ateapipb.Tag{
Metadata: &ateapipb.ResourceMetadata{
Atespace: "space-1",
Name: "tag-1",
Uid: "8ba9b6ee-2e1a-4c0f-9f4e-2f0a0f1c3d55",
Version: 1,
},
Scope: ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE,
Status: &ateapipb.ActorSnapshotTagStatus{
Snapshot: &ateapipb.ExternalSnapshot{SnapshotUri: "gs://private/atespaces/space-1/actor-snapshot-tags/tag-1"},
Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE,
Status: &ateapipb.TagStatus{
Snapshot: &ateapipb.ExternalSnapshot{SnapshotUri: "gs://private/atespaces/space-1/tags/tag-1"},
},
}
client := &fakeTagClient{tag: testTag}
ref := &ateapipb.ObjectRef{Atespace: "space-1", Name: "tag-1"}
want := ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED
want := ateapipb.TagScope_TAG_SCOPE_PUBLISHED
resp, err := updateActorSnapshotTagScope(context.Background(), client, ref, want)
resp, err := updateTagScope(context.Background(), client, ref, want)
if err != nil {
t.Fatalf("updateActorSnapshotTagScope() error = %v", err)
t.Fatalf("updateTagScope() error = %v", err)
}
if client.getCalls != 1 {
t.Errorf("GetActorSnapshotTag called %d times, want 1", client.getCalls)
t.Errorf("GetTag called %d times, want 1", client.getCalls)
}
wantResp := testTag
+13 -13
View File
@@ -286,16 +286,16 @@ func PrintActorTemplate(template *ateapipb.ActorTemplate, format string) error {
return PrintActorTemplates([]*ateapipb.ActorTemplate{template}, format)
}
// PrintActorSnapshotTags prints actor snapshot tags to stdout in the requested
// PrintTags prints tags to stdout in the requested
// format.
func PrintActorSnapshotTags(tags []*ateapipb.ActorSnapshotTag, format string) error {
return PrintActorSnapshotTagsTo(os.Stdout, tags, format)
func PrintTags(tags []*ateapipb.Tag, format string) error {
return PrintTagsTo(os.Stdout, tags, format)
}
// PrintActorSnapshotTagsTo prints a slice of actor snapshot tags to the
// PrintTagsTo prints a slice of tags to the
// provided writer.
func PrintActorSnapshotTagsTo(out io.Writer, tags []*ateapipb.ActorSnapshotTag, format string) error {
slices.SortFunc(tags, func(a, b *ateapipb.ActorSnapshotTag) int {
func PrintTagsTo(out io.Writer, tags []*ateapipb.Tag, format string) error {
slices.SortFunc(tags, func(a, b *ateapipb.Tag) int {
if c := cmp.Compare(a.GetMetadata().GetAtespace(), b.GetMetadata().GetAtespace()); c != 0 {
return c
}
@@ -303,7 +303,7 @@ func PrintActorSnapshotTagsTo(out io.Writer, tags []*ateapipb.ActorSnapshotTag,
})
switch format {
case "json", "yaml":
return printProto(out, &ateapipb.ListActorSnapshotTagsResponse{ActorSnapshotTags: tags}, format)
return printProto(out, &ateapipb.ListTagsResponse{Tags: tags}, format)
case "table":
w := tabwriter.NewWriter(out, 0, 0, 3, ' ', 0)
fmt.Fprintln(w, "ATESPACE\tNAME\tSCOPE\tSTATE\tSNAPSHOT\tCONTENT SCOPE\tAGE")
@@ -317,7 +317,7 @@ func PrintActorSnapshotTagsTo(out io.Writer, tags []*ateapipb.ActorSnapshotTag,
}
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
tag.GetMetadata().GetAtespace(), tag.GetMetadata().GetName(), tag.GetScope(),
actorSnapshotTagState(tag), snapshotURI, contentScope,
tagState(tag), snapshotURI, contentScope,
formatAge(tag.GetMetadata().GetCreateTime()))
}
return w.Flush()
@@ -326,22 +326,22 @@ func PrintActorSnapshotTagsTo(out io.Writer, tags []*ateapipb.ActorSnapshotTag,
}
}
// actorSnapshotTagState reports whether a tag is usable. A tag is Pending until
// tagState reports whether a tag is usable. A tag is Pending until
// the copy of its own snapshot lands; until then it names nothing an Actor can
// be created from, and deleting it collects whatever the create stranded.
func actorSnapshotTagState(tag *ateapipb.ActorSnapshotTag) string {
func tagState(tag *ateapipb.Tag) string {
if tag.GetStatus().GetSnapshot().GetSnapshotUri() == "" {
return "Pending"
}
return "Ready"
}
// PrintActorSnapshotTag prints a single actor snapshot tag to stdout.
func PrintActorSnapshotTag(tag *ateapipb.ActorSnapshotTag, format string) error {
// PrintTag prints a single tag to stdout.
func PrintTag(tag *ateapipb.Tag, format string) error {
if format == "json" || format == "yaml" {
return printProto(os.Stdout, tag, format)
}
return PrintActorSnapshotTags([]*ateapipb.ActorSnapshotTag{tag}, format)
return PrintTags([]*ateapipb.Tag{tag}, format)
}
// PrintAtespaces prints a slice of atespaces to stdout in the requested format.
@@ -451,21 +451,21 @@ func TestPrintActorTemplatesTo_Invalid(t *testing.T) {
}
}
func TestPrintActorSnapshotTagsTo_Table(t *testing.T) {
func TestPrintTagsTo_Table(t *testing.T) {
now := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
pinNow(t, now)
var buf bytes.Buffer
tags := []*ateapipb.ActorSnapshotTag{
tags := []*ateapipb.Tag{
{
Metadata: &ateapipb.ResourceMetadata{
Atespace: "team-a",
Name: "v2",
CreateTime: timestamppb.New(now.Add(-5 * time.Minute)),
},
Scope: ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED,
Status: &ateapipb.ActorSnapshotTagStatus{
Snapshot: &ateapipb.ExternalSnapshot{SnapshotUri: "gs://private/atespaces/team-a/actor-snapshot-tags/v2", ContentScope: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL},
Scope: ateapipb.TagScope_TAG_SCOPE_PUBLISHED,
Status: &ateapipb.TagStatus{
Snapshot: &ateapipb.ExternalSnapshot{SnapshotUri: "gs://private/atespaces/team-a/tags/v2", ContentScope: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL},
},
},
{
@@ -474,9 +474,9 @@ func TestPrintActorSnapshotTagsTo_Table(t *testing.T) {
Name: "v1",
CreateTime: timestamppb.New(now.Add(-5 * time.Hour)),
},
Scope: ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE,
Status: &ateapipb.ActorSnapshotTagStatus{
Snapshot: &ateapipb.ExternalSnapshot{SnapshotUri: "gs://private/atespaces/team-a/actor-snapshot-tags/v1", ContentScope: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL},
Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE,
Status: &ateapipb.TagStatus{
Snapshot: &ateapipb.ExternalSnapshot{SnapshotUri: "gs://private/atespaces/team-a/tags/v1", ContentScope: ateapipb.SnapshotContentScope_SNAPSHOT_CONTENT_SCOPE_FULL},
},
},
{
@@ -487,31 +487,31 @@ func TestPrintActorSnapshotTagsTo_Table(t *testing.T) {
Name: "v3",
CreateTime: timestamppb.New(now.Add(-30 * time.Second)),
},
Scope: ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE,
Status: &ateapipb.ActorSnapshotTagStatus{
InProgressSnapshotUri: "gs://private/atespaces/team-a/actor-snapshot-tags/9f1c",
Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE,
Status: &ateapipb.TagStatus{
InProgressSnapshotUri: "gs://private/atespaces/team-a/tags/9f1c",
},
},
}
if err := PrintActorSnapshotTagsTo(&buf, tags, "table"); err != nil {
if err := PrintTagsTo(&buf, tags, "table"); err != nil {
t.Fatalf("unexpected error: %v", err)
}
// Sorted by atespace, then name.
expected := `ATESPACE NAME SCOPE STATE SNAPSHOT CONTENT SCOPE AGE
team-a v1 ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE Ready gs://private/atespaces/team-a/actor-snapshot-tags/v1 SNAPSHOT_CONTENT_SCOPE_FULL 5h
team-a v2 ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED Ready gs://private/atespaces/team-a/actor-snapshot-tags/v2 SNAPSHOT_CONTENT_SCOPE_FULL 5m
team-a v3 ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE Pending <none> <none> 30s
expected := `ATESPACE NAME SCOPE STATE SNAPSHOT CONTENT SCOPE AGE
team-a v1 TAG_SCOPE_ATESPACE Ready gs://private/atespaces/team-a/tags/v1 SNAPSHOT_CONTENT_SCOPE_FULL 5h
team-a v2 TAG_SCOPE_PUBLISHED Ready gs://private/atespaces/team-a/tags/v2 SNAPSHOT_CONTENT_SCOPE_FULL 5m
team-a v3 TAG_SCOPE_ATESPACE Pending <none> <none> 30s
`
if diff := cmp.Diff(expected, buf.String()); diff != "" {
t.Errorf("output mismatch (-want +got):\n%s", diff)
}
}
func TestPrintActorSnapshotTagsTo_Invalid(t *testing.T) {
func TestPrintTagsTo_Invalid(t *testing.T) {
var buf bytes.Buffer
if err := PrintActorSnapshotTagsTo(&buf, nil, "xml"); err == nil {
if err := PrintTagsTo(&buf, nil, "xml"); err == nil {
t.Errorf("expected error for invalid format, got nil")
}
}
+6 -6
View File
@@ -327,16 +327,16 @@ snapshotsConfig:
```
<location>/atespaces/<atespace>/actors/<actor uid>/snapshots/<snapshot name>
<location>/atespaces/<atespace>/actor-snapshot-tags/<snapshot name>
<location>/atespaces/<atespace>/tags/<snapshot name>
```
The objects of a snapshot (its manifest, memory image, durable-data tar) are named below it. So for the template above, a snapshot of an actor in atespace `team-a` is stored at `gs://my-bucket/secret-agent/atespaces/team-a/actors/3f8b…/snapshots/f47ac10b-…`, and the template's golden snapshot — the golden actor lives in the reserved `ate-golden` atespace — under `gs://my-bucket/secret-agent/atespaces/ate-golden/actors/<uid>/snapshots/<name>`.
An actor takes a series of snapshots over its life, so it gets a prefix of its own and each snapshot sits below it. A tag holds exactly one, so the tag's prefix *is* its snapshot's. The actor level is keyed on the UID rather than the name, so an actor recreated under a name that was used before never inherits its predecessor's objects.
An owner is collected by deleting everything under its prefix, and it can delete nothing else. That is what makes a borrowed snapshot safe: an actor created from a tag points at a URI under `actor-snapshot-tags/`, which its own prefix does not cover. See [Snapshot lifetime](#snapshot-lifetime).
An owner is collected by deleting everything under its prefix, and it can delete nothing else. That is what makes a borrowed snapshot safe: an actor created from a tag points at a URI under `tags/`, which its own prefix does not cover. See [Snapshot lifetime](#snapshot-lifetime).
An `Actor` reports its current snapshot in the server-managed `status.externalSnapshot`, an `ActorSnapshotTag` in `status.snapshot`, and an `ActorTemplate` its golden one in `status.goldenSnapshotStatus.goldenSnapshot` — each an `ExternalSnapshot` carrying `snapshotUri` and the `contentScope` it captured. The URI is recorded when the snapshot is written, not recomputed on read, so the layout can change in future versions without stranding existing snapshots. All three are server-owned: do not send them on input, and parse a URI only against the scheme above.
An `Actor` reports its current snapshot in the server-managed `status.externalSnapshot`, a `Tag` in `status.snapshot`, and an `ActorTemplate` its golden one in `status.goldenSnapshotStatus.goldenSnapshot` — each an `ExternalSnapshot` carrying `snapshotUri` and the `contentScope` it captured. The URI is recorded when the snapshot is written, not recomputed on read, so the layout can change in future versions without stranding existing snapshots. All three are server-owned: do not send them on input, and parse a URI only against the scheme above.
An `ActorTemplate` belongs to one atespace, but one `storageLocation` still holds snapshots for many atespaces: the golden actor lives in the reserved `ate-golden` atespace, and a `PUBLISHED` snapshot may be cloned from other atespaces. The `<atespace>` level exists so that access can be granted per tenant: an object-storage policy can only condition on an **object-name prefix**, and cannot read the identity recorded inside a snapshot's manifest. Binding a per-atespace grant on GCS looks like:
@@ -431,13 +431,13 @@ The Substrate Control Plane (`ate-api-server`) exposes a gRPC interface for mana
Registers a new logical actor in the system.
* **Request:** `CreateActorRequest`
* `actor`: `Actor` — the actor to create. Its `metadata` carries the atespace and name (name must be a DNS-1123 label); the `actor_template` ref (atespace + name) selects the `ActorTemplate`.
* `actor.source_snapshot_tag`: (Optional) `ObjectRef` of an `ActorSnapshotTag` to seed the actor from. The tag must be taken under the same `ActorTemplate`, and either in the actor's own atespace or `PUBLISHED`. Nothing is copied: the new actor's `status.externalSnapshot` points at the tag's snapshot, under the tag's prefix, until its own first suspend.
* `actor.source_tag`: (Optional) `ObjectRef` of a `Tag` to seed the actor from. The tag must be taken under the same `ActorTemplate`, and either in the actor's own atespace or `PUBLISHED`. Nothing is copied: the new actor's `status.externalSnapshot` points at the tag's snapshot, under the tag's prefix, until its own first suspend.
* **Response:** the initialized `Actor`.
#### `UpdateActor`
Replaces the mutable fields of an existing actor with the ones in the request.
* **Request:** `UpdateActorRequest`
* `actor`: `Actor` — the complete replacement actor. `metadata.atespace` and `metadata.name` identify the resource; `metadata.uid` and `metadata.version` are **required** preconditions. `metadata` and `status` are server-owned and whatever the request carries in them is ignored. `source_snapshot_tag` is immutable.
* `actor`: `Actor` — the complete replacement actor. `metadata.atespace` and `metadata.name` identify the resource; `metadata.uid` and `metadata.version` are **required** preconditions. `metadata` and `status` are server-owned and whatever the request carries in them is ignored. `source_tag` is immutable.
* **Response:** the updated `Actor`.
* **Errors:** `INVALID_ARGUMENT` if `uid` or `version` is unset, or if the request changes an immutable field — including by leaving one unset; `ABORTED` if either guard no longer matches the stored resource.
@@ -455,7 +455,7 @@ Hibernate a running actor, capturing its current RAM and disk state into a snaps
* **Request:** `SuspendActorRequest`
* `actor`: `ObjectRef` of the actor to suspend.
* **Response:** `SuspendActorResponse` containing the `Actor` object in `ACTOR_STATE_SUSPENDED`, with its snapshot in `status.externalSnapshot`.
* A successful suspend releases the actor's previous external snapshot: an actor keeps one, and only tags outlive it. To keep the snapshot a suspend just wrote, tag it with `CreateActorSnapshotTag` while the actor is still suspended.
* A successful suspend releases the actor's previous external snapshot: an actor keeps one, and only tags outlive it. To keep the snapshot a suspend just wrote, tag it with `CreateTag` while the actor is still suspended.
#### Snapshot lifetime
+2 -2
View File
@@ -142,7 +142,7 @@ Rules:
- Request message name **must** match the RPC name with a `Request` suffix.
- Response **must** be the resource itself — not a `GetActorResponse` wrapper.
- Request **must** identify the resource with a single `ObjectRef` field (for both atespace-scoped and global-scoped resources).
- That field **must** be named after the resource's own snake_case type name (e.g. `actor` for `Actor`, `actor_snapshot_tag` for `ActorSnapshotTag`), not a generic name like `name` or `ref`.
- That field **must** be named after the resource's own snake_case type name (e.g. `actor` for `Actor`, `tag` for `Tag`), not a generic name like `name` or `ref`.
- If the resource does not exist: return `NOT_FOUND`.
### 3.2 List
@@ -278,7 +278,7 @@ Rules:
- RPC name **must** begin with `Delete` followed by the singular resource name.
- Response **must** be the deleted resource.
- Request **must** identify the resource with an `ObjectRef` field (for both atespace-scoped and global-scoped resources).
- That field **must** be named after the resource's own snake_case type name (e.g. `actor` for `Actor`, `actor_snapshot_tag` for `ActorSnapshotTag`), not a generic name like `name` or `ref`.
- That field **must** be named after the resource's own snake_case type name (e.g. `actor` for `Actor`, `tag` for `Tag`), not a generic name like `name` or `ref`.
- If the resource does not exist: return `NOT_FOUND`.
- `version` and `uid` preconditions are honored via a `DeleteOptions` field (see section #7). Both are optional; the zero value skips the check.
- Further non-resource "control" fields (e.g. dry-run) belong in `DeleteOptions`, not as loose top-level fields on the request.
+21 -21
View File
@@ -125,7 +125,7 @@ func TestActorSnapshotLifecycle(t *testing.T) {
tagRef := &ateapipb.ObjectRef{Atespace: demoAtespace, Name: "e2e-" + nsObj.Name}
t.Cleanup(func() {
_, _ = clients.SubstrateAPI.DeleteActorSnapshotTag(context.Background(), &ateapipb.DeleteActorSnapshotTagRequest{ActorSnapshotTag: tagRef})
_, _ = clients.SubstrateAPI.DeleteTag(context.Background(), &ateapipb.DeleteTagRequest{Tag: tagRef})
})
suspended, err := clients.SubstrateAPI.SuspendActor(ctx, &ateapipb.SuspendActorRequest{
Actor: &ateapipb.ObjectRef{Atespace: demoAtespace, Name: sourceName},
@@ -139,52 +139,52 @@ func TestActorSnapshotLifecycle(t *testing.T) {
}
// The tag is given its own copy of the snapshot the suspend just wrote, so
// it survives the Actor being suspended again or deleted.
tagToUpdate, err := clients.SubstrateAPI.CreateActorSnapshotTag(ctx, &ateapipb.CreateActorSnapshotTagRequest{
ActorSnapshotTag: &ateapipb.ActorSnapshotTag{
tagToUpdate, err := clients.SubstrateAPI.CreateTag(ctx, &ateapipb.CreateTagRequest{
Tag: &ateapipb.Tag{
Metadata: &ateapipb.ResourceMetadata{Atespace: demoAtespace, Name: tagRef.GetName()},
Scope: ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE,
Scope: ateapipb.TagScope_TAG_SCOPE_ATESPACE,
SourceActor: &ateapipb.ObjectRef{Atespace: demoAtespace, Name: sourceName},
},
})
if err != nil {
t.Fatalf("failed to create the ActorSnapshotTag: %v", err)
t.Fatalf("failed to create the Tag: %v", err)
}
if got := tagToUpdate.GetStatus().GetSnapshot().GetSnapshotUri(); got == "" || got == snapshotURI {
t.Fatalf("ActorSnapshotTag %s snapshot uri = %q, want a copy of its own", tagRef.GetName(), got)
t.Fatalf("Tag %s snapshot uri = %q, want a copy of its own", tagRef.GetName(), got)
}
if got := tagToUpdate.GetStatus().GetInProgressSnapshotUri(); got != "" {
t.Fatalf("ActorSnapshotTag %s in-progress snapshot uri = %q, want it cleared", tagRef.GetName(), got)
t.Fatalf("Tag %s in-progress snapshot uri = %q, want it cleared", tagRef.GetName(), got)
}
listed, err := clients.SubstrateAPI.ListActorSnapshotTags(ctx, &ateapipb.ListActorSnapshotTagsRequest{Atespace: demoAtespace})
listed, err := clients.SubstrateAPI.ListTags(ctx, &ateapipb.ListTagsRequest{Atespace: demoAtespace})
if err != nil {
t.Fatalf("failed to list ActorSnapshotTags: %v", err)
t.Fatalf("failed to list Tags: %v", err)
}
found := false
for _, candidate := range listed.GetActorSnapshotTags() {
for _, candidate := range listed.GetTags() {
if candidate.GetMetadata().GetName() == tagRef.GetName() {
found = true
break
}
}
if !found {
t.Fatalf("tag %q missing from ListActorSnapshotTags", tagRef.GetName())
t.Fatalf("tag %q missing from ListTags", tagRef.GetName())
}
tagToUpdate.Scope = ateapipb.ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED
if _, err := clients.SubstrateAPI.UpdateActorSnapshotTag(ctx, &ateapipb.UpdateActorSnapshotTagRequest{
ActorSnapshotTag: tagToUpdate,
tagToUpdate.Scope = ateapipb.TagScope_TAG_SCOPE_PUBLISHED
if _, err := clients.SubstrateAPI.UpdateTag(ctx, &ateapipb.UpdateTagRequest{
Tag: tagToUpdate,
}); err != nil {
t.Fatalf("failed to publish ActorSnapshotTag: %v", err)
t.Fatalf("failed to publish Tag: %v", err)
}
if _, err := clients.SubstrateAPI.CreateActor(ctx, &ateapipb.CreateActorRequest{
Actor: &ateapipb.Actor{
Metadata: &ateapipb.ResourceMetadata{Atespace: demoAtespace, Name: cloneName},
ActorTemplate: e2e.TemplateRef(at),
SourceSnapshotTag: tagRef,
Metadata: &ateapipb.ResourceMetadata{Atespace: demoAtespace, Name: cloneName},
ActorTemplate: e2e.TemplateRef(at),
SourceTag: tagRef,
},
}); err != nil {
t.Fatalf("failed to create Actor from ActorSnapshotTag: %v", err)
t.Fatalf("failed to create Actor from Tag: %v", err)
}
if _, err := e2e.ResumeActorAwaitCapacity(t, ctx, clients, &ateapipb.ResumeActorRequest{Actor: &ateapipb.ObjectRef{Atespace: demoAtespace, Name: cloneName}}); err != nil {
t.Fatalf("failed to resume cloned Actor: %v", err)
@@ -196,8 +196,8 @@ func TestActorSnapshotLifecycle(t *testing.T) {
}
validateCounterResponse(t, response, "clone", 2, 2)
if _, err := clients.SubstrateAPI.DeleteActorSnapshotTag(ctx, &ateapipb.DeleteActorSnapshotTagRequest{ActorSnapshotTag: tagRef}); err != nil {
t.Fatalf("failed to delete ActorSnapshotTag: %v", err)
if _, err := clients.SubstrateAPI.DeleteTag(ctx, &ateapipb.DeleteTagRequest{Tag: tagRef}); err != nil {
t.Fatalf("failed to delete Tag: %v", err)
}
}
+3 -3
View File
@@ -116,7 +116,7 @@ func TestDeletePrefix(t *testing.T) {
mustTagSnapshotURI(t, testLocation, "team-a", "snap-2"): {"manifest.json"},
},
target: mustOwnerPrefix(t, resources.ActorSnapshotOwner("team-a", "actor-1"), testLocation),
wantRemaining: []string{"bucket/root/atespaces/team-a/actor-snapshot-tags/snap-2/manifest.json"},
wantRemaining: []string{"bucket/root/atespaces/team-a/tags/snap-2/manifest.json"},
},
{
name: "an already collected snapshot succeeds",
@@ -235,7 +235,7 @@ func TestCopyPrefix_AcrossBuckets(t *testing.T) {
t.Fatalf("CopyPrefix(%s, %s) = %v, want nil", src, dst, err)
}
want := []string{
"new-bucket/atespaces/team-a/actor-snapshot-tags/snap-2/manifest.json",
"new-bucket/atespaces/team-a/tags/snap-2/manifest.json",
"old-bucket/root/atespaces/team-a/actors/actor-1/snapshots/snap-1/manifest.json",
}
if diff := cmp.Diff(want, fake.Objects()); diff != "" {
@@ -319,7 +319,7 @@ func TestBucketPrefix(t *testing.T) {
name: "a tag's prefix",
prefix: mustTagSnapshotURI(t, "gs://bucket/root", "team-a", "snap-1").Prefix(),
wantBucket: "bucket",
wantPrefix: "root/atespaces/team-a/actor-snapshot-tags/snap-1/",
wantPrefix: "root/atespaces/team-a/tags/snap-1/",
},
{
name: "the zero prefix",
+8 -9
View File
@@ -141,18 +141,17 @@ func ActorTemplateRefFromActorTemplate(t *ateapipb.ActorTemplate) ActorTemplateR
}
}
// ActorSnapshotTagRef identifies an ActorSnapshotTag by the (atespace, name).
type ActorSnapshotTagRef = ResourceRef[*ateapipb.ActorSnapshotTag]
// TagRef identifies a Tag by the (atespace, name).
type TagRef = ResourceRef[*ateapipb.Tag]
// ActorSnapshotTagRefFromObjectRef converts an Ibjectref to an ActorSnapshotTagRef.
func ActorSnapshotTagRefFromObjectRef(ref *ateapipb.ObjectRef) ActorSnapshotTagRef {
return resourceRefFromObjectRef[*ateapipb.ActorSnapshotTag](ref)
// TagRefFromObjectRef converts an ObjectRef to a TagRef.
func TagRefFromObjectRef(ref *ateapipb.ObjectRef) TagRef {
return resourceRefFromObjectRef[*ateapipb.Tag](ref)
}
// ActorSnapshotTagRefFromActorSnapshotTag returns the reference addressing the
// given tag.
func ActorSnapshotTagRefFromActorSnapshotTag(t *ateapipb.ActorSnapshotTag) ActorSnapshotTagRef {
return ActorSnapshotTagRef{
// TagRefFromTag returns the reference addressing the given tag.
func TagRefFromTag(t *ateapipb.Tag) TagRef {
return TagRef{
Atespace: t.GetMetadata().GetAtespace(),
Name: t.GetMetadata().GetName(),
}
+17 -17
View File
@@ -27,9 +27,9 @@ import (
// is expected.
func TestRefAliasesAreDistinctTypes(t *testing.T) {
types := map[string]reflect.Type{
"ActorRef": reflect.TypeFor[ActorRef](),
"ActorTemplateRef": reflect.TypeFor[ActorTemplateRef](),
"ActorSnapshotTagRef": reflect.TypeFor[ActorSnapshotTagRef](),
"ActorRef": reflect.TypeFor[ActorRef](),
"ActorTemplateRef": reflect.TypeFor[ActorTemplateRef](),
"TagRef": reflect.TypeFor[TagRef](),
}
seen := make(map[reflect.Type]string)
for name, typ := range types {
@@ -224,46 +224,46 @@ func TestActorTemplateRefFromActorTemplate(t *testing.T) {
}
}
func TestActorSnapshotTagRefString(t *testing.T) {
got := ActorSnapshotTagRef{Atespace: "team-a", Name: "tag-1"}.String()
func TestTagRefString(t *testing.T) {
got := TagRef{Atespace: "team-a", Name: "tag-1"}.String()
if want := "team-a/tag-1"; got != want {
t.Errorf("String() = %q, want %q", got, want)
}
}
func TestActorSnapshotTagRefObjectRefRoundTrip(t *testing.T) {
tagRef := ActorSnapshotTagRef{Atespace: "team-a", Name: "tag-1"}
func TestTagRefObjectRefRoundTrip(t *testing.T) {
tagRef := TagRef{Atespace: "team-a", Name: "tag-1"}
obj := tagRef.ToObjectRef()
if obj.GetAtespace() != "team-a" || obj.GetName() != "tag-1" {
t.Errorf("ToObjectRef() = (%q, %q), want (team-a, tag-1)", obj.GetAtespace(), obj.GetName())
}
if got := ActorSnapshotTagRefFromObjectRef(obj); got != tagRef {
if got := TagRefFromObjectRef(obj); got != tagRef {
t.Errorf("round-trip = %+v, want %+v", got, tagRef)
}
}
func TestActorSnapshotTagRefFromActorSnapshotTag(t *testing.T) {
func TestTagRefFromTag(t *testing.T) {
tests := []struct {
name string
tag *ateapipb.ActorSnapshotTag
want ActorSnapshotTagRef
tag *ateapipb.Tag
want TagRef
}{
{
name: "populated",
tag: &ateapipb.ActorSnapshotTag{Metadata: &ateapipb.ResourceMetadata{
tag: &ateapipb.Tag{Metadata: &ateapipb.ResourceMetadata{
Atespace: "team-a",
Name: "tag-1",
}},
want: ActorSnapshotTagRef{Atespace: "team-a", Name: "tag-1"},
want: TagRef{Atespace: "team-a", Name: "tag-1"},
},
{"nil tag", nil, ActorSnapshotTagRef{}},
{"nil metadata", &ateapipb.ActorSnapshotTag{}, ActorSnapshotTagRef{}},
{"nil tag", nil, TagRef{}},
{"nil metadata", &ateapipb.Tag{}, TagRef{}},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := ActorSnapshotTagRefFromActorSnapshotTag(tt.tag); got != tt.want {
t.Errorf("ActorSnapshotTagRefFromActorSnapshotTag() = %+v, want %+v", got, tt.want)
if got := TagRefFromTag(tt.tag); got != tt.want {
t.Errorf("TagRefFromTag() = %+v, want %+v", got, tt.want)
}
})
}
+16 -16
View File
@@ -31,7 +31,7 @@ const (
// from an object's name, so a collector can tell whose objects it is
// looking at without consulting the store.
actorsOwnerKind = "actors"
tagsOwnerKind = "actor-snapshot-tags"
tagsOwnerKind = "tags"
)
// NewSnapshotName returns a unique name for a new snapshot, durable or
@@ -53,10 +53,10 @@ func (p StoragePrefix) IsZero() bool { return p == StoragePrefix{} }
func (p StoragePrefix) String() string { return p.uri }
// SnapshotOwner is the resource whose objects a snapshot's prefix holds: the
// Actor that took it, or the ActorSnapshotTag that copied it. Deletion is
// expressed as "collect everything under my own prefix", so an owner cannot
// name another's objects, and a borrowed snapshot is recognized by the URI
// alone rather than by a flag the two have to keep in sync.
// Actor that took it, or the Tag that copied it. Deletion is expressed as
// "collect everything under my own prefix", so an owner cannot name another's
// objects, and a borrowed snapshot is recognized by the URI alone rather than
// by a flag the two have to keep in sync.
type SnapshotOwner struct {
kind string
atespace string
@@ -73,10 +73,10 @@ func ActorSnapshotOwner(atespace, actorUID string) SnapshotOwner {
return SnapshotOwner{kind: actorsOwnerKind, atespace: atespace, id: actorUID}
}
// TagSnapshotOwner returns the owner of the one snapshot an ActorSnapshotTag
// holds. A tag never takes a second snapshot, so its prefix is that snapshot's
// prefix, and the name is minted fresh rather than derived from the tag: a
// recreated tag cannot compute its way onto the objects its predecessor left.
// TagSnapshotOwner returns the owner of the one snapshot a Tag holds. A tag
// never takes a second snapshot, so its prefix is that snapshot's prefix, and
// the name is minted fresh rather than derived from the tag: a recreated tag
// cannot compute its way onto the objects its predecessor left.
func TagSnapshotOwner(atespace, snapshotName string) SnapshotOwner {
return SnapshotOwner{kind: tagsOwnerKind, atespace: atespace, id: snapshotName}
}
@@ -129,8 +129,8 @@ func (o SnapshotOwner) Prefix(location string) (StoragePrefix, error) {
// gs://bucket/root location
// gs://bucket/root/atespaces/team-a/actors/<uid> an Actor's prefix
// gs://bucket/root/atespaces/team-a/actors/<uid>/snapshots/<name> one of its snapshots
// gs://bucket/root/atespaces/team-a/actor-snapshot-tags/<name> a tag's prefix, and its
// only snapshot
// gs://bucket/root/atespaces/team-a/tags/<name> a tag's prefix, and
// its only snapshot
type SnapshotURI struct {
uri string
location string
@@ -157,9 +157,9 @@ func NewActorSnapshotURI(location, atespace, actorUID, name string) (SnapshotURI
return SnapshotURI{uri: uri, location: location, owner: owner, ownerPrefix: prefix.String(), name: name}, nil
}
// NewTagSnapshotURI returns the URI of the snapshot an ActorSnapshotTag owns,
// stored under an ActorTemplate's snapshotsConfig.location. The tag's prefix
// and its snapshot's are the same: a tag holds exactly one snapshot.
// NewTagSnapshotURI returns the URI of the snapshot a Tag owns, stored under
// an ActorTemplate's snapshotsConfig.location. The tag's prefix and its
// snapshot's are the same: a tag holds exactly one snapshot.
func NewTagSnapshotURI(location, atespace, name string) (SnapshotURI, error) {
owner := TagSnapshotOwner(atespace, name)
prefix, err := owner.Prefix(location)
@@ -184,8 +184,8 @@ func ParseSnapshotURI(uri string) (SnapshotURI, error) {
case n >= 6 && segments[n-6] == atespacesPathSegment && segments[n-4] == actorsOwnerKind && segments[n-2] == snapshotsPathSegment:
u.Path = strings.Join(segments[:n-6], "/")
return NewActorSnapshotURI(u.String(), segments[n-5], segments[n-3], segments[n-1])
// /root/atespaces/team-a/actor-snapshot-tags/<name>
// n-4 n-3 n-2 n-1
// /root/atespaces/team-a/tags/<name>
// n-4 n-3 n-2 n-1
case n >= 4 && segments[n-4] == atespacesPathSegment && segments[n-2] == tagsOwnerKind:
u.Path = strings.Join(segments[:n-4], "/")
return NewTagSnapshotURI(u.String(), segments[n-3], segments[n-1])
+7 -7
View File
@@ -173,21 +173,21 @@ func TestNewTagSnapshotURI(t *testing.T) {
location: "gs://bucket/root",
atespace: "team-a",
snapshot: "snap-1",
want: "gs://bucket/root/atespaces/team-a/actor-snapshot-tags/snap-1",
want: "gs://bucket/root/atespaces/team-a/tags/snap-1",
},
{
name: "trailing slash",
location: "gs://bucket/root/",
atespace: "team-a",
snapshot: "snap-1",
want: "gs://bucket/root/atespaces/team-a/actor-snapshot-tags/snap-1",
want: "gs://bucket/root/atespaces/team-a/tags/snap-1",
},
{
name: "bucket only",
location: "gs://bucket",
atespace: "team-a",
snapshot: "snap-1",
want: "gs://bucket/atespaces/team-a/actor-snapshot-tags/snap-1",
want: "gs://bucket/atespaces/team-a/tags/snap-1",
},
{
name: "empty location",
@@ -320,7 +320,7 @@ func TestSnapshotOwnerPrefix(t *testing.T) {
name: "tag",
owner: TagSnapshotOwner("team-a", "snap-1"),
location: "gs://bucket/root",
want: "gs://bucket/root/atespaces/team-a/actor-snapshot-tags/snap-1",
want: "gs://bucket/root/atespaces/team-a/tags/snap-1",
},
{
name: "the zero owner has no prefix",
@@ -386,7 +386,7 @@ func TestParseSnapshotURI(t *testing.T) {
},
{
name: "reads a tag snapshot",
uri: "gs://bucket/root/atespaces/team-a/actor-snapshot-tags/snap-1",
uri: "gs://bucket/root/atespaces/team-a/tags/snap-1",
wantLocation: "gs://bucket/root",
wantAtespace: "team-a",
wantName: "snap-1",
@@ -428,7 +428,7 @@ func TestParseSnapshotURI(t *testing.T) {
},
{
name: "rejects an object within a tag snapshot",
uri: "gs://bucket/root/atespaces/team-a/actor-snapshot-tags/snap-1/manifest.json",
uri: "gs://bucket/root/atespaces/team-a/tags/snap-1/manifest.json",
wantErr: true,
},
{
@@ -551,7 +551,7 @@ func TestSnapshotURIObject(t *testing.T) {
name: "tag snapshot object",
uri: tagURI,
objectName: "manifest.json",
want: "gs://bucket/root/atespaces/team-a/actor-snapshot-tags/snap-2/manifest.json",
want: "gs://bucket/root/atespaces/team-a/tags/snap-2/manifest.json",
},
{
name: "malformed escape",
+218 -220
View File
@@ -89,56 +89,56 @@ func (SnapshotContentScope) EnumDescriptor() ([]byte, []int) {
return file_ateapi_proto_rawDescGZIP(), []int{0}
}
type ActorSnapshotTagScope int32
type TagScope int32
const (
// Not set and rejected wherever a client supplies a scope
ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_UNSPECIFIED ActorSnapshotTagScope = 0
TagScope_TAG_SCOPE_UNSPECIFIED TagScope = 0
// May initialize Actors only in the tag's owning Atespace.
ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE ActorSnapshotTagScope = 1
TagScope_TAG_SCOPE_ATESPACE TagScope = 1
// Published for use by Actors in any Atespace. The tag remains addressed
// through its owning Atespace.
ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED ActorSnapshotTagScope = 2
TagScope_TAG_SCOPE_PUBLISHED TagScope = 2
)
// Enum value maps for ActorSnapshotTagScope.
// Enum value maps for TagScope.
var (
ActorSnapshotTagScope_name = map[int32]string{
0: "ACTOR_SNAPSHOT_TAG_SCOPE_UNSPECIFIED",
1: "ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE",
2: "ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED",
TagScope_name = map[int32]string{
0: "TAG_SCOPE_UNSPECIFIED",
1: "TAG_SCOPE_ATESPACE",
2: "TAG_SCOPE_PUBLISHED",
}
ActorSnapshotTagScope_value = map[string]int32{
"ACTOR_SNAPSHOT_TAG_SCOPE_UNSPECIFIED": 0,
"ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE": 1,
"ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED": 2,
TagScope_value = map[string]int32{
"TAG_SCOPE_UNSPECIFIED": 0,
"TAG_SCOPE_ATESPACE": 1,
"TAG_SCOPE_PUBLISHED": 2,
}
)
func (x ActorSnapshotTagScope) Enum() *ActorSnapshotTagScope {
p := new(ActorSnapshotTagScope)
func (x TagScope) Enum() *TagScope {
p := new(TagScope)
*p = x
return p
}
func (x ActorSnapshotTagScope) String() string {
func (x TagScope) String() string {
return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x))
}
func (ActorSnapshotTagScope) Descriptor() protoreflect.EnumDescriptor {
func (TagScope) Descriptor() protoreflect.EnumDescriptor {
return file_ateapi_proto_enumTypes[1].Descriptor()
}
func (ActorSnapshotTagScope) Type() protoreflect.EnumType {
func (TagScope) Type() protoreflect.EnumType {
return &file_ateapi_proto_enumTypes[1]
}
func (x ActorSnapshotTagScope) Number() protoreflect.EnumNumber {
func (x TagScope) Number() protoreflect.EnumNumber {
return protoreflect.EnumNumber(x)
}
// Deprecated: Use ActorSnapshotTagScope.Descriptor instead.
func (ActorSnapshotTagScope) EnumDescriptor() ([]byte, []int) {
// Deprecated: Use TagScope.Descriptor instead.
func (TagScope) EnumDescriptor() ([]byte, []int) {
return file_ateapi_proto_rawDescGZIP(), []int{1}
}
@@ -991,7 +991,7 @@ type Actor struct {
// +k8s:optional
// +k8s:subfield(atespace)=+k8s:required
// +k8s:immutable
SourceSnapshotTag *ObjectRef `protobuf:"bytes,6,opt,name=source_snapshot_tag,json=sourceSnapshotTag,proto3" json:"source_snapshot_tag,omitempty"`
SourceTag *ObjectRef `protobuf:"bytes,6,opt,name=source_tag,json=sourceTag,proto3" json:"source_tag,omitempty"`
// status is the system-managed state of the Actor. It is updated by the
// server and ignored on input. It is always present in output.
//
@@ -1052,9 +1052,9 @@ func (x *Actor) GetWorkerSelector() *Selector {
return nil
}
func (x *Actor) GetSourceSnapshotTag() *ObjectRef {
func (x *Actor) GetSourceTag() *ObjectRef {
if x != nil {
return x.SourceSnapshotTag
return x.SourceTag
}
return nil
}
@@ -1493,9 +1493,9 @@ type ActorStatus struct {
// +k8s:format=k8s-short-name
InProgressSnapshotName string `protobuf:"bytes,3,opt,name=in_progress_snapshot_name,json=inProgressSnapshotName,proto3" json:"in_progress_snapshot_name,omitempty"`
// external_snapshot is the Actor's current external snapshot.
// If the Actor was created from an ActorSnapshotTag this is the tag's
// snapshot, borrowed until the Actor's first suspend writes one of its own.
// Otherwise it is unset until the Actor is first suspended.
// If the Actor was created from a Tag this is the tag's snapshot, borrowed
// until the Actor's first suspend writes one of its own. Otherwise it is
// unset until the Actor is first suspended.
// +k8s:optional
ExternalSnapshot *ExternalSnapshot `protobuf:"bytes,4,opt,name=external_snapshot,json=externalSnapshot,proto3" json:"external_snapshot,omitempty"`
// Node-local state used only while the Actor is paused.
@@ -1521,9 +1521,9 @@ type ActorStatus struct {
InProgressLocalSnapshotName string `protobuf:"bytes,8,opt,name=in_progress_local_snapshot_name,json=inProgressLocalSnapshotName,proto3" json:"in_progress_local_snapshot_name,omitempty"`
// The UID of the actor template the Actor's guest state is currently built
// on: the one its latest sprint booted with, recorded when the resume (or
// first boot) commits RUNNING, or at creation for an Actor seeded from a
// snapshot tag, since that Actor borrows guest state that was already built.
// Empty means the Actor has no guest state yet.
// first boot) commits RUNNING, or at creation for an Actor seeded from a tag,
// since that Actor borrows guest state that was already built. Empty means
// the Actor has no guest state yet.
// Diverges from the UID of the Actor's actor_template ref when a suspended
// Actor is repointed at a replacement template, which forces the next
// restore to data-only.
@@ -1741,10 +1741,10 @@ func (x *WorkerAssignment) GetWorkerPodIp() string {
return ""
}
// ActorSnapshotTagStatus is the server-owned state of an ActorSnapshotTag.
// TagStatus is the server-owned state of a Tag.
// The tag is fixed to one external snapshot once its creation completes, so
// the rest is immutable provenance.
type ActorSnapshotTagStatus struct {
type TagStatus struct {
state protoimpl.MessageState `protogen:"open.v1"`
// snapshot is the external snapshot this tag points at. Unset while the tag
// is still being created, and immutable once set: a tag never moves between
@@ -1755,11 +1755,10 @@ type ActorSnapshotTagStatus struct {
// UID of the ActorTemplate the snapshot was taken under. Actors can only be
// seeded from a tag under the same template.
ActorTemplateUid string `protobuf:"bytes,2,opt,name=actor_template_uid,json=actorTemplateUid,proto3" json:"actor_template_uid,omitempty"`
// in_progress_snapshot_uri is where CreateActorSnapshotTag is copying the
// tag's own snapshot to. It is minted before a single object is written and
// cleared once snapshot is set, so a tag left with it set names exactly the
// objects its unfinished create stranded, and deleting the tag collects
// them.
// in_progress_snapshot_uri is where CreateTag is copying the tag's own
// snapshot to. It is minted before a single object is written and cleared
// once snapshot is set, so a tag left with it set names exactly the objects
// its unfinished create stranded, and deleting the tag collects them.
InProgressSnapshotUri string `protobuf:"bytes,3,opt,name=in_progress_snapshot_uri,json=inProgressSnapshotUri,proto3" json:"in_progress_snapshot_uri,omitempty"`
// source_actor_uid is the UID of the Actor this tag's snapshot was copied
// from.
@@ -1768,20 +1767,20 @@ type ActorSnapshotTagStatus struct {
sizeCache protoimpl.SizeCache
}
func (x *ActorSnapshotTagStatus) Reset() {
*x = ActorSnapshotTagStatus{}
func (x *TagStatus) Reset() {
*x = TagStatus{}
mi := &file_ateapi_proto_msgTypes[14]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ActorSnapshotTagStatus) String() string {
func (x *TagStatus) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ActorSnapshotTagStatus) ProtoMessage() {}
func (*TagStatus) ProtoMessage() {}
func (x *ActorSnapshotTagStatus) ProtoReflect() protoreflect.Message {
func (x *TagStatus) ProtoReflect() protoreflect.Message {
mi := &file_ateapi_proto_msgTypes[14]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -1793,45 +1792,44 @@ func (x *ActorSnapshotTagStatus) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use ActorSnapshotTagStatus.ProtoReflect.Descriptor instead.
func (*ActorSnapshotTagStatus) Descriptor() ([]byte, []int) {
// Deprecated: Use TagStatus.ProtoReflect.Descriptor instead.
func (*TagStatus) Descriptor() ([]byte, []int) {
return file_ateapi_proto_rawDescGZIP(), []int{14}
}
func (x *ActorSnapshotTagStatus) GetSnapshot() *ExternalSnapshot {
func (x *TagStatus) GetSnapshot() *ExternalSnapshot {
if x != nil {
return x.Snapshot
}
return nil
}
func (x *ActorSnapshotTagStatus) GetActorTemplateUid() string {
func (x *TagStatus) GetActorTemplateUid() string {
if x != nil {
return x.ActorTemplateUid
}
return ""
}
func (x *ActorSnapshotTagStatus) GetInProgressSnapshotUri() string {
func (x *TagStatus) GetInProgressSnapshotUri() string {
if x != nil {
return x.InProgressSnapshotUri
}
return ""
}
func (x *ActorSnapshotTagStatus) GetSourceActorUid() string {
func (x *TagStatus) GetSourceActorUid() string {
if x != nil {
return x.SourceActorUid
}
return ""
}
// ActorSnapshotTag is an Atespace-owned, stable name for one external
// snapshot. The external snapshot pointed by a tag is a copy of an actor snapshot.
// The snapshot is copied when the tag is
// created, and deleted when the tag is. Its owning Atespace cannot be deleted
// until the tag is removed.
type ActorSnapshotTag struct {
// Tag is an Atespace-owned, stable name for one external snapshot. The
// external snapshot pointed by a tag is a copy of an actor snapshot. The
// snapshot is copied when the tag is created, and deleted when the tag is. Its
// owning Atespace cannot be deleted until the tag is removed.
type Tag struct {
state protoimpl.MessageState `protogen:"open.v1"`
// Common resource metadata: atespace, name, uid, version, timestamps.
//
@@ -1841,13 +1839,13 @@ type ActorSnapshotTag struct {
// status is set by the control plane; it is ignored on write.
//
// +k8s:optional
Status *ActorSnapshotTagStatus `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"`
Status *TagStatus `protobuf:"bytes,2,opt,name=status,proto3" json:"status,omitempty"`
// scope controls who may create an Actor from this tag.
//
// +k8s:required
// +k8s:minimum=1
// +k8s:maximum=2 # keep this in sync with the ActorSnapshotTagScope enum
Scope ActorSnapshotTagScope `protobuf:"varint,3,opt,name=scope,proto3,enum=ateapi.ActorSnapshotTagScope" json:"scope,omitempty"`
// +k8s:maximum=2 # keep this in sync with the TagScope enum
Scope TagScope `protobuf:"varint,3,opt,name=scope,proto3,enum=ateapi.TagScope" json:"scope,omitempty"`
// source_actor is the suspended Actor whose external snapshot this tag
// captures. The tag lives in this Actor's Atespace. Set once at creation and
// immutable afterward.
@@ -1860,20 +1858,20 @@ type ActorSnapshotTag struct {
sizeCache protoimpl.SizeCache
}
func (x *ActorSnapshotTag) Reset() {
*x = ActorSnapshotTag{}
func (x *Tag) Reset() {
*x = Tag{}
mi := &file_ateapi_proto_msgTypes[15]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ActorSnapshotTag) String() string {
func (x *Tag) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ActorSnapshotTag) ProtoMessage() {}
func (*Tag) ProtoMessage() {}
func (x *ActorSnapshotTag) ProtoReflect() protoreflect.Message {
func (x *Tag) ProtoReflect() protoreflect.Message {
mi := &file_ateapi_proto_msgTypes[15]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -1885,33 +1883,33 @@ func (x *ActorSnapshotTag) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use ActorSnapshotTag.ProtoReflect.Descriptor instead.
func (*ActorSnapshotTag) Descriptor() ([]byte, []int) {
// Deprecated: Use Tag.ProtoReflect.Descriptor instead.
func (*Tag) Descriptor() ([]byte, []int) {
return file_ateapi_proto_rawDescGZIP(), []int{15}
}
func (x *ActorSnapshotTag) GetMetadata() *ResourceMetadata {
func (x *Tag) GetMetadata() *ResourceMetadata {
if x != nil {
return x.Metadata
}
return nil
}
func (x *ActorSnapshotTag) GetStatus() *ActorSnapshotTagStatus {
func (x *Tag) GetStatus() *TagStatus {
if x != nil {
return x.Status
}
return nil
}
func (x *ActorSnapshotTag) GetScope() ActorSnapshotTagScope {
func (x *Tag) GetScope() TagScope {
if x != nil {
return x.Scope
}
return ActorSnapshotTagScope_ACTOR_SNAPSHOT_TAG_SCOPE_UNSPECIFIED
return TagScope_TAG_SCOPE_UNSPECIFIED
}
func (x *ActorSnapshotTag) GetSourceActor() *ObjectRef {
func (x *Tag) GetSourceActor() *ObjectRef {
if x != nil {
return x.SourceActor
}
@@ -4900,29 +4898,29 @@ func (x *DeleteActorEgressPolicyRequest) GetActor() *ObjectRef {
return nil
}
type GetActorSnapshotTagRequest struct {
type GetTagRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ActorSnapshotTag *ObjectRef `protobuf:"bytes,1,opt,name=actor_snapshot_tag,json=actorSnapshotTag,proto3" json:"actor_snapshot_tag,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Tag *ObjectRef `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *GetActorSnapshotTagRequest) Reset() {
*x = GetActorSnapshotTagRequest{}
func (x *GetTagRequest) Reset() {
*x = GetTagRequest{}
mi := &file_ateapi_proto_msgTypes[66]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *GetActorSnapshotTagRequest) String() string {
func (x *GetTagRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetActorSnapshotTagRequest) ProtoMessage() {}
func (*GetTagRequest) ProtoMessage() {}
func (x *GetActorSnapshotTagRequest) ProtoReflect() protoreflect.Message {
func (x *GetTagRequest) ProtoReflect() protoreflect.Message {
mi := &file_ateapi_proto_msgTypes[66]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -4934,19 +4932,19 @@ func (x *GetActorSnapshotTagRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use GetActorSnapshotTagRequest.ProtoReflect.Descriptor instead.
func (*GetActorSnapshotTagRequest) Descriptor() ([]byte, []int) {
// Deprecated: Use GetTagRequest.ProtoReflect.Descriptor instead.
func (*GetTagRequest) Descriptor() ([]byte, []int) {
return file_ateapi_proto_rawDescGZIP(), []int{66}
}
func (x *GetActorSnapshotTagRequest) GetActorSnapshotTag() *ObjectRef {
func (x *GetTagRequest) GetTag() *ObjectRef {
if x != nil {
return x.ActorSnapshotTag
return x.Tag
}
return nil
}
type ListActorSnapshotTagsRequest struct {
type ListTagsRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The atespace to list tags from. Empty lists across all atespaces.
//
@@ -4960,7 +4958,7 @@ type ListActorSnapshotTagsRequest struct {
// +k8s:optional
// +k8s:minimum=1
PageSize int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3" json:"page_size,omitempty"`
// Pagination token from a previous ListActorSnapshotTags response.
// Pagination token from a previous ListTags response.
// Omit or leave empty for the first request.
//
// +k8s:optional
@@ -4970,20 +4968,20 @@ type ListActorSnapshotTagsRequest struct {
sizeCache protoimpl.SizeCache
}
func (x *ListActorSnapshotTagsRequest) Reset() {
*x = ListActorSnapshotTagsRequest{}
func (x *ListTagsRequest) Reset() {
*x = ListTagsRequest{}
mi := &file_ateapi_proto_msgTypes[67]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ListActorSnapshotTagsRequest) String() string {
func (x *ListTagsRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListActorSnapshotTagsRequest) ProtoMessage() {}
func (*ListTagsRequest) ProtoMessage() {}
func (x *ListActorSnapshotTagsRequest) ProtoReflect() protoreflect.Message {
func (x *ListTagsRequest) ProtoReflect() protoreflect.Message {
mi := &file_ateapi_proto_msgTypes[67]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -4995,54 +4993,54 @@ func (x *ListActorSnapshotTagsRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use ListActorSnapshotTagsRequest.ProtoReflect.Descriptor instead.
func (*ListActorSnapshotTagsRequest) Descriptor() ([]byte, []int) {
// Deprecated: Use ListTagsRequest.ProtoReflect.Descriptor instead.
func (*ListTagsRequest) Descriptor() ([]byte, []int) {
return file_ateapi_proto_rawDescGZIP(), []int{67}
}
func (x *ListActorSnapshotTagsRequest) GetAtespace() string {
func (x *ListTagsRequest) GetAtespace() string {
if x != nil {
return x.Atespace
}
return ""
}
func (x *ListActorSnapshotTagsRequest) GetPageSize() int32 {
func (x *ListTagsRequest) GetPageSize() int32 {
if x != nil {
return x.PageSize
}
return 0
}
func (x *ListActorSnapshotTagsRequest) GetPageToken() string {
func (x *ListTagsRequest) GetPageToken() string {
if x != nil {
return x.PageToken
}
return ""
}
type ListActorSnapshotTagsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
ActorSnapshotTags []*ActorSnapshotTag `protobuf:"bytes,1,rep,name=actor_snapshot_tags,json=actorSnapshotTags,proto3" json:"actor_snapshot_tags,omitempty"`
NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
type ListTagsResponse struct {
state protoimpl.MessageState `protogen:"open.v1"`
Tags []*Tag `protobuf:"bytes,1,rep,name=tags,proto3" json:"tags,omitempty"`
NextPageToken string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *ListActorSnapshotTagsResponse) Reset() {
*x = ListActorSnapshotTagsResponse{}
func (x *ListTagsResponse) Reset() {
*x = ListTagsResponse{}
mi := &file_ateapi_proto_msgTypes[68]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *ListActorSnapshotTagsResponse) String() string {
func (x *ListTagsResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*ListActorSnapshotTagsResponse) ProtoMessage() {}
func (*ListTagsResponse) ProtoMessage() {}
func (x *ListActorSnapshotTagsResponse) ProtoReflect() protoreflect.Message {
func (x *ListTagsResponse) ProtoReflect() protoreflect.Message {
mi := &file_ateapi_proto_msgTypes[68]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -5054,19 +5052,19 @@ func (x *ListActorSnapshotTagsResponse) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use ListActorSnapshotTagsResponse.ProtoReflect.Descriptor instead.
func (*ListActorSnapshotTagsResponse) Descriptor() ([]byte, []int) {
// Deprecated: Use ListTagsResponse.ProtoReflect.Descriptor instead.
func (*ListTagsResponse) Descriptor() ([]byte, []int) {
return file_ateapi_proto_rawDescGZIP(), []int{68}
}
func (x *ListActorSnapshotTagsResponse) GetActorSnapshotTags() []*ActorSnapshotTag {
func (x *ListTagsResponse) GetTags() []*Tag {
if x != nil {
return x.ActorSnapshotTags
return x.Tags
}
return nil
}
func (x *ListActorSnapshotTagsResponse) GetNextPageToken() string {
func (x *ListTagsResponse) GetNextPageToken() string {
if x != nil {
return x.NextPageToken
}
@@ -5087,32 +5085,32 @@ func (x *ListActorSnapshotTagsResponse) GetNextPageToken() string {
// which collects whatever that attempt stranded, and create it again.
//
// +k8s:customValidation # metadata.atespace must match source_actor.atespace
type CreateActorSnapshotTagRequest struct {
type CreateTagRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The tag to create. metadata.atespace, metadata.name, scope and source_actor
// are honored; metadata.atespace must match source_actor's, and status is
// ignored on write.
//
// +k8s:required
ActorSnapshotTag *ActorSnapshotTag `protobuf:"bytes,1,opt,name=actor_snapshot_tag,json=actorSnapshotTag,proto3" json:"actor_snapshot_tag,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Tag *Tag `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *CreateActorSnapshotTagRequest) Reset() {
*x = CreateActorSnapshotTagRequest{}
func (x *CreateTagRequest) Reset() {
*x = CreateTagRequest{}
mi := &file_ateapi_proto_msgTypes[69]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *CreateActorSnapshotTagRequest) String() string {
func (x *CreateTagRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CreateActorSnapshotTagRequest) ProtoMessage() {}
func (*CreateTagRequest) ProtoMessage() {}
func (x *CreateActorSnapshotTagRequest) ProtoReflect() protoreflect.Message {
func (x *CreateTagRequest) ProtoReflect() protoreflect.Message {
mi := &file_ateapi_proto_msgTypes[69]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -5124,51 +5122,50 @@ func (x *CreateActorSnapshotTagRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use CreateActorSnapshotTagRequest.ProtoReflect.Descriptor instead.
func (*CreateActorSnapshotTagRequest) Descriptor() ([]byte, []int) {
// Deprecated: Use CreateTagRequest.ProtoReflect.Descriptor instead.
func (*CreateTagRequest) Descriptor() ([]byte, []int) {
return file_ateapi_proto_rawDescGZIP(), []int{69}
}
func (x *CreateActorSnapshotTagRequest) GetActorSnapshotTag() *ActorSnapshotTag {
func (x *CreateTagRequest) GetTag() *Tag {
if x != nil {
return x.ActorSnapshotTag
return x.Tag
}
return nil
}
// Request to update mutable fields on an existing ActorSnapshotTag.
// Request to update mutable fields on an existing Tag.
// The tag keeps its address: the snapshot it points at cannot be changed.
type UpdateActorSnapshotTagRequest struct {
type UpdateTagRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
// The tag to update.
// actor_snapshot_tag.metadata.atespace and actor_snapshot_tag.metadata.name
// identify which resource to update.
// actor_snapshot_tag.metadata.version and actor_snapshot_tag.metadata.uid
// are required preconditions
// tag.metadata.atespace and tag.metadata.name identify which resource to
// update.
// tag.metadata.version and tag.metadata.uid are required preconditions
//
// +k8s:required
// +k8s:opaqueType # updates are handled in 2 steps, do not descend
// +k8s:subfield(metadata)=+k8s:required
// +k8s:customValidation # TODO: when we get nested subfields, require metadata.atespace
ActorSnapshotTag *ActorSnapshotTag `protobuf:"bytes,1,opt,name=actor_snapshot_tag,json=actorSnapshotTag,proto3" json:"actor_snapshot_tag,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Tag *Tag `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *UpdateActorSnapshotTagRequest) Reset() {
*x = UpdateActorSnapshotTagRequest{}
func (x *UpdateTagRequest) Reset() {
*x = UpdateTagRequest{}
mi := &file_ateapi_proto_msgTypes[70]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *UpdateActorSnapshotTagRequest) String() string {
func (x *UpdateTagRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*UpdateActorSnapshotTagRequest) ProtoMessage() {}
func (*UpdateTagRequest) ProtoMessage() {}
func (x *UpdateActorSnapshotTagRequest) ProtoReflect() protoreflect.Message {
func (x *UpdateTagRequest) ProtoReflect() protoreflect.Message {
mi := &file_ateapi_proto_msgTypes[70]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -5180,41 +5177,41 @@ func (x *UpdateActorSnapshotTagRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use UpdateActorSnapshotTagRequest.ProtoReflect.Descriptor instead.
func (*UpdateActorSnapshotTagRequest) Descriptor() ([]byte, []int) {
// Deprecated: Use UpdateTagRequest.ProtoReflect.Descriptor instead.
func (*UpdateTagRequest) Descriptor() ([]byte, []int) {
return file_ateapi_proto_rawDescGZIP(), []int{70}
}
func (x *UpdateActorSnapshotTagRequest) GetActorSnapshotTag() *ActorSnapshotTag {
func (x *UpdateTagRequest) GetTag() *Tag {
if x != nil {
return x.ActorSnapshotTag
return x.Tag
}
return nil
}
type DeleteActorSnapshotTagRequest struct {
type DeleteTagRequest struct {
state protoimpl.MessageState `protogen:"open.v1"`
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ActorSnapshotTag *ObjectRef `protobuf:"bytes,1,opt,name=actor_snapshot_tag,json=actorSnapshotTag,proto3" json:"actor_snapshot_tag,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
Tag *ObjectRef `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"`
unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache
}
func (x *DeleteActorSnapshotTagRequest) Reset() {
*x = DeleteActorSnapshotTagRequest{}
func (x *DeleteTagRequest) Reset() {
*x = DeleteTagRequest{}
mi := &file_ateapi_proto_msgTypes[71]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
func (x *DeleteActorSnapshotTagRequest) String() string {
func (x *DeleteTagRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*DeleteActorSnapshotTagRequest) ProtoMessage() {}
func (*DeleteTagRequest) ProtoMessage() {}
func (x *DeleteActorSnapshotTagRequest) ProtoReflect() protoreflect.Message {
func (x *DeleteTagRequest) ProtoReflect() protoreflect.Message {
mi := &file_ateapi_proto_msgTypes[71]
if x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
@@ -5226,14 +5223,14 @@ func (x *DeleteActorSnapshotTagRequest) ProtoReflect() protoreflect.Message {
return mi.MessageOf(x)
}
// Deprecated: Use DeleteActorSnapshotTagRequest.ProtoReflect.Descriptor instead.
func (*DeleteActorSnapshotTagRequest) Descriptor() ([]byte, []int) {
// Deprecated: Use DeleteTagRequest.ProtoReflect.Descriptor instead.
func (*DeleteTagRequest) Descriptor() ([]byte, []int) {
return file_ateapi_proto_rawDescGZIP(), []int{71}
}
func (x *DeleteActorSnapshotTagRequest) GetActorSnapshotTag() *ObjectRef {
func (x *DeleteTagRequest) GetTag() *ObjectRef {
if x != nil {
return x.ActorSnapshotTag
return x.Tag
}
return nil
}
@@ -6791,12 +6788,13 @@ const file_ateapi_proto_rawDesc = "" +
"\x12STATUS_UNSPECIFIED\x10\x00\x12\x12\n" +
"\x0eSTATUS_PENDING\x10\x01\x12\x12\n" +
"\x0eSTATUS_CREATED\x10\x02\x12\x13\n" +
"\x0fSTATUS_DELETING\x10\x03\"\xa2\x02\n" +
"\x0fSTATUS_DELETING\x10\x03\"\x91\x02\n" +
"\x05Actor\x124\n" +
"\bmetadata\x18\x01 \x01(\v2\x18.ateapi.ResourceMetadataR\bmetadata\x128\n" +
"\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" +
"\x0fworker_selector\x18\x05 \x01(\v2\x10.ateapi.SelectorR\x0eworkerSelector\x120\n" +
"\n" +
"source_tag\x18\x06 \x01(\v2\x11.ateapi.ObjectRefR\tsourceTag\x12+\n" +
"\x06status\x18\a \x01(\v2\x13.ateapi.ActorStatusR\x06status\"n\n" +
"\fEgressPolicy\x124\n" +
"\bmetadata\x18\x01 \x01(\v2\x18.ateapi.ResourceMetadataR\bmetadata\x12(\n" +
@@ -6834,16 +6832,16 @@ const file_ateapi_proto_rawDesc = "" +
"\n" +
"worker_pod\x18\x03 \x01(\tR\tworkerPod\x12$\n" +
"\x0eworker_pod_uid\x18\x04 \x01(\tR\fworkerPodUid\x12\"\n" +
"\rworker_pod_ip\x18\x05 \x01(\tR\vworkerPodIp\"\xdf\x01\n" +
"\x16ActorSnapshotTagStatus\x124\n" +
"\rworker_pod_ip\x18\x05 \x01(\tR\vworkerPodIp\"\xd2\x01\n" +
"\tTagStatus\x124\n" +
"\bsnapshot\x18\x01 \x01(\v2\x18.ateapi.ExternalSnapshotR\bsnapshot\x12,\n" +
"\x12actor_template_uid\x18\x02 \x01(\tR\x10actorTemplateUid\x127\n" +
"\x18in_progress_snapshot_uri\x18\x03 \x01(\tR\x15inProgressSnapshotUri\x12(\n" +
"\x10source_actor_uid\x18\x04 \x01(\tR\x0esourceActorUid\"\xeb\x01\n" +
"\x10ActorSnapshotTag\x124\n" +
"\bmetadata\x18\x01 \x01(\v2\x18.ateapi.ResourceMetadataR\bmetadata\x126\n" +
"\x06status\x18\x02 \x01(\v2\x1e.ateapi.ActorSnapshotTagStatusR\x06status\x123\n" +
"\x05scope\x18\x03 \x01(\x0e2\x1d.ateapi.ActorSnapshotTagScopeR\x05scope\x124\n" +
"\x10source_actor_uid\x18\x04 \x01(\tR\x0esourceActorUid\"\xc4\x01\n" +
"\x03Tag\x124\n" +
"\bmetadata\x18\x01 \x01(\v2\x18.ateapi.ResourceMetadataR\bmetadata\x12)\n" +
"\x06status\x18\x02 \x01(\v2\x11.ateapi.TagStatusR\x06status\x12&\n" +
"\x05scope\x18\x03 \x01(\x0e2\x10.ateapi.TagScopeR\x05scope\x124\n" +
"\fsource_actor\x18\x04 \x01(\v2\x11.ateapi.ObjectRefR\vsourceActor\"@\n" +
"\bAtespace\x124\n" +
"\bmetadata\x18\x01 \x01(\v2\x18.ateapi.ResourceMetadataR\bmetadata\";\n" +
@@ -6997,23 +6995,23 @@ const file_ateapi_proto_rawDesc = "" +
"\x05actor\x18\x01 \x01(\v2\x11.ateapi.ObjectRefR\x05actor\x129\n" +
"\regress_policy\x18\x02 \x01(\v2\x14.ateapi.EgressPolicyR\fegressPolicy\"I\n" +
"\x1eDeleteActorEgressPolicyRequest\x12'\n" +
"\x05actor\x18\x01 \x01(\v2\x11.ateapi.ObjectRefR\x05actor\"]\n" +
"\x1aGetActorSnapshotTagRequest\x12?\n" +
"\x12actor_snapshot_tag\x18\x01 \x01(\v2\x11.ateapi.ObjectRefR\x10actorSnapshotTag\"v\n" +
"\x1cListActorSnapshotTagsRequest\x12\x1a\n" +
"\x05actor\x18\x01 \x01(\v2\x11.ateapi.ObjectRefR\x05actor\"4\n" +
"\rGetTagRequest\x12#\n" +
"\x03tag\x18\x01 \x01(\v2\x11.ateapi.ObjectRefR\x03tag\"i\n" +
"\x0fListTagsRequest\x12\x1a\n" +
"\batespace\x18\x01 \x01(\tR\batespace\x12\x1b\n" +
"\tpage_size\x18\x02 \x01(\x05R\bpageSize\x12\x1d\n" +
"\n" +
"page_token\x18\x03 \x01(\tR\tpageToken\"\x91\x01\n" +
"\x1dListActorSnapshotTagsResponse\x12H\n" +
"\x13actor_snapshot_tags\x18\x01 \x03(\v2\x18.ateapi.ActorSnapshotTagR\x11actorSnapshotTags\x12&\n" +
"\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"g\n" +
"\x1dCreateActorSnapshotTagRequest\x12F\n" +
"\x12actor_snapshot_tag\x18\x01 \x01(\v2\x18.ateapi.ActorSnapshotTagR\x10actorSnapshotTag\"g\n" +
"\x1dUpdateActorSnapshotTagRequest\x12F\n" +
"\x12actor_snapshot_tag\x18\x01 \x01(\v2\x18.ateapi.ActorSnapshotTagR\x10actorSnapshotTag\"`\n" +
"\x1dDeleteActorSnapshotTagRequest\x12?\n" +
"\x12actor_snapshot_tag\x18\x01 \x01(\v2\x11.ateapi.ObjectRefR\x10actorSnapshotTag\";\n" +
"page_token\x18\x03 \x01(\tR\tpageToken\"[\n" +
"\x10ListTagsResponse\x12\x1f\n" +
"\x04tags\x18\x01 \x03(\v2\v.ateapi.TagR\x04tags\x12&\n" +
"\x0fnext_page_token\x18\x02 \x01(\tR\rnextPageToken\"1\n" +
"\x10CreateTagRequest\x12\x1d\n" +
"\x03tag\x18\x01 \x01(\v2\v.ateapi.TagR\x03tag\"1\n" +
"\x10UpdateTagRequest\x12\x1d\n" +
"\x03tag\x18\x01 \x01(\v2\v.ateapi.TagR\x03tag\"7\n" +
"\x10DeleteTagRequest\x12#\n" +
"\x03tag\x18\x01 \x01(\v2\x11.ateapi.ObjectRefR\x03tag\";\n" +
"\rDeleteOptions\x12\x18\n" +
"\aversion\x18\x01 \x01(\x03R\aversion\x12\x10\n" +
"\x03uid\x18\x02 \x01(\tR\x03uid\"\x8a\x01\n" +
@@ -7103,11 +7101,11 @@ const file_ateapi_proto_rawDesc = "" +
"\x14SnapshotContentScope\x12&\n" +
"\"SNAPSHOT_CONTENT_SCOPE_UNSPECIFIED\x10\x00\x12\x1f\n" +
"\x1bSNAPSHOT_CONTENT_SCOPE_FULL\x10\x01\x12\x1f\n" +
"\x1bSNAPSHOT_CONTENT_SCOPE_DATA\x10\x02*\x90\x01\n" +
"\x15ActorSnapshotTagScope\x12(\n" +
"$ACTOR_SNAPSHOT_TAG_SCOPE_UNSPECIFIED\x10\x00\x12%\n" +
"!ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE\x10\x01\x12&\n" +
"\"ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED\x10\x02*\xf7\x01\n" +
"\x1bSNAPSHOT_CONTENT_SCOPE_DATA\x10\x02*V\n" +
"\bTagScope\x12\x19\n" +
"\x15TAG_SCOPE_UNSPECIFIED\x10\x00\x12\x16\n" +
"\x12TAG_SCOPE_ATESPACE\x10\x01\x12\x17\n" +
"\x13TAG_SCOPE_PUBLISHED\x10\x02*\xf7\x01\n" +
"\n" +
"ActorState\x12\x1b\n" +
"\x17ACTOR_STATE_UNSPECIFIED\x10\x00\x12\x18\n" +
@@ -7138,7 +7136,7 @@ const file_ateapi_proto_rawDesc = "" +
"\x15WORKER_STATE_DRAINING\x10\x02*k\n" +
"\x17ActorCertificatePurpose\x12)\n" +
"%ACTOR_CERTIFICATE_PURPOSE_UNSPECIFIED\x10\x00\x12%\n" +
"!ACTOR_CERTIFICATE_PURPOSE_ATUNNEL\x10\x012\xb6\x13\n" +
"!ACTOR_CERTIFICATE_PURPOSE_ATUNNEL\x10\x012\xf3\x11\n" +
"\aControl\x124\n" +
"\bGetActor\x12\x17.ateapi.GetActorRequest\x1a\r.ateapi.Actor\"\x00\x12:\n" +
"\vCreateActor\x12\x1a.ateapi.CreateActorRequest\x1a\r.ateapi.Actor\"\x00\x12:\n" +
@@ -7151,12 +7149,12 @@ const file_ateapi_proto_rawDesc = "" +
"\x14GetActorEgressPolicy\x12#.ateapi.GetActorEgressPolicyRequest\x1a\x14.ateapi.EgressPolicy\"\x00\x12Y\n" +
"\x17CreateActorEgressPolicy\x12&.ateapi.CreateActorEgressPolicyRequest\x1a\x14.ateapi.EgressPolicy\"\x00\x12Y\n" +
"\x17UpdateActorEgressPolicy\x12&.ateapi.UpdateActorEgressPolicyRequest\x1a\x14.ateapi.EgressPolicy\"\x00\x12Y\n" +
"\x17DeleteActorEgressPolicy\x12&.ateapi.DeleteActorEgressPolicyRequest\x1a\x14.ateapi.EgressPolicy\"\x00\x12[\n" +
"\x16CreateActorSnapshotTag\x12%.ateapi.CreateActorSnapshotTagRequest\x1a\x18.ateapi.ActorSnapshotTag\"\x00\x12U\n" +
"\x13GetActorSnapshotTag\x12\".ateapi.GetActorSnapshotTagRequest\x1a\x18.ateapi.ActorSnapshotTag\"\x00\x12f\n" +
"\x15ListActorSnapshotTags\x12$.ateapi.ListActorSnapshotTagsRequest\x1a%.ateapi.ListActorSnapshotTagsResponse\"\x00\x12[\n" +
"\x16UpdateActorSnapshotTag\x12%.ateapi.UpdateActorSnapshotTagRequest\x1a\x18.ateapi.ActorSnapshotTag\"\x00\x12[\n" +
"\x16DeleteActorSnapshotTag\x12%.ateapi.DeleteActorSnapshotTagRequest\x1a\x18.ateapi.ActorSnapshotTag\"\x00\x12H\n" +
"\x17DeleteActorEgressPolicy\x12&.ateapi.DeleteActorEgressPolicyRequest\x1a\x14.ateapi.EgressPolicy\"\x00\x124\n" +
"\tCreateTag\x12\x18.ateapi.CreateTagRequest\x1a\v.ateapi.Tag\"\x00\x12.\n" +
"\x06GetTag\x12\x15.ateapi.GetTagRequest\x1a\v.ateapi.Tag\"\x00\x12?\n" +
"\bListTags\x12\x17.ateapi.ListTagsRequest\x1a\x18.ateapi.ListTagsResponse\"\x00\x124\n" +
"\tUpdateTag\x12\x18.ateapi.UpdateTagRequest\x1a\v.ateapi.Tag\"\x00\x124\n" +
"\tDeleteTag\x12\x18.ateapi.DeleteTagRequest\x1a\v.ateapi.Tag\"\x00\x12H\n" +
"\vListWorkers\x12\x1a.ateapi.ListWorkersRequest\x1a\x1b.ateapi.ListWorkersResponse\"\x00\x127\n" +
"\tGetWorker\x12\x18.ateapi.GetWorkerRequest\x1a\x0e.ateapi.Worker\"\x00\x12=\n" +
"\fCreateWorker\x12\x1b.ateapi.CreateWorkerRequest\x1a\x0e.ateapi.Worker\"\x00\x12=\n" +
@@ -7196,7 +7194,7 @@ var file_ateapi_proto_enumTypes = make([]protoimpl.EnumInfo, 9)
var file_ateapi_proto_msgTypes = make([]protoimpl.MessageInfo, 97)
var file_ateapi_proto_goTypes = []any{
(SnapshotContentScope)(0), // 0: ateapi.SnapshotContentScope
(ActorSnapshotTagScope)(0), // 1: ateapi.ActorSnapshotTagScope
(TagScope)(0), // 1: ateapi.TagScope
(ActorState)(0), // 2: ateapi.ActorState
(SandboxClass)(0), // 3: ateapi.SandboxClass
(ResumeSource)(0), // 4: ateapi.ResumeSource
@@ -7218,8 +7216,8 @@ var file_ateapi_proto_goTypes = []any{
(*CredentialHeaderInjection)(nil), // 20: ateapi.CredentialHeaderInjection
(*ActorStatus)(nil), // 21: ateapi.ActorStatus
(*WorkerAssignment)(nil), // 22: ateapi.WorkerAssignment
(*ActorSnapshotTagStatus)(nil), // 23: ateapi.ActorSnapshotTagStatus
(*ActorSnapshotTag)(nil), // 24: ateapi.ActorSnapshotTag
(*TagStatus)(nil), // 23: ateapi.TagStatus
(*Tag)(nil), // 24: ateapi.Tag
(*Atespace)(nil), // 25: ateapi.Atespace
(*ObjectRef)(nil), // 26: ateapi.ObjectRef
(*ActorTemplate)(nil), // 27: ateapi.ActorTemplate
@@ -7270,12 +7268,12 @@ var file_ateapi_proto_goTypes = []any{
(*CreateActorEgressPolicyRequest)(nil), // 72: ateapi.CreateActorEgressPolicyRequest
(*UpdateActorEgressPolicyRequest)(nil), // 73: ateapi.UpdateActorEgressPolicyRequest
(*DeleteActorEgressPolicyRequest)(nil), // 74: ateapi.DeleteActorEgressPolicyRequest
(*GetActorSnapshotTagRequest)(nil), // 75: ateapi.GetActorSnapshotTagRequest
(*ListActorSnapshotTagsRequest)(nil), // 76: ateapi.ListActorSnapshotTagsRequest
(*ListActorSnapshotTagsResponse)(nil), // 77: ateapi.ListActorSnapshotTagsResponse
(*CreateActorSnapshotTagRequest)(nil), // 78: ateapi.CreateActorSnapshotTagRequest
(*UpdateActorSnapshotTagRequest)(nil), // 79: ateapi.UpdateActorSnapshotTagRequest
(*DeleteActorSnapshotTagRequest)(nil), // 80: ateapi.DeleteActorSnapshotTagRequest
(*GetTagRequest)(nil), // 75: ateapi.GetTagRequest
(*ListTagsRequest)(nil), // 76: ateapi.ListTagsRequest
(*ListTagsResponse)(nil), // 77: ateapi.ListTagsResponse
(*CreateTagRequest)(nil), // 78: ateapi.CreateTagRequest
(*UpdateTagRequest)(nil), // 79: ateapi.UpdateTagRequest
(*DeleteTagRequest)(nil), // 80: ateapi.DeleteTagRequest
(*DeleteOptions)(nil), // 81: ateapi.DeleteOptions
(*ListWorkerActorAssignmentsRequest)(nil), // 82: ateapi.ListWorkerActorAssignmentsRequest
(*ListWorkerActorAssignmentsResponse)(nil), // 83: ateapi.ListWorkerActorAssignmentsResponse
@@ -7315,7 +7313,7 @@ var file_ateapi_proto_depIdxs = []int32{
12, // 7: ateapi.Actor.metadata:type_name -> ateapi.ResourceMetadata
26, // 8: ateapi.Actor.actor_template:type_name -> ateapi.ObjectRef
11, // 9: ateapi.Actor.worker_selector:type_name -> ateapi.Selector
26, // 10: ateapi.Actor.source_snapshot_tag:type_name -> ateapi.ObjectRef
26, // 10: ateapi.Actor.source_tag:type_name -> ateapi.ObjectRef
21, // 11: ateapi.Actor.status:type_name -> ateapi.ActorStatus
12, // 12: ateapi.EgressPolicy.metadata:type_name -> ateapi.ResourceMetadata
16, // 13: ateapi.EgressPolicy.rules:type_name -> ateapi.EgressRule
@@ -7330,11 +7328,11 @@ var file_ateapi_proto_depIdxs = []int32{
10, // 22: ateapi.ActorStatus.local_snapshot_info:type_name -> ateapi.LocalSnapshotInfo
13, // 23: ateapi.ActorStatus.actor_volumes:type_name -> ateapi.ExternalVolume
26, // 24: ateapi.WorkerAssignment.worker:type_name -> ateapi.ObjectRef
9, // 25: ateapi.ActorSnapshotTagStatus.snapshot:type_name -> ateapi.ExternalSnapshot
12, // 26: ateapi.ActorSnapshotTag.metadata:type_name -> ateapi.ResourceMetadata
23, // 27: ateapi.ActorSnapshotTag.status:type_name -> ateapi.ActorSnapshotTagStatus
1, // 28: ateapi.ActorSnapshotTag.scope:type_name -> ateapi.ActorSnapshotTagScope
26, // 29: ateapi.ActorSnapshotTag.source_actor:type_name -> ateapi.ObjectRef
9, // 25: ateapi.TagStatus.snapshot:type_name -> ateapi.ExternalSnapshot
12, // 26: ateapi.Tag.metadata:type_name -> ateapi.ResourceMetadata
23, // 27: ateapi.Tag.status:type_name -> ateapi.TagStatus
1, // 28: ateapi.Tag.scope:type_name -> ateapi.TagScope
26, // 29: ateapi.Tag.source_actor:type_name -> ateapi.ObjectRef
12, // 30: ateapi.Atespace.metadata:type_name -> ateapi.ResourceMetadata
12, // 31: ateapi.ActorTemplate.metadata:type_name -> ateapi.ResourceMetadata
11, // 32: ateapi.ActorTemplate.worker_selector:type_name -> ateapi.Selector
@@ -7393,11 +7391,11 @@ var file_ateapi_proto_depIdxs = []int32{
26, // 85: ateapi.UpdateActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef
15, // 86: ateapi.UpdateActorEgressPolicyRequest.egress_policy:type_name -> ateapi.EgressPolicy
26, // 87: ateapi.DeleteActorEgressPolicyRequest.actor:type_name -> ateapi.ObjectRef
26, // 88: ateapi.GetActorSnapshotTagRequest.actor_snapshot_tag:type_name -> ateapi.ObjectRef
24, // 89: ateapi.ListActorSnapshotTagsResponse.actor_snapshot_tags:type_name -> ateapi.ActorSnapshotTag
24, // 90: ateapi.CreateActorSnapshotTagRequest.actor_snapshot_tag:type_name -> ateapi.ActorSnapshotTag
24, // 91: ateapi.UpdateActorSnapshotTagRequest.actor_snapshot_tag:type_name -> ateapi.ActorSnapshotTag
26, // 92: ateapi.DeleteActorSnapshotTagRequest.actor_snapshot_tag:type_name -> ateapi.ObjectRef
26, // 88: ateapi.GetTagRequest.tag:type_name -> ateapi.ObjectRef
24, // 89: ateapi.ListTagsResponse.tags:type_name -> ateapi.Tag
24, // 90: ateapi.CreateTagRequest.tag:type_name -> ateapi.Tag
24, // 91: ateapi.UpdateTagRequest.tag:type_name -> ateapi.Tag
26, // 92: ateapi.DeleteTagRequest.tag:type_name -> ateapi.ObjectRef
26, // 93: ateapi.ListWorkerActorAssignmentsRequest.worker:type_name -> ateapi.ObjectRef
96, // 94: ateapi.ListWorkerActorAssignmentsResponse.actor_assignments:type_name -> ateapi.ActorAssignment
93, // 95: ateapi.ListWorkersResponse.workers:type_name -> ateapi.Worker
@@ -7435,11 +7433,11 @@ var file_ateapi_proto_depIdxs = []int32{
72, // 127: ateapi.Control.CreateActorEgressPolicy:input_type -> ateapi.CreateActorEgressPolicyRequest
73, // 128: ateapi.Control.UpdateActorEgressPolicy:input_type -> ateapi.UpdateActorEgressPolicyRequest
74, // 129: ateapi.Control.DeleteActorEgressPolicy:input_type -> ateapi.DeleteActorEgressPolicyRequest
78, // 130: ateapi.Control.CreateActorSnapshotTag:input_type -> ateapi.CreateActorSnapshotTagRequest
75, // 131: ateapi.Control.GetActorSnapshotTag:input_type -> ateapi.GetActorSnapshotTagRequest
76, // 132: ateapi.Control.ListActorSnapshotTags:input_type -> ateapi.ListActorSnapshotTagsRequest
79, // 133: ateapi.Control.UpdateActorSnapshotTag:input_type -> ateapi.UpdateActorSnapshotTagRequest
80, // 134: ateapi.Control.DeleteActorSnapshotTag:input_type -> ateapi.DeleteActorSnapshotTagRequest
78, // 130: ateapi.Control.CreateTag:input_type -> ateapi.CreateTagRequest
75, // 131: ateapi.Control.GetTag:input_type -> ateapi.GetTagRequest
76, // 132: ateapi.Control.ListTags:input_type -> ateapi.ListTagsRequest
79, // 133: ateapi.Control.UpdateTag:input_type -> ateapi.UpdateTagRequest
80, // 134: ateapi.Control.DeleteTag:input_type -> ateapi.DeleteTagRequest
84, // 135: ateapi.Control.ListWorkers:input_type -> ateapi.ListWorkersRequest
86, // 136: ateapi.Control.GetWorker:input_type -> ateapi.GetWorkerRequest
87, // 137: ateapi.Control.CreateWorker:input_type -> ateapi.CreateWorkerRequest
@@ -7470,11 +7468,11 @@ var file_ateapi_proto_depIdxs = []int32{
15, // 162: ateapi.Control.CreateActorEgressPolicy:output_type -> ateapi.EgressPolicy
15, // 163: ateapi.Control.UpdateActorEgressPolicy:output_type -> ateapi.EgressPolicy
15, // 164: ateapi.Control.DeleteActorEgressPolicy:output_type -> ateapi.EgressPolicy
24, // 165: ateapi.Control.CreateActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag
24, // 166: ateapi.Control.GetActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag
77, // 167: ateapi.Control.ListActorSnapshotTags:output_type -> ateapi.ListActorSnapshotTagsResponse
24, // 168: ateapi.Control.UpdateActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag
24, // 169: ateapi.Control.DeleteActorSnapshotTag:output_type -> ateapi.ActorSnapshotTag
24, // 165: ateapi.Control.CreateTag:output_type -> ateapi.Tag
24, // 166: ateapi.Control.GetTag:output_type -> ateapi.Tag
77, // 167: ateapi.Control.ListTags:output_type -> ateapi.ListTagsResponse
24, // 168: ateapi.Control.UpdateTag:output_type -> ateapi.Tag
24, // 169: ateapi.Control.DeleteTag:output_type -> ateapi.Tag
85, // 170: ateapi.Control.ListWorkers:output_type -> ateapi.ListWorkersResponse
93, // 171: ateapi.Control.GetWorker:output_type -> ateapi.Worker
93, // 172: ateapi.Control.CreateWorker:output_type -> ateapi.Worker
+53 -57
View File
@@ -61,22 +61,21 @@ service Control {
// Tag the external snapshot a suspended Actor holds. The tag gets its own
// copy of that snapshot, so suspending or deleting the Actor afterwards
// cannot collect it.
rpc CreateActorSnapshotTag(CreateActorSnapshotTagRequest) returns (ActorSnapshotTag) {}
rpc CreateTag(CreateTagRequest) returns (Tag) {}
// Get an ActorSnapshot tag.
rpc GetActorSnapshotTag(GetActorSnapshotTagRequest) returns (ActorSnapshotTag) {}
// Get a Tag.
rpc GetTag(GetTagRequest) returns (Tag) {}
// List ActorSnapshot tags.
rpc ListActorSnapshotTags(ListActorSnapshotTagsRequest) returns (ListActorSnapshotTagsResponse) {}
// List Tags.
rpc ListTags(ListTagsRequest) returns (ListTagsResponse) {}
// Publish or unpublish an ActorSnapshot tag without changing its address.
rpc UpdateActorSnapshotTag(UpdateActorSnapshotTagRequest) returns (ActorSnapshotTag) {}
// Publish or unpublish a Tag without changing its address.
rpc UpdateTag(UpdateTagRequest) returns (Tag) {}
// Delete an ActorSnapshot tag and the external snapshot it owns. Actors
// created from the tag that have not yet been suspended still point at that
// external snapshot and become unrecoverable, so do not delete a tag while
// such Actors exist.
rpc DeleteActorSnapshotTag(DeleteActorSnapshotTagRequest) returns (ActorSnapshotTag) {}
// Delete a Tag and the external snapshot it owns. Actors created from the
// tag that have not yet been suspended still point at that external snapshot
// and become unrecoverable, so do not delete a tag while such Actors exist.
rpc DeleteTag(DeleteTagRequest) returns (Tag) {}
// List Workers.
rpc ListWorkers(ListWorkersRequest) returns (ListWorkersResponse) {}
@@ -115,7 +114,7 @@ service Control {
rpc ListAtespaces(ListAtespacesRequest) returns (ListAtespacesResponse) {}
// Delete an empty Atespace. Rejects (FailedPrecondition) if any Actors or
// ActorSnapshotTags remain.
// Tags remain.
rpc DeleteAtespace(DeleteAtespaceRequest) returns (Atespace) {}
rpc CreateActorTemplate(CreateActorTemplateRequest) returns (ActorTemplate) {}
@@ -183,14 +182,14 @@ enum SnapshotContentScope {
// Keep this in sync with the maximums on fields of this type.
}
enum ActorSnapshotTagScope {
enum TagScope {
// Not set and rejected wherever a client supplies a scope
ACTOR_SNAPSHOT_TAG_SCOPE_UNSPECIFIED = 0;
TAG_SCOPE_UNSPECIFIED = 0;
// May initialize Actors only in the tag's owning Atespace.
ACTOR_SNAPSHOT_TAG_SCOPE_ATESPACE = 1;
TAG_SCOPE_ATESPACE = 1;
// Published for use by Actors in any Atespace. The tag remains addressed
// through its owning Atespace.
ACTOR_SNAPSHOT_TAG_SCOPE_PUBLISHED = 2;
TAG_SCOPE_PUBLISHED = 2;
}
// Selector matches worker pools by label.
@@ -361,7 +360,7 @@ message Actor {
// +k8s:optional
// +k8s:subfield(atespace)=+k8s:required
// +k8s:immutable
ObjectRef source_snapshot_tag = 6;
ObjectRef source_tag = 6;
// status is the system-managed state of the Actor. It is updated by the
// server and ignored on input. It is always present in output.
@@ -537,9 +536,9 @@ message ActorStatus {
string in_progress_snapshot_name = 3;
// external_snapshot is the Actor's current external snapshot.
// If the Actor was created from an ActorSnapshotTag this is the tag's
// snapshot, borrowed until the Actor's first suspend writes one of its own.
// Otherwise it is unset until the Actor is first suspended.
// If the Actor was created from a Tag this is the tag's snapshot, borrowed
// until the Actor's first suspend writes one of its own. Otherwise it is
// unset until the Actor is first suspended.
// +k8s:optional
ExternalSnapshot external_snapshot = 4;
@@ -569,9 +568,9 @@ message ActorStatus {
// The UID of the actor template the Actor's guest state is currently built
// on: the one its latest sprint booted with, recorded when the resume (or
// first boot) commits RUNNING, or at creation for an Actor seeded from a
// snapshot tag, since that Actor borrows guest state that was already built.
// Empty means the Actor has no guest state yet.
// first boot) commits RUNNING, or at creation for an Actor seeded from a tag,
// since that Actor borrows guest state that was already built. Empty means
// the Actor has no guest state yet.
// Diverges from the UID of the Actor's actor_template ref when a suspended
// Actor is repointed at a replacement template, which forces the next
// restore to data-only.
@@ -631,10 +630,10 @@ message WorkerAssignment {
string worker_pod_ip = 5;
}
// ActorSnapshotTagStatus is the server-owned state of an ActorSnapshotTag.
// TagStatus is the server-owned state of a Tag.
// The tag is fixed to one external snapshot once its creation completes, so
// the rest is immutable provenance.
message ActorSnapshotTagStatus {
message TagStatus {
// snapshot is the external snapshot this tag points at. Unset while the tag
// is still being created, and immutable once set: a tag never moves between
// snapshots. A tag is usable only once this is set.
@@ -646,11 +645,10 @@ message ActorSnapshotTagStatus {
// seeded from a tag under the same template.
string actor_template_uid = 2;
// in_progress_snapshot_uri is where CreateActorSnapshotTag is copying the
// tag's own snapshot to. It is minted before a single object is written and
// cleared once snapshot is set, so a tag left with it set names exactly the
// objects its unfinished create stranded, and deleting the tag collects
// them.
// in_progress_snapshot_uri is where CreateTag is copying the tag's own
// snapshot to. It is minted before a single object is written and cleared
// once snapshot is set, so a tag left with it set names exactly the objects
// its unfinished create stranded, and deleting the tag collects them.
string in_progress_snapshot_uri = 3;
// source_actor_uid is the UID of the Actor this tag's snapshot was copied
@@ -658,12 +656,11 @@ message ActorSnapshotTagStatus {
string source_actor_uid = 4;
}
// ActorSnapshotTag is an Atespace-owned, stable name for one external
// snapshot. The external snapshot pointed by a tag is a copy of an actor snapshot.
// The snapshot is copied when the tag is
// created, and deleted when the tag is. Its owning Atespace cannot be deleted
// until the tag is removed.
message ActorSnapshotTag {
// Tag is an Atespace-owned, stable name for one external snapshot. The
// external snapshot pointed by a tag is a copy of an actor snapshot. The
// snapshot is copied when the tag is created, and deleted when the tag is. Its
// owning Atespace cannot be deleted until the tag is removed.
message Tag {
// Common resource metadata: atespace, name, uid, version, timestamps.
//
// +k8s:required
@@ -673,14 +670,14 @@ message ActorSnapshotTag {
// status is set by the control plane; it is ignored on write.
//
// +k8s:optional
ActorSnapshotTagStatus status = 2;
TagStatus status = 2;
// scope controls who may create an Actor from this tag.
//
// +k8s:required
// +k8s:minimum=1
// +k8s:maximum=2 # keep this in sync with the ActorSnapshotTagScope enum
ActorSnapshotTagScope scope = 3;
// +k8s:maximum=2 # keep this in sync with the TagScope enum
TagScope scope = 3;
// source_actor is the suspended Actor whose external snapshot this tag
// captures. The tag lives in this Actor's Atespace. Set once at creation and
@@ -1435,13 +1432,13 @@ message DeleteActorEgressPolicyRequest {
ObjectRef actor = 1;
}
message GetActorSnapshotTagRequest {
message GetTagRequest {
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ObjectRef actor_snapshot_tag = 1;
ObjectRef tag = 1;
}
message ListActorSnapshotTagsRequest {
message ListTagsRequest {
// The atespace to list tags from. Empty lists across all atespaces.
//
// +k8s:optional
@@ -1456,7 +1453,7 @@ message ListActorSnapshotTagsRequest {
// +k8s:minimum=1
int32 page_size = 2;
// Pagination token from a previous ListActorSnapshotTags response.
// Pagination token from a previous ListTags response.
// Omit or leave empty for the first request.
//
// +k8s:optional
@@ -1464,8 +1461,8 @@ message ListActorSnapshotTagsRequest {
string page_token = 3;
}
message ListActorSnapshotTagsResponse {
repeated ActorSnapshotTag actor_snapshot_tags = 1;
message ListTagsResponse {
repeated Tag tags = 1;
string next_page_token = 2;
}
@@ -1483,35 +1480,34 @@ message ListActorSnapshotTagsResponse {
// which collects whatever that attempt stranded, and create it again.
//
// +k8s:customValidation # metadata.atespace must match source_actor.atespace
message CreateActorSnapshotTagRequest {
message CreateTagRequest {
// The tag to create. metadata.atespace, metadata.name, scope and source_actor
// are honored; metadata.atespace must match source_actor's, and status is
// ignored on write.
//
// +k8s:required
ActorSnapshotTag actor_snapshot_tag = 1;
Tag tag = 1;
}
// Request to update mutable fields on an existing ActorSnapshotTag.
// Request to update mutable fields on an existing Tag.
// The tag keeps its address: the snapshot it points at cannot be changed.
message UpdateActorSnapshotTagRequest {
message UpdateTagRequest {
// The tag to update.
// actor_snapshot_tag.metadata.atespace and actor_snapshot_tag.metadata.name
// identify which resource to update.
// actor_snapshot_tag.metadata.version and actor_snapshot_tag.metadata.uid
// are required preconditions
// tag.metadata.atespace and tag.metadata.name identify which resource to
// update.
// tag.metadata.version and tag.metadata.uid are required preconditions
//
// +k8s:required
// +k8s:opaqueType # updates are handled in 2 steps, do not descend
// +k8s:subfield(metadata)=+k8s:required
// +k8s:customValidation # TODO: when we get nested subfields, require metadata.atespace
ActorSnapshotTag actor_snapshot_tag = 1;
Tag tag = 1;
}
message DeleteActorSnapshotTagRequest {
message DeleteTagRequest {
// +k8s:required
// +k8s:subfield(atespace)=+k8s:required
ObjectRef actor_snapshot_tag = 1;
ObjectRef tag = 1;
}
// DeleteOptions carries per-delete controls. Today it holds optional
+89 -91
View File
@@ -44,11 +44,11 @@ const (
Control_CreateActorEgressPolicy_FullMethodName = "/ateapi.Control/CreateActorEgressPolicy"
Control_UpdateActorEgressPolicy_FullMethodName = "/ateapi.Control/UpdateActorEgressPolicy"
Control_DeleteActorEgressPolicy_FullMethodName = "/ateapi.Control/DeleteActorEgressPolicy"
Control_CreateActorSnapshotTag_FullMethodName = "/ateapi.Control/CreateActorSnapshotTag"
Control_GetActorSnapshotTag_FullMethodName = "/ateapi.Control/GetActorSnapshotTag"
Control_ListActorSnapshotTags_FullMethodName = "/ateapi.Control/ListActorSnapshotTags"
Control_UpdateActorSnapshotTag_FullMethodName = "/ateapi.Control/UpdateActorSnapshotTag"
Control_DeleteActorSnapshotTag_FullMethodName = "/ateapi.Control/DeleteActorSnapshotTag"
Control_CreateTag_FullMethodName = "/ateapi.Control/CreateTag"
Control_GetTag_FullMethodName = "/ateapi.Control/GetTag"
Control_ListTags_FullMethodName = "/ateapi.Control/ListTags"
Control_UpdateTag_FullMethodName = "/ateapi.Control/UpdateTag"
Control_DeleteTag_FullMethodName = "/ateapi.Control/DeleteTag"
Control_ListWorkers_FullMethodName = "/ateapi.Control/ListWorkers"
Control_GetWorker_FullMethodName = "/ateapi.Control/GetWorker"
Control_CreateWorker_FullMethodName = "/ateapi.Control/CreateWorker"
@@ -100,18 +100,17 @@ type ControlClient interface {
// Tag the external snapshot a suspended Actor holds. The tag gets its own
// copy of that snapshot, so suspending or deleting the Actor afterwards
// cannot collect it.
CreateActorSnapshotTag(ctx context.Context, in *CreateActorSnapshotTagRequest, opts ...grpc.CallOption) (*ActorSnapshotTag, error)
// Get an ActorSnapshot tag.
GetActorSnapshotTag(ctx context.Context, in *GetActorSnapshotTagRequest, opts ...grpc.CallOption) (*ActorSnapshotTag, error)
// List ActorSnapshot tags.
ListActorSnapshotTags(ctx context.Context, in *ListActorSnapshotTagsRequest, opts ...grpc.CallOption) (*ListActorSnapshotTagsResponse, error)
// Publish or unpublish an ActorSnapshot tag without changing its address.
UpdateActorSnapshotTag(ctx context.Context, in *UpdateActorSnapshotTagRequest, opts ...grpc.CallOption) (*ActorSnapshotTag, error)
// Delete an ActorSnapshot tag and the external snapshot it owns. Actors
// created from the tag that have not yet been suspended still point at that
// external snapshot and become unrecoverable, so do not delete a tag while
// such Actors exist.
DeleteActorSnapshotTag(ctx context.Context, in *DeleteActorSnapshotTagRequest, opts ...grpc.CallOption) (*ActorSnapshotTag, error)
CreateTag(ctx context.Context, in *CreateTagRequest, opts ...grpc.CallOption) (*Tag, error)
// Get a Tag.
GetTag(ctx context.Context, in *GetTagRequest, opts ...grpc.CallOption) (*Tag, error)
// List Tags.
ListTags(ctx context.Context, in *ListTagsRequest, opts ...grpc.CallOption) (*ListTagsResponse, error)
// Publish or unpublish a Tag without changing its address.
UpdateTag(ctx context.Context, in *UpdateTagRequest, opts ...grpc.CallOption) (*Tag, error)
// Delete a Tag and the external snapshot it owns. Actors created from the
// tag that have not yet been suspended still point at that external snapshot
// and become unrecoverable, so do not delete a tag while such Actors exist.
DeleteTag(ctx context.Context, in *DeleteTagRequest, opts ...grpc.CallOption) (*Tag, error)
// List Workers.
ListWorkers(ctx context.Context, in *ListWorkersRequest, opts ...grpc.CallOption) (*ListWorkersResponse, error)
// Get a Worker.
@@ -138,7 +137,7 @@ type ControlClient interface {
// List Atespaces.
ListAtespaces(ctx context.Context, in *ListAtespacesRequest, opts ...grpc.CallOption) (*ListAtespacesResponse, error)
// Delete an empty Atespace. Rejects (FailedPrecondition) if any Actors or
// ActorSnapshotTags remain.
// Tags remain.
DeleteAtespace(ctx context.Context, in *DeleteAtespaceRequest, opts ...grpc.CallOption) (*Atespace, error)
CreateActorTemplate(ctx context.Context, in *CreateActorTemplateRequest, opts ...grpc.CallOption) (*ActorTemplate, error)
GetActorTemplate(ctx context.Context, in *GetActorTemplateRequest, opts ...grpc.CallOption) (*ActorTemplate, error)
@@ -266,50 +265,50 @@ func (c *controlClient) DeleteActorEgressPolicy(ctx context.Context, in *DeleteA
return out, nil
}
func (c *controlClient) CreateActorSnapshotTag(ctx context.Context, in *CreateActorSnapshotTagRequest, opts ...grpc.CallOption) (*ActorSnapshotTag, error) {
func (c *controlClient) CreateTag(ctx context.Context, in *CreateTagRequest, opts ...grpc.CallOption) (*Tag, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ActorSnapshotTag)
err := c.cc.Invoke(ctx, Control_CreateActorSnapshotTag_FullMethodName, in, out, cOpts...)
out := new(Tag)
err := c.cc.Invoke(ctx, Control_CreateTag_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *controlClient) GetActorSnapshotTag(ctx context.Context, in *GetActorSnapshotTagRequest, opts ...grpc.CallOption) (*ActorSnapshotTag, error) {
func (c *controlClient) GetTag(ctx context.Context, in *GetTagRequest, opts ...grpc.CallOption) (*Tag, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ActorSnapshotTag)
err := c.cc.Invoke(ctx, Control_GetActorSnapshotTag_FullMethodName, in, out, cOpts...)
out := new(Tag)
err := c.cc.Invoke(ctx, Control_GetTag_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *controlClient) ListActorSnapshotTags(ctx context.Context, in *ListActorSnapshotTagsRequest, opts ...grpc.CallOption) (*ListActorSnapshotTagsResponse, error) {
func (c *controlClient) ListTags(ctx context.Context, in *ListTagsRequest, opts ...grpc.CallOption) (*ListTagsResponse, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ListActorSnapshotTagsResponse)
err := c.cc.Invoke(ctx, Control_ListActorSnapshotTags_FullMethodName, in, out, cOpts...)
out := new(ListTagsResponse)
err := c.cc.Invoke(ctx, Control_ListTags_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *controlClient) UpdateActorSnapshotTag(ctx context.Context, in *UpdateActorSnapshotTagRequest, opts ...grpc.CallOption) (*ActorSnapshotTag, error) {
func (c *controlClient) UpdateTag(ctx context.Context, in *UpdateTagRequest, opts ...grpc.CallOption) (*Tag, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ActorSnapshotTag)
err := c.cc.Invoke(ctx, Control_UpdateActorSnapshotTag_FullMethodName, in, out, cOpts...)
out := new(Tag)
err := c.cc.Invoke(ctx, Control_UpdateTag_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *controlClient) DeleteActorSnapshotTag(ctx context.Context, in *DeleteActorSnapshotTagRequest, opts ...grpc.CallOption) (*ActorSnapshotTag, error) {
func (c *controlClient) DeleteTag(ctx context.Context, in *DeleteTagRequest, opts ...grpc.CallOption) (*Tag, error) {
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
out := new(ActorSnapshotTag)
err := c.cc.Invoke(ctx, Control_DeleteActorSnapshotTag_FullMethodName, in, out, cOpts...)
out := new(Tag)
err := c.cc.Invoke(ctx, Control_DeleteTag_FullMethodName, in, out, cOpts...)
if err != nil {
return nil, err
}
@@ -509,18 +508,17 @@ type ControlServer interface {
// Tag the external snapshot a suspended Actor holds. The tag gets its own
// copy of that snapshot, so suspending or deleting the Actor afterwards
// cannot collect it.
CreateActorSnapshotTag(context.Context, *CreateActorSnapshotTagRequest) (*ActorSnapshotTag, error)
// Get an ActorSnapshot tag.
GetActorSnapshotTag(context.Context, *GetActorSnapshotTagRequest) (*ActorSnapshotTag, error)
// List ActorSnapshot tags.
ListActorSnapshotTags(context.Context, *ListActorSnapshotTagsRequest) (*ListActorSnapshotTagsResponse, error)
// Publish or unpublish an ActorSnapshot tag without changing its address.
UpdateActorSnapshotTag(context.Context, *UpdateActorSnapshotTagRequest) (*ActorSnapshotTag, error)
// Delete an ActorSnapshot tag and the external snapshot it owns. Actors
// created from the tag that have not yet been suspended still point at that
// external snapshot and become unrecoverable, so do not delete a tag while
// such Actors exist.
DeleteActorSnapshotTag(context.Context, *DeleteActorSnapshotTagRequest) (*ActorSnapshotTag, error)
CreateTag(context.Context, *CreateTagRequest) (*Tag, error)
// Get a Tag.
GetTag(context.Context, *GetTagRequest) (*Tag, error)
// List Tags.
ListTags(context.Context, *ListTagsRequest) (*ListTagsResponse, error)
// Publish or unpublish a Tag without changing its address.
UpdateTag(context.Context, *UpdateTagRequest) (*Tag, error)
// Delete a Tag and the external snapshot it owns. Actors created from the
// tag that have not yet been suspended still point at that external snapshot
// and become unrecoverable, so do not delete a tag while such Actors exist.
DeleteTag(context.Context, *DeleteTagRequest) (*Tag, error)
// List Workers.
ListWorkers(context.Context, *ListWorkersRequest) (*ListWorkersResponse, error)
// Get a Worker.
@@ -547,7 +545,7 @@ type ControlServer interface {
// List Atespaces.
ListAtespaces(context.Context, *ListAtespacesRequest) (*ListAtespacesResponse, error)
// Delete an empty Atespace. Rejects (FailedPrecondition) if any Actors or
// ActorSnapshotTags remain.
// Tags remain.
DeleteAtespace(context.Context, *DeleteAtespaceRequest) (*Atespace, error)
CreateActorTemplate(context.Context, *CreateActorTemplateRequest) (*ActorTemplate, error)
GetActorTemplate(context.Context, *GetActorTemplateRequest) (*ActorTemplate, error)
@@ -598,20 +596,20 @@ func (UnimplementedControlServer) UpdateActorEgressPolicy(context.Context, *Upda
func (UnimplementedControlServer) DeleteActorEgressPolicy(context.Context, *DeleteActorEgressPolicyRequest) (*EgressPolicy, error) {
return nil, status.Error(codes.Unimplemented, "method DeleteActorEgressPolicy not implemented")
}
func (UnimplementedControlServer) CreateActorSnapshotTag(context.Context, *CreateActorSnapshotTagRequest) (*ActorSnapshotTag, error) {
return nil, status.Error(codes.Unimplemented, "method CreateActorSnapshotTag not implemented")
func (UnimplementedControlServer) CreateTag(context.Context, *CreateTagRequest) (*Tag, error) {
return nil, status.Error(codes.Unimplemented, "method CreateTag not implemented")
}
func (UnimplementedControlServer) GetActorSnapshotTag(context.Context, *GetActorSnapshotTagRequest) (*ActorSnapshotTag, error) {
return nil, status.Error(codes.Unimplemented, "method GetActorSnapshotTag not implemented")
func (UnimplementedControlServer) GetTag(context.Context, *GetTagRequest) (*Tag, error) {
return nil, status.Error(codes.Unimplemented, "method GetTag not implemented")
}
func (UnimplementedControlServer) ListActorSnapshotTags(context.Context, *ListActorSnapshotTagsRequest) (*ListActorSnapshotTagsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListActorSnapshotTags not implemented")
func (UnimplementedControlServer) ListTags(context.Context, *ListTagsRequest) (*ListTagsResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListTags not implemented")
}
func (UnimplementedControlServer) UpdateActorSnapshotTag(context.Context, *UpdateActorSnapshotTagRequest) (*ActorSnapshotTag, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateActorSnapshotTag not implemented")
func (UnimplementedControlServer) UpdateTag(context.Context, *UpdateTagRequest) (*Tag, error) {
return nil, status.Error(codes.Unimplemented, "method UpdateTag not implemented")
}
func (UnimplementedControlServer) DeleteActorSnapshotTag(context.Context, *DeleteActorSnapshotTagRequest) (*ActorSnapshotTag, error) {
return nil, status.Error(codes.Unimplemented, "method DeleteActorSnapshotTag not implemented")
func (UnimplementedControlServer) DeleteTag(context.Context, *DeleteTagRequest) (*Tag, error) {
return nil, status.Error(codes.Unimplemented, "method DeleteTag not implemented")
}
func (UnimplementedControlServer) ListWorkers(context.Context, *ListWorkersRequest) (*ListWorkersResponse, error) {
return nil, status.Error(codes.Unimplemented, "method ListWorkers not implemented")
@@ -880,92 +878,92 @@ func _Control_DeleteActorEgressPolicy_Handler(srv interface{}, ctx context.Conte
return interceptor(ctx, in, info, handler)
}
func _Control_CreateActorSnapshotTag_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateActorSnapshotTagRequest)
func _Control_CreateTag_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(CreateTagRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ControlServer).CreateActorSnapshotTag(ctx, in)
return srv.(ControlServer).CreateTag(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Control_CreateActorSnapshotTag_FullMethodName,
FullMethod: Control_CreateTag_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ControlServer).CreateActorSnapshotTag(ctx, req.(*CreateActorSnapshotTagRequest))
return srv.(ControlServer).CreateTag(ctx, req.(*CreateTagRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Control_GetActorSnapshotTag_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetActorSnapshotTagRequest)
func _Control_GetTag_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(GetTagRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ControlServer).GetActorSnapshotTag(ctx, in)
return srv.(ControlServer).GetTag(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Control_GetActorSnapshotTag_FullMethodName,
FullMethod: Control_GetTag_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ControlServer).GetActorSnapshotTag(ctx, req.(*GetActorSnapshotTagRequest))
return srv.(ControlServer).GetTag(ctx, req.(*GetTagRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Control_ListActorSnapshotTags_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListActorSnapshotTagsRequest)
func _Control_ListTags_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(ListTagsRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ControlServer).ListActorSnapshotTags(ctx, in)
return srv.(ControlServer).ListTags(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Control_ListActorSnapshotTags_FullMethodName,
FullMethod: Control_ListTags_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ControlServer).ListActorSnapshotTags(ctx, req.(*ListActorSnapshotTagsRequest))
return srv.(ControlServer).ListTags(ctx, req.(*ListTagsRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Control_UpdateActorSnapshotTag_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateActorSnapshotTagRequest)
func _Control_UpdateTag_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(UpdateTagRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ControlServer).UpdateActorSnapshotTag(ctx, in)
return srv.(ControlServer).UpdateTag(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Control_UpdateActorSnapshotTag_FullMethodName,
FullMethod: Control_UpdateTag_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ControlServer).UpdateActorSnapshotTag(ctx, req.(*UpdateActorSnapshotTagRequest))
return srv.(ControlServer).UpdateTag(ctx, req.(*UpdateTagRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Control_DeleteActorSnapshotTag_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteActorSnapshotTagRequest)
func _Control_DeleteTag_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(DeleteTagRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(ControlServer).DeleteActorSnapshotTag(ctx, in)
return srv.(ControlServer).DeleteTag(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: Control_DeleteActorSnapshotTag_FullMethodName,
FullMethod: Control_DeleteTag_FullMethodName,
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(ControlServer).DeleteActorSnapshotTag(ctx, req.(*DeleteActorSnapshotTagRequest))
return srv.(ControlServer).DeleteTag(ctx, req.(*DeleteTagRequest))
}
return interceptor(ctx, in, info, handler)
}
@@ -1310,24 +1308,24 @@ var Control_ServiceDesc = grpc.ServiceDesc{
Handler: _Control_DeleteActorEgressPolicy_Handler,
},
{
MethodName: "CreateActorSnapshotTag",
Handler: _Control_CreateActorSnapshotTag_Handler,
MethodName: "CreateTag",
Handler: _Control_CreateTag_Handler,
},
{
MethodName: "GetActorSnapshotTag",
Handler: _Control_GetActorSnapshotTag_Handler,
MethodName: "GetTag",
Handler: _Control_GetTag_Handler,
},
{
MethodName: "ListActorSnapshotTags",
Handler: _Control_ListActorSnapshotTags_Handler,
MethodName: "ListTags",
Handler: _Control_ListTags_Handler,
},
{
MethodName: "UpdateActorSnapshotTag",
Handler: _Control_UpdateActorSnapshotTag_Handler,
MethodName: "UpdateTag",
Handler: _Control_UpdateTag_Handler,
},
{
MethodName: "DeleteActorSnapshotTag",
Handler: _Control_DeleteActorSnapshotTag_Handler,
MethodName: "DeleteTag",
Handler: _Control_DeleteTag_Handler,
},
{
MethodName: "ListWorkers",
+9 -9
View File
@@ -26,7 +26,7 @@
},
{
"rule": "documented",
"subject": "ateapi.ActorSnapshotTagScope",
"subject": "ateapi.TagScope",
"message": "enum has no doc comment"
},
{
@@ -121,12 +121,12 @@
},
{
"rule": "documented",
"subject": "ateapi.DeleteActorSnapshotTagRequest",
"subject": "ateapi.DeleteTagRequest",
"message": "message has no doc comment"
},
{
"rule": "documented",
"subject": "ateapi.DeleteActorSnapshotTagRequest.actor_snapshot_tag",
"subject": "ateapi.DeleteTagRequest.tag",
"message": "field has no doc comment"
},
{
@@ -186,12 +186,12 @@
},
{
"rule": "documented",
"subject": "ateapi.GetActorSnapshotTagRequest",
"subject": "ateapi.GetTagRequest",
"message": "message has no doc comment"
},
{
"rule": "documented",
"subject": "ateapi.GetActorSnapshotTagRequest.actor_snapshot_tag",
"subject": "ateapi.GetTagRequest.tag",
"message": "field has no doc comment"
},
{
@@ -246,22 +246,22 @@
},
{
"rule": "documented",
"subject": "ateapi.ListActorSnapshotTagsRequest",
"subject": "ateapi.ListTagsRequest",
"message": "message has no doc comment"
},
{
"rule": "documented",
"subject": "ateapi.ListActorSnapshotTagsResponse",
"subject": "ateapi.ListTagsResponse",
"message": "message has no doc comment"
},
{
"rule": "documented",
"subject": "ateapi.ListActorSnapshotTagsResponse.actor_snapshot_tags",
"subject": "ateapi.ListTagsResponse.tags",
"message": "field has no doc comment"
},
{
"rule": "documented",
"subject": "ateapi.ListActorSnapshotTagsResponse.next_page_token",
"subject": "ateapi.ListTagsResponse.next_page_token",
"message": "field has no doc comment"
},
{
+1 -1
View File
@@ -554,7 +554,7 @@ func Resources(api *API) ([]Resource, error) {
var resourceNames = []string{
"Actor",
"ActorSnapshot",
"ActorSnapshotTag",
"Tag",
"ActorTemplate",
"Atespace",
"Worker",