agones.dev/agones@v1.53.0/pkg/apis/agones/v1/common.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" 19 20 apivalidation "k8s.io/apimachinery/pkg/api/validation" 21 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 22 metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation" 23 "k8s.io/apimachinery/pkg/labels" 24 "k8s.io/apimachinery/pkg/runtime/schema" 25 "k8s.io/apimachinery/pkg/util/validation" 26 "k8s.io/apimachinery/pkg/util/validation/field" 27 ) 28 29 // Block of const Error messages and GameServerAllocation Counter actions 30 const ( 31 ErrContainerRequired = "Container is required when using multiple containers in the pod template" 32 ErrHostPort = "HostPort cannot be specified with a Dynamic or Passthrough PortPolicy" 33 ErrPortPolicyStatic = "PortPolicy must be Static" 34 ErrContainerPortRequired = "ContainerPort must be defined for Dynamic and Static PortPolicies" 35 ErrContainerPortPassthrough = "ContainerPort cannot be specified with Passthrough PortPolicy" 36 ErrContainerNameInvalid = "Container must be empty or the name of a container in the pod template" 37 // GameServerPriorityIncrement is a Counter Action that indiciates the Counter's Count should be incremented at Allocation. 38 GameServerPriorityIncrement string = "Increment" 39 // GameServerPriorityDecrement is a Counter Action that indiciates the Counter's Count should be decremented at Allocation. 40 GameServerPriorityDecrement string = "Decrement" 41 // GameServerPriorityCounter is a Type for sorting Game Servers by Counter 42 GameServerPriorityCounter string = "Counter" 43 // GameServerPriorityList is a Type for sorting Game Servers by List 44 GameServerPriorityList string = "List" 45 // GameServerPriorityAscending is a Priority Order where the smaller count is preferred in sorting. 46 GameServerPriorityAscending string = "Ascending" 47 // GameServerPriorityDescending is a Priority Order where the larger count is preferred in sorting. 48 GameServerPriorityDescending string = "Descending" 49 ) 50 51 // AggregatedPlayerStatus stores total player tracking values 52 type AggregatedPlayerStatus struct { 53 Count int64 `json:"count"` 54 Capacity int64 `json:"capacity"` 55 } 56 57 // AggregatedCounterStatus stores total and allocated Counter tracking values 58 type AggregatedCounterStatus struct { 59 AllocatedCount int64 `json:"allocatedCount"` 60 AllocatedCapacity int64 `json:"allocatedCapacity"` 61 Count int64 `json:"count"` 62 Capacity int64 `json:"capacity"` 63 } 64 65 // AggregatedListStatus stores total and allocated List tracking values 66 type AggregatedListStatus struct { 67 AllocatedCount int64 `json:"allocatedCount"` 68 AllocatedCapacity int64 `json:"allocatedCapacity"` 69 Count int64 `json:"count"` 70 Capacity int64 `json:"capacity"` 71 } 72 73 // crd is an interface to get Name and Kind of CRD 74 type crd interface { 75 GetName() string 76 GetObjectKind() schema.ObjectKind 77 } 78 79 // validateName Check NameSize of a CRD 80 func validateName(c crd, fldPath *field.Path) field.ErrorList { 81 var allErrs field.ErrorList 82 name := c.GetName() 83 // make sure the Name of a Fleet does not oversize the Label size in GSS and GS 84 if len(name) > validation.LabelValueMaxLength { 85 allErrs = append(allErrs, field.TooLongMaxLength(fldPath.Child("name"), name, 63)) 86 } 87 return allErrs 88 } 89 90 // gsSpec is an interface which contains all necessary 91 // functions to perform common validations against it 92 type gsSpec interface { 93 GetGameServerSpec() *GameServerSpec 94 } 95 96 // validateGSSpec Check GameServerSpec of a CRD 97 // Used by Fleet and GameServerSet 98 func validateGSSpec(apiHooks APIHooks, gs gsSpec, fldPath *field.Path) field.ErrorList { 99 gsSpec := gs.GetGameServerSpec() 100 gsSpec.ApplyDefaults() 101 allErrs := gsSpec.Validate(apiHooks, "", fldPath) 102 return allErrs 103 } 104 105 // validateObjectMeta Check ObjectMeta specification 106 // Used by Fleet, GameServerSet and GameServer 107 func validateObjectMeta(objMeta *metav1.ObjectMeta, fldPath *field.Path) field.ErrorList { 108 allErrs := metav1validation.ValidateLabels(objMeta.Labels, fldPath.Child("labels")) 109 allErrs = append(allErrs, apivalidation.ValidateAnnotations(objMeta.Annotations, fldPath.Child("annotations"))...) 110 return allErrs 111 } 112 113 // AllocationOverflow specifies what labels and/or annotations to apply on Allocated GameServers 114 // if the desired number of the underlying `GameServerSet` drops below the number of Allocated GameServers 115 // attached to it. 116 type AllocationOverflow struct { 117 // Labels to be applied to the `GameServer` 118 // +optional 119 Labels map[string]string `json:"labels,omitempty"` 120 // Annotations to be applied to the `GameServer` 121 // +optional 122 Annotations map[string]string `json:"annotations,omitempty"` 123 } 124 125 // Validate validates the label and annotation values 126 func (ao *AllocationOverflow) Validate(fldPath *field.Path) field.ErrorList { 127 allErrs := metav1validation.ValidateLabels(ao.Labels, fldPath.Child("labels")) 128 allErrs = append(allErrs, apivalidation.ValidateAnnotations(ao.Annotations, fldPath.Child("annotations"))...) 129 return allErrs 130 } 131 132 // CountMatches returns the number of Allocated GameServers that match the labels and annotations, and 133 // the set of GameServers left over. 134 func (ao *AllocationOverflow) CountMatches(list []*GameServer) (int32, []*GameServer) { 135 count := int32(0) 136 var rest []*GameServer 137 labelSelector := labels.Set(ao.Labels).AsSelector() 138 annotationSelector := labels.Set(ao.Annotations).AsSelector() 139 140 for _, gs := range list { 141 if gs.Status.State != GameServerStateAllocated { 142 continue 143 } 144 if !labelSelector.Matches(labels.Set(gs.ObjectMeta.Labels)) { 145 rest = append(rest, gs) 146 continue 147 } 148 if !annotationSelector.Matches(labels.Set(gs.ObjectMeta.Annotations)) { 149 rest = append(rest, gs) 150 continue 151 } 152 count++ 153 } 154 155 return count, rest 156 } 157 158 // Apply applies the labels and annotations to the passed in GameServer 159 func (ao *AllocationOverflow) Apply(gs *GameServer) { 160 if ao.Annotations != nil { 161 if gs.ObjectMeta.Annotations == nil { 162 gs.ObjectMeta.Annotations = map[string]string{} 163 } 164 for k, v := range ao.Annotations { 165 gs.ObjectMeta.Annotations[k] = v 166 } 167 } 168 if ao.Labels != nil { 169 if gs.ObjectMeta.Labels == nil { 170 gs.ObjectMeta.Labels = map[string]string{} 171 } 172 for k, v := range ao.Labels { 173 gs.ObjectMeta.Labels[k] = v 174 } 175 } 176 } 177 178 // Priority is a sorting option for GameServers with Counters or Lists based on the available capacity, 179 // i.e. the current Capacity value, minus either the Count value or List length. 180 type Priority struct { 181 // Type: Sort by a "Counter" or a "List". 182 Type string `json:"type"` 183 // Key: The name of the Counter or List. If not found on the GameServer, has no impact. 184 Key string `json:"key"` 185 // Order: Sort by "Ascending" or "Descending". "Descending" a bigger available capacity is preferred. 186 // "Ascending" would be smaller available capacity is preferred. 187 // The default sort order is "Ascending" 188 Order string `json:"order"` 189 } 190 191 // SafeAdd prevents overflow by limiting the sum to math.MaxInt64. 192 func SafeAdd(x, y int64) int64 { 193 if x > math.MaxInt64-y { 194 return math.MaxInt64 195 } 196 return x + y 197 }