Merge pull request #1944 from justincormack/qemu-multiple-disks

Support multiple disks in qemu
This commit is contained in:
Justin Cormack 2017-06-01 14:17:51 +01:00 committed by GitHub
commit 583c5755fa
7 changed files with 166 additions and 79 deletions

View File

@ -7,14 +7,15 @@
## Make Disk Available
In order to make the disk available, you need to tell `linuxkit` where the disk file or block device is.
All local `linuxkit run` methods (currently `hyperkit`, `qemu`, and `vmware`) take two arguments:
All local `linuxkit run` methods (currently `hyperkit`, `qemu`, and `vmware`) take a `-disk` argument:
* `-disk-size <size>`: size of disk. The default is in MB but a `G` can be appended to specify the size in GB, e.g. `-disk-size 4096` or `disk-size 4G` will provide a 4GB disk.
* `-disk <path>`: use the disk at location _path_, e.g. `-disk foo.img` will use the disk at `$PWD/foo.img`
* `-disk path,size=100M,format=qcow2`. For size the default is in MB but `G` can be aspecified for GB. The format can be omitted for the platform default, and is only useful on `qemu` at present.
If you do not provide `-disk `_path_, `linuxkit` assumes a default, which is _prefix_`-state/disk.img` for `hyperkit` and `vmware` and _prefix_`-disk.img` for `qemu`.
If the _path` is specified it will use the disk at location _path_, if you do not provide `-disk `_path_, `linuxkit` assumes a default, which is _prefix_`-state/disk.img` for `hyperkit` and `vmware` and _prefix_`-disk.img` for `qemu`.
If the disk at `<path>`, or the default if `-disk` option is not provided, does not exist, `linuxkit` will create one of size `<size>`.
If the disk at the specified or default `<path>` does not exist, `linuxkit` will create one of size `<size>`.
The `-disk` specification may be repeated for multiple disks, although a limited number may be supported, and some platforms currently only support a single disk.
**TODO:** GCP

View File

@ -8,6 +8,7 @@ import (
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
log "github.com/Sirupsen/logrus"
@ -35,8 +36,8 @@ func runHyperKit(args []string) {
hyperkitPath := flags.String("hyperkit", "", "Path to hyperkit binary (if not in default location)")
cpus := flags.Int("cpus", 1, "Number of CPUs")
mem := flags.Int("mem", 1024, "Amount of memory in MB")
diskSzFlag := flags.String("disk-size", "", "Size of Disk in MB (or GB if 'G' is appended)")
disk := flags.String("disk", "", "Path to disk image to use")
var disks Disks
flags.Var(&disks, "disk", "Disk config. [file=]path[,size=1G]")
data := flags.String("data", "", "Metadata to pass to VM (either a path to a file or a string)")
ipStr := flags.String("ip", "", "IP address for the VM")
state := flags.String("state", "", "Path to directory to keep VM state in")
@ -61,11 +62,6 @@ func runHyperKit(args []string) {
log.Fatalf("Could not create state directory: %v", err)
}
diskSz, err := getDiskSizeMB(*diskSzFlag)
if err != nil {
log.Fatalf("Could parse disk-size %s: %v", *diskSzFlag, err)
}
isoPath := ""
if *data != "" {
var d []byte
@ -106,8 +102,22 @@ func runHyperKit(args []string) {
log.Fatalf("Cannot open cmdline file: %v", err)
}
if diskSz != 0 && *disk == "" {
*disk = filepath.Join(*state, "disk.img")
for i, d := range disks {
id := ""
if i != 0 {
id = strconv.Itoa(i)
}
if d.Size != 0 && d.Path == "" {
d.Path = filepath.Join(*state, "disk"+id+".img")
}
if d.Path == "" {
log.Fatalf("disk specified with no size or name")
}
disks[i] = d
}
if len(disks) > 1 {
log.Fatalf("Hyperkit driver currently only supports a single disk")
}
// Create new HyperKit instance (w/o networking for now)
@ -163,12 +173,14 @@ func runHyperKit(args []string) {
h.Initrd = prefix + "-initrd.img"
h.VPNKitKey = vpnKitKey
h.UUID = vmUUID
h.DiskImage = *disk
if len(disks) == 1 {
h.DiskImage = disks[0].Path
h.DiskSize = disks[0].Size
}
h.ISOImage = isoPath
h.VSock = true
h.CPUs = *cpus
h.Memory = *mem
h.DiskSize = diskSz
err = h.Run(string(cmdline))
if err != nil {

View File

@ -24,9 +24,7 @@ type QemuConfig struct {
UEFI bool
Kernel bool
GUI bool
DiskPath string
DiskSize string
DiskFormat string
Disks Disks
FWPath string
Arch string
CPUs string
@ -58,9 +56,8 @@ func runQemu(args []string) {
kernelBoot := flags.Bool("kernel", false, "Boot image is kernel+initrd+cmdline 'path'-kernel/-initrd/-cmdline")
// Paths and settings for disks
diskSzFlag := flags.String("disk-size", "", "Size of Disk in MB (or GB if 'G' is appended)")
disk := flags.String("disk", "", "Path to disk image to use")
diskFmt := flags.String("disk-format", "qcow2", "Format of disk: raw, qcow2 etc")
var disks Disks
flags.Var(&disks, "disk", "Disk config, may be repeated. [file=]path[,size=1G][,format=qcow2]")
// Paths and settings for UEFI firware
fw := flags.String("fw", "/usr/share/ovmf/bios.bin", "Path to OVMF firmware for UEFI boot")
@ -113,21 +110,28 @@ func runQemu(args []string) {
}
}
diskSz, err := getDiskSizeMB(*diskSzFlag)
if err != nil {
log.Fatalf("Couldn't parse disk-size %s: %v", *diskSzFlag, err)
}
if diskSz != 0 && *disk == "" {
*disk = prefix + "-disk.img"
for i, d := range disks {
id := ""
if i != 0 {
id = strconv.Itoa(i)
}
if d.Size != 0 && d.Format == "" {
d.Format = "qcow2"
}
if d.Size != 0 && d.Path == "" {
d.Path = prefix + "-disk" + id + ".img"
}
if d.Path == "" {
log.Fatalf("disk specified with no size or name")
}
disks[i] = d
}
// user not trying to boot off ISO or kernel, so assume booting from a disk image
if !*kernelBoot && !*isoBoot {
if *disk != "" {
// Need to add multiple disk support to do this
log.Fatalf("Cannot boot from disk and specify a disk as well at present")
}
*disk = path
// currently no way to set format, but autodetect probably works
d := Disks{DiskConfig{Path: path}}
disks = append(d, disks...)
}
config := QemuConfig{
@ -136,9 +140,7 @@ func runQemu(args []string) {
UEFI: *uefiBoot,
Kernel: *kernelBoot,
GUI: *enableGUI,
DiskPath: *disk,
DiskSize: fmt.Sprintf("%dM", diskSz),
DiskFormat: *diskFmt,
Disks: disks,
FWPath: *fw,
Arch: *arch,
CPUs: *cpus,
@ -163,21 +165,21 @@ func runQemuLocal(config QemuConfig) error {
var args []string
config, args = buildQemuCmdline(config)
if config.DiskPath != "" {
for _, d := range config.Disks {
// If disk doesn't exist then create one
if _, err := os.Stat(config.DiskPath); err != nil {
if _, err := os.Stat(d.Path); err != nil {
if os.IsNotExist(err) {
log.Debugf("Creating new qemu disk [%s] format %s", config.DiskPath, config.DiskFormat)
qemuImgCmd := exec.Command(config.QemuImgPath, "create", "-f", config.DiskFormat, config.DiskPath, config.DiskSize)
log.Debugf("Creating new qemu disk [%s] format %s", d.Path, d.Format)
qemuImgCmd := exec.Command(config.QemuImgPath, "create", "-f", d.Format, d.Path, fmt.Sprintf("%dM", d.Size))
log.Debugf("%v\n", qemuImgCmd.Args)
if err := qemuImgCmd.Run(); err != nil {
return fmt.Errorf("Error creating disk [%s] format %s: %s", config.DiskPath, config.DiskFormat, err.Error())
return fmt.Errorf("Error creating disk [%s] format %s: %s", d.Path, d.Format, err.Error())
}
} else {
return err
}
} else {
log.Infof("Using existing disk [%s] format %s", config.DiskPath, config.DiskFormat)
log.Infof("Using existing disk [%s] format %s", d.Path, d.Format)
}
}
@ -246,23 +248,23 @@ func runQemuContainer(config QemuConfig) error {
return fmt.Errorf("Unable to find docker in the $PATH")
}
if config.DiskPath != "" {
for _, d := range config.Disks {
// If disk doesn't exist then create one
if _, err = os.Stat(config.DiskPath); err != nil {
if _, err = os.Stat(d.Path); err != nil {
if os.IsNotExist(err) {
log.Debugf("Creating new qemu disk [%s] format %s", config.DiskPath, config.DiskFormat)
imgArgs := append(dockerArgsImg, QemuImg, "qemu-img", "create", "-f", config.DiskFormat, config.DiskPath, config.DiskSize)
log.Debugf("Creating new qemu disk [%s] format %s", d.Path, d.Format)
imgArgs := append(dockerArgsImg, QemuImg, "qemu-img", "create", "-f", d.Format, d.Path, fmt.Sprintf("%dM", d.Size))
qemuImgCmd := exec.Command(dockerPath, imgArgs...)
qemuImgCmd.Stderr = os.Stderr
log.Debugf("%v\n", qemuImgCmd.Args)
if err = qemuImgCmd.Run(); err != nil {
return fmt.Errorf("Error creating disk [%s] format %s: %s", config.DiskPath, config.DiskFormat, err.Error())
return fmt.Errorf("Error creating disk [%s] format %s: %s", d.Path, d.Format, err.Error())
}
} else {
return err
}
} else {
log.Infof("Using existing disk [%s] format %s", config.DiskPath, config.DiskFormat)
log.Infof("Using existing disk [%s] format %s", d.Path, d.Format)
}
}
@ -301,8 +303,17 @@ func buildQemuCmdline(config QemuConfig) (QemuConfig, []string) {
qemuArgs = append(qemuArgs, "-machine", "q35,accel=kvm:tcg")
}
if config.DiskPath != "" {
qemuArgs = append(qemuArgs, "-drive", "file="+config.DiskPath+",format="+config.DiskFormat+",index=0,media=disk")
for i, d := range config.Disks {
index := i
// hdc is CDROM in qemu
if i >= 2 && config.ISO {
index++
}
if d.Format != "" {
qemuArgs = append(qemuArgs, "-drive", "file="+d.Path+",format="+d.Format+",index="+strconv.Itoa(index)+",media=disk")
} else {
qemuArgs = append(qemuArgs, "-drive", "file="+d.Path+",index="+strconv.Itoa(index)+",media=disk")
}
}
if config.ISO {

View File

@ -8,6 +8,7 @@ import (
"os/exec"
"path/filepath"
"runtime"
"strconv"
log "github.com/Sirupsen/logrus"
)
@ -76,8 +77,8 @@ func runVMware(args []string) {
}
cpus := flags.Int("cpus", 1, "Number of CPUs")
mem := flags.Int("mem", 1024, "Amount of memory in MB")
diskSzFlag := flags.String("disk-size", "", "Size of Disk in MB (or GB if 'G' is appended)")
disk := flags.String("disk", "", "Path to disk image to use")
var disks Disks
flags.Var(&disks, "disk", "Disk config. [file=]path[,size=1G]")
state := flags.String("state", "", "Path to directory to keep VM state in")
if err := flags.Parse(args); err != nil {
@ -99,27 +100,19 @@ func runVMware(args []string) {
log.Fatalf("Could not create state directory: %v", err)
}
diskSz, err := getDiskSizeMB(*diskSzFlag)
if err != nil {
log.Fatalf("Could parse disk-size %s: %v", *diskSzFlag, err)
}
var vmrunPath, vmDiskManagerPath string
var vmrunArgs []string
if runtime.GOOS == "windows" {
switch runtime.GOOS {
case "windows":
vmrunPath = "C:\\Program\\ files\\VMware Workstation\\vmrun.exe"
vmDiskManagerPath = "C:\\Program\\ files\\VMware Workstation\\vmware-vdiskmanager.exe"
vmrunArgs = []string{"-T", "ws", "start"}
}
if runtime.GOOS == "darwin" {
case "darwin":
vmrunPath = "/Applications/VMware Fusion.app/Contents/Library/vmrun"
vmDiskManagerPath = "/Applications/VMware Fusion.app/Contents/Library/vmware-vdiskmanager"
vmrunArgs = []string{"-T", "fusion", "start"}
}
if runtime.GOOS == "linux" {
default:
vmrunPath = "vmrun"
vmDiskManagerPath = "vmware-vdiskmanager"
fullVMrunPath, err := exec.LookPath(vmrunPath)
@ -136,41 +129,65 @@ func runVMware(args []string) {
log.Fatalf("ERROR VMware executables can not be found, ensure software is installed")
}
if diskSz != 0 && *disk == "" {
*disk = filepath.Join(*state, "disk.vmdk")
for i, d := range disks {
id := ""
if i != 0 {
id = strconv.Itoa(i)
}
if d.Size != 0 && d.Path == "" {
d.Path = filepath.Join(*state, "disk"+id+".vmdk")
}
if d.Format != "" && d.Format != "vmdk" {
log.Fatalf("only vmdk supported for VMware driver")
}
if d.Path == "" {
log.Fatalf("disk specified with no size or name")
}
disks[i] = d
}
if *disk != "" {
for _, d := range disks {
// Check vmDiskManagerPath exist before attempting to execute
if _, err := os.Stat(vmDiskManagerPath); os.IsNotExist(err) {
log.Fatalf("ERROR VMware Disk Manager executables can not be found, ensure software is installed")
}
// If disk doesn't exist then create one, error if disk is unreadable
if _, err := os.Stat(*disk); err != nil {
if _, err := os.Stat(d.Path); err != nil {
if os.IsPermission(err) {
log.Fatalf("Unable to read file [%s], please check permissions", *disk)
}
if os.IsNotExist(err) {
log.Infof("Creating new VMware disk [%s]", *disk)
vmDiskCmd := exec.Command(vmDiskManagerPath, "-c", "-s", fmt.Sprintf("%dMB", diskSz), "-a", "lsilogic", "-t", "0", *disk)
log.Fatalf("Unable to read file [%s], please check permissions", d.Path)
} else if os.IsNotExist(err) {
log.Infof("Creating new VMware disk [%s]", d.Path)
vmDiskCmd := exec.Command(vmDiskManagerPath, "-c", "-s", fmt.Sprintf("%dMB", d.Size), "-a", "lsilogic", "-t", "0", d.Path)
if err = vmDiskCmd.Run(); err != nil {
log.Fatalf("Error creating disk [%s]: %v", *disk, err)
log.Fatalf("Error creating disk [%s]: %v", d.Path, err)
}
} else {
log.Fatalf("Unable to read file [%s]: %v", d.Path, err)
}
} else {
log.Infof("Using existing disk [%s]", *disk)
log.Infof("Using existing disk [%s]", d.Path)
}
}
if len(disks) > 1 {
log.Fatalf("VMware driver currently only supports a single disk")
}
disk := ""
if len(disks) == 1 {
disk = disks[0].Path
}
// Build the contents of the VMWare .vmx file
vmx := buildVMX(*cpus, *mem, *disk, prefix)
vmx := buildVMX(*cpus, *mem, disk, prefix)
if vmx == "" {
log.Fatalf("VMware .vmx file could not be generated, please confirm inputs")
}
// Create the .vmx file
vmxPath := filepath.Join(*state, "linuxkit.vmx")
err = ioutil.WriteFile(vmxPath, []byte(vmx), 0644)
err := ioutil.WriteFile(vmxPath, []byte(vmx), 0644)
if err != nil {
log.Fatalf("Error writing .vmx file: %v", err)
}

View File

@ -1,6 +1,7 @@
package main
import (
"fmt"
"os"
"strconv"
"strings"
@ -118,3 +119,48 @@ func getDiskSizeMB(s string) (int, error) {
}
return strconv.Atoi(s)
}
// DiskConfig is the config for a disk
type DiskConfig struct {
Path string
Size int
Format string
}
// Disks is the type for a list of DiskConfig
type Disks []DiskConfig
func (l *Disks) String() string {
return fmt.Sprint(*l)
}
// Set is used by flag to configure value from CLI
func (l *Disks) Set(value string) error {
d := DiskConfig{}
s := strings.Split(value, ",")
for _, p := range s {
c := strings.SplitN(p, "=", 2)
switch len(c) {
case 1:
// assume it is a filename even if no file=x
d.Path = c[0]
case 2:
switch c[0] {
case "file":
d.Path = c[1]
case "size":
size, err := getDiskSizeMB(c[1])
if err != nil {
return err
}
d.Size = size
case "format":
d.Format = c[1]
default:
return fmt.Errorf("Unknown disk config: %s", c[0])
}
}
}
*l = append(*l, d)
return nil
}

View File

@ -19,5 +19,5 @@ trap clean_up EXIT
moby build -output qcow2 -name "${NAME}" test.yml
[ -f "${NAME}.qcow2" ] || exit 1
linuxkit run qemu -disk-format qcow2 "${NAME}.qcow2" | grep -q "Welcome to LinuxKit"
linuxkit run qemu "${NAME}.qcow2" | grep -q "Welcome to LinuxKit"
exit 0

View File

@ -19,7 +19,7 @@ trap clean_up EXIT
# Test code goes here
moby build -output kernel+initrd run.yml
moby build -output kernel+initrd mkimage.yml
linuxkit run qemu -disk-size 200 -disk-format qcow2 -disk disk.qcow2 -kernel mkimage
linuxkit run qemu -disk disk.qcow2,size=200M,format=qcow2 -kernel mkimage
linuxkit run qemu disk.qcow2
exit 0