[lxc-devel] [lxd/master] VM publish & unified images import

stgraber on Github lxc-bot at linuxcontainers.org
Thu Mar 12 02:57:36 UTC 2020


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200311/b24164c6/attachment.bin>
-------------- next part --------------
From 2689168252b2f1122bb08bb949f8f61dfa973f06 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at 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 <stgraber at 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?= <stgraber at 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 <stgraber at 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?= <stgraber at 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 <stgraber at 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?= <stgraber at 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 <stgraber at 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.
 		}
 	}
 


More information about the lxc-devel mailing list