Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Go SDK client Counter functions #3372

Merged
merged 3 commits into from
Sep 12, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
94 changes: 91 additions & 3 deletions sdks/go/alpha.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,10 @@ package sdk
import (
"context"

"agones.dev/agones/pkg/sdk/alpha"
"github.com/pkg/errors"

"google.golang.org/grpc"

"agones.dev/agones/pkg/sdk/alpha"
"google.golang.org/protobuf/types/known/wrapperspb"
)

// Alpha is the struct for Alpha SDK functionality.
Expand Down Expand Up @@ -84,3 +83,92 @@ func (a *Alpha) GetConnectedPlayers() ([]string, error) {
list, err := a.client.GetConnectedPlayers(context.Background(), &alpha.Empty{})
return list.GetList(), errors.Wrap(err, "could not list connected players")
}

// GetCounterCount returns the Count for a Counter, given the Counter's key (name).
// Will error if the key was not predefined in the GameServer resource on creation.
func (a *Alpha) GetCounterCount(key string) (int64, error) {
counter, err := a.client.GetCounter(context.Background(), &alpha.GetCounterRequest{Name: key})
if err != nil {
return -1, errors.Wrapf(err, "could not get Counter %s count", key)
igooch marked this conversation as resolved.
Show resolved Hide resolved
}
return counter.Count, nil
}

// IncrementCounter increases a counter by the given nonnegative integer amount.
// Will execute the increment operation against the current CRD value. Will max at max(int64).
// Will error if the key was not predefined in the GameServer resource on creation.
// Returns false if the count is at the current capacity (to the latest knowledge of the SDK),
// and no increment will occur.
//
// Note: A potential race condition here is that if count values are set from both the SDK and
// through the K8s API (Allocation or otherwise), since the SDK append operation back to the CRD
// value is batched asynchronous any value incremented past the capacity will be silently truncated.
func (a *Alpha) IncrementCounter(key string, amount int64) (bool, error) {
if amount < 0 {
return false, errors.Errorf("CountIncrement amount must be a positive int64, found %d", amount)
}
_, err := a.client.UpdateCounter(context.Background(), &alpha.UpdateCounterRequest{
CounterUpdateRequest: &alpha.CounterUpdateRequest{
Name: key,
CountDiff: amount,
}})
if err != nil {
return false, errors.Wrapf(err, "could not increment Counter %s by amount %d", key, amount)
}
return true, err
}

// DecrementCounter decreases the current count by the given nonnegative integer amount.
// The Counter Will not go below 0. Will execute the decrement operation against the current CRD value.
// Returns false if the count is at 0 (to the latest knowledge of the SDK), and no decrement will occur.
func (a *Alpha) DecrementCounter(key string, amount int64) (bool, error) {
if amount < 0 {
return false, errors.Errorf("CountDecrement amount must be a positive int64, found %d", amount)
}
_, err := a.client.UpdateCounter(context.Background(), &alpha.UpdateCounterRequest{
CounterUpdateRequest: &alpha.CounterUpdateRequest{
Name: key,
CountDiff: amount * -1,
}})
if err != nil {
return false, errors.Wrapf(err, "could not decrement Counter %s by amount %d", key, amount)
}
return true, err
}

// SetCounterCount sets a count to the given value. Use with care, as this will overwrite any previous
// invocations’ value. Cannot be greater than Capacity.
func (a *Alpha) SetCounterCount(key string, amount int64) (bool, error) {
_, err := a.client.UpdateCounter(context.Background(), &alpha.UpdateCounterRequest{
CounterUpdateRequest: &alpha.CounterUpdateRequest{
Name: key,
Count: wrapperspb.Int64(amount),
}})
if err != nil {
return false, errors.Wrapf(err, "could not set Counter %s count to amount %d", key, amount)
}
return true, err
}

// GetCounterCapacity returns the Capacity for a Counter, given the Counter's key (name).
// Will error if the key was not predefined in the GameServer resource on creation.
func (a *Alpha) GetCounterCapacity(key string) (int64, error) {
counter, err := a.client.GetCounter(context.Background(), &alpha.GetCounterRequest{Name: key})
if err != nil {
return -1, errors.Wrapf(err, "could not get Counter %s capacity", key)
}
return counter.Capacity, nil
}

// SetCounterCapacity sets the capacity for a given count. A capacity of 0 is no capacity.
func (a *Alpha) SetCounterCapacity(key string, amount int64) (bool, error) {
_, err := a.client.UpdateCounter(context.Background(), &alpha.UpdateCounterRequest{
CounterUpdateRequest: &alpha.CounterUpdateRequest{
Name: key,
Capacity: wrapperspb.Int64(amount),
}})
if err != nil {
return false, errors.Wrapf(err, "could not set Counter %s capacity to amount %d", key, amount)
}
return true, err
}
190 changes: 184 additions & 6 deletions sdks/go/alpha_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,160 @@ func TestAlphaGetAndSetPlayerCapacity(t *testing.T) {
assert.Equal(t, []string{playerID}, list)
}

func TestAlphaGetAndUpdateCounter(t *testing.T) {
mock := &alphaMock{}
// Counters must be predefined in the GameServer resource on creation.
mock.counters = make(map[string]*alpha.Counter)
sessions := alpha.Counter{
Name: "sessions",
Count: 21,
Capacity: 42,
}
games := alpha.Counter{
Name: "games",
Count: 12,
Capacity: 24,
}
gamers := alpha.Counter{
Name: "gamers",
Count: 263,
Capacity: 500,
}
mock.counters["sessions"] = &alpha.Counter{
Name: "sessions",
Count: 21,
Capacity: 42,
}
mock.counters["games"] = &alpha.Counter{
Name: "games",
Count: 12,
Capacity: 24,
}
mock.counters["gamers"] = &alpha.Counter{
Name: "gamers",
Count: 263,
Capacity: 500,
}
a := Alpha{
client: mock,
}

t.Parallel()

t.Run("Set Counter and Set Capacity", func(t *testing.T) {
count, err := a.GetCounterCount("sessions")
assert.NoError(t, err)
assert.Equal(t, sessions.Count, count)

capacity, err := a.GetCounterCapacity("sessions")
assert.NoError(t, err)
assert.Equal(t, sessions.Capacity, capacity)

wantCapacity := int64(25)
ok, err := a.SetCounterCapacity("sessions", wantCapacity)
assert.NoError(t, err)
assert.True(t, ok)

capacity, err = a.GetCounterCapacity("sessions")
assert.NoError(t, err)
assert.Equal(t, wantCapacity, capacity)

wantCount := int64(10)
ok, err = a.SetCounterCount("sessions", wantCount)
assert.NoError(t, err)
assert.True(t, ok)

count, err = a.GetCounterCount("sessions")
assert.NoError(t, err)
assert.Equal(t, wantCount, count)
})

t.Run("Get and Set Non-Defined Counter", func(t *testing.T) {
_, err := a.GetCounterCount("secessions")
assert.Error(t, err)

_, err = a.GetCounterCapacity("secessions")
assert.Error(t, err)

ok, err := a.SetCounterCapacity("secessions", int64(100))
assert.Error(t, err)
assert.False(t, ok)

ok, err = a.SetCounterCount("secessions", int64(0))
assert.Error(t, err)
assert.False(t, ok)
})

// nolint:dupl // testing DecrementCounter and IncrementCounter are not duplicates.
t.Run("Decrement Counter Fails then Success", func(t *testing.T) {
count, err := a.GetCounterCount("games")
assert.NoError(t, err)
assert.Equal(t, games.Count, count)

ok, err := a.DecrementCounter("games", 21)
assert.Error(t, err)
assert.False(t, ok)

count, err = a.GetCounterCount("games")
assert.NoError(t, err)
assert.Equal(t, games.Count, count)

ok, err = a.DecrementCounter("games", -12)
assert.Error(t, err)
assert.False(t, ok)

count, err = a.GetCounterCount("games")
assert.NoError(t, err)
assert.Equal(t, games.Count, count)

ok, err = a.DecrementCounter("games", 12)
assert.NoError(t, err)
assert.True(t, ok)

count, err = a.GetCounterCount("games")
assert.NoError(t, err)
assert.Equal(t, int64(0), count)
})

// nolint:dupl // testing DecrementCounter and IncrementCounter are not duplicates.
t.Run("Increment Counter Fails then Success", func(t *testing.T) {
count, err := a.GetCounterCount("gamers")
assert.NoError(t, err)
assert.Equal(t, gamers.Count, count)

ok, err := a.IncrementCounter("gamers", 250)
assert.Error(t, err)
assert.False(t, ok)

count, err = a.GetCounterCount("gamers")
assert.NoError(t, err)
assert.Equal(t, gamers.Count, count)

ok, err = a.IncrementCounter("gamers", -237)
assert.Error(t, err)
assert.False(t, ok)

count, err = a.GetCounterCount("gamers")
assert.NoError(t, err)
assert.Equal(t, gamers.Count, count)

ok, err = a.IncrementCounter("gamers", 237)
assert.NoError(t, err)
assert.True(t, ok)

count, err = a.GetCounterCount("gamers")
assert.NoError(t, err)
assert.Equal(t, int64(500), count)
})

}

type alphaMock struct {
capacity int64
playerCount int64
playerConnected string
playerDisconnected string
counters map[string]*alpha.Counter
}

func (a *alphaMock) PlayerConnect(ctx context.Context, id *alpha.PlayerID, opts ...grpc.CallOption) (*alpha.Bool, error) {
Expand Down Expand Up @@ -112,16 +261,45 @@ func (a *alphaMock) GetPlayerCount(ctx context.Context, in *alpha.Empty, opts ..
return &alpha.Count{Count: a.playerCount}, nil
}

// GetCounter to be implemented
func (a *alphaMock) GetCounter(ctx context.Context, in *alpha.GetCounterRequest, opts ...grpc.CallOption) (*alpha.Counter, error) {
// TODO(#2716): Implement me!
return nil, errors.Errorf("Unimplemented -- GetCounter coming soon")
if counter, ok := a.counters[in.Name]; ok {
return counter, nil
}
return nil, errors.Errorf("NOT_FOUND. %s Counter not found", in.Name)
}

// UpdateCounter to be implemented
func (a *alphaMock) UpdateCounter(ctx context.Context, in *alpha.UpdateCounterRequest, opts ...grpc.CallOption) (*alpha.Counter, error) {
// TODO(#2716): Implement me!
return nil, errors.Errorf("Unimplemented -- UpdateCounter coming soon")
counter, err := a.GetCounter(ctx, &alpha.GetCounterRequest{Name: in.CounterUpdateRequest.Name})
if err != nil {
return nil, err
}

switch {
case in.CounterUpdateRequest.CountDiff != 0:
count := counter.Count + in.CounterUpdateRequest.CountDiff
if count < 0 || count > counter.Capacity {
return nil, errors.Errorf("OUT_OF_RANGE. Count must be within range [0,Capacity]. Found Count: %d, Capacity: %d", count, counter.Capacity)
}
counter.Count = count
case in.CounterUpdateRequest.Count != nil:
countSet := in.CounterUpdateRequest.Count.GetValue()
if countSet < 0 || countSet > counter.Capacity {
return nil, errors.Errorf("OUT_OF_RANGE. Count must be within range [0,Capacity]. Found Count: %d, Capacity: %d", countSet, counter.Capacity)
}
counter.Count = countSet
case in.CounterUpdateRequest.Capacity != nil:
capacity := in.CounterUpdateRequest.Capacity.GetValue()
if capacity < 0 {
return nil, errors.Errorf("OUT_OF_RANGE. Capacity must be greater than or equal to 0. Found Capacity: %d", capacity)
}
counter.Capacity = capacity
default:
return nil, errors.Errorf("INVALID_ARGUMENT. Malformed CounterUpdateRequest: %v",
in.CounterUpdateRequest)
}

a.counters[in.CounterUpdateRequest.Name] = counter
return a.counters[in.CounterUpdateRequest.Name], nil
}

// GetList to be implemented
Expand Down