move implementation to internal/ directory (#828)
This commit is contained in:
committed by
GitHub
parent
b9b9ad10cf
commit
908807bd59
@@ -0,0 +1,134 @@
|
||||
package zfs
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/zrepl/zrepl/internal/util/envconst"
|
||||
"github.com/zrepl/zrepl/internal/zfs/zfscmd"
|
||||
)
|
||||
|
||||
// no need for feature tests, holds have been around forever
|
||||
|
||||
func validateNotEmpty(field, s string) error {
|
||||
if s == "" {
|
||||
return fmt.Errorf("`%s` must not be empty", field)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// returned err != nil is guaranteed to represent invalid hold tag
|
||||
func ValidHoldTag(tag string) error {
|
||||
maxlen := envconst.Int("ZREPL_ZFS_MAX_HOLD_TAG_LEN", 256-1) // 256 include NULL byte, from module/zfs/dsl_userhold.c
|
||||
if len(tag) > maxlen {
|
||||
return fmt.Errorf("hold tag %q exceeds max length of %d", tag, maxlen)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Idemptotent: does not return an error if the tag already exists
|
||||
func ZFSHold(ctx context.Context, fs string, v FilesystemVersion, tag string) error {
|
||||
if !v.IsSnapshot() {
|
||||
return errors.Errorf("can only hold snapshots, got %s", v.RelName())
|
||||
}
|
||||
|
||||
if err := validateNotEmpty("tag", tag); err != nil {
|
||||
return err
|
||||
}
|
||||
fullPath := v.FullPath(fs)
|
||||
output, err := zfscmd.CommandContext(ctx, "zfs", "hold", tag, fullPath).CombinedOutput()
|
||||
if err != nil {
|
||||
if bytes.Contains(output, []byte("tag already exists on this dataset")) {
|
||||
goto success
|
||||
}
|
||||
return &ZFSError{output, errors.Wrapf(err, "cannot hold %q", fullPath)}
|
||||
}
|
||||
success:
|
||||
return nil
|
||||
}
|
||||
|
||||
func ZFSHolds(ctx context.Context, fs, snap string) ([]string, error) {
|
||||
if err := validateZFSFilesystem(fs); err != nil {
|
||||
return nil, errors.Wrap(err, "`fs` is not a valid filesystem path")
|
||||
}
|
||||
if snap == "" {
|
||||
return nil, fmt.Errorf("`snap` must not be empty")
|
||||
}
|
||||
dp := fmt.Sprintf("%s@%s", fs, snap)
|
||||
output, err := zfscmd.CommandContext(ctx, "zfs", "holds", "-H", dp).CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, &ZFSError{output, errors.Wrap(err, "zfs holds failed")}
|
||||
}
|
||||
scan := bufio.NewScanner(bytes.NewReader(output))
|
||||
var tags []string
|
||||
for scan.Scan() {
|
||||
// NAME TAG TIMESTAMP
|
||||
comps := strings.SplitN(scan.Text(), "\t", 3)
|
||||
if len(comps) != 3 {
|
||||
return nil, fmt.Errorf("zfs holds: unexpected output\n%s", output)
|
||||
}
|
||||
if comps[0] != dp {
|
||||
return nil, fmt.Errorf("zfs holds: unexpected output: expecting %q as first component, got %q\n%s", dp, comps[0], output)
|
||||
}
|
||||
tags = append(tags, comps[1])
|
||||
}
|
||||
return tags, nil
|
||||
}
|
||||
|
||||
// Idempotent: if the hold doesn't exist, this is not an error
|
||||
func ZFSRelease(ctx context.Context, tag string, snaps ...string) error {
|
||||
cumLens := make([]int, len(snaps))
|
||||
for i := 1; i < len(snaps); i++ {
|
||||
cumLens[i] = cumLens[i-1] + len(snaps[i])
|
||||
}
|
||||
maxInvocationLen := 12 * os.Getpagesize()
|
||||
var noSuchTagLines, otherLines []string
|
||||
for i := 0; i < len(snaps); {
|
||||
var j = i
|
||||
for ; j < len(snaps); j++ {
|
||||
if cumLens[j]-cumLens[i] > maxInvocationLen {
|
||||
break
|
||||
}
|
||||
}
|
||||
args := []string{"release", tag}
|
||||
args = append(args, snaps[i:j]...)
|
||||
output, err := zfscmd.CommandContext(ctx, "zfs", args...).CombinedOutput()
|
||||
if pe, ok := err.(*os.PathError); err != nil && ok && pe.Err == syscall.E2BIG {
|
||||
maxInvocationLen = maxInvocationLen / 2
|
||||
continue
|
||||
}
|
||||
// further error handling part of error scraper below
|
||||
|
||||
maxInvocationLen = maxInvocationLen + os.Getpagesize()
|
||||
i = j
|
||||
|
||||
// even if release fails for datasets where there's no hold with the tag
|
||||
// the hold is still released on datasets which have a hold with the tag
|
||||
// FIXME verify this in a platformtest
|
||||
// => screen-scrape
|
||||
scan := bufio.NewScanner(bytes.NewReader(output))
|
||||
for scan.Scan() {
|
||||
line := scan.Text()
|
||||
if strings.Contains(line, "no such tag on this dataset") {
|
||||
noSuchTagLines = append(noSuchTagLines, line)
|
||||
} else {
|
||||
otherLines = append(otherLines, line)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if debugEnabled {
|
||||
debug("zfs release: no such tag lines=%v otherLines=%v", noSuchTagLines, otherLines)
|
||||
}
|
||||
if len(otherLines) > 0 {
|
||||
return fmt.Errorf("unknown zfs error while releasing hold with tag %q:\n%s", tag, strings.Join(otherLines, "\n"))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user