diff --git a/src/cmd/linuxkit/run_hyperkit.go b/src/cmd/linuxkit/run_hyperkit.go index df4376b49..5897a90b5 100644 --- a/src/cmd/linuxkit/run_hyperkit.go +++ b/src/cmd/linuxkit/run_hyperkit.go @@ -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) } diff --git a/src/cmd/linuxkit/vendor.conf b/src/cmd/linuxkit/vendor.conf index ebac3d40a..4efa34839 100644 --- a/src/cmd/linuxkit/vendor.conf +++ b/src/cmd/linuxkit/vendor.conf @@ -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 diff --git a/src/cmd/linuxkit/vendor/github.com/docker/docker/hack/README.md b/src/cmd/linuxkit/vendor/github.com/docker/docker/hack/README.md deleted file mode 100644 index 9e588db25..000000000 --- a/src/cmd/linuxkit/vendor/github.com/docker/docker/hack/README.md +++ /dev/null @@ -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) diff --git a/src/cmd/linuxkit/vendor/github.com/docker/docker/hack/integration-cli-on-swarm/README.md b/src/cmd/linuxkit/vendor/github.com/docker/docker/hack/integration-cli-on-swarm/README.md deleted file mode 100644 index 1cea52526..000000000 --- a/src/cmd/linuxkit/vendor/github.com/docker/docker/hack/integration-cli-on-swarm/README.md +++ /dev/null @@ -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 diff --git a/src/cmd/linuxkit/vendor/github.com/docker/docker/hack/integration-cli-on-swarm/agent/vendor.conf b/src/cmd/linuxkit/vendor/github.com/docker/docker/hack/integration-cli-on-swarm/agent/vendor.conf deleted file mode 100644 index efd6d6d04..000000000 --- a/src/cmd/linuxkit/vendor/github.com/docker/docker/hack/integration-cli-on-swarm/agent/vendor.conf +++ /dev/null @@ -1,2 +0,0 @@ -# dependencies specific to worker (i.e. github.com/docker/docker/...) are not vendored here -github.com/bfirsh/funker-go eaa0a2e06f30e72c9a0b7f858951e581e26ef773 diff --git a/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/README.md b/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/README.md index 74f6feb87..3c9546506 100644 --- a/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/README.md +++ b/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/README.md @@ -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: diff --git a/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/go/disk.go b/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/go/disk.go new file mode 100644 index 000000000..0d9f2550a --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/go/disk.go @@ -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 :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 :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 :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 +} diff --git a/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/go/hyperkit.go b/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/go/hyperkit.go index 9cb38b1a2..9efb88396 100644 --- a/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/go/hyperkit.go +++ b/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/go/hyperkit.go @@ -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 diff --git a/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/go/log.go b/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/go/log.go new file mode 100644 index 000000000..93b4e527e --- /dev/null +++ b/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/go/log.go @@ -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 +} diff --git a/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/go/pty_util_fallback.go b/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/go/pty_util_fallback.go index aff274cc5..d00d39495 100644 --- a/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/go/pty_util_fallback.go +++ b/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/go/pty_util_fallback.go @@ -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 } diff --git a/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/src/lib/block_if.c b/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/src/lib/block_if.c index cce6ef185..c8fcf669e 100644 --- a/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/src/lib/block_if.c +++ b/src/cmd/linuxkit/vendor/github.com/moby/hyperkit/src/lib/block_if.c @@ -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;