Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add API Changes and Validation for FleetAutoscaler Schedule/Chain Policy #3893

Merged
merged 30 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
685f518
define and add crd changes for new fleetautoscaler chain policy
indexjoseph Jun 20, 2024
711ceaa
Made API Changes for Chain Policy
indexjoseph Jun 21, 2024
59430d3
API Changes for Fleet Autoscaler Policy and Chain Policy
indexjoseph Jun 23, 2024
d5c3cd2
chain policy tests
indexjoseph Jun 23, 2024
b57d651
change feature flag stage to alpha
indexjoseph Jun 24, 2024
30d3496
rollback api changes and add scheduled autoscaer as dev feature gate
indexjoseph Jun 24, 2024
6097474
remove added vendor files
indexjoseph Jun 24, 2024
707b1e2
fix example spacing
indexjoseph Jun 24, 2024
c8f3e24
fix yaml lint
indexjoseph Jun 24, 2024
df77e6d
fix enumerations for policies
indexjoseph Jun 24, 2024
3d666c4
fixed install yaml
indexjoseph Jun 25, 2024
a753ef1
remove extra character and add documentation
indexjoseph Jun 25, 2024
22dd8b2
fix copyright dates, feature gate and include policy
indexjoseph Jun 26, 2024
a3fff74
fix recursive chain policy issue
indexjoseph Jun 27, 2024
3da4b61
remove extra indentation
indexjoseph Jun 27, 2024
c2860c2
regenerate dependecies
indexjoseph Jul 8, 2024
7989d42
move timezone field into activePeriod and only allow for RFC3339 star…
indexjoseph Jul 9, 2024
7f827c0
Add generated deepcopy for new Chain Policy
indexjoseph Jul 10, 2024
75a4a16
change chain entry uid to id
indexjoseph Jul 15, 2024
1e2f80c
run go mod tidy and crd generation
indexjoseph Jul 15, 2024
f7da4e3
crd changes for schedule
indexjoseph Jul 17, 2024
a921b0e
fix example time to have valid start/end times
indexjoseph Jul 18, 2024
de4b803
install yaml
indexjoseph Jul 18, 2024
1848627
Add schedule as a policy and separate from chain policy
indexjoseph Jul 25, 2024
68fa20a
Merge branch 'main' into chain-api-changes
zmerlynn Jul 25, 2024
3c826c1
Update FleetAutoscaler Chain Policy Definition to Exclude Policy Prop…
indexjoseph Jul 26, 2024
a86aa34
Update chainfleetautoscaler to have list of entries (removing policy …
indexjoseph Jul 26, 2024
1d7eb1d
regenerate api docs
indexjoseph Jul 27, 2024
0e04671
Add validation for ensuring that a user enter's a policy if a chain e…
indexjoseph Jul 29, 2024
067d52b
Merge branch 'main' into chain-api-changes
zmerlynn Jul 29, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 11 additions & 12 deletions examples/chainfleetautoscaler.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,24 +27,24 @@ kind: FleetAutoscaler
metadata:
name: chain-fleet-autoscaler
spec:
fleetName: "fleet-example"
policy:
# Chain based policy for autoscaling.
type: Chain
chain:
# Id of chain entry. If not set, the Id will be defaulted to the index of the entry within the chain.
- id: "weekday"
type: Schedule # Schedule based condition.
indexjoseph marked this conversation as resolved.
Show resolved Hide resolved
schedule:
between:
# The policy becomes eligible for application starting on Feb 20, 2024 at 4:04 PM EST. If not set, the policy will immediately be eligible for application.
start: "2024-02-20T16:04:04-05:00"
# The policy becomes ineligible for application on Feb 23, 2024 at 4:04 PM EST. If not set, the policy will always be eligible for application (after the start time).
end: "2024-02-23T16:04:04-05:00"
# The policy becomes eligible for application starting on Feb 20, 2100 at 4:04 PM EST. If not set, the policy will immediately be eligible for application.
start: "2100-02-20T16:04:04-05:00"
# The policy becomes ineligible for application on Feb 23, 2100 at 4:04 PM EST. If not set, the policy will always be eligible for application (after the start time).
end: "2100-02-23T16:04:04-05:00"
activePeriod:
# Timezone to be used for the startCron field. Defaults to UTC if not set.
timezone: "America/New_York"
# Start applying the policy everyday at 1:00 AM EST. If not set, the policy will always be applied in the .between window.
# (Only eligible starting on Feb 20, 2024 at 4:04 PM).
# (Only eligible starting on Feb 20, 2100 at 4:04 PM).
startCron: "0 1 * * 0"
# Only apply the policy for 5 hours. If not set, the duration will be defaulted to always/indefinite.
duration: "5h"
Expand All @@ -57,18 +57,17 @@ spec:
maxReplicas: 2000
# Id of chain entry. If not set, the Id will be defaulted to the index of the entry within the chain list.
- id: "weekend"
type: Schedule
schedule:
between:
# The policy becomes eligible for application starting on Feb 24, 2024 at 4:05 PM EST. If not set, the policy will immediately be eligible for application.
start: "2024-02-24T16:04:05-05:00"
# The policy becomes ineligible for application starting on Feb 26, 2024 at 4:05 PM EST. If not set, the policy will always be eligible for application (after the start time).
end: "2024-02-26T16:04:05-05:00"
# The policy becomes eligible for application starting on Feb 24, 2100 at 4:05 PM EST. If not set, the policy will immediately be eligible for application.
start: "2100-02-24T16:04:05-05:00"
# The policy becomes ineligible for application starting on Feb 26, 2100 at 4:05 PM EST. If not set, the policy will always be eligible for application (after the start time).
end: "2100-02-26T16:04:05-05:00"
activePeriod:
# Timezone to be used for the startCron field. Defaults to UTC if not set.
timezone: "America/New_York"
# Start applying the policy everyday at 1:00 AM EST. If not set, the policy will always be applied in the .between window.
# (Only eligible starting on Feb 24, 2024 at 4:05 PM EST)
# (Only eligible starting on Feb 24, 2100 at 4:05 PM EST)
startCron: "0 1 * * 0"
# Only apply the policy for 7 hours. If not set the duration will be defaulted to always/indefinite.
duration: "7h"
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ require (
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822
github.com/pkg/errors v0.9.1
github.com/prometheus/client_golang v1.16.0
github.com/robfig/cron/v3 v3.0.1
github.com/sirupsen/logrus v1.9.0
github.com/spf13/pflag v1.0.5
github.com/spf13/viper v1.7.0
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -381,6 +381,8 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
github.com/prometheus/procfs v0.10.1 h1:kYK1Va/YMlutzCGazswoHKo//tZVlFpKYh+PymziUAg=
github.com/prometheus/procfs v0.10.1/go.mod h1:nwNm2aOCAYw8uTR/9bWRREkZFxAUcWzPHWJq+XBB/FM=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
Expand Down
28 changes: 16 additions & 12 deletions install/helm/agones/templates/crds/_fleetautoscalerpolicy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -118,29 +118,33 @@ policy:
nullable: true
items:
type: object
nullable: true
required:
- policy
properties:
id: # The Id of a chain entry.
type: string
schedule: # Defines when the policy is applied.
type: object
nullable: true
properties:
between:
type: object
start: # Defines when to start evaluating the active period, must conform to RFC3339.
type: string
end: # Defines when to stop evaluating the active period, must conform to RFC3339.
type: string
nullable: true
properties:
start: # Defines when to start evaluating the active period, must conform to RFC3339.
type: string
end: # Defines when to stop evaluating the active period, must conform to RFC3339.
type: string
activePeriod:
type: object
timezone: # Timezone to be used for the startCron field, must conform with the IANA Time Zone database (e.g. America/New_York).
type: string
startCron: # Cron expression defining when to start applying the policy. All TZ/CRON_TZ specification within startCron will be rejected, please use the timezone field above to specify a timezone. Must conform with UNIX CRON syntax.
type: string
duration: # The length of time the policy should be applied for (e.g. 2h45m).
type: string
{{- include "fleetautoscaler.policy" (dict "includeChainPolicy" false) | indent 12 }} # Defines which policy to apply during the active period. Required.
nullable: true
properties:
timezone: # Timezone to be used for the startCron field, must conform with the IANA Time Zone database (e.g. America/New_York).
type: string
startCron: # Cron expression defining when to start applying the policy. All TZ/CRON_TZ specification within startCron will be rejected, please use the timezone field above to specify a timezone. Must conform with UNIX CRON syntax.
type: string
duration: # The length of time the policy should be applied for (e.g. 2h45m).
type: string
{{- include "fleetautoscaler.policy" (dict "includeChainPolicy" false) | indent 10 }} # Defines which policy to apply during the active period. Required.
{{- end }}
{{- end }}
174 changes: 166 additions & 8 deletions pkg/apis/autoscaling/v1/fleetautoscaler.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,15 @@ package v1

import (
"crypto/x509"
"errors"
"fmt"
"net/url"
"strings"
"time"

agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
"agones.dev/agones/pkg/util/runtime"
"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 +87,11 @@ type FleetAutoscalerPolicy struct {
// List policy config params. Present only if FleetAutoscalerPolicyType = List.
// +optional
List *ListPolicy `json:"list,omitempty"`
// [Stage:Dev]
// [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 +128,11 @@ const (
// ListPolicyType is for List based fleet autoscaling
// nolint:revive // Linter contains comment doesn't start with ListPolicyType
ListPolicyType FleetAutoscalerPolicyType = "List"
// [Stage:Dev]
// [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 +210,61 @@ 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.
// This field must conform to RFC3339 format. If this field not set or is in the past, the policy is eligible to be applied
// as soon as the fleet autoscaler is running.
Start string `json:"start"`
indexjoseph marked this conversation as resolved.
Show resolved Hide resolved

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

// ActivePeriod defines the time period that the policy is applied.
type ActivePeriod struct {
// Timezone to be used for the startCron field. If not set, startCron is defaulted to the UTC timezone.
Timezone string `json:"timezone"`
indexjoseph marked this conversation as resolved.
Show resolved Hide resolved

// StartCron defines when the policy should be applied.
// If not set, the policy is always to be applied within the start and end time.
// This field must conform to UNIX cron syntax.
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").
// The representation limits the largest representable duration to approximately 290 years.
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
Duration string `json:"duration"`
}

// Schedule defines when the policy should be applied.
type Schedule struct {
// Between defines the time period that the policy is eligible to be applied.
Between Between `json:"between"`

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

// ChainEntry defines a single entry in the ChainPolicy.
type ChainEntry struct {
// ID is the unique identifier for a ChainEntry. If not set the identifier will be set to the index of chain entry.
ID string `json:"id"`

// Schedule defines when the policy is applied.
Schedule Schedule `json:"schedule"`
indexjoseph marked this conversation as resolved.
Show resolved Hide resolved

// 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 []ChainEntry

// 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 +328,32 @@ 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
}

// ValidatePolicy validates a FleetAutoscalerPolicy's 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"))
}
allErrs = f.List.ValidateListPolicy(fldPath.Child("list"))

if fas.Spec.Sync != nil {
allErrs = append(allErrs, fas.Spec.Sync.FixedInterval.ValidateFixedIntervalSync(field.NewPath("spec", "sync", "fixedInterval"))...)
case ChainPolicyType:
allErrs = f.Chain.ValidateChainPolicy(fldPath.Child("chain"))
}
return allErrs
}
Expand Down Expand Up @@ -423,6 +502,85 @@ 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 c == nil {
return append(allErrs, field.Required(fldPath, "chain policy config params are missing"))
}
if !runtime.FeatureEnabled(runtime.FeatureScheduledAutoscaler) {
return append(allErrs, field.Forbidden(fldPath, "feature ScheduledAutoscaler must be enabled"))
}
seenIDs := make(map[string]bool)
for i, entry := range *c {

// Ensure that all IDs are unique
if seenIDs[entry.ID] {
allErrs = append(allErrs, field.Invalid(fldPath, entry.ID, "id of chain entry must be unique"))
} else {
seenIDs[entry.ID] = true
}

var startTime time.Time
var startErr error
if entry.Schedule.Between.Start != "" {
indexjoseph marked this conversation as resolved.
Show resolved Hide resolved
// If start time is not a valid RFC3339 formatted datetime, append an error
startTime, startErr = time.Parse(time.RFC3339, entry.Schedule.Between.Start)
if startErr != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("schedule").Child("between").Child("start"), entry.Schedule.Between.Start, fmt.Sprintf("invalid start time: %s", startErr)))
}
}
if entry.Schedule.Between.End != "" {
// If end time is not a valid RFC3339 formatted datetime, append an error
endTime, endErr := time.Parse(time.RFC3339, entry.Schedule.Between.End)
if endErr != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("schedule").Child("between").Child("end"), entry.Schedule.Between.End, fmt.Sprintf("invalid end time: %s", endErr)))
} else if endTime.Before(time.Now()) {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("schedule").Child("between").Child("end"), entry.Schedule.Between.End, "end time must be after the current time"))
endErr = errors.New("end time before current time")
}
// Check if the end time is before the start time
if (!startTime.IsZero() && startErr == nil) && endErr == nil {
if endTime.Before(startTime) {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("schedule").Child("between"), entry.Schedule.Between, "start time must be before end time"))
}
}
}

// Validate the active period timezone (empty string defaults to UTC).
if _, err := time.LoadLocation(entry.Schedule.ActivePeriod.Timezone); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("schedule").Child("activePeriod").Child("timezone"), entry.Schedule.ActivePeriod.Timezone, fmt.Sprintf("invalid timezone: %s", err)))
}
if entry.Schedule.ActivePeriod.StartCron != "" {
// If startCron is not a valid cron expression, append an error
if _, err := cron.ParseStandard(entry.Schedule.ActivePeriod.StartCron); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("schedule").Child("activePeriod").Child("startCron"), entry.Schedule.ActivePeriod.StartCron, fmt.Sprintf("invalid startCron: %s", err)))
}
// If the cron expression contains a CRON_TZ or TZ specification, append an error
if strings.Contains(entry.Schedule.ActivePeriod.StartCron, "TZ") {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("schedule").Child("activePeriod").Child("startCron"), entry.Schedule.ActivePeriod.StartCron, "CRON_TZ or TZ used in start is not supported, please use the .schedule.timezone field to specify a timezone"))
indexjoseph marked this conversation as resolved.
Show resolved Hide resolved
}
}
if entry.Schedule.ActivePeriod.Duration != "" {
// If the duration is not valid duration format, append an error
if _, err := time.ParseDuration(entry.Schedule.ActivePeriod.Duration); err != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("schedule").Child("activePeriod").Child("duration"), entry.Schedule.ActivePeriod.Duration, fmt.Sprintf("invalid duration: %s", err)))
}
}
// Validate that the chain entry has a policy
if entry.Policy.Type == "" {
allErrs = append(allErrs, field.Required(fldPath.Index(i).Child("policy"), "policy type is missing"))
}
// Ensure the chain entry's policy is not a chain policy (to avoid nested policies)
if entry.Policy.Chain != nil {
allErrs = append(allErrs, field.Invalid(fldPath.Index(i).Child("policy"), entry.Policy.Type, "chain policy cannot be used in chain policy"))
}
// Validate the chain entry's policy
allErrs = append(allErrs, entry.Policy.ValidatePolicy(fldPath.Index(i).Child("policy"))...)
}
return allErrs
}

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