Merge pull request #467 from jimmidyson/hyperv-driver

Add hyperv support
pull/517/head
dlorenc 2016-08-24 09:27:41 -07:00 committed by GitHub
commit d82f428b6b
10 changed files with 661 additions and 1 deletions

5
Godeps/Godeps.json generated
View File

@ -705,6 +705,11 @@
"Comment": "v0.7.0-62-g6002b41",
"Rev": "6002b411ce820eaf03ac972a7fb354bb56f7aa95"
},
{
"ImportPath": "github.com/docker/machine/drivers/hyperv",
"Comment": "v0.7.0-62-g6002b41",
"Rev": "6002b411ce820eaf03ac972a7fb354bb56f7aa95"
},
{
"ImportPath": "github.com/docker/machine/drivers/none",
"Comment": "v0.7.0-62-g6002b41",

View File

@ -25,7 +25,7 @@ minikube start
OR a URI which contains a localkube binary (ex: https://storage.googleapis.com/minikube/k8sReleases/v1.3.0/localkube-linux-amd64)
--memory=1024: Amount of RAM allocated to the minikube VM
--registry-mirror=[]: Registry mirrors to pass to the Docker daemon
--vm-driver="virtualbox": VM driver is one of: [virtualbox vmwarefusion kvm xhyve]
--vm-driver="virtualbox": VM driver is one of: [virtualbox vmwarefusion kvm xhyve hyperv]
```
### Options inherited from parent commands

View File

@ -387,6 +387,8 @@ func createHost(api libmachine.API, config MachineConfig) (*host.Host, error) {
driver = createKVMHost(config)
case "xhyve":
driver = createXhyveHost(config)
case "hyperv":
driver = createHypervHost(config)
default:
glog.Exitf("Unsupported driver: %s\n", config.VMDriver)
}

View File

@ -0,0 +1,25 @@
// +build !windows
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cluster
import "github.com/docker/machine/libmachine/drivers"
func createHypervHost(config MachineConfig) drivers.Driver {
panic("hyperv not supported")
}

View File

@ -0,0 +1,33 @@
/*
Copyright 2016 The Kubernetes Authors All rights reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cluster
import (
"github.com/docker/machine/drivers/hyperv"
"github.com/docker/machine/libmachine/drivers"
"k8s.io/minikube/pkg/minikube/constants"
)
func createHypervHost(config MachineConfig) drivers.Driver {
d := hyperv.NewDriver(constants.MachineName, constants.Minipath)
d.Boot2DockerURL = config.GetISOFileURI()
d.MemSize = config.Memory
d.CPU = config.CPUs
d.DiskSize = int(config.DiskSize)
d.SSHUser = "docker"
return d
}

View File

@ -23,4 +23,5 @@ var SupportedVMDrivers = [...]string{
"vmwarefusion",
"kvm",
"xhyve",
"hyperv",
}

View File

@ -20,4 +20,5 @@ package constants
var SupportedVMDrivers = [...]string{
"virtualbox",
"hyperv",
}

View File

@ -19,6 +19,7 @@ package machine
import (
"os"
"github.com/docker/machine/drivers/hyperv"
"github.com/docker/machine/drivers/virtualbox"
"github.com/docker/machine/drivers/vmwarefusion"
"github.com/docker/machine/libmachine/drivers/plugin"
@ -35,6 +36,8 @@ func StartDriver() {
plugin.RegisterDriver(virtualbox.NewDriver("", ""))
case "vmwarefusion":
plugin.RegisterDriver(vmwarefusion.NewDriver("", ""))
case "hyperv":
plugin.RegisterDriver(hyperv.NewDriver("", ""))
default:
glog.Exitf("Unsupported driver: %s\n", driverName)
}

View File

@ -0,0 +1,476 @@
package hyperv
import (
"fmt"
"net"
"os"
"time"
"errors"
"github.com/docker/machine/libmachine/drivers"
"github.com/docker/machine/libmachine/log"
"github.com/docker/machine/libmachine/mcnflag"
"github.com/docker/machine/libmachine/mcnutils"
"github.com/docker/machine/libmachine/ssh"
"github.com/docker/machine/libmachine/state"
)
type Driver struct {
*drivers.BaseDriver
Boot2DockerURL string
VSwitch string
DiskSize int
MemSize int
CPU int
MacAddr string
VLanID int
}
const (
defaultDiskSize = 20000
defaultMemory = 1024
defaultCPU = 1
defaultVLanID = 0
)
// NewDriver creates a new Hyper-v driver with default settings.
func NewDriver(hostName, storePath string) *Driver {
return &Driver{
DiskSize: defaultDiskSize,
MemSize: defaultMemory,
CPU: defaultCPU,
BaseDriver: &drivers.BaseDriver{
MachineName: hostName,
StorePath: storePath,
},
}
}
// GetCreateFlags registers the flags this driver adds to
// "docker hosts create"
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
return []mcnflag.Flag{
mcnflag.StringFlag{
Name: "hyperv-boot2docker-url",
Usage: "URL of the boot2docker ISO. Defaults to the latest available version.",
EnvVar: "HYPERV_BOOT2DOCKER_URL",
},
mcnflag.StringFlag{
Name: "hyperv-virtual-switch",
Usage: "Virtual switch name. Defaults to first found.",
EnvVar: "HYPERV_VIRTUAL_SWITCH",
},
mcnflag.IntFlag{
Name: "hyperv-disk-size",
Usage: "Maximum size of dynamically expanding disk in MB.",
Value: defaultDiskSize,
EnvVar: "HYPERV_DISK_SIZE",
},
mcnflag.IntFlag{
Name: "hyperv-memory",
Usage: "Memory size for host in MB.",
Value: defaultMemory,
EnvVar: "HYPERV_MEMORY",
},
mcnflag.IntFlag{
Name: "hyperv-cpu-count",
Usage: "number of CPUs for the machine",
Value: defaultCPU,
EnvVar: "HYPERV_CPU_COUNT",
},
mcnflag.StringFlag{
Name: "hyperv-static-macaddress",
Usage: "Hyper-V network adapter's static MAC address.",
EnvVar: "HYPERV_STATIC_MACADDRESS",
},
mcnflag.IntFlag{
Name: "hyperv-vlan-id",
Usage: "Hyper-V network adapter's VLAN ID if any",
Value: defaultVLanID,
EnvVar: "HYPERV_VLAN_ID",
},
}
}
func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
if drivers.EngineInstallURLFlagSet(flags) {
return errors.New("--engine-install-url cannot be used with the hyperv driver, use --hyperv-boot2docker-url instead")
}
d.Boot2DockerURL = flags.String("hyperv-boot2docker-url")
d.VSwitch = flags.String("hyperv-virtual-switch")
d.DiskSize = flags.Int("hyperv-disk-size")
d.MemSize = flags.Int("hyperv-memory")
d.CPU = flags.Int("hyperv-cpu-count")
d.MacAddr = flags.String("hyperv-static-macaddress")
d.VLanID = flags.Int("hyperv-vlan-id")
d.SSHUser = "docker"
d.SetSwarmConfigFromFlags(flags)
return nil
}
func (d *Driver) GetSSHHostname() (string, error) {
return d.GetIP()
}
// DriverName returns the name of the driver
func (d *Driver) DriverName() string {
return "hyperv"
}
func (d *Driver) GetURL() (string, error) {
ip, err := d.GetIP()
if err != nil {
return "", err
}
if ip == "" {
return "", nil
}
return fmt.Sprintf("tcp://%s", net.JoinHostPort(ip, "2376")), nil
}
func (d *Driver) GetState() (state.State, error) {
stdout, err := cmdOut("(", "Get-VM", d.MachineName, ").state")
if err != nil {
return state.None, fmt.Errorf("Failed to find the VM status")
}
resp := parseLines(stdout)
if len(resp) < 1 {
return state.None, nil
}
switch resp[0] {
case "Running":
return state.Running, nil
case "Off":
return state.Stopped, nil
default:
return state.None, nil
}
}
// PreCreateCheck checks that the machine creation process can be started safely.
func (d *Driver) PreCreateCheck() error {
// Check that powershell was found
if powershell == "" {
return ErrPowerShellNotFound
}
// Check that hyperv is installed
if err := hypervAvailable(); err != nil {
return err
}
// Check that the user is an Administrator
isAdmin, err := isAdministrator()
if err != nil {
return err
}
if !isAdmin {
return ErrNotAdministrator
}
// Check that there is a virtual switch already configured
if _, err := d.chooseVirtualSwitch(); err != nil {
return err
}
// Downloading boot2docker to cache should be done here to make sure
// that a download failure will not leave a machine half created.
b2dutils := mcnutils.NewB2dUtils(d.StorePath)
if err := b2dutils.UpdateISOCache(d.Boot2DockerURL); err != nil {
return err
}
return nil
}
func (d *Driver) Create() error {
b2dutils := mcnutils.NewB2dUtils(d.StorePath)
if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil {
return err
}
log.Infof("Creating SSH key...")
if err := ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil {
return err
}
log.Infof("Creating VM...")
virtualSwitch, err := d.chooseVirtualSwitch()
if err != nil {
return err
}
log.Infof("Using switch %q", virtualSwitch)
diskImage, err := d.generateDiskImage()
if err != nil {
return err
}
if err := cmd("New-VM",
d.MachineName,
"-Path", fmt.Sprintf("'%s'", d.ResolveStorePath(".")),
"-SwitchName", quote(virtualSwitch),
"-MemoryStartupBytes", toMb(d.MemSize)); err != nil {
return err
}
if d.CPU > 1 {
if err := cmd("Set-VMProcessor",
d.MachineName,
"-Count", fmt.Sprintf("%d", d.CPU)); err != nil {
return err
}
}
if d.MacAddr != "" {
if err := cmd("Set-VMNetworkAdapter",
"-VMName", d.MachineName,
"-StaticMacAddress", fmt.Sprintf("\"%s\"", d.MacAddr)); err != nil {
return err
}
}
if d.VLanID > 0 {
if err := cmd("Set-VMNetworkAdapterVlan",
"-VMName", d.MachineName,
"-Access",
"-VlanId", fmt.Sprintf("%d", d.VLanID)); err != nil {
return err
}
}
if err := cmd("Set-VMDvdDrive",
"-VMName", d.MachineName,
"-Path", quote(d.ResolveStorePath("boot2docker.iso"))); err != nil {
return err
}
if err := cmd("Add-VMHardDiskDrive",
"-VMName", d.MachineName,
"-Path", quote(diskImage)); err != nil {
return err
}
log.Infof("Starting VM...")
return d.Start()
}
func (d *Driver) chooseVirtualSwitch() (string, error) {
stdout, err := cmdOut("(Get-VMSwitch).Name")
if err != nil {
return "", err
}
switches := parseLines(stdout)
if d.VSwitch == "" {
if len(switches) < 1 {
return "", fmt.Errorf("no vswitch found. A valid vswitch must be available for this command to run. Check https://docs.docker.com/machine/drivers/hyper-v/")
}
return switches[0], nil
}
found := false
for _, name := range switches {
if name == d.VSwitch {
found = true
break
}
}
if !found {
return "", fmt.Errorf("vswitch %q not found", d.VSwitch)
}
return d.VSwitch, nil
}
// waitForIP waits until the host has a valid IP
func (d *Driver) waitForIP() (string, error) {
log.Infof("Waiting for host to start...")
for {
ip, _ := d.GetIP()
if ip != "" {
return ip, nil
}
time.Sleep(1 * time.Second)
}
}
// waitStopped waits until the host is stopped
func (d *Driver) waitStopped() error {
log.Infof("Waiting for host to stop...")
for {
s, err := d.GetState()
if err != nil {
return err
}
if s != state.Running {
return nil
}
time.Sleep(1 * time.Second)
}
}
// Start starts an host
func (d *Driver) Start() error {
if err := cmd("Start-VM", d.MachineName); err != nil {
return err
}
ip, err := d.waitForIP()
if err != nil {
return err
}
d.IPAddress = ip
return nil
}
// Stop stops an host
func (d *Driver) Stop() error {
if err := cmd("Stop-VM", d.MachineName); err != nil {
return err
}
if err := d.waitStopped(); err != nil {
return err
}
d.IPAddress = ""
return nil
}
// Remove removes an host
func (d *Driver) Remove() error {
s, err := d.GetState()
if err != nil {
return err
}
if s == state.Running {
if err := d.Kill(); err != nil {
return err
}
}
return cmd("Remove-VM", d.MachineName, "-Force")
}
// Restart stops and starts an host
func (d *Driver) Restart() error {
err := d.Stop()
if err != nil {
return err
}
return d.Start()
}
// Kill force stops an host
func (d *Driver) Kill() error {
if err := cmd("Stop-VM", d.MachineName, "-TurnOff"); err != nil {
return err
}
if err := d.waitStopped(); err != nil {
return err
}
d.IPAddress = ""
return nil
}
func (d *Driver) GetIP() (string, error) {
s, err := d.GetState()
if err != nil {
return "", err
}
if s != state.Running {
return "", drivers.ErrHostIsNotRunning
}
stdout, err := cmdOut("((", "Get-VM", d.MachineName, ").networkadapters[0]).ipaddresses[0]")
if err != nil {
return "", err
}
resp := parseLines(stdout)
if len(resp) < 1 {
return "", fmt.Errorf("IP not found")
}
return resp[0], nil
}
func (d *Driver) publicSSHKeyPath() string {
return d.GetSSHKeyPath() + ".pub"
}
// generateDiskImage creates a small fixed vhd, put the tar in, convert to dynamic, then resize
func (d *Driver) generateDiskImage() (string, error) {
diskImage := d.ResolveStorePath("disk.vhd")
fixed := d.ResolveStorePath("fixed.vhd")
// Resizing vhds requires administrator priviledges
// incase the user is only a hyper-v admin then create the disk at the target size to avoid resizing.
isWindowsAdmin, err := isWindowsAdministrator()
if err != nil {
return "", err
}
fixedDiskSize := "10MB"
if !isWindowsAdmin {
fixedDiskSize = toMb(d.DiskSize)
}
log.Infof("Creating VHD")
if err := cmd("New-VHD", "-Path", quote(fixed), "-SizeBytes", fixedDiskSize, "-Fixed"); err != nil {
return "", err
}
tarBuf, err := mcnutils.MakeDiskImage(d.publicSSHKeyPath())
if err != nil {
return "", err
}
file, err := os.OpenFile(fixed, os.O_WRONLY, 0644)
if err != nil {
return "", err
}
defer file.Close()
file.Seek(0, os.SEEK_SET)
_, err = file.Write(tarBuf.Bytes())
if err != nil {
return "", err
}
file.Close()
if err := cmd("Convert-VHD", "-Path", quote(fixed), "-DestinationPath", quote(diskImage), "-VHDType", "Dynamic", "-DeleteSource"); err != nil {
return "", err
}
if isWindowsAdmin {
if err := cmd("Resize-VHD", "-Path", quote(diskImage), "-SizeBytes", toMb(d.DiskSize)); err != nil {
return "", err
}
}
return diskImage, nil
}

View File

@ -0,0 +1,114 @@
package hyperv
import (
"bufio"
"bytes"
"errors"
"os/exec"
"strings"
"fmt"
"github.com/docker/machine/libmachine/log"
)
var powershell string
var (
ErrPowerShellNotFound = errors.New("Powershell was not found in the path")
ErrNotAdministrator = errors.New("Hyper-v commands have to be run as an Administrator")
ErrNotInstalled = errors.New("Hyper-V PowerShell Module is not available")
)
func init() {
powershell, _ = exec.LookPath("powershell.exe")
}
func cmdOut(args ...string) (string, error) {
args = append([]string{"-NoProfile", "-NonInteractive"}, args...)
cmd := exec.Command(powershell, args...)
log.Debugf("[executing ==>] : %v %v", powershell, strings.Join(args, " "))
var stdout bytes.Buffer
var stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
err := cmd.Run()
log.Debugf("[stdout =====>] : %s", stdout.String())
log.Debugf("[stderr =====>] : %s", stderr.String())
return stdout.String(), err
}
func cmd(args ...string) error {
_, err := cmdOut(args...)
return err
}
func parseLines(stdout string) []string {
resp := []string{}
s := bufio.NewScanner(strings.NewReader(stdout))
for s.Scan() {
resp = append(resp, s.Text())
}
return resp
}
func hypervAvailable() error {
stdout, err := cmdOut("@(Get-Command Get-VM).ModuleName")
if err != nil {
return err
}
resp := parseLines(stdout)
if resp[0] != "Hyper-V" {
return ErrNotInstalled
}
return nil
}
func isAdministrator() (bool, error) {
hypervAdmin := isHypervAdministrator()
if hypervAdmin {
return true, nil
}
windowsAdmin, err := isWindowsAdministrator()
if err != nil {
return false, err
}
return windowsAdmin, nil
}
func isHypervAdministrator() bool {
stdout, err := cmdOut(`@([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole("Hyper-V Administrators")`)
if err != nil {
log.Debug(err)
return false
}
resp := parseLines(stdout)
return resp[0] == "True"
}
func isWindowsAdministrator() (bool, error) {
stdout, err := cmdOut(`@([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")`)
if err != nil {
return false, err
}
resp := parseLines(stdout)
return resp[0] == "True", nil
}
func quote(text string) string {
return fmt.Sprintf("'%s'", text)
}
func toMb(value int) string {
return fmt.Sprintf("%dMB", value)
}