68fd25221d
We now define the `snapshot.Driver` interface based on earlier work. Many details of the model are worked out, such as snapshot lifecycle and parentage of commits against "Active" snapshots. The impetus of this change is to provide a snapshot POC that does a complete push/pull workflow. The beginnings of a test suite for snapshot drivers is included that we can use to verify the assumptions of drivers. The intent is to port the existing tests over to this test suite and start scaling contributions and test to the snapshot driver subsystem. There are still some details that need to be worked out, such as listing and metadata access. We can do this activity as we further integrate with tooling. Signed-off-by: Stephen J Day <stephen.day@docker.com>
182 lines
3.5 KiB
Go
182 lines
3.5 KiB
Go
package overlay
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/docker/containerd"
|
|
)
|
|
|
|
func NewOverlay(root string) (*Overlay, 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 &Overlay{
|
|
root: root,
|
|
cache: newCache(),
|
|
}, nil
|
|
}
|
|
|
|
type Overlay struct {
|
|
root string
|
|
cache *cache
|
|
}
|
|
|
|
func (o *Overlay) Prepare(key string, parentName string) ([]containerd.Mount, error) {
|
|
if err := validKey(key); err != nil {
|
|
return nil, err
|
|
}
|
|
active, err := o.newActiveDir(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if parentName != "" {
|
|
if err := active.setParent(parentName); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return active.mounts(o.cache)
|
|
}
|
|
|
|
func (o *Overlay) Commit(name, key string) error {
|
|
active := o.getActive(key)
|
|
return active.commit(name)
|
|
}
|
|
|
|
func (o *Overlay) newActiveDir(key string) (*activeDir, error) {
|
|
var (
|
|
hash = hash(key)
|
|
path = filepath.Join(o.root, "active", hash)
|
|
)
|
|
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 *Overlay) getActive(key string) *activeDir {
|
|
return &activeDir{
|
|
path: filepath.Join(o.root, "active", hash(key)),
|
|
snapshotsDir: filepath.Join(o.root, "snapshots"),
|
|
}
|
|
}
|
|
|
|
func validKey(key string) error {
|
|
_, err := filepath.Abs(key)
|
|
return err
|
|
}
|
|
|
|
func hash(k string) string {
|
|
h := md5.New()
|
|
h.Write([]byte(k))
|
|
return hex.EncodeToString(h.Sum(nil))
|
|
}
|
|
|
|
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, name), filepath.Join(a.path, "parent"))
|
|
}
|
|
|
|
func (a *activeDir) commit(name string) error {
|
|
if err := os.RemoveAll(filepath.Join(a.path, "work")); err != nil {
|
|
return err
|
|
}
|
|
return os.Rename(a.path, filepath.Join(a.snapshotsDir, 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
|
|
}
|