Skip to content

Commit c780818

Browse files
authored
Merge pull request #378 from Luap99/staged-layer-creation
Staged layer creation
2 parents 4fc82df + 83dd0eb commit c780818

File tree

13 files changed

+693
-228
lines changed

13 files changed

+693
-228
lines changed

storage/drivers/driver.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ type CreateOpts struct {
4747
MountLabel string
4848
StorageOpt map[string]string
4949
*idtools.IDMappings
50-
ignoreChownErrors bool
5150
}
5251

5352
// MountOpts contains optional arguments for Driver.Get() methods.
@@ -184,7 +183,7 @@ type DiffDriver interface {
184183
// layer with the specified id and parent, returning the size of the
185184
// new layer in bytes.
186185
// The io.Reader must be an uncompressed stream.
187-
ApplyDiff(id string, parent string, options ApplyDiffOpts) (size int64, err error)
186+
ApplyDiff(id string, options ApplyDiffOpts) (size int64, err error)
188187
// DiffSize calculates the changes between the specified id
189188
// and its parent and returns the size in bytes of the changes
190189
// relative to its base filesystem directory.
@@ -299,6 +298,19 @@ type DriverWithDiffer interface {
299298
DifferTarget(id string) (string, error)
300299
}
301300

301+
// ApplyDiffStaging is an interface for driver who can apply the diff without holding the main storage lock.
302+
// This API is experimental and can be changed without bumping the major version number.
303+
type ApplyDiffStaging interface {
304+
// StartStagingDiffToApply applies the new layer into a temporary directory.
305+
// It returns a CleanupTempDirFunc which can be nil or set regardless if the function return an error or not.
306+
// StagedAddition is only set when there is no error returned and the int64 value returns the size of the layer.
307+
// This can be done without holding the storage lock, if a parent is given the caller must check for existence
308+
// beforehand while holding a lock.
309+
StartStagingDiffToApply(parent string, options ApplyDiffOpts) (tempdir.CleanupTempDirFunc, *tempdir.StagedAddition, int64, error)
310+
// CommitStagedLayer commits the staged layer from StartStagingDiffToApply(). This must be done while holding the storage lock.
311+
CommitStagedLayer(id string, commit *tempdir.StagedAddition) error
312+
}
313+
302314
// Capabilities defines a list of capabilities a driver may implement.
303315
// These capabilities are not required; however, they do determine how a
304316
// graphdriver can be used.

storage/drivers/fsdiff.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ func (gdw *NaiveDiffDriver) Changes(id string, idMappings *idtools.IDMappings, p
151151
// ApplyDiff extracts the changeset from the given diff into the
152152
// layer with the specified id and parent, returning the size of the
153153
// new layer in bytes.
154-
func (gdw *NaiveDiffDriver) ApplyDiff(id, parent string, options ApplyDiffOpts) (int64, error) {
154+
func (gdw *NaiveDiffDriver) ApplyDiff(id string, options ApplyDiffOpts) (int64, error) {
155155
driver := gdw.ProtoDriver
156156

157157
if options.Mappings == nil {

storage/drivers/graphtest/graphbench_unix.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ func DriverBenchDiffApplyN(b *testing.B, fileCount int, drivername string, drive
155155
b.Fatal(err)
156156
}
157157

158-
applyDiffSize, err := driver.ApplyDiff(diff, "", graphdriver.ApplyDiffOpts{})
158+
applyDiffSize, err := driver.ApplyDiff(diff, graphdriver.ApplyDiffOpts{})
159159
if err != nil {
160160
b.Fatal(err)
161161
}

storage/drivers/graphtest/graphtest_unix.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,7 @@ func DriverTestDiffApply(t testing.TB, fileCount int, drivername string, driverO
362362
t.Fatal(err)
363363
}
364364

365-
applyDiffSize, err := driver.ApplyDiff(diff, base, graphdriver.ApplyDiffOpts{Diff: bytes.NewReader(buf.Bytes())})
365+
applyDiffSize, err := driver.ApplyDiff(diff, graphdriver.ApplyDiffOpts{Diff: bytes.NewReader(buf.Bytes())})
366366
if err != nil {
367367
t.Fatal(err)
368368
}

storage/drivers/overlay/overlay.go

Lines changed: 129 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -995,38 +995,22 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr
995995
return d.create(id, parent, opts, true)
996996
}
997997

998-
func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, readOnly bool) (retErr error) {
999-
dir, homedir, _ := d.dir2(id, readOnly)
1000-
1001-
disableQuota := readOnly
1002-
1003-
var uidMaps []idtools.IDMap
1004-
var gidMaps []idtools.IDMap
1005-
1006-
if opts != nil && opts.IDMappings != nil {
1007-
uidMaps = opts.IDMappings.UIDs()
1008-
gidMaps = opts.IDMappings.GIDs()
1009-
}
1010-
1011-
// Make the link directory if it does not exist
1012-
if err := idtools.MkdirAllAs(path.Join(homedir, linkDir), 0o755, 0, 0); err != nil {
1013-
return err
1014-
}
1015-
998+
// getLayerPermissions returns the base permissions to use for the layer directories.
999+
// The first return value is the idPair to create the possible parent directories with.
1000+
// The second return value is the mode how it should be stored on disk.
1001+
// The third return value is the mode the layer expects to have which may be stored
1002+
// in an xattr when using forceMask, without forceMask both values are the same.
1003+
func (d *Driver) getLayerPermissions(parent string, uidMaps, gidMaps []idtools.IDMap) (idtools.IDPair, idtools.Stat, idtools.Stat, error) {
10161004
rootUID, rootGID, err := idtools.GetRootUIDGID(uidMaps, gidMaps)
10171005
if err != nil {
1018-
return err
1006+
return idtools.IDPair{}, idtools.Stat{}, idtools.Stat{}, err
10191007
}
10201008

10211009
idPair := idtools.IDPair{
10221010
UID: rootUID,
10231011
GID: rootGID,
10241012
}
10251013

1026-
if err := idtools.MkdirAllAndChownNew(path.Dir(dir), 0o755, idPair); err != nil {
1027-
return err
1028-
}
1029-
10301014
st := idtools.Stat{IDs: idPair, Mode: defaultPerms}
10311015

10321016
if parent != "" {
@@ -1037,14 +1021,50 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, readOnl
10371021
} else {
10381022
systemSt, err := system.Stat(parentDiff)
10391023
if err != nil {
1040-
return err
1024+
return idtools.IDPair{}, idtools.Stat{}, idtools.Stat{}, err
10411025
}
10421026
st.IDs.UID = int(systemSt.UID())
10431027
st.IDs.GID = int(systemSt.GID())
10441028
st.Mode = os.FileMode(systemSt.Mode())
10451029
}
10461030
}
10471031

1032+
forcedSt := st
1033+
if d.options.forceMask != nil {
1034+
forcedSt.IDs = idPair
1035+
forcedSt.Mode = *d.options.forceMask
1036+
}
1037+
1038+
return idPair, forcedSt, st, nil
1039+
}
1040+
1041+
func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, readOnly bool) (retErr error) {
1042+
dir, homedir, _ := d.dir2(id, readOnly)
1043+
1044+
disableQuota := readOnly
1045+
1046+
var uidMaps []idtools.IDMap
1047+
var gidMaps []idtools.IDMap
1048+
1049+
if opts != nil && opts.IDMappings != nil {
1050+
uidMaps = opts.IDMappings.UIDs()
1051+
gidMaps = opts.IDMappings.GIDs()
1052+
}
1053+
1054+
// Make the link directory if it does not exist
1055+
if err := idtools.MkdirAllAs(path.Join(homedir, linkDir), 0o755, 0, 0); err != nil {
1056+
return err
1057+
}
1058+
1059+
idPair, forcedSt, st, err := d.getLayerPermissions(parent, uidMaps, gidMaps)
1060+
if err != nil {
1061+
return err
1062+
}
1063+
1064+
if err := idtools.MkdirAllAndChownNew(path.Dir(dir), 0o755, idPair); err != nil {
1065+
return err
1066+
}
1067+
10481068
if err := fileutils.Lexists(dir); err == nil {
10491069
logrus.Warnf("Trying to create a layer %#v while directory %q already exists; removing it first", id, dir)
10501070
// Don’t just os.RemoveAll(dir) here; d.Remove also removes the link in linkDir,
@@ -1088,12 +1108,6 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts, readOnl
10881108
}
10891109
}
10901110

1091-
forcedSt := st
1092-
if d.options.forceMask != nil {
1093-
forcedSt.IDs = idPair
1094-
forcedSt.Mode = *d.options.forceMask
1095-
}
1096-
10971111
diff := path.Join(dir, "diff")
10981112
if err := idtools.MkdirAs(diff, forcedSt.Mode, forcedSt.IDs.UID, forcedSt.IDs.GID); err != nil {
10991113
return err
@@ -1356,6 +1370,14 @@ func (d *Driver) getTempDirRoot(id string) string {
13561370
return filepath.Join(d.home, tempDirName)
13571371
}
13581372

1373+
// getTempDirRootForNewLayer returns the correct temp directory root based on where
1374+
// the layer should be created.
1375+
//
1376+
// This must be kept in sync with GetTempDirRootDirs().
1377+
func (d *Driver) getTempDirRootForNewLayer() string {
1378+
return filepath.Join(d.homeDirForImageStore(), tempDirName)
1379+
}
1380+
13591381
func (d *Driver) DeferredRemove(id string) (tempdir.CleanupTempDirFunc, error) {
13601382
tempDirRoot := d.getTempDirRoot(id)
13611383
t, err := tempdir.NewTempDir(tempDirRoot)
@@ -2369,31 +2391,94 @@ func (d *Driver) DifferTarget(id string) (string, error) {
23692391
return d.getDiffPath(id)
23702392
}
23712393

2372-
// ApplyDiff applies the new layer into a root
2373-
func (d *Driver) ApplyDiff(id, parent string, options graphdriver.ApplyDiffOpts) (size int64, err error) {
2374-
if !d.isParent(id, parent) {
2375-
if d.options.ignoreChownErrors {
2376-
options.IgnoreChownErrors = d.options.ignoreChownErrors
2394+
// StartStagingDiffToApply applies the new layer into a temporary directory.
2395+
// It returns a CleanupTempDirFunc which can be nil or set regardless if the function return an error or not.
2396+
// StagedAddition is only set when there is no error returned and the int64 value returns the size of the layer.
2397+
// This can be done without holding the storage lock, if a parent is given the caller must check for existence
2398+
// beforehand while holding a lock.
2399+
//
2400+
// This API is experimental and can be changed without bumping the major version number.
2401+
func (d *Driver) StartStagingDiffToApply(parent string, options graphdriver.ApplyDiffOpts) (tempdir.CleanupTempDirFunc, *tempdir.StagedAddition, int64, error) {
2402+
tempDirRoot := d.getTempDirRootForNewLayer()
2403+
t, err := tempdir.NewTempDir(tempDirRoot)
2404+
if err != nil {
2405+
return nil, nil, -1, err
2406+
}
2407+
2408+
sa, err := t.StageAddition()
2409+
if err != nil {
2410+
return t.Cleanup, nil, -1, err
2411+
}
2412+
2413+
_, forcedSt, st, err := d.getLayerPermissions(parent, options.Mappings.UIDs(), options.Mappings.GIDs())
2414+
if err != nil {
2415+
// If we have a ENOENT it means the parent was removed which can happen as we are unlocked here.
2416+
// In this case also wrap ErrLayerUnknown which some callers can handle to retry after recreating the parent.
2417+
if errors.Is(err, fs.ErrNotExist) {
2418+
err = fmt.Errorf("parent layer %q: %w: %w", parent, graphdriver.ErrLayerUnknown, err)
23772419
}
2378-
if d.options.forceMask != nil {
2379-
options.ForceMask = d.options.forceMask
2420+
return t.Cleanup, nil, -1, err
2421+
}
2422+
2423+
if err := idtools.MkdirAs(sa.Path, forcedSt.Mode, forcedSt.IDs.UID, forcedSt.IDs.GID); err != nil {
2424+
return t.Cleanup, nil, -1, err
2425+
}
2426+
2427+
if d.options.forceMask != nil {
2428+
st.Mode |= os.ModeDir
2429+
if err := idtools.SetContainersOverrideXattr(sa.Path, st); err != nil {
2430+
return t.Cleanup, nil, -1, err
23802431
}
2381-
return d.naiveDiff.ApplyDiff(id, parent, options)
23822432
}
23832433

2384-
idMappings := options.Mappings
2385-
if idMappings == nil {
2386-
idMappings = &idtools.IDMappings{}
2434+
size, err := d.applyDiff(sa.Path, options)
2435+
if err != nil {
2436+
return t.Cleanup, nil, -1, err
2437+
}
2438+
2439+
return t.Cleanup, sa, size, nil
2440+
}
2441+
2442+
// CommitStagedLayer that was created with StartStagingDiffToApply().
2443+
//
2444+
// This API is experimental and can be changed without bumping the major version number.
2445+
func (d *Driver) CommitStagedLayer(id string, sa *tempdir.StagedAddition) error {
2446+
applyDir, err := d.getDiffPath(id)
2447+
if err != nil {
2448+
return err
2449+
}
2450+
2451+
// The os.Rename() function used by CommitFunc errors when the target directory already
2452+
// exists, as such delete the dir. The create() function creates it and it would be more
2453+
// complicated to code in a way that it didn't create it.
2454+
if err := os.Remove(applyDir); err != nil {
2455+
return err
23872456
}
23882457

2458+
return sa.Commit(applyDir)
2459+
}
2460+
2461+
// ApplyDiff applies the new layer into a root
2462+
func (d *Driver) ApplyDiff(id string, options graphdriver.ApplyDiffOpts) (size int64, err error) {
23892463
applyDir, err := d.getDiffPath(id)
23902464
if err != nil {
23912465
return 0, err
23922466
}
2467+
return d.applyDiff(applyDir, options)
2468+
}
2469+
2470+
// ApplyDiff applies the new layer into a root.
2471+
// This can run concurrently with any other driver operations, as such it is the
2472+
// callers responsibility to ensure the target path passed is safe to use if that is the case.
2473+
func (d *Driver) applyDiff(target string, options graphdriver.ApplyDiffOpts) (size int64, err error) {
2474+
idMappings := options.Mappings
2475+
if idMappings == nil {
2476+
idMappings = &idtools.IDMappings{}
2477+
}
23932478

2394-
logrus.Debugf("Applying tar in %s", applyDir)
2479+
logrus.Debugf("Applying tar in %s", target)
23952480
// Overlay doesn't need the parent id to apply the diff
2396-
if err := untar(options.Diff, applyDir, &archive.TarOptions{
2481+
if err := untar(options.Diff, target, &archive.TarOptions{
23972482
UIDMaps: idMappings.UIDs(),
23982483
GIDMaps: idMappings.GIDs(),
23992484
IgnoreChownErrors: d.options.ignoreChownErrors,
@@ -2404,7 +2489,7 @@ func (d *Driver) ApplyDiff(id, parent string, options graphdriver.ApplyDiffOpts)
24042489
return 0, err
24052490
}
24062491

2407-
return directory.Size(applyDir)
2492+
return directory.Size(target)
24082493
}
24092494

24102495
func (d *Driver) getComposefsData(id string) string {

storage/drivers/overlay/overlay_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ import (
1717

1818
const driverName = "overlay"
1919

20+
// check that Driver correctly implements the ApplyDiffTemporary interface
21+
var _ graphdriver.ApplyDiffStaging = &Driver{}
22+
2023
func init() {
2124
// Do not sure chroot to speed run time and allow archive
2225
// errors or hangs to be debugged directly from the test process.

storage/drivers/template.go

Lines changed: 0 additions & 52 deletions
This file was deleted.

storage/drivers/vfs/driver.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,11 +132,11 @@ func (d *Driver) CreateFromTemplate(id, template string, templateIDMappings *idt
132132
}
133133

134134
// ApplyDiff applies the new layer into a root
135-
func (d *Driver) ApplyDiff(id, parent string, options graphdriver.ApplyDiffOpts) (size int64, err error) {
135+
func (d *Driver) ApplyDiff(id string, options graphdriver.ApplyDiffOpts) (size int64, err error) {
136136
if d.ignoreChownErrors {
137137
options.IgnoreChownErrors = d.ignoreChownErrors
138138
}
139-
return d.naiveDiff.ApplyDiff(id, parent, options)
139+
return d.naiveDiff.ApplyDiff(id, options)
140140
}
141141

142142
// CreateReadWrite creates a layer that is writable for use as a container

0 commit comments

Comments
 (0)