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 }