agones.dev/agones@v1.54.0/pkg/apis/multicluster/v1/gameserverallocationpolicy.go (about)

     1  // Copyright 2019 Google LLC All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package v1
    16  
    17  import (
    18  	"math/rand"
    19  	"sort"
    20  
    21  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    22  )
    23  
    24  // GameServerAllocationPolicySpec defines the desired state of GameServerAllocationPolicy
    25  type GameServerAllocationPolicySpec struct {
    26  	// +kubebuilder:validation:Minimum=0
    27  	Priority int32 `json:"priority"`
    28  	// +kubebuilder:validation:Minimum=0
    29  	Weight         int                   `json:"weight"`
    30  	ConnectionInfo ClusterConnectionInfo `json:"connectionInfo,omitempty"`
    31  }
    32  
    33  // ClusterConnectionInfo defines the connection information for a cluster
    34  type ClusterConnectionInfo struct {
    35  	// Optional: the name of the targeted cluster
    36  	ClusterName string `json:"clusterName"`
    37  	// The endpoints for the allocator service in the targeted cluster.
    38  	// If the AllocationEndpoints is not set, the allocation happens on local cluster.
    39  	// If there are multiple endpoints any of the endpoints that can handle allocation request should suffice
    40  	AllocationEndpoints []string `json:"allocationEndpoints,omitempty"`
    41  	// The name of the secret that contains TLS client certificates to connect the allocator server in the targeted cluster
    42  	SecretName string `json:"secretName"`
    43  	// The cluster namespace from which to allocate gameservers
    44  	Namespace string `json:"namespace"`
    45  	// The PEM encoded server CA, used by the allocator client to authenticate the remote server.
    46  	ServerCA []byte `json:"serverCa,omitempty"`
    47  }
    48  
    49  // +genclient
    50  // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
    51  
    52  // GameServerAllocationPolicy is the Schema for the gameserverallocationpolicies API
    53  // +k8s:openapi-gen=true
    54  type GameServerAllocationPolicy struct {
    55  	metav1.TypeMeta   `json:",inline"`
    56  	metav1.ObjectMeta `json:"metadata,omitempty"`
    57  
    58  	Spec GameServerAllocationPolicySpec `json:"spec,omitempty"`
    59  }
    60  
    61  // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
    62  
    63  // GameServerAllocationPolicyList contains a list of GameServerAllocationPolicy
    64  type GameServerAllocationPolicyList struct {
    65  	metav1.TypeMeta `json:",inline"`
    66  	metav1.ListMeta `json:"metadata,omitempty"`
    67  	Items           []GameServerAllocationPolicy `json:"items"`
    68  }
    69  
    70  // ConnectionInfoIterator an iterator on ClusterConnectionInfo
    71  type ConnectionInfoIterator struct {
    72  	// currPriority Current priority index from the orderedPriorities
    73  	currPriority int
    74  	// orderedPriorities list of ordered priorities
    75  	orderedPriorities []int32
    76  	// priorityToCluster Map of priority to cluster-policies map
    77  	priorityToCluster map[int32]map[string][]*GameServerAllocationPolicy
    78  	// clusterBlackList the cluster blacklist for the clusters that has already returned
    79  	clusterBlackList map[string]bool
    80  }
    81  
    82  // Next returns the next ClusterConnectionInfo value if available or nil if iterator reaches the end.
    83  func (it *ConnectionInfoIterator) Next() *ClusterConnectionInfo {
    84  	for it.currPriority < len(it.orderedPriorities) {
    85  		// Get clusters with the highest priority
    86  		currPriority := it.orderedPriorities[it.currPriority]
    87  		clusterPolicy := it.priorityToCluster[currPriority]
    88  
    89  		if result := it.getClusterConnectionInfo(clusterPolicy); result == nil {
    90  			// If there is no cluster with the current priority, choose cluster with next highest priority
    91  			it.currPriority++
    92  		} else {
    93  			// To avoid the same cluster again add that to a black list
    94  			it.clusterBlackList[result.ClusterName] = true
    95  			return result
    96  		}
    97  	}
    98  
    99  	return nil
   100  }
   101  
   102  // NewConnectionInfoIterator creates an iterator for connection info
   103  func NewConnectionInfoIterator(policies []*GameServerAllocationPolicy) *ConnectionInfoIterator {
   104  	priorityToCluster := make(map[int32]map[string][]*GameServerAllocationPolicy)
   105  	for _, policy := range policies {
   106  		priority := policy.Spec.Priority
   107  		clusterName := policy.Spec.ConnectionInfo.ClusterName
   108  
   109  		// 1. Add priorities to the map of priority to cluster-priorities map
   110  		clusterPolicy, ok := priorityToCluster[priority]
   111  		if !ok {
   112  			clusterPolicy = make(map[string][]*GameServerAllocationPolicy)
   113  			priorityToCluster[priority] = clusterPolicy
   114  		}
   115  
   116  		// 2. Add cluster to the cluster-priorities map
   117  		if _, ok := clusterPolicy[clusterName]; !ok {
   118  			clusterPolicy[clusterName] = []*GameServerAllocationPolicy{policy}
   119  		} else {
   120  			clusterPolicy[clusterName] = append(clusterPolicy[clusterName], policy)
   121  		}
   122  	}
   123  
   124  	// 3. Sort priorities
   125  	priorities := make([]int32, 0, len(priorityToCluster))
   126  	for k := range priorityToCluster {
   127  		priorities = append(priorities, k)
   128  	}
   129  	sort.Slice(priorities, func(i, j int) bool { return priorities[i] < priorities[j] })
   130  
   131  	// 4. Store initial values for the iterator
   132  	return &ConnectionInfoIterator{priorityToCluster: priorityToCluster, currPriority: 0, orderedPriorities: priorities, clusterBlackList: make(map[string]bool)}
   133  }
   134  
   135  // getClusterConnectionInfo returns a ClusterConnectionInfo selected base on weighted randomization.
   136  func (it *ConnectionInfoIterator) getClusterConnectionInfo(clusterPolicy map[string][]*GameServerAllocationPolicy) *ClusterConnectionInfo {
   137  	connections := []*ClusterConnectionInfo{}
   138  	weights := []int{}
   139  	for cluster, policies := range clusterPolicy {
   140  		if _, ok := it.clusterBlackList[cluster]; ok {
   141  			continue
   142  		}
   143  		weights = append(weights, avgWeight(policies))
   144  		connections = append(connections, &policies[0].Spec.ConnectionInfo)
   145  	}
   146  
   147  	if len(connections) == 0 {
   148  		return nil
   149  	}
   150  
   151  	return selectRandomWeighted(connections, weights)
   152  }
   153  
   154  // avgWeight calculates average over allocation policy Weight field.
   155  func avgWeight(policies []*GameServerAllocationPolicy) int {
   156  	if len(policies) == 0 {
   157  		return 0
   158  	}
   159  	var sum int
   160  	for _, policy := range policies {
   161  		sum += policy.Spec.Weight
   162  	}
   163  	return sum / len(policies)
   164  }
   165  
   166  // selectRandomWeighted selects a ClusterConnectionInfo info from a weighted list of ClusterConnectionInfo
   167  func selectRandomWeighted(connections []*ClusterConnectionInfo, weights []int) *ClusterConnectionInfo {
   168  	sum := 0
   169  	for _, weight := range weights {
   170  		sum += weight
   171  	}
   172  
   173  	if sum <= 0 {
   174  		return nil
   175  	}
   176  
   177  	randValue := rand.Intn(sum)
   178  	sum = 0
   179  	for i, weight := range weights {
   180  		sum += weight
   181  		if randValue < sum {
   182  			return connections[i]
   183  		}
   184  	}
   185  	return nil
   186  }