receiving side: placeholder as simple on|off property

This commit is contained in:
Christian Schwarz
2019-03-19 17:43:28 +01:00
parent 6f7467e8d8
commit 2f2e6e6a00
10 changed files with 306 additions and 132 deletions
+108
View File
@@ -0,0 +1,108 @@
package client
import (
"context"
"fmt"
"github.com/pkg/errors"
"github.com/spf13/pflag"
"github.com/zrepl/zrepl/zfs"
"github.com/zrepl/zrepl/cli"
"github.com/zrepl/zrepl/config"
)
var (
MigrateCmd = &cli.Subcommand{
Use: "migrate",
Short: "perform migration of the on-disk / zfs properties",
SetupSubcommands: func() []*cli.Subcommand {
return migrations
},
}
)
type migration struct {
name string
method func(config *config.Config, args []string) error
}
var migrations = []*cli.Subcommand{
&cli.Subcommand{
Use: "0.0.X:0.1:placeholder",
Run: doMigratePlaceholder0_1,
SetupFlags: func(f *pflag.FlagSet) {
f.BoolVar(&migratePlaceholder0_1Args.dryRun, "dry-run", false, "dry run")
},
},
}
var migratePlaceholder0_1Args struct {
dryRun bool
}
func doMigratePlaceholder0_1(sc *cli.Subcommand, args []string) error {
if len(args) != 0 {
return fmt.Errorf("migration does not take arguments, got %v", args)
}
cfg := sc.Config()
ctx := context.Background()
allFSS, err := zfs.ZFSListMapping(ctx, zfs.NoFilter())
if err != nil {
return errors.Wrap(err, "cannot list filesystems")
}
type workItem struct {
jobName string
rootFS *zfs.DatasetPath
fss []*zfs.DatasetPath
}
var wis []workItem
for i, j := range cfg.Jobs {
var rfsS string
switch job := j.Ret.(type) {
case *config.SinkJob:
rfsS = job.RootFS
case *config.PullJob:
rfsS = job.RootFS
default:
fmt.Printf("ignoring job %q (%d/%d, type %T)\n", j.Name(), i, len(cfg.Jobs), j.Ret)
continue
}
rfs, err := zfs.NewDatasetPath(rfsS)
if err != nil {
return errors.Wrapf(err, "root fs for job %q is not a valid dataset path", j.Name())
}
var fss []*zfs.DatasetPath
for _, fs := range allFSS {
if fs.HasPrefix(rfs) {
fss = append(fss, fs)
}
}
wis = append(wis, workItem{j.Name(), rfs, fss})
}
for _, wi := range wis {
fmt.Printf("job %q => migrate filesystems below root_fs %q\n", wi.jobName, wi.rootFS.ToString())
if len(wi.fss) == 0 {
fmt.Printf("\tno filesystems\n")
continue
}
for _, fs := range wi.fss {
fmt.Printf("\t%q ... ", fs.ToString())
r, err := zfs.ZFSMigrateHashBasedPlaceholderToCurrent(fs, migratePlaceholder0_1Args.dryRun)
if err != nil {
fmt.Printf("error: %s\n", err)
} else if !r.NeedsModification {
fmt.Printf("unchanged (placeholder=%v)\n", r.OriginalState.IsPlaceholder)
} else {
fmt.Printf("migrate (placeholder=%v) (old value = %q)\n",
r.OriginalState.IsPlaceholder, r.OriginalState.RawLocalPropertyValue)
}
}
}
return nil
}
+16
View File
@@ -0,0 +1,16 @@
package client
import "testing"
func TestMigrationsUnambiguousNames(t *testing.T) {
names := make(map[string]bool)
for _, mig := range migrations {
if _, ok := names[mig.Use]; ok {
t.Errorf("duplicate migration name %q", mig.Use)
t.FailNow()
return
} else {
names[mig.Use] = true
}
}
}
+29 -44
View File
@@ -116,17 +116,14 @@ var testPlaceholderArgs struct {
}
var testPlaceholder = &cli.Subcommand{
Use: "placeholder [--all | --dataset DATASET --action [compute | check [--placeholder PROP_VALUE]]]",
Short: fmt.Sprintf("list received placeholder filesystems & compute the ZFS property %q", zfs.ZREPL_PLACEHOLDER_PROPERTY_NAME),
Use: "placeholder [--all | --dataset DATASET]",
Short: fmt.Sprintf("list received placeholder filesystems (zfs property %q)", zfs.PlaceholderPropertyName),
Example: `
placeholder --all
placeholder --dataset path/to/sink/clientident/fs --action compute
placeholder --dataset path/to/sink/clientident/fs --action check --placeholder 1671a61be44d32d1f3f047c5f124b06f98f54143d82900545ee529165060b859`,
placeholder --dataset path/to/sink/clientident/fs`,
NoRequireConfig: true,
SetupFlags: func(f *pflag.FlagSet) {
f.StringVar(&testPlaceholderArgs.action, "action", "", "check | compute")
f.StringVar(&testPlaceholderArgs.ds, "dataset", "", "dataset path (not required to exist)")
f.StringVar(&testPlaceholderArgs.plv, "placeholder", "", "existing placeholder value to check against DATASET path")
f.BoolVar(&testPlaceholderArgs.all, "all", false, "list tab-separated placeholder status of all filesystems")
},
Run: runTestPlaceholder,
@@ -134,58 +131,46 @@ var testPlaceholder = &cli.Subcommand{
func runTestPlaceholder(subcommand *cli.Subcommand, args []string) error {
var checkDPs []*zfs.DatasetPath
// all actions first
if testPlaceholderArgs.all {
out, err := zfs.ZFSList([]string{"name", zfs.ZREPL_PLACEHOLDER_PROPERTY_NAME})
out, err := zfs.ZFSList([]string{"name"})
if err != nil {
return errors.Wrap(err, "could not list ZFS filesystems")
}
fmt.Printf("IS_PLACEHOLDER\tDATASET\tPROPVALUE\tCOMPUTED\n")
for _, row := range out {
dp, err := zfs.NewDatasetPath(row[0])
if err != nil {
panic(err)
}
computedProp := zfs.PlaceholderPropertyValue(dp)
is := "yes"
if computedProp != row[1] {
is = "no"
}
fmt.Printf("%s\t%s\t%q\t%q\n", is, dp.ToString(), row[1], computedProp)
checkDPs = append(checkDPs, dp)
}
return nil
} else {
dp, err := zfs.NewDatasetPath(testPlaceholderArgs.ds)
if err != nil {
return err
}
if dp.Empty() {
return fmt.Errorf("must specify --dataset DATASET or --all")
}
checkDPs = append(checkDPs, dp)
}
// other actions
dp, err := zfs.NewDatasetPath(testPlaceholderArgs.ds)
if err != nil {
return err
}
computedProp := zfs.PlaceholderPropertyValue(dp)
switch testPlaceholderArgs.action {
case "check":
var isPlaceholder bool
if testPlaceholderArgs.plv != "" {
isPlaceholder = computedProp == testPlaceholderArgs.plv
} else {
isPlaceholder, err = zfs.ZFSIsPlaceholderFilesystem(dp)
if err != nil {
return err
}
fmt.Printf("IS_PLACEHOLDER\tDATASET\tzrepl:placeholder\n")
for _, dp := range checkDPs {
ph, err := zfs.ZFSGetFilesystemPlaceholderState(dp)
if err != nil {
return errors.Wrap(err, "cannot get placeholder state")
}
if isPlaceholder {
fmt.Printf("%s is placeholder\n", dp.ToString())
return nil
} else {
return fmt.Errorf("%s is not a placeholder", dp.ToString())
if !ph.FSExists {
panic("placeholder state inconsistent: filesystem " + ph.FS + " must exist in this context")
}
case "compute":
fmt.Printf("%s\n", computedProp)
return nil
is := "yes"
if !ph.IsPlaceholder {
is = "no"
}
fmt.Printf("%s\t%s\t%s\n", is, dp.ToString(), ph.RawLocalPropertyValue)
}
return fmt.Errorf("unknown --action %q", testPlaceholderArgs.action)
return nil
}