2018-08-26 19:20:08 +02:00
|
|
|
package retentiongrid
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/pkg/errors"
|
2018-08-27 22:21:45 +02:00
|
|
|
"github.com/zrepl/zrepl/config"
|
2018-08-26 19:20:08 +02:00
|
|
|
"github.com/zrepl/zrepl/zfs"
|
|
|
|
"math"
|
|
|
|
"sort"
|
|
|
|
"strconv"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
type GridPrunePolicy struct {
|
|
|
|
retentionGrid *retentionGrid
|
|
|
|
keepBookmarks int
|
|
|
|
}
|
|
|
|
|
|
|
|
const GridPrunePolicyMaxBookmarksKeepAll = -1
|
|
|
|
|
|
|
|
type retentionGridAdaptor struct {
|
|
|
|
zfs.FilesystemVersion
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a retentionGridAdaptor) Date() time.Time {
|
|
|
|
return a.Creation
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a retentionGridAdaptor) LessThan(b RetentionGridEntry) bool {
|
|
|
|
return a.CreateTXG < b.(retentionGridAdaptor).CreateTXG
|
|
|
|
}
|
|
|
|
|
|
|
|
// Prune filters snapshots with the retention grid.
|
|
|
|
// Bookmarks are deleted such that keepBookmarks are kept in the end.
|
|
|
|
// The oldest bookmarks are removed first.
|
|
|
|
func (p *GridPrunePolicy) Prune(_ *zfs.DatasetPath, versions []zfs.FilesystemVersion) (keep, remove []zfs.FilesystemVersion, err error) {
|
|
|
|
skeep, sremove := p.pruneSnapshots(versions)
|
|
|
|
keep, remove = p.pruneBookmarks(skeep)
|
|
|
|
remove = append(remove, sremove...)
|
|
|
|
return keep, remove, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *GridPrunePolicy) pruneSnapshots(versions []zfs.FilesystemVersion) (keep, remove []zfs.FilesystemVersion) {
|
|
|
|
|
|
|
|
// Build adaptors for retention grid
|
|
|
|
keep = []zfs.FilesystemVersion{}
|
|
|
|
adaptors := make([]RetentionGridEntry, 0)
|
|
|
|
for fsv := range versions {
|
|
|
|
if versions[fsv].Type != zfs.Snapshot {
|
|
|
|
keep = append(keep, versions[fsv])
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
adaptors = append(adaptors, retentionGridAdaptor{versions[fsv]})
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.SliceStable(adaptors, func(i, j int) bool {
|
|
|
|
return adaptors[i].LessThan(adaptors[j])
|
|
|
|
})
|
|
|
|
now := adaptors[len(adaptors)-1].Date()
|
|
|
|
|
|
|
|
// Evaluate retention grid
|
|
|
|
keepa, removea := p.retentionGrid.FitEntries(now, adaptors)
|
|
|
|
|
|
|
|
// Revert adaptors
|
|
|
|
for i := range keepa {
|
|
|
|
keep = append(keep, keepa[i].(retentionGridAdaptor).FilesystemVersion)
|
|
|
|
}
|
|
|
|
remove = make([]zfs.FilesystemVersion, len(removea))
|
|
|
|
for i := range removea {
|
|
|
|
remove[i] = removea[i].(retentionGridAdaptor).FilesystemVersion
|
|
|
|
}
|
|
|
|
return
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
func (p *GridPrunePolicy) pruneBookmarks(versions []zfs.FilesystemVersion) (keep, remove []zfs.FilesystemVersion) {
|
|
|
|
|
|
|
|
if p.keepBookmarks == GridPrunePolicyMaxBookmarksKeepAll {
|
|
|
|
return versions, []zfs.FilesystemVersion{}
|
|
|
|
}
|
|
|
|
|
|
|
|
keep = []zfs.FilesystemVersion{}
|
|
|
|
bookmarks := make([]zfs.FilesystemVersion, 0)
|
|
|
|
for fsv := range versions {
|
|
|
|
if versions[fsv].Type != zfs.Bookmark {
|
|
|
|
keep = append(keep, versions[fsv])
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
bookmarks = append(bookmarks, versions[fsv])
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(bookmarks) == 0 {
|
|
|
|
return keep, []zfs.FilesystemVersion{}
|
|
|
|
}
|
|
|
|
if len(bookmarks) < p.keepBookmarks {
|
|
|
|
keep = append(keep, bookmarks...)
|
|
|
|
return keep, []zfs.FilesystemVersion{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NOTE: sorting descending by descending by createtxg <=> sorting ascending wrt creation time
|
|
|
|
sort.SliceStable(bookmarks, func(i, j int) bool {
|
|
|
|
return (bookmarks[i].CreateTXG > bookmarks[j].CreateTXG)
|
|
|
|
})
|
|
|
|
|
|
|
|
keep = append(keep, bookmarks[:p.keepBookmarks]...)
|
|
|
|
remove = bookmarks[p.keepBookmarks:]
|
|
|
|
|
|
|
|
return keep, remove
|
|
|
|
}
|
|
|
|
|
|
|
|
func ParseGridPrunePolicy(in config.PruneGrid, willSeeBookmarks bool) (p *GridPrunePolicy, err error) {
|
|
|
|
|
|
|
|
const KeepBookmarksAllString = "all"
|
|
|
|
|
|
|
|
// Assert intervals are of increasing length (not necessarily required, but indicates config mistake)
|
|
|
|
lastDuration := time.Duration(0)
|
|
|
|
for i := range in.Grid {
|
|
|
|
|
|
|
|
if in.Grid[i].Length() < lastDuration {
|
|
|
|
// If all intervals before were keep=all, this is ok
|
|
|
|
allPrevKeepCountAll := true
|
|
|
|
for j := i - 1; allPrevKeepCountAll && j >= 0; j-- {
|
|
|
|
allPrevKeepCountAll = in.Grid[j].KeepCount() == config.RetentionGridKeepCountAll
|
|
|
|
}
|
|
|
|
if allPrevKeepCountAll {
|
|
|
|
goto isMonotonicIncrease
|
|
|
|
}
|
|
|
|
err = errors.New("retention grid interval length must be monotonically increasing")
|
|
|
|
return
|
|
|
|
}
|
|
|
|
isMonotonicIncrease:
|
|
|
|
lastDuration = in.Grid[i].Length()
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse keepBookmarks
|
|
|
|
keepBookmarks := 0
|
|
|
|
if in.KeepBookmarks == KeepBookmarksAllString || (in.KeepBookmarks == "" && !willSeeBookmarks) {
|
|
|
|
keepBookmarks = GridPrunePolicyMaxBookmarksKeepAll
|
|
|
|
} else {
|
|
|
|
i, err := strconv.ParseInt(in.KeepBookmarks, 10, 32)
|
|
|
|
if err != nil || i <= 0 || i > math.MaxInt32 {
|
|
|
|
return nil, errors.Errorf("keep_bookmarks must be positive integer or 'all'")
|
|
|
|
}
|
|
|
|
keepBookmarks = int(i)
|
|
|
|
}
|
|
|
|
|
|
|
|
retentionIntervals := make([]RetentionInterval, len(in.Grid))
|
|
|
|
for i := range in.Grid {
|
|
|
|
retentionIntervals[i] = &in.Grid[i]
|
|
|
|
}
|
|
|
|
|
|
|
|
return &GridPrunePolicy{
|
|
|
|
newRetentionGrid(retentionIntervals),
|
|
|
|
keepBookmarks,
|
|
|
|
}, nil
|
|
|
|
}
|