move implementation to internal/ directory (#828)
This commit is contained in:
committed by
GitHub
parent
b9b9ad10cf
commit
908807bd59
@@ -0,0 +1,281 @@
|
||||
package zfs
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
type VersionType string
|
||||
|
||||
const (
|
||||
Bookmark VersionType = "bookmark"
|
||||
Snapshot VersionType = "snapshot"
|
||||
)
|
||||
|
||||
type VersionTypeSet map[VersionType]bool
|
||||
|
||||
var (
|
||||
AllVersionTypes = VersionTypeSet{
|
||||
Bookmark: true,
|
||||
Snapshot: true,
|
||||
}
|
||||
Bookmarks = VersionTypeSet{
|
||||
Bookmark: true,
|
||||
}
|
||||
Snapshots = VersionTypeSet{
|
||||
Snapshot: true,
|
||||
}
|
||||
)
|
||||
|
||||
func (s VersionTypeSet) zfsListTFlagRepr() string {
|
||||
var types []string
|
||||
for t := range s {
|
||||
types = append(types, t.String())
|
||||
}
|
||||
sort.StringSlice(types).Sort()
|
||||
return strings.Join(types, ",")
|
||||
}
|
||||
func (s VersionTypeSet) String() string { return s.zfsListTFlagRepr() }
|
||||
|
||||
func (t VersionType) DelimiterChar() string {
|
||||
switch t {
|
||||
case Bookmark:
|
||||
return "#"
|
||||
case Snapshot:
|
||||
return "@"
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected VersionType %#v", t))
|
||||
}
|
||||
}
|
||||
|
||||
func (t VersionType) String() string {
|
||||
return string(t)
|
||||
}
|
||||
|
||||
func DecomposeVersionString(v string) (fs string, versionType VersionType, name string, err error) {
|
||||
if len(v) < 3 {
|
||||
err = fmt.Errorf("snapshot or bookmark name implausibly short: %s", v)
|
||||
return
|
||||
}
|
||||
|
||||
snapSplit := strings.SplitN(v, "@", 2)
|
||||
bookmarkSplit := strings.SplitN(v, "#", 2)
|
||||
if len(snapSplit)*len(bookmarkSplit) != 2 {
|
||||
err = fmt.Errorf("dataset cannot be snapshot and bookmark at the same time: %s", v)
|
||||
return
|
||||
}
|
||||
|
||||
if len(snapSplit) == 2 {
|
||||
return snapSplit[0], Snapshot, snapSplit[1], nil
|
||||
} else {
|
||||
return bookmarkSplit[0], Bookmark, bookmarkSplit[1], nil
|
||||
}
|
||||
}
|
||||
|
||||
// The data in a FilesystemVersion is guaranteed to stem from a ZFS CLI invocation.
|
||||
type FilesystemVersion struct {
|
||||
Type VersionType
|
||||
|
||||
// Display name. Should not be used for identification, only for user output
|
||||
Name string
|
||||
|
||||
// GUID as exported by ZFS. Uniquely identifies a snapshot across pools
|
||||
Guid uint64
|
||||
|
||||
// The TXG in which the snapshot was created. For bookmarks,
|
||||
// this is the GUID of the snapshot it was initially tied to.
|
||||
CreateTXG uint64
|
||||
|
||||
// The time the dataset was created
|
||||
Creation time.Time
|
||||
|
||||
// userrefs field (snapshots only)
|
||||
UserRefs OptionUint64
|
||||
}
|
||||
|
||||
type OptionUint64 struct {
|
||||
Value uint64
|
||||
Valid bool
|
||||
}
|
||||
|
||||
func (v FilesystemVersion) GetCreateTXG() uint64 { return v.CreateTXG }
|
||||
func (v FilesystemVersion) GetGUID() uint64 { return v.Guid }
|
||||
func (v FilesystemVersion) GetGuid() uint64 { return v.Guid }
|
||||
func (v FilesystemVersion) GetName() string { return v.Name }
|
||||
func (v FilesystemVersion) IsSnapshot() bool { return v.Type == Snapshot }
|
||||
func (v FilesystemVersion) IsBookmark() bool { return v.Type == Bookmark }
|
||||
func (v FilesystemVersion) RelName() string {
|
||||
return fmt.Sprintf("%s%s", v.Type.DelimiterChar(), v.Name)
|
||||
}
|
||||
func (v FilesystemVersion) String() string { return v.RelName() }
|
||||
|
||||
// Only takes into account those attributes of FilesystemVersion that
|
||||
// are immutable over time in ZFS.
|
||||
func FilesystemVersionEqualIdentity(a, b FilesystemVersion) bool {
|
||||
// .Name is mutable
|
||||
return a.Guid == b.Guid && a.CreateTXG == b.CreateTXG && a.Creation == b.Creation
|
||||
}
|
||||
|
||||
func (v FilesystemVersion) ToAbsPath(p *DatasetPath) string {
|
||||
var b bytes.Buffer
|
||||
b.WriteString(p.ToString())
|
||||
b.WriteString(v.Type.DelimiterChar())
|
||||
b.WriteString(v.Name)
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (v FilesystemVersion) FullPath(fs string) string {
|
||||
return fmt.Sprintf("%s%s", fs, v.RelName())
|
||||
}
|
||||
|
||||
func (v FilesystemVersion) ToSendArgVersion() ZFSSendArgVersion {
|
||||
return ZFSSendArgVersion{
|
||||
RelName: v.RelName(),
|
||||
GUID: v.Guid,
|
||||
}
|
||||
}
|
||||
|
||||
type ParseFilesystemVersionArgs struct {
|
||||
fullname string
|
||||
guid, createtxg, creation, userrefs string
|
||||
}
|
||||
|
||||
func ParseFilesystemVersion(args ParseFilesystemVersionArgs) (v FilesystemVersion, err error) {
|
||||
_, v.Type, v.Name, err = DecomposeVersionString(args.fullname)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
|
||||
if v.Guid, err = strconv.ParseUint(args.guid, 10, 64); err != nil {
|
||||
err = errors.Wrapf(err, "cannot parse GUID %q", args.guid)
|
||||
return v, err
|
||||
}
|
||||
|
||||
if v.CreateTXG, err = strconv.ParseUint(args.createtxg, 10, 64); err != nil {
|
||||
err = errors.Wrapf(err, "cannot parse CreateTXG %q", args.createtxg)
|
||||
return v, err
|
||||
}
|
||||
|
||||
creationUnix, err := strconv.ParseInt(args.creation, 10, 64)
|
||||
if err != nil {
|
||||
err = errors.Wrapf(err, "cannot parse creation date %q", args.creation)
|
||||
return v, err
|
||||
} else {
|
||||
v.Creation = time.Unix(creationUnix, 0)
|
||||
}
|
||||
|
||||
switch v.Type {
|
||||
case Bookmark:
|
||||
if args.userrefs != "-" {
|
||||
return v, errors.Errorf("expecting %q for bookmark property userrefs, got %q", "-", args.userrefs)
|
||||
}
|
||||
v.UserRefs = OptionUint64{Valid: false}
|
||||
case Snapshot:
|
||||
if v.UserRefs.Value, err = strconv.ParseUint(args.userrefs, 10, 64); err != nil {
|
||||
err = errors.Wrapf(err, "cannot parse userrefs %q", args.userrefs)
|
||||
return v, err
|
||||
}
|
||||
v.UserRefs.Valid = true
|
||||
default:
|
||||
panic(v.Type)
|
||||
}
|
||||
|
||||
return v, nil
|
||||
}
|
||||
|
||||
type ListFilesystemVersionsOptions struct {
|
||||
// the prefix of the version name, without the delimiter char
|
||||
// empty means any prefix matches
|
||||
ShortnamePrefix string
|
||||
|
||||
// which types should be returned
|
||||
// nil or len(0) means any prefix matches
|
||||
Types VersionTypeSet
|
||||
}
|
||||
|
||||
func (o *ListFilesystemVersionsOptions) typesFlagArgs() string {
|
||||
if len(o.Types) == 0 {
|
||||
return AllVersionTypes.zfsListTFlagRepr()
|
||||
} else {
|
||||
return o.Types.zfsListTFlagRepr()
|
||||
}
|
||||
}
|
||||
|
||||
func (o *ListFilesystemVersionsOptions) matches(v FilesystemVersion) bool {
|
||||
return (len(o.Types) == 0 || o.Types[v.Type]) && strings.HasPrefix(v.Name, o.ShortnamePrefix)
|
||||
}
|
||||
|
||||
// returned versions are sorted by createtxg FIXME drop sort by createtxg requirement
|
||||
func ZFSListFilesystemVersions(ctx context.Context, fs *DatasetPath, options ListFilesystemVersionsOptions) (res []FilesystemVersion, err error) {
|
||||
listResults := make(chan ZFSListResult)
|
||||
|
||||
promTimer := prometheus.NewTimer(prom.ZFSListFilesystemVersionDuration.WithLabelValues(fs.ToString()))
|
||||
defer promTimer.ObserveDuration()
|
||||
|
||||
// Note: we don't create a separate trace.Task here because our loop that consumes
|
||||
// the goroutine's output doesn't use ctx.
|
||||
ctx, cancel := context.WithCancel(ctx)
|
||||
// make sure the goroutine doesn't outlive this function call
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
defer wg.Wait()
|
||||
defer cancel() // on exit, cancel list process before waiting for it
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
ZFSListChan(ctx, listResults,
|
||||
[]string{"name", "guid", "createtxg", "creation", "userrefs"},
|
||||
fs,
|
||||
"-r", "-d", "1",
|
||||
"-t", options.typesFlagArgs(),
|
||||
"-s", "createtxg", fs.ToString())
|
||||
}()
|
||||
|
||||
res = make([]FilesystemVersion, 0)
|
||||
for listResult := range listResults {
|
||||
if listResult.Err != nil {
|
||||
return nil, listResult.Err
|
||||
}
|
||||
|
||||
line := listResult.Fields
|
||||
args := ParseFilesystemVersionArgs{
|
||||
fullname: line[0],
|
||||
guid: line[1],
|
||||
createtxg: line[2],
|
||||
creation: line[3],
|
||||
userrefs: line[4],
|
||||
}
|
||||
v, err := ParseFilesystemVersion(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if options.matches(v) {
|
||||
res = append(res, v)
|
||||
}
|
||||
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func ZFSGetFilesystemVersion(ctx context.Context, ds string) (v FilesystemVersion, _ error) {
|
||||
props, err := zfsGet(ctx, ds, []string{"createtxg", "guid", "creation", "userrefs"}, SourceAny)
|
||||
if err != nil {
|
||||
return v, err
|
||||
}
|
||||
return ParseFilesystemVersion(ParseFilesystemVersionArgs{
|
||||
fullname: ds,
|
||||
createtxg: props.Get("createtxg"),
|
||||
guid: props.Get("guid"),
|
||||
creation: props.Get("creation"),
|
||||
userrefs: props.Get("userrefs"),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user