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