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