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