86d8758e |
// +build linux freebsd solaris |
35cdcbb3 |
|
d5151ca8 |
package zfs
import (
"fmt"
"os"
"os/exec"
"path"
"strconv"
"strings" |
2cb23527 |
"sync" |
d5151ca8 |
"time"
|
2bf73c4b |
"github.com/Sirupsen/logrus" |
d5151ca8 |
"github.com/docker/docker/daemon/graphdriver" |
442b4562 |
"github.com/docker/docker/pkg/idtools" |
ee00f07e |
"github.com/docker/docker/pkg/mount" |
d5151ca8 |
"github.com/docker/docker/pkg/parsers"
zfs "github.com/mistifyio/go-zfs" |
abbbf914 |
"github.com/opencontainers/selinux/go-selinux/label" |
069fdc8a |
"golang.org/x/sys/unix" |
d5151ca8 |
)
|
e27c904b |
type zfsOptions struct { |
d5151ca8 |
fsName string
mountPath string
}
func init() {
graphdriver.Register("zfs", Init)
}
|
927b334e |
// Logger returns a zfs logger implementation. |
d5151ca8 |
type Logger struct{}
|
e27c904b |
// Log wraps log message from ZFS driver with a prefix '[zfs]'. |
d5151ca8 |
func (*Logger) Log(cmd []string) { |
2bf73c4b |
logrus.Debugf("[zfs] %s", strings.Join(cmd, " ")) |
d5151ca8 |
}
|
e27c904b |
// Init returns a new ZFS driver. |
c1be45fa |
// It takes base mount path and an array of options which are represented as key value pairs. |
e27c904b |
// Each option is in the for key=value. 'zfs.fsname' is expected to be a valid key in the options. |
442b4562 |
func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) { |
d5151ca8 |
var err error |
f95b3a6b |
if _, err := exec.LookPath("zfs"); err != nil { |
2bf73c4b |
logrus.Debugf("[zfs] zfs command is not available: %v", err) |
f95b3a6b |
return nil, graphdriver.ErrPrerequisites
}
file, err := os.OpenFile("/dev/zfs", os.O_RDWR, 600)
if err != nil { |
2bf73c4b |
logrus.Debugf("[zfs] cannot open /dev/zfs: %v", err) |
f95b3a6b |
return nil, graphdriver.ErrPrerequisites
}
defer file.Close()
|
d5151ca8 |
options, err := parseOptions(opt)
if err != nil {
return nil, err
} |
dd614b5e |
options.mountPath = base |
d5151ca8 |
rootdir := path.Dir(base)
if options.fsName == "" {
err = checkRootdirFs(rootdir)
if err != nil {
return nil, err
}
}
if options.fsName == "" {
options.fsName, err = lookupZfsDataset(rootdir)
if err != nil {
return nil, err
}
}
|
dd614b5e |
zfs.SetLogger(new(Logger)) |
d5151ca8 |
|
bad25ccf |
filesystems, err := zfs.Filesystems(options.fsName) |
d5151ca8 |
if err != nil { |
bad25ccf |
return nil, fmt.Errorf("Cannot find root filesystem %s: %v", options.fsName, err)
}
filesystemsCache := make(map[string]bool, len(filesystems))
var rootDataset *zfs.Dataset
for _, fs := range filesystems {
if fs.Name == options.fsName {
rootDataset = fs
}
filesystemsCache[fs.Name] = true
}
if rootDataset == nil { |
416e855e |
return nil, fmt.Errorf("BUG: zfs get all -t filesystem -rHp '%s' should contain '%s'", options.fsName, options.fsName) |
d5151ca8 |
}
|
205bc6fc |
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
if err != nil {
return nil, fmt.Errorf("Failed to get root uid/guid: %v", err)
}
if err := idtools.MkdirAllAs(base, 0700, rootUID, rootGID); err != nil {
return nil, fmt.Errorf("Failed to create '%s': %v", base, err)
}
|
5ec8441b |
if err := mount.MakePrivate(base); err != nil {
return nil, err
} |
bacecabf |
d := &Driver{ |
bad25ccf |
dataset: rootDataset,
options: options,
filesystemsCache: filesystemsCache, |
442b4562 |
uidMaps: uidMaps,
gidMaps: gidMaps, |
8bb4d31b |
ctr: graphdriver.NewRefCounter(graphdriver.NewDefaultChecker()), |
bacecabf |
} |
442b4562 |
return graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps), nil |
d5151ca8 |
}
|
e27c904b |
func parseOptions(opt []string) (zfsOptions, error) {
var options zfsOptions |
d5151ca8 |
options.fsName = ""
for _, option := range opt {
key, val, err := parsers.ParseKeyValueOpt(option)
if err != nil {
return options, err
}
key = strings.ToLower(key)
switch key {
case "zfs.fsname":
options.fsName = val
default: |
dd614b5e |
return options, fmt.Errorf("Unknown option %s", key) |
d5151ca8 |
}
}
return options, nil
}
func lookupZfsDataset(rootdir string) (string, error) { |
069fdc8a |
var stat unix.Stat_t
if err := unix.Stat(rootdir, &stat); err != nil { |
d5151ca8 |
return "", fmt.Errorf("Failed to access '%s': %s", rootdir, err)
}
wantedDev := stat.Dev
|
ee00f07e |
mounts, err := mount.GetMounts() |
dd614b5e |
if err != nil { |
ee00f07e |
return "", err |
d5151ca8 |
} |
ee00f07e |
for _, m := range mounts { |
069fdc8a |
if err := unix.Stat(m.Mountpoint, &stat); err != nil { |
2bf73c4b |
logrus.Debugf("[zfs] failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err) |
d5151ca8 |
continue // may fail on fuse file systems
}
|
ee00f07e |
if stat.Dev == wantedDev && m.Fstype == "zfs" {
return m.Source, nil |
d5151ca8 |
}
}
|
dd614b5e |
return "", fmt.Errorf("Failed to find zfs dataset mounted on '%s' in /proc/mounts", rootdir) |
d5151ca8 |
}
|
e27c904b |
// Driver holds information about the driver, such as zfs dataset, options and cache. |
d5151ca8 |
type Driver struct { |
bad25ccf |
dataset *zfs.Dataset |
e27c904b |
options zfsOptions |
2cb23527 |
sync.Mutex // protects filesystem cache against concurrent access |
bad25ccf |
filesystemsCache map[string]bool |
442b4562 |
uidMaps []idtools.IDMap
gidMaps []idtools.IDMap |
7342060b |
ctr *graphdriver.RefCounter |
d5151ca8 |
}
func (d *Driver) String() string {
return "zfs"
}
|
e27c904b |
// Cleanup is used to implement graphdriver.ProtoDriver. There is no cleanup required for this driver. |
d5151ca8 |
func (d *Driver) Cleanup() error {
return nil
}
|
e27c904b |
// Status returns information about the ZFS filesystem. It returns a two dimensional array of information
// such as pool name, dataset name, disk usage, parent quota and compression used.
// Currently it return 'Zpool', 'Zpool Health', 'Parent Dataset', 'Space Used By Parent',
// 'Space Available', 'Parent Quota' and 'Compression'. |
d5151ca8 |
func (d *Driver) Status() [][2]string {
parts := strings.Split(d.dataset.Name, "/")
pool, err := zfs.GetZpool(parts[0])
|
dd614b5e |
var poolName, poolHealth string
if err == nil {
poolName = pool.Name
poolHealth = pool.Health
} else {
poolName = fmt.Sprintf("error while getting pool information %v", err)
poolHealth = "not available" |
d5151ca8 |
} |
dd614b5e |
quota := "no"
if d.dataset.Quota != 0 { |
d5151ca8 |
quota = strconv.FormatUint(d.dataset.Quota, 10)
}
return [][2]string{ |
dd614b5e |
{"Zpool", poolName},
{"Zpool Health", poolHealth}, |
d5151ca8 |
{"Parent Dataset", d.dataset.Name},
{"Space Used By Parent", strconv.FormatUint(d.dataset.Used, 10)},
{"Space Available", strconv.FormatUint(d.dataset.Avail, 10)},
{"Parent Quota", quota},
{"Compression", d.dataset.Compression},
}
}
|
15a232fd |
// GetMetadata returns image/container metadata related to graph driver |
407a626b |
func (d *Driver) GetMetadata(id string) (map[string]string, error) { |
4addda3f |
return map[string]string{
"Mountpoint": d.mountPath(id),
"Dataset": d.zfsPath(id),
}, nil |
407a626b |
}
|
2cb23527 |
func (d *Driver) cloneFilesystem(name, parentName string) error { |
d5151ca8 |
snapshotName := fmt.Sprintf("%d", time.Now().Nanosecond()) |
11e9167a |
parentDataset := zfs.Dataset{Name: parentName} |
d5151ca8 |
snapshot, err := parentDataset.Snapshot(snapshotName /*recursive */, false) |
dd614b5e |
if err != nil { |
d5151ca8 |
return err
}
|
11e9167a |
_, err = snapshot.Clone(name, map[string]string{"mountpoint": "legacy"}) |
2cb23527 |
if err == nil {
d.Lock()
d.filesystemsCache[name] = true
d.Unlock()
}
|
d5151ca8 |
if err != nil {
snapshot.Destroy(zfs.DestroyDeferDeletion)
return err
} |
dd614b5e |
return snapshot.Destroy(zfs.DestroyDeferDeletion) |
d5151ca8 |
}
|
f5f7fee2 |
func (d *Driver) zfsPath(id string) string { |
d5151ca8 |
return d.options.fsName + "/" + id
}
|
f5f7fee2 |
func (d *Driver) mountPath(id string) string { |
112b7e65 |
return path.Join(d.options.mountPath, "graph", getMountpoint(id)) |
11e9167a |
}
|
ef5bfad3 |
// CreateReadWrite creates a layer that is writable for use as a container
// file system. |
b937aa8e |
func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
return d.Create(id, parent, opts) |
ef5bfad3 |
}
|
e27c904b |
// Create prepares the dataset and filesystem for the ZFS driver for the given id under the parent. |
b937aa8e |
func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
var storageOpt map[string]string
if opts != nil {
storageOpt = opts.StorageOpt
}
|
e9183404 |
err := d.create(id, parent, storageOpt) |
d5151ca8 |
if err == nil { |
11e9167a |
return nil
}
if zfsError, ok := err.(*zfs.Error); ok {
if !strings.HasSuffix(zfsError.Stderr, "dataset already exists\n") { |
dd614b5e |
return err
} |
11e9167a |
// aborted build -> cleanup |
dd614b5e |
} else {
return err |
d5151ca8 |
}
|
f5f7fee2 |
dataset := zfs.Dataset{Name: d.zfsPath(id)} |
11e9167a |
if err := dataset.Destroy(zfs.DestroyRecursiveClones); err != nil { |
d5151ca8 |
return err
} |
11e9167a |
// retry |
e9183404 |
return d.create(id, parent, storageOpt) |
d5151ca8 |
}
|
e9183404 |
func (d *Driver) create(id, parent string, storageOpt map[string]string) error { |
f5f7fee2 |
name := d.zfsPath(id) |
e9183404 |
quota, err := parseStorageOpt(storageOpt) |
373654f4 |
if err != nil {
return err
} |
11e9167a |
if parent == "" {
mountoptions := map[string]string{"mountpoint": "legacy"} |
2cb23527 |
fs, err := zfs.CreateFilesystem(name, mountoptions)
if err == nil { |
e9183404 |
err = setQuota(name, quota)
if err == nil {
d.Lock()
d.filesystemsCache[fs.Name] = true
d.Unlock()
} |
2cb23527 |
} |
d5151ca8 |
return err
} |
e9183404 |
err = d.cloneFilesystem(name, d.zfsPath(parent))
if err == nil {
err = setQuota(name, quota)
}
return err
}
func parseStorageOpt(storageOpt map[string]string) (string, error) {
// Read size to change the disk quota per container
for k, v := range storageOpt {
key := strings.ToLower(k)
switch key {
case "size":
return v, nil
default:
return "0", fmt.Errorf("Unknown option %s", key) |
2cb23527 |
} |
e9183404 |
}
return "0", nil
}
func setQuota(name string, quota string) error {
if quota == "0" {
return nil
}
fs, err := zfs.GetDataset(name)
if err != nil { |
d5151ca8 |
return err
} |
e9183404 |
return fs.SetProperty("quota", quota) |
11e9167a |
} |
d5151ca8 |
|
e27c904b |
// Remove deletes the dataset, filesystem and the cache for the given id. |
11e9167a |
func (d *Driver) Remove(id string) error { |
f5f7fee2 |
name := d.zfsPath(id) |
2cb23527 |
dataset := zfs.Dataset{Name: name}
err := dataset.Destroy(zfs.DestroyRecursive)
if err == nil {
d.Lock()
delete(d.filesystemsCache, name)
d.Unlock()
}
return err |
d5151ca8 |
}
|
e27c904b |
// Get returns the mountpoint for the given id after creating the target directories if necessary. |
d5151ca8 |
func (d *Driver) Get(id, mountLabel string) (string, error) { |
f5f7fee2 |
mountpoint := d.mountPath(id) |
009ee16b |
if count := d.ctr.Increment(mountpoint); count > 1 { |
7342060b |
return mountpoint, nil
}
|
f5f7fee2 |
filesystem := d.zfsPath(id) |
19c31a70 |
options := label.FormatMountLabel("", mountLabel) |
2bf73c4b |
logrus.Debugf(`[zfs] mount("%s", "%s", "%s")`, filesystem, mountpoint, options) |
11e9167a |
|
442b4562 |
rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
if err != nil { |
009ee16b |
d.ctr.Decrement(mountpoint) |
442b4562 |
return "", err
} |
11e9167a |
// Create the target directories if they don't exist |
442b4562 |
if err := idtools.MkdirAllAs(mountpoint, 0755, rootUID, rootGID); err != nil { |
009ee16b |
d.ctr.Decrement(mountpoint) |
d5151ca8 |
return "", err
} |
11e9167a |
|
aef0995b |
if err := mount.Mount(filesystem, mountpoint, "zfs", options); err != nil { |
009ee16b |
d.ctr.Decrement(mountpoint) |
11e9167a |
return "", fmt.Errorf("error creating zfs mount of %s to %s: %v", filesystem, mountpoint, err)
} |
7342060b |
|
aef0995b |
// this could be our first mount after creation of the filesystem, and the root dir may still have root
// permissions instead of the remapped root uid:gid (if user namespaces are enabled):
if err := os.Chown(mountpoint, rootUID, rootGID); err != nil { |
7342060b |
mount.Unmount(mountpoint) |
009ee16b |
d.ctr.Decrement(mountpoint) |
aef0995b |
return "", fmt.Errorf("error modifying zfs mountpoint (%s) directory ownership: %v", mountpoint, err)
} |
11e9167a |
return mountpoint, nil |
d5151ca8 |
}
|
e27c904b |
// Put removes the existing mountpoint for the given id if it exists. |
d5151ca8 |
func (d *Driver) Put(id string) error { |
009ee16b |
mountpoint := d.mountPath(id)
if count := d.ctr.Decrement(mountpoint); count > 0 { |
7342060b |
return nil
} |
65d79e3e |
mounted, err := graphdriver.Mounted(graphdriver.FsMagicZfs, mountpoint)
if err != nil || !mounted {
return err |
922986b7 |
}
|
65d79e3e |
logrus.Debugf(`[zfs] unmount("%s")`, mountpoint) |
922986b7 |
|
65d79e3e |
if err := mount.Unmount(mountpoint); err != nil {
return fmt.Errorf("error unmounting to %s: %v", mountpoint, err) |
11e9167a |
} |
d5151ca8 |
return nil
}
|
e27c904b |
// Exists checks to see if the cache entry exists for the given id. |
d5151ca8 |
func (d *Driver) Exists(id string) bool { |
65d79e3e |
d.Lock()
defer d.Unlock() |
f5f7fee2 |
return d.filesystemsCache[d.zfsPath(id)] == true |
d5151ca8 |
} |