Merge pull request #2881 from rn/hyper

Update hyperkit go binding
This commit is contained in:
Rolf Neugebauer 2018-01-24 17:01:06 +00:00 committed by GitHub
commit e921db1292
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 618 additions and 312 deletions

View File

@ -26,6 +26,10 @@ const (
hyperkitNetworkingDefault = hyperkitNetworkingDockerForMac
)
func init() {
hyperkit.SetLogger(log.StandardLogger())
}
// Process the run arguments and execute run
func runHyperKit(args []string) {
flags := flag.NewFlagSet("hyperkit", flag.ExitOnError)
@ -206,12 +210,15 @@ func runHyperKit(args []string) {
id = strconv.Itoa(i)
}
if d.Size != 0 && d.Path == "" {
d.Path = filepath.Join(*state, "disk"+id+".img")
d.Path = filepath.Join(*state, "disk"+id+".raw")
}
if d.Path == "" {
log.Fatalf("disk specified with no size or name")
}
hd := hyperkit.DiskConfig{Path: d.Path, Size: d.Size}
hd, err := hyperkit.NewDisk(d.Path, d.Size)
if err != nil {
log.Fatalf("NewDisk failed: %v", err)
}
h.Disks = append(h.Disks, hd)
}

View File

@ -23,7 +23,7 @@ github.com/gorilla/mux 4c1c3952b7d9d0a061a3fa7b36fd373ba0398ebc
github.com/jmespath/go-jmespath bd40a432e4c76585ef6b72d3fd96fb9b6dc7b68d
github.com/mitchellh/go-ps 4fdf99ab29366514c69ccccddab5dc58b8d84062
github.com/moby/datakit 97b3d230535397a813323902c23751e176481a86
github.com/moby/hyperkit a12cd7250bcd8d689078e3e42ae4a7cf6a0cbaf3
github.com/moby/hyperkit a285521725f44f3d10ca1042c2c07d3a6e24bed8
# When updating also:
# curl -fsSL -o src/cmd/linuxkit/build.go https://raw.githubusercontent.com/moby/tool/«hash»/cmd/moby/build.go
github.com/moby/tool caca03c097eafd7aa1e011de59ded3604e1b318f

View File

@ -1,60 +0,0 @@
## About
This directory contains a collection of scripts used to build and manage this
repository. If there are any issues regarding the intention of a particular
script (or even part of a certain script), please reach out to us.
It may help us either refine our current scripts, or add on new ones
that are appropriate for a given use case.
## DinD (dind.sh)
DinD is a wrapper script which allows Docker to be run inside a Docker
container. DinD requires the container to
be run with privileged mode enabled.
## Generate Authors (generate-authors.sh)
Generates AUTHORS; a file with all the names and corresponding emails of
individual contributors. AUTHORS can be found in the home directory of
this repository.
## Make
There are two make files, each with different extensions. Neither are supposed
to be called directly; only invoke `make`. Both scripts run inside a Docker
container.
### make.ps1
- The Windows native build script that uses PowerShell semantics; it is limited
unlike `hack\make.sh` since it does not provide support for the full set of
operations provided by the Linux counterpart, `make.sh`. However, `make.ps1`
does provide support for local Windows development and Windows to Windows CI.
More information is found within `make.ps1` by the author, @jhowardmsft
### make.sh
- Referenced via `make test` when running tests on a local machine,
or directly referenced when running tests inside a Docker development container.
- When running on a local machine, `make test` to run all tests found in
`test`, `test-unit`, `test-integration`, and `test-docker-py` on
your local machine. The default timeout is set in `make.sh` to 60 minutes
(`${TIMEOUT:=60m}`), since it currently takes up to an hour to run
all of the tests.
- When running inside a Docker development container, `hack/make.sh` does
not have a single target that runs all the tests. You need to provide a
single command line with multiple targets that performs the same thing.
An example referenced from [Run targets inside a development container](https://docs.docker.com/opensource/project/test-and-docs/#run-targets-inside-a-development-container): `root@5f8630b873fe:/go/src/github.com/moby/moby# hack/make.sh dynbinary binary cross test-unit test-integration test-docker-py`
- For more information related to testing outside the scope of this README,
refer to
[Run tests and test documentation](https://docs.docker.com/opensource/project/test-and-docs/)
## Release (release.sh)
Releases any bundles built by `make` on a public AWS S3 bucket.
For information regarding configuration, please view `release.sh`.
## Vendor (vendor.sh)
A shell script that is a wrapper around Vndr. For information on how to use
this, please refer to [vndr's README](https://github.com/LK4D4/vndr/blob/master/README.md)

View File

@ -1,69 +0,0 @@
# Integration Testing on Swarm
IT on Swarm allows you to execute integration test in parallel across a Docker Swarm cluster
## Architecture
### Master service
- Works as a funker caller
- Calls a worker funker (`-worker-service`) with a chunk of `-check.f` filter strings (passed as a file via `-input` flag, typically `/mnt/input`)
### Worker service
- Works as a funker callee
- Executes an equivalent of `TESTFLAGS=-check.f TestFoo|TestBar|TestBaz ... make test-integration-cli` using the bind-mounted API socket (`docker.sock`)
### Client
- Controls master and workers via `docker stack`
- No need to have a local daemon
Typically, the master and workers are supposed to be running on a cloud environment,
while the client is supposed to be running on a laptop, e.g. Docker for Mac/Windows.
## Requirement
- Docker daemon 1.13 or later
- Private registry for distributed execution with multiple nodes
## Usage
### Step 1: Prepare images
$ make build-integration-cli-on-swarm
Following environment variables are known to work in this step:
- `BUILDFLAGS`
- `DOCKER_INCREMENTAL_BINARY`
Note: during the transition into Moby Project, you might need to create a symbolic link `$GOPATH/src/github.com/docker/docker` to `$GOPATH/src/github.com/moby/moby`.
### Step 2: Execute tests
$ ./hack/integration-cli-on-swarm/integration-cli-on-swarm -replicas 40 -push-worker-image YOUR_REGISTRY.EXAMPLE.COM/integration-cli-worker:latest
Following environment variables are known to work in this step:
- `DOCKER_GRAPHDRIVER`
- `DOCKER_EXPERIMENTAL`
#### Flags
Basic flags:
- `-replicas N`: the number of worker service replicas. i.e. degree of parallelism.
- `-chunks N`: the number of chunks. By default, `chunks` == `replicas`.
- `-push-worker-image REGISTRY/IMAGE:TAG`: push the worker image to the registry. Note that if you have only single node and hence you do not need a private registry, you do not need to specify `-push-worker-image`.
Experimental flags for mitigating makespan nonuniformity:
- `-shuffle`: Shuffle the test filter strings
Flags for debugging IT on Swarm itself:
- `-rand-seed N`: the random seed. This flag is useful for deterministic replaying. By default(0), the timestamp is used.
- `-filters-file FILE`: the file contains `-check.f` strings. By default, the file is automatically generated.
- `-dry-run`: skip the actual workload
- `keep-executor`: do not auto-remove executor containers, which is used for running privileged programs on Swarm

View File

@ -1,2 +0,0 @@
# dependencies specific to worker (i.e. github.com/docker/docker/...) are not vendored here
github.com/bfirsh/funker-go eaa0a2e06f30e72c9a0b7f858951e581e26ef773

View File

@ -1,8 +1,8 @@
## [HyperKit](http://github.com/docker/hyperkit)
## [HyperKit](http://github.com/moby/hyperkit)
![Build Status OSX](https://circleci.com/gh/docker/hyperkit.svg?style=shield&circle-token=cf8379b302eab2bbf33821cafe164dbefb71982d)
![Build Status OSX](https://circleci.com/gh/moby/hyperkit.svg?style=shield&circle-token=cf8379b302eab2bbf33821cafe164dbefb71982d)
*HyperKit* is a toolkit for embedding hypervisor capabilities in your application. It includes a complete hypervisor, based on [xhyve](https://github.com/mist64/xhyve)/[bhyve](http://bhyve.org), which is optimized for lightweight virtual machines and container deployment. It is designed to be interfaced with higher-level components such as the [VPNKit](https://github.com/docker/vpnkit) and [DataKit](https://github.com/docker/datakit).
*HyperKit* is a toolkit for embedding hypervisor capabilities in your application. It includes a complete hypervisor, based on [xhyve](https://github.com/mist64/xhyve)/[bhyve](http://bhyve.org), which is optimized for lightweight virtual machines and container deployment. It is designed to be interfaced with higher-level components such as the [VPNKit](https://github.com/moby/vpnkit) and [DataKit](https://github.com/moby/datakit).
HyperKit currently only supports Mac OS X using the [Hypervisor.framework](https://developer.apple.com/library/mac/documentation/DriversKernelHardware/Reference/Hypervisor/index.html). It is a core component of Docker For Mac.
@ -24,7 +24,7 @@ If you are using Hyperkit directly then please report issues against this reposi
## Building
$ git clone https://github.com/docker/hyperkit
$ git clone https://github.com/moby/hyperkit
$ cd hyperkit
$ make
@ -38,7 +38,7 @@ via `brew` and using `opam` to install the appropriate libraries:
$ brew install opam libev
$ opam init
$ eval `opam config env`
$ 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
$ opam install uri qcow.0.10.3 conduit.1.0.0 lwt.3.1.0 qcow-tool mirage-block-unix.2.9.0 conf-libev logs fmt mirage-unix prometheus-app
Notes:

View File

@ -0,0 +1,433 @@
package hyperkit
import (
"errors"
"fmt"
"net/url"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
)
const (
mib = int64(1024 * 1024)
)
/*-------.
| Disk. |
`-------*/
// Disk in an interface for qcow2 and raw disk images.
type Disk interface {
// GetPath returns the location of the disk image file.
GetPath() string
// SetPath changes the location of the disk image file.
SetPath(p string)
// GetSize returns the desired disk size.
GetSize() int
// GetCurrentSize returns the current disk size in MiB.
GetCurrentSize() (int, error)
// String returns the path.
String() string
// Exists iff the disk image file can be stat'd without error.
Exists() bool
// Ensure creates the disk image if needed, and resizes it if needed.
Ensure() error
// Stop can be called when hyperkit has quit. It performs sanity checks, compaction, etc.
Stop() error
// AsArgument returns the command-line option to pass after `-s <slot>:0,` to hyperkit for this disk.
AsArgument() string
create() error
resize() error
}
// DiskFormat describes the physical format of the disk data
type DiskFormat int
const (
// DiskFormatQcow means the disk is a qcow2.
DiskFormatQcow DiskFormat = iota
// DiskFormatRaw means the disk is a raw file.
DiskFormatRaw
)
// GetDiskFormat computes the format based on the path's extensions.
func GetDiskFormat(path string) DiskFormat {
switch ext := filepath.Ext(path); ext {
case ".qcow2":
return DiskFormatQcow
case ".raw", ".img":
return DiskFormatRaw
default:
log.Debugf("Unknown disk extension %q, will use raw format", path)
return DiskFormatRaw
}
}
// NewDisk creates a qcow/raw disk configuration based on the spec.
func NewDisk(spec string, size int) (Disk, error) {
u, err := url.Parse(spec)
if err != nil {
return nil, fmt.Errorf("invalid disk path %q: %v", spec, err)
}
switch path := u.Path; GetDiskFormat(path) {
case DiskFormatRaw:
return &RawDisk{
Path: path,
Size: size,
Trim: true,
}, nil
case DiskFormatQcow:
return &QcowDisk{
Path: path,
Size: size,
}, nil
}
return nil, fmt.Errorf("impossible")
}
// exists iff the image file can be stat'd without error.
func exists(d Disk) bool {
_, err := os.Stat(d.GetPath())
if err != nil && !os.IsNotExist(err) {
log.Debugf("cannot stat %q: %v", d, err)
}
return err == nil
}
// ensure creates the disk image if needed, and resizes it if needed.
func ensure(d Disk) error {
current, err := d.GetCurrentSize()
if err != nil {
if !os.IsNotExist(err) {
return err
}
return d.create()
}
if current < d.GetSize() {
return d.resize()
}
if d.GetSize() < current {
log.Errorf("Cannot safely shrink %q from %dMiB to %dMiB", d, current, d.GetSize())
}
return nil
}
// diskDriver to use.
//
// Dave Scott writes:
//
// > Regarding TRIM and raw disks
// > (https://github.com/docker/pinata/pull/8235/commits/0e2c7c2e21114b4ed61589bd42b720f7d88c0d8e):
// > it works like this: the `ahci-hd` virtual hardware in hyperkit
// > exposes the `ATA_SUPPORT_DSM_TRIM` capability
// > (https://github.com/moby/hyperkit/blob/81fa6279fcb17e8435f3cec0978e9aa3af02e63b/src/lib/pci_ahci.c#L996)
// > if the `fcntl(F_PUNCHHOLE)`
// > (https://github.com/moby/hyperkit/blob/81fa6279fcb17e8435f3cec0978e9aa3af02e63b/src/lib/block_if.c#L276)
// > API works on the raw file (it's dynamically detected so on HFS+ it's
// > disabled and on APFS it's enabled) -> TRIM on raw doesn't need any
// > special flags set in the Go code; the special flags are only for the
// > TRIM on qcow implementation. When images are deleted in the VM the
// > `trim-after-delete`
// > (https://github.com/linuxkit/linuxkit/tree/master/pkg/trim-after-delete)
// > daemon calls `fstrim /var/lib/docker` which causes Linux to emit the
// > TRIM commands to hyperkit, which calls `fcntl`, which tells macOS to
// > free the space in the file, visible in `ls -sl`.
// >
// > Unfortunately the `virtio-blk` protocol doesn't support `TRIM`
// > requests at all so we have to use `ahci-hd` (if you try to run
// > `fstrim /var/lib/docker` with `virtio-blk` it'll give an `ioctl`
// > error).
func diskDriver(trim bool) string {
if trim {
return "ahci-hd"
}
return "virtio-blk"
}
/*----------.
| RawDisk. |
`----------*/
// RawDisk describes a raw disk image file.
type RawDisk struct {
// Path specifies where the image file will be.
Path string `json:"path"`
// Size specifies the size of the disk.
Size int `json:"size"`
// Format is passed as-is to the driver.
Format string `json:"format"`
// Trim specifies whether we should trim the image file.
Trim bool `json:"trim"`
}
// GetPath returns the location of the disk image file.
func (d *RawDisk) GetPath() string {
return d.Path
}
// SetPath changes the location of the disk image file.
func (d *RawDisk) SetPath(p string) {
d.Path = p
}
// GetSize returns the desired disk size.
func (d *RawDisk) GetSize() int {
return d.Size
}
// String returns the path.
func (d *RawDisk) String() string {
return d.Path
}
// Exists iff the image file can be stat's without error.
func (d *RawDisk) Exists() bool {
return exists(d)
}
// Ensure creates the disk image if needed, and resizes it if needed.
func (d *RawDisk) Ensure() error {
return ensure(d)
}
// Create a disk.
func (d *RawDisk) create() error {
log.Infof("Create %q", d)
f, err := os.Create(d.Path)
if err != nil {
return err
}
if err := f.Close(); err != nil {
return err
}
return d.resize()
}
// GetCurrentSize returns the current disk size in MiB.
func (d *RawDisk) GetCurrentSize() (int, error) {
fileinfo, err := os.Stat(d.Path)
if err != nil {
return 0, err
}
return int(fileinfo.Size() / mib), nil
}
// Resize the virtual size of the disk
func (d *RawDisk) resize() error {
s, err := d.GetCurrentSize()
if err != nil {
return fmt.Errorf("Cannot resize %q: %v", d, err)
}
log.Infof("Resize %q from %vMiB to %vMiB", d, s, d.GetSize())
// APFS exhibits a weird behavior wrt sparse files: we cannot
// create (or grow) them "too fast": there's a limit,
// apparently related to the available disk space. However,
// if the additional space is small enough, we can procede way
// beyond the available disk space. So grow incrementally,
// by steps of 1GB.
for s < d.Size {
s += 1000
if d.Size < s {
s = d.Size
}
if err := os.Truncate(d.Path, int64(s)*mib); err != nil {
return fmt.Errorf("Cannot resize %q to %vMiB: %v", d, s, err)
}
}
log.Infof("Resized %q to %vMiB", d, d.GetSize())
return nil
}
// Stop cleans up this disk when we are quitting.
func (d *RawDisk) Stop() error {
return nil
}
// AsArgument returns the command-line option to pass after `-s <slot>:0,` to hyperkit for this disk.
func (d *RawDisk) AsArgument() string {
res := fmt.Sprintf("%s,%s", diskDriver(d.Trim), d.Path)
if d.Format != "" {
res += ",format=" + d.Format
}
return res
}
/*-----------.
| QcowDisk. |
`-----------*/
// QcowDisk describes a qcow2 disk image file.
type QcowDisk struct {
// Path specifies where the image file will be.
Path string `json:"path"`
// Size specifies the size of the disk.
Size int `json:"size"`
// Format is passed as-is to the driver.
Format string `json:"format"`
// Trim specifies whether we should trim the image file.
Trim bool `json:"trim"`
// QcowToolPath is the path to the binary to use to manage this image.
// Defaults to "qcow-tool" when empty.
QcowToolPath string
OnFlush string
CompactAfter int
KeepErased int
RuntimeAsserts bool
Stats string
}
// GetPath returns the location of the disk image file.
func (d *QcowDisk) GetPath() string {
return d.Path
}
// SetPath changes the location of the disk image file.
func (d *QcowDisk) SetPath(p string) {
d.Path = p
}
// GetSize returns the desired disk size.
func (d *QcowDisk) GetSize() int {
return d.Size
}
// String returns the path.
func (d *QcowDisk) String() string {
return d.Path
}
// QcowTool prepares a call to qcow-tool on this image.
func (d *QcowDisk) QcowTool(verb string, args ...string) *exec.Cmd {
if d.QcowToolPath == "" {
d.QcowToolPath = "qcow-tool"
}
return exec.Command(d.QcowToolPath, append([]string{verb, d.Path}, args...)...)
}
func run(cmd *exec.Cmd) (string, error) {
buf, err := cmd.CombinedOutput()
out := string(buf)
log.Debugf("ran %v: out=%q, err=%v", cmd.Args, out, err)
return out, err
}
// Exists iff the image file can be stat'd without error.
func (d *QcowDisk) Exists() bool {
return exists(d)
}
// Ensure creates the disk image if needed, and resizes it if needed.
func (d *QcowDisk) Ensure() error {
if d.Trim {
log.Infof("%v: TRIM is enabled; recycling thread will keep %v sectors free and will compact after %v more sectors are free",
d, d.KeepErased, d.CompactAfter)
}
if d.RuntimeAsserts {
log.Warnf("%v: Expensive runtime checks are enabled", d)
}
return ensure(d)
}
// Create a disk with the given size in MiB
func (d *QcowDisk) create() error {
log.Infof("Create %q", d)
_, err := run(d.QcowTool("create", "--size", fmt.Sprintf("%dMiB", d.Size)))
return err
}
// GetCurrentSize returns the current disk size in MiB.
func (d *QcowDisk) GetCurrentSize() (int, error) {
if _, err := os.Stat(d.Path); err != nil {
return 0, err
}
out, err := run(d.QcowTool("info", "--filter", ".size"))
if err != nil {
return 0, err
}
size, err := strconv.ParseInt(strings.TrimSpace(string(out)), 10, 64)
if err != nil {
return 0, err
}
return int(size / mib), nil
}
func (d *QcowDisk) sizeString() string {
s, err := d.GetCurrentSize()
if err != nil {
return fmt.Sprintf("cannot get size: %v", err)
}
return fmt.Sprintf("%vMiB", s)
}
// Resize the virtual size of the disk
func (d *QcowDisk) resize() error {
log.Infof("Resize %q from %v to %dMiB", d, d.sizeString(), d.GetSize())
_, err := run(d.QcowTool("resize", "--size", fmt.Sprintf("%dMiB", d.Size)))
return err
}
// compact the disk to shrink the physical size.
func (d *QcowDisk) compact() error {
log.Infof("Compact: %q... (%v)", d, d.sizeString())
cmd := d.QcowTool("compact")
if _, err := run(cmd); err != nil {
if err.(*exec.ExitError) != nil {
return errors.New("Failed to compact qcow2")
}
return err
}
log.Infof("Compact: %q: done (%v)", d, d.sizeString())
return nil
}
// check the disk is well-formed.
func (d *QcowDisk) check() error {
cmd := d.QcowTool("check")
if _, err := run(cmd); err != nil {
if err.(*exec.ExitError) != nil {
return errors.New("qcow2 failed integrity check: it may be corrupt")
}
return err
}
return nil
}
// Stop cleans up this disk when we are quitting.
func (d *QcowDisk) Stop() error {
if !d.Trim && d.CompactAfter == 0 {
log.Infof("TRIM is enabled but auto-compaction disabled: compacting %q now", d)
if err := d.compact(); err != nil {
return fmt.Errorf("Failed to compact %q: %v", d, err)
}
if err := d.check(); err != nil {
return fmt.Errorf("Post-compact disk integrity check of %q failed: %v", d, err)
}
log.Infof("Post-compact disk integrity check of %q successful", d)
}
return nil
}
// AsArgument returns the command-line option to pass after `-s <slot>:0,` to hyperkit for this disk.
func (d *QcowDisk) AsArgument() string {
res := fmt.Sprintf("%s,file://%s?sync=%s&buffered=1", diskDriver(d.Trim), d.Path, d.OnFlush)
{
format := d.Format
if format == "" {
format = "qcow"
}
res += fmt.Sprintf(",format=%v", format)
}
if d.Stats != "" {
res += ",qcow-stats-config=" + d.Stats
}
res += fmt.Sprintf(",qcow-config=discard=%t;compact_after_unmaps=%d;keep_erased=%d;runtime_asserts=%t",
d.Trim, d.CompactAfter, d.KeepErased, d.RuntimeAsserts)
return res
}

View File

@ -27,12 +27,10 @@ import (
"fmt"
"io"
"io/ioutil"
"log"
"net"
"os"
"os/exec"
"os/user"
"path"
"path/filepath"
"strconv"
"strings"
@ -70,18 +68,13 @@ type Socket9P struct {
Tag string `json:"tag"`
}
// DiskConfig contains the path to a disk image and an optional size if the image needs to be created.
type DiskConfig struct {
Path string `json:"path"`
Size int `json:"size"`
Format string `json:"format"`
Driver string `json:"driver"`
}
// HyperKit contains the configuration of the hyperkit VM
type HyperKit struct {
// HyperKit is the path to the hyperkit binary
// HyperKit is the path to the hyperkit binary.
HyperKit string `json:"hyperkit"`
// Argv0 is the name to declare as argv[0]. If left empty, argv[0] is left untouched.
Argv0 string `json:"argv0"`
// StateDir is the directory where runtime state is kept. If left empty, no state will be kept.
StateDir string `json:"state_dir"`
// VPNKitSock is the location of the VPNKit socket used for networking.
@ -93,32 +86,35 @@ type HyperKit struct {
// UUID is a string containing a UUID, it sets BIOS DMI UUID for the VM (as found in /sys/class/dmi/id/product_uuid on Linux).
UUID string `json:"uuid"`
// Disks contains disk images to use/create.
Disks []DiskConfig `json:"disks"`
// ISOImage is the (optional) path to a ISO image to attach
Disks []Disk `json:"disks"`
// ISOImage is the (optional) path to a ISO image to attach.
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"`
// VSockPorts is a list of guest VSock ports that should be exposed as sockets on the host
// VSockDir specifies where the unix domain sockets will be created. Defaults to StateDir when empty.
VSockDir string `json:"vsock_dir"`
// VSockPorts is a list of guest VSock ports that should be exposed as sockets on the host.
VSockPorts []int `json:"vsock_ports"`
// VSock guest CID
VSockGuestCID int `json:"vsock_guest_cid"`
// VMNet whether to create vmnet network
// VMNet is whether to create vmnet network.
VMNet bool `json:"vmnet"`
// 9P sockets
// Sockets9P holds the 9P sockets.
Sockets9P []Socket9P `json:"9p_sockets"`
// Kernel is the path to the kernel image to boot
// Kernel is the path to the kernel image to boot.
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"`
// Bootrom is the path to a boot rom eg for UEFI boot
// 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"`
// Memory is the amount of megabytes of memory for the VM
// Memory is the amount of megabytes of memory for the VM.
Memory int `json:"memory"`
// Console defines where the console of the VM should be
@ -135,9 +131,7 @@ type HyperKit struct {
// CmdLine is a single string of the command line
CmdLine string `json:"cmdline"`
process *os.Process
background bool
log *log.Logger
process *os.Process
}
// New creates a template config structure.
@ -169,58 +163,40 @@ func New(hyperkit, vpnkitsock, statedir string) (*HyperKit, error) {
return &h, nil
}
// FromState reads a json file from statedir and populates a HyperKit structure.
func FromState(statedir string) (*HyperKit, error) {
b, err := ioutil.ReadFile(filepath.Join(statedir, jsonFile))
if err != nil {
return nil, fmt.Errorf("Can't read json file: %s", err)
}
h := &HyperKit{}
err = json.Unmarshal(b, h)
if err != nil {
return nil, fmt.Errorf("Can't parse json file: %s", err)
}
// Make sure the pid written by hyperkit is the same as in the json
d, err := ioutil.ReadFile(filepath.Join(statedir, pidFile))
if err != nil {
return nil, err
}
pid, err := strconv.Atoi(string(d[:]))
if err != nil {
return nil, err
}
if h.Pid != pid {
return nil, fmt.Errorf("pids do not match %d != %d", h.Pid, pid)
}
h.process, err = os.FindProcess(h.Pid)
if err != nil {
return nil, err
}
return h, nil
}
// SetLogger sets the log instance to use for the output of the hyperkit process itself (not the console of the VM).
// This is only relevant when Console is set to ConsoleFile
func (h *HyperKit) SetLogger(logger *log.Logger) {
h.log = logger
}
// Run the VM with a given command line until it exits
// Run the VM with a given command line until it exits.
func (h *HyperKit) Run(cmdline string) error {
h.background = false
return h.execute(cmdline)
errCh, err := h.Start(cmdline)
if err != nil {
return err
}
return <-errCh
}
// Start the VM with a given command line in the background
func (h *HyperKit) Start(cmdline string) error {
h.background = true
return h.execute(cmdline)
// Start the VM with a given command line in the background. On
// success, returns a channel on which the result of Wait'ing for
// hyperkit is sent. Return failures to start the process.
func (h *HyperKit) Start(cmdline string) (chan error, error) {
log.Debugf("hyperkit: Start %#v", h)
if err := h.check(); err != nil {
return nil, err
}
h.buildArgs(cmdline)
cmd, err := h.execute()
if err != nil {
return nil, err
}
errCh := make(chan error, 1)
// Make sure we reap the child when it exits
go func() {
log.Debugf("hyperkit: Waiting for %#v", cmd)
errCh <- cmd.Wait()
}()
return errCh, nil
}
func (h *HyperKit) execute(cmdline string) error {
// check validates `h`. It also creates the disks if needed.
func (h *HyperKit) check() error {
log.Debugf("hyperkit: check %#v", h)
var err error
// Sanity checks on configuration
if h.Console == ConsoleFile && h.StateDir == "" {
@ -234,12 +210,18 @@ func (h *HyperKit) execute(cmdline string) error {
return fmt.Errorf("ISO %s does not exist", image)
}
}
if h.VSock && h.StateDir == "" {
return fmt.Errorf("If virtio-sockets are enabled, StateDir must be specified")
}
if !h.VSock && len(h.VSockPorts) > 0 {
return fmt.Errorf("To forward vsock ports vsock must be enabled")
if h.VSock {
if h.VSockDir == "" {
if h.StateDir == "" {
return fmt.Errorf("If virtio-sockets are enabled, VSockDir or StateDir must be specified")
}
h.VSockDir = h.StateDir
}
} else if len(h.VSockPorts) > 0 {
return fmt.Errorf("To forward vsock ports VSock must be enabled")
}
if h.Bootrom == "" {
if _, err = os.Stat(h.Kernel); os.IsNotExist(err) {
return fmt.Errorf("Kernel %s does not exist", h.Kernel)
@ -258,44 +240,30 @@ func (h *HyperKit) execute(cmdline string) error {
}
}
// Create files
if h.StateDir != "" {
err = os.MkdirAll(h.StateDir, 0755)
if err != nil {
return err
// Create directories.
for _, dir := range []string{h.VSockDir, h.StateDir} {
if dir != "" {
if err := os.MkdirAll(dir, 0755); err != nil {
return fmt.Errorf("Cannot create directory %q: %v", dir, err)
}
}
}
for idx, config := range h.Disks {
if config.Path == "" {
for idx, disk := range h.Disks {
if disk.GetPath() == "" {
if h.StateDir == "" {
return fmt.Errorf("Unable to create disk image when neither path nor state dir is set")
}
if config.Size <= 0 {
if disk.GetSize() <= 0 {
return fmt.Errorf("Unable to create disk image when size is 0 or not set")
}
config.Path = fmt.Sprintf(filepath.Clean(filepath.Join(h.StateDir, "disk%02d.img")), idx)
h.Disks[idx] = config
disk.SetPath(filepath.Clean(filepath.Join(h.StateDir, fmt.Sprintf("disk%02d.img", idx))))
h.Disks[idx] = disk
}
if _, err = os.Stat(config.Path); os.IsNotExist(err) {
if config.Size != 0 {
err = CreateDiskImage(config.Path, config.Size)
if err != nil {
return err
}
} else {
return fmt.Errorf("Disk image %s not found and unable to create it as size is not specified", config.Path)
}
if err := disk.Ensure(); err != nil {
return err
}
}
// Run
h.buildArgs(cmdline)
err = h.execHyperKit()
if err != nil {
return err
}
return nil
}
@ -333,8 +301,8 @@ func (h *HyperKit) IsRunning() bool {
// isDisk checks if the specified path is used as a disk image
func (h *HyperKit) isDisk(path string) bool {
for _, config := range h.Disks {
if filepath.Clean(path) == filepath.Clean(config.Path) {
for _, disk := range h.Disks {
if filepath.Clean(path) == filepath.Clean(disk.GetPath()) {
return true
}
}
@ -360,12 +328,10 @@ func (h *HyperKit) Remove(keepDisk bool) error {
files, _ := ioutil.ReadDir(h.StateDir)
for _, f := range files {
fn := filepath.Clean(filepath.Join(h.StateDir, f.Name()))
if h.isDisk(fn) {
continue
}
err := os.Remove(fn)
if err != nil {
return err
if !h.isDisk(fn) {
if err := os.Remove(fn); err != nil {
return err
}
}
}
return nil
@ -380,24 +346,6 @@ func (h *HyperKit) String() string {
return string(s)
}
// CreateDiskImage creates a empty file suitable for use as a disk image for a hyperkit VM.
func CreateDiskImage(location string, sizeMB int) error {
diskDir := path.Dir(location)
if diskDir != "." {
if err := os.MkdirAll(diskDir, 0755); err != nil {
return err
}
}
f, err := os.Create(location)
if err != nil {
return err
}
defer f.Close()
return f.Truncate(int64(sizeMB) * int64(1024) * int64(1024))
}
func intArrayToString(i []int, sep string) string {
if len(i) == 0 {
return ""
@ -445,24 +393,13 @@ func (h *HyperKit) buildArgs(cmdline string) {
a = append(a, "-U", h.UUID)
}
for _, p := range h.Disks {
// 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)
for _, disk := range h.Disks {
a = append(a, "-s", fmt.Sprintf("%d:0,%s", nextSlot, disk.AsArgument()))
nextSlot++
}
if h.VSock {
l := fmt.Sprintf("%d,virtio-sock,guest_cid=%d,path=%s", nextSlot, h.VSockGuestCID, h.StateDir)
l := fmt.Sprintf("%d,virtio-sock,guest_cid=%d,path=%s", nextSlot, h.VSockGuestCID, h.VSockDir)
if len(h.VSockPorts) > 0 {
l = fmt.Sprintf("%s,guest_forwards=%s", l, intArrayToString(h.VSockPorts, ";"))
}
@ -499,12 +436,18 @@ func (h *HyperKit) buildArgs(cmdline string) {
h.Arguments = a
h.CmdLine = h.HyperKit + " " + strings.Join(a, " ")
log.Debugf("hyperkit: Arguments: %#v", h.Arguments)
log.Debugf("hyperkit: CmdLine: %#v", h.CmdLine)
}
// Execute hyperkit and plumb stdin/stdout/stderr.
func (h *HyperKit) execHyperKit() error {
// execute forges the command to run hyperkit, runs and returns it.
// It also plumbs stdin/stdout/stderr.
func (h *HyperKit) execute() (*exec.Cmd, error) {
cmd := exec.Command(h.HyperKit, h.Arguments...)
if h.Argv0 != "" {
cmd.Args[0] = h.Argv0
}
cmd.Env = os.Environ()
// Plumb in stdin/stdout/stderr.
@ -528,15 +471,13 @@ func (h *HyperKit) execHyperKit() error {
go func() {
ttyPath := fmt.Sprintf("%s/tty", h.StateDir)
var tty *os.File
var err error
for {
var err error
tty, err = os.OpenFile(ttyPath, os.O_RDONLY, 0)
if err != nil {
time.Sleep(10 * 1000 * 1000 * time.Nanosecond)
continue
} else {
if err == nil {
break
}
time.Sleep(10 * time.Millisecond)
}
saneTerminal(tty)
setRaw(tty)
@ -544,16 +485,17 @@ func (h *HyperKit) execHyperKit() error {
tty.Close()
}()
}
} else if h.log != nil {
} else if log != nil {
log.Debugf("hyperkit: Redirecting stdout/stderr to logger")
stdoutChan := make(chan string)
stderrChan := make(chan string)
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
return nil, err
}
stderr, err := cmd.StderrPipe()
if err != nil {
return err
return nil, err
}
stream(stdout, stdoutChan)
stream(stderr, stderrChan)
@ -563,9 +505,9 @@ func (h *HyperKit) execHyperKit() error {
for {
select {
case stderrl := <-stderrChan:
log.Printf("%s", stderrl)
log.Infof("%s", stderrl)
case stdoutl := <-stdoutChan:
log.Printf("%s", stdoutl)
log.Infof("%s", stdoutl)
case <-done:
return
}
@ -573,27 +515,21 @@ func (h *HyperKit) execHyperKit() error {
}()
}
log.Debugf("hyperkit: Starting %#v", cmd)
err := cmd.Start()
if err != nil {
return err
return nil, err
}
h.Pid = cmd.Process.Pid
log.Debugf("hyperkit: Pid is %v", h.Pid)
h.process = cmd.Process
err = h.writeState()
if err != nil {
log.Debugf("hyperkit: Cannot write state: %v, killing %v", err, h.Pid)
h.process.Kill()
return err
return nil, err
}
if !h.background {
err = cmd.Wait()
if err != nil {
return err
}
} else {
// Make sure we reap the child when it exits
go cmd.Wait()
}
return nil
return cmd, nil
}
// writeState write the state to a JSON file

View File

@ -0,0 +1,57 @@
package hyperkit
import (
"fmt"
golog "log"
)
// Logger is an interface for logging.
type Logger interface {
// Debugf logs a message with "debug" severity (very verbose).
Debugf(format string, v ...interface{})
// Infof logs a message with "info" severity (less verbose).
Infof(format string, v ...interface{})
// Warnf logs a message with "warn" (non-fatal) severity.
Warnf(format string, v ...interface{})
// Errorf logs an (non-fatal) error.
Errorf(format string, v ...interface{})
// Fatalf logs a fatal error message, and exits 1.
Fatalf(format string, v ...interface{})
}
// StandardLogger makes the go standard logger comply to our Logger interface.
type StandardLogger struct{}
// Debugf logs a message with "debug" severity.
func (*StandardLogger) Debugf(f string, v ...interface{}) {
golog.Printf("DEBUG: %v", fmt.Sprintf(f, v...))
}
// Infof logs a message with "info" severity.
func (*StandardLogger) Infof(f string, v ...interface{}) {
golog.Printf("INFO : %v", fmt.Sprintf(f, v...))
}
// Warnf logs a message with "warn" (non-fatal) severity.
func (*StandardLogger) Warnf(f string, v ...interface{}) {
golog.Printf("WARN : %v", fmt.Sprintf(f, v...))
}
// Errorf logs an (non-fatal) error.
func (*StandardLogger) Errorf(f string, v ...interface{}) {
golog.Printf("ERROR: %v", fmt.Sprintf(f, v...))
}
// Fatalf logs a fatal error message, and exits 1.
func (*StandardLogger) Fatalf(f string, v ...interface{}) {
golog.Fatalf("FATAL: %v", fmt.Sprintf(f, v...))
}
// Log receives stdout/stderr of the hyperkit process itself, if set.
// It defaults to the go standard logger.
var log Logger = &StandardLogger{}
// SetLogger sets the logger to use.
func SetLogger(l Logger) {
log = l
}

View File

@ -3,22 +3,21 @@
package hyperkit
import (
"log"
"os"
)
func saneTerminal(f *os.File) error {
log.Fatal("Function not supported on your OS")
log.Fatalf("Function not supported on your OS")
return nil
}
func setRaw(f *os.File) error {
log.Fatal("Function not supported on your OS")
log.Fatalf("Function not supported on your OS")
return nil
}
// isTerminal checks if the provided file is a terminal
func isTerminal(f *os.File) bool {
log.Fatal("Function not supported on your OS")
log.Fatalf("Function not supported on your OS")
return false
}

View File

@ -697,7 +697,12 @@ blockif_open(const char *optstr, const char *ident)
perror("Could not open backing file");
goto err;
}
/* Lock the file to prevent concurrent write+write or read+write as this
would usually lead to corruption */
if (flock(fd, LOCK_NB | (ro ? LOCK_SH : LOCK_EX)) < 0) {
perror("Could not lock backing file");
goto err;
}
if (fstat(fd, &sbuf) < 0) {
perror("Could not stat backing file");
goto err;