github.com/hustcat/docker@v1.3.3-0.20160314103604-901c67a8eeab/daemon/graphdriver/zfs/zfs.go (about) 1 // +build linux freebsd 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 "github.com/Sirupsen/logrus" 17 "github.com/docker/docker/daemon/graphdriver" 18 "github.com/docker/docker/pkg/idtools" 19 "github.com/docker/docker/pkg/mount" 20 "github.com/docker/docker/pkg/parsers" 21 zfs "github.com/mistifyio/go-zfs" 22 "github.com/opencontainers/runc/libcontainer/label" 23 ) 24 25 type activeMount struct { 26 count int 27 path string 28 mounted bool 29 } 30 31 type zfsOptions struct { 32 fsName string 33 mountPath string 34 } 35 36 func init() { 37 graphdriver.Register("zfs", Init) 38 } 39 40 // Logger returns a zfs logger implementation. 41 type Logger struct{} 42 43 // Log wraps log message from ZFS driver with a prefix '[zfs]'. 44 func (*Logger) Log(cmd []string) { 45 logrus.Debugf("[zfs] %s", strings.Join(cmd, " ")) 46 } 47 48 // Init returns a new ZFS driver. 49 // It takes base mount path and a array of options which are represented as key value pairs. 50 // Each option is in the for key=value. 'zfs.fsname' is expected to be a valid key in the options. 51 func Init(base string, opt []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) { 52 var err error 53 54 if _, err := exec.LookPath("zfs"); err != nil { 55 logrus.Debugf("[zfs] zfs command is not available: %v", err) 56 return nil, graphdriver.ErrPrerequisites 57 } 58 59 file, err := os.OpenFile("/dev/zfs", os.O_RDWR, 600) 60 if err != nil { 61 logrus.Debugf("[zfs] cannot open /dev/zfs: %v", err) 62 return nil, graphdriver.ErrPrerequisites 63 } 64 defer file.Close() 65 66 options, err := parseOptions(opt) 67 if err != nil { 68 return nil, err 69 } 70 options.mountPath = base 71 72 rootdir := path.Dir(base) 73 74 if options.fsName == "" { 75 err = checkRootdirFs(rootdir) 76 if err != nil { 77 return nil, err 78 } 79 } 80 81 if options.fsName == "" { 82 options.fsName, err = lookupZfsDataset(rootdir) 83 if err != nil { 84 return nil, err 85 } 86 } 87 88 zfs.SetLogger(new(Logger)) 89 90 filesystems, err := zfs.Filesystems(options.fsName) 91 if err != nil { 92 return nil, fmt.Errorf("Cannot find root filesystem %s: %v", options.fsName, err) 93 } 94 95 filesystemsCache := make(map[string]bool, len(filesystems)) 96 var rootDataset *zfs.Dataset 97 for _, fs := range filesystems { 98 if fs.Name == options.fsName { 99 rootDataset = fs 100 } 101 filesystemsCache[fs.Name] = true 102 } 103 104 if rootDataset == nil { 105 return nil, fmt.Errorf("BUG: zfs get all -t filesystem -rHp '%s' should contain '%s'", options.fsName, options.fsName) 106 } 107 108 d := &Driver{ 109 dataset: rootDataset, 110 options: options, 111 filesystemsCache: filesystemsCache, 112 active: make(map[string]*activeMount), 113 uidMaps: uidMaps, 114 gidMaps: gidMaps, 115 } 116 return graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps), nil 117 } 118 119 func parseOptions(opt []string) (zfsOptions, error) { 120 var options zfsOptions 121 options.fsName = "" 122 for _, option := range opt { 123 key, val, err := parsers.ParseKeyValueOpt(option) 124 if err != nil { 125 return options, err 126 } 127 key = strings.ToLower(key) 128 switch key { 129 case "zfs.fsname": 130 options.fsName = val 131 default: 132 return options, fmt.Errorf("Unknown option %s", key) 133 } 134 } 135 return options, nil 136 } 137 138 func lookupZfsDataset(rootdir string) (string, error) { 139 var stat syscall.Stat_t 140 if err := syscall.Stat(rootdir, &stat); err != nil { 141 return "", fmt.Errorf("Failed to access '%s': %s", rootdir, err) 142 } 143 wantedDev := stat.Dev 144 145 mounts, err := mount.GetMounts() 146 if err != nil { 147 return "", err 148 } 149 for _, m := range mounts { 150 if err := syscall.Stat(m.Mountpoint, &stat); err != nil { 151 logrus.Debugf("[zfs] failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err) 152 continue // may fail on fuse file systems 153 } 154 155 if stat.Dev == wantedDev && m.Fstype == "zfs" { 156 return m.Source, nil 157 } 158 } 159 160 return "", fmt.Errorf("Failed to find zfs dataset mounted on '%s' in /proc/mounts", rootdir) 161 } 162 163 // Driver holds information about the driver, such as zfs dataset, options and cache. 164 type Driver struct { 165 dataset *zfs.Dataset 166 options zfsOptions 167 sync.Mutex // protects filesystem cache against concurrent access 168 filesystemsCache map[string]bool 169 active map[string]*activeMount 170 uidMaps []idtools.IDMap 171 gidMaps []idtools.IDMap 172 } 173 174 func (d *Driver) String() string { 175 return "zfs" 176 } 177 178 // Cleanup is used to implement graphdriver.ProtoDriver. There is no cleanup required for this driver. 179 func (d *Driver) Cleanup() error { 180 return nil 181 } 182 183 // Status returns information about the ZFS filesystem. It returns a two dimensional array of information 184 // such as pool name, dataset name, disk usage, parent quota and compression used. 185 // Currently it return 'Zpool', 'Zpool Health', 'Parent Dataset', 'Space Used By Parent', 186 // 'Space Available', 'Parent Quota' and 'Compression'. 187 func (d *Driver) Status() [][2]string { 188 parts := strings.Split(d.dataset.Name, "/") 189 pool, err := zfs.GetZpool(parts[0]) 190 191 var poolName, poolHealth string 192 if err == nil { 193 poolName = pool.Name 194 poolHealth = pool.Health 195 } else { 196 poolName = fmt.Sprintf("error while getting pool information %v", err) 197 poolHealth = "not available" 198 } 199 200 quota := "no" 201 if d.dataset.Quota != 0 { 202 quota = strconv.FormatUint(d.dataset.Quota, 10) 203 } 204 205 return [][2]string{ 206 {"Zpool", poolName}, 207 {"Zpool Health", poolHealth}, 208 {"Parent Dataset", d.dataset.Name}, 209 {"Space Used By Parent", strconv.FormatUint(d.dataset.Used, 10)}, 210 {"Space Available", strconv.FormatUint(d.dataset.Avail, 10)}, 211 {"Parent Quota", quota}, 212 {"Compression", d.dataset.Compression}, 213 } 214 } 215 216 // GetMetadata returns image/container metadata related to graph driver 217 func (d *Driver) GetMetadata(id string) (map[string]string, error) { 218 return nil, nil 219 } 220 221 func (d *Driver) cloneFilesystem(name, parentName string) error { 222 snapshotName := fmt.Sprintf("%d", time.Now().Nanosecond()) 223 parentDataset := zfs.Dataset{Name: parentName} 224 snapshot, err := parentDataset.Snapshot(snapshotName /*recursive */, false) 225 if err != nil { 226 return err 227 } 228 229 _, err = snapshot.Clone(name, map[string]string{"mountpoint": "legacy"}) 230 if err == nil { 231 d.Lock() 232 d.filesystemsCache[name] = true 233 d.Unlock() 234 } 235 236 if err != nil { 237 snapshot.Destroy(zfs.DestroyDeferDeletion) 238 return err 239 } 240 return snapshot.Destroy(zfs.DestroyDeferDeletion) 241 } 242 243 func (d *Driver) zfsPath(id string) string { 244 return d.options.fsName + "/" + id 245 } 246 247 func (d *Driver) mountPath(id string) string { 248 return path.Join(d.options.mountPath, "graph", getMountpoint(id)) 249 } 250 251 // Create prepares the dataset and filesystem for the ZFS driver for the given id under the parent. 252 func (d *Driver) Create(id string, parent string, mountLabel string) error { 253 err := d.create(id, parent) 254 if err == nil { 255 return nil 256 } 257 if zfsError, ok := err.(*zfs.Error); ok { 258 if !strings.HasSuffix(zfsError.Stderr, "dataset already exists\n") { 259 return err 260 } 261 // aborted build -> cleanup 262 } else { 263 return err 264 } 265 266 dataset := zfs.Dataset{Name: d.zfsPath(id)} 267 if err := dataset.Destroy(zfs.DestroyRecursiveClones); err != nil { 268 return err 269 } 270 271 // retry 272 return d.create(id, parent) 273 } 274 275 func (d *Driver) create(id, parent string) error { 276 name := d.zfsPath(id) 277 if parent == "" { 278 mountoptions := map[string]string{"mountpoint": "legacy"} 279 fs, err := zfs.CreateFilesystem(name, mountoptions) 280 if err == nil { 281 d.Lock() 282 d.filesystemsCache[fs.Name] = true 283 d.Unlock() 284 } 285 return err 286 } 287 return d.cloneFilesystem(name, d.zfsPath(parent)) 288 } 289 290 // Remove deletes the dataset, filesystem and the cache for the given id. 291 func (d *Driver) Remove(id string) error { 292 name := d.zfsPath(id) 293 dataset := zfs.Dataset{Name: name} 294 err := dataset.Destroy(zfs.DestroyRecursive) 295 if err == nil { 296 d.Lock() 297 delete(d.filesystemsCache, name) 298 d.Unlock() 299 } 300 return err 301 } 302 303 // Get returns the mountpoint for the given id after creating the target directories if necessary. 304 func (d *Driver) Get(id, mountLabel string) (string, error) { 305 d.Lock() 306 defer d.Unlock() 307 308 mnt := d.active[id] 309 if mnt != nil { 310 mnt.count++ 311 return mnt.path, nil 312 } 313 314 mnt = &activeMount{count: 1} 315 316 mountpoint := d.mountPath(id) 317 filesystem := d.zfsPath(id) 318 options := label.FormatMountLabel("", mountLabel) 319 logrus.Debugf(`[zfs] mount("%s", "%s", "%s")`, filesystem, mountpoint, options) 320 321 rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) 322 if err != nil { 323 return "", err 324 } 325 // Create the target directories if they don't exist 326 if err := idtools.MkdirAllAs(mountpoint, 0755, rootUID, rootGID); err != nil { 327 return "", err 328 } 329 330 if err := mount.Mount(filesystem, mountpoint, "zfs", options); err != nil { 331 return "", fmt.Errorf("error creating zfs mount of %s to %s: %v", filesystem, mountpoint, err) 332 } 333 // this could be our first mount after creation of the filesystem, and the root dir may still have root 334 // permissions instead of the remapped root uid:gid (if user namespaces are enabled): 335 if err := os.Chown(mountpoint, rootUID, rootGID); err != nil { 336 return "", fmt.Errorf("error modifying zfs mountpoint (%s) directory ownership: %v", mountpoint, err) 337 } 338 mnt.path = mountpoint 339 mnt.mounted = true 340 d.active[id] = mnt 341 342 return mountpoint, nil 343 } 344 345 // Put removes the existing mountpoint for the given id if it exists. 346 func (d *Driver) Put(id string) error { 347 d.Lock() 348 defer d.Unlock() 349 350 mnt := d.active[id] 351 if mnt == nil { 352 logrus.Debugf("[zfs] Put on a non-mounted device %s", id) 353 // but it might be still here 354 if d.Exists(id) { 355 err := mount.Unmount(d.mountPath(id)) 356 if err != nil { 357 logrus.Debugf("[zfs] Failed to unmount %s zfs fs: %v", id, err) 358 } 359 } 360 return nil 361 } 362 363 mnt.count-- 364 if mnt.count > 0 { 365 return nil 366 } 367 368 defer delete(d.active, id) 369 if mnt.mounted { 370 logrus.Debugf(`[zfs] unmount("%s")`, mnt.path) 371 372 if err := mount.Unmount(mnt.path); err != nil { 373 return fmt.Errorf("error unmounting to %s: %v", mnt.path, err) 374 } 375 } 376 return nil 377 } 378 379 // Exists checks to see if the cache entry exists for the given id. 380 func (d *Driver) Exists(id string) bool { 381 return d.filesystemsCache[d.zfsPath(id)] == true 382 }