zrepl/cmd/config_prune_grid.go

202 lines
4.8 KiB
Go
Raw Normal View History

package cmd
import (
"fmt"
2017-09-11 15:37:45 +02:00
"github.com/mitchellh/mapstructure"
"github.com/pkg/errors"
2017-09-11 15:37:45 +02:00
"github.com/zrepl/zrepl/util"
"github.com/zrepl/zrepl/zfs"
2017-09-11 15:37:45 +02:00
"regexp"
"sort"
"strconv"
"strings"
2017-09-11 15:37:45 +02:00
"time"
)
type GridPrunePolicy struct {
2017-09-11 15:37:45 +02:00
RetentionGrid *util.RetentionGrid
}
type retentionGridAdaptor struct {
zfs.FilesystemVersion
}
func (a retentionGridAdaptor) Date() time.Time {
return a.Creation
}
2017-09-11 15:37:45 +02:00
func (a retentionGridAdaptor) LessThan(b util.RetentionGridEntry) bool {
return a.CreateTXG < b.(retentionGridAdaptor).CreateTXG
}
2017-09-13 23:46:57 +02:00
func (p *GridPrunePolicy) Prune(_ *zfs.DatasetPath, versions []zfs.FilesystemVersion) (keep, remove []zfs.FilesystemVersion, err error) {
// Build adaptors for retention grid
2017-09-11 15:37:45 +02:00
adaptors := make([]util.RetentionGridEntry, len(versions))
for fsv := range versions {
adaptors[fsv] = 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
keep = make([]zfs.FilesystemVersion, len(keepa))
for i := range keepa {
keep[i] = keepa[i].(retentionGridAdaptor).FilesystemVersion
}
remove = make([]zfs.FilesystemVersion, len(removea))
for i := range removea {
remove[i] = removea[i].(retentionGridAdaptor).FilesystemVersion
}
return
}
func parseGridPrunePolicy(e map[string]interface{}) (p *GridPrunePolicy, err error) {
var i struct {
2017-09-11 15:37:45 +02:00
Grid string
}
if err = mapstructure.Decode(e, &i); err != nil {
err = errors.Wrapf(err, "mapstructure error")
return
}
p = &GridPrunePolicy{}
// Parse grid policy
intervals, err := parseRetentionGridIntervalsString(i.Grid)
if err != nil {
err = fmt.Errorf("cannot parse retention grid: %s", err)
return
}
// Assert intervals are of increasing length (not necessarily required, but indicates config mistake)
lastDuration := time.Duration(0)
for i := range intervals {
if intervals[i].Length < lastDuration {
err = fmt.Errorf("retention grid interval length must be monotonically increasing:"+
"interval %d is shorter than %d", i+1, i)
return
} else {
lastDuration = intervals[i].Length
}
}
2017-09-11 15:37:45 +02:00
p.RetentionGrid = util.NewRetentionGrid(intervals)
return
}
var durationStringRegex *regexp.Regexp = regexp.MustCompile(`^\s*(\d+)\s*(s|m|h|d|w)\s*$`)
func parseDuration(e string) (d time.Duration, err error) {
comps := durationStringRegex.FindStringSubmatch(e)
if len(comps) != 3 {
err = fmt.Errorf("does not match regex: %s %#v", e, comps)
return
}
durationFactor, err := strconv.ParseInt(comps[1], 10, 64)
if err != nil {
return
}
var durationUnit time.Duration
switch comps[2] {
case "s":
durationUnit = time.Second
case "m":
durationUnit = time.Minute
case "h":
durationUnit = time.Hour
case "d":
durationUnit = 24 * time.Hour
case "w":
durationUnit = 24 * 7 * time.Hour
default:
err = fmt.Errorf("contains unknown time unit '%s'", comps[2])
return
}
d = time.Duration(durationFactor) * durationUnit
return
}
var retentionStringIntervalRegex *regexp.Regexp = regexp.MustCompile(`^\s*(\d+)\s*x\s*([^\(]+)\s*(\((.*)\))?\s*$`)
2017-09-11 15:37:45 +02:00
func parseRetentionGridIntervalString(e string) (intervals []util.RetentionInterval, err error) {
comps := retentionStringIntervalRegex.FindStringSubmatch(e)
if comps == nil {
err = fmt.Errorf("retention string does not match expected format")
return
}
times, err := strconv.Atoi(comps[1])
if err != nil {
return nil, err
} else if times <= 0 {
return nil, fmt.Errorf("contains factor <= 0")
}
duration, err := parseDuration(comps[2])
if err != nil {
return nil, err
}
keepCount := 1
if comps[3] != "" {
// Decompose key=value, comma separated
// For now, only keep_count is supported
re := regexp.MustCompile(`^\s*keep=(.+)\s*$`)
res := re.FindStringSubmatch(comps[4])
if res == nil || len(res) != 2 {
err = fmt.Errorf("interval parameter contains unknown parameters")
return
}
if res[1] == "all" {
2017-09-11 15:37:45 +02:00
keepCount = util.RetentionGridKeepCountAll
} else {
keepCount, err = strconv.Atoi(res[1])
if err != nil {
err = fmt.Errorf("cannot parse keep_count value")
return
}
}
}
2017-09-11 15:37:45 +02:00
intervals = make([]util.RetentionInterval, times)
for i := range intervals {
2017-09-11 15:37:45 +02:00
intervals[i] = util.RetentionInterval{
Length: duration,
KeepCount: keepCount,
}
}
return
}
2017-09-11 15:37:45 +02:00
func parseRetentionGridIntervalsString(s string) (intervals []util.RetentionInterval, err error) {
ges := strings.Split(s, "|")
2017-09-11 15:37:45 +02:00
intervals = make([]util.RetentionInterval, 0, 7*len(ges))
for intervalIdx, e := range ges {
parsed, err := parseRetentionGridIntervalString(e)
if err != nil {
return nil, fmt.Errorf("cannot parse interval %d of %d: %s: %s", intervalIdx+1, len(ges), err, strings.TrimSpace(e))
}
intervals = append(intervals, parsed...)
}
return
2017-09-11 15:37:45 +02:00
}