zfs: userrefs, platformtests for ListFilesystemVersions and ListMapping (likely needs fixup from next commit)
This commit is contained in:
+1
-1
@@ -59,7 +59,7 @@ func ZFSListMappingProperties(ctx context.Context, filter DatasetFilter, propert
|
||||
defer cancel()
|
||||
rchan := make(chan ZFSListResult)
|
||||
|
||||
go ZFSListChan(ctx, rchan, properties, "-r", "-t", "filesystem,volume")
|
||||
go ZFSListChan(ctx, rchan, properties, nil, "-r", "-t", "filesystem,volume")
|
||||
|
||||
datasets = make([]ZFSListMappingPropertiesResult, 0)
|
||||
for r := range rchan {
|
||||
|
||||
+73
-25
@@ -20,6 +20,30 @@ const (
|
||||
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())
|
||||
}
|
||||
return strings.Join(types, ",")
|
||||
}
|
||||
func (s VersionTypeSet) String() string { return s.zfsListTFlagRepr() }
|
||||
|
||||
func (t VersionType) DelimiterChar() string {
|
||||
switch t {
|
||||
case Bookmark:
|
||||
@@ -71,6 +95,14 @@ type FilesystemVersion struct {
|
||||
|
||||
// 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 }
|
||||
@@ -104,8 +136,8 @@ func (v FilesystemVersion) ToSendArgVersion() ZFSSendArgVersion {
|
||||
}
|
||||
|
||||
type ParseFilesystemVersionArgs struct {
|
||||
fullname string
|
||||
guid, createtxg, creation string
|
||||
fullname string
|
||||
guid, createtxg, creation, userrefs string
|
||||
}
|
||||
|
||||
func ParseFilesystemVersion(args ParseFilesystemVersionArgs) (v FilesystemVersion, err error) {
|
||||
@@ -132,27 +164,49 @@ func ParseFilesystemVersion(args ParseFilesystemVersionArgs) (v FilesystemVersio
|
||||
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 FilesystemVersionFilter interface {
|
||||
Filter(t VersionType, name string) (accept bool, err error)
|
||||
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
|
||||
}
|
||||
|
||||
type closureFilesystemVersionFilter struct {
|
||||
cb func(t VersionType, name string) (accept bool, err error)
|
||||
func (o *ListFilesystemVersionsOptions) typesFlagArgs() string {
|
||||
if len(o.Types) == 0 {
|
||||
return AllVersionTypes.zfsListTFlagRepr()
|
||||
} else {
|
||||
return o.Types.zfsListTFlagRepr()
|
||||
}
|
||||
}
|
||||
|
||||
func (f *closureFilesystemVersionFilter) Filter(t VersionType, name string) (accept bool, err error) {
|
||||
return f.cb(t, name)
|
||||
func (o *ListFilesystemVersionsOptions) matches(v FilesystemVersion) bool {
|
||||
return (len(o.Types) == 0 || o.Types[v.Type]) && strings.HasPrefix(v.Name, o.ShortnamePrefix)
|
||||
}
|
||||
|
||||
func FilterFromClosure(cb func(t VersionType, name string) (accept bool, err error)) FilesystemVersionFilter {
|
||||
return &closureFilesystemVersionFilter{cb}
|
||||
}
|
||||
|
||||
// returned versions are sorted by createtxg
|
||||
func ZFSListFilesystemVersions(fs *DatasetPath, filter FilesystemVersionFilter) (res []FilesystemVersion, err error) {
|
||||
// returned versions are sorted by createtxg FIXME drop sort by createtxg requirement
|
||||
func ZFSListFilesystemVersions(fs *DatasetPath, options ListFilesystemVersionsOptions) (res []FilesystemVersion, err error) {
|
||||
listResults := make(chan ZFSListResult)
|
||||
|
||||
promTimer := prometheus.NewTimer(prom.ZFSListFilesystemVersionDuration.WithLabelValues(fs.ToString()))
|
||||
@@ -161,9 +215,10 @@ func ZFSListFilesystemVersions(fs *DatasetPath, filter FilesystemVersionFilter)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
go ZFSListChan(ctx, listResults,
|
||||
[]string{"name", "guid", "createtxg", "creation"},
|
||||
[]string{"name", "guid", "createtxg", "creation", "userrefs"},
|
||||
fs,
|
||||
"-r", "-d", "1",
|
||||
"-t", "bookmark,snapshot",
|
||||
"-t", options.typesFlagArgs(),
|
||||
"-s", "createtxg", fs.ToString())
|
||||
|
||||
res = make([]FilesystemVersion, 0)
|
||||
@@ -182,21 +237,14 @@ func ZFSListFilesystemVersions(fs *DatasetPath, filter FilesystemVersionFilter)
|
||||
guid: line[1],
|
||||
createtxg: line[2],
|
||||
creation: line[3],
|
||||
userrefs: line[4],
|
||||
}
|
||||
v, err := ParseFilesystemVersion(args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accept := true
|
||||
if filter != nil {
|
||||
accept, err = filter.Filter(v.Type, v.Name)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("error executing filter: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if accept {
|
||||
if options.matches(v) {
|
||||
res = append(res, v)
|
||||
}
|
||||
|
||||
|
||||
+22
-12
@@ -221,9 +221,12 @@ type ZFSListResult struct {
|
||||
// If no error occurs, it is just closed.
|
||||
// If the operation is cancelled via context, the channel is just closed.
|
||||
//
|
||||
// If notExistHint is not nil and zfs exits with an error,
|
||||
// the stderr is attempted to be interpreted as a *DatasetDoesNotExist error.
|
||||
//
|
||||
// However, if callers do not drain `out` or cancel via `ctx`, the process will leak either running because
|
||||
// IO is pending or as a zombie.
|
||||
func ZFSListChan(ctx context.Context, out chan ZFSListResult, properties []string, zfsArgs ...string) {
|
||||
func ZFSListChan(ctx context.Context, out chan ZFSListResult, properties []string, notExistHint *DatasetPath, zfsArgs ...string) {
|
||||
defer close(out)
|
||||
|
||||
args := make([]string, 0, 4+len(zfsArgs))
|
||||
@@ -272,11 +275,21 @@ func ZFSListChan(ctx context.Context, out chan ZFSListResult, properties []strin
|
||||
}
|
||||
}
|
||||
if err := cmd.Wait(); err != nil {
|
||||
if err, ok := err.(*exec.ExitError); ok {
|
||||
sendResult(nil, &ZFSError{
|
||||
Stderr: stderrBuf.Bytes(),
|
||||
WaitErr: err,
|
||||
})
|
||||
if _, ok := err.(*exec.ExitError); ok {
|
||||
enotexist := func() error {
|
||||
if notExistHint == nil {
|
||||
return nil
|
||||
}
|
||||
return tryDatasetDoesNotExist(notExistHint.ToString(), stderrBuf.Bytes())
|
||||
}
|
||||
if err := enotexist(); err != nil {
|
||||
sendResult(nil, err)
|
||||
} else {
|
||||
sendResult(nil, &ZFSError{
|
||||
Stderr: stderrBuf.Bytes(),
|
||||
WaitErr: err,
|
||||
})
|
||||
}
|
||||
} else {
|
||||
sendResult(nil, &ZFSError{WaitErr: err})
|
||||
}
|
||||
@@ -1075,15 +1088,12 @@ func ZFSRecv(ctx context.Context, fs string, v *ZFSSendArgVersion, streamCopier
|
||||
// does not perform a rollback unless `send -R` was used (which we assume hasn't been the case)
|
||||
var snaps []FilesystemVersion
|
||||
{
|
||||
vs, err := ZFSListFilesystemVersions(fsdp, nil)
|
||||
snaps, err := ZFSListFilesystemVersions(fsdp, ListFilesystemVersionsOptions{
|
||||
Types: Snapshots,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot list versions for rollback for forced receive: %s", err)
|
||||
}
|
||||
for _, v := range vs {
|
||||
if v.Type == Snapshot {
|
||||
snaps = append(snaps, v)
|
||||
}
|
||||
}
|
||||
sort.Slice(snaps, func(i, j int) bool {
|
||||
return snaps[i].CreateTXG < snaps[j].CreateTXG
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user