diff --git a/pkg/sdkserver/localsdk.go b/pkg/sdkserver/localsdk.go index 0289f8de40..26bdd26bbc 100644 --- a/pkg/sdkserver/localsdk.go +++ b/pkg/sdkserver/localsdk.go @@ -588,7 +588,7 @@ func (l *LocalSDKServer) GetPlayerCapacity(_ context.Context, _ *alpha.Empty) (* return result, nil } -// GetCounter returns a Counter. Returns NOT_FOUND if the counter does not exist. +// GetCounter returns a Counter. Returns not found if the counter does not exist. // [Stage:Alpha] // [FeatureFlag:CountsAndLists] func (l *LocalSDKServer) GetCounter(ctx context.Context, in *alpha.GetCounterRequest) (*alpha.Counter, error) { @@ -596,6 +596,10 @@ func (l *LocalSDKServer) GetCounter(ctx context.Context, in *alpha.GetCounterReq return nil, errors.Errorf("%s not enabled", runtime.FeatureCountsAndLists) } + if in == nil { + return nil, errors.Errorf("invalid argument. GetCounterRequest cannot be nil") + } + l.logger.WithField("name", in.Name).Info("Getting Counter") l.recordRequest("getcounter") l.gsMutex.RLock() @@ -604,18 +608,69 @@ func (l *LocalSDKServer) GetCounter(ctx context.Context, in *alpha.GetCounterReq if counter, ok := l.gs.Status.Counters[in.Name]; ok { return &alpha.Counter{Name: in.Name, Count: counter.Count, Capacity: counter.Capacity}, nil } - return nil, errors.Errorf("NOT_FOUND. %s Counter not found", in.Name) + return nil, errors.Errorf("not found. %s Counter not found", in.Name) } -// UpdateCounter returns the updated Counter. +// UpdateCounter updates the given Counter. Unlike the SDKServer, this LocalSDKServer UpdateCounter +// does not batch requests, and directly updates the localsdk gameserver. +// Returns error if the Counter does not exist (name cannot be updated). +// Returns error if the Count is out of range [0,Capacity]. // [Stage:Alpha] // [FeatureFlag:CountsAndLists] func (l *LocalSDKServer) UpdateCounter(ctx context.Context, in *alpha.UpdateCounterRequest) (*alpha.Counter, error) { - // TODO(#2716): Implement me - return nil, errors.Errorf("Unimplemented -- UpdateCounter coming soon") + if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { + return nil, errors.Errorf("%s not enabled", runtime.FeatureCountsAndLists) + } + + if in.CounterUpdateRequest == nil { + return nil, errors.Errorf("invalid argument. CounterUpdateRequest cannot be nil") + } + + name := in.CounterUpdateRequest.Name + + l.logger.WithField("name", name).Info("Updating Counter") + l.recordRequest("updateCounter") + l.gsMutex.Lock() + defer l.gsMutex.Unlock() + + counter, ok := l.gs.Status.Counters[name] + if !ok { + return nil, errors.Errorf("not found. %s Counter not found", name) + } + + tmpCounter := alpha.Counter{Name: name, Count: counter.Count, Capacity: counter.Capacity} + // Set Capacity + if in.CounterUpdateRequest.Capacity != nil { + if in.CounterUpdateRequest.Capacity.GetValue() < 0 { + return nil, errors.Errorf("out of range. Capacity must be greater than or equal to 0. Found Capacity: %d", + in.CounterUpdateRequest.Capacity.GetValue()) + } + tmpCounter.Capacity = in.CounterUpdateRequest.Capacity.GetValue() + } + // Set Count + if in.CounterUpdateRequest.Count != nil { + if in.CounterUpdateRequest.Count.GetValue() < 0 || in.CounterUpdateRequest.Count.GetValue() > tmpCounter.Capacity { + return nil, errors.Errorf("out of range. Count must be within range [0,Capacity]. Found Count: %d, Capacity: %d", + in.CounterUpdateRequest.Count.GetValue(), tmpCounter.Capacity) + } + } + // Increment or Decrement Count + if in.CounterUpdateRequest.CountDiff != 0 { + tmpCounter.Count += in.CounterUpdateRequest.CountDiff + if tmpCounter.Count < 0 || tmpCounter.Count > tmpCounter.Capacity { + return nil, errors.Errorf("out of range. Count must be within range [0,Capacity]. Found Count: %d, Capacity: %d", + tmpCounter.Count, tmpCounter.Capacity) + } + } + + // Write newly updated List to gameserverstatus. + counter.Capacity = tmpCounter.Capacity + counter.Count = tmpCounter.Count + l.gs.Status.Counters[name] = counter + return &tmpCounter, nil } -// GetList returns a List. Returns NOT_FOUND if the List does not exist. +// GetList returns a List. Returns not found if the List does not exist. // [Stage:Alpha] // [FeatureFlag:CountsAndLists] func (l *LocalSDKServer) GetList(ctx context.Context, in *alpha.GetListRequest) (*alpha.List, error) { @@ -631,13 +686,13 @@ func (l *LocalSDKServer) GetList(ctx context.Context, in *alpha.GetListRequest) if list, ok := l.gs.Status.Lists[in.Name]; ok { return &alpha.List{Name: in.Name, Capacity: list.Capacity, Values: list.Values}, nil } - return nil, errors.Errorf("NOT_FOUND. %s List not found", in.Name) + return nil, errors.Errorf("not found. %s List not found", in.Name) } -// UpdateList returns the updated List. Returns NOT_FOUND if the List does not exist (name cannot be updated). +// UpdateList returns the updated List. Returns not found if the List does not exist (name cannot be updated). // **THIS WILL OVERWRITE ALL EXISTING LIST.VALUES WITH ANY REQUEST LIST.VALUES** // Use AddListValue() or RemoveListValue() for modifying the List.Values field. -// Returns INVALID_ARGUMENT if the field mask path(s) are not field(s) of the List. +// Returns invalid argument if the field mask path(s) are not field(s) of the List. // If a field mask path(s) is specified, but the value is not set in the request List object, // then the default value for the variable will be set (i.e. 0 for "capacity", empty list for "values"). // [Stage:Alpha] @@ -648,7 +703,7 @@ func (l *LocalSDKServer) UpdateList(ctx context.Context, in *alpha.UpdateListReq } if in.List == nil || in.UpdateMask == nil { - return nil, errors.Errorf("INVALID_ARGUMENT. List: %v and UpdateMask %v cannot be nil", in.List, in.UpdateMask) + return nil, errors.Errorf("invalid argument. List: %v and UpdateMask %v cannot be nil", in.List, in.UpdateMask) } l.logger.WithField("name", in.List.Name).Info("Updating List") @@ -657,13 +712,13 @@ func (l *LocalSDKServer) UpdateList(ctx context.Context, in *alpha.UpdateListReq defer l.gsMutex.Unlock() // TODO: https://google.aip.dev/134, "Update masks must support a special value *, meaning full replacement." - // Check if the UpdateMask paths are valid, return INVALID_ARGUMENT if not. + // Check if the UpdateMask paths are valid, return invalid argument if not. if !in.UpdateMask.IsValid(in.List.ProtoReflect().Interface()) { - return nil, errors.Errorf("INVALID_ARGUMENT. Field Mask Path(s): %v are invalid for List. Use valid field name(s): %v", in.UpdateMask.GetPaths(), in.List.ProtoReflect().Descriptor().Fields()) + return nil, errors.Errorf("invalid argument. Field Mask Path(s): %v are invalid for List. Use valid field name(s): %v", in.UpdateMask.GetPaths(), in.List.ProtoReflect().Descriptor().Fields()) } if in.List.Capacity < 0 || in.List.Capacity > apiserver.ListMaxCapacity { - return nil, errors.Errorf("OUT_OF_RANGE. Capacity must be within range [0,1000]. Found Capacity: %d", in.List.Capacity) + return nil, errors.Errorf("out of range. Capacity must be within range [0,1000]. Found Capacity: %d", in.List.Capacity) } name := in.List.Name @@ -678,20 +733,20 @@ func (l *LocalSDKServer) UpdateList(ctx context.Context, in *alpha.UpdateListReq proto.Merge(tmpList, in.List) // Verify that Capacity >= len(tmpList.values) if tmpList.Capacity < int64(len(tmpList.Values)) { - return nil, errors.Errorf("OUT_OF_RANGE. Capacity must be great than or equal to the size of the List of values. Found Capacity: %d, List Size: %d", tmpList.Capacity, len(tmpList.Values)) + return nil, errors.Errorf("out of range. Capacity must be great than or equal to the size of the List of values. Found Capacity: %d, List Size: %d", tmpList.Capacity, len(tmpList.Values)) } // Write newly updated List to gameserverstatus. l.gs.Status.Lists[name].Capacity = tmpList.Capacity l.gs.Status.Lists[name].Values = tmpList.Values return &alpha.List{Name: name, Capacity: l.gs.Status.Lists[name].Capacity, Values: l.gs.Status.Lists[name].Values}, nil } - return nil, errors.Errorf("NOT_FOUND. %s List not found", name) + return nil, errors.Errorf("not found. %s List not found", name) } // AddListValue appends a value to the end of a List and returns updated List. -// Returns NOT_FOUND if the List does not exist. -// Returns ALREADY_EXISTS if the value is already in the List. -// Returns OUT_OF_RANGE if the List is already at Capacity. +// Returns not found if the List does not exist. +// Returns already exists if the value is already in the List. +// Returns out of range if the List is already at Capacity. // [Stage:Alpha] // [FeatureFlag:CountsAndLists] func (l *LocalSDKServer) AddListValue(ctx context.Context, in *alpha.AddListValueRequest) (*alpha.List, error) { @@ -707,24 +762,24 @@ func (l *LocalSDKServer) AddListValue(ctx context.Context, in *alpha.AddListValu if list, ok := l.gs.Status.Lists[in.Name]; ok { // Verify room to add another value if list.Capacity <= int64(len(list.Values)) { - return nil, errors.Errorf("OUT_OF_RANGE. No available capacity. Current Capacity: %d, List Size: %d", list.Capacity, len(list.Values)) + return nil, errors.Errorf("out of range. No available capacity. Current Capacity: %d, List Size: %d", list.Capacity, len(list.Values)) } // Verify value does not already exist in the list for _, val := range l.gs.Status.Lists[in.Name].Values { if in.Value == val { - return nil, errors.Errorf("ALREADY_EXISTS. Value: %s already in List: %s", in.Value, in.Name) + return nil, errors.Errorf("already exists. Value: %s already in List: %s", in.Value, in.Name) } } // Add new value to gameserverstatus. l.gs.Status.Lists[in.Name].Values = append(l.gs.Status.Lists[in.Name].Values, in.Value) return &alpha.List{Name: in.Name, Capacity: l.gs.Status.Lists[in.Name].Capacity, Values: l.gs.Status.Lists[in.Name].Values}, nil } - return nil, errors.Errorf("NOT_FOUND. %s List not found", in.Name) + return nil, errors.Errorf("not found. %s List not found", in.Name) } // RemoveListValue removes a value from a List and returns updated List. -// Returns NOT_FOUND if the List does not exist. -// Returns NOT_FOUND if the value is not in the List. +// Returns not found if the List does not exist. +// Returns not found if the value is not in the List. // [Stage:Alpha] // [FeatureFlag:CountsAndLists] func (l *LocalSDKServer) RemoveListValue(ctx context.Context, in *alpha.RemoveListValueRequest) (*alpha.List, error) { @@ -746,9 +801,9 @@ func (l *LocalSDKServer) RemoveListValue(ctx context.Context, in *alpha.RemoveLi return &alpha.List{Name: in.Name, Capacity: l.gs.Status.Lists[in.Name].Capacity, Values: l.gs.Status.Lists[in.Name].Values}, nil } } - return nil, errors.Errorf("NOT_FOUND. Value: %s not found in List: %s", in.Value, in.Name) + return nil, errors.Errorf("not found. Value: %s not found in List: %s", in.Value, in.Name) } - return nil, errors.Errorf("NOT_FOUND. %s List not found", in.Name) + return nil, errors.Errorf("not found. %s List not found", in.Name) } // Close tears down all the things diff --git a/pkg/sdkserver/localsdk_test.go b/pkg/sdkserver/localsdk_test.go index 1575c4cb99..3b1f562203 100644 --- a/pkg/sdkserver/localsdk_test.go +++ b/pkg/sdkserver/localsdk_test.go @@ -33,6 +33,7 @@ import ( "github.com/stretchr/testify/assert" "google.golang.org/protobuf/testing/protocmp" "google.golang.org/protobuf/types/known/fieldmaskpb" + "google.golang.org/protobuf/types/known/wrapperspb" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" ) @@ -662,7 +663,7 @@ func TestLocalSDKServerGetCounter(t *testing.T) { }, "Counter does not exist": { name: "noName", - wantErr: errors.Errorf("NOT_FOUND. %s Counter not found", "noName"), + wantErr: errors.Errorf("not found. %s Counter not found", "noName"), }, } @@ -683,6 +684,158 @@ func TestLocalSDKServerGetCounter(t *testing.T) { } } +func TestLocalSDKServerUpdateCounter(t *testing.T) { + t.Parallel() + + runtime.FeatureTestMutex.Lock() + defer runtime.FeatureTestMutex.Unlock() + assert.NoError(t, runtime.ParseFeatures(string(runtime.FeatureCountsAndLists)+"=true")) + + counters := map[string]agonesv1.CounterStatus{ + "sessions": {Count: 1, Capacity: 100}, + "players": {Count: 100, Capacity: 100}, + "lobbies": {Count: 0, Capacity: 0}, + "games": {Count: 5, Capacity: 10}, + "npcs": {Count: 0, Capacity: 10}, + } + fixture := &agonesv1.GameServer{ + ObjectMeta: metav1.ObjectMeta{Name: "stuff"}, + Status: agonesv1.GameServerStatus{ + Counters: counters, + }, + } + + path, err := gsToTmpFile(fixture) + assert.NoError(t, err) + l, err := NewLocalSDKServer(path, "") + assert.Nil(t, err) + + stream := newGameServerMockStream() + go func() { + err := l.WatchGameServer(&sdk.Empty{}, stream) + assert.Nil(t, err) + }() + assertInitialWatchUpdate(t, stream) + + // wait for watching to begin + err = wait.PollUntilContextTimeout(context.Background(), time.Second, 10*time.Second, true, func(ctx context.Context) (bool, error) { + found := false + l.updateObservers.Range(func(_, _ interface{}) bool { + found = true + return false + }) + return found, nil + }) + assert.NoError(t, err) + + testScenarios := map[string]struct { + updateRequest *alpha.UpdateCounterRequest + want *alpha.Counter + wantErr error + }{ + "Set Counter Capacity": { + updateRequest: &alpha.UpdateCounterRequest{ + CounterUpdateRequest: &alpha.CounterUpdateRequest{ + Name: "lobbies", + Capacity: wrapperspb.Int64(10), + }}, + want: &alpha.Counter{ + Name: "lobbies", Count: 0, Capacity: 10, + }, + }, + "Set Counter Count": { + updateRequest: &alpha.UpdateCounterRequest{ + CounterUpdateRequest: &alpha.CounterUpdateRequest{ + Name: "npcs", + CountDiff: 10, + }}, + want: &alpha.Counter{ + Name: "npcs", Count: 10, Capacity: 10, + }, + }, + "Decrement Counter Count": { + updateRequest: &alpha.UpdateCounterRequest{ + CounterUpdateRequest: &alpha.CounterUpdateRequest{ + Name: "games", + CountDiff: -5, + }}, + want: &alpha.Counter{ + Name: "games", Count: 0, Capacity: 10, + }, + }, + "Cannot Decrement Counter": { + updateRequest: &alpha.UpdateCounterRequest{ + CounterUpdateRequest: &alpha.CounterUpdateRequest{ + Name: "sessions", + CountDiff: -2, + }}, + wantErr: errors.Errorf("out of range. Count must be within range [0,Capacity]. Found Count: %d, Capacity: %d", -1, 100), + }, + "Cannot Increment Counter": { + updateRequest: &alpha.UpdateCounterRequest{ + CounterUpdateRequest: &alpha.CounterUpdateRequest{ + Name: "players", + CountDiff: 1, + }}, + wantErr: errors.Errorf("out of range. Count must be within range [0,Capacity]. Found Count: %d, Capacity: %d", 101, 100), + }, + "Counter does not exist": { + updateRequest: &alpha.UpdateCounterRequest{ + CounterUpdateRequest: &alpha.CounterUpdateRequest{ + Name: "dragons", + CountDiff: 1, + }}, + wantErr: errors.Errorf("not found. %s Counter not found", "dragons"), + }, + "request Counter is nil": { + updateRequest: &alpha.UpdateCounterRequest{ + CounterUpdateRequest: nil, + }, + wantErr: errors.Errorf("invalid argument. CounterUpdateRequest cannot be nil"), + }, + "capacity is less than zero": { + updateRequest: &alpha.UpdateCounterRequest{ + CounterUpdateRequest: &alpha.CounterUpdateRequest{ + Name: "lobbies", + Capacity: wrapperspb.Int64(-1), + }}, + wantErr: errors.Errorf("out of range. Capacity must be greater than or equal to 0. Found Capacity: %d", -1), + }, + "count is less than zero": { + updateRequest: &alpha.UpdateCounterRequest{ + CounterUpdateRequest: &alpha.CounterUpdateRequest{ + Name: "players", + Count: wrapperspb.Int64(-1), + }}, + wantErr: errors.Errorf("out of range. Count must be within range [0,Capacity]. Found Count: %d, Capacity: %d", -1, 100), + }, + "count is greater than capacity": { + updateRequest: &alpha.UpdateCounterRequest{ + CounterUpdateRequest: &alpha.CounterUpdateRequest{ + Name: "players", + Count: wrapperspb.Int64(101), + }}, + wantErr: errors.Errorf("out of range. Count must be within range [0,Capacity]. Found Count: %d, Capacity: %d", 101, 100), + }, + } + + for testName, testScenario := range testScenarios { + t.Run(testName, func(t *testing.T) { + got, err := l.UpdateCounter(context.Background(), testScenario.updateRequest) + // Check tests expecting non-errors + if testScenario.want != nil { + assert.NoError(t, err) + if diff := cmp.Diff(testScenario.want, got, protocmp.Transform()); diff != "" { + t.Errorf("Unexpected difference:\n%v", diff) + } + } else { + // Check tests expecting errors + assert.ErrorContains(t, err, testScenario.wantErr.Error()) + } + }) + } +} + func TestLocalSDKServerGetList(t *testing.T) { t.Parallel() @@ -734,7 +887,7 @@ func TestLocalSDKServerGetList(t *testing.T) { }, "List does not exist": { name: "noName", - wantErr: errors.Errorf("NOT_FOUND. %s List not found", "noName"), + wantErr: errors.Errorf("not found. %s List not found", "noName"), }, } @@ -867,21 +1020,21 @@ func TestLocalSDKServerUpdateList(t *testing.T) { }, UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity"}}, }, - wantErr: errors.Errorf("NOT_FOUND. %s List not found", "dragons"), + wantErr: errors.Errorf("not found. %s List not found", "dragons"), }, "request List is nil": { updateRequest: &alpha.UpdateListRequest{ List: nil, UpdateMask: &fieldmaskpb.FieldMask{}, }, - wantErr: errors.Errorf("INVALID_ARGUMENT. List: %v and UpdateMask %v cannot be nil", nil, &fieldmaskpb.FieldMask{}), + wantErr: errors.Errorf("invalid argument. List: %v and UpdateMask %v cannot be nil", nil, &fieldmaskpb.FieldMask{}), }, "request UpdateMask is nil": { updateRequest: &alpha.UpdateListRequest{ List: &alpha.List{}, UpdateMask: nil, }, - wantErr: errors.Errorf("INVALID_ARGUMENT. List: %v and UpdateMask %v cannot be nil", &alpha.Counter{}, nil), + wantErr: errors.Errorf("invalid argument. List: %v and UpdateMask %v cannot be nil", &alpha.List{}, nil), }, "updateMask contains invalid path": { updateRequest: &alpha.UpdateListRequest{ @@ -890,7 +1043,7 @@ func TestLocalSDKServerUpdateList(t *testing.T) { }, UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"foo"}}, }, - wantErr: errors.Errorf("INVALID_ARGUMENT. Field Mask Path(s): [foo] are invalid for List. Use valid field name(s): "), + wantErr: errors.Errorf("invalid argument. Field Mask Path(s): [foo] are invalid for List. Use valid field name(s): "), }, "updateMask is empty": { updateRequest: &alpha.UpdateListRequest{ @@ -899,7 +1052,7 @@ func TestLocalSDKServerUpdateList(t *testing.T) { }, UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{""}}, }, - wantErr: errors.Errorf("INVALID_ARGUMENT. Field Mask Path(s): [] are invalid for List. Use valid field name(s): "), + wantErr: errors.Errorf("invalid argument. Field Mask Path(s): [] are invalid for List. Use valid field name(s): "), }, "capacity is less than zero": { updateRequest: &alpha.UpdateListRequest{ @@ -909,7 +1062,7 @@ func TestLocalSDKServerUpdateList(t *testing.T) { }, UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity"}}, }, - wantErr: errors.Errorf("OUT_OF_RANGE. Capacity must be within range [0,1000]. Found Capacity: %d", int64(-1)), + wantErr: errors.Errorf("out of range. Capacity must be within range [0,1000]. Found Capacity: %d", -1), }, "capacity greater than max capacity (1000)": { updateRequest: &alpha.UpdateListRequest{ @@ -919,7 +1072,7 @@ func TestLocalSDKServerUpdateList(t *testing.T) { }, UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity"}}, }, - wantErr: errors.Errorf("OUT_OF_RANGE. Capacity must be within range [0,1000]. Found Capacity: %d", int64(1001)), + wantErr: errors.Errorf("out of range. Capacity must be within range [0,1000]. Found Capacity: %d", 1001), }, "capacity is less than List length": { updateRequest: &alpha.UpdateListRequest{ @@ -929,7 +1082,7 @@ func TestLocalSDKServerUpdateList(t *testing.T) { }, UpdateMask: &fieldmaskpb.FieldMask{Paths: []string{"capacity"}}, }, - wantErr: errors.Errorf("OUT_OF_RANGE. Capacity must be great than or equal to the size of the List of values. Found Capacity: %d, List Size: %d", int64(1), int64(2)), + wantErr: errors.Errorf("out of range. Capacity must be great than or equal to the size of the List of values. Found Capacity: %d, List Size: %d", 1, 2), }, } @@ -1007,21 +1160,21 @@ func TestLocalSDKServerAddListValue(t *testing.T) { addRequest: &alpha.AddListValueRequest{ Name: "dragons", }, - wantErr: errors.Errorf("NOT_FOUND. %s List not found", "dragons"), + wantErr: errors.Errorf("not found. %s List not found", "dragons"), }, "add more values than capacity": { addRequest: &alpha.AddListValueRequest{ Name: "hacks", Value: "hack3", }, - wantErr: errors.Errorf("OUT_OF_RANGE. No available capacity. Current Capacity: %d, List Size: %d", int64(2), int64(2)), + wantErr: errors.Errorf("out of range. No available capacity. Current Capacity: %d, List Size: %d", int64(2), int64(2)), }, "add existing value": { addRequest: &alpha.AddListValueRequest{ Name: "lemmings", Value: "lemming1", }, - wantErr: errors.Errorf("ALREADY_EXISTS. Value: %s already in List: %s", "lemming1", "lemmings"), + wantErr: errors.Errorf("already exists. Value: %s already in List: %s", "lemming1", "lemmings"), }, } @@ -1099,14 +1252,14 @@ func TestLocalSDKServerRemoveListValue(t *testing.T) { removeRequest: &alpha.RemoveListValueRequest{ Name: "dragons", }, - wantErr: errors.Errorf("NOT_FOUND. %s List not found", "dragons"), + wantErr: errors.Errorf("not found. %s List not found", "dragons"), }, "value does not exist": { removeRequest: &alpha.RemoveListValueRequest{ Name: "items", Value: "item3", }, - wantErr: errors.Errorf("NOT_FOUND. Value: %s not found in List: %s", "item3", "items"), + wantErr: errors.Errorf("not found. Value: %s not found in List: %s", "item3", "items"), }, } diff --git a/pkg/sdkserver/sdkserver.go b/pkg/sdkserver/sdkserver.go index 42b5e59e5c..74b5e3d94d 100644 --- a/pkg/sdkserver/sdkserver.go +++ b/pkg/sdkserver/sdkserver.go @@ -849,6 +849,7 @@ func (s *SDKServer) GetCounter(ctx context.Context, in *alpha.GetCounterRequest) } // UpdateCounter collapses all UpdateCounterRequests for a given Counter into a single request. +// UpdateCounterRequest must be one and only one of Capacity, Count, or CountDiff. // Returns error if the Counter does not exist (name cannot be updated). // Returns error if the Count is out of range [0,Capacity]. // [Stage:Alpha]