github.com/chenchun/docker@v1.3.2-0.20150629222414-20467faf132b/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 filesystem -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 lookupZfsDataset(rootdir string) (string, error) {
   121  	var stat syscall.Stat_t
   122  	if err := syscall.Stat(rootdir, &stat); err != nil {
   123  		return "", fmt.Errorf("Failed to access '%s': %s", rootdir, err)
   124  	}
   125  	wantedDev := stat.Dev
   126  
   127  	mounts, err := mount.GetMounts()
   128  	if err != nil {
   129  		return "", err
   130  	}
   131  	for _, m := range mounts {
   132  		if err := syscall.Stat(m.Mountpoint, &stat); err != nil {
   133  			log.Debugf("[zfs] failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err)
   134  			continue // may fail on fuse file systems
   135  		}
   136  
   137  		if stat.Dev == wantedDev && m.Fstype == "zfs" {
   138  			return m.Source, nil
   139  		}
   140  	}
   141  
   142  	return "", fmt.Errorf("Failed to find zfs dataset mounted on '%s' in /proc/mounts", rootdir)
   143  }
   144  
   145  type Driver struct {
   146  	dataset          *zfs.Dataset
   147  	options          ZfsOptions
   148  	sync.Mutex       // protects filesystem cache against concurrent access
   149  	filesystemsCache map[string]bool
   150  }
   151  
   152  func (d *Driver) String() string {
   153  	return "zfs"
   154  }
   155  
   156  func (d *Driver) Cleanup() error {
   157  	return nil
   158  }
   159  
   160  func (d *Driver) Status() [][2]string {
   161  	parts := strings.Split(d.dataset.Name, "/")
   162  	pool, err := zfs.GetZpool(parts[0])
   163  
   164  	var poolName, poolHealth string
   165  	if err == nil {
   166  		poolName = pool.Name
   167  		poolHealth = pool.Health
   168  	} else {
   169  		poolName = fmt.Sprintf("error while getting pool information %v", err)
   170  		poolHealth = "not available"
   171  	}
   172  
   173  	quota := "no"
   174  	if d.dataset.Quota != 0 {
   175  		quota = strconv.FormatUint(d.dataset.Quota, 10)
   176  	}
   177  
   178  	return [][2]string{
   179  		{"Zpool", poolName},
   180  		{"Zpool Health", poolHealth},
   181  		{"Parent Dataset", d.dataset.Name},
   182  		{"Space Used By Parent", strconv.FormatUint(d.dataset.Used, 10)},
   183  		{"Space Available", strconv.FormatUint(d.dataset.Avail, 10)},
   184  		{"Parent Quota", quota},
   185  		{"Compression", d.dataset.Compression},
   186  	}
   187  }
   188  
   189  func (d *Driver) GetMetadata(id string) (map[string]string, error) {
   190  	return nil, nil
   191  }
   192  
   193  func (d *Driver) cloneFilesystem(name, parentName string) error {
   194  	snapshotName := fmt.Sprintf("%d", time.Now().Nanosecond())
   195  	parentDataset := zfs.Dataset{Name: parentName}
   196  	snapshot, err := parentDataset.Snapshot(snapshotName /*recursive */, false)
   197  	if err != nil {
   198  		return err
   199  	}
   200  
   201  	_, err = snapshot.Clone(name, map[string]string{"mountpoint": "legacy"})
   202  	if err == nil {
   203  		d.Lock()
   204  		d.filesystemsCache[name] = true
   205  		d.Unlock()
   206  	}
   207  
   208  	if err != nil {
   209  		snapshot.Destroy(zfs.DestroyDeferDeletion)
   210  		return err
   211  	}
   212  	return snapshot.Destroy(zfs.DestroyDeferDeletion)
   213  }
   214  
   215  func (d *Driver) ZfsPath(id string) string {
   216  	return d.options.fsName + "/" + id
   217  }
   218  
   219  func (d *Driver) MountPath(id string) string {
   220  	return path.Join(d.options.mountPath, "graph", getMountpoint(id))
   221  }
   222  
   223  func (d *Driver) Create(id string, parent string) error {
   224  	err := d.create(id, parent)
   225  	if err == nil {
   226  		return nil
   227  	}
   228  	if zfsError, ok := err.(*zfs.Error); ok {
   229  		if !strings.HasSuffix(zfsError.Stderr, "dataset already exists\n") {
   230  			return err
   231  		}
   232  		// aborted build -> cleanup
   233  	} else {
   234  		return err
   235  	}
   236  
   237  	dataset := zfs.Dataset{Name: d.ZfsPath(id)}
   238  	if err := dataset.Destroy(zfs.DestroyRecursiveClones); err != nil {
   239  		return err
   240  	}
   241  
   242  	// retry
   243  	return d.create(id, parent)
   244  }
   245  
   246  func (d *Driver) create(id, parent string) error {
   247  	name := d.ZfsPath(id)
   248  	if parent == "" {
   249  		mountoptions := map[string]string{"mountpoint": "legacy"}
   250  		fs, err := zfs.CreateFilesystem(name, mountoptions)
   251  		if err == nil {
   252  			d.Lock()
   253  			d.filesystemsCache[fs.Name] = true
   254  			d.Unlock()
   255  		}
   256  		return err
   257  	}
   258  	return d.cloneFilesystem(name, d.ZfsPath(parent))
   259  }
   260  
   261  func (d *Driver) Remove(id string) error {
   262  	name := d.ZfsPath(id)
   263  	dataset := zfs.Dataset{Name: name}
   264  	err := dataset.Destroy(zfs.DestroyRecursive)
   265  	if err == nil {
   266  		d.Lock()
   267  		delete(d.filesystemsCache, name)
   268  		d.Unlock()
   269  	}
   270  	return err
   271  }
   272  
   273  func (d *Driver) Get(id, mountLabel string) (string, error) {
   274  	mountpoint := d.MountPath(id)
   275  	filesystem := d.ZfsPath(id)
   276  	options := label.FormatMountLabel("", mountLabel)
   277  	log.Debugf(`[zfs] mount("%s", "%s", "%s")`, filesystem, mountpoint, options)
   278  
   279  	// Create the target directories if they don't exist
   280  	if err := os.MkdirAll(mountpoint, 0755); err != nil && !os.IsExist(err) {
   281  		return "", err
   282  	}
   283  
   284  	err := mount.Mount(filesystem, mountpoint, "zfs", options)
   285  	if err != nil {
   286  		return "", fmt.Errorf("error creating zfs mount of %s to %s: %v", filesystem, mountpoint, err)
   287  	}
   288  
   289  	return mountpoint, nil
   290  }
   291  
   292  func (d *Driver) Put(id string) error {
   293  	mountpoint := d.MountPath(id)
   294  	log.Debugf(`[zfs] unmount("%s")`, mountpoint)
   295  
   296  	if err := mount.Unmount(mountpoint); err != nil {
   297  		return fmt.Errorf("error unmounting to %s: %v", mountpoint, err)
   298  	}
   299  	return nil
   300  }
   301  
   302  func (d *Driver) Exists(id string) bool {
   303  	return d.filesystemsCache[d.ZfsPath(id)] == true
   304  }