Tizen_4.0 base
[platform/upstream/docker-engine.git] / daemon / graphdriver / zfs / zfs.go
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/selinux/go-selinux/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 }