package platformtest import ( "context" "fmt" "os" "path/filepath" "github.com/pkg/errors" "github.com/zrepl/zrepl/zfs" ) type Zpool struct { args ZpoolCreateArgs } type ZpoolCreateArgs struct { PoolName string ImagePath string ImageSize int64 Mountpoint string } func (a ZpoolCreateArgs) Validate() error { if !filepath.IsAbs(a.ImagePath) { return errors.Errorf("ImagePath must be absolute, got %q", a.ImagePath) } const minImageSize = 1024 if a.ImageSize < minImageSize { return errors.Errorf("ImageSize must be > %v, got %v", minImageSize, a.ImageSize) } if a.Mountpoint == "" || a.Mountpoint[0] != '/' { return errors.Errorf("Mountpoint must be an absolute path to a directory") } if a.PoolName == "" { return errors.Errorf("PoolName must not be empty") } return nil } func CreateOrReplaceZpool(ctx context.Context, e Execer, args ZpoolCreateArgs) (*Zpool, error) { if err := args.Validate(); err != nil { return nil, errors.Wrap(err, "zpool create args validation error") } // export pool if it already exists (idempotence) if _, err := zfs.ZFSGetRawAnySource(ctx, args.PoolName, []string{"name"}); err != nil { if _, ok := err.(*zfs.DatasetDoesNotExist); ok { // we'll create it shortly } else { return nil, errors.Wrapf(err, "cannot determine whether test pool %q exists", args.PoolName) } } else { // exists, export it, OpenFile will destroy it if err := e.RunExpectSuccessNoOutput(ctx, "zpool", "export", args.PoolName); err != nil { return nil, errors.Wrapf(err, "cannot destroy test pool %q", args.PoolName) } } // clear the mountpoint dir if err := os.RemoveAll(args.Mountpoint); err != nil { return nil, errors.Wrapf(err, "remove mountpoint dir %q", args.Mountpoint) } if err := os.Mkdir(args.Mountpoint, 0700); err != nil { return nil, errors.Wrapf(err, "create mountpoint dir %q", args.Mountpoint) } // idempotently (re)create the pool image image, err := os.OpenFile(args.ImagePath, os.O_CREATE|os.O_RDWR, 0600) if err != nil { return nil, errors.Wrap(err, "create image file") } defer image.Close() if err := image.Truncate(args.ImageSize); err != nil { return nil, errors.Wrap(err, "create image: truncate") } image.Close() // create the pool err = e.RunExpectSuccessNoOutput(ctx, "zpool", "create", "-f", "-O", fmt.Sprintf("mountpoint=%s", args.Mountpoint), args.PoolName, args.ImagePath, ) if err != nil { return nil, errors.Wrap(err, "zpool create") } return &Zpool{args}, nil } func (p *Zpool) Name() string { return p.args.PoolName } func (p *Zpool) Destroy(ctx context.Context, e Execer) error { if err := e.RunExpectSuccessNoOutput(ctx, "zpool", "export", p.args.PoolName); err != nil { return errors.Wrapf(err, "export pool %q", p.args.PoolName) } if err := os.Remove(p.args.ImagePath); err != nil { return errors.Wrapf(err, "remove pool image") } if err := os.RemoveAll(p.args.Mountpoint); err != nil { return errors.Wrapf(err, "remove mountpoint dir %q", p.args.Mountpoint) } return nil }