2017-07-06 13:03:44 +02:00
|
|
|
package cmd
|
2017-06-22 19:04:48 +02:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2017-07-06 13:03:44 +02:00
|
|
|
"github.com/spf13/cobra"
|
2017-06-22 19:04:48 +02:00
|
|
|
"github.com/zrepl/zrepl/util"
|
|
|
|
"github.com/zrepl/zrepl/zfs"
|
2017-07-06 13:03:44 +02:00
|
|
|
"os"
|
2017-06-22 19:04:48 +02:00
|
|
|
"sort"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2017-07-06 13:03:44 +02:00
|
|
|
var pruneArgs struct {
|
|
|
|
job string
|
|
|
|
dryRun bool
|
|
|
|
}
|
|
|
|
|
|
|
|
var PruneCmd = &cobra.Command{
|
|
|
|
Use: "prune",
|
|
|
|
Short: "perform pruning",
|
|
|
|
Run: cmdPrune,
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
PruneCmd.Flags().StringVar(&pruneArgs.job, "job", "", "job to run")
|
|
|
|
PruneCmd.Flags().BoolVarP(&pruneArgs.dryRun, "dryrun", "n", false, "dry run")
|
|
|
|
RootCmd.AddCommand(PruneCmd)
|
|
|
|
}
|
|
|
|
|
|
|
|
func cmdPrune(cmd *cobra.Command, args []string) {
|
|
|
|
|
2017-07-06 15:36:53 +02:00
|
|
|
if len(args) < 1 {
|
|
|
|
log.Printf("must specify exactly one job as positional argument")
|
|
|
|
os.Exit(1)
|
|
|
|
}
|
2017-07-06 13:03:44 +02:00
|
|
|
|
2017-07-06 15:36:53 +02:00
|
|
|
job, ok := conf.Prunes[args[0]]
|
|
|
|
if !ok {
|
|
|
|
log.Printf("could not find prune job: %s", args[0])
|
|
|
|
os.Exit(1)
|
2017-07-06 13:03:44 +02:00
|
|
|
}
|
|
|
|
|
2017-07-06 15:36:53 +02:00
|
|
|
log.Printf("Beginning prune job:\n%s", job)
|
|
|
|
ctx := PruneContext{job, time.Now(), pruneArgs.dryRun}
|
|
|
|
err := doPrune(ctx, log)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Prune job failed with error: %s", err)
|
2017-07-06 13:03:44 +02:00
|
|
|
os.Exit(1)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2017-06-22 19:04:48 +02:00
|
|
|
type PruneContext struct {
|
2017-07-06 15:36:53 +02:00
|
|
|
Prune *Prune
|
2017-06-22 19:04:48 +02:00
|
|
|
Now time.Time
|
|
|
|
DryRun bool
|
|
|
|
}
|
|
|
|
|
|
|
|
type retentionGridAdaptor struct {
|
|
|
|
zfs.FilesystemVersion
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a retentionGridAdaptor) Date() time.Time {
|
|
|
|
return a.Creation
|
|
|
|
}
|
|
|
|
|
|
|
|
func (a retentionGridAdaptor) LessThan(b util.RetentionGridEntry) bool {
|
|
|
|
return a.CreateTXG < b.(retentionGridAdaptor).CreateTXG
|
|
|
|
}
|
|
|
|
|
|
|
|
func doPrune(ctx PruneContext, log Logger) error {
|
|
|
|
|
|
|
|
if ctx.DryRun {
|
|
|
|
log.Printf("doing dry run")
|
|
|
|
}
|
|
|
|
|
|
|
|
prune := ctx.Prune
|
|
|
|
|
|
|
|
// ZFSListSnapsFiltered --> todo can extend fsfilter or need new? Have already something per fs
|
|
|
|
// Dedicated snapshot object? Adaptor object to FilesystemVersion?
|
|
|
|
|
|
|
|
filesystems, err := zfs.ZFSListMapping(prune.DatasetFilter)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error applying filesystem filter: %s", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, fs := range filesystems {
|
|
|
|
|
|
|
|
fsversions, err := zfs.ZFSListFilesystemVersions(fs, prune.SnapshotFilter)
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("error listing filesytem versions of %s: %s", fs, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(fsversions) == 0 {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
adaptors := make([]util.RetentionGridEntry, len(fsversions))
|
|
|
|
for fsv := range fsversions {
|
|
|
|
adaptors[fsv] = retentionGridAdaptor{fsversions[fsv]}
|
|
|
|
}
|
|
|
|
|
|
|
|
sort.SliceStable(adaptors, func(i, j int) bool {
|
|
|
|
return adaptors[i].LessThan(adaptors[j])
|
|
|
|
})
|
|
|
|
ctx.Now = adaptors[len(adaptors)-1].Date()
|
|
|
|
|
|
|
|
describe := func(a retentionGridAdaptor) string {
|
|
|
|
timeSince := a.Creation.Sub(ctx.Now)
|
|
|
|
const day time.Duration = 24 * time.Hour
|
|
|
|
days := timeSince / day
|
|
|
|
remainder := timeSince % day
|
|
|
|
return fmt.Sprintf("%s@%dd%s from now", a.ToAbsPath(fs), days, remainder)
|
|
|
|
}
|
|
|
|
|
|
|
|
keep, remove := prune.RetentionPolicy.FitEntries(ctx.Now, adaptors)
|
|
|
|
for _, a := range remove {
|
|
|
|
r := a.(retentionGridAdaptor)
|
|
|
|
log.Printf("remove %s", describe(r))
|
|
|
|
// do echo what we'll do and exec zfs destroy if not dry run
|
|
|
|
// special handling for EBUSY (zfs hold)
|
|
|
|
// error handling for clones? just echo to cli, skip over, and exit with non-zero status code (we're idempotent)
|
|
|
|
if !ctx.DryRun {
|
|
|
|
err := zfs.ZFSDestroy(r.ToAbsPath(fs))
|
|
|
|
if err != nil {
|
|
|
|
// handle
|
|
|
|
log.Printf("error: %s", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, a := range keep {
|
|
|
|
r := a.(retentionGridAdaptor)
|
|
|
|
log.Printf("would keep %s", describe(r))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
|
|
|
}
|