Support UEFI ISO boot on hyperkit

Signed-off-by: Justin Cormack <justin.cormack@docker.com>
This commit is contained in:
Justin Cormack 2017-08-04 11:16:25 +01:00
parent 550bf41d0b
commit 8aa811540f
6 changed files with 117 additions and 35 deletions

View File

@ -12,9 +12,8 @@ Alternatively, you can install HyperKit and VPNKit standalone and use it without
## Boot ## Boot
The HyperKit backend currently only supports booting the The HyperKit backend currently supports booting the
`kernel+initrd` output from `moby` (technically we could support EFI `kernel+initrd` output from `moby`, and EFI ISOs using the EFI firmware.
boot as well).
## Console ## Console
@ -28,8 +27,8 @@ HyperKit does not provide a console device.
## Disks ## Disks
The HyperKit backend support configuring a persistent disk using the The HyperKit backend support configuring a persistent disk using the
standard `linuxkit` `-disk` syntax. Currently, only one disk is standard `linuxkit` `-disk` syntax. Multiple disks are
supported and the disk is in raw format. supported and the disks are in raw format.
## Power management ## Power management
@ -128,8 +127,8 @@ there are a number of packages, such as `vsudd`, which enable
tighter integration of the VM with the host (see below). tighter integration of the VM with the host (see below).
The HyperKit backend also allows passing custom userdata into the The HyperKit backend also allows passing custom userdata into the
[metadata pacakge](./metadata.md) using the `-data` command-line [metadata package](./metadata.md) using the `-data` command-line
option. option. This attaches a CD device with the data on.
### `vsudd` unix domain socket forwarding ### `vsudd` unix domain socket forwarding

View File

@ -46,6 +46,15 @@ func runHyperKit(args []string) {
vsockports := flags.String("vsock-ports", "", "List of vsock ports to forward from the guest on startup (comma separated). A unix domain socket for each port will be created in the state directory") vsockports := flags.String("vsock-ports", "", "List of vsock ports to forward from the guest on startup (comma separated). A unix domain socket for each port will be created in the state directory")
networking := flags.String("networking", hyperkitNetworkingDefault, "Networking mode. Valid options are 'default', 'docker-for-mac', 'vpnkit[,socket-path]', 'vmnet' and 'none'. 'docker-for-mac' connects to the network used by Docker for Mac. 'vpnkit' connects to the VPNKit socket specified. If socket-path is omitted a new VPNKit instance will be started and 'vpnkit_eth.sock' will be created in the state directory. 'vmnet' uses the Apple vmnet framework, requires root/sudo. 'none' disables networking.`") networking := flags.String("networking", hyperkitNetworkingDefault, "Networking mode. Valid options are 'default', 'docker-for-mac', 'vpnkit[,socket-path]', 'vmnet' and 'none'. 'docker-for-mac' connects to the network used by Docker for Mac. 'vpnkit' connects to the VPNKit socket specified. If socket-path is omitted a new VPNKit instance will be started and 'vpnkit_eth.sock' will be created in the state directory. 'vmnet' uses the Apple vmnet framework, requires root/sudo. 'none' disables networking.`")
// Boot type; we try to determine automatically
uefiBoot := flags.Bool("uefi", false, "Use UEFI boot")
isoBoot := flags.Bool("iso", false, "Boot image is an ISO")
kernelBoot := flags.Bool("kernel", false, "Boot image is kernel+initrd+cmdline 'path'-kernel/-initrd/-cmdline")
// Paths and settings for UEFI firware
// Note, the default uses the firmware shipped with Docker for Mac
fw := flags.String("fw", "/Applications/Docker.app/Contents/Resources/uefi/UEFI.fd", "Path to OVMF firmware for UEFI boot")
if err := flags.Parse(args); err != nil { if err := flags.Parse(args); err != nil {
log.Fatal("Unable to parse args") log.Fatal("Unable to parse args")
} }
@ -55,7 +64,48 @@ func runHyperKit(args []string) {
flags.Usage() flags.Usage()
os.Exit(1) os.Exit(1)
} }
prefix := remArgs[0] path := remArgs[0]
prefix := path
_, err := os.Stat(path)
stat := err == nil
var isoPaths []string
// if the path does not exist, must be trying to do a kernel boot
if !stat {
_, err = os.Stat(path + "-kernel")
statKernel := err == nil
if statKernel {
*kernelBoot = true
} else {
log.Fatalf("Cannot find kernel file (%s): %v", path+"-kernel", err)
}
_, err = os.Stat(path + "-initrd.img")
statInitrd := err == nil
if !statInitrd {
log.Fatalf("Cannot find initrd file (%s): %v", path+"-initrd.img", err)
}
} else {
// if path ends in .iso they meant an ISO
if strings.HasSuffix(path, ".iso") {
*isoBoot = true
prefix = strings.TrimSuffix(path, ".iso")
// hyperkit only supports UEFI ISO boot at present
*uefiBoot = true
}
}
if *isoBoot {
isoPaths = append(isoPaths, path)
}
if *uefiBoot {
_, err := os.Stat(*fw)
if err != nil {
log.Fatalf("Cannot open UEFI firmware file (%s): %v", *fw, err)
}
}
if *state == "" { if *state == "" {
*state = prefix + "-state" *state = prefix + "-state"
@ -64,7 +114,6 @@ func runHyperKit(args []string) {
log.Fatalf("Could not create state directory: %v", err) log.Fatalf("Could not create state directory: %v", err)
} }
isoPath := ""
if *data != "" { if *data != "" {
var d []byte var d []byte
if _, err := os.Stat(*data); os.IsNotExist(err) { if _, err := os.Stat(*data); os.IsNotExist(err) {
@ -75,10 +124,11 @@ func runHyperKit(args []string) {
log.Fatalf("Cannot read user data: %v", err) log.Fatalf("Cannot read user data: %v", err)
} }
} }
isoPath = filepath.Join(*state, "data.iso") isoPath := filepath.Join(*state, "data.iso")
if err := WriteMetadataISO(isoPath, d); err != nil { if err := WriteMetadataISO(isoPath, d); err != nil {
log.Fatalf("Cannot write user data ISO: %v", err) log.Fatalf("Cannot write user data ISO: %v", err)
} }
isoPaths = append(isoPaths, isoPath)
} }
vpnKitKey := "" vpnKitKey := ""
@ -99,9 +149,12 @@ func runHyperKit(args []string) {
vmUUID := uuid.NewV4().String() vmUUID := uuid.NewV4().String()
// Run // Run
cmdline, err := ioutil.ReadFile(prefix + "-cmdline") var cmdline []byte
if err != nil { if *kernelBoot {
log.Fatalf("Cannot open cmdline file: %v", err) cmdline, err = ioutil.ReadFile(prefix + "-cmdline")
if err != nil {
log.Fatalf("Cannot open cmdline file: %v", err)
}
} }
// Create new HyperKit instance (w/o networking for now) // Create new HyperKit instance (w/o networking for now)
@ -175,11 +228,15 @@ func runHyperKit(args []string) {
log.Fatalf("Invalid networking mode: %s", netMode[0]) log.Fatalf("Invalid networking mode: %s", netMode[0])
} }
h.Kernel = prefix + "-kernel" if *kernelBoot {
h.Initrd = prefix + "-initrd.img" h.Kernel = prefix + "-kernel"
h.Initrd = prefix + "-initrd.img"
} else {
h.Bootrom = *fw
}
h.VPNKitKey = vpnKitKey h.VPNKitKey = vpnKitKey
h.UUID = vmUUID h.UUID = vmUUID
h.ISOImage = isoPath h.ISOImages = isoPaths
h.VSock = true h.VSock = true
h.CPUs = *cpus h.CPUs = *cpus
h.Memory = *mem h.Memory = *mem

View File

@ -12,7 +12,7 @@ github.com/googleapis/gax-go 8c5154c0fe5bf18cf649634d4c6df50897a32751
github.com/gophercloud/gophercloud 2804b72cf099b41d2e25c8afcca786f9f962ddee github.com/gophercloud/gophercloud 2804b72cf099b41d2e25c8afcca786f9f962ddee
github.com/jmespath/go-jmespath bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d github.com/jmespath/go-jmespath bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d
github.com/mitchellh/go-ps 4fdf99ab29366514c69ccccddab5dc58b8d84062 github.com/mitchellh/go-ps 4fdf99ab29366514c69ccccddab5dc58b8d84062
github.com/moby/hyperkit 2dbe405bfb05a93b2237516d1bffed850a4685f3 github.com/moby/hyperkit a82b409a87f12fa3306813410c37f4eed270efac
github.com/packethost/packngo 91d54000aa56874149d348a884ba083c41d38091 github.com/packethost/packngo 91d54000aa56874149d348a884ba083c41d38091
github.com/radu-matei/azure-sdk-for-go 3b12823551999669c9a325a32472508e0af7978e github.com/radu-matei/azure-sdk-for-go 3b12823551999669c9a325a32472508e0af7978e
github.com/radu-matei/azure-vhd-utils e52754d5569d2a643a7775f72ff2a6cf524f4c25 github.com/radu-matei/azure-vhd-utils e52754d5569d2a643a7775f72ff2a6cf524f4c25

View File

@ -38,7 +38,7 @@ via `brew` and using `opam` to install the appropriate libraries:
$ brew install opam libev $ brew install opam libev
$ opam init $ opam init
$ eval `opam config env` $ eval `opam config env`
$ opam install uri qcow.0.10.0 qcow-tool mirage-block-unix.2.7.0 conf-libev logs fmt mirage-unix prometheus-app $ opam install uri qcow.0.10.3 conduit.1.0.0 lwt.3.1.0 qcow-tool mirage-block-unix.2.7.0 conf-libev logs fmt mirage-unix prometheus-app
Notes: Notes:

View File

@ -71,8 +71,10 @@ type Socket9P struct {
// DiskConfig contains the path to a disk image and an optional size if the image needs to be created. // DiskConfig contains the path to a disk image and an optional size if the image needs to be created.
type DiskConfig struct { type DiskConfig struct {
Path string `json:"path"` Path string `json:"path"`
Size int `json:"size"` Size int `json:"size"`
Format string `json:"format"`
Driver string `json:"driver"`
} }
// HyperKit contains the configuration of the hyperkit VM // HyperKit contains the configuration of the hyperkit VM
@ -90,7 +92,7 @@ type HyperKit struct {
// Disks contains disk images to use/create. // Disks contains disk images to use/create.
Disks []DiskConfig `json:"disks"` Disks []DiskConfig `json:"disks"`
// ISOImage is the (optional) path to a ISO image to attach // ISOImage is the (optional) path to a ISO image to attach
ISOImage string `json:"iso"` ISOImages []string `json:"iso"`
// VSock enables the virtio-socket device and exposes it on the host // VSock enables the virtio-socket device and exposes it on the host
VSock bool `json:"vsock"` VSock bool `json:"vsock"`
// VSockPorts is a list of guest VSock ports that should be exposed as sockets on the host // VSockPorts is a list of guest VSock ports that should be exposed as sockets on the host
@ -108,6 +110,8 @@ type HyperKit struct {
Kernel string `json:"kernel"` Kernel string `json:"kernel"`
// Initrd is the path to the initial ramdisk to boot off // Initrd is the path to the initial ramdisk to boot off
Initrd string `json:"initrd"` Initrd string `json:"initrd"`
// Bootrom is the path to a boot rom eg for UEFI boot
Bootrom string `json:"bootrom"`
// CPUs is the number CPUs to configure // CPUs is the number CPUs to configure
CPUs int `json:"cpus"` CPUs int `json:"cpus"`
@ -222,9 +226,9 @@ func (h *HyperKit) execute(cmdline string) error {
if h.Console == ConsoleStdio && !isTerminal(os.Stdout) && h.StateDir == "" { if h.Console == ConsoleStdio && !isTerminal(os.Stdout) && h.StateDir == "" {
return fmt.Errorf("If ConsoleStdio is set but stdio is not a terminal, StateDir must be specified") return fmt.Errorf("If ConsoleStdio is set but stdio is not a terminal, StateDir must be specified")
} }
if h.ISOImage != "" { for _, image := range h.ISOImages {
if _, err = os.Stat(h.ISOImage); os.IsNotExist(err) { if _, err = os.Stat(image); os.IsNotExist(err) {
return fmt.Errorf("ISO %s does not exist", h.ISOImage) return fmt.Errorf("ISO %s does not exist", image)
} }
} }
if h.VSock && h.StateDir == "" { if h.VSock && h.StateDir == "" {
@ -233,11 +237,17 @@ func (h *HyperKit) execute(cmdline string) error {
if !h.VSock && len(h.VSockPorts) > 0 { if !h.VSock && len(h.VSockPorts) > 0 {
return fmt.Errorf("To forward vsock ports vsock must be enabled") return fmt.Errorf("To forward vsock ports vsock must be enabled")
} }
if _, err = os.Stat(h.Kernel); os.IsNotExist(err) { if h.Bootrom == "" {
return fmt.Errorf("Kernel %s does not exist", h.Kernel) if _, err = os.Stat(h.Kernel); os.IsNotExist(err) {
} return fmt.Errorf("Kernel %s does not exist", h.Kernel)
if _, err = os.Stat(h.Initrd); os.IsNotExist(err) { }
return fmt.Errorf("initrd %s does not exist", h.Initrd) if _, err = os.Stat(h.Initrd); os.IsNotExist(err) {
return fmt.Errorf("initrd %s does not exist", h.Initrd)
}
} else {
if _, err = os.Stat(h.Bootrom); os.IsNotExist(err) {
return fmt.Errorf("Bootrom %s does not exist", h.Bootrom)
}
} }
// Create files // Create files
@ -428,7 +438,18 @@ func (h *HyperKit) buildArgs(cmdline string) {
} }
for _, p := range h.Disks { for _, p := range h.Disks {
a = append(a, "-s", fmt.Sprintf("%d:0,virtio-blk,%s", nextSlot, p.Path)) // Default the driver to virtio-blk
driver := "virtio-blk"
if p.Driver != "" {
driver = p.Driver
}
arg := fmt.Sprintf("%d:0,%s,%s", nextSlot, driver, p.Path)
// Add on a format instruction if specified.
if p.Format != "" {
arg += ",format=" + p.Format
}
a = append(a, "-s", arg)
nextSlot++ nextSlot++
} }
@ -441,8 +462,8 @@ func (h *HyperKit) buildArgs(cmdline string) {
nextSlot++ nextSlot++
} }
if h.ISOImage != "" { for _, image := range h.ISOImages {
a = append(a, "-s", fmt.Sprintf("%d,ahci-cd,%s", nextSlot, h.ISOImage)) a = append(a, "-s", fmt.Sprintf("%d,ahci-cd,%s", nextSlot, image))
nextSlot++ nextSlot++
} }
@ -460,8 +481,13 @@ func (h *HyperKit) buildArgs(cmdline string) {
a = append(a, "-l", fmt.Sprintf("com1,autopty=%s/tty,log=%s/console-ring", h.StateDir, h.StateDir)) a = append(a, "-l", fmt.Sprintf("com1,autopty=%s/tty,log=%s/console-ring", h.StateDir, h.StateDir))
} }
kernArgs := fmt.Sprintf("kexec,%s,%s,earlyprintk=serial %s", h.Kernel, h.Initrd, cmdline) if h.Bootrom == "" {
a = append(a, "-f", kernArgs) kernArgs := fmt.Sprintf("kexec,%s,%s,earlyprintk=serial %s", h.Kernel, h.Initrd, cmdline)
a = append(a, "-f", kernArgs)
} else {
kernArgs := fmt.Sprintf("bootrom,%s,,", h.Bootrom)
a = append(a, "-f", kernArgs)
}
h.Arguments = a h.Arguments = a
h.CmdLine = h.HyperKit + " " + strings.Join(a, " ") h.CmdLine = h.HyperKit + " " + strings.Join(a, " ")

View File

@ -212,7 +212,7 @@ struct vring_used {
#define VIRTIO_VENDOR 0x1AF4 #define VIRTIO_VENDOR 0x1AF4
#define VIRTIO_DEV_NET 0x1000 #define VIRTIO_DEV_NET 0x1000
#define VIRTIO_DEV_BLOCK 0x1001 #define VIRTIO_DEV_BLOCK 0x1001
#define VIRTIO_DEV_RANDOM 0x1002 #define VIRTIO_DEV_RANDOM 0x1005
#define VIRTIO_DEV_9P 0x1009 #define VIRTIO_DEV_9P 0x1009
#define VIRTIO_DEV_SOCK 0x103f /* In the legacy range. */ #define VIRTIO_DEV_SOCK 0x103f /* In the legacy range. */