Merge branch 'master' of github.com:kubernetes/minikube into driver-fallback

pull/7389/head
Sharif Elgamal 2020-04-09 10:53:47 -07:00
commit e4b4a5ac8d
11 changed files with 135 additions and 116 deletions

View File

@ -29,6 +29,7 @@ import (
"k8s.io/minikube/pkg/minikube/assets"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/exit"
"k8s.io/minikube/pkg/minikube/mustload"
"k8s.io/minikube/pkg/minikube/out"
)
@ -49,11 +50,12 @@ var addonsListCmd = &cobra.Command{
exit.UsageT("usage: minikube addons list")
}
_, cc := mustload.Partial(ClusterFlagValue())
switch strings.ToLower(addonListOutput) {
case "list":
printAddonsList()
printAddonsList(cc)
case "json":
printAddonsJSON()
printAddonsJSON(cc)
default:
exit.WithCodeT(exit.BadUsage, fmt.Sprintf("invalid output format: %s. Valid values: 'list', 'json'", addonListOutput))
}
@ -85,27 +87,24 @@ var stringFromStatus = func(addonStatus bool) string {
return "disabled"
}
var printAddonsList = func() {
var printAddonsList = func(cc *config.ClusterConfig) {
addonNames := make([]string, 0, len(assets.Addons))
for addonName := range assets.Addons {
addonNames = append(addonNames, addonName)
}
sort.Strings(addonNames)
var tData [][]string
table := tablewriter.NewWriter(os.Stdout)
table.SetHeader([]string{"Addon Name", "Profile", "Status"})
table.SetAutoFormatHeaders(true)
table.SetBorders(tablewriter.Border{Left: true, Top: true, Right: true, Bottom: true})
table.SetCenterSeparator("|")
pName := ClusterFlagValue()
for _, addonName := range addonNames {
addonBundle := assets.Addons[addonName]
addonStatus, err := addonBundle.IsEnabled(pName)
if err != nil {
out.WarningT("Unable to get addon status for {{.name}}: {{.error}}", out.V{"name": addonName, "error": err})
}
tData = append(tData, []string{addonName, pName, fmt.Sprintf("%s %s", stringFromStatus(addonStatus), iconFromStatus(addonStatus))})
enabled := addonBundle.IsEnabled(cc)
tData = append(tData, []string{addonName, cc.Name, fmt.Sprintf("%s %s", stringFromStatus(enabled), iconFromStatus(enabled))})
}
table.AppendBulk(tData)
@ -120,9 +119,8 @@ var printAddonsList = func() {
}
}
var printAddonsJSON = func() {
var printAddonsJSON = func(cc *config.ClusterConfig) {
addonNames := make([]string, 0, len(assets.Addons))
pName := ClusterFlagValue()
for addonName := range assets.Addons {
addonNames = append(addonNames, addonName)
}
@ -132,16 +130,11 @@ var printAddonsJSON = func() {
for _, addonName := range addonNames {
addonBundle := assets.Addons[addonName]
addonStatus, err := addonBundle.IsEnabled(pName)
if err != nil {
glog.Errorf("Unable to get addon status for %s: %v", addonName, err)
continue
}
enabled := addonBundle.IsEnabled(cc)
addonsMap[addonName] = map[string]interface{}{
"Status": stringFromStatus(addonStatus),
"Profile": pName,
"Status": stringFromStatus(enabled),
"Profile": cc.Name,
}
}
jsonString, _ := json.Marshal(addonsMap)

View File

@ -33,7 +33,7 @@ var addonsDisableCmd = &cobra.Command{
}
addon := args[0]
err := addons.Set(addon, "false", ClusterFlagValue())
err := addons.SetAndSave(ClusterFlagValue(), addon, "false")
if err != nil {
exit.WithError("disable failed", err)
}

View File

@ -32,7 +32,7 @@ var addonsEnableCmd = &cobra.Command{
exit.UsageT("usage: minikube addons enable ADDON_NAME")
}
addon := args[0]
err := addons.Set(addon, "true", ClusterFlagValue())
err := addons.SetAndSave(ClusterFlagValue(), addon, "true")
if err != nil {
exit.WithError("enable failed", err)
}

View File

@ -66,11 +66,9 @@ var addonsOpenCmd = &cobra.Command{
To see the list of available addons run:
minikube addons list`, out.V{"name": addonName})
}
ok, err := addon.IsEnabled(cname)
if err != nil {
exit.WithError("IsEnabled failed", err)
}
if !ok {
enabled := addon.IsEnabled(co.Config)
if !enabled {
exit.WithCodeT(exit.Unavailable, `addon '{{.name}}' is currently not enabled.
To enable this addon run:
minikube addons enable {{.name}}`, out.V{"name": addonName})

View File

@ -67,13 +67,14 @@ var dashboardCmd = &cobra.Command{
var err error
// Check dashboard status before enabling it
dashboardAddon := assets.Addons["dashboard"]
dashboardStatus, _ := dashboardAddon.IsEnabled(cname)
if !dashboardStatus {
addon := assets.Addons["dashboard"]
enabled := addon.IsEnabled(co.Config)
if !enabled {
// Send status messages to stderr for folks re-using this output.
out.ErrT(out.Enabling, "Enabling dashboard ...")
// Enable the dashboard add-on
err = addons.Set("dashboard", "true", cname)
err = addons.SetAndSave(cname, "dashboard", "true")
if err != nil {
exit.WithError("Unable to enable dashboard", err)
}

View File

@ -23,6 +23,7 @@ import (
"sort"
"strconv"
"strings"
"sync"
"time"
"github.com/golang/glog"
@ -41,32 +42,49 @@ import (
// defaultStorageClassProvisioner is the name of the default storage class provisioner
const defaultStorageClassProvisioner = "standard"
// Set sets a value
func Set(name, value, profile string) error {
glog.Infof("Setting %s=%s in profile %q", name, value, profile)
// RunCallbacks runs all actions associated to an addon, but does not set it (thread-safe)
func RunCallbacks(cc *config.ClusterConfig, name string, value string) error {
glog.Infof("Setting %s=%s in profile %q", name, value, cc.Name)
a, valid := isAddonValid(name)
if !valid {
return errors.Errorf("%s is not a valid addon", name)
}
cc, err := config.Load(profile)
if err != nil {
return errors.Wrap(err, "loading profile")
}
// Run any additional validations for this property
if err := run(cc, name, value, a.validations); err != nil {
return errors.Wrap(err, "running validations")
}
if err := a.set(cc, name, value); err != nil {
return errors.Wrap(err, "setting new value of addon")
}
// Run any callbacks for this property
if err := run(cc, name, value, a.callbacks); err != nil {
return errors.Wrap(err, "running callbacks")
}
return nil
}
// Set sets a value in the config (not threadsafe)
func Set(cc *config.ClusterConfig, name string, value string) error {
a, valid := isAddonValid(name)
if !valid {
return errors.Errorf("%s is not a valid addon", name)
}
return a.set(cc, name, value)
}
// SetAndSave sets a value and saves the config
func SetAndSave(profile string, name string, value string) error {
cc, err := config.Load(profile)
if err != nil {
return errors.Wrap(err, "loading profile")
}
if err := RunCallbacks(cc, name, value); err != nil {
return errors.Wrap(err, "run callbacks")
}
if err := Set(cc, name, value); err != nil {
return errors.Wrap(err, "set")
}
glog.Infof("Writing out %q config to set %s=%v...", profile, name, value)
return config.Write(profile, cc)
@ -87,7 +105,7 @@ func run(cc *config.ClusterConfig, name string, value string, fns []setFn) error
return nil
}
// SetBool sets a bool value
// SetBool sets a bool value in the config (not threadsafe)
func SetBool(cc *config.ClusterConfig, name string, val string) error {
b, err := strconv.ParseBool(val)
if err != nil {
@ -110,13 +128,7 @@ func enableOrDisableAddon(cc *config.ClusterConfig, name string, val string) err
addon := assets.Addons[name]
// check addon status before enabling/disabling it
alreadySet, err := isAddonAlreadySet(addon, enable, cc.Name)
if err != nil {
out.ErrT(out.Conflict, "{{.error}}", out.V{"error": err})
return err
}
if alreadySet {
if isAddonAlreadySet(cc, addon, enable) {
glog.Warningf("addon %s should already be in state %v", name, val)
if !enable {
return nil
@ -160,7 +172,7 @@ https://github.com/kubernetes/minikube/issues/7332`, out.V{"driver_name": cc.Dri
mName := driver.MachineName(*cc, cp)
host, err := machine.LoadHost(api, mName)
if err != nil || !machine.IsRunning(api, mName) {
glog.Warningf("%q is not running, writing %s=%v to disk and skipping enablement (err=%v)", mName, addon.Name(), enable, err)
glog.Warningf("%q is not running, setting %s=%v and skipping enablement (err=%v)", mName, addon.Name(), enable, err)
return nil
}
@ -173,19 +185,17 @@ https://github.com/kubernetes/minikube/issues/7332`, out.V{"driver_name": cc.Dri
return enableOrDisableAddonInternal(cc, addon, cmd, data, enable)
}
func isAddonAlreadySet(addon *assets.Addon, enable bool, profile string) (bool, error) {
addonStatus, err := addon.IsEnabled(profile)
if err != nil {
return false, errors.Wrap(err, "is enabled")
func isAddonAlreadySet(cc *config.ClusterConfig, addon *assets.Addon, enable bool) bool {
enabled := addon.IsEnabled(cc)
if enabled && enable {
return true
}
if addonStatus && enable {
return true, nil
} else if !addonStatus && !enable {
return true, nil
if !enabled && !enable {
return true
}
return false, nil
return false
}
func enableOrDisableAddonInternal(cc *config.ClusterConfig, addon *assets.Addon, cmd command.Runner, data interface{}, enable bool) error {
@ -287,7 +297,10 @@ func enableOrDisableStorageClasses(cc *config.ClusterConfig, name string, val st
}
// Start enables the default addons for a profile, plus any additional
func Start(profile string, toEnable map[string]bool, additional []string) {
func Start(wg *sync.WaitGroup, cc *config.ClusterConfig, toEnable map[string]bool, additional []string) {
wg.Add(1)
defer wg.Done()
start := time.Now()
glog.Infof("enableAddons start: toEnable=%v, additional=%s", toEnable, additional)
defer func() {
@ -296,11 +309,7 @@ func Start(profile string, toEnable map[string]bool, additional []string) {
// Get the default values of any addons not saved to our config
for name, a := range assets.Addons {
defaultVal, err := a.IsEnabled(profile)
if err != nil {
glog.Errorf("is-enabled failed for %q: %v", a.Name(), err)
continue
}
defaultVal := a.IsEnabled(cc)
_, exists := toEnable[name]
if !exists {
@ -321,12 +330,25 @@ func Start(profile string, toEnable map[string]bool, additional []string) {
}
sort.Strings(toEnableList)
var awg sync.WaitGroup
out.T(out.AddonEnable, "Enabling addons: {{.addons}}", out.V{"addons": strings.Join(toEnableList, ", ")})
for _, a := range toEnableList {
err := Set(a, "true", profile)
if err != nil {
// Intentionally non-fatal
out.WarningT("Enabling '{{.name}}' returned an error: {{.error}}", out.V{"name": a, "error": err})
awg.Add(1)
go func(name string) {
err := RunCallbacks(cc, name, "true")
if err != nil {
out.WarningT("Enabling '{{.name}}' returned an error: {{.error}}", out.V{"name": name, "error": err})
}
awg.Done()
}(a)
}
// Wait until all of the addons are enabled before updating the config (not thread safe)
awg.Wait()
for _, a := range toEnableList {
if err := Set(cc, a, "true"); err != nil {
glog.Errorf("store failed: %v", err)
}
}
}

View File

@ -20,6 +20,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"sync"
"testing"
"k8s.io/minikube/pkg/minikube/assets"
@ -59,47 +60,42 @@ func createTestProfile(t *testing.T) string {
}
func TestIsAddonAlreadySet(t *testing.T) {
profile := createTestProfile(t)
if err := Set("registry", "true", profile); err != nil {
cc := &config.ClusterConfig{Name: "test"}
if err := Set(cc, "registry", "true"); err != nil {
t.Errorf("unable to set registry true: %v", err)
}
enabled, err := assets.Addons["registry"].IsEnabled(profile)
if err != nil {
t.Errorf("registry: %v", err)
}
if !enabled {
if !assets.Addons["registry"].IsEnabled(cc) {
t.Errorf("expected registry to be enabled")
}
enabled, err = assets.Addons["ingress"].IsEnabled(profile)
if err != nil {
t.Errorf("ingress: %v", err)
}
if enabled {
if assets.Addons["ingress"].IsEnabled(cc) {
t.Errorf("expected ingress to not be enabled")
}
}
func TestDisableUnknownAddon(t *testing.T) {
profile := createTestProfile(t)
if err := Set("InvalidAddon", "false", profile); err == nil {
cc := &config.ClusterConfig{Name: "test"}
if err := Set(cc, "InvalidAddon", "false"); err == nil {
t.Fatalf("Disable did not return error for unknown addon")
}
}
func TestEnableUnknownAddon(t *testing.T) {
profile := createTestProfile(t)
if err := Set("InvalidAddon", "true", profile); err == nil {
cc := &config.ClusterConfig{Name: "test"}
if err := Set(cc, "InvalidAddon", "true"); err == nil {
t.Fatalf("Enable did not return error for unknown addon")
}
}
func TestEnableAndDisableAddon(t *testing.T) {
func TestSetAndSave(t *testing.T) {
profile := createTestProfile(t)
// enable
if err := Set("dashboard", "true", profile); err != nil {
if err := SetAndSave(profile, "dashboard", "true"); err != nil {
t.Errorf("Disable returned unexpected error: " + err.Error())
}
@ -112,7 +108,7 @@ func TestEnableAndDisableAddon(t *testing.T) {
}
// disable
if err := Set("dashboard", "false", profile); err != nil {
if err := SetAndSave(profile, "dashboard", "false"); err != nil {
t.Errorf("Disable returned unexpected error: " + err.Error())
}
@ -126,14 +122,18 @@ func TestEnableAndDisableAddon(t *testing.T) {
}
func TestStart(t *testing.T) {
profile := createTestProfile(t)
Start(profile, map[string]bool{}, []string{"dashboard"})
enabled, err := assets.Addons["dashboard"].IsEnabled(profile)
if err != nil {
t.Errorf("dashboard: %v", err)
cc := &config.ClusterConfig{
Name: "start",
CPUs: 2,
Memory: 2500,
KubernetesConfig: config.KubernetesConfig{},
}
if !enabled {
var wg sync.WaitGroup
Start(&wg, cc, map[string]bool{}, []string{"dashboard"})
wg.Wait()
if !assets.Addons["dashboard"].IsEnabled(cc) {
t.Errorf("expected dashboard to be enabled")
}
}

View File

@ -19,8 +19,6 @@ package assets
import (
"runtime"
"github.com/golang/glog"
"github.com/pkg/errors"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/minikube/vmpath"
@ -49,21 +47,14 @@ func (a *Addon) Name() string {
}
// IsEnabled checks if an Addon is enabled for the given profile
func (a *Addon) IsEnabled(profile string) (bool, error) {
c, err := config.Load(profile)
if err != nil {
return false, errors.Wrap(err, "load")
}
// Is this addon explicitly listed in their configuration?
status, ok := c.Addons[a.Name()]
glog.V(1).Infof("IsEnabled %q = %v (listed in config=%v)", a.Name(), status, ok)
func (a *Addon) IsEnabled(cc *config.ClusterConfig) bool {
status, ok := cc.Addons[a.Name()]
if ok {
return status, nil
return status
}
// Return the default unconfigured state of the addon
return a.enabled, nil
return a.enabled
}
// Addons is the list of addons

View File

@ -20,6 +20,7 @@ package exit
import (
"os"
"runtime"
"runtime/debug"
"k8s.io/minikube/pkg/minikube/out"
"k8s.io/minikube/pkg/minikube/problem"
@ -53,6 +54,7 @@ func WithCodeT(code int, format string, a ...out.V) {
// WithError outputs an error and exits.
func WithError(msg string, err error) {
glog.Infof("WithError(%s)=%v called from:\n%s", msg, err, debug.Stack())
p := problem.FromError(err, runtime.GOOS)
if p != nil {
WithProblem(msg, err, p)

View File

@ -22,6 +22,7 @@ import (
"os/exec"
"path/filepath"
"strconv"
"sync"
"github.com/golang/glog"
"github.com/spf13/viper"
@ -46,7 +47,10 @@ func showVersionInfo(k8sVersion string, cr cruntime.Manager) {
}
// configureMounts configures any requested filesystem mounts
func configureMounts() {
func configureMounts(wg *sync.WaitGroup) {
wg.Add(1)
defer wg.Done()
if !viper.GetBool(createMount) {
return
}

View File

@ -134,15 +134,20 @@ func Start(starter Starter, apiServer bool) (*kubeconfig.Settings, error) {
}
}
configureMounts()
var wg sync.WaitGroup
go configureMounts(&wg)
if err := CacheAndLoadImagesInConfig(); err != nil {
out.FailureT("Unable to load cached images from config file.")
}
wg.Add(1)
go func() {
if err := CacheAndLoadImagesInConfig(); err != nil {
out.FailureT("Unable to load cached images from config file: {{error}}", out.V{"error": err})
}
wg.Done()
}()
// enable addons, both old and new!
if starter.ExistingAddons != nil {
addons.Start(viper.GetString(config.ProfileName), starter.ExistingAddons, config.AddonList)
go addons.Start(&wg, &cc, starter.ExistingAddons, config.AddonList)
}
if apiServer {
@ -182,7 +187,10 @@ func Start(starter Starter, apiServer bool) (*kubeconfig.Settings, error) {
}
}
return kcs, nil
wg.Wait()
// Write enabled addons to the config before completion
return kcs, config.Write(viper.GetString(config.ProfileName), &cc)
}
// Provision provisions the machine/container for the node