agones.dev/agones@v1.53.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 u, 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 } else if u.Scheme == "https" && w.CABundle == nil { 438 allErrs = append(allErrs, field.Invalid(fldPath.Child("caBundle"), w.CABundle, "CABundle should be provided if HTTPS webhook is used")) 439 } 440 441 } 442 return allErrs 443 } 444 445 // ValidateBufferPolicy validates the FleetAutoscaler Buffer policy settings 446 func (b *BufferPolicy) ValidateBufferPolicy(fldPath *field.Path) field.ErrorList { 447 var allErrs field.ErrorList 448 if b == nil { 449 return append(allErrs, field.Required(fldPath, "buffer policy config params are missing")) 450 } 451 if b.MinReplicas > b.MaxReplicas { 452 allErrs = append(allErrs, field.Invalid(fldPath.Child("minReplicas"), b.MinReplicas, "minReplicas should be smaller than maxReplicas")) 453 } 454 if b.BufferSize.Type == intstr.Int { 455 if b.BufferSize.IntValue() <= 0 { 456 allErrs = append(allErrs, field.Invalid(fldPath.Child("bufferSize"), b.BufferSize.IntValue(), apimachineryvalidation.IsNegativeErrorMsg)) 457 } 458 if b.MaxReplicas < int32(b.BufferSize.IntValue()) { 459 allErrs = append(allErrs, field.Invalid(fldPath.Child("maxReplicas"), b.MaxReplicas, "maxReplicas should be bigger than or equal to bufferSize")) 460 } 461 if b.MinReplicas != 0 && b.MinReplicas < int32(b.BufferSize.IntValue()) { 462 allErrs = append(allErrs, field.Invalid(fldPath.Child("minReplicas"), b.MinReplicas, "minReplicas should be bigger than or equal to bufferSize")) 463 } 464 } else { 465 r, err := intstr.GetScaledValueFromIntOrPercent(&b.BufferSize, 100, true) 466 if err != nil || r < 1 || r > 99 { 467 allErrs = append(allErrs, field.Invalid(fldPath.Child("bufferSize"), b.BufferSize.String(), "bufferSize should be between 1% and 99%")) 468 } 469 // When there is no allocated gameservers in a fleet, 470 // Fleetautoscaler would reduce size of a fleet to MinReplicas. 471 // If we have 0 MinReplicas and 0 Allocated then Fleetautoscaler would set Ready Replicas to 0 472 // and we will not be able to raise the number of GS in a Fleet above zero 473 if b.MinReplicas < 1 { 474 allErrs = append(allErrs, field.Invalid(fldPath.Child("minReplicas"), b.MinReplicas, apimachineryvalidation.IsNegativeErrorMsg)) 475 } 476 } 477 return allErrs 478 } 479 480 // ValidateCounterPolicy validates the FleetAutoscaler Counter policy settings. 481 // Does not validate if a Counter with name CounterPolicy.Key is present in the fleet. 482 // nolint:dupl // Linter errors on lines are duplicate of ValidateListPolicy 483 func (c *CounterPolicy) ValidateCounterPolicy(fldPath *field.Path) field.ErrorList { 484 var allErrs field.ErrorList 485 if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { 486 return append(allErrs, field.Forbidden(fldPath, "feature CountsAndLists must be enabled")) 487 } 488 489 if c == nil { 490 return append(allErrs, field.Required(fldPath, "counter policy config params are missing")) 491 } 492 493 if c.MinCapacity > c.MaxCapacity { 494 allErrs = append(allErrs, field.Invalid(fldPath.Child("minCapacity"), c.MinCapacity, "minCapacity should be smaller than maxCapacity")) 495 } 496 497 if c.BufferSize.Type == intstr.Int { 498 if c.BufferSize.IntValue() <= 0 { 499 allErrs = append(allErrs, field.Invalid(fldPath.Child("bufferSize"), c.BufferSize.IntValue(), apimachineryvalidation.IsNegativeErrorMsg)) 500 } 501 if c.MaxCapacity < int64(c.BufferSize.IntValue()) { 502 allErrs = append(allErrs, field.Invalid(fldPath.Child("maxCapacity"), c.MaxCapacity, "maxCapacity should be bigger than or equal to bufferSize")) 503 } 504 if c.MinCapacity != 0 && c.MinCapacity < int64(c.BufferSize.IntValue()) { 505 allErrs = append(allErrs, field.Invalid(fldPath.Child("minCapacity"), c.MinCapacity, "minCapacity should be bigger than or equal to bufferSize")) 506 } 507 } else { 508 r, err := intstr.GetScaledValueFromIntOrPercent(&c.BufferSize, 100, true) 509 if err != nil || r < 1 || r > 99 { 510 allErrs = append(allErrs, field.Invalid(fldPath.Child("bufferSize"), c.BufferSize.String(), "bufferSize should be between 1% and 99%")) 511 } 512 // When bufferSize in percentage format is used, minCapacity should be more than 0. 513 if c.MinCapacity < 1 { 514 allErrs = append(allErrs, field.Invalid(fldPath.Child("minCapacity"), c.BufferSize.String(), " when bufferSize in percentage format is used, minCapacity should be more than 0")) 515 } 516 } 517 518 return allErrs 519 } 520 521 // ValidateListPolicy validates the FleetAutoscaler List policy settings. 522 // Does not validate if a List with name ListPolicy.Key is present in the fleet. 523 // nolint:dupl // Linter errors on lines are duplicate of ValidateCounterPolicy 524 func (l *ListPolicy) ValidateListPolicy(fldPath *field.Path) field.ErrorList { 525 var allErrs field.ErrorList 526 if !runtime.FeatureEnabled(runtime.FeatureCountsAndLists) { 527 return append(allErrs, field.Forbidden(fldPath, "feature CountsAndLists must be enabled")) 528 } 529 if l == nil { 530 return append(allErrs, field.Required(fldPath, "list policy config params are missing")) 531 } 532 if l.MinCapacity > l.MaxCapacity { 533 allErrs = append(allErrs, field.Invalid(fldPath.Child("minCapacity"), l.MinCapacity, "minCapacity should be smaller than maxCapacity")) 534 } 535 if l.BufferSize.Type == intstr.Int { 536 if l.BufferSize.IntValue() <= 0 { 537 allErrs = append(allErrs, field.Invalid(fldPath.Child("bufferSize"), l.BufferSize.IntValue(), apimachineryvalidation.IsNegativeErrorMsg)) 538 } 539 if l.MaxCapacity < int64(l.BufferSize.IntValue()) { 540 allErrs = append(allErrs, field.Invalid(fldPath.Child("maxCapacity"), l.MaxCapacity, "maxCapacity should be bigger than or equal to bufferSize")) 541 } 542 if l.MinCapacity != 0 && l.MinCapacity < int64(l.BufferSize.IntValue()) { 543 allErrs = append(allErrs, field.Invalid(fldPath.Child("minCapacity"), l.MinCapacity, "minCapacity should be bigger than or equal to bufferSize")) 544 } 545 } else { 546 r, err := intstr.GetScaledValueFromIntOrPercent(&l.BufferSize, 100, true) 547 if err != nil || r < 1 || r > 99 { 548 allErrs = append(allErrs, field.Invalid(fldPath.Child("bufferSize"), l.BufferSize.String(), "bufferSize should be between 1% and 99%")) 549 } 550 // When bufferSize in percentage format is used, minCapacity should be more than 0. 551 if l.MinCapacity < 1 { 552 allErrs = append(allErrs, field.Invalid(fldPath.Child("minCapacity"), l.BufferSize.String(), " when bufferSize in percentage format is used, minCapacity should be more than 0")) 553 } 554 } 555 return allErrs 556 } 557 558 // ValidateSchedulePolicy validates the FleetAutoscaler Schedule policy settings. 559 func (s *SchedulePolicy) ValidateSchedulePolicy(fldPath *field.Path) field.ErrorList { 560 var allErrs field.ErrorList 561 if s == nil { 562 return append(allErrs, field.Required(fldPath, "schedule policy config params are missing")) 563 } 564 if !runtime.FeatureEnabled(runtime.FeatureScheduledAutoscaler) { 565 return append(allErrs, field.Forbidden(fldPath, "feature ScheduledAutoscaler must be enabled")) 566 } 567 if s.ActivePeriod.Timezone != "" { 568 if _, err := time.LoadLocation(s.ActivePeriod.Timezone); err != nil { 569 allErrs = append(allErrs, field.Invalid(fldPath.Child("activePeriod").Child("timezone"), s.ActivePeriod.Timezone, fmt.Sprintf("Error parsing timezone: %s\n", err))) 570 } 571 } 572 if !s.Between.End.IsZero() { 573 startTime := s.Between.Start.Time 574 endTime := s.Between.End.Time 575 var endErr error 576 if endTime.Before(time.Now()) { 577 allErrs = append(allErrs, field.Invalid(fldPath.Child("between").Child("end"), s.Between.End, "end time must be after the current time")) 578 endErr = errors.New("end time before current time") 579 } 580 581 if !startTime.IsZero() && endErr == nil { 582 if endTime.Before(startTime) { 583 allErrs = append(allErrs, field.Invalid(fldPath.Child("between"), s.Between, "start time must be before end time")) 584 } 585 } 586 } 587 if s.ActivePeriod.StartCron != "" { 588 // If startCron is not a valid cron expression, append an error 589 if _, err := cron.ParseStandard(s.ActivePeriod.StartCron); err != nil { 590 allErrs = append(allErrs, field.Invalid(fldPath.Child("activePeriod").Child("startCron"), s.ActivePeriod.StartCron, fmt.Sprintf("invalid startCron: %s", err))) 591 } 592 // If the cron expression contains a CRON_TZ or TZ specification, append an error 593 if strings.Contains(s.ActivePeriod.StartCron, "TZ") { 594 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")) 595 } 596 } 597 if s.ActivePeriod.Duration != "" { 598 if _, err := time.ParseDuration(s.ActivePeriod.Duration); err != nil { 599 allErrs = append(allErrs, field.Invalid(fldPath.Child("activePeriod").Child("duration"), s.ActivePeriod.StartCron, fmt.Sprintf("invalid duration: %s", err))) 600 } 601 } 602 return allErrs 603 } 604 605 // ValidateChainPolicy validates the FleetAutoscaler Chain policy settings. 606 func (c *ChainPolicy) ValidateChainPolicy(fldPath *field.Path) field.ErrorList { 607 var allErrs field.ErrorList 608 if c == nil { 609 return append(allErrs, field.Required(fldPath, "chain policy config params are missing")) 610 } 611 if !runtime.FeatureEnabled(runtime.FeatureScheduledAutoscaler) { 612 return append(allErrs, field.Forbidden(fldPath, "feature ScheduledAutoscaler must be enabled")) 613 } 614 seenIDs := make(map[string]bool) 615 for i, entry := range *c { 616 617 // Ensure that all IDs are unique 618 if seenIDs[entry.ID] { 619 allErrs = append(allErrs, field.Invalid(fldPath, entry.ID, "id of chain entry must be unique")) 620 } else { 621 seenIDs[entry.ID] = true 622 } 623 // Ensure that chain entry has a policy 624 hasValidPolicy := entry.Buffer != nil || entry.Webhook != nil || entry.Counter != nil || entry.List != nil || entry.Schedule != nil || entry.Wasm != nil 625 if entry.Type == "" || !hasValidPolicy { 626 allErrs = append(allErrs, field.Required(fldPath.Index(i), "valid policy is missing")) 627 } 628 // Validate the chain entry's policy 629 allErrs = append(allErrs, entry.FleetAutoscalerPolicy.ValidatePolicy(fldPath.Index(i).Child("policy"))...) 630 } 631 return allErrs 632 } 633 634 // ValidateWasmPolicy validates the FleetAutoscaler Wasm policy settings 635 func (w *WasmPolicy) ValidateWasmPolicy(fldPath *field.Path) field.ErrorList { 636 var allErrs field.ErrorList 637 if w == nil { 638 return append(allErrs, field.Required(fldPath, "wasm policy config params are missing")) 639 } 640 641 if !runtime.FeatureEnabled(runtime.FeatureWasmAutoscaler) { 642 return append(allErrs, field.Forbidden(fldPath, "feature WasmAutoscaler must be enabled")) 643 } 644 645 fldPath = fldPath.Child("from") 646 if w.From.URL == nil { 647 return append(allErrs, field.Required(fldPath, "wasm from configuration is missing")) 648 } 649 allErrs = w.From.URL.ValidateURLConfiguration(fldPath.Child("url")) 650 651 return allErrs 652 } 653 654 // ValidateFixedIntervalSync validates the FixedIntervalSync settings 655 func (i *FixedIntervalSync) ValidateFixedIntervalSync(fldPath *field.Path) field.ErrorList { 656 var allErrs field.ErrorList 657 if i == nil { 658 return append(allErrs, field.Required(fldPath, "fixedInterval sync config params are missing")) 659 } 660 if i.Seconds <= 0 { 661 allErrs = append(allErrs, field.Invalid(fldPath.Child("seconds"), i.Seconds, apimachineryvalidation.IsNegativeErrorMsg)) 662 } 663 return allErrs 664 } 665 666 // ApplyDefaults applies default values to the FleetAutoscaler 667 func (fas *FleetAutoscaler) ApplyDefaults() { 668 if fas.Spec.Sync == nil { 669 fas.Spec.Sync = &FleetAutoscalerSync{} 670 } 671 if fas.Spec.Sync.Type == "" { 672 fas.Spec.Sync.Type = FixedIntervalSyncType 673 } 674 if fas.Spec.Sync.FixedInterval.Seconds == 0 { 675 fas.Spec.Sync.FixedInterval.Seconds = defaultIntervalSyncSeconds 676 } 677 }