@@ -37,16 +37,19 @@ import (
3737 operator "github.com/arangodb/kube-arangodb/pkg/operatorV2"
3838 "github.com/arangodb/kube-arangodb/pkg/operatorV2/event"
3939 "github.com/arangodb/kube-arangodb/pkg/operatorV2/operation"
40+ "github.com/arangodb/kube-arangodb/pkg/util"
4041 "github.com/arangodb/kube-arangodb/pkg/util/errors"
4142 "github.com/arangodb/kube-arangodb/pkg/util/globals"
4243 "github.com/arangodb/kube-arangodb/pkg/util/k8sutil"
44+ "github.com/arangodb/kube-arangodb/pkg/util/k8sutil/kerrors"
4345)
4446
4547const (
46- backupCreated = "ArangoBackupCreated"
47- policyError = "Error"
48- rescheduled = "Rescheduled"
49- scheduleSkipped = "ScheduleSkipped"
48+ backupCreated = "ArangoBackupCreated"
49+ policyError = "Error"
50+ rescheduled = "Rescheduled"
51+ scheduleSkipped = "ScheduleSkipped"
52+ cleanedUpOldBackups = "CleanedUpOldBackups"
5053)
5154
5255type handler struct {
@@ -138,7 +141,6 @@ func (h *handler) processBackupPolicy(policy *backupApi.ArangoBackupPolicy) back
138141
139142 // Schedule new deployments
140143 listOptions := meta.ListOptions {}
141-
142144 if policy .Spec .DeploymentSelector != nil &&
143145 (policy .Spec .DeploymentSelector .MatchLabels != nil &&
144146 len (policy .Spec .DeploymentSelector .MatchLabels ) > 0 ||
@@ -147,7 +149,6 @@ func (h *handler) processBackupPolicy(policy *backupApi.ArangoBackupPolicy) back
147149 }
148150
149151 deployments , err := h .client .DatabaseV1 ().ArangoDeployments (policy .Namespace ).List (context .Background (), listOptions )
150-
151152 if err != nil {
152153 h .eventRecorder .Warning (policy , policyError , "Policy Error: %s" , err .Error ())
153154
@@ -157,27 +158,39 @@ func (h *handler) processBackupPolicy(policy *backupApi.ArangoBackupPolicy) back
157158 }
158159 }
159160
161+ needToListBackups := ! policy .Spec .GetAllowConcurrent () || policy .Spec .MaxBackups > 0
160162 for _ , deployment := range deployments .Items {
161163 depl := deployment .DeepCopy ()
164+ ctx := context .Background ()
162165
163- if ! policy . Spec . GetAllowConcurrent () {
164- previousBackupInProgress , err := h .isPreviousBackupInProgress ( context . Background () , depl , policy .Name )
166+ if needToListBackups {
167+ backups , err := h .listAllBackupsForPolicy ( ctx , depl , policy .Name )
165168 if err != nil {
166169 h .eventRecorder .Warning (policy , policyError , "Policy Error: %s" , err .Error ())
167170 return backupApi.ArangoBackupPolicyStatus {
168171 Scheduled : policy .Status .Scheduled ,
169172 Message : fmt .Sprintf ("backup creation failed: %s" , err .Error ()),
170173 }
171174 }
172- if previousBackupInProgress {
175+ if numRemoved , err := h .removeOldHealthyBackups (ctx , policy .Spec .MaxBackups , backups ); err != nil {
176+ h .eventRecorder .Warning (policy , policyError , "Policy Error: %s" , err .Error ())
177+ return backupApi.ArangoBackupPolicyStatus {
178+ Scheduled : policy .Status .Scheduled ,
179+ Message : fmt .Sprintf ("automatic backup cleanup failed: %s" , err .Error ()),
180+ }
181+ } else if numRemoved > 0 {
182+ eventMsg := fmt .Sprintf ("Cleaned up %d old backups due to maxBackups setting %s/%s" , numRemoved , deployment .Namespace , deployment .Name )
183+ h .eventRecorder .Normal (policy , cleanedUpOldBackups , eventMsg )
184+ }
185+ if ! policy .Spec .GetAllowConcurrent () && h .isPreviousBackupInProgress (backups ) {
173186 eventMsg := fmt .Sprintf ("Skipping ArangoBackup creation because earlier backup still running %s/%s" , deployment .Namespace , deployment .Name )
174187 h .eventRecorder .Normal (policy , scheduleSkipped , eventMsg )
175188 continue
176189 }
177190 }
178191
179192 b := policy .NewBackup (depl )
180- if _ , err := h .client .BackupV1 ().ArangoBackups (b .Namespace ).Create (context . Background () , b , meta.CreateOptions {}); err != nil {
193+ if _ , err := h .client .BackupV1 ().ArangoBackups (b .Namespace ).Create (ctx , b , meta.CreateOptions {}); err != nil {
181194 h .eventRecorder .Warning (policy , policyError , "Policy Error: %s" , err .Error ())
182195
183196 return backupApi.ArangoBackupPolicyStatus {
@@ -206,7 +219,7 @@ func (*handler) CanBeHandled(item operation.Item) bool {
206219 item .Kind == backup .ArangoBackupPolicyResourceKind
207220}
208221
209- func (h * handler ) listAllBackupsForPolicy (ctx context.Context , d * deployment.ArangoDeployment , policyName string ) ([] * backupApi.ArangoBackup , error ) {
222+ func (h * handler ) listAllBackupsForPolicy (ctx context.Context , d * deployment.ArangoDeployment , policyName string ) (util. List [ * backupApi.ArangoBackup ] , error ) {
210223 var r []* backupApi.ArangoBackup
211224
212225 if err := k8sutil .APIList [* backupApi.ArangoBackupList ](ctx , h .client .BackupV1 ().ArangoBackups (d .Namespace ), meta.ListOptions {
@@ -228,37 +241,57 @@ func (h *handler) listAllBackupsForPolicy(ctx context.Context, d *deployment.Ara
228241
229242 return nil
230243 }); err != nil {
231- return nil , err
244+ return nil , errors . Wrap ( err , "Failed to list ArangoBackups" )
232245 }
233246
234247 return r , nil
235248}
236249
237- func (h * handler ) isPreviousBackupInProgress (ctx context.Context , d * deployment.ArangoDeployment , policyName string ) (bool , error ) {
238- // It would be nice to List CRs with fieldSelector set, but this is not supported:
239- // https://github.com/kubernetes/kubernetes/issues/53459
240- // Instead we fetch all ArangoBackups:
241- backups , err := h .listAllBackupsForPolicy (ctx , d , policyName )
242- if err != nil {
243- return false , errors .Wrap (err , "Failed to list ArangoBackups" )
244- }
245-
246- for _ , b := range backups {
247- // Check if we are in the failed state
250+ func (h * handler ) isPreviousBackupInProgress (backups util.List [* backupApi.ArangoBackup ]) bool {
251+ inProgressBackups := backups .Count (func (b * backupApi.ArangoBackup ) bool {
248252 switch b .Status .State {
249253 case backupApi .ArangoBackupStateFailed :
250- continue
254+ return false
251255 }
252256
253257 if b .Spec .Download != nil {
254- continue
258+ return false
255259 }
256260
257261 // Backup is not yet done
258262 if b .Status .Backup == nil {
259- return true , nil
263+ return true
260264 }
265+ return false
266+ })
267+ return inProgressBackups > 0
268+ }
269+
270+ func (h * handler ) removeOldHealthyBackups (ctx context.Context , limit int , backups util.List [* backupApi.ArangoBackup ]) (int , error ) {
271+ if limit <= 0 {
272+ // no limit set
273+ return 0 , nil
261274 }
262275
263- return false , nil
276+ healthyBackups := backups .Filter (func (b * backupApi.ArangoBackup ) bool {
277+ return b .Status .State == backupApi .ArangoBackupStateReady
278+ }).Sort (func (a * backupApi.ArangoBackup , b * backupApi.ArangoBackup ) bool {
279+ // newest first
280+ return a .CreationTimestamp .After (b .CreationTimestamp .Time )
281+ })
282+ if len (healthyBackups ) < limit {
283+ return 0 , nil
284+ }
285+ toDelete := healthyBackups [limit - 1 :]
286+ numDeleted := 0
287+ for _ , b := range toDelete {
288+ err := globals .GetGlobalTimeouts ().Kubernetes ().RunWithTimeout (ctx , func (ctxChild context.Context ) error {
289+ return h .client .BackupV1 ().ArangoBackups (b .Namespace ).Delete (ctx , b .Name , meta.DeleteOptions {})
290+ })
291+ if err != nil && ! kerrors .IsNotFound (err ) {
292+ return numDeleted , errors .Wrapf (err , "could not trigger deletion of backup %s" , b .Name )
293+ }
294+ numDeleted ++
295+ }
296+ return numDeleted , nil
264297}
0 commit comments