From aa96c3176bf9dc6e14c6bbcf065ceb3a3870886a Mon Sep 17 00:00:00 2001 From: Alfred Landrum Date: Mon, 20 Mar 2017 11:38:17 -0700 Subject: [PATCH] Let graphdrivers declare diff stream fidelity This allows graphdrivers to declare that they can reproduce the original diff stream for a layer. If they do so, the layer store will not use tar-split processing, but will still verify the digest on layer export. This makes it easier to experiment with non-default diff formats. Signed-off-by: Alfred Landrum --- daemon/graphdriver/driver.go | 17 ++++++++ daemon/graphdriver/plugin.go | 2 +- daemon/graphdriver/proxy.go | 37 +++++++++++++--- docs/extend/plugins_graphdriver.md | 23 ++++++++++ layer/layer_store.go | 69 +++++++++++++++++++++++------- layer/ro_layer.go | 15 ++----- 6 files changed, 128 insertions(+), 35 deletions(-) diff --git a/daemon/graphdriver/driver.go b/daemon/graphdriver/driver.go index 52804cf875..88f190d9e1 100644 --- a/daemon/graphdriver/driver.go +++ b/daemon/graphdriver/driver.go @@ -112,6 +112,23 @@ type Driver interface { DiffDriver } +// Capabilities defines a list of capabilities a driver may implement. +// These capabilities are not required; however, they do determine how a +// graphdriver can be used. +type Capabilities struct { + // Flags that this driver is capable of reproducing exactly equivalent + // diffs for read-only layers. If set, clients can rely on the driver + // for consistent tar streams, and avoid extra processing to account + // for potential differences (eg: the layer store's use of tar-split). + ReproducesExactDiffs bool +} + +// CapabilityDriver is the interface for layered file system drivers that +// can report on their Capabilities. +type CapabilityDriver interface { + Capabilities() Capabilities +} + // DiffGetterDriver is the interface for layered file system drivers that // provide a specialized function for getting file contents for tar-split. type DiffGetterDriver interface { diff --git a/daemon/graphdriver/plugin.go b/daemon/graphdriver/plugin.go index d89d1b4964..f6852f0752 100644 --- a/daemon/graphdriver/plugin.go +++ b/daemon/graphdriver/plugin.go @@ -38,6 +38,6 @@ func newPluginDriver(name string, pl plugingetter.CompatPlugin, config Options) } } } - proxy := &graphDriverProxy{name, pl} + proxy := &graphDriverProxy{name, pl, Capabilities{}} return proxy, proxy.Init(filepath.Join(home, name), config.DriverOptions, config.UIDMaps, config.GIDMaps) } diff --git a/daemon/graphdriver/proxy.go b/daemon/graphdriver/proxy.go index a74ef8c472..120afad459 100644 --- a/daemon/graphdriver/proxy.go +++ b/daemon/graphdriver/proxy.go @@ -9,11 +9,13 @@ import ( "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/idtools" "github.com/docker/docker/pkg/plugingetter" + "github.com/docker/docker/pkg/plugins" ) type graphDriverProxy struct { name string p plugingetter.CompatPlugin + caps Capabilities } type graphDriverRequest struct { @@ -24,13 +26,14 @@ type graphDriverRequest struct { } type graphDriverResponse struct { - Err string `json:",omitempty"` - Dir string `json:",omitempty"` - Exists bool `json:",omitempty"` - Status [][2]string `json:",omitempty"` - Changes []archive.Change `json:",omitempty"` - Size int64 `json:",omitempty"` - Metadata map[string]string `json:",omitempty"` + Err string `json:",omitempty"` + Dir string `json:",omitempty"` + Exists bool `json:",omitempty"` + Status [][2]string `json:",omitempty"` + Changes []archive.Change `json:",omitempty"` + Size int64 `json:",omitempty"` + Metadata map[string]string `json:",omitempty"` + Capabilities Capabilities `json:",omitempty"` } type graphDriverInitRequest struct { @@ -60,13 +63,33 @@ func (d *graphDriverProxy) Init(home string, opts []string, uidMaps, gidMaps []i if ret.Err != "" { return errors.New(ret.Err) } + caps, err := d.fetchCaps() + if err != nil { + return err + } + d.caps = caps return nil } +func (d *graphDriverProxy) fetchCaps() (Capabilities, error) { + args := &graphDriverRequest{} + var ret graphDriverResponse + if err := d.p.Client().Call("GraphDriver.Capabilities", args, &ret); err != nil { + if !plugins.IsNotFound(err) { + return Capabilities{}, err + } + } + return ret.Capabilities, nil +} + func (d *graphDriverProxy) String() string { return d.name } +func (d *graphDriverProxy) Capabilities() Capabilities { + return d.caps +} + func (d *graphDriverProxy) CreateReadWrite(id, parent string, opts *CreateOpts) error { return d.create("GraphDriver.CreateReadWrite", id, parent, opts) } diff --git a/docs/extend/plugins_graphdriver.md b/docs/extend/plugins_graphdriver.md index 74caa938f8..c134b1ebc4 100644 --- a/docs/extend/plugins_graphdriver.md +++ b/docs/extend/plugins_graphdriver.md @@ -84,6 +84,29 @@ The request also includes a list of UID and GID mappings, structed as follows: Respond with a non-empty string error if an error occurred. +### /GraphDriver.Capabilities + +**Request**: +```json +{} +``` + +Get behavioral characteristics of the graph driver. If a plugin does not handle +this request, the engine will use default values for all capabilities. + +**Response**: +```json +{ + "ReproducesExactDiffs": false, +} +``` + +Respond with values of capabilities: + +* **ReproducesExactDiffs** Defaults to false. Flags that this driver is capable +of reproducing exactly equivalent diffs for read-only filesystem layers. + + ### /GraphDriver.Create **Request**: diff --git a/layer/layer_store.go b/layer/layer_store.go index 09a55afeab..5caa5d41f2 100644 --- a/layer/layer_store.go +++ b/layer/layer_store.go @@ -34,6 +34,8 @@ type layerStore struct { mounts map[string]*mountedLayer mountL sync.Mutex + + useTarSplit bool } // StoreOptions are the options used to create a new Store instance @@ -74,11 +76,17 @@ func NewStoreFromOptions(options StoreOptions) (Store, error) { // metadata store and graph driver. The metadata store will be used to restore // the Store. func NewStoreFromGraphDriver(store MetadataStore, driver graphdriver.Driver) (Store, error) { + caps := graphdriver.Capabilities{} + if capDriver, ok := driver.(graphdriver.CapabilityDriver); ok { + caps = capDriver.Capabilities() + } + ls := &layerStore{ - store: store, - driver: driver, - layerMap: map[ChainID]*roLayer{}, - mounts: map[string]*mountedLayer{}, + store: store, + driver: driver, + layerMap: map[ChainID]*roLayer{}, + mounts: map[string]*mountedLayer{}, + useTarSplit: !caps.ReproducesExactDiffs, } ids, mounts, err := store.List() @@ -207,18 +215,21 @@ func (ls *layerStore) applyTar(tx MetadataTransaction, ts io.Reader, parent stri digester := digest.Canonical.Digester() tr := io.TeeReader(ts, digester.Hash()) - tsw, err := tx.TarSplitWriter(true) - if err != nil { - return err - } - metaPacker := storage.NewJSONPacker(tsw) - defer tsw.Close() + rdr := tr + if ls.useTarSplit { + tsw, err := tx.TarSplitWriter(true) + if err != nil { + return err + } + metaPacker := storage.NewJSONPacker(tsw) + defer tsw.Close() - // we're passing nil here for the file putter, because the ApplyDiff will - // handle the extraction of the archive - rdr, err := asm.NewInputTarStream(tr, metaPacker, nil) - if err != nil { - return err + // we're passing nil here for the file putter, because the ApplyDiff will + // handle the extraction of the archive + rdr, err = asm.NewInputTarStream(tr, metaPacker, nil) + if err != nil { + return err + } } applySize, err := ls.driver.ApplyDiff(layer.cacheID, parent, rdr) @@ -640,6 +651,34 @@ func (ls *layerStore) initMount(graphID, parent, mountLabel string, initFunc Mou return initID, nil } +func (ls *layerStore) getTarStream(rl *roLayer) (io.ReadCloser, error) { + if !ls.useTarSplit { + var parentCacheID string + if rl.parent != nil { + parentCacheID = rl.parent.cacheID + } + + return ls.driver.Diff(rl.cacheID, parentCacheID) + } + + r, err := ls.store.TarSplitReader(rl.chainID) + if err != nil { + return nil, err + } + + pr, pw := io.Pipe() + go func() { + err := ls.assembleTarTo(rl.cacheID, r, nil, pw) + if err != nil { + pw.CloseWithError(err) + } else { + pw.Close() + } + }() + + return pr, nil +} + func (ls *layerStore) assembleTarTo(graphID string, metadata io.ReadCloser, size *int64, w io.Writer) error { diffDriver, ok := ls.driver.(graphdriver.DiffGetterDriver) if !ok { diff --git a/layer/ro_layer.go b/layer/ro_layer.go index c037390050..8b4cf8f0de 100644 --- a/layer/ro_layer.go +++ b/layer/ro_layer.go @@ -24,25 +24,16 @@ type roLayer struct { // TarStream for roLayer guarantees that the data that is produced is the exact // data that the layer was registered with. func (rl *roLayer) TarStream() (io.ReadCloser, error) { - r, err := rl.layerStore.store.TarSplitReader(rl.chainID) + rc, err := rl.layerStore.getTarStream(rl) if err != nil { return nil, err } - pr, pw := io.Pipe() - go func() { - err := rl.layerStore.assembleTarTo(rl.cacheID, r, nil, pw) - if err != nil { - pw.CloseWithError(err) - } else { - pw.Close() - } - }() - rc, err := newVerifiedReadCloser(pr, digest.Digest(rl.diffID)) + vrc, err := newVerifiedReadCloser(rc, digest.Digest(rl.diffID)) if err != nil { return nil, err } - return rc, nil + return vrc, nil } // TarStreamFrom does not make any guarantees to the correctness of the produced