agones.dev/agones@v1.54.0/pkg/apis/autoscaling/v1/fleetautoscaler.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 "crypto/x509" 19 "errors" 20 "fmt" 21 "net/url" 22 "strings" 23 "time" 24 25 agonesv1 "agones.dev/agones/pkg/apis/agones/v1" 26 "agones.dev/agones/pkg/util/runtime" 27 "github.com/robfig/cron/v3" 28 admregv1 "k8s.io/api/admissionregistration/v1" 29 apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/types" 32 "k8s.io/apimachinery/pkg/util/intstr" 33 "k8s.io/apimachinery/pkg/util/validation/field" 34 ) 35 36 // +genclient 37 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 38 39 // FleetAutoscaler is the data structure for a FleetAutoscaler resource 40 type FleetAutoscaler struct { 41 metav1.TypeMeta `json:",inline"` 42 metav1.ObjectMeta `json:"metadata,omitempty"` 43 44 Spec FleetAutoscalerSpec `json:"spec"` 45 Status FleetAutoscalerStatus `json:"status"` 46 } 47 48 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 49 50 // FleetAutoscalerList is a list of Fleet Scaler resources 51 type FleetAutoscalerList struct { 52 metav1.TypeMeta `json:",inline"` 53 metav1.ListMeta `json:"metadata,omitempty"` 54 55 Items []FleetAutoscaler `json:"items"` 56 } 57 58 // FleetAutoscalerSpec is the spec for a Fleet Scaler 59 type FleetAutoscalerSpec struct { 60 FleetName string `json:"fleetName"` 61 62 // Autoscaling policy 63 Policy FleetAutoscalerPolicy `json:"policy"` 64 // Sync defines when FleetAutoscalers runs autoscaling 65 // +optional 66 Sync *FleetAutoscalerSync `json:"sync,omitempty"` 67 } 68 69 // FleetAutoscalerPolicy describes how to scale a fleet 70 type FleetAutoscalerPolicy struct { 71 // Type of autoscaling policy. 72 Type FleetAutoscalerPolicyType `json:"type"` 73 74 // Buffer policy config params. Present only if FleetAutoscalerPolicyType = Buffer. 75 // +optional 76 Buffer *BufferPolicy `json:"buffer,omitempty"` 77 // Webhook policy config params. Present only if FleetAutoscalerPolicyType = Webhook. 78 // +optional 79 Webhook *URLConfiguration `json:"webhook,omitempty"` 80 // [Stage:Beta] 81 // [FeatureFlag:CountsAndLists] 82 // Counter policy config params. Present only if FleetAutoscalerPolicyType = Counter. 83 // +optional 84 Counter *CounterPolicy `json:"counter,omitempty"` 85 // [Stage:Beta] 86 // [FeatureFlag:CountsAndLists] 87 // List policy config params. Present only if FleetAutoscalerPolicyType = List. 88 // +optional 89 List *ListPolicy `json:"list,omitempty"` 90 // [Stage:Beta] 91 // [FeatureFlag:ScheduledAutoscaler] 92 // Schedule policy config params. Present only if FleetAutoscalerPolicyType = Schedule. 93 // +optional 94 Schedule *SchedulePolicy `json:"schedule,omitempty"` 95 // [Stage:Beta] 96 // [FeatureFlag:ScheduledAutoscaler] 97 // Chain policy config params. Present only if FleetAutoscalerPolicyType = Chain. 98 // +optional 99 Chain ChainPolicy `json:"chain,omitempty"` 100 // Wasm policy config params. Present only if FleetAutoscalerPolicyType = Wasm. 101 // +optional 102 Wasm *WasmPolicy `json:"wasm,omitempty"` 103 } 104 105 // FleetAutoscalerPolicyType is the policy for autoscaling 106 // for a given Fleet 107 type FleetAutoscalerPolicyType string 108 109 // FleetAutoscalerSync describes when to sync a fleet 110 type FleetAutoscalerSync struct { 111 // Type of autoscaling sync. 112 Type FleetAutoscalerSyncType `json:"type"` 113 114 // FixedInterval config params. Present only if FleetAutoscalerSyncType = FixedInterval. 115 // +optional 116 FixedInterval FixedIntervalSync `json:"fixedInterval"` 117 } 118 119 // FleetAutoscalerSyncType is the sync strategy for a given Fleet 120 type FleetAutoscalerSyncType string 121 122 const ( 123 // BufferPolicyType FleetAutoscalerPolicyType is a simple buffering strategy for Ready 124 // GameServers 125 BufferPolicyType FleetAutoscalerPolicyType = "Buffer" 126 // WebhookPolicyType is a simple webhook strategy used for horizontal fleet scaling 127 // GameServers 128 WebhookPolicyType FleetAutoscalerPolicyType = "Webhook" 129 // [Stage:Beta] 130 // [FeatureFlag:CountsAndLists] 131 // CounterPolicyType is for Counter based fleet autoscaling 132 // nolint:revive // Linter contains comment doesn't start with CounterPolicyType 133 CounterPolicyType FleetAutoscalerPolicyType = "Counter" 134 // [Stage:Beta] 135 // [FeatureFlag:CountsAndLists] 136 // ListPolicyType is for List based fleet autoscaling 137 // nolint:revive // Linter contains comment doesn't start with ListPolicyType 138 ListPolicyType FleetAutoscalerPolicyType = "List" 139 // [Stage:Beta] 140 // [FeatureFlag:ScheduledAutoscaler] 141 // SchedulePolicyType is for Schedule based fleet autoscaling 142 // nolint:revive // Linter contains comment doesn't start with SchedulePolicyType 143 SchedulePolicyType FleetAutoscalerPolicyType = "Schedule" 144 // [Stage:Beta] 145 // [FeatureFlag:ScheduledAutoscaler] 146 // ChainPolicyType is for Chain based fleet autoscaling 147 // nolint:revive // Linter contains comment doesn't start with ChainPolicyType 148 ChainPolicyType FleetAutoscalerPolicyType = "Chain" 149 // WasmPolicyType is for WebAssembly based fleet autoscaling 150 // [Stage:Alpha] 151 // [FeatureFlag:WasmAutoscaler] 152 WasmPolicyType FleetAutoscalerPolicyType = "Wasm" 153 // FixedIntervalSyncType is a simple fixed interval based strategy for trigger autoscaling 154 FixedIntervalSyncType FleetAutoscalerSyncType = "FixedInterval" 155 156 defaultIntervalSyncSeconds int32 = 30 157 ) 158 159 // BufferPolicy controls the desired behavior of the buffer policy. 160 type BufferPolicy struct { 161 // MaxReplicas is the maximum amount of replicas that the fleet may have. 162 // It must be bigger than both MinReplicas and BufferSize 163 MaxReplicas int32 `json:"maxReplicas"` 164 165 // MinReplicas is the minimum amount of replicas that the fleet must have 166 // If zero, it is ignored. 167 // If non zero, it must be smaller than MaxReplicas and bigger than BufferSize 168 MinReplicas int32 `json:"minReplicas"` 169 170 // BufferSize defines how many replicas the autoscaler tries to have ready all the time 171 // Value can be an absolute number (ex: 5) or a percentage of desired gs instances (ex: 15%) 172 // Absolute number is calculated from percentage by rounding up. 173 // Example: when this is set to 20%, the autoscaler will make sure that 20% 174 // of the fleet's game server replicas are ready. When this is set to 20, 175 // the autoscaler will make sure that there are 20 available game servers 176 // Must be bigger than 0 177 // Note: by "ready" we understand in this case "non-allocated"; this is done to ensure robustness 178 // and computation stability in different edge case (fleet just created, not enough 179 // capacity in the cluster etc) 180 BufferSize intstr.IntOrString `json:"bufferSize"` 181 } 182 183 // URLConfiguration is a generic configuration for any integration with an external URL or 184 // cluster provided service. For example, it can be used to configure a webhook or Wasm module 185 type URLConfiguration admregv1.WebhookClientConfig 186 187 // CounterPolicy controls the desired behavior of the Counter autoscaler policy. 188 type CounterPolicy struct { 189 // Key is the name of the Counter. Required field. 190 Key string `json:"key"` 191 192 // MaxCapacity is the maximum aggregate Counter total capacity across the fleet. 193 // MaxCapacity must be bigger than both MinCapacity and BufferSize. Required field. 194 MaxCapacity int64 `json:"maxCapacity"` 195 196 // MinCapacity is the minimum aggregate Counter total capacity across the fleet. 197 // If zero, MinCapacity is ignored. 198 // If non zero, MinCapacity must be smaller than MaxCapacity and bigger than BufferSize. 199 MinCapacity int64 `json:"minCapacity"` 200 201 // BufferSize is the size of a buffer of counted items that are available in the Fleet (available 202 // capacity). Value can be an absolute number (ex: 5) or a percentage of desired gs instances 203 // (ex: 5%). An absolute number is calculated from percentage by rounding up. 204 // Must be bigger than 0. Required field. 205 BufferSize intstr.IntOrString `json:"bufferSize"` 206 } 207 208 // ListPolicy controls the desired behavior of the List autoscaler policy. 209 type ListPolicy struct { 210 // Key is the name of the List. Required field. 211 Key string `json:"key"` 212 213 // MaxCapacity is the maximum aggregate List total capacity across the fleet. 214 // MaxCapacity must be bigger than both MinCapacity and BufferSize. Required field. 215 MaxCapacity int64 `json:"maxCapacity"` 216 217 // MinCapacity is the minimum aggregate List total capacity across the fleet. 218 // If zero, it is ignored. 219 // If non zero, it must be smaller than MaxCapacity and bigger than BufferSize. 220 MinCapacity int64 `json:"minCapacity"` 221 222 // BufferSize is the size of a buffer based on the List capacity that is available over the 223 // current aggregate List length in the Fleet (available capacity). It can be specified either 224 // as an absolute value (i.e. 5) or percentage format (i.e. 5%). 225 // Must be bigger than 0. Required field. 226 BufferSize intstr.IntOrString `json:"bufferSize"` 227 } 228 229 // Between defines the time period that the policy is eligible to be applied. 230 type Between struct { 231 // Start is the datetime that the policy is eligible to be applied. 232 // This field must conform to RFC3339 format. If this field not set or is in the past, the policy is eligible to be applied 233 // as soon as the fleet autoscaler is running. 234 Start metav1.Time `json:"start"` 235 236 // End is the datetime that the policy is no longer eligible to be applied. 237 // This field must conform to RFC3339 format. If not set, the policy is always eligible to be applied, after the start time above. 238 End metav1.Time `json:"end"` 239 } 240 241 // ActivePeriod defines the time period that the policy is applied. 242 type ActivePeriod struct { 243 // Timezone to be used for the startCron field. If not set, startCron is defaulted to the UTC timezone. 244 Timezone string `json:"timezone"` 245 246 // StartCron defines when the policy should be applied. 247 // If not set, the policy is always to be applied within the start and end time. 248 // This field must conform to UNIX cron syntax. 249 StartCron string `json:"startCron"` 250 251 // Duration is the length of time that the policy is applied. 252 // If not set, the duration is indefinite. 253 // A duration string is a possibly signed sequence of decimal numbers, 254 // (e.g. "300ms", "-1.5h" or "2h45m"). 255 // The representation limits the largest representable duration to approximately 290 years. 256 // Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h". 257 Duration string `json:"duration"` 258 } 259 260 // SchedulePolicy controls the desired behavior of the Schedule autoscaler policy. 261 type SchedulePolicy struct { 262 // Between defines the time period that the policy is eligible to be applied. 263 Between Between `json:"between"` 264 265 // ActivePeriod defines the time period that the policy is applied. 266 ActivePeriod ActivePeriod `json:"activePeriod"` 267 268 // Policy is the name of the policy to be applied. Required field. 269 Policy FleetAutoscalerPolicy `json:"policy"` 270 } 271 272 // ChainEntry defines a single entry in the ChainPolicy. 273 type ChainEntry struct { 274 // ID is the unique identifier for a ChainEntry. If not set the identifier will be set to the index of chain entry. 275 ID string `json:"id"` 276 277 // Policy is the name of the policy to be applied. Required field. 278 FleetAutoscalerPolicy `json:",inline"` 279 } 280 281 // ChainPolicy controls the desired behavior of the Chain autoscaler policy. 282 type ChainPolicy []ChainEntry 283 284 // WasmFrom defines the source of the Wasm module 285 type WasmFrom struct { 286 // URL is the URL of the Wasm module to use for autoscaling. 287 URL *URLConfiguration `json:"url"` 288 } 289 290 // WasmPolicy controls the desired behavior of the Wasm policy. 291 // It contains the configuration for the WebAssembly module used for autoscaling. 292 type WasmPolicy struct { 293 // Function is the exported function to call in the wasm module, defaults to 'scale' 294 // +optional 295 Function string `json:"function,omitempty"` 296 // Config values to pass to the wasm program on startup 297 // +optional 298 Config map[string]string `json:"config,omitempty"` 299 // WasmFrom defines the source of the Wasm module 300 From WasmFrom `json:"from"` 301 // Hash of the Wasm module, used to verify the integrity of the module 302 // +optional 303 Hash string `json:"hash,omitempty"` 304 } 305 306 // FixedIntervalSync controls the desired behavior of the fixed interval based sync. 307 type FixedIntervalSync struct { 308 // Seconds defines how often we run fleet autoscaling in seconds 309 Seconds int32 `json:"seconds"` 310 } 311 312 // FleetAutoscalerStatus defines the current status of a FleetAutoscaler 313 type FleetAutoscalerStatus struct { 314 // CurrentReplicas is the current number of gameserver replicas 315 // of the fleet managed by this autoscaler, as last seen by the autoscaler 316 CurrentReplicas int32 `json:"currentReplicas"` 317 318 // DesiredReplicas is the desired number of gameserver replicas 319 // of the fleet managed by this autoscaler, as last calculated by the autoscaler 320 DesiredReplicas int32 `json:"desiredReplicas"` 321 322 // lastScaleTime is the last time the FleetAutoscaler scaled the attached fleet, 323 // +optional 324 LastScaleTime *metav1.Time `json:"lastScaleTime"` 325 326 // AbleToScale indicates that we can access the target fleet 327 AbleToScale bool `json:"ableToScale"` 328 329 // ScalingLimited indicates that the calculated scale would be above or below the range 330 // defined by MinReplicas and MaxReplicas, and has thus been capped. 331 ScalingLimited bool `json:"scalingLimited"` 332 333 // LastAppliedPolicy is the ID of the last applied policy in the ChainPolicy. 334 // Used to track policy transitions for logging purposes. 335 LastAppliedPolicy FleetAutoscalerPolicyType `json:"lastAppliedPolicy"` 336 } 337 338 // FleetAutoscaleRequest defines the request to webhook autoscaler endpoint 339 type FleetAutoscaleRequest struct { 340 // UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are 341 // otherwise identical (parallel requests, requests when earlier requests did not modify etc) 342 // The UID is meant to track the round trip (request/response) between the Autoscaler and the WebHook, not the user request. 343 // It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging. 344 UID types.UID `json:"uid"` 345 // Name is the name of the Fleet being scaled 346 Name string `json:"name"` 347 // Namespace is the namespace associated with the request (if any). 348 Namespace string `json:"namespace"` 349 // The Fleet's status values 350 Status agonesv1.FleetStatus `json:"status"` 351 // Standard map labels; More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/labels. 352 Labels map[string]string `json:"labels,omitempty"` 353 // Standard map annotations; More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/annotations. 354 Annotations map[string]string `json:"annotations,omitempty"` 355 } 356 357 // FleetAutoscaleResponse defines the response of webhook autoscaler endpoint 358 type FleetAutoscaleResponse struct { 359 // UID is an identifier for the individual request/response. 360 // This should be copied over from the corresponding FleetAutoscaleRequest. 361 UID types.UID `json:"uid"` 362 // Set to false if no scaling should occur to the Fleet 363 Scale bool `json:"scale"` 364 // The targeted replica count 365 Replicas int32 `json:"replicas"` 366 } 367 368 // FleetAutoscaleReview is passed to the webhook with a populated Request value, 369 // and then returned with a populated Response. 370 type FleetAutoscaleReview struct { 371 Request *FleetAutoscaleRequest `json:"request"` 372 Response *FleetAutoscaleResponse `json:"response"` 373 } 374 375 // Validate validates the FleetAutoscaler scaling settings 376 func (fas *FleetAutoscaler) Validate() field.ErrorList { 377 allErrs := fas.Spec.Policy.ValidatePolicy(field.NewPath("spec", "policy")) 378 379 if fas.Spec.Sync != nil { 380 allErrs = append(allErrs, fas.Spec.Sync.FixedInterval.ValidateFixedIntervalSync(field.NewPath("spec", "sync", "fixedInterval"))...) 381 } 382 return allErrs 383 } 384 385 // ValidatePolicy validates a FleetAutoscalerPolicy's settings. 386 func (f *FleetAutoscalerPolicy) ValidatePolicy(fldPath *field.Path) field.ErrorList { 387 var allErrs field.ErrorList 388 switch f.Type { 389 case BufferPolicyType: 390 allErrs = f.Buffer.ValidateBufferPolicy(fldPath.Child("buffer")) 391 392 case WebhookPolicyType: 393 allErrs = f.Webhook.ValidateURLConfiguration(fldPath.Child("webhook")) 394 395 case CounterPolicyType: 396 allErrs = f.Counter.ValidateCounterPolicy(fldPath.Child("counter")) 397 398 case ListPolicyType: 399 allErrs = f.List.ValidateListPolicy(fldPath.Child("list")) 400 401 case SchedulePolicyType: 402 allErrs = f.Schedule.ValidateSchedulePolicy(fldPath.Child("schedule")) 403 404 case ChainPolicyType: 405 allErrs = f.Chain.ValidateChainPolicy(fldPath.Child("chain")) 406 407 case WasmPolicyType: 408 allErrs = f.Wasm.ValidateWasmPolicy(fldPath.Child("wasm")) 409 } 410 return allErrs 411 } 412 413 // ValidateURLConfiguration validates the URLConfiguration settings to make sure either a URL is set or 414 // a Service is set, but not both. Also checks that the CABundle is valid if HTTPS is used. 415 func (w *URLConfiguration) ValidateURLConfiguration(fldPath *field.Path) field.ErrorList { 416 var allErrs field.ErrorList 417 if w == nil { 418 return append(allErrs, field.Required(fldPath, "webhook policy config params are missing")) 419 } 420 if w.Service == nil && w.URL == nil { 421 allErrs = append(allErrs, field.Required(fldPath, "url should be provided")) 422 } 423 if w.Service != nil && w.URL != nil { 424 allErrs = append(allErrs, field.Duplicate(fldPath.Child("url"), "service and url cannot be used simultaneously")) 425 } 426 if w.CABundle != nil { 427 rootCAs := x509.NewCertPool() 428 // Check that CABundle provided is correctly encoded certificate 429 if ok := rootCAs.AppendCertsFromPEM(w.CABundle); !ok { 430 allErrs = append(allErrs, field.Invalid(fldPath.Child("caBundle"), w.CABundle, "CA Bundle is not valid")) 431 } 432 } 433 if w.URL != nil { 434 _, err := url.Parse(*w.URL) 435 if err != nil { 436 allErrs = append(allErrs, field.Invalid(fldPath.Child("url"), *w.URL, "url is not valid")) 437 } 438 } 439 return allErrs 440 } 441 442 // ValidateBufferPolicy validates the FleetAutoscaler Buffer policy settings 443 func (b *BufferPolicy) ValidateBufferPolicy(fldPath *field.Path) field.ErrorList { 444 var allErrs field.ErrorList 445 if b == nil { 446 return append(allErrs, field.Required(fldPath, "buffer policy config params are missing")) 447 } 448 if b.MinReplicas > b.MaxReplicas { 449 allErrs = append(allErrs, field.Invalid(fldPath.Child("minReplicas"), b.MinReplicas, "minReplicas should be smaller than maxReplicas")) 450 } 451 if b.BufferSize.Type == intstr.Int { 452 if b.BufferSize.IntValue() <= 0 { 453 allErrs = append(allErrs, field.Invalid(fldPath.Child("bufferSize"), b.BufferSize.IntValue(), apimachineryvalidation.IsNegativeErrorMsg)) 454 } 455 if b.MaxReplicas < int32(b.BufferSize.IntValue()) { 456 allErrs = append(allErrs, field.Invalid(fldPath.Child("maxReplicas"), b.MaxReplicas, "maxReplicas should be bigger than or equal to bufferSize")) 457 } 458 if b.MinReplicas != 0 && b.MinReplicas < int32(b.BufferSize.IntValue()) { 459 allErrs = append(allErrs, field.Invalid(fldPath.Child("minReplicas"), b.MinReplicas, "minReplicas should be bigger than or equal to bufferSize")) 460 } 461 } else { 462 r, err := intstr.GetScaledValueFromIntOrPercent(&b.BufferSize, 100, true) 463 if err != nil || r < 1 || r > 99 { 464 allErrs = append(allErrs, field.Invalid(fldPath.Child("bufferSize"), b.BufferSize.String(), "bufferSize should be between 1% and 99%")) 465 } 466 // When there is no allocated gameservers in a fleet, 467 // Fleetautoscaler would reduce size of a fleet to MinReplicas. 468 // If we have 0 MinReplicas and 0 Allocated then Fleetautoscaler would set Ready Replicas to 0 469 // and we will not be able to raise the number of GS in a Fleet above zero 470 if b.MinReplicas < 1 { 471 allErrs = append(allErrs, field.Invalid(fldPath.Child("minReplicas"), b.MinReplicas, apimachineryvalidation.IsNegativeErrorMsg)) 472 } 473 } 474 return allErrs 475 } 476 477 // ValidateCounterPolicy validates the FleetAutoscaler Counter policy settings. 478 // Does not validate if a Counter with name CounterPolicy.Key is present in the fleet. 479 // nolint:dupl // Linter errors on lines are duplicate of ValidateListPolicy 480 func (c *CounterPolicy) ValidateCounterPolicy(fldPath *field.Path) field.ErrorList { 481 var allErrs field.ErrorList 482 if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { 483 return append(allErrs, field.Forbidden(fldPath, "feature CountsAndLists must be enabled")) 484 } 485 486 if c == nil { 487 return append(allErrs, field.Required(fldPath, "counter policy config params are missing")) 488 } 489 490 if c.MinCapacity > c.MaxCapacity { 491 allErrs = append(allErrs, field.Invalid(fldPath.Child("minCapacity"), c.MinCapacity, "minCapacity should be smaller than maxCapacity")) 492 } 493 494 if c.BufferSize.Type == intstr.Int { 495 if c.BufferSize.IntValue() <= 0 { 496 allErrs = append(allErrs, field.Invalid(fldPath.Child("bufferSize"), c.BufferSize.IntValue(), apimachineryvalidation.IsNegativeErrorMsg)) 497 } 498 if c.MaxCapacity < int64(c.BufferSize.IntValue()) { 499 allErrs = append(allErrs, field.Invalid(fldPath.Child("maxCapacity"), c.MaxCapacity, "maxCapacity should be bigger than or equal to bufferSize")) 500 } 501 if c.MinCapacity != 0 && c.MinCapacity < int64(c.BufferSize.IntValue()) { 502 allErrs = append(allErrs, field.Invalid(fldPath.Child("minCapacity"), c.MinCapacity, "minCapacity should be bigger than or equal to bufferSize")) 503 } 504 } else { 505 r, err := intstr.GetScaledValueFromIntOrPercent(&c.BufferSize, 100, true) 506 if err != nil || r < 1 || r > 99 { 507 allErrs = append(allErrs, field.Invalid(fldPath.Child("bufferSize"), c.BufferSize.String(), "bufferSize should be between 1% and 99%")) 508 } 509 // When bufferSize in percentage format is used, minCapacity should be more than 0. 510 if c.MinCapacity < 1 { 511 allErrs = append(allErrs, field.Invalid(fldPath.Child("minCapacity"), c.BufferSize.String(), " when bufferSize in percentage format is used, minCapacity should be more than 0")) 512 } 513 } 514 515 return allErrs 516 } 517 518 // ValidateListPolicy validates the FleetAutoscaler List policy settings. 519 // Does not validate if a List with name ListPolicy.Key is present in the fleet. 520 // nolint:dupl // Linter errors on lines are duplicate of ValidateCounterPolicy 521 func (l *ListPolicy) ValidateListPolicy(fldPath *field.Path) field.ErrorList { 522 var allErrs field.ErrorList 523 if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { 524 return append(allErrs, field.Forbidden(fldPath, "feature CountsAndLists must be enabled")) 525 } 526 if l == nil { 527 return append(allErrs, field.Required(fldPath, "list policy config params are missing")) 528 } 529 if l.MinCapacity > l.MaxCapacity { 530 allErrs = append(allErrs, field.Invalid(fldPath.Child("minCapacity"), l.MinCapacity, "minCapacity should be smaller than maxCapacity")) 531 } 532 if l.BufferSize.Type == intstr.Int { 533 if l.BufferSize.IntValue() <= 0 { 534 allErrs = append(allErrs, field.Invalid(fldPath.Child("bufferSize"), l.BufferSize.IntValue(), apimachineryvalidation.IsNegativeErrorMsg)) 535 } 536 if l.MaxCapacity < int64(l.BufferSize.IntValue()) { 537 allErrs = append(allErrs, field.Invalid(fldPath.Child("maxCapacity"), l.MaxCapacity, "maxCapacity should be bigger than or equal to bufferSize")) 538 } 539 if l.MinCapacity != 0 && l.MinCapacity < int64(l.BufferSize.IntValue()) { 540 allErrs = append(allErrs, field.Invalid(fldPath.Child("minCapacity"), l.MinCapacity, "minCapacity should be bigger than or equal to bufferSize")) 541 } 542 } else { 543 r, err := intstr.GetScaledValueFromIntOrPercent(&l.BufferSize, 100, true) 544 if err != nil || r < 1 || r > 99 { 545 allErrs = append(allErrs, field.Invalid(fldPath.Child("bufferSize"), l.BufferSize.String(), "bufferSize should be between 1% and 99%")) 546 } 547 // When bufferSize in percentage format is used, minCapacity should be more than 0. 548 if l.MinCapacity < 1 { 549 allErrs = append(allErrs, field.Invalid(fldPath.Child("minCapacity"), l.BufferSize.String(), " when bufferSize in percentage format is used, minCapacity should be more than 0")) 550 } 551 } 552 return allErrs 553 } 554 555 // ValidateSchedulePolicy validates the FleetAutoscaler Schedule policy settings. 556 func (s *SchedulePolicy) ValidateSchedulePolicy(fldPath *field.Path) field.ErrorList { 557 var allErrs field.ErrorList 558 if s == nil { 559 return append(allErrs, field.Required(fldPath, "schedule policy config params are missing")) 560 } 561 if !runtime.FeatureEnabled(runtime.FeatureScheduledAutoscaler) { 562 return append(allErrs, field.Forbidden(fldPath, "feature ScheduledAutoscaler must be enabled")) 563 } 564 if s.ActivePeriod.Timezone != "" { 565 if _, err := time.LoadLocation(s.ActivePeriod.Timezone); err != nil { 566 allErrs = append(allErrs, field.Invalid(fldPath.Child("activePeriod").Child("timezone"), s.ActivePeriod.Timezone, fmt.Sprintf("Error parsing timezone: %s\n", err))) 567 } 568 } 569 if !s.Between.End.IsZero() { 570 startTime := s.Between.Start.Time 571 endTime := s.Between.End.Time 572 var endErr error 573 if endTime.Before(time.Now()) { 574 allErrs = append(allErrs, field.Invalid(fldPath.Child("between").Child("end"), s.Between.End, "end time must be after the current time")) 575 endErr = errors.New("end time before current time") 576 } 577 578 if !startTime.IsZero() && endErr == nil { 579 if endTime.Before(startTime) { 580 allErrs = append(allErrs, field.Invalid(fldPath.Child("between"), s.Between, "start time must be before end time")) 581 } 582 } 583 } 584 if s.ActivePeriod.StartCron != "" { 585 // If startCron is not a valid cron expression, append an error 586 if _, err := cron.ParseStandard(s.ActivePeriod.StartCron); err != nil { 587 allErrs = append(allErrs, field.Invalid(fldPath.Child("activePeriod").Child("startCron"), s.ActivePeriod.StartCron, fmt.Sprintf("invalid startCron: %s", err))) 588 } 589 // If the cron expression contains a CRON_TZ or TZ specification, append an error 590 if strings.Contains(s.ActivePeriod.StartCron, "TZ") { 591 allErrs = append(allErrs, field.Invalid(fldPath.Child("activePeriod").Child("startCron"), s.ActivePeriod.StartCron, "CRON_TZ or TZ used in activePeriod is not supported, please use the .schedule.timezone field to specify a timezone")) 592 } 593 } 594 if s.ActivePeriod.Duration != "" { 595 if _, err := time.ParseDuration(s.ActivePeriod.Duration); err != nil { 596 allErrs = append(allErrs, field.Invalid(fldPath.Child("activePeriod").Child("duration"), s.ActivePeriod.StartCron, fmt.Sprintf("invalid duration: %s", err))) 597 } 598 } 599 return allErrs 600 } 601 602 // ValidateChainPolicy validates the FleetAutoscaler Chain policy settings. 603 func (c *ChainPolicy) ValidateChainPolicy(fldPath *field.Path) field.ErrorList { 604 var allErrs field.ErrorList 605 if c == nil { 606 return append(allErrs, field.Required(fldPath, "chain policy config params are missing")) 607 } 608 if !runtime.FeatureEnabled(runtime.FeatureScheduledAutoscaler) { 609 return append(allErrs, field.Forbidden(fldPath, "feature ScheduledAutoscaler must be enabled")) 610 } 611 seenIDs := make(map[string]bool) 612 for i, entry := range *c { 613 614 // Ensure that all IDs are unique 615 if seenIDs[entry.ID] { 616 allErrs = append(allErrs, field.Invalid(fldPath, entry.ID, "id of chain entry must be unique")) 617 } else { 618 seenIDs[entry.ID] = true 619 } 620 // Ensure that chain entry has a policy 621 hasValidPolicy := entry.Buffer != nil || entry.Webhook != nil || entry.Counter != nil || entry.List != nil || entry.Schedule != nil || entry.Wasm != nil 622 if entry.Type == "" || !hasValidPolicy { 623 allErrs = append(allErrs, field.Required(fldPath.Index(i), "valid policy is missing")) 624 } 625 // Validate the chain entry's policy 626 allErrs = append(allErrs, entry.FleetAutoscalerPolicy.ValidatePolicy(fldPath.Index(i).Child("policy"))...) 627 } 628 return allErrs 629 } 630 631 // ValidateWasmPolicy validates the FleetAutoscaler Wasm policy settings 632 func (w *WasmPolicy) ValidateWasmPolicy(fldPath *field.Path) field.ErrorList { 633 var allErrs field.ErrorList 634 if w == nil { 635 return append(allErrs, field.Required(fldPath, "wasm policy config params are missing")) 636 } 637 638 if !runtime.FeatureEnabled(runtime.FeatureWasmAutoscaler) { 639 return append(allErrs, field.Forbidden(fldPath, "feature WasmAutoscaler must be enabled")) 640 } 641 642 fldPath = fldPath.Child("from") 643 if w.From.URL == nil { 644 return append(allErrs, field.Required(fldPath, "wasm from configuration is missing")) 645 } 646 allErrs = w.From.URL.ValidateURLConfiguration(fldPath.Child("url")) 647 648 return allErrs 649 } 650 651 // ValidateFixedIntervalSync validates the FixedIntervalSync settings 652 func (i *FixedIntervalSync) ValidateFixedIntervalSync(fldPath *field.Path) field.ErrorList { 653 var allErrs field.ErrorList 654 if i == nil { 655 return append(allErrs, field.Required(fldPath, "fixedInterval sync config params are missing")) 656 } 657 if i.Seconds <= 0 { 658 allErrs = append(allErrs, field.Invalid(fldPath.Child("seconds"), i.Seconds, apimachineryvalidation.IsNegativeErrorMsg)) 659 } 660 return allErrs 661 } 662 663 // ApplyDefaults applies default values to the FleetAutoscaler 664 func (fas *FleetAutoscaler) ApplyDefaults() { 665 if fas.Spec.Sync == nil { 666 fas.Spec.Sync = &FleetAutoscalerSync{} 667 } 668 if fas.Spec.Sync.Type == "" { 669 fas.Spec.Sync.Type = FixedIntervalSyncType 670 } 671 if fas.Spec.Sync.FixedInterval.Seconds == 0 { 672 fas.Spec.Sync.FixedInterval.Seconds = defaultIntervalSyncSeconds 673 } 674 }