mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 11:50:44 +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,
|
||||
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
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user