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 }