Refactor to optimize storage driver ApplyDiff()

To avoid an expensive call to archive.ChangesDirs() which walks two directory
trees and compares every entry, archive.ApplyLayer() has been extended to
also return the size of the layer changes.

Docker-DCO-1.1-Signed-off-by: Josh Hawn <josh.hawn@docker.com> (github: jlhawn)
This commit is contained in:
Josh Hawn 2014-12-17 18:26:03 -08:00
parent 52679b2b7f
commit c57c03f841
5 changed files with 61 additions and 29 deletions

View file

@ -286,7 +286,7 @@ func TestApplyLayer(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
if err := ApplyLayer(src, layerCopy); err != nil { if _, err := ApplyLayer(src, layerCopy); err != nil {
t.Fatal(err) t.Fatal(err)
} }

View file

@ -15,7 +15,7 @@ import (
"github.com/docker/docker/pkg/system" "github.com/docker/docker/pkg/system"
) )
func UnpackLayer(dest string, layer ArchiveReader) error { func UnpackLayer(dest string, layer ArchiveReader) (size int64, err error) {
tr := tar.NewReader(layer) tr := tar.NewReader(layer)
trBuf := pools.BufioReader32KPool.Get(tr) trBuf := pools.BufioReader32KPool.Get(tr)
defer pools.BufioReader32KPool.Put(trBuf) defer pools.BufioReader32KPool.Put(trBuf)
@ -33,9 +33,11 @@ func UnpackLayer(dest string, layer ArchiveReader) error {
break break
} }
if err != nil { if err != nil {
return err return 0, err
} }
size += hdr.Size
// Normalize name, for safety and for a simple is-root check // Normalize name, for safety and for a simple is-root check
hdr.Name = filepath.Clean(hdr.Name) hdr.Name = filepath.Clean(hdr.Name)
@ -48,7 +50,7 @@ func UnpackLayer(dest string, layer ArchiveReader) error {
if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) { if _, err := os.Lstat(parentPath); err != nil && os.IsNotExist(err) {
err = os.MkdirAll(parentPath, 0600) err = os.MkdirAll(parentPath, 0600)
if err != nil { if err != nil {
return err return 0, err
} }
} }
} }
@ -63,12 +65,12 @@ func UnpackLayer(dest string, layer ArchiveReader) error {
aufsHardlinks[basename] = hdr aufsHardlinks[basename] = hdr
if aufsTempdir == "" { if aufsTempdir == "" {
if aufsTempdir, err = ioutil.TempDir("", "dockerplnk"); err != nil { if aufsTempdir, err = ioutil.TempDir("", "dockerplnk"); err != nil {
return err return 0, err
} }
defer os.RemoveAll(aufsTempdir) defer os.RemoveAll(aufsTempdir)
} }
if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, true); err != nil { if err := createTarFile(filepath.Join(aufsTempdir, basename), dest, hdr, tr, true); err != nil {
return err return 0, err
} }
} }
continue continue
@ -77,10 +79,10 @@ func UnpackLayer(dest string, layer ArchiveReader) error {
path := filepath.Join(dest, hdr.Name) path := filepath.Join(dest, hdr.Name)
rel, err := filepath.Rel(dest, path) rel, err := filepath.Rel(dest, path)
if err != nil { if err != nil {
return err return 0, err
} }
if strings.HasPrefix(rel, "..") { if strings.HasPrefix(rel, "..") {
return breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest)) return 0, breakoutError(fmt.Errorf("%q is outside of %q", hdr.Name, dest))
} }
base := filepath.Base(path) base := filepath.Base(path)
@ -88,7 +90,7 @@ func UnpackLayer(dest string, layer ArchiveReader) error {
originalBase := base[len(".wh."):] originalBase := base[len(".wh."):]
originalPath := filepath.Join(filepath.Dir(path), originalBase) originalPath := filepath.Join(filepath.Dir(path), originalBase)
if err := os.RemoveAll(originalPath); err != nil { if err := os.RemoveAll(originalPath); err != nil {
return err return 0, err
} }
} else { } else {
// If path exits we almost always just want to remove and replace it. // If path exits we almost always just want to remove and replace it.
@ -98,7 +100,7 @@ func UnpackLayer(dest string, layer ArchiveReader) error {
if fi, err := os.Lstat(path); err == nil { if fi, err := os.Lstat(path); err == nil {
if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) { if !(fi.IsDir() && hdr.Typeflag == tar.TypeDir) {
if err := os.RemoveAll(path); err != nil { if err := os.RemoveAll(path); err != nil {
return err return 0, err
} }
} }
} }
@ -113,18 +115,18 @@ func UnpackLayer(dest string, layer ArchiveReader) error {
linkBasename := filepath.Base(hdr.Linkname) linkBasename := filepath.Base(hdr.Linkname)
srcHdr = aufsHardlinks[linkBasename] srcHdr = aufsHardlinks[linkBasename]
if srcHdr == nil { if srcHdr == nil {
return fmt.Errorf("Invalid aufs hardlink") return 0, fmt.Errorf("Invalid aufs hardlink")
} }
tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename)) tmpFile, err := os.Open(filepath.Join(aufsTempdir, linkBasename))
if err != nil { if err != nil {
return err return 0, err
} }
defer tmpFile.Close() defer tmpFile.Close()
srcData = tmpFile srcData = tmpFile
} }
if err := createTarFile(path, dest, srcHdr, srcData, true); err != nil { if err := createTarFile(path, dest, srcHdr, srcData, true); err != nil {
return err return 0, err
} }
// Directory mtimes must be handled at the end to avoid further // Directory mtimes must be handled at the end to avoid further
@ -139,27 +141,29 @@ func UnpackLayer(dest string, layer ArchiveReader) error {
path := filepath.Join(dest, hdr.Name) path := filepath.Join(dest, hdr.Name)
ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)}
if err := syscall.UtimesNano(path, ts); err != nil { if err := syscall.UtimesNano(path, ts); err != nil {
return err return 0, err
} }
} }
return nil
return size, nil
} }
// ApplyLayer parses a diff in the standard layer format from `layer`, and // ApplyLayer parses a diff in the standard layer format from `layer`, and
// applies it to the directory `dest`. // applies it to the directory `dest`. Returns the size in bytes of the
func ApplyLayer(dest string, layer ArchiveReader) error { // contents of the layer.
func ApplyLayer(dest string, layer ArchiveReader) (int64, error) {
dest = filepath.Clean(dest) dest = filepath.Clean(dest)
// We need to be able to set any perms // We need to be able to set any perms
oldmask, err := system.Umask(0) oldmask, err := system.Umask(0)
if err != nil { if err != nil {
return err return 0, err
} }
defer system.Umask(oldmask) // ignore err, ErrNotSupportedPlatform defer system.Umask(oldmask) // ignore err, ErrNotSupportedPlatform
layer, err = DecompressStream(layer) layer, err = DecompressStream(layer)
if err != nil { if err != nil {
return err return 0, err
} }
return UnpackLayer(dest, layer) return UnpackLayer(dest, layer)
} }

View file

@ -17,7 +17,8 @@ var testUntarFns = map[string]func(string, io.Reader) error{
return Untar(r, dest, nil) return Untar(r, dest, nil)
}, },
"applylayer": func(dest string, r io.Reader) error { "applylayer": func(dest string, r io.Reader) error {
return ApplyLayer(dest, ArchiveReader(r)) _, err := ApplyLayer(dest, ArchiveReader(r))
return err
}, },
} }

View file

@ -95,7 +95,7 @@ func TestChrootApplyEmptyArchiveFromSlowReader(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
stream := &slowEmptyTarReader{size: 10240, chunkSize: 1024} stream := &slowEmptyTarReader{size: 10240, chunkSize: 1024}
if err := ApplyLayer(dest, stream); err != nil { if _, err := ApplyLayer(dest, stream); err != nil {
t.Fatal(err) t.Fatal(err)
} }
} }

View file

@ -1,6 +1,8 @@
package chrootarchive package chrootarchive
import ( import (
"bytes"
"encoding/json"
"flag" "flag"
"fmt" "fmt"
"io" "io"
@ -14,6 +16,10 @@ import (
"github.com/docker/docker/pkg/reexec" "github.com/docker/docker/pkg/reexec"
) )
type applyLayerResponse struct {
LayerSize int64 `json:"layerSize"`
}
func applyLayer() { func applyLayer() {
runtime.LockOSThread() runtime.LockOSThread()
flag.Parse() flag.Parse()
@ -21,6 +27,7 @@ func applyLayer() {
if err := chroot(flag.Arg(0)); err != nil { if err := chroot(flag.Arg(0)); err != nil {
fatal(err) fatal(err)
} }
// We need to be able to set any perms // We need to be able to set any perms
oldmask := syscall.Umask(0) oldmask := syscall.Umask(0)
defer syscall.Umask(oldmask) defer syscall.Umask(oldmask)
@ -28,33 +35,53 @@ func applyLayer() {
if err != nil { if err != nil {
fatal(err) fatal(err)
} }
os.Setenv("TMPDIR", tmpDir) os.Setenv("TMPDIR", tmpDir)
err = archive.UnpackLayer("/", os.Stdin) size, err := archive.UnpackLayer("/", os.Stdin)
os.RemoveAll(tmpDir) os.RemoveAll(tmpDir)
if err != nil { if err != nil {
fatal(err) fatal(err)
} }
os.RemoveAll(tmpDir)
encoder := json.NewEncoder(os.Stdout)
if err := encoder.Encode(applyLayerResponse{size}); err != nil {
fatal(fmt.Errorf("unable to encode layerSize JSON: %s", err))
}
flush(os.Stdout)
flush(os.Stdin) flush(os.Stdin)
os.Exit(0) os.Exit(0)
} }
func ApplyLayer(dest string, layer archive.ArchiveReader) error { func ApplyLayer(dest string, layer archive.ArchiveReader) (size int64, err error) {
dest = filepath.Clean(dest) dest = filepath.Clean(dest)
decompressed, err := archive.DecompressStream(layer) decompressed, err := archive.DecompressStream(layer)
if err != nil { if err != nil {
return err return 0, err
} }
defer func() { defer func() {
if c, ok := decompressed.(io.Closer); ok { if c, ok := decompressed.(io.Closer); ok {
c.Close() c.Close()
} }
}() }()
cmd := reexec.Command("docker-applyLayer", dest) cmd := reexec.Command("docker-applyLayer", dest)
cmd.Stdin = decompressed cmd.Stdin = decompressed
out, err := cmd.CombinedOutput()
if err != nil { outBuf, errBuf := new(bytes.Buffer), new(bytes.Buffer)
return fmt.Errorf("ApplyLayer %s %s", err, out) cmd.Stdout, cmd.Stderr = outBuf, errBuf
if err = cmd.Run(); err != nil {
return 0, fmt.Errorf("ApplyLayer %s stdout: %s stderr: %s", err, outBuf, errBuf)
} }
return nil
// Stdout should be a valid JSON struct representing an applyLayerResponse.
response := applyLayerResponse{}
decoder := json.NewDecoder(outBuf)
if err = decoder.Decode(&response); err != nil {
return 0, fmt.Errorf("unable to decode ApplyLayer JSON response: %s", err)
}
return response.LayerSize, nil
} }