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

Ability to define schedule for bundles #450

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
70 changes: 70 additions & 0 deletions charts/fleet-crd/templates/crds.yaml
Original file line number Diff line number Diff line change
@@ -290,6 +290,12 @@ spec:
nullable: true
type: array
type: object
schedule:
nullable: true
type: string
scheduleWindow:
nullable: true
type: string
serviceAccount:
nullable: true
type: string
@@ -556,6 +562,12 @@ spec:
namespace:
nullable: true
type: string
schedule:
nullable: true
type: string
scheduleWindow:
nullable: true
type: string
serviceAccount:
nullable: true
type: string
@@ -1060,6 +1072,12 @@ spec:
namespace:
nullable: true
type: string
schedule:
nullable: true
type: string
scheduleWindow:
nullable: true
type: string
serviceAccount:
nullable: true
type: string
@@ -1203,6 +1221,12 @@ spec:
namespace:
nullable: true
type: string
schedule:
nullable: true
type: string
scheduleWindow:
nullable: true
type: string
serviceAccount:
nullable: true
type: string
@@ -1328,6 +1352,11 @@ spec:
release:
nullable: true
type: string
scheduled:
type: boolean
scheduledAt:
nullable: true
type: string
syncGeneration:
nullable: true
type: integer
@@ -1763,6 +1792,12 @@ spec:
type: boolean
redeployAgentGeneration:
type: integer
schedule:
nullable: true
type: string
scheduleWindow:
nullable: true
type: string
type: object
status:
properties:
@@ -2961,6 +2996,12 @@ spec:
nullable: true
type: array
type: object
schedule:
nullable: true
type: string
scheduleWindow:
nullable: true
type: string
serviceAccount:
nullable: true
type: string
@@ -3227,6 +3268,12 @@ spec:
namespace:
nullable: true
type: string
schedule:
nullable: true
type: string
scheduleWindow:
nullable: true
type: string
serviceAccount:
nullable: true
type: string
@@ -3732,6 +3779,12 @@ spec:
namespace:
nullable: true
type: string
schedule:
nullable: true
type: string
scheduleWindow:
nullable: true
type: string
serviceAccount:
nullable: true
type: string
@@ -3875,6 +3928,12 @@ spec:
namespace:
nullable: true
type: string
schedule:
nullable: true
type: string
scheduleWindow:
nullable: true
type: string
serviceAccount:
nullable: true
type: string
@@ -4000,6 +4059,11 @@ spec:
release:
nullable: true
type: string
scheduled:
type: boolean
scheduledAt:
nullable: true
type: string
syncGeneration:
nullable: true
type: integer
@@ -4438,6 +4502,12 @@ spec:
type: boolean
redeployAgentGeneration:
type: integer
schedule:
nullable: true
type: string
scheduleWindow:
nullable: true
type: string
type: object
status:
properties:
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -49,6 +49,7 @@ require (
github.com/rancher/lasso v0.0.0-20210616224652-fc3ebd901c08
github.com/rancher/wrangler v0.8.4
github.com/rancher/wrangler-cli v0.0.0-20200815040857-81c48cf8ab43
github.com/robfig/cron v1.1.0 // indirect
github.com/sirupsen/logrus v1.8.1
github.com/spf13/cobra v1.1.3
github.com/stretchr/testify v1.7.0
59 changes: 59 additions & 0 deletions modules/agent/pkg/controllers/bundledeployment/controller.go
Original file line number Diff line number Diff line change
@@ -8,6 +8,8 @@ import (
"sync"
"time"

"github.com/robfig/cron"

"github.com/rancher/fleet/modules/agent/pkg/deployer"
"github.com/rancher/fleet/modules/agent/pkg/trigger"
fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
@@ -90,6 +92,55 @@ func (h *handler) Cleanup(key string, bd *fleet.BundleDeployment) (*fleet.Bundle
}

func (h *handler) DeployBundle(bd *fleet.BundleDeployment, status fleet.BundleDeploymentStatus) (fleet.BundleDeploymentStatus, error) {

if bd.Spec.Options.Schedule != "" && status.ScheduledAt == "" {
cronSched, err := cron.ParseStandard(bd.Spec.Options.Schedule)
if err != nil {
return status, err
}
scheduledRun := cronSched.Next(time.Now())
after := scheduledRun.Sub(time.Now())
h.bdController.EnqueueAfter(bd.Namespace, bd.Name, after)
status.ScheduledAt = scheduledRun.Format(time.RFC3339)
status.Scheduled = true
condition.Cond(fleet.BundleScheduledCondition).SetStatusBool(&status, true)
condition.Cond(fleet.BundleDeploymentConditionDeployed).SetStatusBool(&status, false)
return status, nil
}

if bd.Spec.Options.Schedule != "" && status.ScheduledAt != "" {
nextRun, err := time.Parse(time.RFC3339, status.ScheduledAt)
if err != nil {
return status, err
}
window := fleet.DefaultWindow
if bd.Spec.Options.ScheduleWindow != "" {
window = bd.Spec.Options.ScheduleWindow
}

windowDuration, err := time.ParseDuration(window)
if err != nil {
return status, err
}

if err != nil {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi,

There is a duplicated error check here

return status, err
}
if nextRun.After(time.Now()) {
after := nextRun.Sub(time.Now())
h.bdController.EnqueueAfter(bd.Namespace, bd.Name, after)
return status, nil
}

// case of disconnected agent during the actual window //
if nextRun.Add(windowDuration).Before(time.Now()) {
// clean up scheduled at to allow object to fall through scheduling
status.ScheduledAt = ""
status.Scheduled = false
return status, nil
}
}

depBundles, ok, err := h.checkDependency(bd)
if err != nil {
return status, err
@@ -108,6 +159,7 @@ func (h *handler) DeployBundle(bd *fleet.BundleDeployment, status fleet.BundleDe
if err != nil {
return status, err
}
status.Scheduled = false
status.Release = release
status.AppliedDeploymentID = bd.Spec.DeploymentID
return status, nil
@@ -219,6 +271,11 @@ func (h *handler) cleanupOldAgent(modifiedStatuses []fleet.ModifiedStatus) error
}

func (h *handler) MonitorBundle(bd *fleet.BundleDeployment, status fleet.BundleDeploymentStatus) (fleet.BundleDeploymentStatus, error) {

if status.Scheduled {
return status, nil
}

if bd.Spec.DeploymentID != status.AppliedDeploymentID {
return status, nil
}
@@ -271,6 +328,8 @@ func readyError(status fleet.BundleDeploymentStatus) error {
if len(status.ModifiedStatus) > 0 {
msg = status.ModifiedStatus[0].String()
}
} else if status.Scheduled {
msg = "scheduled"
}

return errors.New(msg)
7 changes: 7 additions & 0 deletions pkg/apis/fleet.cattle.io/v1alpha1/bundle.go
Original file line number Diff line number Diff line change
@@ -31,6 +31,8 @@ var (
}
)

const DefaultWindow = "1h"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should put this in the Helm chart.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nickgerace do you want to expose this default Window as an option for the agent controller? similar to checkin interval?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes!


type BundleState string

// +genclient
@@ -134,6 +136,7 @@ var (
BundleConditionReady = "Ready"
BundleDeploymentConditionReady = "Ready"
BundleDeploymentConditionDeployed = "Deployed"
BundleScheduledCondition = "Scheduled"
)

type BundleStatus struct {
@@ -192,6 +195,8 @@ type BundleDeploymentOptions struct {
ForceSyncGeneration int64 `json:"forceSyncGeneration,omitempty"`
YAML *YAMLOptions `json:"yaml,omitempty"`
Diff *DiffOptions `json:"diff,omitempty"`
Schedule string `json:"schedule,omitempty"`
ScheduleWindow string `json:"scheduleWindow,omitempty"`
}

type DiffOptions struct {
@@ -279,10 +284,12 @@ type BundleDeploymentStatus struct {
Release string `json:"release,omitempty"`
Ready bool `json:"ready,omitempty"`
NonModified bool `json:"nonModified,omitempty"`
Scheduled bool `json:"scheduled,omitempty"`
NonReadyStatus []NonReadyStatus `json:"nonReadyStatus,omitempty"`
ModifiedStatus []ModifiedStatus `json:"modifiedStatus,omitempty"`
Display BundleDeploymentDisplay `json:"display,omitempty"`
SyncGeneration *int64 `json:"syncGeneration,omitempty"`
ScheduledAt string `json:"scheduledAt,omitempty"`
}

type BundleDeploymentDisplay struct {
2 changes: 2 additions & 0 deletions pkg/apis/fleet.cattle.io/v1alpha1/target.go
Original file line number Diff line number Diff line change
@@ -66,6 +66,8 @@ type ClusterSpec struct {
KubeConfigSecret string `json:"kubeConfigSecret,omitempty"`
RedeployAgentGeneration int64 `json:"redeployAgentGeneration,omitempty"`
AgentEnvVars []v1.EnvVar `json:"agentEnvVars,omitempty"`
Schedule string `json:"schedule,omitempty"`
ScheduleWindow string `json:"scheduleWindow,omitempty"`
AgentNamespace string `json:"agentNamespace,omitempty"`
}

15 changes: 15 additions & 0 deletions pkg/controllers/bundle/controller.go
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@ package bundle
import (
"context"
"sort"
"strings"

fleet "github.com/rancher/fleet/pkg/apis/fleet.cattle.io/v1alpha1"
fleetcontrollers "github.com/rancher/fleet/pkg/generated/controllers/fleet.cattle.io/v1alpha1"
@@ -232,6 +233,7 @@ func toRuntimeObjects(targets []*target.Target, bundle *fleet.Bundle) (result []
if target.Deployment == nil {
continue
}

dp := &fleet.BundleDeployment{
ObjectMeta: v1.ObjectMeta{
Name: target.Deployment.Name,
@@ -241,6 +243,19 @@ func toRuntimeObjects(targets []*target.Target, bundle *fleet.Bundle) (result []
Spec: target.Deployment.Spec,
}
dp.Spec.DependsOn = bundle.Spec.DependsOn

// Cluster level schedule and window takes priority over bundle specific schedule and window
// Ensure cluster schedule is always applied to all bundles
if !strings.HasPrefix(bundle.Name, "fleet-agent") {
if target.Cluster.Spec.Schedule != "" {
dp.Spec.Options.Schedule = target.Cluster.Spec.Schedule
}

if target.Cluster.Spec.ScheduleWindow != "" {
dp.Spec.Options.ScheduleWindow = target.Cluster.Spec.ScheduleWindow
}
}

result = append(result, dp)
}