Skip to content

Commit

Permalink
Merge pull request #3 from indexjoseph/api-changes
Browse files Browse the repository at this point in the history
add API changes for scheduled autoscalers
  • Loading branch information
indexjoseph committed Jun 23, 2024
2 parents cafb1ad + b73e383 commit 618f76c
Show file tree
Hide file tree
Showing 27 changed files with 3,193 additions and 13 deletions.
104 changes: 104 additions & 0 deletions examples/chainfleetautoscaler.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Copyright 2023 Google LLC All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

#
# [Stage:Alpha]
# [FeatureFlag:ScheduledAutoscaler]
# Example of a FleetAutoscaler - This is used to scale a fleet on a schedule,
# allowing for different policies to be applied at different times.
#

#
# For a full reference and details: https://agones.dev/site/docs/reference/fleetautoscaler/
#
apiVersion: autoscaling.agones.dev/v1
kind: FleetAutoscaler
metadata:
name: chain-fleet-autoscaler
spec:
policy:
type: Chain # Chain based policy for autoscaling.
chain:
# Id of chain entry.
# Optional.
- uid: "weekday"
type: Schedule # Schedule based condition.
schedule:
# Timezone to be used for the schedule.
timezone: "America/New_York"
between:
# The policy becomes eligible for application starting on
# Feb 20, 2024 at 4:04 PM.
# Optional.
start: "2024-02-19 16:04:00"
# The policy becomes ineligible for application on
# Feb 23, 2024 at 4:04 PM.
# Optional.
end: "2024-02-23 16:04:00" # optional
activePeriod:
# Start applying the bufferSize everyday at 1:00 AM
# (Only eligible starting on Feb 20, 2024 at 4:04 PM.)
# Optional.
startCron: "0 1 * * 0"
# Only apply the bufferSize for this 5 hours
# Optional.
duration: "5h"
# Policy to be applied when the condition is met.
# Required.
policy:
type: Buffer
buffer:
bufferSize: 50
minReplicas: 100
maxReplicas: 2000
# Id of chain entry.
# Optional.
- uid: "weekend"
type: Schedule
schedule:
# Timezone to be used for the schedule.
timezone: "America/New_York"
between:
# The policy becomes eligible for application starting on
# Feb 24, 2024 at 4:05 PM.
# Optional.
start: "2024-02-24 16:05:00"
start: "2024-02-26 16:05:00"
activePeriod:
# Start applying the bufferSize everyday at 1:00 AM
# (Only eligible starting on Feb 24, 2024 at 4:05 PM.)
# Optional.
startCron: "0 1 * * 0"
# Only apply the bufferSize for this 7 hours
# Optional.
duration: "7h"
# Policy to be applied when the condition is met.
# Required.
policy:
type: Counter
counter:
key: rooms
bufferSize: 10
minCapacity: 500
maxCapacity: 1000
# Id of chain entry.
- uid: "default"
# Policy will always be applied when no other policy is applicable.
# Required.
policy:
type: Buffer
buffer:
bufferSize: 5
minReplicas: 100
maxReplicas: 2000
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ policy:
required:
- policy
properties:
id: # The id of a chain entry
uid: # The id of a chain entry, the id should be unique within the chain.
type: string
schedule:
type: object
Expand Down
160 changes: 151 additions & 9 deletions pkg/apis/autoscaling/v1/fleetautoscaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ package v1
import (
"crypto/x509"
"net/url"
"time"

agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
"agones.dev/agones/pkg/util/runtime"
"github.com/relvacode/iso8601"
"github.com/robfig/cron/v3"
admregv1 "k8s.io/api/admissionregistration/v1"
apimachineryvalidation "k8s.io/apimachinery/pkg/api/validation"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -82,6 +85,11 @@ type FleetAutoscalerPolicy struct {
// List policy config params. Present only if FleetAutoscalerPolicyType = List.
// +optional
List *ListPolicy `json:"list,omitempty"`
// [Stage:Beta]
// [FeatureFlag:ScheduledAutoscaler]
// Chain policy config params. Present only if FleetAutoscalerPolicyType = Chain.
// +optional
Chain *ChainPolicy `json:"chain,omitempty"`
}

// FleetAutoscalerPolicyType is the policy for autoscaling
Expand Down Expand Up @@ -118,6 +126,11 @@ const (
// ListPolicyType is for List based fleet autoscaling
// nolint:revive // Linter contains comment doesn't start with ListPolicyType
ListPolicyType FleetAutoscalerPolicyType = "List"
// [Stage:Beta]
// [FeatureFlag:ScheduledAutoscaler]
// ChainPolicyType is for Chain based fleet autoscaling
// nolint:revive // Linter contains comment doesn't start with ChainPolicyType
ChainPolicyType FleetAutoscalerPolicyType = "Chain"
// FixedIntervalSyncType is a simple fixed interval based strategy for trigger autoscaling
FixedIntervalSyncType FleetAutoscalerSyncType = "FixedInterval"

Expand Down Expand Up @@ -195,6 +208,77 @@ type ListPolicy struct {
BufferSize intstr.IntOrString `json:"bufferSize"`
}

// Between defines the time period that the policy is eligible to be applied.
type Between struct {
// Start is the datetime that the policy is eligible to be applied.
// If not set, the policy is always eligible to be applied
// as soon as possible. If the datetime is in the past, the policy is
// immediately eligible to be applied as well.
// Optional field.
Start string `json:"start"`

// End is the datetime that the policy is no longer eligible to be applied.
// If not set, the policy is always eligible to be applied.
// after the start time. Optional field.
End string `json:"end"`
}

// ActivePeriod defines the time period that the policy is applied.
type ActivePeriod struct {
// StartCron defines when the policy should be applied.
// If not set, the policy is always eligible to be applied.
// This must conform to UNIX cron syntax.
// Optional field.
StartCron string `json:"startCron"`

// Duration is the length of time that the policy is applied.
// If not set, the duration is indefinite.
// A duration string is a possibly signed sequence of decimal numbers,
// (e.g. "300ms", "-1.5h" or "2h45m").
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
// Optional field.
Duration string `json:"duration"`
}

// Schedule defines when the policy should be applied.
type Schedule struct {
// Timezone is the timezone that the schedule is in.
// If not set, the schedule is in the UTC timezone.
// Optional field.
Timezone string `json:"timezone"`

// Between defines the time period that the policy is eligible to be applied.
// Optional field.
Between Between `json:"between"`

// ActivePeriod defines the time period that the policy is applied.
// Optional field.
ActivePeriod ActivePeriod `json:"activePeriod"`
}

// ChainEntry defines a single entry in the ChainPolicy.
type ChainEntry struct {
// UID is the unique identifier of the ChainEntry.
UID types.UID `json:"uid"`

// Schedule defines when the policy should be applied.
// Optional field.
Schedule Schedule `json:"schedule"`

// Policy is the name of the policy to be applied.
// Required field.
Policy FleetAutoscalerPolicy `json:"policy"`
}

// ChainPolicy controls the desired behavior of the Chain autoscaler policy.
type ChainPolicy struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata,omitempty"`

// Items is a list of ChainEntry objects
Items []ChainEntry `json:"items"`
}

// FixedIntervalSync controls the desired behavior of the fixed interval based sync.
type FixedIntervalSync struct {
// Seconds defines how often we run fleet autoscaling in seconds
Expand Down Expand Up @@ -258,23 +342,29 @@ type FleetAutoscaleReview struct {

// Validate validates the FleetAutoscaler scaling settings
func (fas *FleetAutoscaler) Validate() field.ErrorList {
allErrs := fas.Spec.Policy.ValidatePolicy(field.NewPath("spec", "policy"))

if fas.Spec.Sync != nil {
allErrs = append(allErrs, fas.Spec.Sync.FixedInterval.ValidateFixedIntervalSync(field.NewPath("spec", "sync", "fixedInterval"))...)
}
return allErrs
}

// Validate validates the FleetAutoscalerPolicy settings.
func (f *FleetAutoscalerPolicy) ValidatePolicy(fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
switch fas.Spec.Policy.Type {
switch f.Type {
case BufferPolicyType:
allErrs = fas.Spec.Policy.Buffer.ValidateBufferPolicy(field.NewPath("spec", "policy", "buffer"))
allErrs = f.Buffer.ValidateBufferPolicy(fldPath.Child("buffer"))

case WebhookPolicyType:
allErrs = fas.Spec.Policy.Webhook.ValidateWebhookPolicy(field.NewPath("spec", "policy", "webhook"))
allErrs = f.Webhook.ValidateWebhookPolicy(fldPath.Child("webhook"))

case CounterPolicyType:
allErrs = fas.Spec.Policy.Counter.ValidateCounterPolicy(field.NewPath("spec", "policy", "counter"))
allErrs = f.Counter.ValidateCounterPolicy(fldPath.Child("counter"))

case ListPolicyType:
allErrs = fas.Spec.Policy.List.ValidateListPolicy(field.NewPath("spec", "policy", "list"))
}

if fas.Spec.Sync != nil {
allErrs = append(allErrs, fas.Spec.Sync.FixedInterval.ValidateFixedIntervalSync(field.NewPath("spec", "sync", "fixedInterval"))...)
allErrs = f.List.ValidateListPolicy(fldPath.Child("list"))
}
return allErrs
}
Expand Down Expand Up @@ -423,6 +513,58 @@ func (l *ListPolicy) ValidateListPolicy(fldPath *field.Path) field.ErrorList {
return allErrs
}

// ValidateChainPolicy validates the FleetAutoscaler Chain policy settings.
func (c *ChainPolicy) ValidateChainPolicy(fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
if !runtime.FeatureEnabled(runtime.FeatureScheduledAutoscaler) {
return append(allErrs, field.Forbidden(fldPath, "feature ChainFleetAutoscaler must be enabled"))
}
for i, entry := range c.Items {
// Validate that the chain entry has a policy
if entry.Policy.Type == "" {
allErrs = append(allErrs, field.Required(fldPath.Child("items").Index(i).Child("policy"), "policy type is missing"))
}
// Ensure the chain entry's policy is not a chain policy (to avoid nested chain policies)
if entry.Policy.Type == ChainPolicyType {
allErrs = append(allErrs, field.Invalid(fldPath.Child("items").Index(i).Child("policy"), entry.Policy.Type, "chain policy cannot be used in chain policy"))
}
allErrs = append(allErrs, entry.Policy.ValidatePolicy(fldPath.Child("items").Index(i).Child("policy"))...)
// Validate the chain entry's timezone.
if _, err := time.LoadLocation(entry.Schedule.Timezone); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("items").Index(i).Child("schedule").Child("timezone"), entry.Schedule.Timezone, "timezone is not a valid timezone"))
}
if entry.Schedule.Between.Start != "" {
// If the start time is not a valid RFC3339 or ISO8601 formatted datetime, add an error
if _, err := time.Parse(time.RFC3339, entry.Schedule.Between.Start); err != nil {
if _, err := iso8601.ParseString(entry.Schedule.Between.Start); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("items").Index(i).Child("schedule").Child("between").Child("start"), entry.Schedule.Between.Start, "start must be a valid RFC3339 or ISO8601 formatted datetime"))
}
}
}
if entry.Schedule.Between.End != "" {
// If the end time is not a valid RFC3339 or ISO8601 formatted datetime, add an error
if _, err := time.Parse(time.RFC3339, entry.Schedule.Between.End); err != nil {
if _, err := iso8601.ParseString(entry.Schedule.Between.End); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("items").Index(i).Child("schedule").Child("between").Child("end"), entry.Schedule.Between.End, "end must be a valid RFC3339 or ISO8601 formatted datetime"))
}
}
}
if entry.Schedule.ActivePeriod.StartCron != "" {
// If the startCron is not a valid cron expression, add an error
if _, err := cron.ParseStandard(entry.Schedule.ActivePeriod.StartCron); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("items").Index(i).Child("schedule").Child("activePeriod").Child("startCron"), entry.Schedule.ActivePeriod.StartCron, "startCron is not a valid cron expression"))
}
}
if entry.Schedule.ActivePeriod.Duration != "" {
// If the duration is not a valid golang duration, add an error
if _, err := time.ParseDuration(entry.Schedule.ActivePeriod.Duration); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Child("items").Index(i).Child("schedule").Child("activePeriod").Child("duration"), entry.Schedule.ActivePeriod.Duration, "duration is not a valid duration"))
}
}
}
return allErrs
}

// ValidateFixedIntervalSync validates the FixedIntervalSync settings
func (i *FixedIntervalSync) ValidateFixedIntervalSync(fldPath *field.Path) field.ErrorList {
var allErrs field.ErrorList
Expand Down
Loading

0 comments on commit 618f76c

Please sign in to comment.