From f6465bb7934fa67e9d27d97e9597503d9ab602ab Mon Sep 17 00:00:00 2001 From: Sharif Elgamal Date: Wed, 11 Sep 2019 15:07:29 -0700 Subject: [PATCH 1/3] Moving minikube hyperkit changes upstream --- main.go | 14 +- pkg/drivers/drivers.go | 67 +++++--- pkg/hyperkit/driver.go | 366 +++++++++++++++++++++++----------------- pkg/hyperkit/network.go | 48 ++++-- pkg/hyperkit/util.go | 106 ------------ pkg/hyperkit/vmnet.go | 4 +- 6 files changed, 298 insertions(+), 307 deletions(-) delete mode 100644 pkg/hyperkit/util.go diff --git a/main.go b/main.go index 37e4d17..32788b0 100644 --- a/main.go +++ b/main.go @@ -2,10 +2,13 @@ /* 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. @@ -16,10 +19,19 @@ limitations under the License. package main import ( + "fmt" + "os" + "github.com/docker/machine/libmachine/drivers/plugin" - "github.com/machine-drivers/docker-machine-driver-hyperkit/pkg/hyperkit" + "k8s.io/minikube/pkg/drivers/hyperkit" ) func main() { + if len(os.Args) > 1 && os.Args[1] == "version" { + fmt.Println("version:", hyperkit.GetVersion()) + fmt.Println("commit:", hyperkit.GetGitCommitID()) + return + } + plugin.RegisterDriver(hyperkit.NewDriver("", "")) } diff --git a/pkg/drivers/drivers.go b/pkg/drivers/drivers.go index 5ac3c3b..ca5262f 100644 --- a/pkg/drivers/drivers.go +++ b/pkg/drivers/drivers.go @@ -17,31 +17,34 @@ limitations under the License. package drivers import ( + "io" "io/ioutil" "os" "path/filepath" "syscall" - "github.com/cloudflare/cfssl/log" "github.com/docker/machine/libmachine/drivers" "github.com/docker/machine/libmachine/mcnflag" "github.com/docker/machine/libmachine/mcnutils" "github.com/docker/machine/libmachine/ssh" + "github.com/golang/glog" "github.com/pkg/errors" ) +// GetDiskPath returns the path of the machine disk image func GetDiskPath(d *drivers.BaseDriver) string { return filepath.Join(d.ResolveStorePath("."), d.GetMachineName()+".rawdisk") } +// CommonDriver is the common driver base class type CommonDriver struct{} -//Not implemented yet +// GetCreateFlags is not implemented yet func (d *CommonDriver) GetCreateFlags() []mcnflag.Flag { return nil } -//Not implemented yet +// SetConfigFromFlags is not implemented yet func (d *CommonDriver) SetConfigFromFlags(flags drivers.DriverOptions) error { return nil } @@ -49,25 +52,27 @@ func (d *CommonDriver) SetConfigFromFlags(flags drivers.DriverOptions) error { func createRawDiskImage(sshKeyPath, diskPath string, diskSizeMb int) error { tarBuf, err := mcnutils.MakeDiskImage(sshKeyPath) if err != nil { - return err + return errors.Wrap(err, "make disk image") } file, err := os.OpenFile(diskPath, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0644) if err != nil { - return err + return errors.Wrap(err, "open") } defer file.Close() - file.Seek(0, os.SEEK_SET) + if _, err := file.Seek(0, io.SeekStart); err != nil { + return errors.Wrap(err, "seek") + } if _, err := file.Write(tarBuf.Bytes()); err != nil { - return err + return errors.Wrap(err, "write tar") } if err := file.Close(); err != nil { return errors.Wrapf(err, "closing file %s", diskPath) } if err := os.Truncate(diskPath, int64(diskSizeMb*1000000)); err != nil { - return err + return errors.Wrap(err, "truncate") } return nil } @@ -79,46 +84,54 @@ func publicSSHKeyPath(d *drivers.BaseDriver) string { // Restart a host. This may just call Stop(); Start() if the provider does not // have any special restart behaviour. func Restart(d drivers.Driver) error { - for _, f := range []func() error{d.Stop, d.Start} { - if err := f(); err != nil { - return err - } + if err := d.Stop(); err != nil { + return err } - return nil + + return d.Start() } +// MakeDiskImage makes a boot2docker VM disk image. func MakeDiskImage(d *drivers.BaseDriver, boot2dockerURL string, diskSize int) error { - //TODO(r2d4): rewrite this, not using b2dutils - b2dutils := mcnutils.NewB2dUtils(d.StorePath) - if err := b2dutils.CopyIsoToMachineDir(boot2dockerURL, d.MachineName); err != nil { - return errors.Wrap(err, "Error copying ISO to machine dir") + glog.Infof("Making disk image using store path: %s", d.StorePath) + b2 := mcnutils.NewB2dUtils(d.StorePath) + if err := b2.CopyIsoToMachineDir(boot2dockerURL, d.MachineName); err != nil { + return errors.Wrap(err, "copy iso to machine dir") } - log.Info("Creating ssh key...") - if err := ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil { - return err + keyPath := d.GetSSHKeyPath() + glog.Infof("Creating ssh key: %s...", keyPath) + if err := ssh.GenerateSSHKey(keyPath); err != nil { + return errors.Wrap(err, "generate ssh key") } - log.Info("Creating raw disk image...") diskPath := GetDiskPath(d) + glog.Infof("Creating raw disk image: %s...", diskPath) if _, err := os.Stat(diskPath); os.IsNotExist(err) { if err := createRawDiskImage(publicSSHKeyPath(d), diskPath, diskSize); err != nil { - return err + return errors.Wrapf(err, "createRawDiskImage(%s)", diskPath) } - if err := fixPermissions(d.ResolveStorePath(".")); err != nil { - return err + machPath := d.ResolveStorePath(".") + if err := fixPermissions(machPath); err != nil { + return errors.Wrapf(err, "fixing permissions on %s", machPath) } } return nil } func fixPermissions(path string) error { - os.Chown(path, syscall.Getuid(), syscall.Getegid()) - files, _ := ioutil.ReadDir(path) + glog.Infof("Fixing permissions on %s ...", path) + if err := os.Chown(path, syscall.Getuid(), syscall.Getegid()); err != nil { + return errors.Wrap(err, "chown dir") + } + files, err := ioutil.ReadDir(path) + if err != nil { + return errors.Wrap(err, "read dir") + } for _, f := range files { fp := filepath.Join(path, f.Name()) if err := os.Chown(fp, syscall.Getuid(), syscall.Getegid()); err != nil { - return err + return errors.Wrap(err, "chown file") } } return nil diff --git a/pkg/hyperkit/driver.go b/pkg/hyperkit/driver.go index 84ac8e8..a995fca 100644 --- a/pkg/hyperkit/driver.go +++ b/pkg/hyperkit/driver.go @@ -21,10 +21,13 @@ package hyperkit import ( "encoding/json" "fmt" + "io/ioutil" + golog "log" "os" "os/user" "path" "path/filepath" + "strconv" "strings" "syscall" "time" @@ -32,17 +35,15 @@ import ( "github.com/docker/machine/libmachine/drivers" "github.com/docker/machine/libmachine/log" "github.com/docker/machine/libmachine/state" - nfsexports "github.com/johanneswuerbach/nfsexports" + "github.com/johanneswuerbach/nfsexports" + pkgdrivers "github.com/machine-drivers/docker-machine-driver-hyperkit/pkg/drivers" + ps "github.com/mitchellh/go-ps" hyperkit "github.com/moby/hyperkit/go" "github.com/pkg/errors" - pkgdrivers "github.com/machine-drivers/docker-machine-driver-hyperkit/pkg/drivers" - "regexp" - "github.com/docker/machine/libmachine/mcnutils" ) const ( isoFilename = "boot2docker.iso" - isoMountPath = "b2d-image" pidFileName = "hyperkit.pid" machineFileName = "hyperkit.json" permErr = "%s needs to run with elevated permissions. " + @@ -50,11 +51,7 @@ const ( "sudo chown root:wheel %s && sudo chmod u+s %s" ) -var ( - kernelRegexp = regexp.MustCompile(`(vmlinu[xz]|bzImage)[\d]*`) - kernelOptionRegexp = regexp.MustCompile(`(?:\t|\s{2})append\s+([[:print:]]+)`) -) - +// Driver is the machine driver for Hyperkit type Driver struct { *drivers.BaseDriver *pkgdrivers.CommonDriver @@ -66,12 +63,11 @@ type Driver struct { NFSShares []string NFSSharesRoot string UUID string - BootKernel string - BootInitrd string - Initrd string - Vmlinuz string + VpnKitSock string + VSockPorts []string } +// NewDriver creates a new driver for a host func NewDriver(hostName, storePath string) *Driver { return &Driver{ BaseDriver: &drivers.BaseDriver{ @@ -83,19 +79,29 @@ func NewDriver(hostName, storePath string) *Driver { // PreCreateCheck is called to enforce pre-creation steps func (d *Driver) PreCreateCheck() error { + return d.verifyRootPermissions() +} + +// verifyRootPermissions is called before any step which needs root access +func (d *Driver) verifyRootPermissions() error { exe, err := os.Executable() if err != nil { return err } - - if syscall.Geteuid() != 0 { + euid := syscall.Geteuid() + log.Debugf("exe=%s uid=%d", exe, euid) + if euid != 0 { return fmt.Errorf(permErr, filepath.Base(exe), exe, exe) } - return nil } +// Create a host using the driver's config func (d *Driver) Create() error { + if err := d.verifyRootPermissions(); err != nil { + return err + } + // TODO: handle different disk types. if err := pkgdrivers.MakeDiskImage(d.BaseDriver, d.Boot2DockerURL, d.DiskSize); err != nil { return errors.Wrap(err, "making disk image") @@ -103,7 +109,7 @@ func (d *Driver) Create() error { isoPath := d.ResolveStorePath(isoFilename) if err := d.extractKernel(isoPath); err != nil { - return err + return errors.Wrap(err, "extracting kernel") } return d.Start() @@ -129,37 +135,55 @@ func (d *Driver) GetURL() (string, error) { 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() +// Return the state of the hyperkit pid +func pidState(pid int) (state.State, error) { if pid == 0 { return state.Stopped, nil } - p, err := os.FindProcess(pid) + p, err := ps.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 { + if p == nil { + log.Debugf("hyperkit pid %d missing from process table", pid) return state.Stopped, nil } - if p == nil { + // hyperkit or com.docker.hyper + if !strings.Contains(p.Executable(), "hyper") { + log.Debugf("pid %d is stale, and is being used by %s", pid, p.Executable()) return state.Stopped, nil } return state.Running, nil } +// GetState returns the state that the host is in (running, stopped, etc) +func (d *Driver) GetState() (state.State, error) { + if err := d.verifyRootPermissions(); err != nil { + return state.Error, err + } + + pid := d.getPid() + log.Debugf("hyperkit pid from json: %d", pid) + return pidState(pid) +} + // Kill stops a host forcefully func (d *Driver) Kill() error { + if err := d.verifyRootPermissions(); err != nil { + return err + } return d.sendSignal(syscall.SIGKILL) } // Remove a host func (d *Driver) Remove() error { + if err := d.verifyRootPermissions(); err != nil { + return err + } + s, err := d.GetState() if err != nil || s == state.Error { - log.Infof("Error checking machine status: %s, assuming it has been removed already", err) + log.Debugf("Error checking machine status: %v, assuming it has been removed already", err) } if s == state.Running { if err := d.Stop(); err != nil { @@ -169,36 +193,55 @@ func (d *Driver) Remove() error { return nil } +// Restart a host func (d *Driver) Restart() error { return pkgdrivers.Restart(d) } // Start a host func (d *Driver) Start() error { - h, err := hyperkit.New("", "", filepath.Join(d.StorePath, "machines", d.MachineName)) - if err != nil { + if err := d.verifyRootPermissions(); err != nil { + return err + } + + stateDir := filepath.Join(d.StorePath, "machines", d.MachineName) + if err := d.recoverFromUncleanShutdown(); err != nil { return err } + h, err := hyperkit.New("", d.VpnKitSock, stateDir) + if err != nil { + return errors.Wrap(err, "new-ing Hyperkit") + } // TODO: handle the rest of our settings. - h.Kernel = d.ResolveStorePath(d.Vmlinuz) - h.Initrd =d.ResolveStorePath(d.Initrd) + h.Kernel = d.ResolveStorePath("bzimage") + h.Initrd = d.ResolveStorePath("initrd") h.VMNet = true h.ISOImages = []string{d.ResolveStorePath(isoFilename)} h.Console = hyperkit.ConsoleFile h.CPUs = d.CPU h.Memory = d.Memory h.UUID = d.UUID + // This should stream logs from hyperkit, but doesn't seem to work. + logger := golog.New(os.Stderr, "hyperkit", golog.LstdFlags) + h.SetLogger(logger) + + if vsockPorts, err := d.extractVSockPorts(); err != nil { + return err + } else if len(vsockPorts) >= 1 { + h.VSock = true + h.VSockPorts = vsockPorts + } - log.Infof("Using UUID %s", h.UUID) + log.Debugf("Using UUID %s", h.UUID) mac, err := GetMACAddressFromUUID(h.UUID) if err != nil { - return err + return errors.Wrap(err, "getting MAC address from UUID") } // Need to strip 0's mac = trimMacAddress(mac) - log.Infof("Generated MAC %s", mac) + log.Debugf("Generated MAC %s", mac) h.Disks = []hyperkit.DiskConfig{ { Path: pkgdrivers.GetDiskPath(d.BaseDriver), @@ -206,37 +249,52 @@ func (d *Driver) Start() error { Driver: "virtio-blk", }, } - log.Infof("Starting with cmdline: %s", d.Cmdline) + log.Debugf("Starting with cmdline: %s", d.Cmdline) if err := h.Start(d.Cmdline); err != nil { - return err + return errors.Wrapf(err, "starting with cmd line: %s", d.Cmdline) } getIP := func() error { - var err error + st, err := d.GetState() + if err != nil { + return errors.Wrap(err, "get state") + } + if st == state.Error || st == state.Stopped { + return fmt.Errorf("hyperkit crashed! command line:\n hyperkit %s", d.Cmdline) + } + d.IPAddress, err = GetIPAddressByMACAddress(mac) if err != nil { - return &RetriableError{Err: err} + return &tempError{err} } return nil } - if err := RetryAfter(30, getIP, 2*time.Second); err != nil { + for i := 0; i < 30; i++ { + log.Debugf("Attempt %d", i) + err = getIP() + if err == nil { + break + } + if _, ok := err.(*tempError); !ok { + return err + } + time.Sleep(2 * time.Second) + } + + if err != nil { return fmt.Errorf("IP address never found in dhcp leases file %v", err) } + log.Debugf("IP: %s", d.IPAddress) if len(d.NFSShares) > 0 { log.Info("Setting up NFS mounts") - // takes some time here for ssh / nfsd to work properly - err = d.waitForIP() - if err != nil { - log.Errorf("Failed to get IP address for VM: %s", err.Error()) - return err - } - + time.Sleep(time.Second * 30) err = d.setupNFSShare() if err != nil { - log.Errorf("NFS setup failed: %s", err.Error()) + // TODO(tstromberg): Check that logging an and error and return it is appropriate. Seems weird. + log.Errorf("NFS setup failed: %v", err) return err } } @@ -244,64 +302,127 @@ func (d *Driver) Start() error { return nil } -// Stop a host gracefully -func (d *Driver) Stop() error { - d.cleanupNfsExports() - return d.sendSignal(syscall.SIGTERM) +type tempError struct { + Err error } -func (d *Driver) extractKernel(isoPath string) error { - log.Debugf("Mounting %s", isoFilename) +func (t tempError) Error() string { + return "Temporary error: " + t.Err.Error() +} + +//recoverFromUncleanShutdown searches for an existing hyperkit.pid file in +//the machine directory. If it can't find it, a clean shutdown is assumed. +//If it finds the pid file, it checks for a running hyperkit process with that pid +//as the existence of a file might not indicate an unclean shutdown but an actual running +//hyperkit server. If the PID in the pidfile does not belong to a running hyperkit +//process, we can safely delete it, and there is a good chance the machine will recover when restarted. +func (d *Driver) recoverFromUncleanShutdown() error { + stateDir := filepath.Join(d.StorePath, "machines", d.MachineName) + pidFile := filepath.Join(stateDir, pidFileName) + + if _, err := os.Stat(pidFile); err != nil { + if os.IsNotExist(err) { + log.Debugf("clean start, hyperkit pid file doesn't exist: %s", pidFile) + return nil + } + return errors.Wrap(err, "stat") + } - volumeRootDir := d.ResolveStorePath(isoMountPath) - err := hdiutil("attach", d.ResolveStorePath(isoFilename), "-mountpoint", volumeRootDir) + log.Warnf("hyperkit pid file still exists: %s", pidFile) + bs, err := ioutil.ReadFile(pidFile) if err != nil { - return err + return errors.Wrapf(err, "reading pidfile %s", pidFile) + } + content := strings.TrimSpace(string(bs)) + pid, err := strconv.Atoi(content) + if err != nil { + return errors.Wrapf(err, "parsing pidfile %s", pidFile) } - defer func() error { - log.Debugf("Unmounting %s", isoFilename) - return hdiutil("detach", volumeRootDir) - }() - log.Debugf("Extracting Kernel Options...") - if err := d.extractKernelOptions(); err != nil { - return err + st, err := pidState(pid) + if err != nil { + return errors.Wrap(err, "pidState") } - if d.BootKernel == "" && d.BootInitrd == "" { - filepath.Walk(volumeRootDir, func(path string, f os.FileInfo, err error) error { - if kernelRegexp.MatchString(path) { - d.BootKernel = path - _, d.Vmlinuz = filepath.Split(path) - } - if strings.Contains(path, "initrd") { - d.BootInitrd = path - _, d.Initrd = filepath.Split(path) - } - return nil - }) + log.Debugf("pid %d is in state %q", pid, st) + if st == state.Running { + return nil } - - if d.BootKernel == "" || d.BootInitrd == "" { - err := fmt.Errorf("==== Can't extract Kernel and Ramdisk file ====") - return err - } + log.Debugf("Removing stale pid file %s...", pidFile) + if err := os.Remove(pidFile); err != nil { + return errors.Wrap(err, fmt.Sprintf("removing pidFile %s", pidFile)) + } + return nil +} - dest := d.ResolveStorePath(d.Vmlinuz) - log.Debugf("Extracting %s into %s", d.BootKernel, dest) - if err := mcnutils.CopyFile(d.BootKernel, dest); err != nil { +// Stop a host gracefully +func (d *Driver) Stop() error { + if err := d.verifyRootPermissions(); err != nil { return err } + d.cleanupNfsExports() + err := d.sendSignal(syscall.SIGTERM) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("hyperkit sigterm failed")) + } - dest = d.ResolveStorePath(d.Initrd) - log.Debugf("Extracting %s into %s", d.BootInitrd, dest) - if err := mcnutils.CopyFile(d.BootInitrd, dest); err != nil { - return err + // wait 5s for graceful shutdown + for i := 0; i < 5; i++ { + log.Debug("waiting for graceful shutdown") + time.Sleep(time.Second * 1) + s, err := d.GetState() + if err != nil { + return errors.Wrap(err, fmt.Sprintf("hyperkit waiting graceful shutdown failed")) + } + if s == state.Stopped { + return nil + } } + log.Debug("sending sigkill") + return d.Kill() +} + +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 } +// InvalidPortNumberError implements the Error interface. +// It is used when a VSockPorts port number cannot be recognised as an integer. +type InvalidPortNumberError string + +// Error returns an Error for InvalidPortNumberError +func (port InvalidPortNumberError) Error() string { + return fmt.Sprintf("vsock port '%s' is not an integer", string(port)) +} + +func (d *Driver) extractVSockPorts() ([]int, error) { + vsockPorts := make([]int, 0, len(d.VSockPorts)) + + for _, port := range d.VSockPorts { + p, err := strconv.Atoi(port) + if err != nil { + return nil, InvalidPortNumberError(port) + } + vsockPorts = append(vsockPorts, p) + } + + return vsockPorts, nil +} + func (d *Driver) setupNFSShare() error { user, err := user.Current() if err != nil { @@ -367,13 +488,13 @@ func (d *Driver) getPid() int { f, err := os.Open(pidPath) if err != nil { - log.Warnf("Error reading pid file: %s", err) + log.Warnf("Error reading pid file: %v", 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) + log.Warnf("Error decoding pid file: %v", err) return 0 } @@ -385,75 +506,12 @@ func (d *Driver) cleanupNfsExports() { log.Infof("You must be root to remove NFS shared folders. Please type root password.") for _, share := range d.NFSShares { if _, err := nfsexports.Remove("", d.nfsExportIdentifier(share)); err != nil { - log.Errorf("failed removing nfs share (%s): %s", share, err.Error()) + log.Errorf("failed removing nfs share (%s): %v", share, err) } } if err := nfsexports.ReloadDaemon(); err != nil { - log.Errorf("failed to reload the nfs daemon: %s", err.Error()) - } - } -} - -func (d *Driver) extractKernelOptions() error { - volumeRootDir := d.ResolveStorePath(isoMountPath) - if d.Cmdline == "" { - err := filepath.Walk(volumeRootDir, func(path string, f os.FileInfo, err error) error { - if strings.Contains(path, "isolinux.cfg") { - d.Cmdline, err = readLine(path) - if err != nil { - return err - } - } - return nil - }) - if err != nil { - return err - } - - if d.Cmdline == "" { - return errors.New("Not able to parse isolinux.cfg") + log.Errorf("failed to reload the nfs daemon: %v", err) } } - - log.Debugf("Extracted Options %q", d.Cmdline) - return nil -} - -func (d *Driver) waitForIP() error { - var ip string - var err error - mac, err := GetMACAddressFromUUID(d.UUID) - if err != nil { - return err - } - - log.Infof("Waiting for VM to come online...") - for i := 1; i <= 60; i++ { - - ip, err = GetIPAddressByMACAddress(mac) - if err != nil { - log.Debugf("Not there yet %d/%d, error: %s", i, 60, err) - time.Sleep(2 * time.Second) - continue - } - - if ip != "" { - log.Debugf("Got an ip: %s", ip) - d.IPAddress = ip - - break - } - } - - if ip == "" { - return fmt.Errorf("Machine didn't return an IP after 120 seconds, aborting") - } - - // Wait for SSH over NAT to be available before returning to user - if err := drivers.WaitForSSH(d); err != nil { - return err - } - - return nil } diff --git a/pkg/hyperkit/network.go b/pkg/hyperkit/network.go index 61ff3b0..2f5c46b 100644 --- a/pkg/hyperkit/network.go +++ b/pkg/hyperkit/network.go @@ -1,3 +1,5 @@ +// +build darwin + /* Copyright 2016 The Kubernetes Authors All rights reserved. @@ -25,14 +27,24 @@ import ( "os/exec" "regexp" "strings" + + "github.com/docker/machine/libmachine/log" ) const ( - DHCPLeasesFile = "/var/db/dhcpd_leases" - CONFIG_PLIST = "/Library/Preferences/SystemConfiguration/com.apple.vmnet" - NET_ADDR_KEY = "Shared_Net_Address" + // LeasesPath is the path to dhcpd leases + LeasesPath = "/var/db/dhcpd_leases" + // VMNetDomain is the domain for vmnet + VMNetDomain = "/Library/Preferences/SystemConfiguration/com.apple.vmnet" + // SharedNetAddrKey is the key for the network address + SharedNetAddrKey = "Shared_Net_Address" +) + +var ( + leadingZeroRegexp = regexp.MustCompile(`0([A-Fa-f0-9](:|$))`) ) +// DHCPEntry holds a parsed DNS entry type DHCPEntry struct { Name string IPAddress string @@ -41,11 +53,13 @@ type DHCPEntry struct { Lease string } +// GetIPAddressByMACAddress gets the IP address of a MAC address func GetIPAddressByMACAddress(mac string) (string, error) { - return getIpAddressFromFile(mac, DHCPLeasesFile) + return getIPAddressFromFile(mac, LeasesPath) } -func getIpAddressFromFile(mac, path string) (string, error) { +func getIPAddressFromFile(mac, path string) (string, error) { + log.Debugf("Searching for %s in %s ...", mac, path) file, err := os.Open(path) if err != nil { return "", err @@ -56,12 +70,15 @@ func getIpAddressFromFile(mac, path string) (string, error) { if err != nil { return "", err } + log.Debugf("Found %d entries in %s!", len(dhcpEntries), path) for _, dhcpEntry := range dhcpEntries { + log.Debugf("dhcp entry: %+v", dhcpEntry) if dhcpEntry.HWAddress == mac { + log.Debugf("Found match: %s", mac) return dhcpEntry.IPAddress, nil } } - return "", fmt.Errorf("Could not find an IP address for %s", mac) + return "", fmt.Errorf("could not find an IP address for %s", mac) } func parseDHCPdLeasesFile(file io.Reader) ([]DHCPEntry, error) { @@ -99,7 +116,7 @@ func parseDHCPdLeasesFile(file io.Reader) ([]DHCPEntry, error) { case "lease": dhcpEntry.Lease = val default: - return dhcpEntries, fmt.Errorf("Unable to parse line: %s", line) + return dhcpEntries, fmt.Errorf("unable to parse line: %s", line) } } return dhcpEntries, scanner.Err() @@ -107,25 +124,22 @@ func parseDHCPdLeasesFile(file io.Reader) ([]DHCPEntry, error) { // 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 + return leadingZeroRegexp.ReplaceAllString(rawUUID, "$1") } +// GetNetAddr gets the network address for vmnet func GetNetAddr() (net.IP, error) { - _, err := os.Stat(CONFIG_PLIST + ".plist") - if err != nil { - return nil, fmt.Errorf("Does not exist %s", CONFIG_PLIST+".plist") + plistPath := VMNetDomain + ".plist" + if _, err := os.Stat(plistPath); err != nil { + return nil, fmt.Errorf("stat: %v", err) } - - out, err := exec.Command("defaults", "read", CONFIG_PLIST, NET_ADDR_KEY).Output() + out, err := exec.Command("defaults", "read", VMNetDomain, SharedNetAddrKey).Output() if err != nil { return nil, err } ip := net.ParseIP(strings.TrimSpace(string(out))) if ip == nil { - return nil, fmt.Errorf("Could not get the network address for vmnet") + return nil, fmt.Errorf("could not get the network address for vmnet") } return ip, nil } diff --git a/pkg/hyperkit/util.go b/pkg/hyperkit/util.go deleted file mode 100644 index 4d3f1d6..0000000 --- a/pkg/hyperkit/util.go +++ /dev/null @@ -1,106 +0,0 @@ -/* -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 hyperkit - -import ( - "time" - "errors" - "strings" - "os/exec" - "os" - "github.com/docker/machine/libmachine/log" - "bufio" - "fmt" -) - -type RetriableError struct { - Err error -} - -func (r RetriableError) Error() string { - return "Temporary Error: " + r.Err.Error() -} - -type MultiError struct { - Errors []error -} - -func (m *MultiError) Collect(err error) { - if err != nil { - m.Errors = append(m.Errors, err) - } -} - -func (m MultiError) ToError() error { - if len(m.Errors) == 0 { - return nil - } - - errStrings := []string{} - for _, err := range m.Errors { - errStrings = append(errStrings, err.Error()) - } - return errors.New(strings.Join(errStrings, "\n")) -} - -func RetryAfter(attempts int, callback func() error, d time.Duration) (err error) { - m := MultiError{} - for i := 0; i < attempts; i++ { - err = callback() - if err == nil { - return nil - } - m.Collect(err) - if _, ok := err.(*RetriableError); !ok { - return m.ToError() - } - time.Sleep(d) - } - return m.ToError() -} - -func hdiutil(args ...string) error { - cmd := exec.Command("hdiutil", args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - log.Debugf("executing: %v %v", cmd, strings.Join(args, " ")) - - err := cmd.Run() - if err != nil { - return err - } - - return nil -} - -func readLine(path string) (string, error) { - inFile, err := os.Open(path) - if err != nil { - return "", err - } - defer inFile.Close() - - scanner := bufio.NewScanner(inFile) - for scanner.Scan() { - if kernelOptionRegexp.Match(scanner.Bytes()) { - m := kernelOptionRegexp.FindSubmatch(scanner.Bytes()) - return string(m[1]), nil - } - } - return "", fmt.Errorf("couldn't find kernel option from %s image", path) -} \ No newline at end of file diff --git a/pkg/hyperkit/vmnet.go b/pkg/hyperkit/vmnet.go index bcef540..fb1a7d0 100644 --- a/pkg/hyperkit/vmnet.go +++ b/pkg/hyperkit/vmnet.go @@ -22,6 +22,6 @@ import ( vmnet "github.com/zchee/go-vmnet" ) -func GetMACAddressFromUUID(UUID string) (string, error) { - return vmnet.GetMACAddressFromUUID(UUID) +func GetMACAddressFromUUID(id string) (string, error) { + return vmnet.GetMACAddressFromUUID(id) } From b3ae087727a1241197c2f38a6fb19092ad72b737 Mon Sep 17 00:00:00 2001 From: Sharif Elgamal Date: Wed, 11 Sep 2019 15:14:46 -0700 Subject: [PATCH 2/3] remove minikube import --- main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.go b/main.go index 32788b0..7b46735 100644 --- a/main.go +++ b/main.go @@ -23,7 +23,7 @@ import ( "os" "github.com/docker/machine/libmachine/drivers/plugin" - "k8s.io/minikube/pkg/drivers/hyperkit" + "github.com/machine-drivers/docker-machine-driver-hyperkit/pkg/hyperkit" ) func main() { From 7e53423cd01166d6731f244bbb9af75e54c2a955 Mon Sep 17 00:00:00 2001 From: Sharif Elgamal Date: Fri, 13 Sep 2019 16:24:01 -0700 Subject: [PATCH 3/3] add missing files --- pkg/drivers/drivers_test.go | 50 +++++++++++++++++ pkg/hyperkit/driver_test.go | 86 ++++++++++++++++++++++++++++ pkg/hyperkit/iso.go | 65 ++++++++++++++++++++++ pkg/hyperkit/network_test.go | 105 +++++++++++++++++++++++++++++++++++ pkg/hyperkit/version.go | 35 ++++++++++++ 5 files changed, 341 insertions(+) create mode 100644 pkg/drivers/drivers_test.go create mode 100644 pkg/hyperkit/driver_test.go create mode 100644 pkg/hyperkit/iso.go create mode 100644 pkg/hyperkit/network_test.go create mode 100644 pkg/hyperkit/version.go diff --git a/pkg/drivers/drivers_test.go b/pkg/drivers/drivers_test.go new file mode 100644 index 0000000..98982b9 --- /dev/null +++ b/pkg/drivers/drivers_test.go @@ -0,0 +1,50 @@ +/* +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 drivers + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" + + "k8s.io/minikube/pkg/minikube/tests" +) + +func Test_createDiskImage(t *testing.T) { + tmpdir := tests.MakeTempDir() + defer os.RemoveAll(tmpdir) + + sshPath := filepath.Join(tmpdir, "ssh") + if err := ioutil.WriteFile(sshPath, []byte("mysshkey"), 0644); err != nil { + t.Fatalf("writefile: %v", err) + } + diskPath := filepath.Join(tmpdir, "disk") + + sizeInMb := 100 + sizeInBytes := int64(sizeInMb) * 1000000 + if err := createRawDiskImage(sshPath, diskPath, sizeInMb); err != nil { + t.Errorf("createDiskImage() error = %v", err) + } + fi, err := os.Lstat(diskPath) + if err != nil { + t.Errorf("Lstat() error = %v", err) + } + if fi.Size() != sizeInBytes { + t.Errorf("Disk size is %v, want %v", fi.Size(), sizeInBytes) + } +} diff --git a/pkg/hyperkit/driver_test.go b/pkg/hyperkit/driver_test.go new file mode 100644 index 0000000..22d4ddb --- /dev/null +++ b/pkg/hyperkit/driver_test.go @@ -0,0 +1,86 @@ +// +build darwin + +/* +Copyright 2018 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 hyperkit + +import ( + "testing" +) + +func Test_portExtraction(t *testing.T) { + tests := []struct { + name string + ports []string + want []int + wantErr error + }{ + { + "valid_empty", + []string{}, + []int{}, + nil, + }, + { + "valid_list", + []string{"10", "20", "30"}, + []int{10, 20, 30}, + nil, + }, + { + "invalid", + []string{"8080", "not_an_integer"}, + nil, + InvalidPortNumberError("not_an_integer"), + }, + } + + for _, tt := range tests { + d := NewDriver("", "") + d.VSockPorts = tt.ports + got, gotErr := d.extractVSockPorts() + if !testEq(got, tt.want) { + t.Errorf("extractVSockPorts() got: %v, want: %v", got, tt.want) + } + if gotErr != tt.wantErr { + t.Errorf("extractVSockPorts() gotErr: %s, wantErr: %s", gotErr.Error(), tt.wantErr.Error()) + } + } +} + +func testEq(a, b []int) bool { + + if a == nil && b == nil { + return true + } + + if a == nil || b == nil { + return false + } + + if len(a) != len(b) { + return false + } + + for i := range a { + if a[i] != b[i] { + return false + } + } + + return true +} diff --git a/pkg/hyperkit/iso.go b/pkg/hyperkit/iso.go new file mode 100644 index 0000000..bed0187 --- /dev/null +++ b/pkg/hyperkit/iso.go @@ -0,0 +1,65 @@ +/* +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 hyperkit + +import ( + "fmt" + "io" + "os" + "strings" + + "github.com/hooklift/iso9660" +) + +// ExtractFile extracts a file from an ISO +func ExtractFile(isoPath, srcPath, destPath string) error { + iso, err := os.Open(isoPath) + if err != nil { + return err + } + defer iso.Close() + + 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) + if err != nil { + return err + } + defer dst.Close() + + _, err = io.Copy(dst, f.Sys().(io.Reader)) + return err +} + +func findFile(r *iso9660.Reader, path string) (os.FileInfo, error) { + // Look through the ISO for a file with a matching path. + for f, err := r.Next(); err != io.EOF; f, err = r.Next() { + // For some reason file paths in the ISO sometimes contain a '.' character at the end, so strip that off. + if strings.TrimSuffix(f.Name(), ".") == path { + return f, nil + } + } + return nil, fmt.Errorf("unable to find file %s", path) +} diff --git a/pkg/hyperkit/network_test.go b/pkg/hyperkit/network_test.go new file mode 100644 index 0000000..9b02e14 --- /dev/null +++ b/pkg/hyperkit/network_test.go @@ -0,0 +1,105 @@ +// +build darwin + +/* +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 hyperkit + +import ( + "io/ioutil" + "os" + "path/filepath" + "testing" +) + +var validLeases = []byte(`{ + name=foo + ip_address=1.2.3.4 + hw_address=1,a1:b2:c3:d4:e5:f6 + identifier=1,a2:b3:c4:d5:e6:f7 + lease=0x597e1267 +} +{ + name=bar + ip_address=192.168.64.3 + hw_address=1,a4:b5:c6:d7:e8:f9 + identifier=1,a0:b0:c0:d0:e0:f0 + lease=0x597e1267 +} +{ + name=bar + ip_address=192.168.64.4 + hw_address=1,a5:b6:c7:d8:e9:f1 + identifier=1,a5:b6:c7:d8:e9:f1 + lease=0x597e1268 +}`) + +func Test_getIpAddressFromFile(t *testing.T) { + tmpdir := ioutil.TempDir() + defer os.RemoveAll(tmpdir) + + dhcpFile := filepath.Join(tmpdir, "dhcp") + if err := ioutil.WriteFile(dhcpFile, validLeases, 0644); err != nil { + t.Fatalf("writefile: %v", err) + } + + invalidFile := filepath.Join(tmpdir, "invalid") + if err := ioutil.WriteFile(invalidFile, []byte("foo"), 0644); err != nil { + t.Fatalf("writefile: %v", err) + } + + type args struct { + mac string + path string + } + tests := []struct { + name string + args args + want string + wantErr bool + }{ + { + "valid", + args{"a1:b2:c3:d4:e5:f6", dhcpFile}, + "1.2.3.4", + false, + }, + { + "duplicate", + args{"a4:b5:c6:d7:e8:f9", dhcpFile}, + "192.168.64.3", + false, + }, + { + "invalid", + args{"a1:b2:c3:d4:e5:f6", invalidFile}, + "", + true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := getIPAddressFromFile(tt.args.mac, tt.args.path) + if (err != nil) != tt.wantErr { + t.Errorf("getIPAddressFromFile() error = %v, wantErr %v", err, tt.wantErr) + return + } + if got != tt.want { + t.Errorf("getIPAddressFromFile() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/pkg/hyperkit/version.go b/pkg/hyperkit/version.go new file mode 100644 index 0000000..249da2f --- /dev/null +++ b/pkg/hyperkit/version.go @@ -0,0 +1,35 @@ +/* +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 hyperkit + +// The current version of the docker-machine-driver-hyperkit + +// version is a private field and should be set when compiling with --ldflags="-X k8s.io/minikube/pkg/drivers/hyperkit.version=vX.Y.Z" +var version = "v0.0.0-unset" + +// gitCommitID is a private field and should be set when compiling with --ldflags="-X k8s.io/minikube/pkg/drivers/hyperkit.gitCommitID=" +var gitCommitID = "" + +// GetVersion returns the current docker-machine-driver-hyperkit version +func GetVersion() string { + return version +} + +// GetGitCommitID returns the git commit id from which it is being built +func GetGitCommitID() string { + return gitCommitID +}