mirror of
https://github.com/linuxkit/linuxkit.git
synced 2026-04-09 00:20:29 +00:00
Support multiple disks in qemu
This changes the CLI specification for disks, as it needs to be able to be repeated. ``` linuxkit run qemu -disk name,size=1G,format=qcow2 ... ``` Options may be omitted. Currently other local backends may not support multiple disks, but this can be added in future. Code for cloud backends has not changed as the disk support is specific to the platform. Signed-off-by: Justin Cormack <justin.cormack@docker.com>
This commit is contained in:
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user