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 }