containerd/snapshot/overlay/overlay.go
Stephen J Day aeffd4f92c
btrfs: test btrfs snapshots with driver suite
We now include btrfs in the snapshot driver test suite. This includes
the addition of parent links and name hashing into the btrfs driver.
We'll probably endup replacing this with a common metadata store, as
these relationships are generally identical between implementations.

A small bug was discovered in the delete implementation in the course
testing, so the btrfs package has been updated with a fix.

The overlay driver was modified accordingly with the btrfs driver to use
`Driver` as the exported type of each driver packge.

Signed-off-by: Stephen J Day <stephen.day@docker.com>
2017-02-03 15:54:11 -08:00

252 lines
5.5 KiB
Go

package overlay
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"sync"
"github.com/docker/containerd"
digest "github.com/opencontainers/go-digest"
)
type Driver struct {
root string
cache *cache
}
func NewDriver(root string) (*Driver, error) {
if err := os.MkdirAll(root, 0700); err != nil {
return nil, err
}
for _, p := range []string{
"snapshots",
"active",
} {
if err := os.MkdirAll(filepath.Join(root, p), 0700); err != nil {
return nil, err
}
}
return &Driver{
root: root,
cache: newCache(),
}, nil
}
func (o *Driver) Prepare(key, parent string) ([]containerd.Mount, error) {
active, err := o.newActiveDir(key)
if err != nil {
return nil, err
}
if parent != "" {
if err := active.setParent(parent); err != nil {
return nil, err
}
}
return o.Mounts(key)
}
func (o *Driver) View(key, parent string) ([]containerd.Mount, error) {
panic("not implemented")
}
// Mounts returns the mounts for the transaction identified by key. Can be
// called on an read-write or readonly transaction.
//
// This can be used to recover mounts after calling View or Prepare.
func (o *Driver) Mounts(key string) ([]containerd.Mount, error) {
active := o.getActive(key)
return active.mounts(o.cache)
}
func (o *Driver) Commit(name, key string) error {
active := o.getActive(key)
return active.commit(name, o.cache)
}
// Remove abandons the transaction identified by key. All resources
// associated with the key will be removed.
func (o *Driver) Remove(key string) error {
panic("not implemented")
}
// Parent returns the parent of snapshot identified by name.
func (o *Driver) Parent(name string) (string, error) {
ppath, err := o.cache.get(filepath.Join(o.root, "snapshots", hash(name)))
if err != nil {
if os.IsNotExist(err) {
return "", nil // no parent
}
return "", err
}
p, err := ioutil.ReadFile(filepath.Join(ppath, "name"))
if err != nil {
return "", err
}
return string(p), nil
}
// Exists returns true if the snapshot with name exists.
func (o *Driver) Exists(name string) bool {
panic("not implemented")
}
// Delete the snapshot idenfitied by name.
//
// If name has children, the operation will fail.
func (o *Driver) Delete(name string) error {
panic("not implemented")
}
// Walk the committed snapshots.
func (o *Driver) Walk(fn func(name string) error) error {
panic("not implemented")
}
// Active will call fn for each active transaction.
func (o *Driver) Active(fn func(key string) error) error {
panic("not implemented")
}
func (o *Driver) newActiveDir(key string) (*activeDir, error) {
var (
path = filepath.Join(o.root, "active", hash(key))
)
a := &activeDir{
path: path,
snapshotsDir: filepath.Join(o.root, "snapshots"),
}
for _, p := range []string{
"work",
"fs",
} {
if err := os.MkdirAll(filepath.Join(path, p), 0700); err != nil {
a.delete()
return nil, err
}
}
return a, nil
}
func (o *Driver) getActive(key string) *activeDir {
return &activeDir{
path: filepath.Join(o.root, "active", hash(key)),
snapshotsDir: filepath.Join(o.root, "snapshots"),
}
}
func hash(k string) string {
return digest.FromString(k).Hex()
}
type activeDir struct {
snapshotsDir string
path string
}
func (a *activeDir) delete() error {
return os.RemoveAll(a.path)
}
func (a *activeDir) setParent(name string) error {
return os.Symlink(filepath.Join(a.snapshotsDir, hash(name)), filepath.Join(a.path, "parent"))
}
func (a *activeDir) commit(name string, c *cache) error {
// TODO(stevvooe): This doesn't quite meet the current model. The new model
// is to copy all of this out and let the transaction continue. We don't
// really have tests for it yet, but this will be the spot to fix it.
//
// Nothing should be removed until remove is called on the active
// transaction.
if err := os.RemoveAll(filepath.Join(a.path, "work")); err != nil {
return err
}
if err := ioutil.WriteFile(filepath.Join(a.path, "name"), []byte(name), 0644); err != nil {
return err
}
c.invalidate(a.path) // clears parent cache, since we end up moving.
return os.Rename(a.path, filepath.Join(a.snapshotsDir, hash(name)))
}
func (a *activeDir) mounts(c *cache) ([]containerd.Mount, error) {
var (
parents []string
err error
current = a.path
)
for {
if current, err = c.get(current); err != nil {
if os.IsNotExist(err) {
break
}
return nil, err
}
parents = append(parents, filepath.Join(current, "fs"))
}
if len(parents) == 0 {
// if we only have one layer/no parents then just return a bind mount as overlay
// will not work
return []containerd.Mount{
{
Source: filepath.Join(a.path, "fs"),
Type: "bind",
Options: []string{
"rw",
"rbind",
},
},
}, nil
}
options := []string{
fmt.Sprintf("workdir=%s", filepath.Join(a.path, "work")),
fmt.Sprintf("upperdir=%s", filepath.Join(a.path, "fs")),
fmt.Sprintf("lowerdir=%s", strings.Join(parents, ":")),
}
return []containerd.Mount{
{
Type: "overlay",
Source: "overlay",
Options: options,
},
}, nil
}
func newCache() *cache {
return &cache{
parents: make(map[string]string),
}
}
type cache struct {
mu sync.Mutex
parents map[string]string
}
func (c *cache) get(path string) (string, error) {
c.mu.Lock()
defer c.mu.Unlock()
parentRoot, ok := c.parents[path]
if !ok {
link, err := os.Readlink(filepath.Join(path, "parent"))
if err != nil {
return "", err
}
c.parents[path], parentRoot = link, link
}
return parentRoot, nil
}
func (c *cache) invalidate(path string) {
c.mu.Lock()
defer c.mu.Unlock()
delete(c.parents, path)
}