github.com/circular-dark/docker@v1.7.0/daemon/graphdriver/zfs/zfs.go (about)

     1  // +build linux
     2  
     3  package zfs
     4  
     5  import (
     6  	"fmt"
     7  	"os"
     8  	"os/exec"
     9  	"path"
    10  	"strconv"
    11  	"strings"
    12  	"sync"
    13  	"syscall"
    14  	"time"
    15  
    16  	log "github.com/Sirupsen/logrus"
    17  	"github.com/docker/docker/daemon/graphdriver"
    18  	"github.com/docker/docker/pkg/mount"
    19  	"github.com/docker/docker/pkg/parsers"
    20  	"github.com/docker/libcontainer/label"
    21  	zfs "github.com/mistifyio/go-zfs"
    22  )
    23  
    24  type ZfsOptions struct {
    25  	fsName    string
    26  	mountPath string
    27  }
    28  
    29  func init() {
    30  	graphdriver.Register("zfs", Init)
    31  }
    32  
    33  type Logger struct{}
    34  
    35  func (*Logger) Log(cmd []string) {
    36  	log.Debugf("[zfs] %s", strings.Join(cmd, " "))
    37  }
    38  
    39  func Init(base string, opt []string) (graphdriver.Driver, error) {
    40  	var err error
    41  	options, err := parseOptions(opt)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  	options.mountPath = base
    46  
    47  	rootdir := path.Dir(base)
    48  
    49  	if options.fsName == "" {
    50  		err = checkRootdirFs(rootdir)
    51  		if err != nil {
    52  			return nil, err
    53  		}
    54  	}
    55  
    56  	if _, err := exec.LookPath("zfs"); err != nil {
    57  		return nil, fmt.Errorf("zfs command is not available: %v", err)
    58  	}
    59  
    60  	file, err := os.OpenFile("/dev/zfs", os.O_RDWR, 600)
    61  	if err != nil {
    62  		return nil, fmt.Errorf("cannot open /dev/zfs: %v", err)
    63  	}
    64  	defer file.Close()
    65  
    66  	if options.fsName == "" {
    67  		options.fsName, err = lookupZfsDataset(rootdir)
    68  		if err != nil {
    69  			return nil, err
    70  		}
    71  	}
    72  
    73  	zfs.SetLogger(new(Logger))
    74  
    75  	filesystems, err := zfs.Filesystems(options.fsName)
    76  	if err != nil {
    77  		return nil, fmt.Errorf("Cannot find root filesystem %s: %v", options.fsName, err)
    78  	}
    79  
    80  	filesystemsCache := make(map[string]bool, len(filesystems))
    81  	var rootDataset *zfs.Dataset
    82  	for _, fs := range filesystems {
    83  		if fs.Name == options.fsName {
    84  			rootDataset = fs
    85  		}
    86  		filesystemsCache[fs.Name] = true
    87  	}
    88  
    89  	if rootDataset == nil {
    90  		return nil, fmt.Errorf("BUG: zfs get all -t filesystems -rHp '%s' should contain '%s'", options.fsName, options.fsName)
    91  	}
    92  
    93  	d := &Driver{
    94  		dataset:          rootDataset,
    95  		options:          options,
    96  		filesystemsCache: filesystemsCache,
    97  	}
    98  	return graphdriver.NaiveDiffDriver(d), nil
    99  }
   100  
   101  func parseOptions(opt []string) (ZfsOptions, error) {
   102  	var options ZfsOptions
   103  	options.fsName = ""
   104  	for _, option := range opt {
   105  		key, val, err := parsers.ParseKeyValueOpt(option)
   106  		if err != nil {
   107  			return options, err
   108  		}
   109  		key = strings.ToLower(key)
   110  		switch key {
   111  		case "zfs.fsname":
   112  			options.fsName = val
   113  		default:
   114  			return options, fmt.Errorf("Unknown option %s", key)
   115  		}
   116  	}
   117  	return options, nil
   118  }
   119  
   120  func checkRootdirFs(rootdir string) error {
   121  	var buf syscall.Statfs_t
   122  	if err := syscall.Statfs(rootdir, &buf); err != nil {
   123  		return fmt.Errorf("Failed to access '%s': %s", rootdir, err)
   124  	}
   125  
   126  	if graphdriver.FsMagic(buf.Type) != graphdriver.FsMagicZfs {
   127  		log.Debugf("[zfs] no zfs dataset found for rootdir '%s'", rootdir)
   128  		return graphdriver.ErrPrerequisites
   129  	}
   130  	return nil
   131  }
   132  
   133  func lookupZfsDataset(rootdir string) (string, error) {
   134  	var stat syscall.Stat_t
   135  	if err := syscall.Stat(rootdir, &stat); err != nil {
   136  		return "", fmt.Errorf("Failed to access '%s': %s", rootdir, err)
   137  	}
   138  	wantedDev := stat.Dev
   139  
   140  	mounts, err := mount.GetMounts()
   141  	if err != nil {
   142  		return "", err
   143  	}
   144  	for _, m := range mounts {
   145  		if err := syscall.Stat(m.Mountpoint, &stat); err != nil {
   146  			log.Debugf("[zfs] failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err)
   147  			continue // may fail on fuse file systems
   148  		}
   149  
   150  		if stat.Dev == wantedDev && m.Fstype == "zfs" {
   151  			return m.Source, nil
   152  		}
   153  	}
   154  
   155  	return "", fmt.Errorf("Failed to find zfs dataset mounted on '%s' in /proc/mounts", rootdir)
   156  }
   157  
   158  type Driver struct {
   159  	dataset          *zfs.Dataset
   160  	options          ZfsOptions
   161  	sync.Mutex       // protects filesystem cache against concurrent access
   162  	filesystemsCache map[string]bool
   163  }
   164  
   165  func (d *Driver) String() string {
   166  	return "zfs"
   167  }
   168  
   169  func (d *Driver) Cleanup() error {
   170  	return nil
   171  }
   172  
   173  func (d *Driver) Status() [][2]string {
   174  	parts := strings.Split(d.dataset.Name, "/")
   175  	pool, err := zfs.GetZpool(parts[0])
   176  
   177  	var poolName, poolHealth string
   178  	if err == nil {
   179  		poolName = pool.Name
   180  		poolHealth = pool.Health
   181  	} else {
   182  		poolName = fmt.Sprintf("error while getting pool information %v", err)
   183  		poolHealth = "not available"
   184  	}
   185  
   186  	quota := "no"
   187  	if d.dataset.Quota != 0 {
   188  		quota = strconv.FormatUint(d.dataset.Quota, 10)
   189  	}
   190  
   191  	return [][2]string{
   192  		{"Zpool", poolName},
   193  		{"Zpool Health", poolHealth},
   194  		{"Parent Dataset", d.dataset.Name},
   195  		{"Space Used By Parent", strconv.FormatUint(d.dataset.Used, 10)},
   196  		{"Space Available", strconv.FormatUint(d.dataset.Avail, 10)},
   197  		{"Parent Quota", quota},
   198  		{"Compression", d.dataset.Compression},
   199  	}
   200  }
   201  
   202  func (d *Driver) cloneFilesystem(name, parentName string) error {
   203  	snapshotName := fmt.Sprintf("%d", time.Now().Nanosecond())
   204  	parentDataset := zfs.Dataset{Name: parentName}
   205  	snapshot, err := parentDataset.Snapshot(snapshotName /*recursive */, false)
   206  	if err != nil {
   207  		return err
   208  	}
   209  
   210  	_, err = snapshot.Clone(name, map[string]string{"mountpoint": "legacy"})
   211  	if err == nil {
   212  		d.Lock()
   213  		d.filesystemsCache[name] = true
   214  		d.Unlock()
   215  	}
   216  
   217  	if err != nil {
   218  		snapshot.Destroy(zfs.DestroyDeferDeletion)
   219  		return err
   220  	}
   221  	return snapshot.Destroy(zfs.DestroyDeferDeletion)
   222  }
   223  
   224  func (d *Driver) ZfsPath(id string) string {
   225  	return d.options.fsName + "/" + id
   226  }
   227  
   228  func (d *Driver) MountPath(id string) string {
   229  	return path.Join(d.options.mountPath, "graph", id)
   230  }
   231  
   232  func (d *Driver) Create(id string, parent string) error {
   233  	err := d.create(id, parent)
   234  	if err == nil {
   235  		return nil
   236  	}
   237  	if zfsError, ok := err.(*zfs.Error); ok {
   238  		if !strings.HasSuffix(zfsError.Stderr, "dataset already exists\n") {
   239  			return err
   240  		}
   241  		// aborted build -> cleanup
   242  	} else {
   243  		return err
   244  	}
   245  
   246  	dataset := zfs.Dataset{Name: d.ZfsPath(id)}
   247  	if err := dataset.Destroy(zfs.DestroyRecursiveClones); err != nil {
   248  		return err
   249  	}
   250  
   251  	// retry
   252  	return d.create(id, parent)
   253  }
   254  
   255  func (d *Driver) create(id, parent string) error {
   256  	name := d.ZfsPath(id)
   257  	if parent == "" {
   258  		mountoptions := map[string]string{"mountpoint": "legacy"}
   259  		fs, err := zfs.CreateFilesystem(name, mountoptions)
   260  		if err == nil {
   261  			d.Lock()
   262  			d.filesystemsCache[fs.Name] = true
   263  			d.Unlock()
   264  		}
   265  		return err
   266  	}
   267  	return d.cloneFilesystem(name, d.ZfsPath(parent))
   268  }
   269  
   270  func (d *Driver) Remove(id string) error {
   271  	name := d.ZfsPath(id)
   272  	dataset := zfs.Dataset{Name: name}
   273  	err := dataset.Destroy(zfs.DestroyRecursive)
   274  	if err == nil {
   275  		d.Lock()
   276  		delete(d.filesystemsCache, name)
   277  		d.Unlock()
   278  	}
   279  	return err
   280  }
   281  
   282  func (d *Driver) Get(id, mountLabel string) (string, error) {
   283  	mountpoint := d.MountPath(id)
   284  	filesystem := d.ZfsPath(id)
   285  	options := label.FormatMountLabel("", mountLabel)
   286  	log.Debugf(`[zfs] mount("%s", "%s", "%s")`, filesystem, mountpoint, options)
   287  
   288  	// Create the target directories if they don't exist
   289  	if err := os.MkdirAll(mountpoint, 0755); err != nil && !os.IsExist(err) {
   290  		return "", err
   291  	}
   292  
   293  	err := mount.Mount(filesystem, mountpoint, "zfs", options)
   294  	if err != nil {
   295  		return "", fmt.Errorf("error creating zfs mount of %s to %s: %v", filesystem, mountpoint, err)
   296  	}
   297  
   298  	return mountpoint, nil
   299  }
   300  
   301  func (d *Driver) Put(id string) error {
   302  	mountpoint := d.MountPath(id)
   303  	log.Debugf(`[zfs] unmount("%s")`, mountpoint)
   304  
   305  	if err := mount.Unmount(mountpoint); err != nil {
   306  		return fmt.Errorf("error unmounting to %s: %v", mountpoint, err)
   307  	}
   308  	return nil
   309  }
   310  
   311  func (d *Driver) Exists(id string) bool {
   312  	return d.filesystemsCache[d.ZfsPath(id)] == true
   313  }