Skip to content

Commit

Permalink
Support additional layer store
Browse files Browse the repository at this point in the history
Signed-off-by: Kohei Tokunaga <[email protected]>
  • Loading branch information
ktock committed Jan 12, 2021
1 parent a774873 commit 01176fb
Show file tree
Hide file tree
Showing 4 changed files with 282 additions and 3 deletions.
27 changes: 27 additions & 0 deletions drivers/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,19 @@ type LayerIDMapUpdater interface {
SupportsShifting() bool
}

// AdditionalLayerStore is the directory where exploded layer diff is stored.
// Each layer can be accessed at:
// Path/base64(RequiredAnnotations[0]=<value>)/.../base64(RequiredAnnotations[len(RequiredAnnotations)]=<value>)/
type AdditionalLayerStore struct {

// Path is the directory where AdditionalLayerStore is available on the host.
Path string

// RequiredAnnotations is a list of keys of annotations necessary to access to
// the contents in this AdditionalLayerStore.
RequiredAnnotations []string
}

// Driver is the interface for layered/snapshot file system drivers.
type Driver interface {
ProtoDriver
Expand All @@ -173,6 +186,20 @@ type CapabilityDriver interface {
Capabilities() Capabilities
}

// AdditionalLayerStoreDriver is the interface for driver that supports
// additional layer store functionality.
type AdditionalLayerStoreDriver interface {
Driver

// CreateFromDirectory creates a new filesystem layer with the specified id and
// parent. Parent may be "". The contents of the layer (diff) will be the directory
// specified by targetDiffDir.
CreateFromDirectory(id, parent string, targetDiffDir string) error

// AdditionalLayerStores returns additional layer stores supported by the driver
AdditionalLayerStores() []AdditionalLayerStore
}

// DiffGetterDriver is the interface for layered file system drivers that
// provide a specialized function for getting file contents for tar-split.
type DiffGetterDriver interface {
Expand Down
73 changes: 70 additions & 3 deletions drivers/overlay/overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ const (

type overlayOptions struct {
imageStores []string
layerStores []graphdriver.AdditionalLayerStore
quota quota.Quota
mountProgram string
skipMountHome bool
Expand Down Expand Up @@ -316,6 +317,33 @@ func parseOptions(options []string) (*overlayOptions, error) {
}
o.imageStores = append(o.imageStores, store)
}
case "layerstore", "additionallayerstore":
logrus.Debugf("overlay: layerstore=%s", val)
// Additional read only layer stores to use for lower paths
if val == "" {
continue
}
for _, store := range strings.Split(val, ",") {
elems := strings.Split(val, ":")
if len(elems) < 2 {
return nil, fmt.Errorf("overlay: layerstore config must be <path>:<anntation1>:<anntation2>:...:<anntationX>")
}
store = filepath.Clean(elems[0])
if !filepath.IsAbs(store) {
return nil, fmt.Errorf("overlay: layerstore path %q is not absolute. Can not be relative", store)
}
st, err := os.Stat(store)
if err != nil {
return nil, fmt.Errorf("overlay: can't stat layerStore dir %s: %v", store, err)
}
if !st.IsDir() {
return nil, fmt.Errorf("overlay: layerstore path %q must be a directory", store)
}
o.layerStores = append(o.layerStores, graphdriver.AdditionalLayerStore{
Path: store,
RequiredAnnotations: elems[1:],
})
}
case "mount_program":
logrus.Debugf("overlay: mount_program=%s", val)
_, err := os.Stat(val)
Expand Down Expand Up @@ -528,6 +556,27 @@ func (d *Driver) Cleanup() error {
return mount.Unmount(d.home)
}

// AdditionalLayerStores returns additional layer stores supported by the driver
func (d *Driver) AdditionalLayerStores() []graphdriver.AdditionalLayerStore {
return d.options.layerStores
}

// CreateFromDirectory creates a new filesystem layer with the specified id and
// parent. Parent may be "". The contents of the layer (diff) will be the directory
// specified by targetDiffDir.
func (d *Driver) CreateFromDirectory(id, parent string, targetDiffDir string) error {
// TODO: support opts
if err := d.Create(id, parent, nil); err != nil {
return err
}
dir := d.dir(id)
diffDir := path.Join(dir, "diff")
if err := os.RemoveAll(diffDir); err != nil {
return err
}
return os.Symlink(targetDiffDir, diffDir)
}

// CreateFromTemplate creates a layer with the same contents and parent as another layer.
func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idtools.IDMappings, parent string, parentIDMappings *idtools.IDMappings, opts *graphdriver.CreateOpts, readWrite bool) error {
if readWrite {
Expand Down Expand Up @@ -1200,8 +1249,26 @@ func (d *Driver) ApplyDiff(id, parent string, options graphdriver.ApplyDiffOpts)

func (d *Driver) getDiffPath(id string) string {
dir := d.dir(id)
diffPath := path.Join(dir, "diff")
if ld, err := os.Readlink(diffPath); err == nil {
// `diff` of additional layer store is symlink to the actual `diff` directory.
diffPath = ld
}
return diffPath
}

return path.Join(dir, "diff")
func (d *Driver) getLowerDiffPaths(id string) ([]string, error) {
layers, err := d.getLowerDirs(id)
if err != nil {
return nil, err
}
for i, l := range layers {
if ld, err := os.Readlink(l); err == nil {
// `diff` of additional layer store is symlink to the actual `diff` directory.
layers[i] = ld
}
}
return layers, nil
}

// DiffSize calculates the changes between the specified id
Expand All @@ -1225,7 +1292,7 @@ func (d *Driver) Diff(id string, idMappings *idtools.IDMappings, parent string,
idMappings = &idtools.IDMappings{}
}

lowerDirs, err := d.getLowerDirs(id)
lowerDirs, err := d.getLowerDiffPaths(id)
if err != nil {
return nil, err
}
Expand All @@ -1250,7 +1317,7 @@ func (d *Driver) Changes(id string, idMappings *idtools.IDMappings, parent strin
// Overlay doesn't have snapshots, so we need to get changes from all parent
// layers.
diffPath := d.getDiffPath(id)
layers, err := d.getLowerDirs(id)
layers, err := d.getLowerDiffPaths(id)
if err != nil {
return nil, err
}
Expand Down
64 changes: 64 additions & 0 deletions layers.go
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,10 @@ type LayerStore interface {
// LoadLocked wraps Load in a locked state. This means it loads the store
// and cleans-up invalid layers if needed.
LoadLocked() error

// PutFromAdditionalLayerStore creates a layer using the diff contained in the additional layer
// store.
PutFromAdditionalLayerStore(id string, parentLayer *Layer, labels map[string]string) (layer *Layer, err error)
}

type layerStore struct {
Expand Down Expand Up @@ -602,6 +606,66 @@ func (r *layerStore) Status() ([][2]string, error) {
return r.driver.Status(), nil
}

func (r *layerStore) PutFromAdditionalLayerStore(id string, parentLayer *Layer, labels map[string]string) (layer *Layer, err error) {
adriver, ok := r.driver.(drivers.AdditionalLayerStoreDriver)
if !ok {
return nil, ErrLayerUnknown
}

var diffDir string
var layerInfo *Layer
for _, ls := range adriver.AdditionalLayerStores() {
if layerInfo, diffDir, err = searchLayer(ls, labels); err == nil {
break
} else if err != ErrLayerUnknown {
return nil, err
}
}

parent := ""
if parentLayer != nil {
parent = parentLayer.ID
}
if err = adriver.CreateFromDirectory(id, parent, diffDir); err != nil {
if id != "" {
return nil, errors.Wrapf(err, "error creating layer from dir with ID %q", id)
}
return nil, errors.Wrapf(err, "error creating layer from dir")
}

compressedsums := make(map[digest.Digest][]string)
uncompressedsums := make(map[digest.Digest][]string)
names := make(map[string]*Layer)
layer = copyLayer(layerInfo)
layer.ID = id
layer.Parent = parent
layer.Created = time.Now().UTC()
if layer.CompressedDigest != "" {
compressedsums[layer.CompressedDigest] = append(compressedsums[layer.CompressedDigest], layer.ID)
}
if layer.UncompressedDigest != "" {
uncompressedsums[layer.UncompressedDigest] = append(uncompressedsums[layer.UncompressedDigest], layer.ID)
}
for _, name := range layer.Names {
if conflict, ok := names[name]; ok {
r.removeName(conflict, name)
}
names[name] = layer
}
// TODO: check if necessary fields are filled
r.layers = append(r.layers, layer)
r.idindex.Add(id)
r.byid[id] = layer
r.byname = names
r.bycompressedsum = compressedsums
r.byuncompressedsum = uncompressedsums
if err := r.Save(); err != nil {
adriver.Remove(id)
return nil, err
}
return copyLayer(layer), nil
}

func (r *layerStore) Put(id string, parentLayer *Layer, names []string, mountLabel string, options map[string]string, moreOptions *LayerOptions, writeable bool, flags map[string]interface{}, diff io.Reader) (layer *Layer, size int64, err error) {
if !r.IsReadWrite() {
return nil, -1, errors.Wrapf(ErrStoreIsReadOnly, "not allowed to create new layers at %q", r.layerspath())
Expand Down
121 changes: 121 additions & 0 deletions store.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package storage

import (
"encoding/base64"
"encoding/json"
"fmt"
"io"
"io/ioutil"
Expand Down Expand Up @@ -488,6 +489,15 @@ type Store interface {

// GetDigestLock returns digest-specific Locker.
GetDigestLock(digest.Digest) (Locker, error)

// LayerFromAdditionalLayerStore searches layers from the additional layer store and
// returns the Layer obeject. Note that this hasn't been stored to this store yet so
// this needs to be done through PutFromAdditionalLayerStore.
LayerFromAdditionalLayerStore(d digest.Digest, labels map[string]string) (*Layer, error)

// PutFromAdditionalLayerStore creates layer using diff contents from additional
// layer store.
PutFromAdditionalLayerStore(id, parent string, d digest.Digest, labels map[string]string) (*Layer, error)
}

// AutoUserNsOptions defines how to automatically create a user namespace.
Expand Down Expand Up @@ -3076,6 +3086,83 @@ func (s *store) Layer(id string) (*Layer, error) {
return nil, ErrLayerUnknown
}

const (
// additionalLayerStoreLabelPrefix is the prefix of labels that will be passed to
// additional layer stores for searching layer contents.
additionalLayerStoreLabelPrefix = "containers/image/target."

// defaultAdditionalLayerStoreDigestLabel is the default label that will be passed to
// additional layer stores for searching layer diff contents. This indicates the digest
// of the compressed diff contents.
defaultAdditionalLayerStoreDigestLabel = additionalLayerStoreLabelPrefix + "layerdigest"
)

func (s *store) LayerFromAdditionalLayerStore(d digest.Digest, labels map[string]string) (*Layer, error) {
adriver, ok := s.graphDriver.(drivers.AdditionalLayerStoreDriver)
if !ok {
return nil, ErrLayerUnknown
}
if labels == nil {
labels = make(map[string]string)
}
for _, ls := range adriver.AdditionalLayerStores() {
labels[defaultAdditionalLayerStoreDigestLabel] = d.String()
if l, _, err := searchLayer(ls, labels); err == nil {
return l, nil
} else if err != ErrLayerUnknown {
return nil, err
}
}
return nil, ErrLayerUnknown
}

func (s *store) PutFromAdditionalLayerStore(id, parent string, d digest.Digest, labels map[string]string) (*Layer, error) {
rlstore, err := s.LayerStore()
if err != nil {
return nil, err
}
rlstore.Lock()
defer rlstore.Unlock()
if modified, err := rlstore.Modified(); modified || err != nil {
if err = rlstore.Load(); err != nil {
return nil, err
}
}
rlstores, err := s.ROLayerStores()
if err != nil {
return nil, err
}

var parentLayer *Layer
if parent != "" {
for _, s := range append([]ROLayerStore{rlstore}, rlstores...) {
lstore := s
if lstore != rlstore {
lstore.RLock()
defer lstore.Unlock()
if modified, err := lstore.Modified(); modified || err != nil {
if err = lstore.Load(); err != nil {
return nil, err
}
}
}
parentLayer, err = lstore.Get(parent)
if err == nil {
break
}
}
if parentLayer == nil {
return nil, ErrLayerUnknown
}
}

if labels == nil {
labels = make(map[string]string)
}
labels[defaultAdditionalLayerStoreDigestLabel] = d.String()
return rlstore.PutFromAdditionalLayerStore(id, parentLayer, labels)
}

func (s *store) Image(id string) (*Image, error) {
istore, err := s.ImageStore()
if err != nil {
Expand Down Expand Up @@ -3682,3 +3769,37 @@ func (s *store) Free() {
}
}
}

func searchLayer(ls drivers.AdditionalLayerStore, annotations map[string]string) (*Layer, string, error) {
var kvs []string
for _, k := range ls.RequiredAnnotations {
v, ok := annotations[additionalLayerStoreLabelPrefix+k]
if !ok {
return nil, "", ErrLayerUnknown
}
name := base64.StdEncoding.EncodeToString([]byte(k + "=" + v))
if name == "diff" || name == "info" {
return nil, "", fmt.Errorf("annotation must not be \"diff\" or \"info\"")
}
kvs = append(kvs, name)
}
target := filepath.Join(append([]string{ls.Path}, kvs...)...)
if _, err := os.Stat(filepath.Join(target, "diff")); err == nil {
infoF, err := os.Open(filepath.Join(target, "info"))
if err != nil {
return nil, "", err
}
var layer Layer
if err := json.NewDecoder(infoF).Decode(&layer); err != nil {
return nil, "", err
}
diffPath := filepath.Join(target, "diff")
if ld, err := os.Readlink(diffPath); err == nil {
// `diff` can be symlink
diffPath = ld
}
return &layer, diffPath, nil
}

return nil, "", ErrLayerUnknown
}

0 comments on commit 01176fb

Please sign in to comment.