Skip to content

Commit

Permalink
Merge pull request #1214 from gibmat/incus-migrate-qcow2
Browse files Browse the repository at this point in the history
Add qcow2 and vmdk support to incus-migrate
  • Loading branch information
stgraber authored Sep 13, 2024
2 parents ca06259 + 010e617 commit 7076330
Show file tree
Hide file tree
Showing 4 changed files with 83 additions and 13 deletions.
88 changes: 77 additions & 11 deletions cmd/incus-migrate/main_migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ import (

"github.com/lxc/incus/v6/client"
cli "github.com/lxc/incus/v6/internal/cmd"
"github.com/lxc/incus/v6/internal/linux"
"github.com/lxc/incus/v6/internal/revert"
"github.com/lxc/incus/v6/internal/version"
"github.com/lxc/incus/v6/shared/api"
"github.com/lxc/incus/v6/shared/archive"
"github.com/lxc/incus/v6/shared/osarch"
localtls "github.com/lxc/incus/v6/shared/tls"
"github.com/lxc/incus/v6/shared/units"
Expand Down Expand Up @@ -59,28 +61,31 @@ func (c *cmdMigrate) Command() *cobra.Command {

type cmdMigrateData struct {
SourcePath string
SourceFormat string
Mounts []string
InstanceArgs api.InstancesPost
Project string
}

func (c *cmdMigrateData) Render() string {
data := struct {
Name string `yaml:"Name"`
Project string `yaml:"Project"`
Type api.InstanceType `yaml:"Type"`
Source string `yaml:"Source"`
Mounts []string `yaml:"Mounts,omitempty"`
Profiles []string `yaml:"Profiles,omitempty"`
StoragePool string `yaml:"Storage pool,omitempty"`
StorageSize string `yaml:"Storage pool size,omitempty"`
Network string `yaml:"Network name,omitempty"`
Config map[string]string `yaml:"Config,omitempty"`
Name string `yaml:"Name"`
Project string `yaml:"Project"`
Type api.InstanceType `yaml:"Type"`
Source string `yaml:"Source"`
SourceFormat string `yaml:"Source format,omitempty"`
Mounts []string `yaml:"Mounts,omitempty"`
Profiles []string `yaml:"Profiles,omitempty"`
StoragePool string `yaml:"Storage pool,omitempty"`
StorageSize string `yaml:"Storage pool size,omitempty"`
Network string `yaml:"Network name,omitempty"`
Config map[string]string `yaml:"Config,omitempty"`
}{
c.InstanceArgs.Name,
c.Project,
c.InstanceArgs.Type,
c.SourcePath,
c.SourceFormat,
c.Mounts,
c.InstanceArgs.Profiles,
"",
Expand Down Expand Up @@ -333,7 +338,7 @@ func (c *cmdMigrate) RunInteractive(server incus.InstanceServer) (cmdMigrateData

// Provide source path
if config.InstanceArgs.Type == api.InstanceTypeVM {
question = "Please provide the path to a disk, partition, or raw image file: "
question = "Please provide the path to a disk, partition, or qcow2/raw/vmdk image file: "
} else {
question = "Please provide the path to a root filesystem: "
}
Expand All @@ -348,6 +353,21 @@ func (c *cmdMigrate) RunInteractive(server incus.InstanceServer) (cmdMigrateData
return err
}

// When migrating a VM, report the detected source format
if config.InstanceArgs.Type == api.InstanceTypeVM {
if linux.IsBlockdevPath(s) {
config.SourceFormat = "Block device"
} else if _, ext, _, _ := archive.DetectCompression(s); ext == ".qcow2" {
config.SourceFormat = "qcow2"
} else if _, ext, _, _ := archive.DetectCompression(s); ext == ".vmdk" {
config.SourceFormat = "vmdk"
} else {
// If the input isn't a block device or qcow2/vmdk image, assume it's raw.
// Positively identifying a raw image depends on parsing MBR/GPT partition tables.
config.SourceFormat = "raw"
}
}

return nil
})
if err != nil {
Expand Down Expand Up @@ -536,7 +556,15 @@ func (c *cmdMigrate) Run(cmd *cobra.Command, args []string) error {

// Automatically clean-up the temporary path on exit
defer func(path string) {
// Unmount the path if it's a mountpoint.
_ = unix.Unmount(path, unix.MNT_DETACH)
_ = unix.Unmount(filepath.Join(path, "root.img"), unix.MNT_DETACH)

// Cleanup VM image files.
_ = os.Remove(filepath.Join(path, "converted-raw-image.img"))
_ = os.Remove(filepath.Join(path, "root.img"))

// Remove the directory itself.
_ = os.Remove(path)
}(path)

Expand All @@ -557,6 +585,44 @@ func (c *cmdMigrate) Run(cmd *cobra.Command, args []string) error {
return fmt.Errorf("Failed to setup the source: %w", err)
}
} else {
_, ext, convCmd, _ := archive.DetectCompression(config.SourcePath)
if ext == ".qcow2" || ext == ".vmdk" {
destImg := filepath.Join(path, "converted-raw-image.img")

cmd := []string{
"nice", "-n19", // Run with low priority to reduce CPU impact on other processes.
}

cmd = append(cmd, convCmd...)
cmd = append(cmd, "-p", "-t", "writeback")

// Check for Direct I/O support.
from, err := os.OpenFile(config.SourcePath, unix.O_DIRECT|unix.O_RDONLY, 0)
if err == nil {
cmd = append(cmd, "-T", "none")
_ = from.Close()
}

to, err := os.OpenFile(destImg, unix.O_DIRECT|unix.O_RDONLY, 0)
if err == nil {
cmd = append(cmd, "-t", "none")
_ = to.Close()
}

cmd = append(cmd, config.SourcePath, destImg)

fmt.Printf("Converting image %q to raw format before importing\n", config.SourcePath)

c := exec.Command(cmd[0], cmd[1:]...)
err = c.Run()

if err != nil {
return fmt.Errorf("Failed to convert image %q for importing: %w", config.SourcePath, err)
}

config.SourcePath = destImg
}

fullPath = path
target := filepath.Join(path, "root.img")

Expand Down
4 changes: 2 additions & 2 deletions doc/.sphinx/.markdownlint/exceptions.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
.tmp/doc/howto/import_machines_to_instances.md:104: MD034 Bare URL used
.tmp/doc/howto/import_machines_to_instances.md:208: MD034 Bare URL used
.tmp/doc/howto/import_machines_to_instances.md:106: MD034 Bare URL used
.tmp/doc/howto/import_machines_to_instances.md:210: MD034 Bare URL used
.tmp/doc/howto/network_forwards.md:52: MD004 Unordered list style
.tmp/doc/howto/network_forwards.md:56: MD004 Unordered list style
.tmp/doc/howto/network_forwards.md:53: MD005 Inconsistent indentation for list items at the same level
Expand Down
2 changes: 2 additions & 0 deletions doc/howto/import_machines_to_instances.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ You can run the tool on any Linux machine.
It connects to an Incus server and creates a blank instance, which you can configure during or after the migration.
The tool then copies the data from the disk or image that you provide to the instance.

`incus-migrate` can import images in `raw`, `qcow2`, and `vmdk` file formats.

```{note}
If you want to configure your new instance during the migration process, set up the entities that you want your instance to use before starting the migration process.
Expand Down
2 changes: 2 additions & 0 deletions shared/archive/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ func DetectCompressionFile(f io.Reader) ([]string, string, []string, error) {
return []string{"-xf"}, ".squashfs", []string{"sqfs2tar", "--no-skip"}, nil
case bytes.Equal(header[0:3], []byte{'Q', 'F', 'I'}):
return []string{""}, ".qcow2", []string{"qemu-img", "convert", "-O", "raw"}, nil
case bytes.Equal(header[0:4], []byte{'K', 'D', 'M', 'V'}):
return []string{""}, ".vmdk", []string{"qemu-img", "convert", "-O", "raw"}, nil
case bytes.Equal(header[0:4], []byte{0x28, 0xb5, 0x2f, 0xfd}):
return []string{"--zstd", "-xf"}, ".tar.zst", []string{"zstd", "-d"}, nil
default:
Expand Down

0 comments on commit 7076330

Please sign in to comment.