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

Sort By Counters or Lists during GameServerAllocation 2716 #3091

Merged
merged 4 commits into from
Apr 20, 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
66 changes: 66 additions & 0 deletions pkg/apis/allocation/v1/gameserverallocation.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ const (
// GameServerAllocationContention when the allocation is unsuccessful
// because of contention
GameServerAllocationContention GameServerAllocationState = "Contention"
// GameServerAllocationPriorityCounter is a PriorityType for sorting Game Servers by Counter
GameServerAllocationPriorityCounter string = "Counter"
// GameServerAllocationPriorityList is a PriorityType for sorting Game Servers by List
GameServerAllocationPriorityList string = "List"
// GameServerAllocationAscending is a Priority Order where the smaller count is preferred in sorting.
GameServerAllocationAscending string = "Ascending"
// GameServerAllocationDescending is a Priority Order where the larger count is preferred in sorting.
GameServerAllocationDescending string = "Descending"
)

// GameServerAllocationState is the Allocation state
Expand Down Expand Up @@ -82,6 +90,13 @@ type GameServerAllocationSpec struct {
// This is useful for things like smoke testing of new game servers.
Preferred []GameServerSelector `json:"preferred,omitempty"`

// (Alpha, CountsAndLists feature flag) The first Priority on the array of Priorities is the most
// important for sorting. The allocator will use the first priority for sorting GameServers in the
// Selector set, and will only use any following priority for tie-breaking during sort.
// Impacts which GameServer is checked first.
// +optional
Priorities []Priority `json:"priorities,omitempty"`

// Ordered list of GameServer label selectors.
// If the first selector is not matched, the selection attempts the second selector, and so on.
// This is useful for things like smoke testing of new game servers.
Expand Down Expand Up @@ -484,6 +499,41 @@ func (mp *MetaPatch) Validate() ([]metav1.StatusCause, bool) {
return causes, len(causes) == 0
}

// Priority is a sorting option for GameServers with Counters or Lists based on the count or
// number of items in a List.
// PriorityType: Sort by a "Counter" or a "List".
// Key: The name of the Counter or List. If not found on the GameServer, has no impact.
// Order: Sort by "Ascending" or "Descending". Default is "Descending" so bigger count is preferred.
// "Ascending" would be smaller count is preferred.
type Priority struct {
PriorityType string `json:"priorityType"`
Key string `json:"key"`
Order string `json:"order"`
}

// Validate returns if the Priority is valid.
func (p *Priority) validate(field string) ([]metav1.StatusCause, bool) {
var causes []metav1.StatusCause

if !(p.PriorityType == GameServerAllocationPriorityCounter || p.PriorityType == GameServerAllocationPriorityList) {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Message: "Priority.Sort must be either `Counter` or `List`",
Field: field,
})
}

if !(p.Order == GameServerAllocationAscending || p.Order == GameServerAllocationDescending || p.Order == "") {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Message: "Priority.Order must be either `Ascending` or `Descending`",
Field: field,
})
}

igooch marked this conversation as resolved.
Show resolved Hide resolved
return causes, len(causes) == 0
}

// GameServerAllocationStatus is the status for an GameServerAllocation resource
type GameServerAllocationStatus struct {
// GameServerState is the current state of an GameServerAllocation, e.g. Allocated, or UnAllocated
Expand Down Expand Up @@ -547,6 +597,22 @@ func (gsa *GameServerAllocation) Validate() ([]metav1.StatusCause, bool) {
}
}

if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) && (gsa.Spec.Priorities != nil) {
causes = append(causes, metav1.StatusCause{
Type: metav1.CauseTypeFieldValueInvalid,
Message: "Feature CountsAndLists must be enabled if Priorities is specified",
Field: "spec.priorities",
})
}

if runtime.FeatureEnabled(runtime.FeatureCountsAndLists) && (gsa.Spec.Priorities != nil) {
for i := range gsa.Spec.Priorities {
if c, ok := gsa.Spec.Priorities[i].validate(fmt.Sprintf("spec.priorities[%d]", i)); !ok {
causes = append(causes, c...)
}
}
}

if c, ok := gsa.Spec.MetaPatch.Validate(); !ok {
causes = append(causes, c...)
}
Expand Down
165 changes: 164 additions & 1 deletion pkg/apis/allocation/v1/gameserverallocation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,16 @@ func TestGameServerAllocationApplyDefaults(t *testing.T) {

runtime.FeatureTestMutex.Lock()
defer runtime.FeatureTestMutex.Unlock()
assert.NoError(t, runtime.ParseFeatures(fmt.Sprintf("%s=true&%s=true", runtime.FeaturePlayerAllocationFilter, runtime.FeatureStateAllocationFilter)))
assert.NoError(t, runtime.ParseFeatures(fmt.Sprintf("%s=true&%s=true&%s=true", runtime.FeaturePlayerAllocationFilter, runtime.FeatureStateAllocationFilter, runtime.FeatureCountsAndLists)))

gsa = &GameServerAllocation{}
gsa.ApplyDefaults()

assert.Equal(t, agonesv1.GameServerStateReady, *gsa.Spec.Required.GameServerState)
assert.Equal(t, int64(0), gsa.Spec.Required.Players.MaxAvailable)
assert.Equal(t, int64(0), gsa.Spec.Required.Players.MinAvailable)
assert.Equal(t, []Priority(nil), gsa.Spec.Priorities)
assert.Nil(t, gsa.Spec.Priorities)
}

// nolint // Current lint duplicate threshold will consider this function is a duplication of the function TestGameServerAllocationSpecSelectors
Expand Down Expand Up @@ -351,6 +353,167 @@ func TestGameServerSelectorValidate(t *testing.T) {
}
}

func TestGameServerPriorityValidate(t *testing.T) {
t.Parallel()

runtime.FeatureTestMutex.Lock()
defer runtime.FeatureTestMutex.Unlock()
assert.NoError(t, runtime.ParseFeatures(fmt.Sprintf("%s=true", runtime.FeatureCountsAndLists)))

type expected struct {
valid bool
causeLen int
fields []string
}

fixtures := map[string]struct {
gsa *GameServerAllocation
expected expected
}{
"valid Counter Ascending": {
gsa: &GameServerAllocation{
Spec: GameServerAllocationSpec{Priorities: []Priority{
{
PriorityType: "Counter",
Key: "Foo",
Order: "Ascending",
},
},
},
},
expected: expected{
valid: true,
causeLen: 0,
},
},
"valid Counter Descending": {
gsa: &GameServerAllocation{
Spec: GameServerAllocationSpec{Priorities: []Priority{
{
PriorityType: "Counter",
Key: "Bar",
Order: "Descending",
},
},
},
},
expected: expected{
valid: true,
causeLen: 0,
},
},
"valid Counter empty Order": {
gsa: &GameServerAllocation{
Spec: GameServerAllocationSpec{Priorities: []Priority{
{
PriorityType: "Counter",
Key: "Bar",
Order: "",
},
},
},
},
expected: expected{
valid: true,
causeLen: 0,
},
},
"invalid counter type and order": {
gsa: &GameServerAllocation{
Spec: GameServerAllocationSpec{Priorities: []Priority{
{
PriorityType: "counter",
Key: "Babar",
Order: "descending",
},
},
},
},
expected: expected{
valid: false,
causeLen: 2,
},
},
"valid List Ascending": {
gsa: &GameServerAllocation{
Spec: GameServerAllocationSpec{Priorities: []Priority{
{
PriorityType: "List",
Key: "Baz",
Order: "Ascending",
},
},
},
},
expected: expected{
valid: true,
causeLen: 0,
},
},
"valid List Descending": {
gsa: &GameServerAllocation{
Spec: GameServerAllocationSpec{Priorities: []Priority{
{
PriorityType: "List",
Key: "Blerg",
Order: "Descending",
},
},
},
},
expected: expected{
valid: true,
causeLen: 0,
},
},
"valid List empty Order": {
gsa: &GameServerAllocation{
Spec: GameServerAllocationSpec{Priorities: []Priority{
{
PriorityType: "List",
Key: "Blerg",
Order: "Ascending",
},
},
},
},
expected: expected{
valid: true,
causeLen: 0,
},
},
"invalid list type and order": {
gsa: &GameServerAllocation{
Spec: GameServerAllocationSpec{Priorities: []Priority{
{
PriorityType: "list",
Key: "Schmorg",
Order: "ascending",
},
},
},
},
expected: expected{
valid: false,
causeLen: 2,
},
},
}

for k, v := range fixtures {
t.Run(k, func(t *testing.T) {
v.gsa.ApplyDefaults()
causes, valid := v.gsa.Validate()
assert.Equal(t, v.expected.valid, valid)
assert.Len(t, causes, v.expected.causeLen)

for i := range v.expected.fields {
assert.Equal(t, v.expected.fields[i], causes[i].Field)
}
})
}
}

func TestMetaPatchValidate(t *testing.T) {
t.Parallel()

Expand Down
21 changes: 21 additions & 0 deletions pkg/apis/allocation/v1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading