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