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