From fd4b54af59758255476d79e753ff7abd31689272 Mon Sep 17 00:00:00 2001 From: Nanik T Date: Fri, 1 Nov 2019 16:42:21 +1100 Subject: [PATCH] Fix --kubernetes-version for upgrading cluster to correct version This PR fix the issue of checking kubernetes-version and upgrading it. The fix now will do the following * Not specifying the --kubernetes-version flag means just use the currently deployed version. * If an update is available inform the user that they may use --kubernetes-version=. * When --kubernetes-version is specifically set, upgrade the cluster. Also add integration testing to test upgrading and downgrading --- cmd/minikube/cmd/start.go | 26 ++++-- cmd/minikube/cmd/start_test.go | 81 +++++++++++++++++ test/integration/version_upgrade_test.go | 106 +++++++++++++++++++++++ 3 files changed, 206 insertions(+), 7 deletions(-) diff --git a/cmd/minikube/cmd/start.go b/cmd/minikube/cmd/start.go index 3dc76e09ef1e..f344d820b192 100644 --- a/cmd/minikube/cmd/start.go +++ b/cmd/minikube/cmd/start.go @@ -179,7 +179,7 @@ func initMinikubeFlags() { // initKubernetesFlags inits the commandline flags for kubernetes related options func initKubernetesFlags() { - startCmd.Flags().String(kubernetesVersion, constants.DefaultKubernetesVersion, "The kubernetes version that the minikube VM will use (ex: v1.2.3)") + startCmd.Flags().String(kubernetesVersion, "", "The kubernetes version that the minikube VM will use (ex: v1.2.3)") startCmd.Flags().Var(&extraOptions, "extra-config", `A set of key=value pairs that describe configuration that may be passed to different components. The key should be '.' separated, and the first part before the dot is the component to apply the configuration to. @@ -1057,15 +1057,20 @@ Suggested workarounds: // getKubernetesVersion ensures that the requested version is reasonable func getKubernetesVersion(old *cfg.Config) (string, bool) { - rawVersion := viper.GetString(kubernetesVersion) + paramVersion := viper.GetString(kubernetesVersion) isUpgrade := false - if rawVersion == "" { - rawVersion = constants.DefaultKubernetesVersion + + if paramVersion == "" { // if the user did not specify any version then ... + if old != nil { // .. use the old version from config + paramVersion = old.KubernetesConfig.KubernetesVersion + } else { // .. otherwise use the default version + paramVersion = constants.DefaultKubernetesVersion + } } - nvs, err := semver.Make(strings.TrimPrefix(rawVersion, version.VersionPrefix)) + nvs, err := semver.Make(strings.TrimPrefix(paramVersion, version.VersionPrefix)) if err != nil { - exit.WithCodeT(exit.Data, `Unable to parse "{{.kubernetes_version}}": {{.error}}`, out.V{"kubernetes_version": rawVersion, "error": err}) + exit.WithCodeT(exit.Data, `Unable to parse "{{.kubernetes_version}}": {{.error}}`, out.V{"kubernetes_version": paramVersion, "error": err}) } nv := version.VersionPrefix + nvs.String() @@ -1077,6 +1082,10 @@ func getKubernetesVersion(old *cfg.Config) (string, bool) { if err != nil { exit.WithCodeT(exit.Data, "Unable to parse oldest Kubernetes version from constants: {{.error}}", out.V{"error": err}) } + defaultVersion, err := semver.Make(strings.TrimPrefix(constants.DefaultKubernetesVersion, version.VersionPrefix)) + if err != nil { + exit.WithCodeT(exit.Data, "Unable to parse default Kubernetes version from constants: {{.error}}", out.V{"error": err}) + } if nvs.LT(oldestVersion) { out.WarningT("Specified Kubernetes version {{.specified}} is less than the oldest supported version: {{.oldest}}", out.V{"specified": nvs, "oldest": constants.OldestKubernetesVersion}) @@ -1105,8 +1114,11 @@ func getKubernetesVersion(old *cfg.Config) (string, bool) { * Reuse the existing cluster with Kubernetes v{{.old}} or newer: Run "minikube start {{.profile}} --kubernetes-version={{.old}}"`, out.V{"new": nvs, "old": ovs, "profile": profileArg}) } + if defaultVersion.GT(nvs) { + out.T(out.ThumbsUp, "Kubernetes {{.new}} is now available. If you would like to upgrade, specify: --kubernetes-version={{.new}}", out.V{"new": defaultVersion}) + } + if nvs.GT(ovs) { - out.T(out.ThumbsUp, "Upgrading from Kubernetes {{.old}} to {{.new}}", out.V{"old": ovs, "new": nvs}) isUpgrade = true } return nv, isUpgrade diff --git a/cmd/minikube/cmd/start_test.go b/cmd/minikube/cmd/start_test.go index 0f4fe397d2d6..f990c8f294c0 100644 --- a/cmd/minikube/cmd/start_test.go +++ b/cmd/minikube/cmd/start_test.go @@ -17,14 +17,95 @@ limitations under the License. package cmd import ( + "io/ioutil" "os" + "strings" "testing" "github.com/spf13/cobra" "github.com/spf13/viper" + cfg "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/constants" ) +func TestGetKuberneterVersion(t *testing.T) { + var tests = []struct { + description string + expectedVersion string + paramVersion string + upgrade bool + stderrmsg string + cfg *cfg.Config + }{ + { + description: "kubernetes-version not given, no config", + expectedVersion: constants.DefaultKubernetesVersion, + paramVersion: "", + upgrade: false, + }, + { + description: "kubernetes-version not given, config available", + expectedVersion: "v1.15.0", + paramVersion: "", + upgrade: false, + cfg: &cfg.Config{KubernetesConfig: cfg.KubernetesConfig{KubernetesVersion: "v1.15.0"}}, + }, + { + description: "kubernetes-version given, no config", + expectedVersion: "v1.15.0", + paramVersion: "v1.15.0", + upgrade: false, + }, + { + description: "kubernetes-version given, config available", + expectedVersion: "v1.16.0", + paramVersion: "v1.16.0", + upgrade: true, + stderrmsg: "Kubernetes 1.16.2 is now available", + cfg: &cfg.Config{KubernetesConfig: cfg.KubernetesConfig{KubernetesVersion: "v1.15.0"}}, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + oldStderr := os.Stderr + reader, writer, err := os.Pipe() + if err != nil { + t.Fatalf("something is wrong when trying os.Pipe") + } + + os.Stderr = writer + + viper.SetDefault(kubernetesVersion, test.paramVersion) + version, upgrade := getKubernetesVersion(test.cfg) + + writer.Close() + // read the output printed on stdErr + out, err := ioutil.ReadAll(reader) + if err != nil { + t.Fatalf("error reading from the piped stderr") + } + os.Stderr = oldStderr + reader.Close() + + // check whether the printed output is the same + if !strings.Contains(string(out), test.stderrmsg) { + t.Fatalf("test failed returned result does not contain the string %s", test.stderrmsg) + } + + // check whether we are getting the expected version + if version != test.expectedVersion { + t.Fatalf("test failed because the expected version %s is not returned", test.expectedVersion) + } + + // check whether the upgrade flag is correct + if test.upgrade != upgrade { + t.Fatalf("test failed expected upgrade is %t", test.upgrade) + } + }) + } +} + func TestGenerateCfgFromFlagsHTTPProxyHandling(t *testing.T) { viper.SetDefault(memory, defaultMemorySize) viper.SetDefault(humanReadableDiskSize, defaultDiskSize) diff --git a/test/integration/version_upgrade_test.go b/test/integration/version_upgrade_test.go index 78bc9ef71221..2d714d233343 100644 --- a/test/integration/version_upgrade_test.go +++ b/test/integration/version_upgrade_test.go @@ -35,6 +35,112 @@ import ( pkgutil "k8s.io/minikube/pkg/util" ) +// TestDifferentVersionUpgrade test installation of different version of Kubernetes. +// Start with installing from v1.13.0 then to v1.15.0 and then try to downgrade it +// to v1.13.0 +// Following are the test cases that the function is doing +// 1. Start with specific version +// ./out/minikube start -p vupgrade --kubernetes-version=v1.13.0 +// 2. Upgrade with newer version +// ./out/minikube start -p vupgrade --kubernetes-version=v1.13.0 +// 3. Downgrade with older version (this will spit out error and test the error message) +// ./out/minikube start -p vupgrade --kubernetes-version=v1.13.0 +func TestDifferentVersionUpgrade(t *testing.T) { + MaybeParallel(t) + WaitForStartSlot(t) + profile := UniqueProfileName("testkubernetestversions") + + ctx, cancel := context.WithTimeout(context.Background(), 55*time.Minute) + defer CleanupWithLogs(t, profile, cancel) + + // create a temporary file + tf, err := ioutil.TempFile("", "minikube-release.*.exe") + if err != nil { + t.Fatalf("tempfile: %v", err) + } + defer os.Remove(tf.Name()) + tf.Close() + + // download minikube + url := pkgutil.GetBinaryDownloadURL("latest", runtime.GOOS) + + // chmod the binary (when Linux) + if runtime.GOOS != "windows" { + if err := os.Chmod(tf.Name(), 0700); err != nil { + t.Errorf("chmod: %v", err) + } + } + + // download the file + if err := retry.Expo(func() error { return getter.GetFile(tf.Name(), url) }, 3*time.Second, 3*time.Minute); err != nil { + t.Fatalf("get failed: %v", err) + } + + t.Run("parallel", func(t *testing.T) { + tests := []struct { + name string + message string + kubernetesversion string + expectstartupfailure bool + }{ + {"fresh install kubernetes v1.13.0", "v1.13.0","v1.13.0", false}, + {"upgrade kubernetes to v 1.14.0", "v1.14.0","v1.14.0", false}, + {"downgrade kubernetes to v 1.12.0", "Non-destructive downgrades are not supported","v1.12.0", true}, + } + for _, tc := range tests { + tc := tc + t.Run(tc.name, func(t *testing.T) { + // instruct to install kubernetes v1.13.0 + args := append([]string{"start", "-p", profile, fmt.Sprintf("--kubernetes-version=%s", tc.kubernetesversion), "--alsologtostderr", "-v=1"}, StartArgs()...) + + rr := &RunResult{} + var startingFunc = func() error { + rr, err = Run(t, exec.CommandContext(ctx, tf.Name(), args...)) + return err + } + + // if the test expect failure the function is different + if (tc.expectstartupfailure) { + startingFunc = func() error { + rr, err = Run(t, exec.CommandContext(ctx, tf.Name(), args...)) + stderr := rr.Stderr.String() + if (! strings.Contains(stderr, tc.message)) { + t.Fatalf("test failed as no message %s detected", tc.message) + return err + } + return nil + } + } + + err := retry.Expo(startingFunc, 1*time.Second, 30*time.Minute, 3) + + // as the text expect failure there is no need to continue the flow + if (tc.expectstartupfailure) { + return + } + + if err != nil { + t.Fatalf("release start failed: %v", err) + } + + // grab the Stdout for checking... + ss := rr.Stdout.String() + + // fail if it does not contain the string + if (! strings.Contains(ss, tc.message)) { + t.Fatalf("test failed as no message %s detected", tc.message) + } + + // stop minikube + rr, err = Run(t, exec.CommandContext(ctx, tf.Name(), "stop", "-p", profile)) + if err != nil { + t.Fatalf("%s failed: %v", rr.Args, err) + } + }) + } + }) + +} // TestVersionUpgrade downloads latest version of minikube and runs with // the odlest supported k8s version and then runs the current head minikube // and it tries to upgrade from the older supported k8s to news supported k8s