From fb5fe04a1ee0fb676c08fbaa545da574f7bb6bb8 Mon Sep 17 00:00:00 2001 From: Chuck Ha Date: Thu, 26 Apr 2018 11:36:13 +0100 Subject: [PATCH] Enable bypassing online checks in kubeadm upgrade plan `kubeadm upgrade plan ` is now supported. If no version is supplied then the original behavior remains. If a version is supplied there will be no pause when figuring out versions. Kubeadm will assume the version you pass in is the latest stable version. Signed-off-by: Chuck Ha --- cmd/kubeadm/app/cmd/upgrade/common.go | 2 +- cmd/kubeadm/app/cmd/upgrade/plan.go | 38 ++++-- .../app/phases/upgrade/compute_test.go | 113 +++++++++++++++--- .../app/phases/upgrade/versiongetter.go | 27 +++++ 4 files changed, 153 insertions(+), 27 deletions(-) diff --git a/cmd/kubeadm/app/cmd/upgrade/common.go b/cmd/kubeadm/app/cmd/upgrade/common.go index 82c3c108198..1598e6e7423 100644 --- a/cmd/kubeadm/app/cmd/upgrade/common.go +++ b/cmd/kubeadm/app/cmd/upgrade/common.go @@ -87,7 +87,7 @@ func enforceRequirements(flags *cmdUpgradeFlags, dryRun bool, newK8sVersion stri client: client, cfg: cfg, // Use a real version getter interface that queries the API server, the kubeadm client and the Kubernetes CI system for latest versions - versionGetter: upgrade.NewKubeVersionGetter(client, os.Stdout), + versionGetter: upgrade.NewOfflineVersionGetter(upgrade.NewKubeVersionGetter(client, os.Stdout), newK8sVersion), // Use the waiter conditionally based on the dryrunning variable waiter: getWaiter(dryRun, client), }, nil diff --git a/cmd/kubeadm/app/cmd/upgrade/plan.go b/cmd/kubeadm/app/cmd/upgrade/plan.go index da7294874c5..b2e20ec927b 100644 --- a/cmd/kubeadm/app/cmd/upgrade/plan.go +++ b/cmd/kubeadm/app/cmd/upgrade/plan.go @@ -34,12 +34,21 @@ import ( etcdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd" ) +type planFlags struct { + newK8sVersionStr string + parent *cmdUpgradeFlags +} + // NewCmdPlan returns the cobra command for `kubeadm upgrade plan` func NewCmdPlan(parentFlags *cmdUpgradeFlags) *cobra.Command { + flags := &planFlags{ + parent: parentFlags, + } + cmd := &cobra.Command{ - Use: "plan", - Short: "Check which versions are available to upgrade to and validate whether your current cluster is upgradeable.", - Run: func(_ *cobra.Command, _ []string) { + Use: "plan [version] [flags]", + Short: "Check which versions are available to upgrade to and validate whether your current cluster is upgradeable. To skip the internet check, pass in the optional [version] parameter.", + Run: func(_ *cobra.Command, args []string) { var err error parentFlags.ignorePreflightErrorsSet, err = validation.ValidateIgnorePreflightErrors(parentFlags.ignorePreflightErrors, parentFlags.skipPreFlight) kubeadmutil.CheckErr(err) @@ -47,7 +56,22 @@ func NewCmdPlan(parentFlags *cmdUpgradeFlags) *cobra.Command { err = runPreflightChecks(parentFlags.ignorePreflightErrorsSet) kubeadmutil.CheckErr(err) - err = RunPlan(parentFlags) + // If the version is specified in config file, pick up that value. + if parentFlags.cfgPath != "" { + glog.V(1).Infof("fetching configuration from file", parentFlags.cfgPath) + cfg, err := upgrade.FetchConfigurationFromFile(parentFlags.cfgPath) + kubeadmutil.CheckErr(err) + + if cfg.KubernetesVersion != "" { + flags.newK8sVersionStr = cfg.KubernetesVersion + } + } + // If option was specified in both args and config file, args will overwrite the config file. + if len(args) == 1 { + flags.newK8sVersionStr = args[0] + } + + err = RunPlan(flags) kubeadmutil.CheckErr(err) }, } @@ -56,11 +80,11 @@ func NewCmdPlan(parentFlags *cmdUpgradeFlags) *cobra.Command { } // RunPlan takes care of outputting available versions to upgrade to for the user -func RunPlan(parentFlags *cmdUpgradeFlags) error { +func RunPlan(flags *planFlags) error { // Start with the basics, verify that the cluster is healthy, build a client and a versionGetter. Never dry-run when planning. glog.V(1).Infof("[upgrade/plan] verifying health of cluster") glog.V(1).Infof("[upgrade/plan] retrieving configuration from cluster") - upgradeVars, err := enforceRequirements(parentFlags, false, "") + upgradeVars, err := enforceRequirements(flags.parent, false, flags.newK8sVersionStr) if err != nil { return err } @@ -77,7 +101,7 @@ func RunPlan(parentFlags *cmdUpgradeFlags) error { // Compute which upgrade possibilities there are glog.V(1).Infof("[upgrade/plan] computing upgrade possibilities") - availUpgrades, err := upgrade.GetAvailableUpgrades(upgradeVars.versionGetter, parentFlags.allowExperimentalUpgrades, parentFlags.allowRCUpgrades, etcdClient, upgradeVars.cfg.FeatureGates) + availUpgrades, err := upgrade.GetAvailableUpgrades(upgradeVars.versionGetter, flags.parent.allowExperimentalUpgrades, flags.parent.allowRCUpgrades, etcdClient, upgradeVars.cfg.FeatureGates) if err != nil { return fmt.Errorf("[upgrade/versions] FATAL: %v", err) } diff --git a/cmd/kubeadm/app/phases/upgrade/compute_test.go b/cmd/kubeadm/app/phases/upgrade/compute_test.go index 6374978976f..071284a56cd 100644 --- a/cmd/kubeadm/app/phases/upgrade/compute_test.go +++ b/cmd/kubeadm/app/phases/upgrade/compute_test.go @@ -79,12 +79,14 @@ func (f fakeEtcdCluster) WaitForStatus(delay time.Duration, retries int, retryIn func TestGetAvailableUpgrades(t *testing.T) { featureGates := make(map[string]bool) tests := []struct { - vg *fakeVersionGetter + name string + vg VersionGetter expectedUpgrades []Upgrade allowExperimental, allowRCs bool errExpected bool }{ - { // no action needed, already up-to-date + { + name: "no action needed, already up-to-date", vg: &fakeVersionGetter{ clusterVersion: "v1.9.3", kubeletVersion: "v1.9.3", @@ -97,7 +99,8 @@ func TestGetAvailableUpgrades(t *testing.T) { allowExperimental: false, errExpected: false, }, - { // simple patch version upgrade + { + name: "simple patch version upgrade", vg: &fakeVersionGetter{ clusterVersion: "v1.9.1", kubeletVersion: "v1.9.1", // the kubelet are on the same version as the control plane @@ -129,7 +132,41 @@ func TestGetAvailableUpgrades(t *testing.T) { allowExperimental: false, errExpected: false, }, - { // minor version upgrade only + { + name: "no version provided to offline version getter does not change behavior", + vg: NewOfflineVersionGetter(&fakeVersionGetter{ + clusterVersion: "v1.9.1", + kubeletVersion: "v1.9.1", // the kubelet are on the same version as the control plane + kubeadmVersion: "v1.9.2", + + stablePatchVersion: "v1.9.3", + stableVersion: "v1.9.3", + }, ""), + expectedUpgrades: []Upgrade{ + { + Description: "version in the v1.9 series", + Before: ClusterState{ + KubeVersion: "v1.9.1", + KubeletVersions: map[string]uint16{ + "v1.9.1": 1, + }, + KubeadmVersion: "v1.9.2", + DNSVersion: "1.14.10", + EtcdVersion: "3.1.12", + }, + After: ClusterState{ + KubeVersion: "v1.9.3", + KubeadmVersion: "v1.9.3", + DNSVersion: "1.14.10", + EtcdVersion: "3.1.12", + }, + }, + }, + allowExperimental: false, + errExpected: false, + }, + { + name: "minor version upgrade only", vg: &fakeVersionGetter{ clusterVersion: "v1.9.1", kubeletVersion: "v1.9.1", // the kubelet are on the same version as the control plane @@ -161,7 +198,8 @@ func TestGetAvailableUpgrades(t *testing.T) { allowExperimental: false, errExpected: false, }, - { // both minor version upgrade and patch version upgrade available + { + name: "both minor version upgrade and patch version upgrade available", vg: &fakeVersionGetter{ clusterVersion: "v1.9.3", kubeletVersion: "v1.9.3", // the kubelet are on the same version as the control plane @@ -211,7 +249,8 @@ func TestGetAvailableUpgrades(t *testing.T) { allowExperimental: false, errExpected: false, }, - { // allow experimental upgrades, but no upgrade available + { + name: "allow experimental upgrades, but no upgrade available", vg: &fakeVersionGetter{ clusterVersion: "v1.10.0-alpha.2", kubeletVersion: "v1.9.5", @@ -225,7 +264,8 @@ func TestGetAvailableUpgrades(t *testing.T) { allowExperimental: true, errExpected: false, }, - { // upgrade to an unstable version should be supported + { + name: "upgrade to an unstable version should be supported", vg: &fakeVersionGetter{ clusterVersion: "v1.9.5", kubeletVersion: "v1.9.5", @@ -258,7 +298,8 @@ func TestGetAvailableUpgrades(t *testing.T) { allowExperimental: true, errExpected: false, }, - { // upgrade from an unstable version to an unstable version should be supported + { + name: "upgrade from an unstable version to an unstable version should be supported", vg: &fakeVersionGetter{ clusterVersion: "v1.10.0-alpha.1", kubeletVersion: "v1.9.5", @@ -291,7 +332,8 @@ func TestGetAvailableUpgrades(t *testing.T) { allowExperimental: true, errExpected: false, }, - { // v1.X.0-alpha.0 should be ignored + { + name: "v1.X.0-alpha.0 should be ignored", vg: &fakeVersionGetter{ clusterVersion: "v1.9.5", kubeletVersion: "v1.9.5", @@ -325,7 +367,8 @@ func TestGetAvailableUpgrades(t *testing.T) { allowExperimental: true, errExpected: false, }, - { // upgrade to an RC version should be supported + { + name: "upgrade to an RC version should be supported", vg: &fakeVersionGetter{ clusterVersion: "v1.9.5", kubeletVersion: "v1.9.5", @@ -359,7 +402,8 @@ func TestGetAvailableUpgrades(t *testing.T) { allowRCs: true, errExpected: false, }, - { // it is possible (but very uncommon) that the latest version from the previous branch is an rc and the current latest version is alpha.0. In that case, show the RC + { + name: "it is possible (but very uncommon) that the latest version from the previous branch is an rc and the current latest version is alpha.0. In that case, show the RC", vg: &fakeVersionGetter{ clusterVersion: "v1.9.5", kubeletVersion: "v1.9.5", @@ -393,7 +437,8 @@ func TestGetAvailableUpgrades(t *testing.T) { allowExperimental: true, errExpected: false, }, - { // upgrade to an RC version should be supported. There may also be an even newer unstable version. + { + name: "upgrade to an RC version should be supported. There may also be an even newer unstable version.", vg: &fakeVersionGetter{ clusterVersion: "v1.9.5", kubeletVersion: "v1.9.5", @@ -446,19 +491,49 @@ func TestGetAvailableUpgrades(t *testing.T) { allowExperimental: true, errExpected: false, }, + { + name: "offline version getter", + vg: NewOfflineVersionGetter(&fakeVersionGetter{ + clusterVersion: "v1.10.1", + kubeletVersion: "v1.10.0", + kubeadmVersion: "v1.10.1", + }, "v1.11.1"), + expectedUpgrades: []Upgrade{ + { + Description: "version in the v1.1 series", + Before: ClusterState{ + KubeVersion: "v1.10.1", + KubeletVersions: map[string]uint16{ + "v1.10.0": 1, + }, + KubeadmVersion: "v1.10.1", + DNSVersion: "1.14.10", + EtcdVersion: "3.1.12", + }, + After: ClusterState{ + KubeVersion: "v1.11.1", + KubeadmVersion: "v1.11.1", + DNSVersion: "1.14.10", + EtcdVersion: "3.2.18", + }, + }, + }, + }, } // Instantiating a fake etcd cluster for being able to get etcd version for a corresponding // kubernetes release. testCluster := fakeEtcdCluster{} for _, rt := range tests { - actualUpgrades, actualErr := GetAvailableUpgrades(rt.vg, rt.allowExperimental, rt.allowRCs, testCluster, featureGates) - if !reflect.DeepEqual(actualUpgrades, rt.expectedUpgrades) { - t.Errorf("failed TestGetAvailableUpgrades\n\texpected upgrades: %v\n\tgot: %v", rt.expectedUpgrades, actualUpgrades) - } - if (actualErr != nil) != rt.errExpected { - t.Errorf("failed TestGetAvailableUpgrades\n\texpected error: %t\n\tgot error: %t", rt.errExpected, (actualErr != nil)) - } + t.Run(rt.name, func(t *testing.T) { + actualUpgrades, actualErr := GetAvailableUpgrades(rt.vg, rt.allowExperimental, rt.allowRCs, testCluster, featureGates) + if !reflect.DeepEqual(actualUpgrades, rt.expectedUpgrades) { + t.Errorf("failed TestGetAvailableUpgrades\n\texpected upgrades: %v\n\tgot: %v", rt.expectedUpgrades, actualUpgrades) + } + if (actualErr != nil) != rt.errExpected { + t.Errorf("failed TestGetAvailableUpgrades\n\texpected error: %t\n\tgot error: %t", rt.errExpected, (actualErr != nil)) + } + }) } } diff --git a/cmd/kubeadm/app/phases/upgrade/versiongetter.go b/cmd/kubeadm/app/phases/upgrade/versiongetter.go index 13c65294e50..d9c9d69aace 100644 --- a/cmd/kubeadm/app/phases/upgrade/versiongetter.go +++ b/cmd/kubeadm/app/phases/upgrade/versiongetter.go @@ -122,3 +122,30 @@ func computeKubeletVersions(nodes []v1.Node) map[string]uint16 { } return kubeletVersions } + +// OfflineVersionGetter will use the version provided or +type OfflineVersionGetter struct { + VersionGetter + version string +} + +// NewOfflineVersionGetter wraps a VersionGetter and skips online communication if default information is supplied. +// Version can be "" and the behavior will be identical to the versionGetter passed in. +func NewOfflineVersionGetter(versionGetter VersionGetter, version string) VersionGetter { + return &OfflineVersionGetter{ + VersionGetter: versionGetter, + version: version, + } +} + +// VersionFromCILabel will return the version that was passed into the struct +func (o *OfflineVersionGetter) VersionFromCILabel(ciVersionLabel, description string) (string, *versionutil.Version, error) { + if o.version == "" { + return o.VersionGetter.VersionFromCILabel(ciVersionLabel, description) + } + ver, err := versionutil.ParseSemantic(o.version) + if err != nil { + return "", nil, fmt.Errorf("Couldn't parse version %s: %v", description, err) + } + return o.version, ver, nil +}