...

Source file src/k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade/compute_test.go

Documentation: k8s.io/kubernetes/cmd/kubeadm/app/phases/upgrade

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package upgrade
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  	"testing"
    23  
    24  	"github.com/google/go-cmp/cmp"
    25  	apps "k8s.io/api/apps/v1"
    26  	v1 "k8s.io/api/core/v1"
    27  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    28  	versionutil "k8s.io/apimachinery/pkg/util/version"
    29  	clientsetfake "k8s.io/client-go/kubernetes/fake"
    30  
    31  	"k8s.io/kubernetes/cmd/kubeadm/app/constants"
    32  	"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
    33  )
    34  
    35  type fakeVersionGetter struct {
    36  	clusterVersion         string
    37  	kubeadmVersion         string
    38  	stableVersion          string
    39  	latestVersion          string
    40  	latestDevBranchVersion string
    41  	stablePatchVersion     string
    42  	kubeletVersion         string
    43  	componentVersion       string
    44  	etcdVersion            string
    45  	isExternalEtcd         bool
    46  }
    47  
    48  var _ VersionGetter = &fakeVersionGetter{}
    49  
    50  // ClusterVersion gets a fake API server version
    51  func (f *fakeVersionGetter) ClusterVersion() (string, *versionutil.Version, error) {
    52  	return f.clusterVersion, versionutil.MustParseSemantic(f.clusterVersion), nil
    53  }
    54  
    55  // KubeadmVersion gets a fake kubeadm version
    56  func (f *fakeVersionGetter) KubeadmVersion() (string, *versionutil.Version, error) {
    57  	return f.kubeadmVersion, versionutil.MustParseSemantic(f.kubeadmVersion), nil
    58  }
    59  
    60  // VersionFromCILabel gets fake latest versions from CI
    61  func (f *fakeVersionGetter) VersionFromCILabel(ciVersionLabel, _ string) (string, *versionutil.Version, error) {
    62  	if ciVersionLabel == "stable" {
    63  		return f.stableVersion, versionutil.MustParseSemantic(f.stableVersion), nil
    64  	}
    65  	if ciVersionLabel == "latest" {
    66  		return f.latestVersion, versionutil.MustParseSemantic(f.latestVersion), nil
    67  	}
    68  	if f.latestDevBranchVersion != "" && strings.HasPrefix(ciVersionLabel, "latest-") {
    69  		return f.latestDevBranchVersion, versionutil.MustParseSemantic(f.latestDevBranchVersion), nil
    70  	}
    71  	return f.stablePatchVersion, versionutil.MustParseSemantic(f.stablePatchVersion), nil
    72  }
    73  
    74  // KubeletVersions should return a map with a version and a list of node names that describes how many kubelets there are for that version
    75  func (f *fakeVersionGetter) KubeletVersions() (map[string][]string, error) {
    76  	return map[string][]string{
    77  		f.kubeletVersion: {"node1"},
    78  	}, nil
    79  }
    80  
    81  // ComponentVersions should return a map with a version and a list of node names that describes how many a given control-plane components there are for that version
    82  func (f *fakeVersionGetter) ComponentVersions(name string) (map[string][]string, error) {
    83  	if name == constants.Etcd {
    84  		if f.isExternalEtcd {
    85  			return map[string][]string{}, nil
    86  		}
    87  		return map[string][]string{
    88  			f.etcdVersion: {"node1"},
    89  		}, nil
    90  	}
    91  
    92  	return map[string][]string{
    93  		f.componentVersion: {"node1"},
    94  	}, nil
    95  }
    96  
    97  const fakeCurrentEtcdVersion = "3.1.12"
    98  
    99  func getEtcdVersion(v *versionutil.Version) string {
   100  	etcdVer, _, _ := constants.EtcdSupportedVersion(constants.SupportedEtcdVersion, v.String())
   101  	return etcdVer.String()
   102  }
   103  
   104  const fakeCurrentCoreDNSVersion = "1.0.6"
   105  
   106  func TestGetAvailableUpgrades(t *testing.T) {
   107  
   108  	// constansts for test cases
   109  	// variables are in the form v{MAJOR}{MINOR}{PATCH}, where MINOR is a variable so test are automatically uptodate to the latest MinimumControlPlaneVersion/
   110  
   111  	// v1.X series, e.g. v1.14
   112  	v1X0 := versionutil.MustParseSemantic("v1.14.0")
   113  	v1X5 := v1X0.WithPatch(5)
   114  
   115  	// v1.Y series, where Y = X+1, e.g. v1.15
   116  	v1Y0 := versionutil.MustParseSemantic("v1.15.0")
   117  	v1Y0alpha0 := v1Y0.WithPreRelease("alpha.0")
   118  	v1Y0alpha1 := v1Y0.WithPreRelease("alpha.1")
   119  	v1Y1 := v1Y0.WithPatch(1)
   120  	v1Y2 := v1Y0.WithPatch(2)
   121  	v1Y3 := v1Y0.WithPatch(3)
   122  	v1Y5 := v1Y0.WithPatch(5)
   123  
   124  	// v1.Z series, where Z = Y+1, e.g. v1.16
   125  	v1Z0 := versionutil.MustParseSemantic("v1.16.0")
   126  	v1Z0alpha1 := v1Z0.WithPreRelease("alpha.1")
   127  	v1Z0alpha2 := v1Z0.WithPreRelease("alpha.2")
   128  	v1Z0beta1 := v1Z0.WithPreRelease("beta.1")
   129  	v1Z0rc1 := v1Z0.WithPreRelease("rc.1")
   130  	v1Z1 := v1Z0.WithPatch(1)
   131  
   132  	tests := []struct {
   133  		name                        string
   134  		vg                          VersionGetter
   135  		expectedUpgrades            []Upgrade
   136  		allowExperimental, allowRCs bool
   137  		errExpected                 bool
   138  		beforeDNSVersion            string
   139  	}{
   140  		{
   141  			name: "no action needed, already up-to-date",
   142  			vg: &fakeVersionGetter{
   143  				clusterVersion:   v1Y0.String(),
   144  				componentVersion: v1Y0.String(),
   145  				kubeletVersion:   v1Y0.String(),
   146  				kubeadmVersion:   v1Y0.String(),
   147  				etcdVersion:      fakeCurrentEtcdVersion,
   148  
   149  				stablePatchVersion: v1Y0.String(),
   150  				stableVersion:      v1Y0.String(),
   151  			},
   152  			beforeDNSVersion:  fakeCurrentCoreDNSVersion,
   153  			expectedUpgrades:  nil,
   154  			allowExperimental: false,
   155  			errExpected:       false,
   156  		},
   157  		{
   158  			name: "simple patch version upgrade",
   159  			vg: &fakeVersionGetter{
   160  				clusterVersion:   v1Y1.String(),
   161  				componentVersion: v1Y1.String(),
   162  				kubeletVersion:   v1Y1.String(), // the kubelet are on the same version as the control plane
   163  				kubeadmVersion:   v1Y2.String(),
   164  				etcdVersion:      fakeCurrentEtcdVersion,
   165  
   166  				stablePatchVersion: v1Y3.String(),
   167  				stableVersion:      v1Y3.String(),
   168  			},
   169  			beforeDNSVersion: fakeCurrentCoreDNSVersion,
   170  			expectedUpgrades: []Upgrade{
   171  				{
   172  					Description: fmt.Sprintf("version in the v%d.%d series", v1Y0.Major(), v1Y0.Minor()),
   173  					Before: ClusterState{
   174  						KubeVersion: v1Y1.String(),
   175  						KubeAPIServerVersions: map[string][]string{
   176  							v1Y1.String(): {"node1"},
   177  						},
   178  						KubeControllerManagerVersions: map[string][]string{
   179  							v1Y1.String(): {"node1"},
   180  						},
   181  						KubeSchedulerVersions: map[string][]string{
   182  							v1Y1.String(): {"node1"},
   183  						},
   184  						KubeletVersions: map[string][]string{
   185  							v1Y1.String(): {"node1"},
   186  						},
   187  						KubeadmVersion: v1Y2.String(),
   188  						DNSVersion:     fakeCurrentCoreDNSVersion,
   189  						EtcdVersions:   map[string][]string{fakeCurrentEtcdVersion: {"node1"}},
   190  					},
   191  					After: ClusterState{
   192  						KubeVersion:    v1Y3.String(),
   193  						KubeadmVersion: v1Y3.String(),
   194  						DNSVersion:     constants.CoreDNSVersion,
   195  						EtcdVersion:    getEtcdVersion(v1Y3),
   196  					},
   197  				},
   198  			},
   199  			allowExperimental: false,
   200  			errExpected:       false,
   201  		},
   202  		{
   203  			name: "simple patch version upgrade with external etcd",
   204  			vg: &fakeVersionGetter{
   205  				clusterVersion:   v1Y1.String(),
   206  				componentVersion: v1Y1.String(),
   207  				kubeletVersion:   v1Y1.String(), // the kubelet are on the same version as the control plane
   208  				kubeadmVersion:   v1Y2.String(),
   209  				isExternalEtcd:   true,
   210  
   211  				stablePatchVersion: v1Y3.String(),
   212  				stableVersion:      v1Y3.String(),
   213  			},
   214  			beforeDNSVersion: fakeCurrentCoreDNSVersion,
   215  			expectedUpgrades: []Upgrade{
   216  				{
   217  					Description: fmt.Sprintf("version in the v%d.%d series", v1Y0.Major(), v1Y0.Minor()),
   218  					Before: ClusterState{
   219  						KubeVersion: v1Y1.String(),
   220  						KubeAPIServerVersions: map[string][]string{
   221  							v1Y1.String(): {"node1"},
   222  						},
   223  						KubeControllerManagerVersions: map[string][]string{
   224  							v1Y1.String(): {"node1"},
   225  						},
   226  						KubeSchedulerVersions: map[string][]string{
   227  							v1Y1.String(): {"node1"},
   228  						},
   229  						KubeletVersions: map[string][]string{
   230  							v1Y1.String(): {"node1"},
   231  						},
   232  						EtcdVersions:   map[string][]string{},
   233  						KubeadmVersion: v1Y2.String(),
   234  						DNSVersion:     fakeCurrentCoreDNSVersion,
   235  					},
   236  					After: ClusterState{
   237  						KubeVersion:    v1Y3.String(),
   238  						KubeadmVersion: v1Y3.String(),
   239  						DNSVersion:     constants.CoreDNSVersion,
   240  						EtcdVersion:    "",
   241  					},
   242  				},
   243  			},
   244  			allowExperimental: false,
   245  			errExpected:       false,
   246  		},
   247  		{
   248  			name: "no version provided to offline version getter does not change behavior",
   249  			vg: NewOfflineVersionGetter(&fakeVersionGetter{
   250  				clusterVersion:   v1Y1.String(),
   251  				componentVersion: v1Y1.String(),
   252  				kubeletVersion:   v1Y1.String(), // the kubelet are on the same version as the control plane
   253  				kubeadmVersion:   v1Y2.String(),
   254  				etcdVersion:      fakeCurrentEtcdVersion,
   255  
   256  				stablePatchVersion: v1Y3.String(),
   257  				stableVersion:      v1Y3.String(),
   258  			}, ""),
   259  			beforeDNSVersion: fakeCurrentCoreDNSVersion,
   260  			expectedUpgrades: []Upgrade{
   261  				{
   262  					Description: fmt.Sprintf("version in the v%d.%d series", v1Y0.Major(), v1Y0.Minor()),
   263  					Before: ClusterState{
   264  						KubeVersion: v1Y1.String(),
   265  						KubeAPIServerVersions: map[string][]string{
   266  							v1Y1.String(): {"node1"},
   267  						},
   268  						KubeControllerManagerVersions: map[string][]string{
   269  							v1Y1.String(): {"node1"},
   270  						},
   271  						KubeSchedulerVersions: map[string][]string{
   272  							v1Y1.String(): {"node1"},
   273  						},
   274  						KubeletVersions: map[string][]string{
   275  							v1Y1.String(): {"node1"},
   276  						},
   277  						KubeadmVersion: v1Y2.String(),
   278  						DNSVersion:     fakeCurrentCoreDNSVersion,
   279  						EtcdVersions:   map[string][]string{fakeCurrentEtcdVersion: {"node1"}},
   280  					},
   281  					After: ClusterState{
   282  						KubeVersion:    v1Y3.String(),
   283  						KubeadmVersion: v1Y3.String(),
   284  						DNSVersion:     constants.CoreDNSVersion,
   285  						EtcdVersion:    getEtcdVersion(v1Y3),
   286  					},
   287  				},
   288  			},
   289  			allowExperimental: false,
   290  			errExpected:       false,
   291  		},
   292  		{
   293  			name: "minor version upgrade only",
   294  			vg: &fakeVersionGetter{
   295  				clusterVersion:   v1Y1.String(),
   296  				componentVersion: v1Y1.String(),
   297  				kubeletVersion:   v1Y1.String(), // the kubelet are on the same version as the control plane
   298  				kubeadmVersion:   v1Z0.String(),
   299  				etcdVersion:      fakeCurrentEtcdVersion,
   300  
   301  				stablePatchVersion: v1Y1.String(),
   302  				stableVersion:      v1Z0.String(),
   303  			},
   304  			beforeDNSVersion: fakeCurrentCoreDNSVersion,
   305  			expectedUpgrades: []Upgrade{
   306  				{
   307  					Description: "stable version",
   308  					Before: ClusterState{
   309  						KubeVersion: v1Y1.String(),
   310  						KubeAPIServerVersions: map[string][]string{
   311  							v1Y1.String(): {"node1"},
   312  						},
   313  						KubeControllerManagerVersions: map[string][]string{
   314  							v1Y1.String(): {"node1"},
   315  						},
   316  						KubeSchedulerVersions: map[string][]string{
   317  							v1Y1.String(): {"node1"},
   318  						},
   319  						KubeletVersions: map[string][]string{
   320  							v1Y1.String(): {"node1"},
   321  						},
   322  						KubeadmVersion: v1Z0.String(),
   323  						DNSVersion:     fakeCurrentCoreDNSVersion,
   324  						EtcdVersions:   map[string][]string{fakeCurrentEtcdVersion: {"node1"}},
   325  					},
   326  					After: ClusterState{
   327  						KubeVersion:    v1Z0.String(),
   328  						KubeadmVersion: v1Z0.String(),
   329  						DNSVersion:     constants.CoreDNSVersion,
   330  						EtcdVersion:    getEtcdVersion(v1Z0),
   331  					},
   332  				},
   333  			},
   334  			allowExperimental: false,
   335  			errExpected:       false,
   336  		},
   337  		{
   338  			name: "both minor version upgrade and patch version upgrade available",
   339  			vg: &fakeVersionGetter{
   340  				clusterVersion:   v1Y3.String(),
   341  				componentVersion: v1Y3.String(),
   342  				kubeletVersion:   v1Y3.String(), // the kubelet are on the same version as the control plane
   343  				kubeadmVersion:   v1Y5.String(),
   344  				etcdVersion:      fakeCurrentEtcdVersion,
   345  
   346  				stablePatchVersion: v1Y5.String(),
   347  				stableVersion:      v1Z1.String(),
   348  			},
   349  			beforeDNSVersion: fakeCurrentCoreDNSVersion,
   350  			expectedUpgrades: []Upgrade{
   351  				{
   352  					Description: fmt.Sprintf("version in the v%d.%d series", v1Y0.Major(), v1Y0.Minor()),
   353  					Before: ClusterState{
   354  						KubeVersion: v1Y3.String(),
   355  						KubeAPIServerVersions: map[string][]string{
   356  							v1Y3.String(): {"node1"},
   357  						},
   358  						KubeControllerManagerVersions: map[string][]string{
   359  							v1Y3.String(): {"node1"},
   360  						},
   361  						KubeSchedulerVersions: map[string][]string{
   362  							v1Y3.String(): {"node1"},
   363  						},
   364  						KubeletVersions: map[string][]string{
   365  							v1Y3.String(): {"node1"},
   366  						},
   367  						KubeadmVersion: v1Y5.String(),
   368  						DNSVersion:     fakeCurrentCoreDNSVersion,
   369  						EtcdVersions:   map[string][]string{fakeCurrentEtcdVersion: {"node1"}},
   370  					},
   371  					After: ClusterState{
   372  						KubeVersion:    v1Y5.String(),
   373  						KubeadmVersion: v1Y5.String(), // Note: The kubeadm version mustn't be "downgraded" here
   374  						DNSVersion:     constants.CoreDNSVersion,
   375  						EtcdVersion:    getEtcdVersion(v1Y5),
   376  					},
   377  				},
   378  				{
   379  					Description: "stable version",
   380  					Before: ClusterState{
   381  						KubeVersion: v1Y3.String(),
   382  						KubeAPIServerVersions: map[string][]string{
   383  							v1Y3.String(): {"node1"},
   384  						},
   385  						KubeControllerManagerVersions: map[string][]string{
   386  							v1Y3.String(): {"node1"},
   387  						},
   388  						KubeSchedulerVersions: map[string][]string{
   389  							v1Y3.String(): {"node1"},
   390  						},
   391  						KubeletVersions: map[string][]string{
   392  							v1Y3.String(): {"node1"},
   393  						},
   394  						KubeadmVersion: v1Y5.String(),
   395  						DNSVersion:     fakeCurrentCoreDNSVersion,
   396  						EtcdVersions:   map[string][]string{fakeCurrentEtcdVersion: {"node1"}},
   397  					},
   398  					After: ClusterState{
   399  						KubeVersion:    v1Z1.String(),
   400  						KubeadmVersion: v1Z1.String(),
   401  						DNSVersion:     constants.CoreDNSVersion,
   402  						EtcdVersion:    getEtcdVersion(v1Z1),
   403  					},
   404  				},
   405  			},
   406  			allowExperimental: false,
   407  			errExpected:       false,
   408  		},
   409  		{
   410  			name: "allow experimental upgrades, but no upgrade available",
   411  			vg: &fakeVersionGetter{
   412  				clusterVersion:   v1Z0alpha2.String(),
   413  				componentVersion: v1Z0alpha2.String(),
   414  				kubeletVersion:   v1Y5.String(),
   415  				kubeadmVersion:   v1Y5.String(),
   416  				etcdVersion:      fakeCurrentEtcdVersion,
   417  
   418  				stablePatchVersion: v1Y5.String(),
   419  				stableVersion:      v1Y5.String(),
   420  				latestVersion:      v1Z0alpha2.String(),
   421  			},
   422  			beforeDNSVersion:  fakeCurrentCoreDNSVersion,
   423  			expectedUpgrades:  nil,
   424  			allowExperimental: true,
   425  			errExpected:       false,
   426  		},
   427  		{
   428  			name: "upgrade to an unstable version should be supported",
   429  			vg: &fakeVersionGetter{
   430  				clusterVersion:   v1Y5.String(),
   431  				componentVersion: v1Y5.String(),
   432  				kubeletVersion:   v1Y5.String(),
   433  				kubeadmVersion:   v1Y5.String(),
   434  				etcdVersion:      fakeCurrentEtcdVersion,
   435  
   436  				stablePatchVersion: v1Y5.String(),
   437  				stableVersion:      v1Y5.String(),
   438  				latestVersion:      v1Z0alpha2.String(),
   439  			},
   440  			beforeDNSVersion: fakeCurrentCoreDNSVersion,
   441  			expectedUpgrades: []Upgrade{
   442  				{
   443  					Description: "experimental version",
   444  					Before: ClusterState{
   445  						KubeVersion: v1Y5.String(),
   446  						KubeAPIServerVersions: map[string][]string{
   447  							v1Y5.String(): {"node1"},
   448  						},
   449  						KubeControllerManagerVersions: map[string][]string{
   450  							v1Y5.String(): {"node1"},
   451  						},
   452  						KubeSchedulerVersions: map[string][]string{
   453  							v1Y5.String(): {"node1"},
   454  						},
   455  						KubeletVersions: map[string][]string{
   456  							v1Y5.String(): {"node1"},
   457  						},
   458  						KubeadmVersion: v1Y5.String(),
   459  						DNSVersion:     fakeCurrentCoreDNSVersion,
   460  						EtcdVersions:   map[string][]string{fakeCurrentEtcdVersion: {"node1"}},
   461  					},
   462  					After: ClusterState{
   463  						KubeVersion:    v1Z0alpha2.String(),
   464  						KubeadmVersion: v1Z0alpha2.String(),
   465  						DNSVersion:     constants.CoreDNSVersion,
   466  						EtcdVersion:    getEtcdVersion(v1Z0alpha2),
   467  					},
   468  				},
   469  			},
   470  			allowExperimental: true,
   471  			errExpected:       false,
   472  		},
   473  		{
   474  			name: "upgrade from an unstable version to an unstable version should be supported",
   475  			vg: &fakeVersionGetter{
   476  				clusterVersion:   v1Z0alpha1.String(),
   477  				componentVersion: v1Z0alpha1.String(),
   478  				kubeletVersion:   v1Y5.String(),
   479  				kubeadmVersion:   v1Y5.String(),
   480  				etcdVersion:      fakeCurrentEtcdVersion,
   481  
   482  				stablePatchVersion: v1Y5.String(),
   483  				stableVersion:      v1Y5.String(),
   484  				latestVersion:      v1Z0alpha2.String(),
   485  			},
   486  			beforeDNSVersion: fakeCurrentCoreDNSVersion,
   487  			expectedUpgrades: []Upgrade{
   488  				{
   489  					Description: "experimental version",
   490  					Before: ClusterState{
   491  						KubeVersion: v1Z0alpha1.String(),
   492  						KubeAPIServerVersions: map[string][]string{
   493  							v1Z0alpha1.String(): {"node1"},
   494  						},
   495  						KubeControllerManagerVersions: map[string][]string{
   496  							v1Z0alpha1.String(): {"node1"},
   497  						},
   498  						KubeSchedulerVersions: map[string][]string{
   499  							v1Z0alpha1.String(): {"node1"},
   500  						},
   501  						KubeletVersions: map[string][]string{
   502  							v1Y5.String(): {"node1"},
   503  						},
   504  						KubeadmVersion: v1Y5.String(),
   505  						DNSVersion:     fakeCurrentCoreDNSVersion,
   506  						EtcdVersions:   map[string][]string{fakeCurrentEtcdVersion: {"node1"}},
   507  					},
   508  					After: ClusterState{
   509  						KubeVersion:    v1Z0alpha2.String(),
   510  						KubeadmVersion: v1Z0alpha2.String(),
   511  						DNSVersion:     constants.CoreDNSVersion,
   512  						EtcdVersion:    getEtcdVersion(v1Z0alpha2),
   513  					},
   514  				},
   515  			},
   516  			allowExperimental: true,
   517  			errExpected:       false,
   518  		},
   519  		{
   520  			name: "v1.X.0-alpha.0 should be ignored",
   521  			vg: &fakeVersionGetter{
   522  				clusterVersion:   v1X5.String(),
   523  				componentVersion: v1X5.String(),
   524  				kubeletVersion:   v1X5.String(),
   525  				kubeadmVersion:   v1X5.String(),
   526  				etcdVersion:      fakeCurrentEtcdVersion,
   527  
   528  				stablePatchVersion:     v1X5.String(),
   529  				stableVersion:          v1X5.String(),
   530  				latestDevBranchVersion: v1Z0beta1.String(),
   531  				latestVersion:          v1Y0alpha0.String(),
   532  			},
   533  			beforeDNSVersion: fakeCurrentCoreDNSVersion,
   534  			expectedUpgrades: []Upgrade{
   535  				{
   536  					Description: "experimental version",
   537  					Before: ClusterState{
   538  						KubeVersion: v1X5.String(),
   539  						KubeAPIServerVersions: map[string][]string{
   540  							v1X5.String(): {"node1"},
   541  						},
   542  						KubeControllerManagerVersions: map[string][]string{
   543  							v1X5.String(): {"node1"},
   544  						},
   545  						KubeSchedulerVersions: map[string][]string{
   546  							v1X5.String(): {"node1"},
   547  						},
   548  						KubeletVersions: map[string][]string{
   549  							v1X5.String(): {"node1"},
   550  						},
   551  						KubeadmVersion: v1X5.String(),
   552  						DNSVersion:     fakeCurrentCoreDNSVersion,
   553  						EtcdVersions:   map[string][]string{fakeCurrentEtcdVersion: {"node1"}},
   554  					},
   555  					After: ClusterState{
   556  						KubeVersion:    v1Z0beta1.String(),
   557  						KubeadmVersion: v1Z0beta1.String(),
   558  						DNSVersion:     constants.CoreDNSVersion,
   559  						EtcdVersion:    getEtcdVersion(v1Z0beta1),
   560  					},
   561  				},
   562  			},
   563  			allowExperimental: true,
   564  			errExpected:       false,
   565  		},
   566  		{
   567  			name: "upgrade to an RC version should be supported",
   568  			vg: &fakeVersionGetter{
   569  				clusterVersion:   v1X5.String(),
   570  				componentVersion: v1X5.String(),
   571  				kubeletVersion:   v1X5.String(),
   572  				kubeadmVersion:   v1X5.String(),
   573  				etcdVersion:      fakeCurrentEtcdVersion,
   574  
   575  				stablePatchVersion:     v1X5.String(),
   576  				stableVersion:          v1X5.String(),
   577  				latestDevBranchVersion: v1Z0rc1.String(),
   578  				latestVersion:          v1Y0alpha1.String(),
   579  			},
   580  			beforeDNSVersion: fakeCurrentCoreDNSVersion,
   581  			expectedUpgrades: []Upgrade{
   582  				{
   583  					Description: "release candidate version",
   584  					Before: ClusterState{
   585  						KubeVersion: v1X5.String(),
   586  						KubeAPIServerVersions: map[string][]string{
   587  							v1X5.String(): {"node1"},
   588  						},
   589  						KubeControllerManagerVersions: map[string][]string{
   590  							v1X5.String(): {"node1"},
   591  						},
   592  						KubeSchedulerVersions: map[string][]string{
   593  							v1X5.String(): {"node1"},
   594  						},
   595  						KubeletVersions: map[string][]string{
   596  							v1X5.String(): {"node1"},
   597  						},
   598  						KubeadmVersion: v1X5.String(),
   599  						DNSVersion:     fakeCurrentCoreDNSVersion,
   600  						EtcdVersions:   map[string][]string{fakeCurrentEtcdVersion: {"node1"}},
   601  					},
   602  					After: ClusterState{
   603  						KubeVersion:    v1Z0rc1.String(),
   604  						KubeadmVersion: v1Z0rc1.String(),
   605  						DNSVersion:     constants.CoreDNSVersion,
   606  						EtcdVersion:    getEtcdVersion(v1Z0rc1),
   607  					},
   608  				},
   609  			},
   610  			allowRCs:    true,
   611  			errExpected: false,
   612  		},
   613  		{
   614  			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",
   615  			vg: &fakeVersionGetter{
   616  				clusterVersion:   v1X5.String(),
   617  				componentVersion: v1X5.String(),
   618  				kubeletVersion:   v1X5.String(),
   619  				kubeadmVersion:   v1X5.String(),
   620  				etcdVersion:      fakeCurrentEtcdVersion,
   621  
   622  				stablePatchVersion:     v1X5.String(),
   623  				stableVersion:          v1X5.String(),
   624  				latestDevBranchVersion: v1Z0rc1.String(),
   625  				latestVersion:          v1Y0alpha0.String(),
   626  			},
   627  			beforeDNSVersion: fakeCurrentCoreDNSVersion,
   628  			expectedUpgrades: []Upgrade{
   629  				{
   630  					Description: "experimental version", // Note that this is considered an experimental version in this uncommon scenario
   631  					Before: ClusterState{
   632  						KubeVersion: v1X5.String(),
   633  						KubeAPIServerVersions: map[string][]string{
   634  							v1X5.String(): {"node1"},
   635  						},
   636  						KubeControllerManagerVersions: map[string][]string{
   637  							v1X5.String(): {"node1"},
   638  						},
   639  						KubeSchedulerVersions: map[string][]string{
   640  							v1X5.String(): {"node1"},
   641  						},
   642  						KubeletVersions: map[string][]string{
   643  							v1X5.String(): {"node1"},
   644  						},
   645  						KubeadmVersion: v1X5.String(),
   646  						DNSVersion:     fakeCurrentCoreDNSVersion,
   647  						EtcdVersions:   map[string][]string{fakeCurrentEtcdVersion: {"node1"}},
   648  					},
   649  					After: ClusterState{
   650  						KubeVersion:    v1Z0rc1.String(),
   651  						KubeadmVersion: v1Z0rc1.String(),
   652  						DNSVersion:     constants.CoreDNSVersion,
   653  						EtcdVersion:    getEtcdVersion(v1Z0rc1),
   654  					},
   655  				},
   656  			},
   657  			allowExperimental: true,
   658  			errExpected:       false,
   659  		},
   660  		{
   661  			name: "upgrade to an RC version should be supported. There may also be an even newer unstable version.",
   662  			vg: &fakeVersionGetter{
   663  				clusterVersion:   v1X5.String(),
   664  				componentVersion: v1X5.String(),
   665  				kubeletVersion:   v1X5.String(),
   666  				kubeadmVersion:   v1X5.String(),
   667  				etcdVersion:      fakeCurrentEtcdVersion,
   668  
   669  				stablePatchVersion:     v1X5.String(),
   670  				stableVersion:          v1X5.String(),
   671  				latestDevBranchVersion: v1Z0rc1.String(),
   672  				latestVersion:          v1Y0alpha1.String(),
   673  			},
   674  			beforeDNSVersion: fakeCurrentCoreDNSVersion,
   675  			expectedUpgrades: []Upgrade{
   676  				{
   677  					Description: "release candidate version",
   678  					Before: ClusterState{
   679  						KubeVersion: v1X5.String(),
   680  						KubeAPIServerVersions: map[string][]string{
   681  							v1X5.String(): {"node1"},
   682  						},
   683  						KubeControllerManagerVersions: map[string][]string{
   684  							v1X5.String(): {"node1"},
   685  						},
   686  						KubeSchedulerVersions: map[string][]string{
   687  							v1X5.String(): {"node1"},
   688  						},
   689  						KubeletVersions: map[string][]string{
   690  							v1X5.String(): {"node1"},
   691  						},
   692  						KubeadmVersion: v1X5.String(),
   693  						DNSVersion:     fakeCurrentCoreDNSVersion,
   694  						EtcdVersions:   map[string][]string{fakeCurrentEtcdVersion: {"node1"}},
   695  					},
   696  					After: ClusterState{
   697  						KubeVersion:    v1Z0rc1.String(),
   698  						KubeadmVersion: v1Z0rc1.String(),
   699  						DNSVersion:     constants.CoreDNSVersion,
   700  						EtcdVersion:    getEtcdVersion(v1Z0rc1),
   701  					},
   702  				},
   703  				{
   704  					Description: "experimental version",
   705  					Before: ClusterState{
   706  						KubeVersion: v1X5.String(),
   707  						KubeAPIServerVersions: map[string][]string{
   708  							v1X5.String(): {"node1"},
   709  						},
   710  						KubeControllerManagerVersions: map[string][]string{
   711  							v1X5.String(): {"node1"},
   712  						},
   713  						KubeSchedulerVersions: map[string][]string{
   714  							v1X5.String(): {"node1"},
   715  						},
   716  						KubeletVersions: map[string][]string{
   717  							v1X5.String(): {"node1"},
   718  						},
   719  						KubeadmVersion: v1X5.String(),
   720  						DNSVersion:     fakeCurrentCoreDNSVersion,
   721  						EtcdVersions:   map[string][]string{fakeCurrentEtcdVersion: {"node1"}},
   722  					},
   723  					After: ClusterState{
   724  						KubeVersion:    v1Y0alpha1.String(),
   725  						KubeadmVersion: v1Y0alpha1.String(),
   726  						DNSVersion:     constants.CoreDNSVersion,
   727  						EtcdVersion:    getEtcdVersion(v1Y0alpha1),
   728  					},
   729  				},
   730  			},
   731  			allowRCs:          true,
   732  			allowExperimental: true,
   733  			errExpected:       false,
   734  		},
   735  		{
   736  			name: "offline version getter",
   737  			vg: NewOfflineVersionGetter(&fakeVersionGetter{
   738  				clusterVersion:   v1Y1.String(),
   739  				componentVersion: v1Y1.String(),
   740  				kubeletVersion:   v1Y0.String(),
   741  				kubeadmVersion:   v1Y1.String(),
   742  				etcdVersion:      fakeCurrentEtcdVersion,
   743  			}, v1Z1.String()),
   744  			beforeDNSVersion: fakeCurrentCoreDNSVersion,
   745  			expectedUpgrades: []Upgrade{
   746  				{
   747  					Description: fmt.Sprintf("version in the v%d.%d series", v1Y0.Major(), v1Y0.Minor()),
   748  					Before: ClusterState{
   749  						KubeVersion: v1Y1.String(),
   750  						KubeAPIServerVersions: map[string][]string{
   751  							v1Y1.String(): {"node1"},
   752  						},
   753  						KubeControllerManagerVersions: map[string][]string{
   754  							v1Y1.String(): {"node1"},
   755  						},
   756  						KubeSchedulerVersions: map[string][]string{
   757  							v1Y1.String(): {"node1"},
   758  						},
   759  						KubeletVersions: map[string][]string{
   760  							v1Y0.String(): {"node1"},
   761  						},
   762  						KubeadmVersion: v1Y1.String(),
   763  						DNSVersion:     fakeCurrentCoreDNSVersion,
   764  						EtcdVersions:   map[string][]string{fakeCurrentEtcdVersion: {"node1"}},
   765  					},
   766  					After: ClusterState{
   767  						KubeVersion:    v1Z1.String(),
   768  						KubeadmVersion: v1Z1.String(),
   769  						DNSVersion:     constants.CoreDNSVersion,
   770  						EtcdVersion:    getEtcdVersion(v1Z1),
   771  					},
   772  				},
   773  			},
   774  		},
   775  	}
   776  
   777  	// Instantiating a fake etcd cluster for being able to get etcd version for a corresponding
   778  	// Kubernetes release.
   779  	for _, rt := range tests {
   780  		t.Run(rt.name, func(t *testing.T) {
   781  
   782  			dnsName := constants.CoreDNSDeploymentName
   783  
   784  			client := clientsetfake.NewSimpleClientset(&apps.Deployment{
   785  				TypeMeta: metav1.TypeMeta{
   786  					Kind:       "Deployment",
   787  					APIVersion: "apps/v1",
   788  				},
   789  				ObjectMeta: metav1.ObjectMeta{
   790  					Name:      dnsName,
   791  					Namespace: "kube-system",
   792  					Labels: map[string]string{
   793  						"k8s-app": "kube-dns",
   794  					},
   795  				},
   796  				Spec: apps.DeploymentSpec{
   797  					Template: v1.PodTemplateSpec{
   798  						Spec: v1.PodSpec{
   799  							Containers: []v1.Container{
   800  								{
   801  									Image: "test:" + rt.beforeDNSVersion,
   802  								},
   803  							},
   804  						},
   805  					},
   806  				},
   807  			})
   808  
   809  			actualUpgrades, actualErr := GetAvailableUpgrades(rt.vg, rt.allowExperimental, rt.allowRCs, client, &output.TextPrinter{})
   810  			if diff := cmp.Diff(rt.expectedUpgrades, actualUpgrades); len(diff) > 0 {
   811  				t.Errorf("failed TestGetAvailableUpgrades\n\texpected upgrades:\n%v\n\tgot:\n%v\n\tdiff:\n%v", rt.expectedUpgrades, actualUpgrades, diff)
   812  			}
   813  			if rt.errExpected && actualErr == nil {
   814  				t.Error("unexpected success")
   815  			} else if !rt.errExpected && actualErr != nil {
   816  				t.Errorf("unexpected failure: %v", actualErr)
   817  			}
   818  			if diff := cmp.Diff(rt.expectedUpgrades, actualUpgrades); len(diff) > 0 {
   819  				t.Logf("diff: %s", cmp.Diff(rt.expectedUpgrades, actualUpgrades))
   820  				t.Errorf("failed TestGetAvailableUpgrades\n\texpected upgrades:\n%v\n\tgot:\n%v\n\tdiff:\n%v", rt.expectedUpgrades, actualUpgrades, diff)
   821  			}
   822  		})
   823  	}
   824  }
   825  
   826  func TestKubeletUpgrade(t *testing.T) {
   827  	tests := []struct {
   828  		name     string
   829  		before   map[string][]string
   830  		after    string
   831  		expected bool
   832  	}{
   833  		{
   834  			name: "upgrade from v1.10.1 to v1.10.3 is available",
   835  			before: map[string][]string{
   836  				"v1.10.1": {"node1"},
   837  			},
   838  			after:    "v1.10.3",
   839  			expected: true,
   840  		},
   841  		{
   842  			name: "upgrade from v1.10.1 and v1.10.3/2 to v1.10.3 is available",
   843  			before: map[string][]string{
   844  				"v1.10.1": {"node1"},
   845  				"v1.10.3": {"node2", "node3"},
   846  			},
   847  			after:    "v1.10.3",
   848  			expected: true,
   849  		},
   850  		{
   851  			name: "upgrade from v1.10.3 to v1.10.3 is not available",
   852  			before: map[string][]string{
   853  				"v1.10.3": {"node1"},
   854  			},
   855  			after:    "v1.10.3",
   856  			expected: false,
   857  		},
   858  		{
   859  			name: "upgrade from v1.10.3/2 to v1.10.3 is not available",
   860  			before: map[string][]string{
   861  				"v1.10.3": {"node1", "node2"},
   862  			},
   863  			after:    "v1.10.3",
   864  			expected: false,
   865  		},
   866  		{
   867  			name:     "upgrade is not available if we don't know anything about the earlier state",
   868  			before:   map[string][]string{},
   869  			after:    "v1.10.3",
   870  			expected: false,
   871  		},
   872  	}
   873  
   874  	for _, rt := range tests {
   875  		t.Run(rt.name, func(t *testing.T) {
   876  			upgrade := Upgrade{
   877  				Before: ClusterState{
   878  					KubeletVersions: rt.before,
   879  				},
   880  				After: ClusterState{
   881  					KubeVersion: rt.after,
   882  				},
   883  			}
   884  			actual := upgrade.CanUpgradeKubelets()
   885  			if actual != rt.expected {
   886  				t.Errorf("failed TestKubeletUpgrade\n\texpected: %t\n\tgot: %t\n\ttest object: %v", rt.expected, actual, upgrade)
   887  			}
   888  		})
   889  	}
   890  }
   891  
   892  func TestGetBranchFromVersion(t *testing.T) {
   893  	testCases := []struct {
   894  		version         string
   895  		expectedVersion string
   896  	}{
   897  		{
   898  			version:         "v1.9.5",
   899  			expectedVersion: "1.9",
   900  		},
   901  		{
   902  			version:         "v1.9.0-alpha.2",
   903  			expectedVersion: "1.9",
   904  		},
   905  		{
   906  			version:         "v1.9.0-beta.0",
   907  			expectedVersion: "1.9",
   908  		},
   909  		{
   910  			version:         "v1.9.0-rc.1",
   911  			expectedVersion: "1.9",
   912  		},
   913  		{
   914  			version:         "v1.11.0-alpha.0",
   915  			expectedVersion: "1.11",
   916  		},
   917  
   918  		{
   919  			version:         "v1.11.0-beta.1",
   920  			expectedVersion: "1.11",
   921  		},
   922  		{
   923  			version:         "v1.11.0-rc.0",
   924  			expectedVersion: "1.11",
   925  		},
   926  		{
   927  			version:         "1.12.5",
   928  			expectedVersion: "1.12",
   929  		},
   930  	}
   931  
   932  	for _, tc := range testCases {
   933  		t.Run(tc.version, func(t *testing.T) {
   934  			v := getBranchFromVersion(tc.version)
   935  			if v != tc.expectedVersion {
   936  				t.Errorf("expected version %s, got %s", tc.expectedVersion, v)
   937  			}
   938  		})
   939  	}
   940  }
   941  
   942  func TestGetSuggestedEtcdVersion(t *testing.T) {
   943  	constants.SupportedEtcdVersion = map[uint8]string{
   944  		16: "3.3.17-0",
   945  		17: "3.4.3-0",
   946  		18: "3.4.3-0",
   947  		19: "3.4.13-0",
   948  		20: "3.4.13-0",
   949  		21: "3.4.13-0",
   950  		22: "3.5.5-0",
   951  	}
   952  
   953  	tests := []struct {
   954  		name              string
   955  		externalEtcd      bool
   956  		kubernetesVersion string
   957  		expectedVersion   string
   958  	}{
   959  		{
   960  			name:              "external etcd: no version",
   961  			externalEtcd:      true,
   962  			kubernetesVersion: "1.1.0",
   963  			expectedVersion:   "",
   964  		},
   965  		{
   966  			name:              "local etcd: illegal kubernetes version",
   967  			externalEtcd:      false,
   968  			kubernetesVersion: "1.x.5",
   969  			expectedVersion:   "N/A",
   970  		},
   971  		{
   972  			name:              "local etcd: no supported version for 1.10.5, return the nearest version",
   973  			externalEtcd:      false,
   974  			kubernetesVersion: "1.10.5",
   975  			expectedVersion:   "3.3.17-0",
   976  		},
   977  		{
   978  			name:              "local etcd: has supported version for 1.17.0",
   979  			externalEtcd:      false,
   980  			kubernetesVersion: "1.17.0",
   981  			expectedVersion:   "3.4.3-0",
   982  		},
   983  		{
   984  			name:              "local etcd: no supported version for v1.99.0, return the nearest version",
   985  			externalEtcd:      false,
   986  			kubernetesVersion: "v1.99.0",
   987  			expectedVersion:   "3.5.5-0",
   988  		},
   989  	}
   990  	for _, tt := range tests {
   991  		t.Run(tt.name, func(t *testing.T) {
   992  			if got := getSuggestedEtcdVersion(tt.externalEtcd, tt.kubernetesVersion); got != tt.expectedVersion {
   993  				t.Errorf("getSuggestedEtcdVersion() want %v, got %v", tt.expectedVersion, got)
   994  			}
   995  		})
   996  	}
   997  }
   998  

View as plain text