Initial hyperkit driver implementation.

pull/1776/head
dlorenc 2017-05-22 13:19:25 -07:00 committed by dlorenc
parent d4e211ca4d
commit 28f9d5dc1a
9 changed files with 515 additions and 1 deletions

View File

@ -213,6 +213,15 @@ out/minikube-installer.exe: out/minikube-windows-amd64.exe
mv out/windows_tmp/minikube-installer.exe out/minikube-installer.exe
rm -rf out/windows_tmp
out/docker-machine-driver-hyperkit:
go build -o $(BUILD_DIR)/hyperkit k8s.io/minikube/cmd/drivers/hyperkit
.PHONY: install-hyperkit-driver
install-hyperkit-driver: out/docker-machine-driver-hyperkit
sudo cp out/hyperkit $(HOME)/bin/docker-machine-driver-hyperkit
sudo chown root:wheel $(HOME)/bin/docker-machine-driver-hyperkit
sudo chmod u+s $(HOME)/bin/docker-machine-driver-hyperkit
.PHONY: check-release
check-release:
go test -v ./deploy/minikube/release_sanity_test.go -tags=release

View File

@ -0,0 +1,10 @@
package main
import (
"github.com/docker/machine/libmachine/drivers/plugin"
"k8s.io/minikube/pkg/minikube/drivers/hyperkit"
)
func main() {
plugin.RegisterDriver(hyperkit.NewDriver("", ""))
}

View File

@ -391,6 +391,8 @@ func createHost(api libmachine.API, config MachineConfig) (*host.Host, error) {
driver = createHypervHost(config)
case "none":
driver = createNoneHost(config)
case "hyperkit":
driver = createHyperkitHost(config)
default:
glog.Exitf("Unsupported driver: %s\n", config.VMDriver)
}

View File

@ -23,6 +23,7 @@ import (
"github.com/docker/machine/libmachine/drivers"
cfg "k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/minikube/drivers/hyperkit"
)
func createVMwareFusionHost(config MachineConfig) drivers.Driver {
@ -57,6 +58,21 @@ type xhyveDriver struct {
RawDisk bool
}
func createHyperkitHost(config MachineConfig) *hyperkit.Driver {
return &hyperkit.Driver{
BaseDriver: &drivers.BaseDriver{
MachineName: cfg.GetMachineName(),
StorePath: constants.GetMinipath(),
SSHUser: "docker",
},
Boot2DockerURL: config.Downloader.GetISOFileURI(config.MinikubeISO),
DiskSize: config.DiskSize,
Memory: config.Memory,
CPU: config.CPUs,
Cmdline: "loglevel=3 user=docker console=ttyS0 console=tty0 noembed nomodeset norestore waitusb=10 systemd.legacy_systemd_cgroup_controller=yes base host=" + cfg.GetMachineName(),
}
}
func createXhyveHost(config MachineConfig) *xhyveDriver {
useVirtio9p := !config.DisableDriverMounts
return &xhyveDriver{

View File

@ -0,0 +1,48 @@
package hyperkit
import (
"io/ioutil"
"os"
"path/filepath"
"syscall"
"github.com/cloudflare/cfssl/log"
"github.com/docker/machine/libmachine/mcnutils"
)
func createDiskImage(sshKeyPath, diskPath string, diskSize int) error {
tarBuf, err := mcnutils.MakeDiskImage(sshKeyPath)
if err != nil {
return err
}
file, err := os.OpenFile(diskPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644)
if err != nil {
return err
}
defer file.Close()
file.Seek(0, os.SEEK_SET)
if _, err := file.Write(tarBuf.Bytes()); err != nil {
return err
}
file.Close()
if err := os.Truncate(diskPath, int64(diskSize*1048576)); err != nil {
return err
}
return nil
}
func fixPermissions(path string) error {
os.Chown(path, syscall.Getuid(), syscall.Getegid())
files, _ := ioutil.ReadDir(path)
for _, f := range files {
fp := filepath.Join(path, f.Name())
log.Debugf(fp)
if err := os.Chown(fp, syscall.Getuid(), syscall.Getegid()); err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,271 @@
package hyperkit
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"syscall"
"time"
"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"
hyperkit "github.com/moby/hyperkit/go"
"github.com/pborman/uuid"
vmnet "github.com/zchee/go-vmnet"
commonutil "k8s.io/minikube/pkg/util"
)
const (
isoFilename = "boot2docker.iso"
pidFileName = "hyperkit.pid"
machineFileName = "hyperkit.json"
)
type Driver struct {
*drivers.BaseDriver
Boot2DockerURL string
DiskSize int
CPU int
Memory int
Cmdline string
}
func NewDriver(hostName, storePath string) *Driver {
return &Driver{
BaseDriver: &drivers.BaseDriver{
SSHUser: "docker",
},
}
}
func (d *Driver) Create() error {
b2dutils := mcnutils.NewB2dUtils(d.StorePath)
if err := ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil {
return err
}
if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil {
return err
}
isoPath := d.ResolveStorePath(isoFilename)
if err := d.extractKernel(isoPath); err != nil {
return err
}
return d.Start()
}
// DriverName returns the name of the driver
func (d *Driver) DriverName() string {
return "hyperkit"
}
// GetCreateFlags returns the mcnflag.Flag slice representing the flags
// that can be set, their descriptions and defaults.
func (d *Driver) GetCreateFlags() []mcnflag.Flag {
return nil
}
// GetSSHHostname returns hostname for use with ssh
func (d *Driver) GetSSHHostname() (string, error) {
return d.IPAddress, nil
}
// GetURL returns a Docker compatible host URL for connecting to this host
// e.g. tcp://1.2.3.4:2376
func (d *Driver) GetURL() (string, error) {
ip, err := d.GetIP()
if err != nil {
return "", err
}
return fmt.Sprintf("tcp://%s:2376", ip), nil
}
// GetState returns the state that the host is in (running, stopped, etc)
func (d *Driver) GetState() (state.State, error) {
pid := d.getPid()
if pid == 0 {
return state.Stopped, nil
}
p, err := os.FindProcess(pid)
if err != nil {
return state.Error, err
}
// Sending a signal of 0 can be used to check the existence of a process.
if err := p.Signal(syscall.Signal(0)); err != nil {
return state.Stopped, nil
}
if p == nil {
return state.Stopped, nil
}
return state.Running, nil
}
// Kill stops a host forcefully
func (d *Driver) Kill() error {
return d.sendSignal(syscall.SIGKILL)
}
// PreCreateCheck allows for pre-create operations to make sure a driver is ready for creation
func (d *Driver) PreCreateCheck() error {
return nil
}
// Remove a host
func (d *Driver) Remove() error {
s, err := d.GetState()
if err != nil || s == state.Error {
log.Infof("Error checking machine status: %s, assuming it has been removed already", err)
}
if s == state.Running {
if err := d.Stop(); err != nil {
return err
}
}
return nil
}
// Restart a host. This may just call Stop(); Start() if the provider does not
// have any special restart behaviour.
func (d *Driver) Restart() error {
for _, f := range []func() error{d.Stop, d.Start} {
if err := f(); err != nil {
return err
}
}
return nil
}
// SetConfigFromFlags configures the driver with the object that was returned
// by RegisterCreateFlags
func (d *Driver) SetConfigFromFlags(opts drivers.DriverOptions) error {
return nil
}
// Start a host
func (d *Driver) Start() error {
// TODO: handle different disk types.
diskPath := filepath.Join(d.ResolveStorePath("."), d.MachineName+".rawdisk")
if _, err := os.Stat(diskPath); os.IsNotExist(err) {
if err := createDiskImage(d.publicSSHKeyPath(), diskPath, d.DiskSize); err != nil {
return err
}
if err := fixPermissions(d.ResolveStorePath(".")); err != nil {
return err
}
}
h, err := hyperkit.New("", "", filepath.Join(d.StorePath, "machines", d.MachineName))
if err != nil {
return err
}
// TODO: handle the rest of our settings.
h.Kernel = d.ResolveStorePath("bzimage")
h.Initrd = d.ResolveStorePath("initrd")
h.VMNet = true
h.ISOImage = d.ResolveStorePath(isoFilename)
h.Console = hyperkit.ConsoleFile
h.CPUs = d.CPU
h.Memory = d.Memory
// Set UUID
h.UUID = uuid.NewUUID().String()
log.Infof("Generated UUID %s", h.UUID)
mac, err := vmnet.GetMACAddressFromUUID(h.UUID)
if err != nil {
return err
}
// Need to strip 0's
mac = trimMacAddress(mac)
log.Infof("Generated MAC %s", mac)
h.Disks = []hyperkit.DiskConfig{
{
Path: diskPath,
Size: d.DiskSize,
Driver: "virtio-blk",
},
}
log.Infof("Starting with cmdline: %s", d.Cmdline)
if err := h.Start(d.Cmdline); err != nil {
return err
}
getIP := func() error {
var err error
d.IPAddress, err = GetIPAddressByMACAddress(mac)
if err != nil {
return &commonutil.RetriableError{Err: err}
}
return nil
}
if err := commonutil.RetryAfter(30, getIP, 2*time.Second); err != nil {
return fmt.Errorf("IP address never found in dhcp leases file %v", err)
}
return nil
}
// Stop a host gracefully
func (d *Driver) Stop() error {
return d.sendSignal(syscall.SIGTERM)
}
func (d *Driver) extractKernel(isoPath string) error {
for _, f := range []struct {
pathInIso string
destPath string
}{
{"/boot/bzimage", "bzimage"},
{"/boot/initrd", "initrd"},
{"/isolinux/isolinux.cfg", "isolinux.cfg"},
} {
fullDestPath := d.ResolveStorePath(f.destPath)
if err := ExtractFile(isoPath, f.pathInIso, fullDestPath); err != nil {
return err
}
}
return nil
}
func (d *Driver) publicSSHKeyPath() string {
return d.GetSSHKeyPath() + ".pub"
}
func (d *Driver) sendSignal(s os.Signal) error {
pid := d.getPid()
proc, err := os.FindProcess(pid)
if err != nil {
return err
}
return proc.Signal(s)
}
func (d *Driver) getPid() int {
pidPath := d.ResolveStorePath(machineFileName)
f, err := os.Open(pidPath)
if err != nil {
log.Warnf("Error reading pid file: %s", err)
return 0
}
dec := json.NewDecoder(f)
config := hyperkit.HyperKit{}
if err := dec.Decode(&config); err != nil {
log.Warnf("Error decoding pid file: %s", err)
return 0
}
return config.Pid
}

View File

@ -0,0 +1,70 @@
package hyperkit
import (
"fmt"
"io"
"io/ioutil"
"os"
"strings"
"github.com/hooklift/iso9660"
)
func ExtractFile(isoPath, srcPath, destPath string) error {
iso, err := os.Open(isoPath)
defer iso.Close()
if err != nil {
return err
}
r, err := iso9660.NewReader(iso)
if err != nil {
return err
}
f, err := findFile(r, srcPath)
if err != nil {
return err
}
dst, err := os.Create(destPath)
defer dst.Close()
if err != nil {
return err
}
_, err = io.Copy(dst, f.Sys().(io.Reader))
return err
}
func ReadFile(isoPath, srcPath string) (string, error) {
iso, err := os.Open(isoPath)
defer iso.Close()
if err != nil {
return "", err
}
r, err := iso9660.NewReader(iso)
if err != nil {
return "", err
}
f, err := findFile(r, srcPath)
if err != nil {
return "", err
}
contents, err := ioutil.ReadAll(f.Sys().(io.Reader))
return string(contents), err
}
func findFile(r *iso9660.Reader, path string) (os.FileInfo, error) {
for f, err := r.Next(); err != io.EOF; f, err = r.Next() {
// Some files get an extra ',' at the end.
if strings.TrimSuffix(f.Name(), ".") == path {
return f, nil
}
}
return nil, fmt.Errorf("Unable to find file %s.", path)
}

View File

@ -0,0 +1,87 @@
package hyperkit
import (
"bufio"
"fmt"
"io"
"os"
"regexp"
"strings"
)
const (
DHCPLeasesFile = "/var/db/dhcpd_leases"
)
type DHCPEntry struct {
Name string
IPAddress string
HWAddress string
ID string
Lease string
}
func GetIPAddressByMACAddress(mac string) (string, error) {
file, err := os.Open(DHCPLeasesFile)
if err != nil {
return "", err
}
defer file.Close()
dhcpEntries, err := parseDHCPdLeasesFile(file)
if err != nil {
return "", err
}
for _, dhcpEntry := range dhcpEntries {
if dhcpEntry.HWAddress == mac {
return dhcpEntry.IPAddress, nil
}
}
return "", fmt.Errorf("Could not find an IP address for %s", mac)
}
func parseDHCPdLeasesFile(file io.Reader) ([]DHCPEntry, error) {
var (
dhcpEntry *DHCPEntry
dhcpEntries []DHCPEntry
)
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "{" {
dhcpEntry = new(DHCPEntry)
continue
} else if line == "}" {
dhcpEntries = append(dhcpEntries, *dhcpEntry)
continue
}
split := strings.SplitN(line, "=", 2)
key, val := split[0], split[1]
switch key {
case "name":
dhcpEntry.Name = val
case "ip_address":
dhcpEntry.IPAddress = val
case "hw_address":
// The mac addresses have a '1,' at the start.
dhcpEntry.HWAddress = val[2:]
case "identifier":
dhcpEntry.ID = val
case "lease":
dhcpEntry.Lease = val
default:
return dhcpEntries, fmt.Errorf("Unable to parse line: %s", line)
}
}
return dhcpEntries, scanner.Err()
}
// trimMacAddress trimming "0" of the ten's digit
func trimMacAddress(rawUUID string) string {
re := regexp.MustCompile(`0([A-Fa-f0-9](:|$))`)
mac := re.ReplaceAllString(rawUUID, "$1")
return mac
}

View File

@ -57,7 +57,8 @@ Please use this plugin through the main 'docker-machine' binary.
continue
case <-time.After(heartbeatTimeout):
// TODO: Add heartbeat retry logic
os.Exit(1)
continue
// os.Exit(1)
}
}
}