github.com/endocode/docker@v1.4.2-0.20160113120958-46eb4700391e/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 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 a 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 d := &Driver{ 103 dataset: rootDataset, 104 options: options, 105 filesystemsCache: filesystemsCache, 106 uidMaps: uidMaps, 107 gidMaps: gidMaps, 108 } 109 return graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps), nil 110 } 111 112 func parseOptions(opt []string) (zfsOptions, error) { 113 var options zfsOptions 114 options.fsName = "" 115 for _, option := range opt { 116 key, val, err := parsers.ParseKeyValueOpt(option) 117 if err != nil { 118 return options, err 119 } 120 key = strings.ToLower(key) 121 switch key { 122 case "zfs.fsname": 123 options.fsName = val 124 default: 125 return options, fmt.Errorf("Unknown option %s", key) 126 } 127 } 128 return options, nil 129 } 130 131 func lookupZfsDataset(rootdir string) (string, error) { 132 var stat syscall.Stat_t 133 if err := syscall.Stat(rootdir, &stat); err != nil { 134 return "", fmt.Errorf("Failed to access '%s': %s", rootdir, err) 135 } 136 wantedDev := stat.Dev 137 138 mounts, err := mount.GetMounts() 139 if err != nil { 140 return "", err 141 } 142 for _, m := range mounts { 143 if err := syscall.Stat(m.Mountpoint, &stat); err != nil { 144 logrus.Debugf("[zfs] failed to stat '%s' while scanning for zfs mount: %v", m.Mountpoint, err) 145 continue // may fail on fuse file systems 146 } 147 148 if stat.Dev == wantedDev && m.Fstype == "zfs" { 149 return m.Source, nil 150 } 151 } 152 153 return "", fmt.Errorf("Failed to find zfs dataset mounted on '%s' in /proc/mounts", rootdir) 154 } 155 156 // Driver holds information about the driver, such as zfs dataset, options and cache. 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 uidMaps []idtools.IDMap 163 gidMaps []idtools.IDMap 164 } 165 166 func (d *Driver) String() string { 167 return "zfs" 168 } 169 170 // Cleanup is used to implement graphdriver.ProtoDriver. There is no cleanup required for this driver. 171 func (d *Driver) Cleanup() error { 172 return nil 173 } 174 175 // Status returns information about the ZFS filesystem. It returns a two dimensional array of information 176 // such as pool name, dataset name, disk usage, parent quota and compression used. 177 // Currently it return 'Zpool', 'Zpool Health', 'Parent Dataset', 'Space Used By Parent', 178 // 'Space Available', 'Parent Quota' and 'Compression'. 179 func (d *Driver) Status() [][2]string { 180 parts := strings.Split(d.dataset.Name, "/") 181 pool, err := zfs.GetZpool(parts[0]) 182 183 var poolName, poolHealth string 184 if err == nil { 185 poolName = pool.Name 186 poolHealth = pool.Health 187 } else { 188 poolName = fmt.Sprintf("error while getting pool information %v", err) 189 poolHealth = "not available" 190 } 191 192 quota := "no" 193 if d.dataset.Quota != 0 { 194 quota = strconv.FormatUint(d.dataset.Quota, 10) 195 } 196 197 return [][2]string{ 198 {"Zpool", poolName}, 199 {"Zpool Health", poolHealth}, 200 {"Parent Dataset", d.dataset.Name}, 201 {"Space Used By Parent", strconv.FormatUint(d.dataset.Used, 10)}, 202 {"Space Available", strconv.FormatUint(d.dataset.Avail, 10)}, 203 {"Parent Quota", quota}, 204 {"Compression", d.dataset.Compression}, 205 } 206 } 207 208 // GetMetadata returns image/container metadata related to graph driver 209 func (d *Driver) GetMetadata(id string) (map[string]string, error) { 210 return nil, nil 211 } 212 213 func (d *Driver) cloneFilesystem(name, parentName string) error { 214 snapshotName := fmt.Sprintf("%d", time.Now().Nanosecond()) 215 parentDataset := zfs.Dataset{Name: parentName} 216 snapshot, err := parentDataset.Snapshot(snapshotName /*recursive */, false) 217 if err != nil { 218 return err 219 } 220 221 _, err = snapshot.Clone(name, map[string]string{"mountpoint": "legacy"}) 222 if err == nil { 223 d.Lock() 224 d.filesystemsCache[name] = true 225 d.Unlock() 226 } 227 228 if err != nil { 229 snapshot.Destroy(zfs.DestroyDeferDeletion) 230 return err 231 } 232 return snapshot.Destroy(zfs.DestroyDeferDeletion) 233 } 234 235 func (d *Driver) zfsPath(id string) string { 236 return d.options.fsName + "/" + id 237 } 238 239 func (d *Driver) mountPath(id string) string { 240 return path.Join(d.options.mountPath, "graph", getMountpoint(id)) 241 } 242 243 // Create prepares the dataset and filesystem for the ZFS driver for the given id under the parent. 244 func (d *Driver) Create(id string, parent string, mountLabel string) error { 245 err := d.create(id, parent) 246 if err == nil { 247 return nil 248 } 249 if zfsError, ok := err.(*zfs.Error); ok { 250 if !strings.HasSuffix(zfsError.Stderr, "dataset already exists\n") { 251 return err 252 } 253 // aborted build -> cleanup 254 } else { 255 return err 256 } 257 258 dataset := zfs.Dataset{Name: d.zfsPath(id)} 259 if err := dataset.Destroy(zfs.DestroyRecursiveClones); err != nil { 260 return err 261 } 262 263 // retry 264 return d.create(id, parent) 265 } 266 267 func (d *Driver) create(id, parent string) error { 268 name := d.zfsPath(id) 269 if parent == "" { 270 mountoptions := map[string]string{"mountpoint": "legacy"} 271 fs, err := zfs.CreateFilesystem(name, mountoptions) 272 if err == nil { 273 d.Lock() 274 d.filesystemsCache[fs.Name] = true 275 d.Unlock() 276 } 277 return err 278 } 279 return d.cloneFilesystem(name, d.zfsPath(parent)) 280 } 281 282 // Remove deletes the dataset, filesystem and the cache for the given id. 283 func (d *Driver) Remove(id string) error { 284 name := d.zfsPath(id) 285 dataset := zfs.Dataset{Name: name} 286 err := dataset.Destroy(zfs.DestroyRecursive) 287 if err == nil { 288 d.Lock() 289 delete(d.filesystemsCache, name) 290 d.Unlock() 291 } 292 return err 293 } 294 295 // Get returns the mountpoint for the given id after creating the target directories if necessary. 296 func (d *Driver) Get(id, mountLabel string) (string, error) { 297 mountpoint := d.mountPath(id) 298 filesystem := d.zfsPath(id) 299 options := label.FormatMountLabel("", mountLabel) 300 logrus.Debugf(`[zfs] mount("%s", "%s", "%s")`, filesystem, mountpoint, options) 301 302 rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps) 303 if err != nil { 304 return "", err 305 } 306 // Create the target directories if they don't exist 307 if err := idtools.MkdirAllAs(mountpoint, 0755, rootUID, rootGID); err != nil { 308 return "", err 309 } 310 311 err = mount.Mount(filesystem, mountpoint, "zfs", options) 312 if err != nil { 313 return "", fmt.Errorf("error creating zfs mount of %s to %s: %v", filesystem, mountpoint, err) 314 } 315 316 return mountpoint, nil 317 } 318 319 // Put removes the existing mountpoint for the given id if it exists. 320 func (d *Driver) Put(id string) error { 321 mountpoint := d.mountPath(id) 322 logrus.Debugf(`[zfs] unmount("%s")`, mountpoint) 323 324 if err := mount.Unmount(mountpoint); err != nil { 325 return fmt.Errorf("error unmounting to %s: %v", mountpoint, err) 326 } 327 return nil 328 } 329 330 // Exists checks to see if the cache entry exists for the given id. 331 func (d *Driver) Exists(id string) bool { 332 return d.filesystemsCache[d.zfsPath(id)] == true 333 }