The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/3675
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) ===
From df098ccda7be31b3ce2124ab756c98acce81b14c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <[email protected]> Date: Fri, 18 Aug 2017 00:56:07 -0400 Subject: [PATCH 1/4] client: Cleanup code duplication in image download MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <[email protected]> --- client/simplestreams_images.go | 39 +++++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/client/simplestreams_images.go b/client/simplestreams_images.go index fd82d9912..1ae9c6acf 100644 --- a/client/simplestreams_images.go +++ b/client/simplestreams_images.go @@ -2,6 +2,7 @@ package lxd import ( "fmt" + "io" "strings" "github.com/lxc/lxd/shared/api" @@ -57,22 +58,32 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe // Prepare the response resp := ImageFileResponse{} - // Download the LXD image file - meta, ok := files["meta"] - if ok && req.MetaFile != nil { + // Download function + download := func(path string, filename string, sha256 string, target io.WriteSeeker) (int64, error) { // Try over http - url := fmt.Sprintf("http://%s/%s", strings.TrimPrefix(r.httpHost, "https://"), meta.Path) + url := fmt.Sprintf("http://%s/%s", strings.TrimPrefix(r.httpHost, "https://"), path) - size, err := downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, req.Canceler, "metadata", url, meta.Sha256, req.MetaFile) + size, err := downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, req.Canceler, filename, url, sha256, target) if err != nil { // Try over https - url = fmt.Sprintf("%s/%s", r.httpHost, meta.Path) - size, err = downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, req.Canceler, "metadata", url, meta.Sha256, req.MetaFile) + url = fmt.Sprintf("%s/%s", r.httpHost, path) + size, err = downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, req.Canceler, filename, url, sha256, target) if err != nil { - return nil, err + return -1, err } } + return size, nil + } + + // Download the LXD image file + meta, ok := files["meta"] + if ok && req.MetaFile != nil { + size, err := download(meta.Path, "metadata", meta.Sha256, req.MetaFile) + if err != nil { + return nil, err + } + parts := strings.Split(meta.Path, "/") resp.MetaName = parts[len(parts)-1] resp.MetaSize = size @@ -81,17 +92,9 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe // Download the rootfs rootfs, ok := files["root"] if ok && req.RootfsFile != nil { - // Try over http - url := fmt.Sprintf("http://%s/%s", strings.TrimPrefix(r.httpHost, "https://"), rootfs.Path) - - size, err := downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, req.Canceler, "rootfs", url, rootfs.Sha256, req.RootfsFile) + size, err := download(rootfs.Path, "rootfs", rootfs.Sha256, req.RootfsFile) if err != nil { - // Try over https - url = fmt.Sprintf("%s/%s", r.httpHost, rootfs.Path) - size, err = downloadFileSha256(r.http, r.httpUserAgent, req.ProgressHandler, req.Canceler, "rootfs", url, rootfs.Sha256, req.RootfsFile) - if err != nil { - return nil, err - } + return nil, err } parts := strings.Split(rootfs.Path, "/") From 3011ed610aca6ba4e71ee8236de58b2f62834233 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <[email protected]> Date: Fri, 18 Aug 2017 01:40:37 -0400 Subject: [PATCH 2/4] shared/simplestreams: Add delta support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <[email protected]> --- shared/simplestreams/simplestreams.go | 39 ++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/shared/simplestreams/simplestreams.go b/shared/simplestreams/simplestreams.go index 0615fe011..322f4fffe 100644 --- a/shared/simplestreams/simplestreams.go +++ b/shared/simplestreams/simplestreams.go @@ -115,8 +115,14 @@ func (s *SimpleStreamsManifest) ToLXD() ([]api.Image, map[string][][]string) { var meta SimpleStreamsManifestProductVersionItem var rootTar SimpleStreamsManifestProductVersionItem var rootSquash SimpleStreamsManifestProductVersionItem + deltas := []SimpleStreamsManifestProductVersionItem{} for _, item := range version.Items { + // Identify deltas + if item.FileType == "squashfs.vcdiff" { + deltas = append(deltas, item) + } + // Skip the files we don't care about if !shared.StringInSlice(item.FileType, []string{"root.tar.xz", "lxd.tar.xz", "squashfs"}) { continue @@ -223,9 +229,39 @@ func (s *SimpleStreamsManifest) ToLXD() ([]api.Image, map[string][][]string) { } } - downloads[fingerprint] = [][]string{ + imgDownloads := [][]string{ {metaPath, metaHash, "meta", fmt.Sprintf("%d", metaSize)}, {rootfsPath, rootfsHash, "root", fmt.Sprintf("%d", rootfsSize)}} + + // Add the deltas + for _, delta := range deltas { + srcImage, ok := product.Versions[delta.DeltaBase] + if !ok { + continue + } + + var srcFingerprint string + for _, item := range srcImage.Items { + if item.FileType != "lxd.tar.xz" { + continue + } + + srcFingerprint = item.LXDHashSha256SquashFs + break + } + + if srcFingerprint == "" { + continue + } + + imgDownloads = append(imgDownloads, []string{ + delta.Path, + delta.HashSha256, + fmt.Sprintf("root.delta-%s", srcFingerprint), + fmt.Sprintf("%d", delta.Size)}) + } + + downloads[fingerprint] = imgDownloads images = append(images, image) } } @@ -261,6 +297,7 @@ type SimpleStreamsManifestProductVersionItem struct { LXDHashSha256RootXz string `json:"combined_rootxz_sha256"` LXDHashSha256SquashFs string `json:"combined_squashfs_sha256"` Size int64 `json:"size"` + DeltaBase string `json:"delta_base"` } type SimpleStreamsIndex struct { From 217b609723b4a9a33b379d47d7ee3fb20ff7312f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <[email protected]> Date: Fri, 18 Aug 2017 02:08:52 -0400 Subject: [PATCH 3/4] client: Add support for delta image transfer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <[email protected]> --- client/interfaces.go | 4 +++ client/simplestreams_images.go | 77 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 75 insertions(+), 6 deletions(-) diff --git a/client/interfaces.go b/client/interfaces.go index ed9b001a3..f6cb6006c 100644 --- a/client/interfaces.go +++ b/client/interfaces.go @@ -209,6 +209,10 @@ type ImageFileRequest struct { // A canceler that can be used to interrupt some part of the image download request Canceler *cancel.Canceler + + // Path retriever for image delta downloads + // If set, it must return the path to the image file or an empty string if not available + DeltaSourceRetriever func(fingerprint string, file string) string } // The ImageFileResponse struct is used as the response for image downloads diff --git a/client/simplestreams_images.go b/client/simplestreams_images.go index 1ae9c6acf..21c266d8f 100644 --- a/client/simplestreams_images.go +++ b/client/simplestreams_images.go @@ -3,8 +3,12 @@ package lxd import ( "fmt" "io" + "io/ioutil" + "os" + "os/exec" "strings" + "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" ) @@ -92,14 +96,75 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe // Download the rootfs rootfs, ok := files["root"] if ok && req.RootfsFile != nil { - size, err := download(rootfs.Path, "rootfs", rootfs.Sha256, req.RootfsFile) - if err != nil { - return nil, err + // Look for deltas (requires xdelta3) + downloaded := false + _, err := exec.LookPath("xdelta3") + if err == nil && req.DeltaSourceRetriever != nil { + for filename, file := range files { + if !strings.HasPrefix(filename, "root.delta-") { + continue + } + + // Check if we have the source file for the delta + srcFingerprint := strings.Split(filename, "root.delta-")[1] + srcPath := req.DeltaSourceRetriever(srcFingerprint, "rootfs") + if srcPath == "" { + continue + } + + // Create temporary file for the delta + deltaFile, err := ioutil.TempFile("", "lxc_image_") + if err != nil { + return nil, err + } + defer deltaFile.Close() + defer os.Remove(deltaFile.Name()) + + // Download the delta + _, err = download(file.Path, "rootfs delta", file.Sha256, deltaFile) + if err != nil { + return nil, err + } + deltaFile.Close() + + // Create temporary file for the delta + patchedFile, err := ioutil.TempFile("", "lxc_image_") + if err != nil { + return nil, err + } + defer patchedFile.Close() + defer os.Remove(patchedFile.Name()) + + // Apply it + _, err = shared.RunCommand("xdelta3", "-f", "-d", "-s", srcPath, deltaFile.Name(), patchedFile.Name()) + if err != nil { + return nil, err + } + + // Copy to the target + size, err := io.Copy(req.RootfsFile, patchedFile) + if err != nil { + return nil, err + } + + parts := strings.Split(rootfs.Path, "/") + resp.RootfsName = parts[len(parts)-1] + resp.RootfsSize = size + downloaded = true + } } - parts := strings.Split(rootfs.Path, "/") - resp.RootfsName = parts[len(parts)-1] - resp.RootfsSize = size + // Download the whole file + if !downloaded { + size, err := download(rootfs.Path, "rootfs", rootfs.Sha256, req.RootfsFile) + if err != nil { + return nil, err + } + + parts := strings.Split(rootfs.Path, "/") + resp.RootfsName = parts[len(parts)-1] + resp.RootfsSize = size + } } return &resp, nil From 81a19a6707c912edd03cb5522c67ea76e327cc81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <[email protected]> Date: Fri, 18 Aug 2017 02:30:27 -0400 Subject: [PATCH 4/4] lxd: Add support for delta images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <[email protected]> --- lxd/daemon_images.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go index 659bb0d1a..4a56ca861 100644 --- a/lxd/daemon_images.go +++ b/lxd/daemon_images.go @@ -403,6 +403,14 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce RootfsFile: io.WriteSeeker(destRootfs), ProgressHandler: progress, Canceler: canceler, + DeltaSourceRetriever: func(fingerprint string, file string) string { + path := shared.VarPath("images", fmt.Sprintf("%s.%s", fingerprint, file)) + if shared.PathExists(path) { + return path + } + + return "" + }, } if secret != "" {
_______________________________________________ lxc-devel mailing list [email protected] http://lists.linuxcontainers.org/listinfo/lxc-devel
