├── .gitignore ├── LICENSE ├── Makefile ├── Makefile.inc ├── README.md ├── cmd └── main.go ├── go.mod ├── go.sum ├── mk ├── build.mk ├── coverage.mk ├── dev.mk ├── main.mk ├── test.mk └── validate.mk ├── qemu.go └── version └── version.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | bin/ 3 | *.o 4 | *.a 5 | *.so 6 | 7 | # Folders 8 | _obj 9 | _test 10 | 11 | # Architecture specific extensions/prefixes 12 | *.[568vq] 13 | [568vq].out 14 | 15 | *.cgo1.go 16 | *.cgo2.c 17 | _cgo_defun.c 18 | _cgo_gotypes.go 19 | _cgo_export.* 20 | 21 | _testmain.go 22 | 23 | *.exe 24 | *.test 25 | *.prof 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Plain make targets if not requested inside a container 2 | ifneq (,$(findstring test-integration,$(MAKECMDGOALS))) 3 | include Makefile.inc 4 | include mk/main.mk 5 | else ifneq ($(USE_CONTAINER), true) 6 | include Makefile.inc 7 | include mk/main.mk 8 | else 9 | # Otherwise, with docker, swallow all targets and forward into a container 10 | DOCKER_BUILD_DONE := "" 11 | 12 | test: .DEFAULT 13 | 14 | .DEFAULT: 15 | @test ! -z "$(DOCKER_BUILD_DONE)" || ./script/build_in_container.sh $(MAKECMDGOALS) 16 | $(eval DOCKER_BUILD_DONE := "done") 17 | 18 | endif 19 | -------------------------------------------------------------------------------- /Makefile.inc: -------------------------------------------------------------------------------- 1 | # Project name, used to name the binaries 2 | PKG_NAME := docker-machine-driver-qemu 3 | 4 | # If true, disable optimizations and does NOT strip the binary 5 | DEBUG ?= 6 | # If true, "build" will produce a static binary (cross compile always produce static build regardless) 7 | STATIC ?= 8 | # If true, turn on verbose output for build 9 | VERBOSE ?= 10 | # Build tags 11 | BUILDTAGS ?= 12 | # Adjust number of parallel builds (XXX not used) 13 | PARALLEL ?= -1 14 | # Coverage default directory 15 | COVERAGE_DIR ?= cover 16 | # Whether to perform targets inside a docker container, or natively on the host 17 | USE_CONTAINER ?= 18 | 19 | # List of cross compilation targets 20 | ifeq ($(TARGET_OS),) 21 | TARGET_OS := darwin linux windows 22 | endif 23 | 24 | ifeq ($(TARGET_ARCH),) 25 | TARGET_ARCH := amd64 arm arm64 386 26 | endif 27 | 28 | # Output prefix, defaults to local directory if not specified 29 | ifeq ($(PREFIX),) 30 | PREFIX := $(shell pwd) 31 | endif 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Docker machine qemu driver 2 | 3 | I needed a non-libvirt qemu driver, so this is it. 4 | 5 | 6 | from @SvenDowideit 7 | 8 | Its initial use is going to be for running the [Rancher OS](https://github.com/rancher/os) tests, but maybe you'll find a use for it too. 9 | 10 | 11 | from @fventuri 12 | 13 | #### QEMU 14 | 15 | Create machines locally using [QEMU](http://www.qemu.org/). 16 | This driver requires QEMU to be installed on your host. 17 | 18 | $ docker-machine create --driver=qemu qemu-test 19 | 20 | Options: 21 | 22 | - `--qemu-boot2docker-url`: The URL of the boot2docker image. Defaults to the latest available version. 23 | - `--qemu-disk-size`: Size of disk for the host in MB. Default: `20000` 24 | - `--qemu-memory`: Size of memory for the host in MB. Default: `1024` 25 | - `--qemu-cpu-count`: Number of CPUs. Default: `1` 26 | - `--qemu-program` : Name of the qemu program to run. Default: `qemu-system-x86_64` 27 | - `--qemu-display` : Show the graphical display output to the user. Default: false 28 | - `--qemu-display-type` : Select type of display to use (sdl/vnc=localhost:0/etc) 29 | - `--qemu-nographic` : Use -nographic instead of -display none. Default: false 30 | - `--qemu-virtio-drives` : Use virtio for drives (cdrom and disk). Default: false 31 | - `--qemu-network`: Networking to be used: user, tap or bridge. Default: `user` 32 | - `--qemu-network-interface`: Name of the network interface to be used for networking. Default: `tap0` 33 | - `--qemu-network-address`: IP of the network address to be used for networking. 34 | - `--qemu-network-bridge`: Name of the network bridge to be used for networking. Default: `br0` 35 | 36 | The `--qemu-boot2docker-url` flag takes a few different forms. By 37 | default, if no value is specified for this flag, Machine will check locally for 38 | a boot2docker ISO. If one is found, that will be used as the ISO for the 39 | created machine. If one is not found, the latest ISO release available on 40 | [boot2docker/boot2docker](https://github.com/boot2docker/boot2docker) will be 41 | downloaded and stored locally for future use. Note that this means you must run 42 | `docker-machine upgrade` deliberately on a machine if you wish to update the "cached" 43 | boot2docker ISO. 44 | 45 | This is the default behavior (when `--qemu-boot2docker-url=""`), but the 46 | option also supports specifying ISOs by the `http://` and `file://` protocols. 47 | `file://` will look at the path specified locally to locate the ISO: for 48 | instance, you could specify `--qemu-boot2docker-url 49 | file://$HOME/Downloads/rc.iso` to test out a release candidate ISO that you have 50 | downloaded already. You could also just get an ISO straight from the Internet 51 | using the `http://` form. 52 | 53 | Note that when using virtio the drives will be mounted as `/dev/vda` and `/dev/vdb`, 54 | instead of the usual `/dev/cdrom` and `/dev/sda`, since they are using paravirtualization. 55 | 56 | If using the real network (tap or bridge), note that it needs a DHCP server running. 57 | The user network has it's own NAT network, which usually means it is running on 10.0.2.15 58 | Ultimately this driver should be able to query for IP, but for now the workaround is 59 | to use `--qemu-network-address` (and fixed addresses) until that feature is implemented. 60 | 61 | Environment variables: 62 | 63 | Here comes the list of the supported variables with the corresponding options. If both environment 64 | variable and CLI option are provided the CLI option takes the precedence. 65 | 66 | | Environment variable | CLI option | 67 | |-----------------------------------|-----------------------------------| 68 | | `QEMU_BOOT2DOCKER_URL` | `--qemu-boot2docker-url` | 69 | | `QEMU_VIRTIO_DRIVES` | `--qemu-virtio-drives` | 70 | -------------------------------------------------------------------------------- /cmd/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/docker/machine/libmachine/drivers/plugin" 5 | "github.com/machine-drivers/docker-machine-driver-qemu" 6 | ) 7 | 8 | func main() { 9 | plugin.RegisterDriver(qemu.NewDriver("default", "path")) 10 | } 11 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/machine-drivers/docker-machine-driver-qemu 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect 7 | github.com/docker/docker v0.0.0-20180621001606-093424bec097 // indirect 8 | github.com/docker/machine v0.16.2 9 | github.com/sirupsen/logrus v1.0.4 // indirect 10 | golang.org/x/crypto v0.0.0-20170704135851-51714a8c4ac1 // indirect 11 | golang.org/x/sys v0.0.0-20180202135801-37707fdb30a5 // indirect 12 | ) 13 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= 2 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= 3 | github.com/docker/docker v0.0.0-20180621001606-093424bec097 h1:HkcfUIW2Fg4Jf0Gz3Cjr/YuiU12XrksJZO+dCKx5ols= 4 | github.com/docker/docker v0.0.0-20180621001606-093424bec097/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= 5 | github.com/docker/machine v0.16.2 h1:jyF9k3Zg+oIGxxSdYKPScyj3HqFZ6FjgA/3sblcASiU= 6 | github.com/docker/machine v0.16.2/go.mod h1:I8mPNDeK1uH+JTcUU7X0ZW8KiYz0jyAgNaeSJ1rCfDI= 7 | github.com/sirupsen/logrus v1.0.4 h1:gzbtLsZC3Ic5PptoRG+kQj4L60qjK7H7XszrU163JNQ= 8 | github.com/sirupsen/logrus v1.0.4/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= 9 | golang.org/x/crypto v0.0.0-20170704135851-51714a8c4ac1 h1:HJSvIvK9iXhheeDr8hbIfZ0HWmqeVmv6cfxZV5Ot8AI= 10 | golang.org/x/crypto v0.0.0-20170704135851-51714a8c4ac1/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 11 | golang.org/x/sys v0.0.0-20180202135801-37707fdb30a5 h1:MF92a0wJ3gzSUVBpjcwdrDr5+klMFRNEEu6Mev4n00I= 12 | golang.org/x/sys v0.0.0-20180202135801-37707fdb30a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 13 | -------------------------------------------------------------------------------- /mk/build.mk: -------------------------------------------------------------------------------- 1 | extension = $(patsubst windows,.exe,$(filter windows,$(1))) 2 | 3 | # Valid target combinations 4 | VALID_OS_ARCH := "[darwin/amd64][linux/amd64][linux/arm][linux/arm64][windows/amd64][windows/386]" 5 | 6 | os.darwin := Darwin 7 | os.linux := Linux 8 | os.windows := Windows 9 | 10 | arch.amd64 := x86_64 11 | arch.arm := armhf 12 | arch.arm64 := aarch64 13 | arch.386 := i386 14 | 15 | define gocross 16 | $(if $(findstring [$(1)/$(2)],$(VALID_OS_ARCH)), \ 17 | GOOS=$(1) GOARCH=$(2) CGO_ENABLED=0 \ 18 | $(GO) build \ 19 | -o $(PREFIX)/bin/$(PKG_NAME)-${os.$(1)}-${arch.$(2)}$(call extension,$(GOOS)) \ 20 | -a $(VERBOSE_GO) -tags "static_build netgo $(BUILDTAGS)" -installsuffix netgo \ 21 | -ldflags "$(GO_LDFLAGS) -extldflags -static" $(GO_GCFLAGS) ./cmd/main.go;) 22 | endef 23 | 24 | build-clean: 25 | rm -Rf $(PREFIX)/bin/* 26 | 27 | build-x: $(shell find . -type f -name '*.go') 28 | $(foreach GOARCH,$(TARGET_ARCH),$(foreach GOOS,$(TARGET_OS),$(call gocross,$(GOOS),$(GOARCH)))) 29 | 30 | $(PREFIX)/bin/$(PKG_NAME)$(call extension,$(GOOS)): $(shell find . -type f -name '*.go') 31 | $(GO) build \ 32 | -o $@ \ 33 | $(VERBOSE_GO) -tags "$(BUILDTAGS)" \ 34 | -ldflags "$(GO_LDFLAGS)" $(GO_GCFLAGS) ./cmd/main.go 35 | 36 | build: $(PREFIX)/bin/$(PKG_NAME)$(call extension,$(GOOS)) 37 | -------------------------------------------------------------------------------- /mk/coverage.mk: -------------------------------------------------------------------------------- 1 | # COVERAGE_OUTPUT dir is a temp dir (OSX/Linux compatible), unless explicitly specified through env COVERAGE_DIR 2 | COVERAGE_OUTPUT := $(COVERAGE_DIR) 3 | ifeq ($(COVERAGE_OUTPUT),) 4 | COVERAGE_OUTPUT := $(shell mktemp -d 2>/dev/null || mktemp -d -t machine-coverage) 5 | endif 6 | 7 | # Final cover file, html, and mode 8 | COVERAGE_PROFILE := $(COVERAGE_OUTPUT)/profile.out 9 | COVERAGE_HTML := $(COVERAGE_OUTPUT)/index.html 10 | COVERAGE_MODE := set 11 | 12 | # Goveralls dependency 13 | GOVERALLS_BIN := $(GOPATH)/bin/goveralls 14 | GOVERALLS := $(shell [ -x $(GOVERALLS_BIN) ] && echo $(GOVERALLS_BIN) || echo '') 15 | 16 | # Generate coverage 17 | coverage-generate: $(COVERAGE_PROFILE) 18 | 19 | # Send the results to coveralls 20 | coverage-send: $(COVERAGE_PROFILE) 21 | $(if $(GOVERALLS), , $(error Please install goveralls: go get github.com/mattn/goveralls)) 22 | @$(GOVERALLS) -service travis-ci -coverprofile="$(COVERAGE_PROFILE)" 23 | 24 | # Generate html report 25 | coverage-html: $(COVERAGE_HTML) 26 | @open "$(COVERAGE_HTML)" 27 | 28 | # Serve over http - useful only if building remote/headless 29 | coverage-serve: $(COVERAGE_HTML) 30 | @cd "$(COVERAGE_OUTPUT)" && python -m SimpleHTTPServer 8000 31 | 32 | # Clean up coverage coverage output 33 | coverage-clean: 34 | @rm -Rf "$(COVERAGE_OUTPUT)/coverage" 35 | @rm -f "$(COVERAGE_HTML)" 36 | @rm -f "$(COVERAGE_PROFILE)" 37 | 38 | $(COVERAGE_PROFILE): $(shell find . -type f -name '*.go') 39 | @mkdir -p "$(COVERAGE_OUTPUT)/coverage" 40 | @$(foreach PKG,$(PKGS), go test $(VERBOSE_GO) -tags "$(BUILDTAGS)" -covermode=$(COVERAGE_MODE) -coverprofile="$(COVERAGE_OUTPUT)/coverage/`echo $(PKG) | tr "/" "-"`.cover" "$(PKG)";) 41 | @echo "mode: $(COVERAGE_MODE)" > "$(COVERAGE_PROFILE)" 42 | @grep -h -v "^mode:" "$(COVERAGE_OUTPUT)/coverage"/*.cover >> "$(COVERAGE_PROFILE)" 43 | 44 | $(COVERAGE_HTML): $(COVERAGE_PROFILE) 45 | $(GO) tool cover -html="$(COVERAGE_PROFILE)" -o "$(COVERAGE_HTML)" 46 | -------------------------------------------------------------------------------- /mk/dev.mk: -------------------------------------------------------------------------------- 1 | dep-save: 2 | $(if $(GODEP), , \ 3 | $(error Please install godep: go get github.com/tools/godep)) 4 | $(GODEP) save $(shell go list ./... | grep -v vendor/) 5 | 6 | dep-restore: 7 | $(if $(GODEP), , \ 8 | $(error Please install godep: go get github.com/tools/godep)) 9 | $(GODEP) restore -v 10 | -------------------------------------------------------------------------------- /mk/main.mk: -------------------------------------------------------------------------------- 1 | # Initialize version and gc flags 2 | GO_LDFLAGS := -X `go list ./version`.GitCommit=`git rev-parse --short HEAD 2>/dev/null` 3 | GO_GCFLAGS := 4 | 5 | # Full package list 6 | PKGS := $(shell go list -tags "$(BUILDTAGS)" ./... | grep -v "/vendor/" | grep -v "/cmd") 7 | 8 | # Resolving binary dependencies for specific targets 9 | GOLINT_BIN := $(GOPATH)/bin/golint 10 | GOLINT := $(shell [ -x $(GOLINT_BIN) ] && echo $(GOLINT_BIN) || echo '') 11 | 12 | GODEP_BIN := $(GOPATH)/bin/godep 13 | GODEP := $(shell [ -x $(GODEP_BIN) ] && echo $(GODEP_BIN) || echo '') 14 | 15 | # Honor debug 16 | ifeq ($(DEBUG),true) 17 | # Disable function inlining and variable registerization 18 | GO_GCFLAGS := -gcflags "-N -l" 19 | else 20 | # Turn of DWARF debugging information and strip the binary otherwise 21 | GO_LDFLAGS := $(GO_LDFLAGS) -w -s 22 | endif 23 | 24 | # Honor static 25 | ifeq ($(STATIC),true) 26 | # Append to the version 27 | GO_LDFLAGS := $(GO_LDFLAGS) -extldflags -static 28 | endif 29 | 30 | # Honor verbose 31 | VERBOSE_GO := 32 | GO := go 33 | ifeq ($(VERBOSE),true) 34 | VERBOSE_GO := -v 35 | endif 36 | 37 | include mk/build.mk 38 | include mk/coverage.mk 39 | include mk/dev.mk 40 | include mk/test.mk 41 | include mk/validate.mk 42 | 43 | .all_build: build build-clean build-x 44 | .all_coverage: coverage-generate coverage-html coverage-send coverage-serve coverage-clean 45 | .all_release: release-checksum release 46 | .all_test: test-short test-long test-integration 47 | .all_validate: dco fmt vet lint 48 | 49 | default: build 50 | 51 | install: 52 | cp $(PREFIX)/bin/$(PKG_NAME) /usr/local/bin 53 | 54 | clean: coverage-clean build-clean 55 | test: dco fmt test-short lint vet 56 | validate: dco fmt lint vet test-long 57 | 58 | .PHONY: .all_build .all_coverage .all_release .all_test .all_validate test build validate clean 59 | -------------------------------------------------------------------------------- /mk/test.mk: -------------------------------------------------------------------------------- 1 | # Quick test. You can bypass long tests using: `if testing.Short() { t.Skip("Skipping in short mode.") }` 2 | test-short: 3 | $(GO) test $(VERBOSE_GO) -test.short -tags "$(BUILDTAGS)" $(PKGS) 4 | 5 | # Runs long tests also, plus race detection 6 | test-long: 7 | $(GO) test $(VERBOSE_GO) -race -tags "$(BUILDTAGS)" $(PKGS) 8 | 9 | test-integration: build 10 | $(eval TESTSUITE=$(filter-out $@,$(MAKECMDGOALS))) 11 | test/integration/run-bats.sh $(TESTSUITE) 12 | -------------------------------------------------------------------------------- /mk/validate.mk: -------------------------------------------------------------------------------- 1 | # Validate DCO on all history 2 | mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) 3 | current_dir := $(notdir $(patsubst %/,%,$(dir $(mkfile_path)))) 4 | 5 | # XXX vendorized script miss exec bit, hence the gymnastic 6 | # plus the path resolution... 7 | # TODO migrate away from the shell script and have a make equivalent instead 8 | dco: 9 | @echo 10 | 11 | fmt: 12 | @test -z "$$(gofmt -s -l . 2>&1 | grep -v vendor/ | tee /dev/stderr)" 13 | 14 | vet: 15 | @test -z "$$(go vet $(PKGS) 2>&1 | tee /dev/stderr)" 16 | 17 | lint: 18 | $(if $(GOLINT), , \ 19 | $(error Please install golint: go get -u github.com/golang/lint/golint)) 20 | @test -z "$$($(GOLINT) ./... 2>&1 | grep -v vendor/ | grep -v "cli/" | grep -v "amazonec2/" |grep -v "openstack/" |grep -v "softlayer/" | grep -v "should have comment" | tee /dev/stderr)" 21 | -------------------------------------------------------------------------------- /qemu.go: -------------------------------------------------------------------------------- 1 | package qemu 2 | 3 | import ( 4 | "archive/tar" 5 | "bytes" 6 | "encoding/json" 7 | "fmt" 8 | "io/ioutil" 9 | "math/rand" 10 | "net" 11 | "os" 12 | "os/exec" 13 | "path/filepath" 14 | "strconv" 15 | "strings" 16 | "syscall" 17 | "time" 18 | 19 | "github.com/docker/machine/libmachine/drivers" 20 | "github.com/docker/machine/libmachine/log" 21 | "github.com/docker/machine/libmachine/mcnflag" 22 | "github.com/docker/machine/libmachine/mcnutils" 23 | "github.com/docker/machine/libmachine/ssh" 24 | "github.com/docker/machine/libmachine/state" 25 | ) 26 | 27 | const ( 28 | isoFilename = "boot2docker.iso" 29 | privateNetworkName = "docker-machines" 30 | 31 | defaultSSHUser = "docker" 32 | ) 33 | 34 | type Driver struct { 35 | *drivers.BaseDriver 36 | EnginePort int 37 | FirstQuery bool 38 | 39 | Memory int 40 | DiskSize int 41 | CPU int 42 | Program string 43 | Display bool 44 | DisplayType string 45 | Nographic bool 46 | VirtioDrives bool 47 | Network string 48 | PrivateNetwork string 49 | Boot2DockerURL string 50 | NetworkInterface string 51 | NetworkAddress string 52 | NetworkBridge string 53 | CaCertPath string 54 | PrivateKeyPath string 55 | DiskPath string 56 | CacheMode string 57 | IOMode string 58 | connectionString string 59 | // conn *libvirt.Connect 60 | // VM *libvirt.Domain 61 | vmLoaded bool 62 | UserDataFile string 63 | CloudConfigRoot string 64 | LocalPorts string 65 | } 66 | 67 | func (d *Driver) GetCreateFlags() []mcnflag.Flag { 68 | return []mcnflag.Flag{ 69 | mcnflag.IntFlag{ 70 | Name: "qemu-memory", 71 | Usage: "Size of memory for host in MB", 72 | Value: 1024, 73 | }, 74 | mcnflag.IntFlag{ 75 | Name: "qemu-disk-size", 76 | Usage: "Size of disk for host in MB", 77 | Value: 20000, 78 | }, 79 | mcnflag.IntFlag{ 80 | Name: "qemu-cpu-count", 81 | Usage: "Number of CPUs", 82 | Value: 1, 83 | }, 84 | mcnflag.StringFlag{ 85 | Name: "qemu-program", 86 | Usage: "Name of program to run", 87 | Value: "qemu-system-x86_64", 88 | }, 89 | mcnflag.BoolFlag{ 90 | Name: "qemu-display", 91 | Usage: "Display video output", 92 | }, 93 | mcnflag.StringFlag{ 94 | EnvVar: "QEMU_DISPLAY_TYPE", 95 | Name: "qemu-display-type", 96 | Usage: "Select type of display", 97 | }, 98 | mcnflag.BoolFlag{ 99 | Name: "qemu-nographic", 100 | Usage: "Use -nographic instead of -display none", 101 | }, 102 | mcnflag.BoolFlag{ 103 | EnvVar: "QEMU_VIRTIO_DRIVES", 104 | Name: "qemu-virtio-drives", 105 | Usage: "Use virtio for drives (cdrom and disk)", 106 | }, 107 | mcnflag.StringFlag{ 108 | Name: "qemu-network", 109 | Usage: "Name of network to connect to (user, tap, bridge)", 110 | Value: "user", 111 | }, 112 | mcnflag.StringFlag{ 113 | EnvVar: "QEMU_BOOT2DOCKER_URL", 114 | Name: "qemu-boot2docker-url", 115 | Usage: "The URL of the boot2docker image. Defaults to the latest available version", 116 | Value: "", 117 | }, 118 | mcnflag.StringFlag{ 119 | Name: "qemu-network-interface", 120 | Usage: "Name of the network interface to be used for networking (for tap)", 121 | Value: "tap0", 122 | }, 123 | mcnflag.StringFlag{ 124 | Name: "qemu-network-address", 125 | Usage: "IP of the network adress to be used for networking (for tap)", 126 | }, 127 | mcnflag.StringFlag{ 128 | Name: "qemu-network-bridge", 129 | Usage: "Name of the network bridge to be used for networking (for bridge)", 130 | Value: "br0", 131 | }, 132 | mcnflag.StringFlag{ 133 | Name: "qemu-cache-mode", 134 | Usage: "Disk cache mode: default, none, writethrough, writeback, directsync, or unsafe", 135 | Value: "default", 136 | }, 137 | mcnflag.StringFlag{ 138 | Name: "qemu-io-mode", 139 | Usage: "Disk IO mode: threads, native", 140 | Value: "threads", 141 | }, 142 | mcnflag.StringFlag{ 143 | EnvVar: "QEMU_SSH_USER", 144 | Name: "qemu-ssh-user", 145 | Usage: "SSH username", 146 | Value: defaultSSHUser, 147 | }, 148 | mcnflag.StringFlag{ 149 | Name: "qemu-userdata", 150 | Usage: "cloud-config userdata file", 151 | }, 152 | mcnflag.StringFlag{ 153 | EnvVar: "QEMU_LOCALPORTS", 154 | Name: "qemu-localports", 155 | Usage: "Port range to bind local SSH and engine ports", 156 | }, 157 | /* Not yet implemented 158 | mcnflag.Flag{ 159 | Name: "qemu-no-share", 160 | Usage: "Disable the mount of your home directory", 161 | }, 162 | */ 163 | } 164 | } 165 | 166 | func (d *Driver) GetMachineName() string { 167 | return d.MachineName 168 | } 169 | 170 | func (d *Driver) GetSSHHostname() (string, error) { 171 | return "localhost", nil 172 | //return d.GetIP() 173 | } 174 | 175 | func (d *Driver) GetSSHKeyPath() string { 176 | return d.ResolveStorePath("id_rsa") 177 | } 178 | 179 | func (d *Driver) GetSSHPort() (int, error) { 180 | if d.SSHPort == 0 { 181 | d.SSHPort = 22 182 | } 183 | 184 | return d.SSHPort, nil 185 | } 186 | 187 | func (d *Driver) GetSSHUsername() string { 188 | if d.SSHUser == "" { 189 | d.SSHUser = "docker" 190 | } 191 | 192 | return d.SSHUser 193 | } 194 | 195 | func (d *Driver) DriverName() string { 196 | return "qemu" 197 | } 198 | 199 | func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error { 200 | log.Debugf("SetConfigFromFlags called") 201 | d.Memory = flags.Int("qemu-memory") 202 | d.DiskSize = flags.Int("qemu-disk-size") 203 | d.CPU = flags.Int("qemu-cpu-count") 204 | d.Program = flags.String("qemu-program") 205 | d.Display = flags.Bool("qemu-display") 206 | d.DisplayType = flags.String("qemu-display-type") 207 | d.Nographic = flags.Bool("qemu-nographic") 208 | d.VirtioDrives = flags.Bool("qemu-virtio-drives") 209 | d.Network = flags.String("qemu-network") 210 | d.Boot2DockerURL = flags.String("qemu-boot2docker-url") 211 | d.NetworkInterface = flags.String("qemu-network-interface") 212 | d.NetworkAddress = flags.String("qemu-network-address") 213 | d.NetworkBridge = flags.String("qemu-network-bridge") 214 | d.CacheMode = flags.String("qemu-cache-mode") 215 | d.IOMode = flags.String("qemu-io-mode") 216 | 217 | d.SwarmMaster = flags.Bool("swarm-master") 218 | d.SwarmHost = flags.String("swarm-host") 219 | d.SwarmDiscovery = flags.String("swarm-discovery") 220 | d.SSHUser = flags.String("qemu-ssh-user") 221 | d.UserDataFile = flags.String("qemu-userdata") 222 | d.EnginePort = 2376 223 | d.LocalPorts = flags.String("qemu-localports") 224 | d.FirstQuery = true 225 | d.SSHPort = 22 226 | d.DiskPath = d.ResolveStorePath(fmt.Sprintf("%s.img", d.MachineName)) 227 | return nil 228 | } 229 | 230 | func (d *Driver) GetURL() (string, error) { 231 | log.Debugf("GetURL called") 232 | if _, err := os.Stat(d.pidfilePath()); err != nil { 233 | return "", nil 234 | } 235 | ip, err := d.GetIP() 236 | if err != nil { 237 | log.Warnf("Failed to get IP: %s", err) 238 | return "", err 239 | } 240 | if ip == "" { 241 | return "", nil 242 | } 243 | port := d.GetPort() 244 | return fmt.Sprintf("tcp://%s:%d", ip, port), nil 245 | } 246 | 247 | func NewDriver(hostName, storePath string) drivers.Driver { 248 | return &Driver{ 249 | PrivateNetwork: privateNetworkName, 250 | BaseDriver: &drivers.BaseDriver{ 251 | SSHUser: defaultSSHUser, 252 | MachineName: hostName, 253 | StorePath: storePath, 254 | }, 255 | } 256 | } 257 | 258 | func (d *Driver) GetIP() (string, error) { 259 | if d.Network == "user" { 260 | return "127.0.0.1", nil 261 | } 262 | return d.NetworkAddress, nil 263 | } 264 | 265 | func (d *Driver) GetPort() int { 266 | var port = d.EnginePort 267 | if d.FirstQuery { 268 | d.FirstQuery = false 269 | port = 2376 270 | } 271 | return port 272 | } 273 | 274 | func checkPid(pid int) error { 275 | process, err := os.FindProcess(pid) 276 | if err != nil { 277 | return err 278 | } 279 | return process.Signal(syscall.Signal(0)) 280 | } 281 | 282 | func (d *Driver) GetState() (state.State, error) { 283 | 284 | if _, err := os.Stat(d.pidfilePath()); err != nil { 285 | return state.Stopped, nil 286 | } 287 | p, err := ioutil.ReadFile(d.pidfilePath()) 288 | if err != nil { 289 | return state.Error, err 290 | } 291 | pid, err := strconv.Atoi(strings.TrimSpace(string(p))) 292 | if err != nil { 293 | return state.Error, err 294 | } 295 | if err := checkPid(pid); err != nil { 296 | // No pid, remove pidfile 297 | os.Remove(d.pidfilePath()) 298 | return state.Stopped, nil 299 | } 300 | ret, err := d.RunQMPCommand("query-status") 301 | if err != nil { 302 | return state.Error, err 303 | } 304 | // RunState is one of: 305 | // 'debug', 'inmigrate', 'internal-error', 'io-error', 'paused', 306 | // 'postmigrate', 'prelaunch', 'finish-migrate', 'restore-vm', 307 | // 'running', 'save-vm', 'shutdown', 'suspended', 'watchdog', 308 | // 'guest-panicked' 309 | switch ret["status"] { 310 | case "running": 311 | return state.Running, nil 312 | case "paused": 313 | return state.Paused, nil 314 | case "shutdown": 315 | return state.Stopped, nil 316 | } 317 | return state.None, nil 318 | } 319 | 320 | func (d *Driver) PreCreateCheck() error { 321 | return nil 322 | } 323 | 324 | func (d *Driver) Create() error { 325 | var err error 326 | if d.Network == "user" { 327 | minPort, maxPort, err := parsePortRange(d.LocalPorts) 328 | log.Debugf("port range: %d -> %d", minPort, maxPort) 329 | if err != nil { 330 | return err 331 | } 332 | d.SSHPort, err = getAvailableTCPPortFromRange(minPort, maxPort) 333 | if err != nil { 334 | return err 335 | } 336 | 337 | for { 338 | d.EnginePort, err = getAvailableTCPPortFromRange(minPort, maxPort) 339 | if err != nil { 340 | return err 341 | } 342 | if d.EnginePort == d.SSHPort { 343 | // can't have both on same port 344 | continue 345 | } 346 | break 347 | } 348 | } 349 | b2dutils := mcnutils.NewB2dUtils(d.StorePath) 350 | if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil { 351 | return err 352 | } 353 | 354 | log.Infof("Creating SSH key...") 355 | if err := ssh.GenerateSSHKey(d.sshKeyPath()); err != nil { 356 | return err 357 | } 358 | 359 | log.Infof("Creating Disk image...") 360 | if err := d.generateDiskImage(d.DiskSize); err != nil { 361 | return err 362 | } 363 | 364 | if d.UserDataFile != "" { 365 | log.Infof("Creating Userdata Disk...") 366 | if d.CloudConfigRoot, err = d.generateUserdataDisk(d.UserDataFile); err != nil { 367 | return err 368 | } 369 | } 370 | 371 | log.Infof("Starting QEMU VM...") 372 | return d.Start() 373 | } 374 | 375 | func parsePortRange(rawPortRange string) (int, int, error) { 376 | if rawPortRange == "" { 377 | return 0, 65535, nil 378 | } 379 | 380 | portRange := strings.Split(rawPortRange, "-") 381 | 382 | minPort, err := strconv.Atoi(portRange[0]) 383 | if err != nil { 384 | return 0, 0, fmt.Errorf("Invalid port range") 385 | } 386 | maxPort, err := strconv.Atoi(portRange[1]) 387 | if err != nil { 388 | return 0, 0, fmt.Errorf("Invalid port range") 389 | } 390 | 391 | if maxPort < minPort { 392 | return 0, 0, fmt.Errorf("Invalid port range") 393 | } 394 | 395 | if maxPort-minPort < 2 { 396 | return 0, 0, fmt.Errorf("Port range must be minimum 2 ports") 397 | } 398 | 399 | return minPort, maxPort, nil 400 | } 401 | 402 | func getRandomPortNumberInRange(min int, max int) int { 403 | return rand.Intn(max-min) + min 404 | } 405 | 406 | func getAvailableTCPPortFromRange(minPort int, maxPort int) (int, error) { 407 | port := 0 408 | for i := 0; i <= 10; i++ { 409 | var ln net.Listener 410 | var err error 411 | if minPort == 0 && maxPort == 65535 { 412 | ln, err = net.Listen("tcp4", "127.0.0.1:0") 413 | if err != nil { 414 | return 0, err 415 | } 416 | } else { 417 | port = getRandomPortNumberInRange(minPort, maxPort) 418 | log.Debugf("testing port: %d", port) 419 | ln, err = net.Listen("tcp4", fmt.Sprintf("127.0.0.1:%d", port)) 420 | if err != nil { 421 | log.Debugf("port already in use: %d", port) 422 | continue 423 | } 424 | } 425 | defer ln.Close() 426 | addr := ln.Addr().String() 427 | addrParts := strings.SplitN(addr, ":", 2) 428 | p, err := strconv.Atoi(addrParts[1]) 429 | if err != nil { 430 | return 0, err 431 | } 432 | if p != 0 { 433 | port = p 434 | return port, nil 435 | } 436 | time.Sleep(1) 437 | } 438 | return 0, fmt.Errorf("unable to allocate tcp port") 439 | } 440 | 441 | func (d *Driver) Start() error { 442 | // fmt.Printf("Init qemu %s\n", i.VM) 443 | machineDir := filepath.Join(d.StorePath, "machines", d.GetMachineName()) 444 | 445 | var startCmd []string 446 | 447 | if d.Display { 448 | if d.DisplayType != "" { 449 | startCmd = append(startCmd, 450 | "-display", d.DisplayType, 451 | ) 452 | } else { 453 | // Use the default graphic output 454 | } 455 | } else { 456 | if d.Nographic { 457 | startCmd = append(startCmd, 458 | "-nographic", 459 | ) 460 | } else { 461 | startCmd = append(startCmd, 462 | "-display", "none", 463 | ) 464 | } 465 | } 466 | 467 | startCmd = append(startCmd, 468 | "-m", fmt.Sprintf("%d", d.Memory), 469 | "-smp", fmt.Sprintf("%d", d.CPU), 470 | "-boot", "d") 471 | var isoPath = filepath.Join(machineDir, isoFilename) 472 | if d.VirtioDrives { 473 | startCmd = append(startCmd, 474 | "-drive", fmt.Sprintf("file=%s,index=2,media=cdrom,if=virtio", isoPath)) 475 | } else { 476 | startCmd = append(startCmd, 477 | "-cdrom", isoPath) 478 | } 479 | startCmd = append(startCmd, 480 | "-qmp", fmt.Sprintf("unix:%s,server,nowait", d.monitorPath()), 481 | "-pidfile", d.pidfilePath(), 482 | ) 483 | 484 | if d.Network == "user" { 485 | startCmd = append(startCmd, 486 | "-nic", fmt.Sprintf("user,model=virtio,hostfwd=tcp::%d-:22,hostfwd=tcp::%d-:2376,hostname=%s", d.SSHPort, d.EnginePort, d.GetMachineName()), 487 | ) 488 | } else if d.Network == "tap" { 489 | startCmd = append(startCmd, 490 | "-nic", fmt.Sprintf("tap,model=virtio,ifname=%s,script=no,downscript=no", d.NetworkInterface), 491 | ) 492 | } else if d.Network == "bridge" { 493 | startCmd = append(startCmd, 494 | "-nic", fmt.Sprintf("bridge,model=virtio,br=%s", d.NetworkBridge), 495 | ) 496 | } else { 497 | log.Errorf("Unknown network: %s", d.Network) 498 | } 499 | 500 | startCmd = append(startCmd, "-daemonize") 501 | 502 | // other options 503 | // "-enable-kvm" if its available 504 | if _, err := os.Stat("/dev/kvm"); err == nil { 505 | startCmd = append(startCmd, "-enable-kvm") 506 | } 507 | 508 | if d.CloudConfigRoot != "" { 509 | startCmd = append(startCmd, 510 | "-fsdev", 511 | fmt.Sprintf("local,security_model=passthrough,readonly,id=fsdev0,path=%s", d.CloudConfigRoot)) 512 | startCmd = append(startCmd, "-device", "virtio-9p-pci,id=fs0,fsdev=fsdev0,mount_tag=config-2") 513 | } 514 | 515 | if d.VirtioDrives { 516 | startCmd = append(startCmd, 517 | "-drive", fmt.Sprintf("file=%s,index=0,media=disk,if=virtio", d.diskPath())) 518 | } else { 519 | // last argument is always the name of the disk image 520 | startCmd = append(startCmd, d.diskPath()) 521 | } 522 | 523 | if stdout, stderr, err := cmdOutErr(d.Program, startCmd...); err != nil { 524 | fmt.Printf("OUTPUT: %s\n", stdout) 525 | fmt.Printf("ERROR: %s\n", stderr) 526 | return err 527 | //if err := cmdStart(d.Program, startCmd...); err != nil { 528 | // return err 529 | } 530 | log.Infof("Waiting for VM to start (ssh -p %d docker@localhost)...", d.SSHPort) 531 | 532 | //return ssh.WaitForTCP(fmt.Sprintf("localhost:%d", d.SSHPort)) 533 | return WaitForTCPWithDelay(fmt.Sprintf("localhost:%d", d.SSHPort), time.Second) 534 | } 535 | 536 | func cmdOutErr(cmdStr string, args ...string) (string, string, error) { 537 | cmd := exec.Command(cmdStr, args...) 538 | log.Debugf("executing: %v %v", cmdStr, strings.Join(args, " ")) 539 | var stdout bytes.Buffer 540 | var stderr bytes.Buffer 541 | cmd.Stdout = &stdout 542 | cmd.Stderr = &stderr 543 | err := cmd.Run() 544 | stderrStr := stderr.String() 545 | log.Debugf("STDOUT: %v", stdout.String()) 546 | log.Debugf("STDERR: %v", stderrStr) 547 | if err != nil { 548 | if ee, ok := err.(*exec.Error); ok && ee == exec.ErrNotFound { 549 | err = fmt.Errorf("mystery error: %s", ee) 550 | } 551 | } else { 552 | // also catch error messages in stderr, even if the return code 553 | // looks OK 554 | if strings.Contains(stderrStr, "error:") { 555 | err = fmt.Errorf("%v %v failed: %v", cmdStr, strings.Join(args, " "), stderrStr) 556 | } 557 | } 558 | return stdout.String(), stderrStr, err 559 | } 560 | 561 | func cmdStart(cmdStr string, args ...string) error { 562 | cmd := exec.Command(cmdStr, args...) 563 | log.Debugf("executing: %v %v", cmdStr, strings.Join(args, " ")) 564 | return cmd.Start() 565 | } 566 | 567 | func (d *Driver) Stop() error { 568 | // _, err := d.RunQMPCommand("stop") 569 | _, err := d.RunQMPCommand("system_powerdown") 570 | if err != nil { 571 | return err 572 | } 573 | return nil 574 | } 575 | 576 | func (d *Driver) Remove() error { 577 | s, err := d.GetState() 578 | if err != nil { 579 | return err 580 | } 581 | if s == state.Running { 582 | if err := d.Kill(); err != nil { 583 | return err 584 | } 585 | } 586 | if s != state.Stopped { 587 | _, err = d.RunQMPCommand("quit") 588 | if err != nil { 589 | return err 590 | } 591 | } 592 | return nil 593 | } 594 | 595 | func (d *Driver) Restart() error { 596 | s, err := d.GetState() 597 | if err != nil { 598 | return err 599 | } 600 | 601 | if s == state.Running { 602 | if err := d.Stop(); err != nil { 603 | return err 604 | } 605 | } 606 | return d.Start() 607 | } 608 | 609 | func (d *Driver) Kill() error { 610 | // _, err := d.RunQMPCommand("quit") 611 | _, err := d.RunQMPCommand("system_powerdown") 612 | if err != nil { 613 | return err 614 | } 615 | return nil 616 | } 617 | 618 | func (d *Driver) StartDocker() error { 619 | return fmt.Errorf("hosts without a driver cannot start docker") 620 | } 621 | 622 | func (d *Driver) StopDocker() error { 623 | return fmt.Errorf("hosts without a driver cannot stop docker") 624 | } 625 | 626 | func (d *Driver) GetDockerConfigDir() string { 627 | return "" 628 | } 629 | 630 | func (d *Driver) Upgrade() error { 631 | return fmt.Errorf("hosts without a driver cannot be upgraded") 632 | } 633 | 634 | //func (d *Driver) GetSSHCommand(args ...string) (*exec.Cmd, error) { 635 | // return ssh.GetSSHCommand("localhost", d.SSHPort, "docker", d.sshKeyPath(), args...), nil 636 | //} 637 | 638 | func (d *Driver) sshKeyPath() string { 639 | machineDir := filepath.Join(d.StorePath, "machines", d.GetMachineName()) 640 | return filepath.Join(machineDir, "id_rsa") 641 | } 642 | 643 | func (d *Driver) publicSSHKeyPath() string { 644 | return d.sshKeyPath() + ".pub" 645 | } 646 | 647 | func (d *Driver) diskPath() string { 648 | machineDir := filepath.Join(d.StorePath, "machines", d.GetMachineName()) 649 | return filepath.Join(machineDir, "disk.qcow2") 650 | } 651 | 652 | func (d *Driver) monitorPath() string { 653 | machineDir := filepath.Join(d.StorePath, "machines", d.GetMachineName()) 654 | return filepath.Join(machineDir, "monitor") 655 | } 656 | 657 | func (d *Driver) pidfilePath() string { 658 | machineDir := filepath.Join(d.StorePath, "machines", d.GetMachineName()) 659 | return filepath.Join(machineDir, "qemu.pid") 660 | } 661 | 662 | // Make a boot2docker VM disk image. 663 | func (d *Driver) generateDiskImage(size int) error { 664 | log.Debugf("Creating %d MB hard disk image...", size) 665 | 666 | magicString := "boot2docker, please format-me" 667 | 668 | buf := new(bytes.Buffer) 669 | tw := tar.NewWriter(buf) 670 | 671 | // magicString first so the automount script knows to format the disk 672 | file := &tar.Header{Name: magicString, Size: int64(len(magicString))} 673 | if err := tw.WriteHeader(file); err != nil { 674 | return err 675 | } 676 | if _, err := tw.Write([]byte(magicString)); err != nil { 677 | return err 678 | } 679 | // .ssh/key.pub => authorized_keys 680 | file = &tar.Header{Name: ".ssh", Typeflag: tar.TypeDir, Mode: 0700} 681 | if err := tw.WriteHeader(file); err != nil { 682 | return err 683 | } 684 | pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) 685 | if err != nil { 686 | return err 687 | } 688 | file = &tar.Header{Name: ".ssh/authorized_keys", Size: int64(len(pubKey)), Mode: 0644} 689 | if err := tw.WriteHeader(file); err != nil { 690 | return err 691 | } 692 | if _, err := tw.Write([]byte(pubKey)); err != nil { 693 | return err 694 | } 695 | file = &tar.Header{Name: ".ssh/authorized_keys2", Size: int64(len(pubKey)), Mode: 0644} 696 | if err := tw.WriteHeader(file); err != nil { 697 | return err 698 | } 699 | if _, err := tw.Write([]byte(pubKey)); err != nil { 700 | return err 701 | } 702 | if err := tw.Close(); err != nil { 703 | return err 704 | } 705 | rawFile := fmt.Sprintf("%s.raw", d.diskPath()) 706 | if err := ioutil.WriteFile(rawFile, buf.Bytes(), 0644); err != nil { 707 | return nil 708 | } 709 | if stdout, stderr, err := cmdOutErr("qemu-img", "convert", "-f", "raw", "-O", "qcow2", rawFile, d.diskPath()); err != nil { 710 | fmt.Printf("OUTPUT: %s\n", stdout) 711 | fmt.Printf("ERROR: %s\n", stderr) 712 | return err 713 | } 714 | if stdout, stderr, err := cmdOutErr("qemu-img", "resize", d.diskPath(), fmt.Sprintf("+%dM", size)); err != nil { 715 | fmt.Printf("OUTPUT: %s\n", stdout) 716 | fmt.Printf("ERROR: %s\n", stderr) 717 | return err 718 | } 719 | log.Debugf("DONE writing to %s and %s", rawFile, d.diskPath()) 720 | 721 | return nil 722 | } 723 | 724 | func (d *Driver) generateUserdataDisk(userdataFile string) (string, error) { 725 | // Start with virtio, add ISO & FAT format later 726 | // Start with local file, add wget/fetct URL? (or if URL, use datasource..) 727 | userdata, err := ioutil.ReadFile(userdataFile) 728 | if err != nil { 729 | return "", err 730 | } 731 | 732 | machineDir := filepath.Join(d.StorePath, "machines", d.GetMachineName()) 733 | ccRoot := filepath.Join(machineDir, "cloud-config") 734 | os.MkdirAll(ccRoot, 0755) 735 | 736 | userDataDir := filepath.Join(ccRoot, "openstack/latest") 737 | os.MkdirAll(userDataDir, 0755) 738 | 739 | writeFile := filepath.Join(userDataDir, "user_data") 740 | if err := ioutil.WriteFile(writeFile, userdata, 0644); err != nil { 741 | return "", err 742 | } 743 | 744 | return ccRoot, nil 745 | 746 | } 747 | 748 | func (d *Driver) RunQMPCommand(command string) (map[string]interface{}, error) { 749 | 750 | // connect to monitor 751 | conn, err := net.Dial("unix", d.monitorPath()) 752 | if err != nil { 753 | return nil, err 754 | } 755 | defer conn.Close() 756 | 757 | // initial QMP response 758 | var buf [1024]byte 759 | nr, err := conn.Read(buf[:]) 760 | if err != nil { 761 | return nil, err 762 | } 763 | type qmpInitialResponse struct { 764 | QMP struct { 765 | Version struct { 766 | QEMU struct { 767 | Micro int `json:"micro"` 768 | Minor int `json:"minor"` 769 | Major int `json:"major"` 770 | } `json:"qemu"` 771 | Package string `json:"package"` 772 | } `json:"version"` 773 | Capabilities []string `json:"capabilities"` 774 | } `jason:"QMP"` 775 | } 776 | 777 | var initialResponse qmpInitialResponse 778 | json.Unmarshal(buf[:nr], &initialResponse) 779 | 780 | // run 'qmp_capabilities' to switch to command mode 781 | // { "execute": "qmp_capabilities" } 782 | type qmpCommand struct { 783 | Command string `json:"execute"` 784 | } 785 | jsonCommand, err := json.Marshal(qmpCommand{Command: "qmp_capabilities"}) 786 | if err != nil { 787 | return nil, err 788 | } 789 | _, err = conn.Write(jsonCommand) 790 | if err != nil { 791 | return nil, err 792 | } 793 | nr, err = conn.Read(buf[:]) 794 | if err != nil { 795 | return nil, err 796 | } 797 | type qmpResponse struct { 798 | Return map[string]interface{} `json:"return"` 799 | } 800 | var response qmpResponse 801 | err = json.Unmarshal(buf[:nr], &response) 802 | if err != nil { 803 | return nil, err 804 | } 805 | // expecting empty response 806 | if len(response.Return) != 0 { 807 | return nil, fmt.Errorf("qmp_capabilities failed: %v", response.Return) 808 | } 809 | 810 | // { "execute": command } 811 | jsonCommand, err = json.Marshal(qmpCommand{Command: command}) 812 | if err != nil { 813 | return nil, err 814 | } 815 | _, err = conn.Write(jsonCommand) 816 | if err != nil { 817 | return nil, err 818 | } 819 | nr, err = conn.Read(buf[:]) 820 | if err != nil { 821 | return nil, err 822 | } 823 | err = json.Unmarshal(buf[:nr], &response) 824 | if err != nil { 825 | return nil, err 826 | } 827 | if strings.HasPrefix(command, "query-") { 828 | return response.Return, nil 829 | } 830 | // non-query commands should return an empty response 831 | if len(response.Return) != 0 { 832 | return nil, fmt.Errorf("%s failed: %v", command, response.Return) 833 | } 834 | return response.Return, nil 835 | } 836 | 837 | func WaitForTCPWithDelay(addr string, duration time.Duration) error { 838 | for { 839 | conn, err := net.Dial("tcp", addr) 840 | if err != nil { 841 | continue 842 | } 843 | defer conn.Close() 844 | if _, err = conn.Read(make([]byte, 1)); err != nil { 845 | time.Sleep(duration) 846 | continue 847 | } 848 | break 849 | } 850 | return nil 851 | } 852 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | var ( 9 | // Version should be updated by hand at each release 10 | Version = "0.10.0" 11 | 12 | // GitCommit will be overwritten automatically by the build system 13 | GitCommit = "HEAD" 14 | ) 15 | 16 | // FullVersion formats the version to be printed 17 | func FullVersion() string { 18 | return fmt.Sprintf("%s, build %s", Version, GitCommit) 19 | } 20 | 21 | // RC checks if the Machine version is a release candidate or not 22 | func RC() bool { 23 | return strings.Contains(Version, "rc") 24 | } 25 | --------------------------------------------------------------------------------