[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