The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/7008
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 2689168252b2f1122bb08bb949f8f61dfa973f06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Wed, 11 Mar 2020 19:28:09 -0400 Subject: [PATCH 1/4] lxd/images: Allow virtual-machine and instance as source MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- lxd/images.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lxd/images.go b/lxd/images.go index c05c7d6582..5be76ebca6 100644 --- a/lxd/images.go +++ b/lxd/images.go @@ -174,7 +174,7 @@ func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost, op *operati if !shared.IsSnapshot(name) { return nil, fmt.Errorf("Not a snapshot") } - case "container": + case "container", "virtual-machine", "instance": if shared.IsSnapshot(name) { return nil, fmt.Errorf("This is a snapshot") } From 49484aceac6fb2e396f8705e29d48d19d2688251 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Wed, 11 Mar 2020 19:33:18 -0400 Subject: [PATCH 2/4] lxd/images: Set right image type on publish MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- lxd/images.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lxd/images.go b/lxd/images.go index 5be76ebca6..fa9cb1cee6 100644 --- a/lxd/images.go +++ b/lxd/images.go @@ -160,7 +160,6 @@ func compressFile(compress string, infile io.Reader, outfile io.Writer) error { */ func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost, op *operations.Operation, builddir string) (*api.Image, error) { info := api.Image{} - info.Type = "container" info.Properties = map[string]string{} project := projectParam(r) name := req.Source.Name @@ -195,6 +194,8 @@ func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost, op *operati return nil, err } + info.Type = c.Type().String() + // Build the actual image file imageFile, err := ioutil.TempFile(builddir, "lxd_build_image_") if err != nil { From 261c5165ca1a9182e489b6b525903a7b5e4d5d67 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Wed, 11 Mar 2020 17:59:53 -0400 Subject: [PATCH 3/4] lxd/vm: Implement Export MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- lxd/instance/drivers/driver_qemu.go | 237 +++++++++++++++++++++++++++- 1 file changed, 236 insertions(+), 1 deletion(-) diff --git a/lxd/instance/drivers/driver_qemu.go b/lxd/instance/drivers/driver_qemu.go index 3c83c55619..874b64c3bd 100644 --- a/lxd/instance/drivers/driver_qemu.go +++ b/lxd/instance/drivers/driver_qemu.go @@ -50,6 +50,7 @@ import ( "github.com/lxc/lxd/lxd/vsock" "github.com/lxc/lxd/shared" "github.com/lxc/lxd/shared/api" + "github.com/lxc/lxd/shared/containerwriter" log "github.com/lxc/lxd/shared/log15" "github.com/lxc/lxd/shared/logger" "github.com/lxc/lxd/shared/osarch" @@ -2956,7 +2957,241 @@ func (vm *qemu) deviceRemove(deviceName string, rawConfig deviceConfig.Device) e // Export publishes the instance. func (vm *qemu) Export(w io.Writer, properties map[string]string) error { - return instance.ErrNotImplemented + ctxMap := log.Ctx{ + "project": vm.project, + "name": vm.name, + "created": vm.creationDate, + "ephemeral": vm.ephemeral, + "used": vm.lastUsedDate} + + if vm.IsRunning() { + return fmt.Errorf("Cannot export a running instance as an image") + } + + logger.Info("Exporting instance", ctxMap) + + // Start the storage. + ourStart, err := vm.mount() + if err != nil { + logger.Error("Failed exporting instance", ctxMap) + return err + } + if ourStart { + defer vm.unmount() + } + + // Create the tarball. + ctw := containerwriter.NewContainerTarWriter(w, nil) + + // Path inside the tar image is the pathname starting after cDir. + cDir := vm.Path() + offset := len(cDir) + 1 + + writeToTar := func(path string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + + err = ctw.WriteFile(offset, path, fi) + if err != nil { + logger.Debugf("Error tarring up %s: %s", path, err) + return err + } + + return nil + } + + // Look for metadata.yaml. + fnam := filepath.Join(cDir, "metadata.yaml") + if !shared.PathExists(fnam) { + // Generate a new metadata.yaml. + tempDir, err := ioutil.TempDir("", "lxd_lxd_metadata_") + if err != nil { + ctw.Close() + logger.Error("Failed exporting instance", ctxMap) + return err + } + defer os.RemoveAll(tempDir) + + // Get the instance's architecture. + var arch string + if vm.IsSnapshot() { + parentName, _, _ := shared.InstanceGetParentAndSnapshotName(vm.name) + parent, err := instance.LoadByProjectAndName(vm.state, vm.project, parentName) + if err != nil { + ctw.Close() + logger.Error("Failed exporting instance", ctxMap) + return err + } + + arch, _ = osarch.ArchitectureName(parent.Architecture()) + } else { + arch, _ = osarch.ArchitectureName(vm.architecture) + } + + if arch == "" { + arch, err = osarch.ArchitectureName(vm.state.OS.Architectures[0]) + if err != nil { + logger.Error("Failed exporting instance", ctxMap) + return err + } + } + + // Fill in the metadata. + meta := api.ImageMetadata{} + meta.Architecture = arch + meta.CreationDate = time.Now().UTC().Unix() + meta.Properties = properties + + data, err := yaml.Marshal(&meta) + if err != nil { + ctw.Close() + logger.Error("Failed exporting instance", ctxMap) + return err + } + + // Write the actual file. + fnam = filepath.Join(tempDir, "metadata.yaml") + err = ioutil.WriteFile(fnam, data, 0644) + if err != nil { + ctw.Close() + logger.Error("Failed exporting instance", ctxMap) + return err + } + + fi, err := os.Lstat(fnam) + if err != nil { + ctw.Close() + logger.Error("Failed exporting instance", ctxMap) + return err + } + + tmpOffset := len(filepath.Dir(fnam)) + 1 + if err := ctw.WriteFile(tmpOffset, fnam, fi); err != nil { + ctw.Close() + logger.Error("Failed exporting instance", ctxMap) + return err + } + } else { + if properties != nil { + // Parse the metadata. + content, err := ioutil.ReadFile(fnam) + if err != nil { + ctw.Close() + logger.Error("Failed exporting instance", ctxMap) + return err + } + + metadata := new(api.ImageMetadata) + err = yaml.Unmarshal(content, &metadata) + if err != nil { + ctw.Close() + logger.Error("Failed exporting instance", ctxMap) + return err + } + metadata.Properties = properties + + // Generate a new metadata.yaml. + tempDir, err := ioutil.TempDir("", "lxd_lxd_metadata_") + if err != nil { + ctw.Close() + logger.Error("Failed exporting instance", ctxMap) + return err + } + defer os.RemoveAll(tempDir) + + data, err := yaml.Marshal(&metadata) + if err != nil { + ctw.Close() + logger.Error("Failed exporting instance", ctxMap) + return err + } + + // Write the actual file. + fnam = filepath.Join(tempDir, "metadata.yaml") + err = ioutil.WriteFile(fnam, data, 0644) + if err != nil { + ctw.Close() + logger.Error("Failed exporting instance", ctxMap) + return err + } + } + + // Include metadata.yaml in the tarball. + fi, err := os.Lstat(fnam) + if err != nil { + ctw.Close() + logger.Debugf("Error statting %s during export", fnam) + logger.Error("Failed exporting instance", ctxMap) + return err + } + + if properties != nil { + tmpOffset := len(filepath.Dir(fnam)) + 1 + err = ctw.WriteFile(tmpOffset, fnam, fi) + } else { + err = ctw.WriteFile(offset, fnam, fi) + } + if err != nil { + ctw.Close() + logger.Debugf("Error writing to tarfile: %s", err) + logger.Error("Failed exporting instance", ctxMap) + return err + } + } + + // Convert and include the root image. + pool, err := vm.getStoragePool() + if err != nil { + return err + } + + rootDrivePath, err := pool.GetInstanceDisk(vm) + if err != nil { + return err + } + + // Convert from raw to qcow2 and add to tarball. + tmpPath, err := ioutil.TempDir("", "lxd_export_") + if err != nil { + return err + } + defer os.RemoveAll(tmpPath) + + fPath := fmt.Sprintf("%s/rootfs.img", tmpPath) + _, err = shared.RunCommand("qemu-img", "convert", "-c", "-O", "qcow2", rootDrivePath, fPath) + if err != nil { + return fmt.Errorf("Failed converting image to qcow2: %v", err) + } + + fi, err := os.Lstat(fPath) + if err != nil { + return err + } + + err = ctw.WriteFile(len(tmpPath)+1, fPath, fi) + if err != nil { + return err + } + + // Include all the templates. + fnam = vm.TemplatesPath() + if shared.PathExists(fnam) { + err = filepath.Walk(fnam, writeToTar) + if err != nil { + logger.Error("Failed exporting instance", ctxMap) + return err + } + } + + err = ctw.Close() + if err != nil { + logger.Error("Failed exporting instance", ctxMap) + return err + } + + logger.Info("Exported instance", ctxMap) + return nil } // Migrate migrates the instance to another node. From 9b75384f0309f5d53108fe386de3df8d53a1f14f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Wed, 11 Mar 2020 22:57:06 -0400 Subject: [PATCH 4/4] lxd/images: Unpack unified VM images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- lxd/storage/utils.go | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/lxd/storage/utils.go b/lxd/storage/utils.go index f5a4ebe734..939082396e 100644 --- a/lxd/storage/utils.go +++ b/lxd/storage/utils.go @@ -590,26 +590,25 @@ func ImageUnpack(imageFile, destPath, destBlockFile string, blockBackend, runnin } else { // If a rootBlockPath is supplied then this is a VM image unpack. - // VM images require a separate rootfs file. - if !shared.PathExists(imageRootfsFile) { - return fmt.Errorf("Image is missing a rootfs file: %s", imageRootfsFile) - } - - // Check that the rootBlockPath exists and is a file. + // Validate the target. fileInfo, err := os.Stat(destBlockFile) if err != nil && !os.IsNotExist(err) { return err } - if os.IsNotExist(err) || !fileInfo.IsDir() { + if fileInfo.IsDir() { + // If the dest block file exists, and it is a directory, fail. + return fmt.Errorf("Root block path isn't a file: %s", destBlockFile) + } + + if shared.PathExists(imageRootfsFile) { // Convert the qcow2 format to a raw block device. _, err = shared.RunCommand("qemu-img", "convert", "-O", "raw", imageRootfsFile, destBlockFile) if err != nil { return fmt.Errorf("Failed converting image to raw at %s: %v", destBlockFile, err) } } else { - // If the dest block file exists, and it is a directory, fail. - return fmt.Errorf("Root block path isn't a file: %s", destBlockFile) + // Get the qcow2 image out of the tarball. } }
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel