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

fix: Don't require VolumeSnapshot CRDs to run the manager #327

Merged
merged 3 commits into from
Jul 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions api/v1alpha1/scheduledvolumesnapshot_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,9 @@ const (

// SnapshotPhaseSuspended means the controller is not creating snapshots. Suspended by the user.
SnapshotPhaseSuspended SnapshotPhase = "Suspended"

// SnapshotPhaseMissingCRDs means the controller is not creating snapshots. The required VolumeSnapshot CRDs are missing.
SnapshotPhaseMissingCRDs SnapshotPhase = "MissingCRDs"
)

type VolumeSnapshotStatus struct {
Expand Down
28 changes: 19 additions & 9 deletions controllers/scheduledvolumesnapshot_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,24 +36,27 @@ import (
// ScheduledVolumeSnapshotReconciler reconciles a ScheduledVolumeSnapshot object
type ScheduledVolumeSnapshotReconciler struct {
client.Client
fullNodeControl *volsnapshot.FullNodeControl
recorder record.EventRecorder
scheduler *volsnapshot.Scheduler
volSnapshotControl *volsnapshot.VolumeSnapshotControl
fullNodeControl *volsnapshot.FullNodeControl
missingVolSnapshotCRD bool
recorder record.EventRecorder
scheduler *volsnapshot.Scheduler
volSnapshotControl *volsnapshot.VolumeSnapshotControl
}

func NewScheduledVolumeSnapshotReconciler(
client client.Client,
recorder record.EventRecorder,
statusClient *fullnode.StatusClient,
cache *cosmos.CacheController,
missingVolSnapCRD bool,
) *ScheduledVolumeSnapshotReconciler {
return &ScheduledVolumeSnapshotReconciler{
Client: client,
fullNodeControl: volsnapshot.NewFullNodeControl(statusClient, client),
recorder: recorder,
scheduler: volsnapshot.NewScheduler(client),
volSnapshotControl: volsnapshot.NewVolumeSnapshotControl(client, cache),
Client: client,
fullNodeControl: volsnapshot.NewFullNodeControl(statusClient, client),
missingVolSnapshotCRD: missingVolSnapCRD,
recorder: recorder,
scheduler: volsnapshot.NewScheduler(client),
volSnapshotControl: volsnapshot.NewVolumeSnapshotControl(client, cache),
}
}

Expand Down Expand Up @@ -83,6 +86,13 @@ func (r *ScheduledVolumeSnapshotReconciler) Reconcile(ctx context.Context, req c
volsnapshot.ResetStatus(crd)
defer r.updateStatus(ctx, crd)

if r.missingVolSnapshotCRD {
logger.Error(errMissingVolSnapCRD, "Controller is disabled")
r.reportError(crd, "MissingCRDs", errMissingVolSnapCRD)
crd.Status.Phase = cosmosv1alpha1.SnapshotPhaseMissingCRDs
return ctrl.Result{}, nil
}

retryResult := ctrl.Result{RequeueAfter: 10 * time.Second}

phase := crd.Status.Phase
Expand Down
52 changes: 34 additions & 18 deletions controllers/statefuljob_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package controllers

import (
"context"
"errors"
"fmt"
"time"

Expand All @@ -37,17 +38,39 @@ import (
"sigs.k8s.io/controller-runtime/pkg/source"
)

var errMissingVolSnapCRD = errors.New("cluster does not have VolumeSnapshot CRDs installed")

// IndexVolumeSnapshots indexes all VolumeSnapshots by name. Exposed as a separate method so caller can
// test for presence of VolumeSnapshot CRDs in the cluster.
func IndexVolumeSnapshots(ctx context.Context, mgr ctrl.Manager) error {
// Index all VolumeSnapshots. Controller does not own any because it does not create them.
if err := mgr.GetFieldIndexer().IndexField(
ctx,
&snapshotv1.VolumeSnapshot{},
".metadata.name",
func(object client.Object) []string {
return []string{object.GetName()}
},
); err != nil {
return fmt.Errorf("volume snapshot index: %w", err)
}
return nil
}

// StatefulJobReconciler reconciles a StatefulJob object.
type StatefulJobReconciler struct {
client.Client
recorder record.EventRecorder
recorder record.EventRecorder
missingVolSnapshotCRD bool
}

// NewStatefulJob returns a valid controller.
func NewStatefulJob(client client.Client, recorder record.EventRecorder) *StatefulJobReconciler {
// NewStatefulJob returns a valid controller. If missingVolSnapCRD is true, the controller errors on every reconcile loop
// and will not function.
func NewStatefulJob(client client.Client, recorder record.EventRecorder, missingVolSnapCRD bool) *StatefulJobReconciler {
return &StatefulJobReconciler{
Client: client,
recorder: recorder,
Client: client,
recorder: recorder,
missingVolSnapshotCRD: missingVolSnapCRD,
}
}

Expand Down Expand Up @@ -78,6 +101,11 @@ func (r *StatefulJobReconciler) Reconcile(ctx context.Context, req ctrl.Request)
return requeueStatefulJob, kube.IgnoreNotFound(err)
}

if r.missingVolSnapshotCRD {
r.reportErr(logger, crd, errMissingVolSnapCRD)
return ctrl.Result{}, nil
}

crd.Status.ObservedGeneration = crd.Generation
crd.Status.StatusMessage = nil
defer r.updateStatus(ctx, crd)
Expand Down Expand Up @@ -170,20 +198,8 @@ func (r *StatefulJobReconciler) updateStatus(ctx context.Context, crd *cosmosalp
}
}

// SetupWithManager sets up the controller with the Manager.
// SetupWithManager sets up the controller with the Manager. IndexVolumeSnapshots should be called first.
func (r *StatefulJobReconciler) SetupWithManager(ctx context.Context, mgr ctrl.Manager) error {
// Index all VolumeSnapshots. Controller does not own any because it does not create them.
if err := mgr.GetFieldIndexer().IndexField(
ctx,
&snapshotv1.VolumeSnapshot{},
".metadata.name",
func(object client.Object) []string {
return []string{object.GetName()}
},
); err != nil {
return fmt.Errorf("VolumeSnapshot index: %w", err)
}

cbuilder := ctrl.NewControllerManagedBy(mgr).For(&cosmosalpha.StatefulJob{})

// Watch for delete events for jobs.
Expand Down
26 changes: 18 additions & 8 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,24 +195,34 @@ func startManager(cmd *cobra.Command, args []string) error {
return fmt.Errorf("unable to create SelfHealing controller: %w", err)
}

// Test for presence of VolumeSnapshot CRD.
snapshotErr := controllers.IndexVolumeSnapshots(ctx, mgr)
if snapshotErr != nil {
setupLog.Info("Warning: VolumeSnapshot CRD not found, StatefulJob and ScheduledVolumeSnapshot controllers will be disabled")
}

// StatefulJobs
jobCtl := controllers.NewStatefulJob(
mgr.GetClient(),
mgr.GetEventRecorderFor(cosmosv1alpha1.StatefulJobController),
snapshotErr != nil,
)

if err = jobCtl.SetupWithManager(ctx, mgr); err != nil {
return fmt.Errorf("unable to create StatefulJob controller: %w", err)
}

// ScheduledVolumeSnapshots
if err = controllers.NewScheduledVolumeSnapshotReconciler(
mgr.GetClient(),
mgr.GetEventRecorderFor(cosmosv1alpha1.ScheduledVolumeSnapshotController),
statusClient,
cacheController,
snapshotErr != nil,
).SetupWithManager(ctx, mgr); err != nil {
return fmt.Errorf("unable to create ScheduledVolumeSnapshot controller: %w", err)
}

// StatefulJobs
if err = controllers.NewStatefulJob(
mgr.GetClient(),
mgr.GetEventRecorderFor(cosmosv1alpha1.StatefulJobController),
).SetupWithManager(ctx, mgr); err != nil {
return fmt.Errorf("unable to create StatefulJob controller: %w", err)
}

//+kubebuilder:scaffold:builder

if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
Expand Down