move implementation to internal/ directory (#828)
This commit is contained in:
committed by
GitHub
parent
b9b9ad10cf
commit
908807bd59
@@ -0,0 +1,197 @@
|
||||
package zfs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/sha512"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/zrepl/zrepl/internal/zfs/zfscmd"
|
||||
)
|
||||
|
||||
const (
|
||||
// For a placeholder filesystem to be a placeholder, the property source must be local,
|
||||
// i.e. not inherited.
|
||||
PlaceholderPropertyName string = "zrepl:placeholder"
|
||||
placeholderPropertyOn string = "on"
|
||||
placeholderPropertyOff string = "off"
|
||||
)
|
||||
|
||||
// computeLegacyPlaceholderPropertyValue is a legacy-compatibility function.
|
||||
//
|
||||
// In the 0.0.x series, the value stored in the PlaceholderPropertyName user property
|
||||
// was a hash value of the dataset path.
|
||||
// A simple `on|off` value could not be used at the time because `zfs list` was used to
|
||||
// list all filesystems and their placeholder state with a single command: due to property
|
||||
// inheritance, `zfs list` would print the placeholder state for all (non-placeholder) children
|
||||
// of a dataset, so the hash value was used to distinguish whether the property was local or
|
||||
// inherited.
|
||||
//
|
||||
// One of the drawbacks of the above approach is that `zfs rename` renders a placeholder filesystem
|
||||
// a non-placeholder filesystem if any of the parent path components change.
|
||||
//
|
||||
// We `zfs get` nowadays, which returns the property source, making the hash value no longer
|
||||
// necessary. However, we want to keep legacy compatibility.
|
||||
func computeLegacyHashBasedPlaceholderPropertyValue(p *DatasetPath) string {
|
||||
ps := []byte(p.ToString())
|
||||
sum := sha512.Sum512_256(ps)
|
||||
return hex.EncodeToString(sum[:])
|
||||
}
|
||||
|
||||
// the caller asserts that placeholderPropertyValue is sourceLocal
|
||||
func isLocalPlaceholderPropertyValuePlaceholder(p *DatasetPath, placeholderPropertyValue string) (isPlaceholder bool) {
|
||||
legacy := computeLegacyHashBasedPlaceholderPropertyValue(p)
|
||||
switch placeholderPropertyValue {
|
||||
case legacy:
|
||||
return true
|
||||
case placeholderPropertyOn:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
type FilesystemPlaceholderState struct {
|
||||
FS string
|
||||
FSExists bool
|
||||
IsPlaceholder bool
|
||||
RawLocalPropertyValue string
|
||||
}
|
||||
|
||||
// ZFSGetFilesystemPlaceholderState is the authoritative way to determine whether a filesystem
|
||||
// is a placeholder. Note that the property source must be `local` for the returned value to be valid.
|
||||
//
|
||||
// For nonexistent FS, err == nil and state.FSExists == false
|
||||
func ZFSGetFilesystemPlaceholderState(ctx context.Context, p *DatasetPath) (state *FilesystemPlaceholderState, err error) {
|
||||
state = &FilesystemPlaceholderState{FS: p.ToString()}
|
||||
state.FS = p.ToString()
|
||||
props, err := zfsGet(ctx, p.ToString(), []string{PlaceholderPropertyName}, SourceLocal)
|
||||
var _ error = (*DatasetDoesNotExist)(nil) // weak assertion on zfsGet's interface
|
||||
if _, ok := err.(*DatasetDoesNotExist); ok {
|
||||
return state, nil
|
||||
} else if err != nil {
|
||||
return state, err
|
||||
}
|
||||
state.FSExists = true
|
||||
state.RawLocalPropertyValue = props.Get(PlaceholderPropertyName)
|
||||
state.IsPlaceholder = isLocalPlaceholderPropertyValuePlaceholder(p, state.RawLocalPropertyValue)
|
||||
return state, nil
|
||||
}
|
||||
|
||||
//go:generate enumer -type=FilesystemPlaceholderCreateEncryptionValue -trimprefix=FilesystemPlaceholderCreateEncryption
|
||||
type FilesystemPlaceholderCreateEncryptionValue int
|
||||
|
||||
const (
|
||||
FilesystemPlaceholderCreateEncryptionInherit FilesystemPlaceholderCreateEncryptionValue = 1 << iota
|
||||
FilesystemPlaceholderCreateEncryptionOff
|
||||
)
|
||||
|
||||
func ZFSCreatePlaceholderFilesystem(ctx context.Context, fs *DatasetPath, parent *DatasetPath, encryption FilesystemPlaceholderCreateEncryptionValue) (err error) {
|
||||
if fs.Length() == 1 {
|
||||
return fmt.Errorf("cannot create %q: pools cannot be created with zfs create", fs.ToString())
|
||||
}
|
||||
|
||||
cmdline := []string{
|
||||
"create",
|
||||
"-o", fmt.Sprintf("%s=%s", PlaceholderPropertyName, placeholderPropertyOn),
|
||||
"-o", "mountpoint=none",
|
||||
}
|
||||
|
||||
if !encryption.IsAFilesystemPlaceholderCreateEncryptionValue() {
|
||||
panic(encryption)
|
||||
}
|
||||
switch encryption {
|
||||
case FilesystemPlaceholderCreateEncryptionInherit:
|
||||
// no-op
|
||||
case FilesystemPlaceholderCreateEncryptionOff:
|
||||
cmdline = append(cmdline, "-o", "encryption=off")
|
||||
default:
|
||||
panic(encryption)
|
||||
}
|
||||
|
||||
cmdline = append(cmdline, fs.ToString())
|
||||
cmd := zfscmd.CommandContext(ctx, ZFS_BINARY, cmdline...)
|
||||
|
||||
stdio, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
err = &ZFSError{
|
||||
Stderr: stdio,
|
||||
WaitErr: err,
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func ZFSSetPlaceholder(ctx context.Context, p *DatasetPath, isPlaceholder bool) error {
|
||||
prop := placeholderPropertyOff
|
||||
if isPlaceholder {
|
||||
prop = placeholderPropertyOn
|
||||
}
|
||||
props := map[string]string{PlaceholderPropertyName: prop}
|
||||
return zfsSet(ctx, p.ToString(), props)
|
||||
}
|
||||
|
||||
type MigrateHashBasedPlaceholderReport struct {
|
||||
OriginalState FilesystemPlaceholderState
|
||||
NeedsModification bool
|
||||
}
|
||||
|
||||
// fs must exist, will panic otherwise
|
||||
func ZFSMigrateHashBasedPlaceholderToCurrent(ctx context.Context, fs *DatasetPath, dryRun bool) (*MigrateHashBasedPlaceholderReport, error) {
|
||||
st, err := ZFSGetFilesystemPlaceholderState(ctx, fs)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error getting placeholder state: %s", err)
|
||||
}
|
||||
if !st.FSExists {
|
||||
panic("inconsistent placeholder state returned: fs must exist")
|
||||
}
|
||||
|
||||
report := MigrateHashBasedPlaceholderReport{
|
||||
OriginalState: *st,
|
||||
}
|
||||
report.NeedsModification = st.IsPlaceholder && st.RawLocalPropertyValue != placeholderPropertyOn
|
||||
|
||||
if dryRun || !report.NeedsModification {
|
||||
return &report, nil
|
||||
}
|
||||
|
||||
err = ZFSSetPlaceholder(ctx, fs, st.IsPlaceholder)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error re-writing placeholder property: %s", err)
|
||||
}
|
||||
return &report, nil
|
||||
}
|
||||
|
||||
func ZFSListPlaceholderFilesystemsWithAdditionalProps(ctx context.Context, root string, additionalProps []string) (map[string]*ZFSProperties, error) {
|
||||
|
||||
props := []string{PlaceholderPropertyName}
|
||||
if len(additionalProps) > 0 {
|
||||
props = append(props, additionalProps...)
|
||||
}
|
||||
|
||||
propsByFS, err := zfsGetRecursive(ctx, root, -1, []string{"filesystem", "volume"}, props, SourceAny)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "cannot get placeholder filesystems under %q", root)
|
||||
}
|
||||
|
||||
filtered := make(map[string]*ZFSProperties)
|
||||
for fs, props := range propsByFS {
|
||||
details := props.GetDetails(PlaceholderPropertyName)
|
||||
if details.Source != SourceLocal {
|
||||
continue
|
||||
}
|
||||
fsp, err := NewDatasetPath(fs)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "zfs get returned invalid dataset path %q", fs)
|
||||
}
|
||||
if !isLocalPlaceholderPropertyValuePlaceholder(fsp, details.Value) {
|
||||
continue
|
||||
}
|
||||
filtered[fs] = props
|
||||
}
|
||||
|
||||
return filtered, nil
|
||||
}
|
||||
Reference in New Issue
Block a user