mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 12:43:23 +00:00
Merge pull request #63201 from chuckha/offline-plan
Automatic merge from submit-queue (batch tested with PRs 63138, 63091, 63201, 63341). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Enable bypassing online checks in kubeadm upgrade plan Signed-off-by: Chuck Ha <ha.chuck@gmail.com> **What this PR does / why we need it**: This PR makes `kubeadm upgrade plan` a little nicer to use in an air gapped environment. `kubeadm upgrade plan` now accepts a version and returns that instead of checking the internet. **Which issue(s) this PR fixes**: Fixes kubernetes/kubeadm#698 **Special notes for your reviewer**: I also cleaned up the tests for this section of code by adding formal names for table tests and using `t.Run`. **Release note**: ```release-note `kubeadm upgrade plan` now accepts a version which improves the UX nicer in air-gapped environments. ```
This commit is contained in:
commit
18b545f67c
@ -87,7 +87,7 @@ func enforceRequirements(flags *cmdUpgradeFlags, dryRun bool, newK8sVersion stri
|
|||||||
client: client,
|
client: client,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
// Use a real version getter interface that queries the API server, the kubeadm client and the Kubernetes CI system for latest versions
|
// 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
|
// Use the waiter conditionally based on the dryrunning variable
|
||||||
waiter: getWaiter(dryRun, client),
|
waiter: getWaiter(dryRun, client),
|
||||||
}, nil
|
}, nil
|
||||||
|
@ -34,12 +34,21 @@ import (
|
|||||||
etcdutil "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd"
|
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`
|
// NewCmdPlan returns the cobra command for `kubeadm upgrade plan`
|
||||||
func NewCmdPlan(parentFlags *cmdUpgradeFlags) *cobra.Command {
|
func NewCmdPlan(parentFlags *cmdUpgradeFlags) *cobra.Command {
|
||||||
|
flags := &planFlags{
|
||||||
|
parent: parentFlags,
|
||||||
|
}
|
||||||
|
|
||||||
cmd := &cobra.Command{
|
cmd := &cobra.Command{
|
||||||
Use: "plan",
|
Use: "plan [version] [flags]",
|
||||||
Short: "Check which versions are available to upgrade to and validate whether your current cluster is upgradeable.",
|
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, _ []string) {
|
Run: func(_ *cobra.Command, args []string) {
|
||||||
var err error
|
var err error
|
||||||
parentFlags.ignorePreflightErrorsSet, err = validation.ValidateIgnorePreflightErrors(parentFlags.ignorePreflightErrors, parentFlags.skipPreFlight)
|
parentFlags.ignorePreflightErrorsSet, err = validation.ValidateIgnorePreflightErrors(parentFlags.ignorePreflightErrors, parentFlags.skipPreFlight)
|
||||||
kubeadmutil.CheckErr(err)
|
kubeadmutil.CheckErr(err)
|
||||||
@ -47,7 +56,22 @@ func NewCmdPlan(parentFlags *cmdUpgradeFlags) *cobra.Command {
|
|||||||
err = runPreflightChecks(parentFlags.ignorePreflightErrorsSet)
|
err = runPreflightChecks(parentFlags.ignorePreflightErrorsSet)
|
||||||
kubeadmutil.CheckErr(err)
|
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)
|
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
|
// 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.
|
// 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] verifying health of cluster")
|
||||||
glog.V(1).Infof("[upgrade/plan] retrieving configuration from 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -77,7 +101,7 @@ func RunPlan(parentFlags *cmdUpgradeFlags) error {
|
|||||||
|
|
||||||
// Compute which upgrade possibilities there are
|
// Compute which upgrade possibilities there are
|
||||||
glog.V(1).Infof("[upgrade/plan] computing upgrade possibilities")
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("[upgrade/versions] FATAL: %v", err)
|
return fmt.Errorf("[upgrade/versions] FATAL: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -79,12 +79,14 @@ func (f fakeEtcdCluster) WaitForStatus(delay time.Duration, retries int, retryIn
|
|||||||
func TestGetAvailableUpgrades(t *testing.T) {
|
func TestGetAvailableUpgrades(t *testing.T) {
|
||||||
featureGates := make(map[string]bool)
|
featureGates := make(map[string]bool)
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
vg *fakeVersionGetter
|
name string
|
||||||
|
vg VersionGetter
|
||||||
expectedUpgrades []Upgrade
|
expectedUpgrades []Upgrade
|
||||||
allowExperimental, allowRCs bool
|
allowExperimental, allowRCs bool
|
||||||
errExpected bool
|
errExpected bool
|
||||||
}{
|
}{
|
||||||
{ // no action needed, already up-to-date
|
{
|
||||||
|
name: "no action needed, already up-to-date",
|
||||||
vg: &fakeVersionGetter{
|
vg: &fakeVersionGetter{
|
||||||
clusterVersion: "v1.9.3",
|
clusterVersion: "v1.9.3",
|
||||||
kubeletVersion: "v1.9.3",
|
kubeletVersion: "v1.9.3",
|
||||||
@ -97,7 +99,8 @@ func TestGetAvailableUpgrades(t *testing.T) {
|
|||||||
allowExperimental: false,
|
allowExperimental: false,
|
||||||
errExpected: false,
|
errExpected: false,
|
||||||
},
|
},
|
||||||
{ // simple patch version upgrade
|
{
|
||||||
|
name: "simple patch version upgrade",
|
||||||
vg: &fakeVersionGetter{
|
vg: &fakeVersionGetter{
|
||||||
clusterVersion: "v1.9.1",
|
clusterVersion: "v1.9.1",
|
||||||
kubeletVersion: "v1.9.1", // the kubelet are on the same version as the control plane
|
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,
|
allowExperimental: false,
|
||||||
errExpected: 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{
|
vg: &fakeVersionGetter{
|
||||||
clusterVersion: "v1.9.1",
|
clusterVersion: "v1.9.1",
|
||||||
kubeletVersion: "v1.9.1", // the kubelet are on the same version as the control plane
|
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,
|
allowExperimental: false,
|
||||||
errExpected: false,
|
errExpected: false,
|
||||||
},
|
},
|
||||||
{ // both minor version upgrade and patch version upgrade available
|
{
|
||||||
|
name: "both minor version upgrade and patch version upgrade available",
|
||||||
vg: &fakeVersionGetter{
|
vg: &fakeVersionGetter{
|
||||||
clusterVersion: "v1.9.3",
|
clusterVersion: "v1.9.3",
|
||||||
kubeletVersion: "v1.9.3", // the kubelet are on the same version as the control plane
|
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,
|
allowExperimental: false,
|
||||||
errExpected: false,
|
errExpected: false,
|
||||||
},
|
},
|
||||||
{ // allow experimental upgrades, but no upgrade available
|
{
|
||||||
|
name: "allow experimental upgrades, but no upgrade available",
|
||||||
vg: &fakeVersionGetter{
|
vg: &fakeVersionGetter{
|
||||||
clusterVersion: "v1.10.0-alpha.2",
|
clusterVersion: "v1.10.0-alpha.2",
|
||||||
kubeletVersion: "v1.9.5",
|
kubeletVersion: "v1.9.5",
|
||||||
@ -225,7 +264,8 @@ func TestGetAvailableUpgrades(t *testing.T) {
|
|||||||
allowExperimental: true,
|
allowExperimental: true,
|
||||||
errExpected: false,
|
errExpected: false,
|
||||||
},
|
},
|
||||||
{ // upgrade to an unstable version should be supported
|
{
|
||||||
|
name: "upgrade to an unstable version should be supported",
|
||||||
vg: &fakeVersionGetter{
|
vg: &fakeVersionGetter{
|
||||||
clusterVersion: "v1.9.5",
|
clusterVersion: "v1.9.5",
|
||||||
kubeletVersion: "v1.9.5",
|
kubeletVersion: "v1.9.5",
|
||||||
@ -258,7 +298,8 @@ func TestGetAvailableUpgrades(t *testing.T) {
|
|||||||
allowExperimental: true,
|
allowExperimental: true,
|
||||||
errExpected: false,
|
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{
|
vg: &fakeVersionGetter{
|
||||||
clusterVersion: "v1.10.0-alpha.1",
|
clusterVersion: "v1.10.0-alpha.1",
|
||||||
kubeletVersion: "v1.9.5",
|
kubeletVersion: "v1.9.5",
|
||||||
@ -291,7 +332,8 @@ func TestGetAvailableUpgrades(t *testing.T) {
|
|||||||
allowExperimental: true,
|
allowExperimental: true,
|
||||||
errExpected: false,
|
errExpected: false,
|
||||||
},
|
},
|
||||||
{ // v1.X.0-alpha.0 should be ignored
|
{
|
||||||
|
name: "v1.X.0-alpha.0 should be ignored",
|
||||||
vg: &fakeVersionGetter{
|
vg: &fakeVersionGetter{
|
||||||
clusterVersion: "v1.9.5",
|
clusterVersion: "v1.9.5",
|
||||||
kubeletVersion: "v1.9.5",
|
kubeletVersion: "v1.9.5",
|
||||||
@ -325,7 +367,8 @@ func TestGetAvailableUpgrades(t *testing.T) {
|
|||||||
allowExperimental: true,
|
allowExperimental: true,
|
||||||
errExpected: false,
|
errExpected: false,
|
||||||
},
|
},
|
||||||
{ // upgrade to an RC version should be supported
|
{
|
||||||
|
name: "upgrade to an RC version should be supported",
|
||||||
vg: &fakeVersionGetter{
|
vg: &fakeVersionGetter{
|
||||||
clusterVersion: "v1.9.5",
|
clusterVersion: "v1.9.5",
|
||||||
kubeletVersion: "v1.9.5",
|
kubeletVersion: "v1.9.5",
|
||||||
@ -359,7 +402,8 @@ func TestGetAvailableUpgrades(t *testing.T) {
|
|||||||
allowRCs: true,
|
allowRCs: true,
|
||||||
errExpected: false,
|
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{
|
vg: &fakeVersionGetter{
|
||||||
clusterVersion: "v1.9.5",
|
clusterVersion: "v1.9.5",
|
||||||
kubeletVersion: "v1.9.5",
|
kubeletVersion: "v1.9.5",
|
||||||
@ -393,7 +437,8 @@ func TestGetAvailableUpgrades(t *testing.T) {
|
|||||||
allowExperimental: true,
|
allowExperimental: true,
|
||||||
errExpected: false,
|
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{
|
vg: &fakeVersionGetter{
|
||||||
clusterVersion: "v1.9.5",
|
clusterVersion: "v1.9.5",
|
||||||
kubeletVersion: "v1.9.5",
|
kubeletVersion: "v1.9.5",
|
||||||
@ -446,12 +491,41 @@ func TestGetAvailableUpgrades(t *testing.T) {
|
|||||||
allowExperimental: true,
|
allowExperimental: true,
|
||||||
errExpected: false,
|
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
|
// Instantiating a fake etcd cluster for being able to get etcd version for a corresponding
|
||||||
// kubernetes release.
|
// kubernetes release.
|
||||||
testCluster := fakeEtcdCluster{}
|
testCluster := fakeEtcdCluster{}
|
||||||
for _, rt := range tests {
|
for _, rt := range tests {
|
||||||
|
t.Run(rt.name, func(t *testing.T) {
|
||||||
actualUpgrades, actualErr := GetAvailableUpgrades(rt.vg, rt.allowExperimental, rt.allowRCs, testCluster, featureGates)
|
actualUpgrades, actualErr := GetAvailableUpgrades(rt.vg, rt.allowExperimental, rt.allowRCs, testCluster, featureGates)
|
||||||
if !reflect.DeepEqual(actualUpgrades, rt.expectedUpgrades) {
|
if !reflect.DeepEqual(actualUpgrades, rt.expectedUpgrades) {
|
||||||
t.Errorf("failed TestGetAvailableUpgrades\n\texpected upgrades: %v\n\tgot: %v", rt.expectedUpgrades, actualUpgrades)
|
t.Errorf("failed TestGetAvailableUpgrades\n\texpected upgrades: %v\n\tgot: %v", rt.expectedUpgrades, actualUpgrades)
|
||||||
@ -459,6 +533,7 @@ func TestGetAvailableUpgrades(t *testing.T) {
|
|||||||
if (actualErr != nil) != rt.errExpected {
|
if (actualErr != nil) != rt.errExpected {
|
||||||
t.Errorf("failed TestGetAvailableUpgrades\n\texpected error: %t\n\tgot error: %t", rt.errExpected, (actualErr != nil))
|
t.Errorf("failed TestGetAvailableUpgrades\n\texpected error: %t\n\tgot error: %t", rt.errExpected, (actualErr != nil))
|
||||||
}
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -122,3 +122,30 @@ func computeKubeletVersions(nodes []v1.Node) map[string]uint16 {
|
|||||||
}
|
}
|
||||||
return kubeletVersions
|
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
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user