diff --git a/cmd/minikube/cmd/start.go b/cmd/minikube/cmd/start.go index 2033a331a0ad..3ad89b75b36c 100644 --- a/cmd/minikube/cmd/start.go +++ b/cmd/minikube/cmd/start.go @@ -63,6 +63,7 @@ import ( "k8s.io/minikube/pkg/minikube/notify" "k8s.io/minikube/pkg/minikube/out" "k8s.io/minikube/pkg/minikube/proxy" + "k8s.io/minikube/pkg/minikube/translate" pkgutil "k8s.io/minikube/pkg/util" "k8s.io/minikube/pkg/util/lock" "k8s.io/minikube/pkg/util/retry" @@ -194,7 +195,7 @@ func initKubernetesFlags() { // initDriverFlags inits the commandline flags for vm drivers func initDriverFlags() { - startCmd.Flags().String("vm-driver", "", fmt.Sprintf("Driver is one of: %v (defaults to %s)", driver.SupportedDrivers(), driver.Default())) + startCmd.Flags().String("vm-driver", "", fmt.Sprintf("Driver is one of: %v (defaults to auto-detect)", driver.SupportedDrivers())) startCmd.Flags().Bool(disableDriverMounts, false, "Disables the filesystem mounts provided by the hypervisors") // kvm2 @@ -289,6 +290,7 @@ func runStart(cmd *cobra.Command, args []string) { } driverName := selectDriver(oldConfig) + glog.Infof("selected: %v", driverName) err = autoSetDriverOptions(cmd, driverName) if err != nil { glog.Errorf("Error autoSetOptions : %v", err) @@ -297,11 +299,14 @@ func runStart(cmd *cobra.Command, args []string) { validateFlags(driverName) validateUser(driverName) - v, err := version.GetSemverVersion() - if err != nil { - out.WarningT("Error parsing minikube version: {{.error}}", out.V{"error": err}) - } else if err := driver.InstallOrUpdate(driverName, localpath.MakeMiniPath("bin"), v, viper.GetBool(interactive), viper.GetBool(autoUpdate)); err != nil { - out.WarningT("Unable to update {{.driver}} driver: {{.error}}", out.V{"driver": driverName, "error": err}) + // No need to install a driver in download-only mode + if !viper.GetBool(downloadOnly) { + v, err := version.GetSemverVersion() + if err != nil { + out.WarningT("Error parsing minikube version: {{.error}}", out.V{"error": err}) + } else if err := driver.InstallOrUpdate(driverName, localpath.MakeMiniPath("bin"), v, viper.GetBool(interactive), viper.GetBool(autoUpdate)); err != nil { + out.WarningT("Unable to update {{.driver}} driver: {{.error}}", out.V{"driver": driverName, "error": err}) + } } k8sVersion, isUpgrade := getKubernetesVersion(oldConfig) @@ -534,17 +539,41 @@ func showKubectlInfo(kcs *kubeconfig.Settings, k8sVersion string) error { func selectDriver(oldConfig *cfg.Config) string { name := viper.GetString("vm-driver") - // By default, the driver is whatever we used last time + glog.Infof("selectDriver: flag=%q, old=%v", name, oldConfig) if name == "" { - name = driver.Default() + // By default, the driver is whatever we used last time if oldConfig != nil { return oldConfig.MachineConfig.VMDriver } + options := driver.Choices() + pick, alts := driver.Choose(options) + if len(options) > 1 { + out.T(out.Sparkle, `Automatically selected the '{{.driver}}' driver (alternates: {{.alternates}})`, out.V{"driver": pick.Name, "alternates": alts}) + } else { + out.T(out.Sparkle, `Automatically selected the '{{.driver}}' driver`, out.V{"driver": pick.Name}) + } + + if pick.Name == "" { + exit.WithCodeT(exit.Config, "Unable to determine a default driver to use. Try specifying --vm-driver, or see https://minikube.sigs.k8s.io/docs/start/") + } + + name = pick.Name } if !driver.Supported(name) { exit.WithCodeT(exit.Failure, "The driver '{{.driver}}' is not supported on {{.os}}", out.V{"driver": name, "os": runtime.GOOS}) } + st := driver.Status(name) + if st.Error != nil { + out.ErrLn("") + out.WarningT("'{{.driver}}' driver reported a possible issue: {{.error}}", out.V{"driver": name, "error": st.Error, "fix": st.Fix}) + out.ErrT(out.Tip, "Suggestion: {{.fix}}", out.V{"fix": translate.T(st.Fix)}) + if st.Doc != "" { + out.ErrT(out.Documentation, "Documentation: {{.url}}", out.V{"url": st.Doc}) + } + out.ErrLn("") + } + // Detect if our driver conflicts with a previously created VM. If we run into any errors, just move on. api, err := machine.NewAPIClient() if err != nil { diff --git a/hack/jenkins/common.sh b/hack/jenkins/common.sh index 64abce7819da..ad44c3a23921 100755 --- a/hack/jenkins/common.sh +++ b/hack/jenkins/common.sh @@ -256,11 +256,14 @@ fi echo "" echo ">> Starting ${E2E_BIN} at $(date)" +set -x ${SUDO_PREFIX}${E2E_BIN} \ -minikube-start-args="--vm-driver=${VM_DRIVER} ${EXTRA_START_ARGS}" \ + -expected-default-driver="${EXPECTED_DEFAULT_DRIVER}" \ -test.timeout=60m \ -test.parallel=${PARALLEL_COUNT} \ -binary="${MINIKUBE_BIN}" && result=$? || result=$? +set +x echo ">> ${E2E_BIN} exited with ${result} at $(date)" echo "" diff --git a/hack/jenkins/linux_integration_tests_kvm.sh b/hack/jenkins/linux_integration_tests_kvm.sh index 5af0e48b3039..fe40576f65e4 100755 --- a/hack/jenkins/linux_integration_tests_kvm.sh +++ b/hack/jenkins/linux_integration_tests_kvm.sh @@ -29,6 +29,7 @@ OS_ARCH="linux-amd64" VM_DRIVER="kvm2" JOB_NAME="KVM_Linux" PARALLEL_COUNT=4 +EXPECTED_DEFAULT_DRIVER="kvm2" # Download files and set permissions source ./common.sh diff --git a/hack/jenkins/linux_integration_tests_none.sh b/hack/jenkins/linux_integration_tests_none.sh index 6721d15e2455..767e6867a09b 100755 --- a/hack/jenkins/linux_integration_tests_none.sh +++ b/hack/jenkins/linux_integration_tests_none.sh @@ -31,6 +31,7 @@ VM_DRIVER="none" JOB_NAME="none_Linux" EXTRA_ARGS="--bootstrapper=kubeadm" PARALLEL_COUNT=1 +EXPECTED_DEFAULT_DRIVER="kvm2" SUDO_PREFIX="sudo -E " export KUBECONFIG="/root/.kube/config" diff --git a/hack/jenkins/linux_integration_tests_virtualbox.sh b/hack/jenkins/linux_integration_tests_virtualbox.sh index bd413c158687..6f624eeead0a 100755 --- a/hack/jenkins/linux_integration_tests_virtualbox.sh +++ b/hack/jenkins/linux_integration_tests_virtualbox.sh @@ -29,6 +29,7 @@ OS_ARCH="linux-amd64" VM_DRIVER="virtualbox" JOB_NAME="VirtualBox_Linux" PARALLEL_COUNT=4 +EXPECTED_DEFAULT_DRIVER="kvm2" # Download files and set permissions source ./common.sh diff --git a/hack/jenkins/osx_integration_tests_hyperkit.sh b/hack/jenkins/osx_integration_tests_hyperkit.sh index 7091d6512b0f..aa9d5d69d104 100755 --- a/hack/jenkins/osx_integration_tests_hyperkit.sh +++ b/hack/jenkins/osx_integration_tests_hyperkit.sh @@ -32,6 +32,8 @@ JOB_NAME="HyperKit_macOS" EXTRA_ARGS="--bootstrapper=kubeadm" EXTRA_START_ARGS="" PARALLEL_COUNT=3 +EXPECTED_DEFAULT_DRIVER="hyperkit" + # Download files and set permissions source common.sh diff --git a/hack/jenkins/osx_integration_tests_virtualbox.sh b/hack/jenkins/osx_integration_tests_virtualbox.sh index cb48a389ed2a..8f7c6e3dd0b8 100755 --- a/hack/jenkins/osx_integration_tests_virtualbox.sh +++ b/hack/jenkins/osx_integration_tests_virtualbox.sh @@ -30,6 +30,10 @@ VM_DRIVER="virtualbox" JOB_NAME="VirtualBox_macOS" EXTRA_ARGS="--bootstrapper=kubeadm" PARALLEL_COUNT=3 +# hyperkit behaves better, so it has higher precedence. +# Assumes that hyperkit is also installed on the VirtualBox CI host. +EXPECTED_DEFAULT_DRIVER="hyperkit" + # Download files and set permissions source common.sh diff --git a/hack/jenkins/windows_integration_test_hyperv.ps1 b/hack/jenkins/windows_integration_test_hyperv.ps1 index fb42370c4977..995b17506c52 100644 --- a/hack/jenkins/windows_integration_test_hyperv.ps1 +++ b/hack/jenkins/windows_integration_test_hyperv.ps1 @@ -19,7 +19,7 @@ gsutil.cmd -m cp -r gs://minikube-builds/$env:MINIKUBE_LOCATION/testdata . ./out/minikube-windows-amd64.exe delete -out/e2e-windows-amd64.exe -minikube-start-args="--vm-driver=hyperv --hyperv-virtual-switch=primary-virtual-switch" -binary=out/minikube-windows-amd64.exe -test.v -test.timeout=65m +out/e2e-windows-amd64.exe --expected-default-driver=hyperv -minikube-start-args="--vm-driver=hyperv --hyperv-virtual-switch=primary-virtual-switch" -binary=out/minikube-windows-amd64.exe -test.v -test.timeout=65m $env:result=$lastexitcode # If the last exit code was 0->success, x>0->error If($env:result -eq 0){$env:status="success"} diff --git a/hack/jenkins/windows_integration_test_virtualbox.ps1 b/hack/jenkins/windows_integration_test_virtualbox.ps1 index 559a6e220e1f..86dfde120a2c 100644 --- a/hack/jenkins/windows_integration_test_virtualbox.ps1 +++ b/hack/jenkins/windows_integration_test_virtualbox.ps1 @@ -19,7 +19,7 @@ gsutil.cmd -m cp -r gs://minikube-builds/$env:MINIKUBE_LOCATION/testdata . ./out/minikube-windows-amd64.exe delete -out/e2e-windows-amd64.exe -minikube-start-args="--vm-driver=virtualbox" -binary=out/minikube-windows-amd64.exe -test.v -test.timeout=30m +out/e2e-windows-amd64.exe -minikube-start-args="--vm-driver=virtualbox" -expected-default-driver=hyperv -binary=out/minikube-windows-amd64.exe -test.v -test.timeout=30m $env:result=$lastexitcode # If the last exit code was 0->success, x>0->error If($env:result -eq 0){$env:status="success"} diff --git a/pkg/minikube/cluster/cluster.go b/pkg/minikube/cluster/cluster.go index f976b297de8d..3e1a4683f02a 100644 --- a/pkg/minikube/cluster/cluster.go +++ b/pkg/minikube/cluster/cluster.go @@ -433,15 +433,11 @@ func createHost(api libmachine.API, config cfg.MachineConfig) (*host.Host, error } } - def, err := registry.Driver(config.VMDriver) - if err != nil { - if err == registry.ErrDriverNotFound { - return nil, fmt.Errorf("unsupported/missing driver: %s", config.VMDriver) - } - return nil, errors.Wrap(err, "error getting driver") + def := registry.Driver(config.VMDriver) + if def.Empty() { + return nil, fmt.Errorf("unsupported/missing driver: %s", config.VMDriver) } - - dd := def.ConfigCreator(config) + dd := def.Config(config) data, err := json.Marshal(dd) if err != nil { return nil, errors.Wrap(err, "marshal") diff --git a/pkg/minikube/cluster/cluster_test.go b/pkg/minikube/cluster/cluster_test.go index 62d7fd3f483f..ae2ee8c5b897 100644 --- a/pkg/minikube/cluster/cluster_test.go +++ b/pkg/minikube/cluster/cluster_test.go @@ -22,8 +22,8 @@ import ( "testing" "time" - // Register drivers - _ "k8s.io/minikube/pkg/minikube/registry/drvs" + // Driver used by testdata + _ "k8s.io/minikube/pkg/minikube/registry/drvs/virtualbox" "github.com/docker/machine/libmachine/drivers" "github.com/docker/machine/libmachine/host" @@ -47,18 +47,13 @@ func createMockDriverHost(c config.MachineConfig) interface{} { func RegisterMockDriver(t *testing.T) { t.Helper() - _, err := registry.Driver(driver.Mock) - // Already registered - if err == nil { + if !registry.Driver(driver.Mock).Empty() { return } - err = registry.Register(registry.DriverDef{ - Name: driver.Mock, - Builtin: true, - ConfigCreator: createMockDriverHost, - DriverCreator: func() drivers.Driver { - return &tests.MockDriver{T: t} - }, + err := registry.Register(registry.DriverDef{ + Name: driver.Mock, + Config: createMockDriverHost, + Init: func() drivers.Driver { return &tests.MockDriver{T: t} }, }) if err != nil { t.Fatalf("register failed: %v", err) @@ -103,7 +98,7 @@ func TestCreateHost(t *testing.T) { } found := false - for _, def := range registry.ListDrivers() { + for _, def := range registry.List() { if h.DriverName == def.Name { found = true break @@ -111,7 +106,7 @@ func TestCreateHost(t *testing.T) { } if !found { - t.Fatalf("Wrong driver name: %v. It should be among drivers %v", h.DriverName, registry.ListDrivers()) + t.Fatalf("Wrong driver name: %v. It should be among drivers %v", h.DriverName, registry.List()) } } diff --git a/pkg/minikube/config/config_test.go b/pkg/minikube/config/config_test.go index 9eef5a7a61cb..19671ef61788 100644 --- a/pkg/minikube/config/config_test.go +++ b/pkg/minikube/config/config_test.go @@ -22,8 +22,6 @@ import ( "os" "reflect" "testing" - - "k8s.io/minikube/pkg/minikube/driver" ) type configTestCase struct { @@ -48,10 +46,10 @@ var configTestCases = []configTestCase{ "log_dir": "/etc/hosts", "show-libmachine-logs": true, "v": 5, - "vm-driver": "kvm2" + "vm-driver": "test-driver" }`, config: map[string]interface{}{ - "vm-driver": driver.KVM2, + "vm-driver": "test-driver", "cpus": 4, "disk-size": "20g", "v": 5, @@ -132,7 +130,7 @@ func TestReadConfig(t *testing.T) { } expectedConfig := map[string]interface{}{ - "vm-driver": driver.KVM2, + "vm-driver": "test-driver", "cpus": 4, "disk-size": "20g", "show-libmachine-logs": true, @@ -151,7 +149,7 @@ func TestWriteConfig(t *testing.T) { } cfg := map[string]interface{}{ - "vm-driver": driver.KVM2, + "vm-driver": "test-driver", "cpus": 4, "disk-size": "20g", "show-libmachine-logs": true, diff --git a/pkg/minikube/driver/driver.go b/pkg/minikube/driver/driver.go index 7d6712d8ea55..a87d7a607120 100644 --- a/pkg/minikube/driver/driver.go +++ b/pkg/minikube/driver/driver.go @@ -19,6 +19,10 @@ package driver import ( "fmt" "os" + "sort" + + "github.com/golang/glog" + "k8s.io/minikube/pkg/minikube/registry" ) const ( @@ -33,6 +37,11 @@ const ( Parallels = "parallels" ) +var ( + // systemdResolvConf is path to systemd's DNS configuration. https://github.com/kubernetes/minikube/issues/3511 + systemdResolvConf = "/run/systemd/resolve/resolv.conf" +) + // SupportedDrivers returns a list of supported drivers func SupportedDrivers() []string { return supportedDrivers @@ -62,14 +71,12 @@ type FlagHints struct { // FlagDefaults returns suggested defaults based on a driver func FlagDefaults(name string) FlagHints { if name != None { - return FlagHints{} + return FlagHints{CacheImages: true} } - // for more info see: https://github.com/kubernetes/minikube/issues/3511 - f := "/run/systemd/resolve/resolv.conf" extraOpts := "" - if _, err := os.Stat(f); err == nil { - extraOpts = fmt.Sprintf("kubelet.resolv-conf=%s", f) + if _, err := os.Stat(systemdResolvConf); err == nil { + extraOpts = fmt.Sprintf("kubelet.resolv-conf=%s", systemdResolvConf) } return FlagHints{ ExtraOptions: extraOpts, @@ -77,7 +84,50 @@ func FlagDefaults(name string) FlagHints { } } -// Default returns the default driver on this hos -func Default() string { - return VirtualBox +// Choices returns a list of drivers which are possible on this system +func Choices() []registry.DriverState { + options := []registry.DriverState{} + for _, ds := range registry.Installed() { + if !ds.State.Healthy { + glog.Warningf("%q is installed, but unhealthy: %v", ds.Name, ds.State.Error) + continue + } + options = append(options, ds) + } + + // Descending priority for predictability and appearance + sort.Slice(options, func(i, j int) bool { + return options[i].Priority > options[j].Priority + }) + return options +} + +// Choose returns a suggested driver from a set of options +func Choose(options []registry.DriverState) (registry.DriverState, []registry.DriverState) { + pick := registry.DriverState{} + for _, ds := range options { + if ds.Priority <= registry.Discouraged { + glog.Infof("not recommending %q due to priority: %d", ds.Name, ds.Priority) + continue + } + if ds.Priority > pick.Priority { + glog.V(1).Infof("%q has a higher priority (%d) than %q (%d)", ds.Name, ds.Priority, pick.Name, pick.Priority) + pick = ds + } + } + + alternates := []registry.DriverState{} + for _, ds := range options { + if ds != pick { + alternates = append(alternates, ds) + } + } + glog.Infof("Picked: %+v", pick) + glog.Infof("Alternatives: %+v", alternates) + return pick, alternates +} + +// Status returns the status of a driver +func Status(name string) registry.State { + return registry.Status(name) } diff --git a/pkg/minikube/driver/driver_test.go b/pkg/minikube/driver/driver_test.go new file mode 100644 index 000000000000..cd35e8d77598 --- /dev/null +++ b/pkg/minikube/driver/driver_test.go @@ -0,0 +1,162 @@ +/* +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 driver + +import ( + "fmt" + "io/ioutil" + "os" + "testing" + + "github.com/google/go-cmp/cmp" + "k8s.io/minikube/pkg/minikube/registry" +) + +func TestSupportedDrivers(t *testing.T) { + got := SupportedDrivers() + found := false + for _, s := range SupportedDrivers() { + if s == VirtualBox { + found = true + } + } + + if found == false { + t.Errorf("%s not in supported drivers: %v", VirtualBox, got) + } +} + +func TestSupported(t *testing.T) { + if !Supported(VirtualBox) { + t.Errorf("Supported(%s) is false", VirtualBox) + } + if Supported("yabba?") { + t.Errorf("Supported(yabba?) is true") + } +} + +func TestBareMetal(t *testing.T) { + if !BareMetal(None) { + t.Errorf("Supported(%s) is false", None) + } + if BareMetal(VirtualBox) { + t.Errorf("Supported(%s) is true", VirtualBox) + } +} + +func TestFlagDefaults(t *testing.T) { + expected := FlagHints{CacheImages: true} + if diff := cmp.Diff(FlagDefaults(VirtualBox), expected); diff != "" { + t.Errorf("defaults mismatch (-want +got):\n%s", diff) + } + + tf, err := ioutil.TempFile("", "resolv.conf") + if err != nil { + t.Fatalf("tempfile: %v", err) + } + defer os.Remove(tf.Name()) // clean up + + expected = FlagHints{ + CacheImages: false, + ExtraOptions: fmt.Sprintf("kubelet.resolv-conf=%s", tf.Name()), + } + systemdResolvConf = tf.Name() + if diff := cmp.Diff(FlagDefaults(None), expected); diff != "" { + t.Errorf("defaults mismatch (-want +got):\n%s", diff) + } +} + +func TestChoices(t *testing.T) { + + tests := []struct { + def registry.DriverDef + choices []string + pick string + alts []string + }{ + { + def: registry.DriverDef{ + Name: "unhealthy", + Priority: registry.Default, + Status: func() registry.State { return registry.State{Installed: true, Healthy: false} }, + }, + choices: []string{}, + pick: "", + alts: []string{}, + }, + { + def: registry.DriverDef{ + Name: "discouraged", + Priority: registry.Discouraged, + Status: func() registry.State { return registry.State{Installed: true, Healthy: true} }, + }, + choices: []string{"discouraged"}, + pick: "", + alts: []string{"discouraged"}, + }, + { + def: registry.DriverDef{ + Name: "default", + Priority: registry.Default, + Status: func() registry.State { return registry.State{Installed: true, Healthy: true} }, + }, + choices: []string{"default", "discouraged"}, + pick: "default", + alts: []string{"discouraged"}, + }, + { + def: registry.DriverDef{ + Name: "preferred", + Priority: registry.Preferred, + Status: func() registry.State { return registry.State{Installed: true, Healthy: true} }, + }, + choices: []string{"preferred", "default", "discouraged"}, + pick: "preferred", + alts: []string{"default", "discouraged"}, + }, + } + for _, tc := range tests { + t.Run(tc.def.Name, func(t *testing.T) { + if err := registry.Register(tc.def); err != nil { + t.Errorf("register returned error: %v", err) + } + + got := Choices() + gotNames := []string{} + for _, c := range got { + gotNames = append(gotNames, c.Name) + } + + if diff := cmp.Diff(gotNames, tc.choices); diff != "" { + t.Errorf("choices mismatch (-want +got):\n%s", diff) + } + + pick, alts := Choose(got) + if pick.Name != tc.pick { + t.Errorf("pick = %q, expected %q", pick.Name, tc.pick) + } + + gotAlts := []string{} + for _, a := range alts { + gotAlts = append(gotAlts, a.Name) + } + if diff := cmp.Diff(gotAlts, tc.alts); diff != "" { + t.Errorf("alts mismatch (-want +got):\n%s", diff) + } + }) + } +} diff --git a/pkg/minikube/driver/install_test.go b/pkg/minikube/driver/install_test.go new file mode 100644 index 000000000000..f57e1f541e3b --- /dev/null +++ b/pkg/minikube/driver/install_test.go @@ -0,0 +1,45 @@ +/* +Copyright 2019 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 driver + +import ( + "testing" +) + +func TestExtractVMDriverVersion(t *testing.T) { + v := extractVMDriverVersion("") + if len(v) != 0 { + t.Error("Expected empty string") + } + + v = extractVMDriverVersion("random text") + if len(v) != 0 { + t.Error("Expected empty string") + } + + expectedVersion := "1.2.3" + + v = extractVMDriverVersion("version: v1.2.3") + if expectedVersion != v { + t.Errorf("Expected version: %s, got: %s", expectedVersion, v) + } + + v = extractVMDriverVersion("version: 1.2.3") + if expectedVersion != v { + t.Errorf("Expected version: %s, got: %s", expectedVersion, v) + } +} diff --git a/pkg/minikube/machine/client.go b/pkg/minikube/machine/client.go index 2c6464b430fb..dd10ee31c9b6 100644 --- a/pkg/minikube/machine/client.go +++ b/pkg/minikube/machine/client.go @@ -19,6 +19,7 @@ package machine import ( "crypto/tls" "encoding/json" + "fmt" "net" "os" "path/filepath" @@ -81,17 +82,15 @@ type LocalClient struct { // NewHost creates a new Host func (api *LocalClient) NewHost(drvName string, rawDriver []byte) (*host.Host, error) { - var def registry.DriverDef - var err error - if def, err = registry.Driver(drvName); err != nil { - return nil, err - } else if !def.Builtin || def.DriverCreator == nil { + def := registry.Driver(drvName) + if def.Empty() { + return nil, fmt.Errorf("driver %q does not exist", drvName) + } + if def.Init == nil { return api.legacyClient.NewHost(drvName, rawDriver) } - - d := def.DriverCreator() - - err = json.Unmarshal(rawDriver, d) + d := def.Init() + err := json.Unmarshal(rawDriver, d) if err != nil { return nil, errors.Wrapf(err, "Error getting driver %s", string(rawDriver)) } @@ -127,14 +126,14 @@ func (api *LocalClient) Load(name string) (*host.Host, error) { return nil, errors.Wrapf(err, "filestore %q", name) } - var def registry.DriverDef - if def, err = registry.Driver(h.DriverName); err != nil { - return nil, err - } else if !def.Builtin || def.DriverCreator == nil { + def := registry.Driver(h.DriverName) + if def.Empty() { + return nil, fmt.Errorf("driver %q does not exist", h.DriverName) + } + if def.Init == nil { return api.legacyClient.Load(name) } - - h.Driver = def.DriverCreator() + h.Driver = def.Init() return h, json.Unmarshal(h.RawDriver, h.Driver) } @@ -163,9 +162,11 @@ func CommandRunner(h *host.Host) (command.Runner, error) { // Create creates the host func (api *LocalClient) Create(h *host.Host) error { - if def, err := registry.Driver(h.DriverName); err != nil { - return err - } else if !def.Builtin || def.DriverCreator == nil { + def := registry.Driver(h.DriverName) + if def.Empty() { + return fmt.Errorf("driver %q does not exist", h.DriverName) + } + if def.Init == nil { return api.legacyClient.Create(h) } @@ -271,12 +272,9 @@ func (cg *CertGenerator) ValidateCertificate(addr string, authOptions *auth.Opti } func registerDriver(drvName string) { - def, err := registry.Driver(drvName) - if err != nil { - if err == registry.ErrDriverNotFound { - exit.UsageT("unsupported or missing driver: {{.name}}", out.V{"name": drvName}) - } - exit.WithError("error getting driver", err) + def := registry.Driver(drvName) + if def.Empty() { + exit.UsageT("unsupported or missing driver: {{.name}}", out.V{"name": drvName}) } - plugin.RegisterDriver(def.DriverCreator()) + plugin.RegisterDriver(def.Init()) } diff --git a/pkg/minikube/out/style.go b/pkg/minikube/out/style.go index 4791787413b6..0af52ed464d1 100644 --- a/pkg/minikube/out/style.go +++ b/pkg/minikube/out/style.go @@ -81,6 +81,7 @@ var styles = map[StyleEnum]style{ Check: {Prefix: "✅ "}, Celebration: {Prefix: "🎉 "}, Workaround: {Prefix: "👉 ", LowPrefix: lowIndent}, + Sparkle: {Prefix: "✨ "}, // Specialized purpose styles ISODownload: {Prefix: "💿 "}, diff --git a/pkg/minikube/out/style_enum.go b/pkg/minikube/out/style_enum.go index 890c54f6b001..0bc8ac182279 100644 --- a/pkg/minikube/out/style_enum.go +++ b/pkg/minikube/out/style_enum.go @@ -83,4 +83,5 @@ const ( Fileserver Empty Workaround + Sparkle ) diff --git a/pkg/minikube/registry/drvs/hyperkit/driver.go b/pkg/minikube/registry/drvs/hyperkit/hyperkit.go similarity index 68% rename from pkg/minikube/registry/drvs/hyperkit/driver.go rename to pkg/minikube/registry/drvs/hyperkit/hyperkit.go index 0002af550d16..e42b4207dafe 100644 --- a/pkg/minikube/registry/drvs/hyperkit/driver.go +++ b/pkg/minikube/registry/drvs/hyperkit/hyperkit.go @@ -20,9 +20,12 @@ package hyperkit import ( "fmt" + "os/exec" + "strings" "github.com/docker/machine/libmachine/drivers" "github.com/pborman/uuid" + "k8s.io/minikube/pkg/drivers/hyperkit" cfg "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/driver" @@ -30,20 +33,25 @@ import ( "k8s.io/minikube/pkg/minikube/registry" ) +const ( + docURL = "https://minikube.sigs.k8s.io/docs/reference/drivers/hyperkit/" +) + func init() { if err := registry.Register(registry.DriverDef{ - Name: driver.HyperKit, - Builtin: false, - ConfigCreator: createHyperkitHost, + Name: driver.HyperKit, + Config: configure, + Status: status, + Priority: registry.Preferred, }); err != nil { panic(fmt.Sprintf("register: %v", err)) } } -func createHyperkitHost(config cfg.MachineConfig) interface{} { - uuID := config.UUID - if uuID == "" { - uuID = uuid.NewUUID().String() +func configure(config cfg.MachineConfig) interface{} { + u := config.UUID + if u == "" { + u = uuid.NewUUID().String() } return &hyperkit.Driver{ @@ -58,9 +66,24 @@ func createHyperkitHost(config cfg.MachineConfig) interface{} { CPU: config.CPUs, NFSShares: config.NFSShare, NFSSharesRoot: config.NFSSharesRoot, - UUID: uuID, + UUID: u, VpnKitSock: config.HyperkitVpnKitSock, VSockPorts: config.HyperkitVSockPorts, Cmdline: "loglevel=3 console=ttyS0 console=tty0 noembed nomodeset norestore waitusb=10 systemd.legacy_systemd_cgroup_controller=yes random.trust_cpu=on hw_rng_model=virtio base host=" + cfg.GetMachineName(), } } + +func status() registry.State { + path, err := exec.LookPath("hyperkit") + if err != nil { + return registry.State{Error: err, Fix: "Run 'brew install hyperkit'", Doc: docURL} + } + + cmd := exec.Command(path, "-v") + out, err := cmd.CombinedOutput() + if err != nil { + return registry.State{Installed: true, Error: fmt.Errorf("%s failed:\n%s", strings.Join(cmd.Args, " "), out), Fix: "Run 'brew install hyperkit'", Doc: docURL} + } + + return registry.State{Installed: true, Healthy: true} +} diff --git a/pkg/minikube/registry/drvs/hyperv/driver.go b/pkg/minikube/registry/drvs/hyperv/hyperv.go similarity index 55% rename from pkg/minikube/registry/drvs/hyperv/driver.go rename to pkg/minikube/registry/drvs/hyperv/hyperv.go index 50dc236607bd..e0216f12cfe8 100644 --- a/pkg/minikube/registry/drvs/hyperv/driver.go +++ b/pkg/minikube/registry/drvs/hyperv/hyperv.go @@ -19,28 +19,37 @@ limitations under the License. package hyperv import ( + "fmt" + "os/exec" + "strings" + "github.com/docker/machine/drivers/hyperv" "github.com/docker/machine/libmachine/drivers" + cfg "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/driver" "k8s.io/minikube/pkg/minikube/localpath" "k8s.io/minikube/pkg/minikube/registry" ) +const ( + docURL = "https://minikube.sigs.k8s.io/docs/reference/drivers/hyperv/" +) + func init() { - registry.Register(registry.DriverDef{ - Name: driver.HyperV, - Builtin: true, - ConfigCreator: createHypervHost, - DriverCreator: func() drivers.Driver { - return hyperv.NewDriver("", "") - }, - }) + if err := registry.Register(registry.DriverDef{ + Name: driver.HyperV, + Init: func() drivers.Driver { return hyperv.NewDriver("", "") }, + Config: configure, + Status: status, + Priority: registry.Preferred, + }); err != nil { + panic(fmt.Sprintf("register: %v", err)) + } } -func createHypervHost(config cfg.MachineConfig) interface{} { +func configure(config cfg.MachineConfig) interface{} { d := hyperv.NewDriver(cfg.GetMachineName(), localpath.MiniPath()) - d.Boot2DockerURL = config.Downloader.GetISOFileURI(config.MinikubeISO) d.VSwitch = config.HypervVirtualSwitch d.MemSize = config.Memory @@ -48,6 +57,19 @@ func createHypervHost(config cfg.MachineConfig) interface{} { d.DiskSize = config.DiskSize d.SSHUser = "docker" d.DisableDynamicMemory = true // default to disable dynamic memory as minikube is unlikely to work properly with dynamic memory - return d } + +func status() registry.State { + path, err := exec.LookPath("powershell") + if err != nil { + return registry.State{Error: err} + } + + cmd := exec.Command(path, "Get-WindowsOptionalFeature", "-FeatureName", "Microsoft-Hyper-V-All", "-Online") + out, err := cmd.CombinedOutput() + if err != nil { + return registry.State{Installed: false, Error: fmt.Errorf("%s failed:\n%s", strings.Join(cmd.Args, " "), out), Fix: "Start PowerShell as Administrator, and run: 'Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V -All'", Doc: docURL} + } + return registry.State{Installed: true, Healthy: true} +} diff --git a/pkg/minikube/registry/drvs/kvm2/driver.go b/pkg/minikube/registry/drvs/kvm2/kvm2.go similarity index 59% rename from pkg/minikube/registry/drvs/kvm2/driver.go rename to pkg/minikube/registry/drvs/kvm2/kvm2.go index 7d14ed952ea7..cdd61f20945e 100644 --- a/pkg/minikube/registry/drvs/kvm2/driver.go +++ b/pkg/minikube/registry/drvs/kvm2/kvm2.go @@ -20,27 +20,34 @@ package kvm2 import ( "fmt" + "os/exec" "path/filepath" + "strings" "github.com/docker/machine/libmachine/drivers" + "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/driver" "k8s.io/minikube/pkg/minikube/localpath" "k8s.io/minikube/pkg/minikube/registry" ) +const ( + docURL = "https://minikube.sigs.k8s.io/docs/reference/drivers/kvm2/" +) + func init() { if err := registry.Register(registry.DriverDef{ - Name: driver.KVM2, - Builtin: false, - ConfigCreator: createKVM2Host, + Name: driver.KVM2, + Config: configure, + Status: status, + Priority: registry.Preferred, }); err != nil { panic(fmt.Sprintf("register failed: %v", err)) } } -// Delete this once the following PR is merged: -// https://github.com/dhiltgen/docker-machine-kvm/pull/68 +// This is duplicate of kvm.Driver. Avoids importing the kvm2 driver, which requires cgo & libvirt. type kvmDriver struct { *drivers.BaseDriver @@ -57,9 +64,9 @@ type kvmDriver struct { ConnectionURI string } -func createKVM2Host(mc config.MachineConfig) interface{} { +func configure(mc config.MachineConfig) interface{} { name := config.GetMachineName() - return &kvmDriver{ + return kvmDriver{ BaseDriver: &drivers.BaseDriver{ MachineName: name, StorePath: localpath.MiniPath(), @@ -78,3 +85,33 @@ func createKVM2Host(mc config.MachineConfig) interface{} { ConnectionURI: mc.KVMQemuURI, } } + +func status() registry.State { + path, err := exec.LookPath("virsh") + if err != nil { + return registry.State{Error: err, Fix: "Install libvirt", Doc: docURL} + } + + cmd := exec.Command(path, "domcapabilities", "--virttype", "kvm") + out, err := cmd.CombinedOutput() + if err != nil { + return registry.State{ + Installed: true, + Error: fmt.Errorf("%s failed:\n%s", strings.Join(cmd.Args, " "), strings.TrimSpace(string(out))), + Fix: "Follow your Linux distribution instructions for configuring KVM", + Doc: docURL, + } + } + + cmd = exec.Command("virsh", "list") + out, err = cmd.CombinedOutput() + if err != nil { + return registry.State{ + Installed: true, + Error: fmt.Errorf("%s failed:\n%s", strings.Join(cmd.Args, " "), strings.TrimSpace(string(out))), + Fix: "Check that libvirtd is properly installed and that you are a member of the appropriate libvirt group", + Doc: docURL, + } + } + return registry.State{Installed: true, Healthy: true} +} diff --git a/pkg/minikube/registry/drvs/none/driver.go b/pkg/minikube/registry/drvs/none/none.go similarity index 67% rename from pkg/minikube/registry/drvs/none/driver.go rename to pkg/minikube/registry/drvs/none/none.go index 119a7439374a..fa094f9aeaa8 100644 --- a/pkg/minikube/registry/drvs/none/driver.go +++ b/pkg/minikube/registry/drvs/none/none.go @@ -1,3 +1,5 @@ +// +build linux + /* Copyright 2018 The Kubernetes Authors All rights reserved. @@ -18,6 +20,7 @@ package none import ( "fmt" + "os/exec" "github.com/docker/machine/libmachine/drivers" "k8s.io/minikube/pkg/drivers/none" @@ -29,22 +32,28 @@ import ( func init() { if err := registry.Register(registry.DriverDef{ - Name: driver.None, - Builtin: true, - ConfigCreator: createNoneHost, - DriverCreator: func() drivers.Driver { - return none.NewDriver(none.Config{}) - }, + Name: driver.None, + Config: configure, + Init: func() drivers.Driver { return none.NewDriver(none.Config{}) }, + Status: status, + Priority: registry.Discouraged, // requires root }); err != nil { panic(fmt.Sprintf("register failed: %v", err)) } } -// createNoneHost creates a none Driver from a MachineConfig -func createNoneHost(mc config.MachineConfig) interface{} { +func configure(mc config.MachineConfig) interface{} { return none.NewDriver(none.Config{ MachineName: config.GetMachineName(), StorePath: localpath.MiniPath(), ContainerRuntime: mc.ContainerRuntime, }) } + +func status() registry.State { + _, err := exec.LookPath("systemctl") + if err != nil { + return registry.State{Error: err, Fix: "Use a systemd based Linux distribution", Doc: "https://minikube.sigs.k8s.io/docs/reference/drivers/none/"} + } + return registry.State{Installed: true, Healthy: true} +} diff --git a/pkg/minikube/registry/drvs/parallels/driver.go b/pkg/minikube/registry/drvs/parallels/parallels.go similarity index 69% rename from pkg/minikube/registry/drvs/parallels/driver.go rename to pkg/minikube/registry/drvs/parallels/parallels.go index f0648fb6a821..193ea54da9d9 100644 --- a/pkg/minikube/registry/drvs/parallels/driver.go +++ b/pkg/minikube/registry/drvs/parallels/parallels.go @@ -20,6 +20,7 @@ package parallels import ( "fmt" + "os/exec" parallels "github.com/Parallels/docker-machine-parallels" "github.com/docker/machine/libmachine/drivers" @@ -31,12 +32,11 @@ import ( func init() { err := registry.Register(registry.DriverDef{ - Name: driver.Parallels, - Builtin: true, - ConfigCreator: createParallelsHost, - DriverCreator: func() drivers.Driver { - return parallels.NewDriver("", "") - }, + Name: driver.Parallels, + Config: configure, + Status: status, + Priority: registry.Default, + Init: func() drivers.Driver { return parallels.NewDriver("", "") }, }) if err != nil { panic(fmt.Sprintf("unable to register: %v", err)) @@ -44,7 +44,7 @@ func init() { } -func createParallelsHost(config cfg.MachineConfig) interface{} { +func configure(config cfg.MachineConfig) interface{} { d := parallels.NewDriver(cfg.GetMachineName(), localpath.MiniPath()).(*parallels.Driver) d.Boot2DockerURL = config.Downloader.GetISOFileURI(config.MinikubeISO) d.Memory = config.Memory @@ -52,3 +52,11 @@ func createParallelsHost(config cfg.MachineConfig) interface{} { d.DiskSize = config.DiskSize return d } + +func status() registry.State { + _, err := exec.LookPath("docker-machine-driver-parallels") + if err != nil { + return registry.State{Error: err, Fix: "Install docker-machine-driver-parallels", Doc: "https://minikube.sigs.k8s.io/docs/reference/drivers/parallels/"} + } + return registry.State{Installed: true, Healthy: true} +} diff --git a/pkg/minikube/registry/drvs/virtualbox/doc.go b/pkg/minikube/registry/drvs/virtualbox/doc.go deleted file mode 100644 index 0e6eff9e8ca1..000000000000 --- a/pkg/minikube/registry/drvs/virtualbox/doc.go +++ /dev/null @@ -1,17 +0,0 @@ -/* -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 virtualbox diff --git a/pkg/minikube/registry/drvs/virtualbox/driver.go b/pkg/minikube/registry/drvs/virtualbox/virtualbox.go similarity index 57% rename from pkg/minikube/registry/drvs/virtualbox/driver.go rename to pkg/minikube/registry/drvs/virtualbox/virtualbox.go index 5677ec614631..fa4483cf0b3c 100644 --- a/pkg/minikube/registry/drvs/virtualbox/driver.go +++ b/pkg/minikube/registry/drvs/virtualbox/virtualbox.go @@ -18,34 +18,38 @@ package virtualbox import ( "fmt" + "os/exec" + "strings" "github.com/docker/machine/drivers/virtualbox" "github.com/docker/machine/libmachine/drivers" + "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/driver" "k8s.io/minikube/pkg/minikube/localpath" "k8s.io/minikube/pkg/minikube/registry" ) -const defaultVirtualboxNicType = "virtio" +const ( + defaultVirtualboxNicType = "virtio" + docURL = "https://minikube.sigs.k8s.io/docs/reference/drivers/virtualbox/" +) func init() { err := registry.Register(registry.DriverDef{ - Name: driver.VirtualBox, - Builtin: true, - ConfigCreator: createVirtualboxHost, - DriverCreator: func() drivers.Driver { - return virtualbox.NewDriver("", "") - }, + Name: driver.VirtualBox, + Config: configure, + Status: status, + Priority: registry.Fallback, + Init: func() drivers.Driver { return virtualbox.NewDriver("", "") }, }) if err != nil { panic(fmt.Sprintf("unable to register: %v", err)) } } -func createVirtualboxHost(mc config.MachineConfig) interface{} { +func configure(mc config.MachineConfig) interface{} { d := virtualbox.NewDriver(config.GetMachineName(), localpath.MiniPath()) - d.Boot2DockerURL = mc.Downloader.GetISOFileURI(mc.MinikubeISO) d.Memory = mc.Memory d.CPU = mc.CPUs @@ -57,6 +61,31 @@ func createVirtualboxHost(mc config.MachineConfig) interface{} { d.HostOnlyNicType = defaultVirtualboxNicType d.DNSProxy = mc.DNSProxy d.HostDNSResolver = mc.HostDNSResolver - return d } + +func status() registry.State { + // Re-use this function as it's particularly helpful for Windows + tryPath := driver.VBoxManagePath() + path, err := exec.LookPath(tryPath) + if err != nil { + return registry.State{ + Error: fmt.Errorf("unable to find VBoxManage in $PATH"), + Fix: "Install VirtualBox", + Doc: docURL, + } + } + + cmd := exec.Command(path, "list", "hostinfo") + out, err := cmd.CombinedOutput() + if err != nil { + return registry.State{ + Installed: true, + Error: fmt.Errorf("%s failed:\n%s", strings.Join(cmd.Args, " "), out), + Fix: "Install the latest version of VirtualBox", + Doc: docURL, + } + } + + return registry.State{Installed: true, Healthy: true} +} diff --git a/pkg/minikube/registry/drvs/vmware/driver.go b/pkg/minikube/registry/drvs/vmware/vmware.go similarity index 74% rename from pkg/minikube/registry/drvs/vmware/driver.go rename to pkg/minikube/registry/drvs/vmware/vmware.go index 10b53ca390bc..a8a91c78b2f6 100644 --- a/pkg/minikube/registry/drvs/vmware/driver.go +++ b/pkg/minikube/registry/drvs/vmware/vmware.go @@ -18,6 +18,7 @@ package vmware import ( "fmt" + "os/exec" vmwcfg "github.com/machine-drivers/docker-machine-driver-vmware/pkg/drivers/vmware/config" "k8s.io/minikube/pkg/minikube/config" @@ -28,16 +29,17 @@ import ( func init() { err := registry.Register(registry.DriverDef{ - Name: driver.VMware, - Builtin: false, - ConfigCreator: createVMwareHost, + Name: driver.VMware, + Config: configure, + Priority: registry.Default, + Status: status, }) if err != nil { panic(fmt.Sprintf("unable to register: %v", err)) } } -func createVMwareHost(mc config.MachineConfig) interface{} { +func configure(mc config.MachineConfig) interface{} { d := vmwcfg.NewConfig(config.GetMachineName(), localpath.MiniPath()) d.Boot2DockerURL = mc.Downloader.GetISOFileURI(mc.MinikubeISO) d.Memory = mc.Memory @@ -49,3 +51,11 @@ func createVMwareHost(mc config.MachineConfig) interface{} { d.ISO = d.ResolveStorePath("boot2docker.iso") return d } + +func status() registry.State { + _, err := exec.LookPath("docker-machine-driver-vmware") + if err != nil { + return registry.State{Error: err, Fix: "Install docker-machine-driver-vmware", Doc: "https://minikube.sigs.k8s.io/docs/reference/drivers/vmware/"} + } + return registry.State{Installed: true, Healthy: true} +} diff --git a/pkg/minikube/registry/drvs/vmwarefusion/driver.go b/pkg/minikube/registry/drvs/vmwarefusion/vmwarefusion.go similarity index 70% rename from pkg/minikube/registry/drvs/vmwarefusion/driver.go rename to pkg/minikube/registry/drvs/vmwarefusion/vmwarefusion.go index 394c623bc230..cbff0022ed0f 100644 --- a/pkg/minikube/registry/drvs/vmwarefusion/driver.go +++ b/pkg/minikube/registry/drvs/vmwarefusion/vmwarefusion.go @@ -20,9 +20,12 @@ package vmwarefusion import ( "fmt" + "os/exec" "github.com/docker/machine/drivers/vmwarefusion" "github.com/docker/machine/libmachine/drivers" + "github.com/pkg/errors" + cfg "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/driver" "k8s.io/minikube/pkg/minikube/localpath" @@ -31,18 +34,17 @@ import ( func init() { if err := registry.Register(registry.DriverDef{ - Name: driver.VMwareFusion, - Builtin: true, - ConfigCreator: createVMwareFusionHost, - DriverCreator: func() drivers.Driver { - return vmwarefusion.NewDriver("", "") - }, + Name: driver.VMwareFusion, + Config: configure, + Status: status, + Init: func() drivers.Driver { return vmwarefusion.NewDriver("", "") }, + Priority: registry.Deprecated, }); err != nil { panic(fmt.Sprintf("register: %v", err)) } } -func createVMwareFusionHost(config cfg.MachineConfig) interface{} { +func configure(config cfg.MachineConfig) interface{} { d := vmwarefusion.NewDriver(cfg.GetMachineName(), localpath.MiniPath()).(*vmwarefusion.Driver) d.Boot2DockerURL = config.Downloader.GetISOFileURI(config.MinikubeISO) d.Memory = config.Memory @@ -54,3 +56,11 @@ func createVMwareFusionHost(config cfg.MachineConfig) interface{} { d.ISO = d.ResolveStorePath("boot2docker.iso") return d } + +func status() registry.State { + _, err := exec.LookPath("vmrun") + if err != nil { + return registry.State{Error: errors.Wrap(err, "vmrun path check"), Fix: "Install VMWare Fusion", Doc: "https://minikube.sigs.k8s.io/docs/reference/drivers/vmwarefusion/"} + } + return registry.State{Installed: true, Healthy: true} +} diff --git a/pkg/minikube/registry/global.go b/pkg/minikube/registry/global.go new file mode 100644 index 000000000000..97882296a540 --- /dev/null +++ b/pkg/minikube/registry/global.go @@ -0,0 +1,85 @@ +/* +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 registry + +import ( + "os" + + "github.com/golang/glog" +) + +var ( + // globalRegistry is a globally accessible driver registry + globalRegistry = newRegistry() +) + +// DriverState is metadata relating to a driver and status +type DriverState struct { + Name string + Priority Priority + State State +} + +func (d DriverState) String() string { + return d.Name +} + +// List lists drivers in global registry +func List() []DriverDef { + return globalRegistry.List() +} + +// Register registers driver with the global registry +func Register(driver DriverDef) error { + return globalRegistry.Register(driver) +} + +// Driver gets a named driver from the global registry +func Driver(name string) DriverDef { + return globalRegistry.Driver(name) +} + +// Installed returns a list of installed drivers in the global registry +func Installed() []DriverState { + sts := []DriverState{} + glog.Infof("Querying for installed drivers using PATH=%s", os.Getenv("PATH")) + + for _, d := range globalRegistry.List() { + if d.Status == nil { + glog.Errorf("%q does not implement Status", d.Name) + continue + } + s := d.Status() + glog.Infof("%s priority: %d, state: %+v", d.Name, d.Priority, s) + + if !s.Installed { + glog.Infof("%q not installed: %v", d.Name, s.Error) + continue + } + sts = append(sts, DriverState{Name: d.Name, Priority: d.Priority, State: s}) + } + return sts +} + +// Status returns the state of a driver within the global registry +func Status(name string) State { + d := globalRegistry.Driver(name) + if d.Empty() { + return State{} + } + return d.Status() +} diff --git a/pkg/minikube/registry/global_test.go b/pkg/minikube/registry/global_test.go new file mode 100644 index 000000000000..1ccb418a9145 --- /dev/null +++ b/pkg/minikube/registry/global_test.go @@ -0,0 +1,118 @@ +/* +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 registry + +import ( + "testing" + + "github.com/google/go-cmp/cmp" +) + +func TestGlobalRegister(t *testing.T) { + globalRegistry = newRegistry() + foo := DriverDef{Name: "foo"} + if err := Register(foo); err != nil { + t.Errorf("Register = %v, expected nil", err) + } + if err := Register(foo); err == nil { + t.Errorf("Register = nil, expected duplicate err") + } +} + +func TestGlobalDriver(t *testing.T) { + foo := DriverDef{Name: "foo"} + globalRegistry = newRegistry() + + if err := Register(foo); err != nil { + t.Errorf("Register = %v, expected nil", err) + } + + d := Driver("foo") + if d.Empty() { + t.Errorf("driver.Empty = true, expected false") + } + + d = Driver("bar") + if !d.Empty() { + t.Errorf("driver.Empty = false, expected true") + } +} + +func TestGlobalList(t *testing.T) { + foo := DriverDef{Name: "foo"} + globalRegistry = newRegistry() + if err := Register(foo); err != nil { + t.Errorf("register returned error: %v", err) + } + + if diff := cmp.Diff(List(), []DriverDef{foo}); diff != "" { + t.Errorf("list mismatch (-want +got):\n%s", diff) + } +} + +func TestGlobalInstalled(t *testing.T) { + globalRegistry = newRegistry() + + if err := Register(DriverDef{Name: "foo"}); err != nil { + t.Errorf("register returned error: %v", err) + } + + bar := DriverDef{ + Name: "bar", + Priority: Default, + Status: func() State { return State{Installed: true} }, + } + if err := Register(bar); err != nil { + t.Errorf("register returned error: %v", err) + } + + expected := []DriverState{ + DriverState{ + Name: "bar", + Priority: Default, + State: State{ + Installed: true, + }, + }, + } + + if diff := cmp.Diff(Installed(), expected); diff != "" { + t.Errorf("installed mismatch (-want +got):\n%s", diff) + } +} + +func TestGlobalStatus(t *testing.T) { + globalRegistry = newRegistry() + + if err := Register(DriverDef{Name: "foo"}); err != nil { + t.Errorf("register returned error: %v", err) + } + + expected := State{Installed: true, Healthy: true} + bar := DriverDef{ + Name: "bar", + Priority: Default, + Status: func() State { return expected }, + } + if err := Register(bar); err != nil { + t.Errorf("register returned error: %v", err) + } + + if diff := cmp.Diff(Status("bar"), expected); diff != "" { + t.Errorf("status mismatch (-want +got):\n%s", diff) + } +} diff --git a/pkg/minikube/registry/registry.go b/pkg/minikube/registry/registry.go index 15b7b15e41e9..9fd491ea9e97 100644 --- a/pkg/minikube/registry/registry.go +++ b/pkg/minikube/registry/registry.go @@ -21,18 +21,21 @@ import ( "sync" "github.com/docker/machine/libmachine/drivers" - "github.com/pkg/errors" + "k8s.io/minikube/pkg/minikube/config" ) -var ( - // ErrDriverNameExist is the error returned when trying to register a driver - // which already exists in registry - ErrDriverNameExist = errors.New("registry: duplicated driver name") - - // ErrDriverNotFound is the error returned when driver of a given name does - // not exist in registry - ErrDriverNotFound = errors.New("registry: driver not found") +// Priority is how we determine what driver to default to +type Priority int + +const ( + Unknown Priority = iota + Discouraged + Deprecated + Fallback + Default + Preferred + StronglyPreferred ) // Registry contains all the supported driver definitions on the host @@ -47,31 +50,49 @@ type Registry interface { List() []DriverDef } -// ConfigFactory is a function that creates a driver config from MachineConfig -type ConfigFactory func(config.MachineConfig) interface{} +// Configurator emits a struct to be marshalled into JSON for Machine Driver +type Configurator func(config.MachineConfig) interface{} + +// Loader is a function that loads a byte stream and creates a driver. +type Loader func() drivers.Driver -// DriverFactory is a function that loads a byte stream and creates a driver. -type DriverFactory func() drivers.Driver +// StatusChecker checks if a driver is available, offering a +type StatusChecker func() State + +// State is the current state of the driver and its dependencies +type State struct { + Installed bool + Healthy bool + Error error + Fix string + Doc string +} -// DriverDef defines a machine driver metadata. It tells minikube how to initialize -// and load drivers. +// DriverDef defines how to initialize and load a machine driver type DriverDef struct { // Name of the machine driver. It has to be unique. Name string - // BuiltIn indicates if the driver is builtin minikube binary, or the driver is - // triggered through RPC. - Builtin bool + // Config is a function that emits a configured driver struct + Config Configurator - // ConfigCreator generates a raw driver object by minikube's machine config. - ConfigCreator ConfigFactory + // Init is a function that initializes a machine driver, if built-in to the minikube binary + Init Loader - // DriverCreator is the factory method that creates a machine driver instance. - DriverCreator DriverFactory + // Status returns the installation status of the driver + Status StatusChecker + + // Priority returns the prioritization for selecting a driver by default. + Priority Priority +} + +// Empty returns true if the driver is nil +func (d DriverDef) Empty() bool { + return d.Name == "" } func (d DriverDef) String() string { - return fmt.Sprintf("{name: %s, builtin: %t}", d.Name, d.Builtin) + return d.Name } type driverRegistry struct { @@ -79,43 +100,26 @@ type driverRegistry struct { lock sync.Mutex } -func createRegistry() *driverRegistry { +func newRegistry() *driverRegistry { return &driverRegistry{ drivers: make(map[string]DriverDef), } } -var ( - registry = createRegistry() -) - -// ListDrivers lists all drivers in registry -func ListDrivers() []DriverDef { - return registry.List() -} - -// Register registers driver -func Register(driver DriverDef) error { - return registry.Register(driver) -} - -// Driver gets a named driver -func Driver(name string) (DriverDef, error) { - return registry.Driver(name) -} - +// Register registers a driver func (r *driverRegistry) Register(def DriverDef) error { r.lock.Lock() defer r.lock.Unlock() if _, ok := r.drivers[def.Name]; ok { - return ErrDriverNameExist + return fmt.Errorf("%q is already registered: %+v", def.Name, def) } r.drivers[def.Name] = def return nil } +// List returns a list of registered drivers func (r *driverRegistry) List() []DriverDef { r.lock.Lock() defer r.lock.Unlock() @@ -129,13 +133,9 @@ func (r *driverRegistry) List() []DriverDef { return result } -func (r *driverRegistry) Driver(name string) (DriverDef, error) { +// Driver returns a driver given a name +func (r *driverRegistry) Driver(name string) DriverDef { r.lock.Lock() defer r.lock.Unlock() - - if driver, ok := r.drivers[name]; ok { - return driver, nil - } - - return DriverDef{}, ErrDriverNotFound + return r.drivers[name] } diff --git a/pkg/minikube/registry/registry_test.go b/pkg/minikube/registry/registry_test.go index 8d44019a08a8..e1d84b25efae 100644 --- a/pkg/minikube/registry/registry_test.go +++ b/pkg/minikube/registry/registry_test.go @@ -19,107 +19,47 @@ package registry import ( "testing" - "k8s.io/minikube/pkg/minikube/config" + "github.com/google/go-cmp/cmp" ) -func TestDriverString(t *testing.T) { - bar := DriverDef{ - Name: "bar", - Builtin: true, - ConfigCreator: func(_ config.MachineConfig) interface{} { - return nil - }, +func TestRegister(t *testing.T) { + r := newRegistry() + foo := DriverDef{Name: "foo"} + if err := r.Register(foo); err != nil { + t.Errorf("Register = %v, expected nil", err) } - s := bar.String() - if s != "{name: bar, builtin: true}" { - t.Fatalf("Driver bar.String() returned unexpected: %v", s) + if err := r.Register(foo); err == nil { + t.Errorf("Register = nil, expected duplicate err") } } -func testDriver(name string) DriverDef { - return DriverDef{ - Name: name, - Builtin: true, - ConfigCreator: func(_ config.MachineConfig) interface{} { - return nil - }, +func TestDriver(t *testing.T) { + foo := DriverDef{Name: "foo"} + r := newRegistry() + + if err := r.Register(foo); err != nil { + t.Errorf("Register = %v, expected nil", err) } -} -func TestRegistry1(t *testing.T) { - foo := testDriver("foo") - bar := testDriver("bar") + d := r.Driver("foo") + if d.Empty() { + t.Errorf("driver.Empty = true, expected false") + } - registry := createRegistry() - t.Run("registry.Register", func(t *testing.T) { - t.Run("foo", func(t *testing.T) { - if err := registry.Register(foo); err != nil { - t.Fatalf("error not expected but got %v", err) - } - }) - t.Run("fooAlreadyExist", func(t *testing.T) { - if err := registry.Register(foo); err != ErrDriverNameExist { - t.Fatalf("expect ErrDriverNameExist but got: %v", err) - } - }) - t.Run("bar", func(t *testing.T) { - if err := registry.Register(bar); err != nil { - t.Fatalf("error not expect but got: %v", err) - } - }) - }) - t.Run("registry.List", func(t *testing.T) { - list := registry.List() - if !(list[0].Name == "bar" && list[1].Name == "foo" || - list[0].Name == "foo" && list[1].Name == "bar") { - t.Fatalf("expect registry.List return %s; got %s", []string{"bar", "foo"}, list) - } - if drivers := ListDrivers(); len(list) == len(drivers) { - t.Fatalf("Expectect ListDrivers and registry.List() to return same number of items, but got: drivers=%v and list=%v", drivers, list) - } else if len(list) == len(drivers) { - t.Fatalf("expect len(list) to be %d; got %d", 2, len(list)) - } - }) + d = r.Driver("bar") + if !d.Empty() { + t.Errorf("driver.Empty = false, expected true") + } } -func TestRegistry2(t *testing.T) { - foo := testDriver("foo") - bar := testDriver("bar") - - registry := createRegistry() - if err := registry.Register(foo); err != nil { - t.Skipf("error not expect but got: %v", err) - } - if err := registry.Register(bar); err != nil { - t.Skipf("error not expect but got: %v", err) +func TestList(t *testing.T) { + foo := DriverDef{Name: "foo"} + r := newRegistry() + if err := r.Register(foo); err != nil { + t.Errorf("register returned error: %v", err) } - t.Run("Driver", func(t *testing.T) { - name := "foo" - d, err := registry.Driver(name) - if err != nil { - t.Fatalf("expect nil for registering foo driver, but got: %v", err) - } - if d.Name != name { - t.Fatalf("expect registry.Driver(%s) returns registered driver, but got: %s", name, d.Name) - } - }) - t.Run("NotExistingDriver", func(t *testing.T) { - _, err := registry.Driver("foo2") - if err != ErrDriverNotFound { - t.Fatalf("expect ErrDriverNotFound bug got: %v", err) - } - }) - t.Run("Driver", func(t *testing.T) { - if _, err := Driver("no_such_driver"); err == nil { - t.Fatal("expect to get error for not existing driver") - } - }) - if _, err := Driver("foo"); err == nil { - t.Fatal("expect to not get error during existing driver foo") + + if diff := cmp.Diff(r.List(), []DriverDef{foo}); diff != "" { + t.Errorf("list mismatch (-want +got):\n%s", diff) } - t.Run("Register", func(t *testing.T) { - if err := Register(foo); err != nil { - t.Fatalf("expect to not get error during registering driver foo, but got: %v", err) - } - }) } diff --git a/test/integration/Untitled-1 b/test/integration/Untitled-1 deleted file mode 100644 index e69de29bb2d1..000000000000 diff --git a/test/integration/a_serial_tests.go b/test/integration/a_serial_test.go similarity index 52% rename from test/integration/a_serial_tests.go rename to test/integration/a_serial_test.go index 7e4b604efff2..ae931662d707 100644 --- a/test/integration/a_serial_tests.go +++ b/test/integration/a_serial_test.go @@ -20,6 +20,7 @@ package integration import ( "context" + "encoding/json" "fmt" "os" "os/exec" @@ -29,15 +30,20 @@ import ( "time" "k8s.io/minikube/pkg/minikube/bootstrapper/images" + "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/constants" "k8s.io/minikube/pkg/minikube/localpath" ) -func TestDownloadAndDeleteAll(t *testing.T) { +func TestDownloadOnly(t *testing.T) { profile := UniqueProfileName("download") ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute) defer Cleanup(t, profile, cancel) + // Stores the startup run result for later error messages + var rrr *RunResult + var err error + t.Run("group", func(t *testing.T) { versions := []string{ constants.OldestKubernetesVersion, @@ -46,22 +52,28 @@ func TestDownloadAndDeleteAll(t *testing.T) { } for _, v := range versions { t.Run(v, func(t *testing.T) { - args := append([]string{"start", "--download-only", "-p", profile, fmt.Sprintf("--kubernetes-version=%s", v)}, StartArgs()...) - _, err := Run(t, exec.CommandContext(ctx, Target(), args...)) + // Explicitly does not pass StartArgs() to test driver default + // --force to avoid uid check + args := []string{"start", "--download-only", "-p", profile, "--force", "--alsologtostderr", fmt.Sprintf("--kubernetes-version=%s", v)} + + // Preserve the initial run-result for debugging + if rrr == nil { + rrr, err = Run(t, exec.CommandContext(ctx, Target(), args...)) + } else { + _, err = Run(t, exec.CommandContext(ctx, Target(), args...)) + } + if err != nil { t.Errorf("%s failed: %v", args, err) } - // None driver does not cache images, so this test will fail - if !NoneDriver() { - imgs := images.CachedImages("", v) - for _, img := range imgs { - img = strings.Replace(img, ":", "_", 1) // for example kube-scheduler:v1.15.2 --> kube-scheduler_v1.15.2 - fp := filepath.Join(localpath.MiniPath(), "cache", "images", img) - _, err := os.Stat(fp) - if err != nil { - t.Errorf("expected image file exist at %q but got error: %v", fp, err) - } + imgs := images.CachedImages("", v) + for _, img := range imgs { + img = strings.Replace(img, ":", "_", 1) // for example kube-scheduler:v1.15.2 --> kube-scheduler_v1.15.2 + fp := filepath.Join(localpath.MiniPath(), "cache", "images", img) + _, err := os.Stat(fp) + if err != nil { + t.Errorf("expected image file exist at %q but got error: %v", fp, err) } } @@ -75,8 +87,40 @@ func TestDownloadAndDeleteAll(t *testing.T) { } }) } + + // Check that the profile we've created has the expected driver + t.Run("ExpectedDefaultDriver", func(t *testing.T) { + if ExpectedDefaultDriver() == "" { + t.Skipf("--expected-default-driver is unset, skipping test") + return + } + rr, err := Run(t, exec.CommandContext(ctx, Target(), "profile", "list", "--output", "json")) + if err != nil { + t.Errorf("%s failed: %v", rr.Args, err) + } + var ps map[string][]config.Profile + err = json.Unmarshal(rr.Stdout.Bytes(), &ps) + if err != nil { + t.Errorf("%s failed: %v", rr.Args, err) + } + + got := "" + for _, p := range ps["valid"] { + if p.Name == profile { + got = p.Config.MachineConfig.VMDriver + } + } + + if got != ExpectedDefaultDriver() { + t.Errorf("got driver %q, expected %q\nstart output: %s", got, ExpectedDefaultDriver(), rrr.Output()) + } + }) + // This is a weird place to test profile deletion, but this test is serial, and we have a profile to delete! t.Run("DeleteAll", func(t *testing.T) { + if !CanCleanup() { + t.Skip("skipping, as cleanup is disabled") + } rr, err := Run(t, exec.CommandContext(ctx, Target(), "delete", "--all")) if err != nil { t.Errorf("%s failed: %v", rr.Args, err) @@ -84,6 +128,9 @@ func TestDownloadAndDeleteAll(t *testing.T) { }) // Delete should always succeed, even if previously partially or fully deleted. t.Run("DeleteAlwaysSucceeds", func(t *testing.T) { + if !CanCleanup() { + t.Skip("skipping, as cleanup is disabled") + } rr, err := Run(t, exec.CommandContext(ctx, Target(), "delete", "-p", profile)) if err != nil { t.Errorf("%s failed: %v", rr.Args, err) diff --git a/test/integration/main.go b/test/integration/main.go index 02ce476cf9db..dbd5830c156b 100644 --- a/test/integration/main.go +++ b/test/integration/main.go @@ -27,6 +27,7 @@ import ( // General configuration: used to set the VM Driver var startArgs = flag.String("minikube-start-args", "", "Arguments to pass to minikube start") +var defaultDriver = flag.String("expected-default-driver", "", "Expected default driver") // Flags for faster local integration testing var forceProfile = flag.String("profile", "", "force tests to run against a particular profile") @@ -65,3 +66,13 @@ func NoneDriver() bool { func HyperVDriver() bool { return strings.Contains(*startArgs, "--vm-driver=hyperv") } + +// ExpectedDefaultDriver returns the expected default driver, if any +func ExpectedDefaultDriver() string { + return *defaultDriver +} + +// CanCleanup returns if cleanup is allowed +func CanCleanup() bool { + return *cleanup +}