@@ -10,6 +10,7 @@ import (
10
10
"strconv"
11
11
"strings"
12
12
"time"
13
+ "unicode"
13
14
14
15
"github.com/pkg/errors"
15
16
@@ -305,12 +306,21 @@ func (m *Manager) DestroySnapshot(snapshotName string) error {
305
306
return nil
306
307
}
307
308
308
- // CleanupSnapshots destroys old snapshots considering retention limit.
309
+ // CleanupSnapshots destroys old snapshots considering retention limit and related clones .
309
310
func (m * Manager ) CleanupSnapshots (retentionLimit int ) ([]string , error ) {
311
+ clonesCmd := fmt .Sprintf ("zfs list -S clones -o name,origin -H -r %s" , m .config .Pool .Name )
312
+
313
+ clonesOutput , err := m .runner .Run (clonesCmd )
314
+ if err != nil {
315
+ return nil , errors .Wrap (err , "failed to list snapshots" )
316
+ }
317
+
318
+ busySnapshots := m .getBusySnapshotList (clonesOutput )
319
+
310
320
cleanupCmd := fmt .Sprintf (
311
- "zfs list -t snapshot -H -o name -s %s -s creation -r %s | grep -v clone | head -n -%d " +
321
+ "zfs list -t snapshot -H -o name -s %s -s creation -r %s | grep -v clone | head -n -%d %s " +
312
322
"| xargs -n1 --no-run-if-empty zfs destroy -R " ,
313
- dataStateAtLabel , m .config .Pool .Name , retentionLimit )
323
+ dataStateAtLabel , m .config .Pool .Name , retentionLimit , excludeBusySnapshots ( busySnapshots ) )
314
324
315
325
out , err := m .runner .Run (cleanupCmd )
316
326
if err != nil {
@@ -322,6 +332,52 @@ func (m *Manager) CleanupSnapshots(retentionLimit int) ([]string, error) {
322
332
return lines , nil
323
333
}
324
334
335
+ func (m * Manager ) getBusySnapshotList (clonesOutput string ) []string {
336
+ systemClones , userClones := make (map [string ]string ), make (map [string ]struct {})
337
+
338
+ userClonePrefix := m .config .Pool .Name + "/" + util .ClonePrefix
339
+
340
+ for _ , line := range strings .Split (clonesOutput , "\n " ) {
341
+ cloneLine := strings .FieldsFunc (line , unicode .IsSpace )
342
+
343
+ if len (cloneLine ) != 2 || cloneLine [1 ] == "-" {
344
+ continue
345
+ }
346
+
347
+ if strings .HasPrefix (cloneLine [0 ], userClonePrefix ) {
348
+ origin := cloneLine [1 ]
349
+
350
+ if idx := strings .Index (origin , "@" ); idx != - 1 {
351
+ origin = origin [:idx ]
352
+ }
353
+
354
+ userClones [origin ] = struct {}{}
355
+
356
+ continue
357
+ }
358
+
359
+ systemClones [cloneLine [0 ]] = cloneLine [1 ]
360
+ }
361
+
362
+ busySnapshots := make ([]string , 0 , len (userClones ))
363
+
364
+ for userClone := range userClones {
365
+ busySnapshots = append (busySnapshots , systemClones [userClone ])
366
+ }
367
+
368
+ return busySnapshots
369
+ }
370
+
371
+ // excludeBusySnapshots excludes snapshots that match a pattern by name.
372
+ // The exclusion logic relies on the fact that snapshots have unique substrings (timestamps).
373
+ func excludeBusySnapshots (busySnapshots []string ) string {
374
+ if len (busySnapshots ) == 0 {
375
+ return ""
376
+ }
377
+
378
+ return fmt .Sprintf ("| grep -Ev '%s' " , strings .Join (busySnapshots , "|" ))
379
+ }
380
+
325
381
// GetSessionState returns a state of a session.
326
382
func (m * Manager ) GetSessionState (name string ) (* resources.SessionState , error ) {
327
383
entries , err := m .listFilesystems (m .config .Pool .Name )
0 commit comments