k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/kubeadm/app/phases/upgrade/compute_test.go (about) 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 }