2017-08-02 17:27:17 +00:00
/ *
2019-03-20 19:32:48 +00:00
Copyright 2017 the Velero contributors .
2017-08-02 17:27:17 +00:00
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 .
* /
package controller
import (
k8s 1.18 import (#2651)
* k8s 1.18 import wip
backup, cmd, controller, generated, restic, restore, serverstatusrequest, test and util
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go mod tidy
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* add changelog file
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* go fmt
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* update code-generator and controller-gen in CI
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* checkout proper code-generator version, regen
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix remaining calls
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* regenerate CRDs with ./hack/update-generated-crd-code.sh
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use existing context in restic and server
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* fix test cases by resetting resource version
also use main library go context, not golang.org/x/net/context, in pkg/restore/restore.go
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* clarify changelog message
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* use github.com/kubernetes-csi/external-snapshotter/v2@v2.2.0-rc1
Signed-off-by: Andrew Lavery <laverya@umich.edu>
* run 'go mod tidy' to remove old external-snapshotter version
Signed-off-by: Andrew Lavery <laverya@umich.edu>
2020-07-16 16:21:37 +00:00
"context"
2017-08-02 17:27:17 +00:00
"fmt"
"time"
2017-09-14 21:27:31 +00:00
"github.com/pkg/errors"
2017-08-02 17:27:17 +00:00
"github.com/robfig/cron"
2017-09-14 21:27:31 +00:00
"github.com/sirupsen/logrus"
2022-04-19 01:23:40 +00:00
apierrors "k8s.io/apimachinery/pkg/api/errors"
2017-08-02 17:27:17 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2022-09-29 07:01:30 +00:00
"k8s.io/apimachinery/pkg/labels"
2023-02-15 07:20:41 +00:00
clocks "k8s.io/utils/clock"
2022-03-11 02:16:41 +00:00
ctrl "sigs.k8s.io/controller-runtime"
2022-09-02 02:59:09 +00:00
bld "sigs.k8s.io/controller-runtime/pkg/builder"
2022-03-11 02:16:41 +00:00
"sigs.k8s.io/controller-runtime/pkg/client"
2017-08-02 17:27:17 +00:00
2022-03-11 02:16:41 +00:00
velerov1 "github.com/vmware-tanzu/velero/pkg/apis/velero/v1"
2019-09-30 21:26:56 +00:00
"github.com/vmware-tanzu/velero/pkg/builder"
"github.com/vmware-tanzu/velero/pkg/metrics"
2022-03-11 02:16:41 +00:00
"github.com/vmware-tanzu/velero/pkg/util/kube"
2019-09-30 21:26:56 +00:00
kubeutil "github.com/vmware-tanzu/velero/pkg/util/kube"
2017-08-02 17:27:17 +00:00
)
2018-08-09 16:54:48 +00:00
const (
scheduleSyncPeriod = time . Minute
)
2022-03-11 02:16:41 +00:00
type scheduleReconciler struct {
client . Client
namespace string
logger logrus . FieldLogger
2023-02-15 07:20:41 +00:00
clock clocks . WithTickerAndDelayedExecution
2022-03-11 02:16:41 +00:00
metrics * metrics . ServerMetrics
2017-08-02 17:27:17 +00:00
}
2022-03-11 02:16:41 +00:00
func NewScheduleReconciler (
2017-12-22 14:43:44 +00:00
namespace string ,
2017-12-11 22:10:52 +00:00
logger logrus . FieldLogger ,
2022-03-11 02:16:41 +00:00
client client . Client ,
2018-07-20 13:03:44 +00:00
metrics * metrics . ServerMetrics ,
2022-03-11 02:16:41 +00:00
) * scheduleReconciler {
return & scheduleReconciler {
Client : client ,
namespace : namespace ,
logger : logger ,
2023-02-15 07:20:41 +00:00
clock : clocks . RealClock { } ,
2022-03-11 02:16:41 +00:00
metrics : metrics ,
2017-08-02 17:27:17 +00:00
}
2022-03-11 02:16:41 +00:00
}
2017-08-02 17:27:17 +00:00
2022-03-11 02:16:41 +00:00
func ( c * scheduleReconciler ) SetupWithManager ( mgr ctrl . Manager ) error {
2022-08-30 08:55:39 +00:00
s := kube . NewPeriodicalEnqueueSource ( c . logger , mgr . GetClient ( ) , & velerov1 . ScheduleList { } , scheduleSyncPeriod , kube . PeriodicalEnqueueSourceOption { } )
2022-03-11 02:16:41 +00:00
return ctrl . NewControllerManagedBy ( mgr ) .
2022-09-02 02:59:09 +00:00
// global predicate, works for both For and Watch
WithEventFilter ( kube . NewAllEventPredicate ( func ( obj client . Object ) bool {
schedule := obj . ( * velerov1 . Schedule )
if pause := schedule . Spec . Paused ; pause {
c . logger . Infof ( "schedule %s is paused, skip" , schedule . Name )
return false
}
return true
} ) ) .
For ( & velerov1 . Schedule { } , bld . WithPredicates ( kube . SpecChangePredicate { } ) ) .
2022-03-11 02:16:41 +00:00
Watches ( s , nil ) .
Complete ( c )
2017-08-02 17:27:17 +00:00
}
2022-03-11 02:16:41 +00:00
// +kubebuilder:rbac:groups=velero.io,resources=schedules,verbs=get;list;watch;create;update;patch;delete
// +kubebuilder:rbac:groups=velero.io,resources=schedules/status,verbs=get;update;patch
// +kubebuilder:rbac:groups=velero.io,resources=backups,verbs=create
2017-08-02 17:27:17 +00:00
2022-03-11 02:16:41 +00:00
func ( c * scheduleReconciler ) Reconcile ( ctx context . Context , req ctrl . Request ) ( ctrl . Result , error ) {
log := c . logger . WithField ( "schedule" , req . String ( ) )
2017-08-02 17:27:17 +00:00
2022-03-11 02:16:41 +00:00
log . Debug ( "Getting schedule" )
schedule := & velerov1 . Schedule { }
if err := c . Get ( ctx , req . NamespacedName , schedule ) ; err != nil {
2022-04-19 01:23:40 +00:00
if apierrors . IsNotFound ( err ) {
log . WithError ( err ) . Error ( "schedule not found" )
return ctrl . Result { } , nil
}
return ctrl . Result { } , errors . Wrapf ( err , "error getting schedule %s" , req . String ( ) )
2017-08-02 17:27:17 +00:00
}
2022-03-11 02:16:41 +00:00
c . metrics . InitSchedule ( schedule . Name )
2017-08-02 17:27:17 +00:00
2022-06-07 13:07:44 +00:00
original := schedule . DeepCopy ( )
2017-08-02 17:27:17 +00:00
// validation - even if the item is Enabled, we can't trust it
// so re-validate
currentPhase := schedule . Status . Phase
2018-08-29 19:52:09 +00:00
cronSchedule , errs := parseCronSchedule ( schedule , c . logger )
2017-08-02 17:27:17 +00:00
if len ( errs ) > 0 {
2022-03-11 02:16:41 +00:00
schedule . Status . Phase = velerov1 . SchedulePhaseFailedValidation
2017-08-02 17:27:17 +00:00
schedule . Status . ValidationErrors = errs
} else {
2022-03-11 02:16:41 +00:00
schedule . Status . Phase = velerov1 . SchedulePhaseEnabled
2017-08-02 17:27:17 +00:00
}
// update status if it's changed
if currentPhase != schedule . Status . Phase {
2022-06-07 13:07:44 +00:00
if err := c . Patch ( ctx , schedule , client . MergeFrom ( original ) ) ; err != nil {
2022-04-19 01:23:40 +00:00
return ctrl . Result { } , errors . Wrapf ( err , "error updating phase of schedule %s to %s" , req . String ( ) , schedule . Status . Phase )
2017-08-02 17:27:17 +00:00
}
}
2022-03-11 02:16:41 +00:00
if schedule . Status . Phase != velerov1 . SchedulePhaseEnabled {
log . Debugf ( "the schedule's phase is %s, isn't %s, skip" , schedule . Status . Phase , velerov1 . SchedulePhaseEnabled )
return ctrl . Result { } , nil
2017-08-02 17:27:17 +00:00
}
2022-09-29 07:01:30 +00:00
// Check for the schedule being due to run.
// If there are backup created by this schedule still in New or InProgress state,
// skip current backup creation to avoid running overlap backups.
2022-09-02 02:59:09 +00:00
// As the schedule must be validated before checking whether it's due, we cannot put the checking log in Predicate
2022-09-29 07:01:30 +00:00
if c . ifDue ( schedule , cronSchedule ) && ! c . checkIfBackupInNewOrProgress ( schedule ) {
if err := c . submitBackup ( ctx , schedule ) ; err != nil {
return ctrl . Result { } , errors . Wrapf ( err , "error submit backup for schedule %s" , req . String ( ) )
}
2017-08-02 17:27:17 +00:00
}
2022-03-11 02:16:41 +00:00
return ctrl . Result { } , nil
2017-08-02 17:27:17 +00:00
}
2022-03-11 02:16:41 +00:00
func parseCronSchedule ( itm * velerov1 . Schedule , logger logrus . FieldLogger ) ( cron . Schedule , [ ] string ) {
2017-08-02 17:27:17 +00:00
var validationErrors [ ] string
var schedule cron . Schedule
// cron.Parse panics if schedule is empty
if len ( itm . Spec . Schedule ) == 0 {
validationErrors = append ( validationErrors , "Schedule must be a non-empty valid Cron expression" )
return nil , validationErrors
}
2018-08-29 19:52:09 +00:00
log := logger . WithField ( "schedule" , kubeutil . NamespaceAndName ( itm ) )
2017-09-14 21:27:31 +00:00
2017-08-02 17:27:17 +00:00
// adding a recover() around cron.Parse because it panics on empty string and is possible
// that it panics under other scenarios as well.
func ( ) {
defer func ( ) {
if r := recover ( ) ; r != nil {
2018-08-29 19:52:09 +00:00
log . WithFields ( logrus . Fields {
2017-09-14 21:27:31 +00:00
"schedule" : itm . Spec . Schedule ,
"recover" : r ,
} ) . Debug ( "Panic parsing schedule" )
2017-08-02 17:27:17 +00:00
validationErrors = append ( validationErrors , fmt . Sprintf ( "invalid schedule: %v" , r ) )
}
} ( )
2017-08-10 16:44:00 +00:00
if res , err := cron . ParseStandard ( itm . Spec . Schedule ) ; err != nil {
2018-08-29 19:52:09 +00:00
log . WithError ( errors . WithStack ( err ) ) . WithField ( "schedule" , itm . Spec . Schedule ) . Debug ( "Error parsing schedule" )
2017-08-02 17:27:17 +00:00
validationErrors = append ( validationErrors , fmt . Sprintf ( "invalid schedule: %v" , err ) )
} else {
schedule = res
}
} ( )
if len ( validationErrors ) > 0 {
return nil , validationErrors
}
return schedule , nil
}
2022-09-29 07:01:30 +00:00
// checkIfBackupInNewOrProgress check whether there are backups created by this schedule still in New or InProgress state
func ( c * scheduleReconciler ) checkIfBackupInNewOrProgress ( schedule * velerov1 . Schedule ) bool {
log := c . logger . WithField ( "schedule" , kubeutil . NamespaceAndName ( schedule ) )
backupList := & velerov1 . BackupList { }
options := & client . ListOptions {
Namespace : schedule . Namespace ,
LabelSelector : labels . Set ( map [ string ] string {
velerov1 . ScheduleNameLabel : schedule . Name ,
} ) . AsSelector ( ) ,
}
err := c . List ( context . Background ( ) , backupList , options )
if err != nil {
log . Errorf ( "fail to list backup for schedule %s/%s: %s" , schedule . Namespace , schedule . Name , err . Error ( ) )
return true
}
for _ , backup := range backupList . Items {
if backup . Status . Phase == velerov1 . BackupPhaseNew || backup . Status . Phase == velerov1 . BackupPhaseInProgress {
return true
}
}
log . Debugf ( "Schedule %s/%s still has backups are in InProgress or New state, skip submitting backup to avoid overlap." , schedule . Namespace , schedule . Name )
return false
}
// ifDue check whether schedule is due to create a new backup.
func ( c * scheduleReconciler ) ifDue ( schedule * velerov1 . Schedule , cronSchedule cron . Schedule ) bool {
isDue , nextRunTime := getNextRunTime ( schedule , cronSchedule , c . clock . Now ( ) )
log := c . logger . WithField ( "schedule" , kubeutil . NamespaceAndName ( schedule ) )
2017-08-02 17:27:17 +00:00
if ! isDue {
2018-11-02 21:55:47 +00:00
log . WithField ( "nextRunTime" , nextRunTime ) . Debug ( "Schedule is not due, skipping" )
2022-09-29 07:01:30 +00:00
return false
2017-08-02 17:27:17 +00:00
}
2022-09-29 07:01:30 +00:00
return true
}
// submitBackup create a backup from schedule.
func ( c * scheduleReconciler ) submitBackup ( ctx context . Context , schedule * velerov1 . Schedule ) error {
c . logger . WithField ( "schedule" , schedule . Namespace + "/" + schedule . Name ) . Info ( "Schedule is due, going to submit backup." )
now := c . clock . Now ( )
2017-08-02 17:27:17 +00:00
// Don't attempt to "catch up" if there are any missed or failed runs - simply
// trigger a Backup if it's time.
2022-09-29 07:01:30 +00:00
backup := getBackup ( schedule , now )
2022-03-11 02:16:41 +00:00
if err := c . Create ( ctx , backup ) ; err != nil {
2017-09-14 21:27:31 +00:00
return errors . Wrap ( err , "error creating Backup" )
2017-08-02 17:27:17 +00:00
}
2022-09-29 07:01:30 +00:00
original := schedule . DeepCopy ( )
schedule . Status . LastBackup = & metav1 . Time { Time : now }
2017-08-02 17:27:17 +00:00
2022-09-29 07:01:30 +00:00
if err := c . Patch ( ctx , schedule , client . MergeFrom ( original ) ) ; err != nil {
return errors . Wrapf ( err , "error updating Schedule's LastBackup time to %v" , schedule . Status . LastBackup )
2017-08-02 17:27:17 +00:00
}
return nil
}
2022-03-11 02:16:41 +00:00
func getNextRunTime ( schedule * velerov1 . Schedule , cronSchedule cron . Schedule , asOf time . Time ) ( bool , time . Time ) {
2019-10-14 16:20:28 +00:00
var lastBackupTime time . Time
if schedule . Status . LastBackup != nil {
lastBackupTime = schedule . Status . LastBackup . Time
2021-10-26 23:31:58 +00:00
} else {
lastBackupTime = schedule . CreationTimestamp . Time
2019-10-14 16:20:28 +00:00
}
2017-08-02 17:27:17 +00:00
nextRunTime := cronSchedule . Next ( lastBackupTime )
return asOf . After ( nextRunTime ) , nextRunTime
}
2022-03-11 02:16:41 +00:00
func getBackup ( item * velerov1 . Schedule , timestamp time . Time ) * velerov1 . Backup {
2020-05-28 19:31:25 +00:00
name := item . TimestampedName ( timestamp )
2023-03-21 06:39:25 +00:00
return builder .
2019-08-23 20:03:51 +00:00
ForBackup ( item . Namespace , name ) .
FromSchedule ( item ) .
Result ( )
2017-08-02 17:27:17 +00:00
}