add 'test filesystems' subcommand for testing filesystem filters

This commit is contained in:
Christian Schwarz 2018-10-13 15:53:52 +02:00
parent 5c3c83b2cb
commit 63169c51b7
5 changed files with 145 additions and 3 deletions

View File

@ -45,6 +45,7 @@ type Subcommand struct {
NoRequireConfig bool NoRequireConfig bool
Run func(subcommand *Subcommand, args []string) error Run func(subcommand *Subcommand, args []string) error
SetupFlags func(f *pflag.FlagSet) SetupFlags func(f *pflag.FlagSet)
SetupSubcommands func() []*Subcommand
config *config.Config config *config.Config
configErr error configErr error
@ -86,15 +87,25 @@ func (s *Subcommand) tryParseConfig() {
} }
func AddSubcommand(s *Subcommand) { func AddSubcommand(s *Subcommand) {
addSubcommandToCobraCmd(rootCmd, s)
}
func addSubcommandToCobraCmd(c *cobra.Command, s *Subcommand) {
cmd := cobra.Command{ cmd := cobra.Command{
Use: s.Use, Use: s.Use,
Short: s.Short, Short: s.Short,
Run: s.run, }
if s.SetupSubcommands == nil {
cmd.Run = s.run
} else {
for _, sub := range s.SetupSubcommands() {
addSubcommandToCobraCmd(&cmd, sub)
}
} }
if s.SetupFlags != nil { if s.SetupFlags != nil {
s.SetupFlags(cmd.Flags()) s.SetupFlags(cmd.Flags())
} }
rootCmd.AddCommand(&cmd) c.AddCommand(&cmd)
} }

108
client/testcmd.go Normal file
View File

@ -0,0 +1,108 @@
package client
import (
"fmt"
"github.com/spf13/pflag"
"github.com/zrepl/zrepl/cli"
"github.com/zrepl/zrepl/config"
"github.com/zrepl/zrepl/daemon/filters"
"github.com/zrepl/zrepl/zfs"
)
var TestCmd = &cli.Subcommand {
Use: "test",
SetupSubcommands: func() []*cli.Subcommand {
return []*cli.Subcommand{testFilter}
},
}
var testFilterArgs struct {
job string
all bool
input string
}
var testFilter = &cli.Subcommand{
Use: "filesystems --job JOB [--all | --input INPUT]",
Short: "test filesystems filter specified in push or source job",
SetupFlags: func(f *pflag.FlagSet) {
f.StringVar(&testFilterArgs.job, "job", "", "the name of the push or source job")
f.StringVar(&testFilterArgs.input, "input", "", "a filesystem name to test against the job's filters")
f.BoolVar(&testFilterArgs.all, "all", false, "test all local filesystems")
},
Run: runTestFilterCmd,
}
func runTestFilterCmd(subcommand *cli.Subcommand, args []string) error {
if testFilterArgs.job == "" {
return fmt.Errorf("must specify --job flag")
}
if !(testFilterArgs.all != (testFilterArgs.input != "")) { // xor
return fmt.Errorf("must set one: --all or --input")
}
conf := subcommand.Config()
var confFilter config.FilesystemsFilter
job, err := conf.Job(testFilterArgs.job)
if err != nil {
return err
}
switch j := job.Ret.(type) {
case *config.SourceJob: confFilter = j.Filesystems
case *config.PushJob: confFilter = j.Filesystems
default:
return fmt.Errorf("job type %T does not have filesystems filter", j)
}
f, err := filters.DatasetMapFilterFromConfig(confFilter)
if err != nil {
return fmt.Errorf("filter invalid: %s", err)
}
var fsnames []string
if testFilterArgs.input != "" {
fsnames = []string{testFilterArgs.input}
} else {
out, err := zfs.ZFSList([]string{"name"})
if err != nil {
return fmt.Errorf("could not list ZFS filesystems: %s", err)
}
for _, row := range out {
fsnames = append(fsnames, row[0])
}
}
fspaths := make([]*zfs.DatasetPath, len(fsnames))
for i, fsname := range fsnames {
path, err := zfs.NewDatasetPath(fsname)
if err != nil {
return err
}
fspaths[i] = path
}
hadFilterErr := false
for _, in := range fspaths {
var res string
var errStr string
pass, err := f.Filter(in)
if err != nil {
res = "ERROR"
errStr = err.Error()
hadFilterErr = true
} else if pass {
res = "ACCEPT"
} else {
res = "REJECT"
}
fmt.Printf("%s\t%s\t%s\n", res, in.ToString(), errStr)
}
if hadFilterErr {
return fmt.Errorf("filter errors occurred")
}
return nil
}

View File

@ -17,10 +17,32 @@ type Config struct {
Global *Global `yaml:"global,optional,fromdefaults"` Global *Global `yaml:"global,optional,fromdefaults"`
} }
func (c *Config) Job(name string) (*JobEnum, error) {
for _, j := range c.Jobs {
if j.Name() == name {
return &j, nil
}
}
return nil, fmt.Errorf("job %q not defined in config", name)
}
type JobEnum struct { type JobEnum struct {
Ret interface{} Ret interface{}
} }
func (j JobEnum) Name() string {
var name string
switch v := j.Ret.(type) {
case *PushJob: name = v.Name
case *SinkJob: name = v.Name
case *PullJob: name = v.Name
case *SourceJob: name = v.Name
default:
panic(fmt.Sprintf("unknownn job type %T", v))
}
return name
}
type ActiveJob struct { type ActiveJob struct {
Type string `yaml:"type"` Type string `yaml:"type"`
Name string `yaml:"name"` Name string `yaml:"name"`

View File

@ -18,7 +18,7 @@ The following rules determine which result is chosen for a given filesystem path
The **subtree wildcard** ``<`` means "the dataset left of ``<`` and all its children". The **subtree wildcard** ``<`` means "the dataset left of ``<`` and all its children".
.. TIP:: .. TIP::
You can try out patterns for a configured job using the ``zrepl test`` subcommand. You can try out patterns for a configured job using the ``zrepl test filesystems`` subcommand for push and source jobs.
Examples Examples
-------- --------

View File

@ -15,6 +15,7 @@ func init() {
cli.AddSubcommand(client.ConfigcheckCmd) cli.AddSubcommand(client.ConfigcheckCmd)
cli.AddSubcommand(client.VersionCmd) cli.AddSubcommand(client.VersionCmd)
cli.AddSubcommand(client.PprofCmd) cli.AddSubcommand(client.PprofCmd)
cli.AddSubcommand(client.TestCmd)
} }
func main() { func main() {