sigs.k8s.io/cluster-api@v1.7.1/controlplane/kubeadm/internal/workload_cluster_coredns_test.go (about) 1 /* 2 Copyright 2020 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 internal 18 19 import ( 20 "testing" 21 22 "github.com/blang/semver/v4" 23 "github.com/google/go-cmp/cmp" 24 . "github.com/onsi/gomega" 25 "github.com/pkg/errors" 26 appsv1 "k8s.io/api/apps/v1" 27 corev1 "k8s.io/api/core/v1" 28 rbacv1 "k8s.io/api/rbac/v1" 29 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 30 "sigs.k8s.io/controller-runtime/pkg/client" 31 "sigs.k8s.io/controller-runtime/pkg/client/fake" 32 33 bootstrapv1 "sigs.k8s.io/cluster-api/bootstrap/kubeadm/api/v1beta1" 34 controlplanev1 "sigs.k8s.io/cluster-api/controlplane/kubeadm/api/v1beta1" 35 utilyaml "sigs.k8s.io/cluster-api/util/yaml" 36 ) 37 38 func TestUpdateCoreDNS(t *testing.T) { 39 validKCP := &controlplanev1.KubeadmControlPlane{ 40 Spec: controlplanev1.KubeadmControlPlaneSpec{ 41 KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ 42 ClusterConfiguration: &bootstrapv1.ClusterConfiguration{ 43 DNS: bootstrapv1.DNS{ 44 ImageMeta: bootstrapv1.ImageMeta{ 45 ImageRepository: "", 46 ImageTag: "", 47 }, 48 }, 49 ImageRepository: "", 50 }, 51 }, 52 }, 53 } 54 // This is used to force an error to be returned so we can assert the 55 // following pre-checks that need to happen before we retrieve the 56 // CoreDNSInfo. 57 badCM := &corev1.ConfigMap{ 58 ObjectMeta: metav1.ObjectMeta{ 59 Name: coreDNSKey, 60 Namespace: metav1.NamespaceSystem, 61 }, 62 Data: map[string]string{ 63 "BadCoreFileKey": "", 64 }, 65 } 66 67 depl := &appsv1.Deployment{ 68 TypeMeta: metav1.TypeMeta{ 69 Kind: "Deployment", 70 APIVersion: "apps/v1", 71 }, 72 ObjectMeta: metav1.ObjectMeta{ 73 Name: coreDNSKey, 74 Namespace: metav1.NamespaceSystem, 75 }, 76 Spec: appsv1.DeploymentSpec{ 77 Template: corev1.PodTemplateSpec{ 78 ObjectMeta: metav1.ObjectMeta{ 79 Name: coreDNSKey, 80 Labels: map[string]string{"app": coreDNSKey}, 81 }, 82 Spec: corev1.PodSpec{ 83 Containers: []corev1.Container{{ 84 Name: coreDNSKey, 85 Image: "k8s.gcr.io/some-folder/coredns:1.6.2", 86 }}, 87 }, 88 }, 89 Selector: &metav1.LabelSelector{ 90 MatchLabels: map[string]string{"app": coreDNSKey}, 91 }, 92 }, 93 } 94 95 deplWithImage := func(image string) *appsv1.Deployment { 96 d := depl.DeepCopy() 97 d.Spec.Template.Spec.Containers[0].Image = image 98 return d 99 } 100 101 expectedCorefile := "coredns-core-file" 102 cm := &corev1.ConfigMap{ 103 ObjectMeta: metav1.ObjectMeta{ 104 Name: coreDNSKey, 105 Namespace: metav1.NamespaceSystem, 106 }, 107 Data: map[string]string{ 108 "Corefile": expectedCorefile, 109 }, 110 } 111 updatedCM := &corev1.ConfigMap{ 112 ObjectMeta: metav1.ObjectMeta{ 113 Name: coreDNSKey, 114 Namespace: metav1.NamespaceSystem, 115 }, 116 Data: map[string]string{ 117 "Corefile": "updated-core-file", 118 "Corefile-backup": expectedCorefile, 119 }, 120 } 121 kubeadmCM := &corev1.ConfigMap{ 122 ObjectMeta: metav1.ObjectMeta{ 123 Name: kubeadmConfigKey, 124 Namespace: metav1.NamespaceSystem, 125 }, 126 Data: map[string]string{ 127 "ClusterConfiguration": utilyaml.Raw(` 128 apiServer: 129 apiVersion: kubeadm.k8s.io/v1beta2 130 dns: 131 type: CoreDNS 132 imageRepository: k8s.gcr.io 133 kind: ClusterConfiguration 134 `), 135 }, 136 } 137 kubeadmCM181 := &corev1.ConfigMap{ 138 ObjectMeta: metav1.ObjectMeta{ 139 Name: kubeadmConfigKey, 140 Namespace: metav1.NamespaceSystem, 141 }, 142 Data: map[string]string{ 143 "ClusterConfiguration": utilyaml.Raw(` 144 apiServer: 145 apiVersion: kubeadm.k8s.io/v1beta2 146 dns: 147 type: CoreDNS 148 imageTag: v1.8.1 149 imageRepository: k8s.gcr.io 150 kind: ClusterConfiguration 151 `), 152 }, 153 } 154 155 oldCR := &rbacv1.ClusterRole{ 156 TypeMeta: metav1.TypeMeta{ 157 Kind: "ClusterRole", 158 APIVersion: "rbac.authorization.k8s.io/v1", 159 }, 160 ObjectMeta: metav1.ObjectMeta{ 161 Name: coreDNSClusterRoleName, 162 }, 163 } 164 165 semver1191 := semver.MustParse("1.19.1") 166 semver1221 := semver.MustParse("1.22.1") 167 semver1230 := semver.MustParse("1.23.0") 168 169 tests := []struct { 170 name string 171 kcp *controlplanev1.KubeadmControlPlane 172 migrator coreDNSMigrator 173 semver semver.Version 174 objs []client.Object 175 expectErr bool 176 expectUpdates bool 177 expectImage string 178 expectRules []rbacv1.PolicyRule 179 }{ 180 { 181 name: "returns early without error if skip core dns annotation is present", 182 kcp: &controlplanev1.KubeadmControlPlane{ 183 ObjectMeta: metav1.ObjectMeta{ 184 Annotations: map[string]string{ 185 controlplanev1.SkipCoreDNSAnnotation: "", 186 }, 187 }, 188 Spec: controlplanev1.KubeadmControlPlaneSpec{ 189 KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ 190 ClusterConfiguration: &bootstrapv1.ClusterConfiguration{ 191 DNS: bootstrapv1.DNS{}, 192 }, 193 }, 194 }, 195 }, 196 semver: semver1191, 197 objs: []client.Object{badCM}, 198 expectErr: false, 199 }, 200 { 201 name: "returns early without error if KCP ClusterConfiguration is nil", 202 kcp: &controlplanev1.KubeadmControlPlane{ 203 Spec: controlplanev1.KubeadmControlPlaneSpec{ 204 KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{}, 205 }, 206 }, 207 semver: semver1191, 208 objs: []client.Object{badCM}, 209 expectErr: false, 210 }, 211 { 212 name: "returns early without error if CoreDNS info is not found", 213 kcp: validKCP, 214 semver: semver1191, 215 expectErr: false, 216 }, 217 { 218 name: "returns error if there was a problem retrieving CoreDNS info", 219 kcp: validKCP, 220 semver: semver1191, 221 objs: []client.Object{badCM}, 222 expectErr: true, 223 }, 224 { 225 name: "returns early without error if CoreDNS fromImage == ToImage", 226 kcp: validKCP, 227 semver: semver1191, 228 objs: []client.Object{depl, cm}, 229 expectErr: false, 230 }, 231 { 232 name: "returns error if validation of CoreDNS image tag fails", 233 kcp: &controlplanev1.KubeadmControlPlane{ 234 Spec: controlplanev1.KubeadmControlPlaneSpec{ 235 KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ 236 ClusterConfiguration: &bootstrapv1.ClusterConfiguration{ 237 DNS: bootstrapv1.DNS{ 238 ImageMeta: bootstrapv1.ImageMeta{ 239 // image is older than what's already 240 // installed. 241 ImageRepository: "k8s.gcr.io/some-folder/coredns", 242 ImageTag: "1.1.2", 243 }, 244 }, 245 }, 246 }, 247 }, 248 }, 249 semver: semver1191, 250 objs: []client.Object{depl, cm}, 251 expectErr: true, 252 }, 253 { 254 name: "returns error if unable to update CoreDNS image info in kubeadm config map", 255 kcp: &controlplanev1.KubeadmControlPlane{ 256 Spec: controlplanev1.KubeadmControlPlaneSpec{ 257 KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ 258 ClusterConfiguration: &bootstrapv1.ClusterConfiguration{ 259 DNS: bootstrapv1.DNS{ 260 ImageMeta: bootstrapv1.ImageMeta{ 261 // provide an newer image to update to 262 ImageRepository: "k8s.gcr.io/some-folder/coredns", 263 ImageTag: "1.7.2", 264 }, 265 }, 266 }, 267 }, 268 }, 269 }, 270 semver: semver1191, 271 // no kubeadmConfigMap available so it will trigger an error 272 objs: []client.Object{depl, cm}, 273 expectErr: true, 274 }, 275 { 276 name: "returns error if unable to update CoreDNS corefile", 277 kcp: &controlplanev1.KubeadmControlPlane{ 278 Spec: controlplanev1.KubeadmControlPlaneSpec{ 279 KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ 280 ClusterConfiguration: &bootstrapv1.ClusterConfiguration{ 281 DNS: bootstrapv1.DNS{ 282 ImageMeta: bootstrapv1.ImageMeta{ 283 // provide an newer image to update to 284 ImageRepository: "k8s.gcr.io/some-folder/coredns", 285 ImageTag: "1.7.2", 286 }, 287 }, 288 }, 289 }, 290 }, 291 }, 292 migrator: &fakeMigrator{ 293 migrateErr: errors.New("failed to migrate"), 294 }, 295 semver: semver1191, 296 objs: []client.Object{depl, cm, kubeadmCM}, 297 expectErr: true, 298 }, 299 { 300 name: "updates everything successfully", 301 kcp: &controlplanev1.KubeadmControlPlane{ 302 Spec: controlplanev1.KubeadmControlPlaneSpec{ 303 KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ 304 ClusterConfiguration: &bootstrapv1.ClusterConfiguration{ 305 DNS: bootstrapv1.DNS{ 306 ImageMeta: bootstrapv1.ImageMeta{ 307 // provide an newer image to update to 308 ImageRepository: "k8s.gcr.io/some-repo", 309 ImageTag: "1.7.2", 310 }, 311 }, 312 }, 313 }, 314 }, 315 }, 316 migrator: &fakeMigrator{ 317 migratedCorefile: "updated-core-file", 318 }, 319 semver: semver1191, 320 objs: []client.Object{depl, cm, kubeadmCM}, 321 expectErr: false, 322 expectUpdates: true, 323 expectImage: "k8s.gcr.io/some-repo/coredns:1.7.2", 324 }, 325 { 326 name: "updates everything successfully to v1.8.0 with a custom repo should not change the image name", 327 kcp: &controlplanev1.KubeadmControlPlane{ 328 Spec: controlplanev1.KubeadmControlPlaneSpec{ 329 KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ 330 ClusterConfiguration: &bootstrapv1.ClusterConfiguration{ 331 DNS: bootstrapv1.DNS{ 332 ImageMeta: bootstrapv1.ImageMeta{ 333 // provide an newer image to update to 334 ImageRepository: "k8s.gcr.io/some-repo", 335 ImageTag: "1.8.0", 336 }, 337 }, 338 }, 339 }, 340 }, 341 }, 342 migrator: &fakeMigrator{ 343 migratedCorefile: "updated-core-file", 344 }, 345 semver: semver1191, 346 objs: []client.Object{deplWithImage("k8s.gcr.io/some-repo/coredns:1.7.0"), cm, kubeadmCM}, 347 expectErr: false, 348 expectUpdates: true, 349 expectImage: "k8s.gcr.io/some-repo/coredns:1.8.0", 350 }, 351 { 352 name: "kubeadm defaults, upgrade from Kubernetes v1.18.x to v1.19.y (from k8s.gcr.io/coredns:1.6.7 to k8s.gcr.io/coredns:1.7.0)", 353 kcp: &controlplanev1.KubeadmControlPlane{ 354 Spec: controlplanev1.KubeadmControlPlaneSpec{ 355 KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ 356 ClusterConfiguration: &bootstrapv1.ClusterConfiguration{ 357 DNS: bootstrapv1.DNS{ 358 ImageMeta: bootstrapv1.ImageMeta{ 359 ImageRepository: "k8s.gcr.io", 360 ImageTag: "1.7.0", 361 }, 362 }, 363 }, 364 }, 365 }, 366 }, 367 migrator: &fakeMigrator{ 368 migratedCorefile: "updated-core-file", 369 }, 370 semver: semver1191, 371 objs: []client.Object{deplWithImage("k8s.gcr.io/coredns:1.6.7"), cm, kubeadmCM}, 372 expectErr: false, 373 expectUpdates: true, 374 expectImage: "k8s.gcr.io/coredns:1.7.0", 375 }, 376 { 377 name: "kubeadm defaults, upgrade from Kubernetes v1.19.x to v1.20.y (stay on k8s.gcr.io/coredns:1.7.0)", 378 kcp: &controlplanev1.KubeadmControlPlane{ 379 Spec: controlplanev1.KubeadmControlPlaneSpec{ 380 KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ 381 ClusterConfiguration: &bootstrapv1.ClusterConfiguration{ 382 DNS: bootstrapv1.DNS{ 383 ImageMeta: bootstrapv1.ImageMeta{ 384 ImageRepository: "k8s.gcr.io", 385 ImageTag: "1.7.0", 386 }, 387 }, 388 }, 389 }, 390 }, 391 }, 392 migrator: &fakeMigrator{ 393 migratedCorefile: "updated-core-file", 394 }, 395 semver: semver1191, 396 objs: []client.Object{deplWithImage("k8s.gcr.io/coredns:1.7.0"), cm, kubeadmCM}, 397 expectErr: false, 398 expectUpdates: false, 399 }, 400 { 401 name: "kubeadm defaults, upgrade from Kubernetes v1.20.x to v1.21.y (from k8s.gcr.io/coredns:1.7.0 to k8s.gcr.io/coredns/coredns:v1.8.0)", 402 kcp: &controlplanev1.KubeadmControlPlane{ 403 Spec: controlplanev1.KubeadmControlPlaneSpec{ 404 KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ 405 ClusterConfiguration: &bootstrapv1.ClusterConfiguration{ 406 DNS: bootstrapv1.DNS{ 407 ImageMeta: bootstrapv1.ImageMeta{ 408 ImageRepository: "k8s.gcr.io", 409 ImageTag: "v1.8.0", // NOTE: ImageTags requires the v prefix 410 }, 411 }, 412 }, 413 }, 414 }, 415 }, 416 migrator: &fakeMigrator{ 417 migratedCorefile: "updated-core-file", 418 }, 419 semver: semver1191, 420 objs: []client.Object{deplWithImage("k8s.gcr.io/coredns:1.7.0"), cm, kubeadmCM}, 421 expectErr: false, 422 expectUpdates: true, 423 expectImage: "k8s.gcr.io/coredns/coredns:v1.8.0", // NOTE: ImageName has coredns/coredns 424 }, 425 { 426 name: "kubeadm defaults, upgrade from Kubernetes v1.21.x to v1.22.y (stay on k8s.gcr.io/coredns/coredns:v1.8.0)", 427 kcp: &controlplanev1.KubeadmControlPlane{ 428 Spec: controlplanev1.KubeadmControlPlaneSpec{ 429 KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ 430 ClusterConfiguration: &bootstrapv1.ClusterConfiguration{ 431 DNS: bootstrapv1.DNS{ 432 ImageMeta: bootstrapv1.ImageMeta{ 433 ImageRepository: "k8s.gcr.io", 434 ImageTag: "v1.8.0", // NOTE: ImageTags requires the v prefix 435 }, 436 }, 437 }, 438 }, 439 }, 440 }, 441 semver: semver1191, 442 migrator: &fakeMigrator{ 443 migratedCorefile: "updated-core-file", 444 }, 445 objs: []client.Object{deplWithImage("k8s.gcr.io/coredns/coredns:v1.8.0"), cm, kubeadmCM}, 446 expectErr: false, 447 expectUpdates: false, 448 expectRules: oldCR.Rules, 449 }, 450 { 451 name: "upgrade from Kubernetes v1.21.x to v1.22.y and update cluster role", 452 kcp: &controlplanev1.KubeadmControlPlane{ 453 Spec: controlplanev1.KubeadmControlPlaneSpec{ 454 KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ 455 ClusterConfiguration: &bootstrapv1.ClusterConfiguration{ 456 DNS: bootstrapv1.DNS{ 457 ImageMeta: bootstrapv1.ImageMeta{ 458 ImageRepository: "k8s.gcr.io", 459 ImageTag: "v1.8.1", // NOTE: ImageTags requires the v prefix 460 }, 461 }, 462 }, 463 }, 464 }, 465 }, 466 migrator: &fakeMigrator{ 467 migratedCorefile: "updated-core-file", 468 }, 469 semver: semver1221, 470 objs: []client.Object{deplWithImage("k8s.gcr.io/coredns/coredns:v1.8.1"), updatedCM, kubeadmCM181, oldCR}, 471 expectErr: false, 472 expectUpdates: true, 473 expectImage: "k8s.gcr.io/coredns/coredns:v1.8.1", // NOTE: ImageName has coredns/coredns 474 expectRules: coreDNS181PolicyRules, 475 }, 476 { 477 name: "returns early without error if kubernetes version is >= v1.23", 478 kcp: &controlplanev1.KubeadmControlPlane{ 479 Spec: controlplanev1.KubeadmControlPlaneSpec{ 480 KubeadmConfigSpec: bootstrapv1.KubeadmConfigSpec{ 481 ClusterConfiguration: &bootstrapv1.ClusterConfiguration{ 482 DNS: bootstrapv1.DNS{ 483 ImageMeta: bootstrapv1.ImageMeta{ 484 ImageRepository: "k8s.gcr.io", 485 ImageTag: "v1.8.1", // NOTE: ImageTags requires the v prefix 486 }, 487 }, 488 }, 489 }, 490 }, 491 }, 492 semver: semver1230, 493 objs: []client.Object{deplWithImage("k8s.gcr.io/coredns/coredns:v1.8.1"), updatedCM, kubeadmCM181}, 494 expectErr: false, 495 expectRules: oldCR.Rules, 496 }, 497 } 498 499 // We are using testEnv as a workload cluster, and given that each test case assumes well known objects with specific 500 // Namespace/Name (e.g. The CoderDNS ConfigMap & Deployment, the kubeadm ConfigMap), it is not possible to run the use cases in parallel. 501 for _, tt := range tests { 502 t.Run(tt.name, func(t *testing.T) { 503 g := NewWithT(t) 504 505 for _, o := range tt.objs { 506 // NB. deep copy test object so changes applied during a test does not affect other tests. 507 o := o.DeepCopyObject().(client.Object) 508 g.Expect(env.CreateAndWait(ctx, o)).To(Succeed()) 509 } 510 511 // Register cleanup function 512 t.Cleanup(func() { 513 _ = env.CleanupAndWait(ctx, tt.objs...) 514 }) 515 516 w := &Workload{ 517 Client: env.GetClient(), 518 CoreDNSMigrator: tt.migrator, 519 } 520 err := w.UpdateCoreDNS(ctx, tt.kcp, tt.semver) 521 522 if tt.expectErr { 523 g.Expect(err).To(HaveOccurred()) 524 return 525 } 526 g.Expect(err).ToNot(HaveOccurred()) 527 528 // Assert that CoreDNS updates have been made 529 if tt.expectUpdates { 530 // assert kubeadmConfigMap 531 g.Eventually(func(g Gomega) error { 532 var expectedKubeadmConfigMap corev1.ConfigMap 533 g.Expect(env.Get(ctx, client.ObjectKey{Name: kubeadmConfigKey, Namespace: metav1.NamespaceSystem}, &expectedKubeadmConfigMap)).To(Succeed()) 534 g.Expect(expectedKubeadmConfigMap.Data).To(HaveKeyWithValue("ClusterConfiguration", ContainSubstring(tt.kcp.Spec.KubeadmConfigSpec.ClusterConfiguration.DNS.ImageTag))) 535 g.Expect(expectedKubeadmConfigMap.Data).To(HaveKeyWithValue("ClusterConfiguration", ContainSubstring(tt.kcp.Spec.KubeadmConfigSpec.ClusterConfiguration.DNS.ImageRepository))) 536 return nil 537 }, "5s").Should(Succeed()) 538 539 // assert CoreDNS corefile 540 var expectedConfigMap corev1.ConfigMap 541 g.Eventually(func() error { 542 if err := env.Get(ctx, client.ObjectKey{Name: coreDNSKey, Namespace: metav1.NamespaceSystem}, &expectedConfigMap); err != nil { 543 return errors.Wrap(err, "failed to get the coredns ConfigMap") 544 } 545 if len(expectedConfigMap.Data) != 2 { 546 return errors.Errorf("the coredns ConfigMap has %d data items, expected 2", len(expectedConfigMap.Data)) 547 } 548 if val, ok := expectedConfigMap.Data["Corefile"]; !ok || val != "updated-core-file" { 549 return errors.New("the coredns ConfigMap does not have the Corefile entry or this it has an unexpected value") 550 } 551 if val, ok := expectedConfigMap.Data["Corefile-backup"]; !ok || val != expectedCorefile { 552 return errors.New("the coredns ConfigMap does not have the Corefile-backup entry or this it has an unexpected value") 553 } 554 return nil 555 }, "5s").Should(BeNil()) 556 557 // assert CoreDNS deployment 558 var actualDeployment appsv1.Deployment 559 g.Eventually(func() string { 560 g.Expect(env.Get(ctx, client.ObjectKey{Name: coreDNSKey, Namespace: metav1.NamespaceSystem}, &actualDeployment)).To(Succeed()) 561 return actualDeployment.Spec.Template.Spec.Containers[0].Image 562 }, "5s").Should(Equal(tt.expectImage)) 563 564 // assert CoreDNS ClusterRole 565 if tt.expectRules != nil { 566 var actualClusterRole rbacv1.ClusterRole 567 g.Eventually(func() []rbacv1.PolicyRule { 568 g.Expect(env.Get(ctx, client.ObjectKey{Name: coreDNSClusterRoleName}, &actualClusterRole)).To(Succeed()) 569 return actualClusterRole.Rules 570 }, "5s").Should(BeComparableTo(tt.expectRules)) 571 } 572 } 573 }) 574 } 575 } 576 577 func TestValidateCoreDNSImageTag(t *testing.T) { 578 tests := []struct { 579 name string 580 fromVer string 581 toVer string 582 expectErrSubStr string 583 }{ 584 { 585 name: "fromVer is higher than toVer", 586 fromVer: "1.6.2", 587 toVer: "1.1.3", 588 expectErrSubStr: "must be greater than", 589 }, 590 { 591 name: "fromVer is not a valid coredns version", 592 fromVer: "0.204.123", 593 toVer: "1.6.3", 594 expectErrSubStr: "not a compatible coredns version", 595 }, 596 { 597 name: "toVer is not a valid semver", 598 fromVer: "1.5.1", 599 toVer: "foobar", 600 expectErrSubStr: "failed to parse CoreDNS target version", 601 }, 602 { 603 name: "fromVer is not a valid semver", 604 fromVer: "foobar", 605 toVer: "1.6.1", 606 expectErrSubStr: "failed to parse CoreDNS current version", 607 }, 608 { 609 name: "fromVer is equal to toVer, but different patch versions", 610 fromVer: "1.6.5_foobar.1", 611 toVer: "1.6.5_foobar.2", 612 }, 613 { 614 name: "fromVer is equal to toVer", 615 fromVer: "1.6.5_foobar.1", 616 toVer: "1.6.5_foobar.1", 617 }, 618 { 619 name: "fromVer is lower but has meta", 620 fromVer: "1.6.5-foobar.1", 621 toVer: "1.7.5", 622 }, 623 { 624 name: "fromVer is lower and has meta and leading v", 625 fromVer: "v1.6.5-foobar.1", 626 toVer: "1.7.5", 627 }, 628 { 629 name: "fromVer is lower, toVer has meta and leading v", 630 fromVer: "1.6.5-foobar.1", 631 toVer: "v1.7.5_foobar.1", 632 }, 633 } 634 635 for _, tt := range tests { 636 t.Run(tt.name, func(t *testing.T) { 637 g := NewWithT(t) 638 err := validateCoreDNSImageTag(tt.fromVer, tt.toVer) 639 if tt.expectErrSubStr != "" { 640 g.Expect(err.Error()).To(ContainSubstring(tt.expectErrSubStr)) 641 } else { 642 g.Expect(err).ToNot(HaveOccurred()) 643 } 644 }) 645 } 646 } 647 648 func TestUpdateCoreDNSClusterRole(t *testing.T) { 649 coreDNS180PolicyRules := []rbacv1.PolicyRule{ 650 { 651 Verbs: []string{"list", "watch"}, 652 APIGroups: []string{""}, 653 Resources: []string{"endpoints", "services", "pods", "namespaces"}, 654 }, 655 { 656 Verbs: []string{"get"}, 657 APIGroups: []string{""}, 658 Resources: []string{"nodes"}, 659 }, 660 } 661 662 tests := []struct { 663 name string 664 kubernetesVersion semver.Version 665 coreDNSVersion string 666 sourceCoreDNSVersion string 667 coreDNSPolicyRules []rbacv1.PolicyRule 668 expectErr bool 669 expectCoreDNSPolicyRules []rbacv1.PolicyRule 670 }{ 671 { 672 name: "does not patch ClusterRole: invalid CoreDNS tag", 673 kubernetesVersion: semver.Version{Major: 1, Minor: 22, Patch: 0}, 674 coreDNSVersion: "no-semver", 675 coreDNSPolicyRules: coreDNS180PolicyRules, 676 expectErr: true, 677 }, 678 { 679 name: "does not patch ClusterRole: Kubernetes < 1.22", 680 kubernetesVersion: semver.Version{Major: 1, Minor: 21, Patch: 0}, 681 coreDNSVersion: "1.8.4", 682 coreDNSPolicyRules: coreDNS180PolicyRules, 683 expectCoreDNSPolicyRules: coreDNS180PolicyRules, 684 }, 685 { 686 name: "does not patch ClusterRole: Kubernetes > 1.22 && CoreDNS >= 1.8.1", 687 kubernetesVersion: semver.Version{Major: 1, Minor: 23, Patch: 0}, 688 coreDNSVersion: "1.8.1", 689 sourceCoreDNSVersion: "1.8.1", 690 coreDNSPolicyRules: coreDNS180PolicyRules, 691 expectCoreDNSPolicyRules: coreDNS180PolicyRules, 692 }, 693 { 694 name: "does not patch ClusterRole: CoreDNS < 1.8.1", 695 kubernetesVersion: semver.Version{Major: 1, Minor: 22, Patch: 0}, 696 coreDNSVersion: "1.8.0", 697 sourceCoreDNSVersion: "1.7.0", 698 coreDNSPolicyRules: coreDNS180PolicyRules, 699 expectCoreDNSPolicyRules: coreDNS180PolicyRules, 700 }, 701 { 702 name: "patch ClusterRole: Kubernetes == 1.22 alpha and CoreDNS == 1.8.1", 703 kubernetesVersion: semver.Version{Major: 1, Minor: 22, Patch: 0, Pre: []semver.PRVersion{{VersionStr: "alpha"}}}, 704 coreDNSVersion: "1.8.1", 705 sourceCoreDNSVersion: "1.8.1", 706 coreDNSPolicyRules: coreDNS180PolicyRules, 707 expectCoreDNSPolicyRules: coreDNS181PolicyRules, 708 }, 709 { 710 name: "patch ClusterRole: Kubernetes == 1.22 and CoreDNS == 1.8.1", 711 kubernetesVersion: semver.Version{Major: 1, Minor: 22, Patch: 0}, 712 coreDNSVersion: "1.8.1", 713 sourceCoreDNSVersion: "1.8.1", 714 coreDNSPolicyRules: coreDNS180PolicyRules, 715 expectCoreDNSPolicyRules: coreDNS181PolicyRules, 716 }, 717 { 718 name: "patch ClusterRole: Kubernetes > 1.22 and CoreDNS > 1.8.1", 719 kubernetesVersion: semver.Version{Major: 1, Minor: 22, Patch: 2}, 720 coreDNSVersion: "1.8.5", 721 sourceCoreDNSVersion: "1.8.1", 722 coreDNSPolicyRules: coreDNS180PolicyRules, 723 expectCoreDNSPolicyRules: coreDNS181PolicyRules, 724 }, 725 { 726 name: "patch ClusterRole: Kubernetes > 1.22 and CoreDNS > 1.8.1: no-op", 727 kubernetesVersion: semver.Version{Major: 1, Minor: 22, Patch: 2}, 728 coreDNSVersion: "1.8.5", 729 sourceCoreDNSVersion: "1.8.5", 730 coreDNSPolicyRules: coreDNS181PolicyRules, 731 expectCoreDNSPolicyRules: coreDNS181PolicyRules, 732 }, 733 } 734 735 for _, tt := range tests { 736 t.Run(tt.name, func(t *testing.T) { 737 g := NewWithT(t) 738 739 cr := &rbacv1.ClusterRole{ 740 ObjectMeta: metav1.ObjectMeta{ 741 Name: coreDNSClusterRoleName, 742 Namespace: metav1.NamespaceSystem, 743 }, 744 Rules: tt.coreDNSPolicyRules, 745 } 746 fakeClient := fake.NewClientBuilder().WithObjects(cr).Build() 747 748 w := &Workload{ 749 Client: fakeClient, 750 } 751 752 err := w.updateCoreDNSClusterRole(ctx, tt.kubernetesVersion, &coreDNSInfo{ToImageTag: tt.coreDNSVersion, FromImageTag: tt.sourceCoreDNSVersion}) 753 754 if tt.expectErr { 755 g.Expect(err).To(HaveOccurred()) 756 return 757 } 758 g.Expect(err).ToNot(HaveOccurred()) 759 760 var actualClusterRole rbacv1.ClusterRole 761 g.Expect(fakeClient.Get(ctx, client.ObjectKey{Name: coreDNSClusterRoleName, Namespace: metav1.NamespaceSystem}, &actualClusterRole)).To(Succeed()) 762 763 g.Expect(actualClusterRole.Rules).To(BeComparableTo(tt.expectCoreDNSPolicyRules)) 764 }) 765 } 766 } 767 768 func TestSemanticallyDeepEqualPolicyRules(t *testing.T) { 769 tests := []struct { 770 name string 771 r1 []rbacv1.PolicyRule 772 r2 []rbacv1.PolicyRule 773 want bool 774 }{ 775 { 776 name: "equal: identical arrays", 777 r1: []rbacv1.PolicyRule{ 778 { 779 Verbs: []string{"list", "watch"}, 780 APIGroups: []string{""}, 781 Resources: []string{"endpoints", "services", "pods", "namespaces"}, 782 }, 783 { 784 Verbs: []string{"list", "watch"}, 785 APIGroups: []string{"discovery.k8s.io"}, 786 Resources: []string{"endpointslices"}, 787 }, 788 }, 789 r2: []rbacv1.PolicyRule{ 790 { 791 Verbs: []string{"list", "watch"}, 792 APIGroups: []string{""}, 793 Resources: []string{"endpoints", "services", "pods", "namespaces"}, 794 }, 795 { 796 Verbs: []string{"list", "watch"}, 797 APIGroups: []string{"discovery.k8s.io"}, 798 Resources: []string{"endpointslices"}, 799 }, 800 }, 801 want: true, 802 }, 803 { 804 name: "equal: arrays with different order", 805 r1: []rbacv1.PolicyRule{ 806 { 807 Verbs: []string{"list", "watch"}, 808 APIGroups: []string{""}, 809 Resources: []string{"endpoints", "services", "pods", "namespaces"}, 810 }, 811 { 812 Verbs: []string{"list", "watch"}, 813 APIGroups: []string{"discovery.k8s.io"}, 814 Resources: []string{"endpointslices"}, 815 }, 816 }, 817 r2: []rbacv1.PolicyRule{ 818 { 819 Verbs: []string{"watch", "list"}, 820 APIGroups: []string{"discovery.k8s.io"}, 821 Resources: []string{"endpointslices"}, 822 }, 823 { 824 Verbs: []string{"list", "watch"}, 825 APIGroups: []string{""}, 826 Resources: []string{"endpoints", "pods", "services", "namespaces"}, 827 }, 828 }, 829 want: true, 830 }, 831 { 832 name: "equal: separate rules but same semantic", 833 r1: []rbacv1.PolicyRule{ 834 { 835 Verbs: []string{"list", "watch"}, 836 APIGroups: []string{""}, 837 Resources: []string{"endpoints", "services", "pods", "namespaces"}, 838 }, 839 { 840 Verbs: []string{"list", "watch"}, 841 APIGroups: []string{"discovery.k8s.io"}, 842 Resources: []string{"endpointslices"}, 843 }, 844 }, 845 r2: []rbacv1.PolicyRule{ 846 { 847 Verbs: []string{"watch", "list"}, 848 APIGroups: []string{"discovery.k8s.io"}, 849 Resources: []string{"endpointslices"}, 850 }, 851 { 852 Verbs: []string{"list", "watch"}, 853 APIGroups: []string{""}, 854 Resources: []string{"endpoints", "pods"}, 855 }, 856 { 857 Verbs: []string{"list", "watch"}, 858 APIGroups: []string{""}, 859 Resources: []string{"services"}, 860 }, 861 { 862 Verbs: []string{"list", "watch"}, 863 APIGroups: []string{""}, 864 Resources: []string{"namespaces"}, 865 }, 866 }, 867 want: true, 868 }, 869 { 870 name: "not equal: one array has additional rules", 871 r1: []rbacv1.PolicyRule{ 872 { 873 Verbs: []string{"list", "watch"}, 874 APIGroups: []string{""}, 875 Resources: []string{"endpoints", "services", "pods", "namespaces"}, 876 }, 877 { 878 Verbs: []string{"list", "watch"}, 879 APIGroups: []string{"discovery.k8s.io"}, 880 Resources: []string{"endpointslices"}, 881 }, 882 }, 883 r2: []rbacv1.PolicyRule{ 884 { 885 Verbs: []string{"list", "watch"}, 886 APIGroups: []string{""}, 887 Resources: []string{"endpoints", "services", "pods", "namespaces"}, 888 }, 889 { 890 Verbs: []string{"list", "watch"}, 891 APIGroups: []string{"discovery.k8s.io"}, 892 Resources: []string{"endpointslices"}, 893 }, 894 { 895 Verbs: []string{"get"}, 896 APIGroups: []string{""}, 897 Resources: []string{"nodes"}, 898 }, 899 }, 900 want: false, 901 }, 902 { 903 name: "not equal: one array has additional verbs", 904 r1: []rbacv1.PolicyRule{ 905 { 906 Verbs: []string{"list", "watch"}, 907 APIGroups: []string{""}, 908 Resources: []string{"endpoints", "services", "pods", "namespaces"}, 909 }, 910 { 911 Verbs: []string{"list", "watch"}, 912 APIGroups: []string{"discovery.k8s.io"}, 913 Resources: []string{"endpointslices"}, 914 }, 915 }, 916 r2: []rbacv1.PolicyRule{ 917 { 918 Verbs: []string{"list", "watch"}, 919 APIGroups: []string{""}, 920 Resources: []string{"endpoints", "services", "pods", "namespaces"}, 921 }, 922 { 923 Verbs: []string{"list", "watch", "get", "update"}, 924 APIGroups: []string{"discovery.k8s.io"}, 925 Resources: []string{"endpointslices"}, 926 }, 927 }, 928 want: false, 929 }, 930 } 931 for _, tt := range tests { 932 t.Run(tt.name, func(t *testing.T) { 933 if got := semanticDeepEqualPolicyRules(tt.r1, tt.r2); got != tt.want { 934 t.Errorf("semanticDeepEqualPolicyRules() = %v, want %v", got, tt.want) 935 } 936 }) 937 } 938 } 939 940 func TestUpdateCoreDNSCorefile(t *testing.T) { 941 currentImageTag := "1.6.2" 942 originalCorefile := "some-coredns-core-file" 943 depl := &appsv1.Deployment{ 944 ObjectMeta: metav1.ObjectMeta{ 945 Name: coreDNSKey, 946 Namespace: metav1.NamespaceSystem, 947 }, 948 Spec: appsv1.DeploymentSpec{ 949 Template: corev1.PodTemplateSpec{ 950 ObjectMeta: metav1.ObjectMeta{ 951 Name: coreDNSKey, 952 }, 953 Spec: corev1.PodSpec{ 954 Containers: []corev1.Container{{ 955 Name: coreDNSKey, 956 Image: "k8s.gcr.io/coredns:" + currentImageTag, 957 }}, 958 Volumes: []corev1.Volume{{ 959 Name: "config-volume", 960 VolumeSource: corev1.VolumeSource{ 961 ConfigMap: &corev1.ConfigMapVolumeSource{ 962 LocalObjectReference: corev1.LocalObjectReference{ 963 Name: coreDNSKey, 964 }, 965 Items: []corev1.KeyToPath{{ 966 Key: "Corefile", 967 Path: "Corefile", 968 }}, 969 }, 970 }, 971 }}, 972 }, 973 }, 974 }, 975 } 976 cm := &corev1.ConfigMap{ 977 ObjectMeta: metav1.ObjectMeta{ 978 Name: coreDNSKey, 979 Namespace: metav1.NamespaceSystem, 980 }, 981 Data: map[string]string{ 982 "Corefile": originalCorefile, 983 }, 984 } 985 986 t.Run("returns error if migrate failed to update corefile", func(t *testing.T) { 987 g := NewWithT(t) 988 objs := []client.Object{depl, cm} 989 fakeClient := fake.NewClientBuilder().WithObjects(objs...).Build() 990 fakeMigrator := &fakeMigrator{ 991 migrateErr: errors.New("failed to migrate"), 992 } 993 994 w := &Workload{ 995 Client: fakeClient, 996 CoreDNSMigrator: fakeMigrator, 997 } 998 999 info := &coreDNSInfo{ 1000 Corefile: "updated-core-file", 1001 Deployment: depl, 1002 CurrentMajorMinorPatch: "1.6.2", 1003 TargetMajorMinorPatch: "1.7.2", 1004 } 1005 1006 err := w.updateCoreDNSCorefile(ctx, info) 1007 g.Expect(err).To(HaveOccurred()) 1008 g.Expect(fakeMigrator.migrateCalled).To(BeTrue()) 1009 1010 var expectedConfigMap corev1.ConfigMap 1011 g.Expect(fakeClient.Get(ctx, client.ObjectKey{Name: coreDNSKey, Namespace: metav1.NamespaceSystem}, &expectedConfigMap)).To(Succeed()) 1012 g.Expect(expectedConfigMap.Data).To(HaveLen(1)) 1013 g.Expect(expectedConfigMap.Data).To(HaveKeyWithValue("Corefile", originalCorefile)) 1014 }) 1015 1016 t.Run("creates a backup of the corefile", func(t *testing.T) { 1017 g := NewWithT(t) 1018 // Not including the deployment so as to fail early and verify that 1019 // the intermediate config map update occurred 1020 objs := []client.Object{cm} 1021 fakeClient := fake.NewClientBuilder().WithObjects(objs...).Build() 1022 fakeMigrator := &fakeMigrator{ 1023 migratedCorefile: "updated-core-file", 1024 } 1025 1026 w := &Workload{ 1027 Client: fakeClient, 1028 CoreDNSMigrator: fakeMigrator, 1029 } 1030 1031 info := &coreDNSInfo{ 1032 Corefile: originalCorefile, 1033 Deployment: depl, 1034 CurrentMajorMinorPatch: currentImageTag, 1035 TargetMajorMinorPatch: "1.7.2", 1036 } 1037 1038 err := w.updateCoreDNSCorefile(ctx, info) 1039 g.Expect(err).To(HaveOccurred()) 1040 1041 var expectedConfigMap corev1.ConfigMap 1042 g.Expect(fakeClient.Get(ctx, client.ObjectKey{Name: coreDNSKey, Namespace: metav1.NamespaceSystem}, &expectedConfigMap)).To(Succeed()) 1043 g.Expect(expectedConfigMap.Data).To(HaveLen(2)) 1044 g.Expect(expectedConfigMap.Data).To(HaveKeyWithValue("Corefile", originalCorefile)) 1045 g.Expect(expectedConfigMap.Data).To(HaveKeyWithValue("Corefile-backup", originalCorefile)) 1046 }) 1047 1048 t.Run("patches the core dns deployment to point to the backup corefile before migration", func(t *testing.T) { 1049 t.Skip("Updating the corefile, after updating controller runtime somehow makes this test fail in a conflict, needs investigation") 1050 1051 g := NewWithT(t) 1052 objs := []client.Object{depl, cm} 1053 fakeClient := fake.NewClientBuilder().WithObjects(objs...).Build() 1054 fakeMigrator := &fakeMigrator{ 1055 migratedCorefile: "updated-core-file", 1056 } 1057 1058 w := &Workload{ 1059 Client: fakeClient, 1060 CoreDNSMigrator: fakeMigrator, 1061 } 1062 1063 info := &coreDNSInfo{ 1064 Corefile: originalCorefile, 1065 Deployment: depl, 1066 CurrentMajorMinorPatch: currentImageTag, 1067 TargetMajorMinorPatch: "1.7.2", 1068 } 1069 1070 err := w.updateCoreDNSCorefile(ctx, info) 1071 g.Expect(err).ToNot(HaveOccurred()) 1072 1073 expectedVolume := corev1.Volume{ 1074 Name: coreDNSVolumeKey, 1075 VolumeSource: corev1.VolumeSource{ 1076 ConfigMap: &corev1.ConfigMapVolumeSource{ 1077 LocalObjectReference: corev1.LocalObjectReference{ 1078 Name: coreDNSKey, 1079 }, 1080 Items: []corev1.KeyToPath{{ 1081 Key: "Corefile-backup", 1082 Path: "Corefile", 1083 }}, 1084 }, 1085 }, 1086 } 1087 1088 var actualDeployment appsv1.Deployment 1089 g.Expect(fakeClient.Get(ctx, client.ObjectKey{Name: coreDNSKey, Namespace: metav1.NamespaceSystem}, &actualDeployment)).To(Succeed()) 1090 g.Expect(actualDeployment.Spec.Template.Spec.Volumes).To(ConsistOf(expectedVolume)) 1091 1092 var expectedConfigMap corev1.ConfigMap 1093 g.Expect(fakeClient.Get(ctx, client.ObjectKey{Name: coreDNSKey, Namespace: metav1.NamespaceSystem}, &expectedConfigMap)).To(Succeed()) 1094 g.Expect(expectedConfigMap.Data).To(HaveLen(2)) 1095 g.Expect(expectedConfigMap.Data).To(HaveKeyWithValue("Corefile", "updated-core-file")) 1096 g.Expect(expectedConfigMap.Data).To(HaveKeyWithValue("Corefile-backup", originalCorefile)) 1097 }) 1098 } 1099 1100 func TestGetCoreDNSInfo(t *testing.T) { 1101 t.Run("get coredns info", func(t *testing.T) { 1102 imageSomeFolder162 := "k8s.gcr.io/some-folder/coredns:1.6.2" 1103 image162 := "k8s.gcr.io/coredns:1.6.2" 1104 1105 expectedCorefile := "some-coredns-core-file" 1106 cm := &corev1.ConfigMap{ 1107 ObjectMeta: metav1.ObjectMeta{ 1108 Name: coreDNSKey, 1109 Namespace: metav1.NamespaceSystem, 1110 }, 1111 Data: map[string]string{ 1112 "Corefile": expectedCorefile, 1113 }, 1114 } 1115 1116 emptycm := cm.DeepCopy() 1117 delete(emptycm.Data, "Corefile") 1118 1119 emptyDepl := newCoreDNSInfoDeploymentWithimage("") 1120 emptyDepl.Spec.Template.Spec.Containers = []corev1.Container{} 1121 1122 clusterConfig := &bootstrapv1.ClusterConfiguration{ 1123 DNS: bootstrapv1.DNS{ 1124 ImageMeta: bootstrapv1.ImageMeta{ 1125 ImageRepository: "myrepo", 1126 ImageTag: "1.7.2-foobar.1", 1127 }, 1128 }, 1129 } 1130 badImgTagDNS := clusterConfig.DeepCopy() 1131 badImgTagDNS.DNS.ImageTag = "v1X6.2-foobar.1" 1132 1133 tests := []struct { 1134 name string 1135 expectErr bool 1136 objs []client.Object 1137 clusterConfig *bootstrapv1.ClusterConfiguration 1138 kubernetesVersion semver.Version 1139 expectedInfo coreDNSInfo 1140 }{ 1141 { 1142 name: "returns core dns info", 1143 objs: []client.Object{newCoreDNSInfoDeploymentWithimage(imageSomeFolder162), cm}, 1144 clusterConfig: clusterConfig, 1145 expectedInfo: coreDNSInfo{ 1146 CurrentMajorMinorPatch: "1.6.2", 1147 FromImageTag: "1.6.2", 1148 TargetMajorMinorPatch: "1.7.2", 1149 FromImage: imageSomeFolder162, 1150 ToImage: "myrepo/coredns:1.7.2-foobar.1", 1151 ToImageTag: "1.7.2-foobar.1", 1152 }, 1153 }, 1154 { 1155 name: "uses global config ImageRepository if DNS ImageRepository is not set", 1156 objs: []client.Object{newCoreDNSInfoDeploymentWithimage(imageSomeFolder162), cm}, 1157 clusterConfig: &bootstrapv1.ClusterConfiguration{ 1158 ImageRepository: "globalRepo/sub-path", 1159 DNS: bootstrapv1.DNS{ 1160 ImageMeta: bootstrapv1.ImageMeta{ 1161 ImageTag: "1.7.2-foobar.1", 1162 }, 1163 }, 1164 }, 1165 expectedInfo: coreDNSInfo{ 1166 CurrentMajorMinorPatch: "1.6.2", 1167 FromImageTag: "1.6.2", 1168 TargetMajorMinorPatch: "1.7.2", 1169 FromImage: imageSomeFolder162, 1170 ToImage: "globalRepo/sub-path/coredns:1.7.2-foobar.1", 1171 ToImageTag: "1.7.2-foobar.1", 1172 }, 1173 }, 1174 { 1175 name: "uses DNS ImageRepository config if both global and DNS-level are set", 1176 objs: []client.Object{newCoreDNSInfoDeploymentWithimage(imageSomeFolder162), cm}, 1177 clusterConfig: &bootstrapv1.ClusterConfiguration{ 1178 ImageRepository: "globalRepo", 1179 DNS: bootstrapv1.DNS{ 1180 ImageMeta: bootstrapv1.ImageMeta{ 1181 ImageRepository: "dnsRepo", 1182 ImageTag: "1.7.2-foobar.1", 1183 }, 1184 }, 1185 }, 1186 expectedInfo: coreDNSInfo{ 1187 CurrentMajorMinorPatch: "1.6.2", 1188 FromImageTag: "1.6.2", 1189 TargetMajorMinorPatch: "1.7.2", 1190 FromImage: imageSomeFolder162, 1191 ToImage: "dnsRepo/coredns:1.7.2-foobar.1", 1192 ToImageTag: "1.7.2-foobar.1", 1193 }, 1194 }, 1195 { 1196 name: "patches ImageRepository to registry.k8s.io if it's set on neither global nor DNS-level and kubernetesVersion >= v1.25", 1197 objs: []client.Object{newCoreDNSInfoDeploymentWithimage(imageSomeFolder162), cm}, 1198 clusterConfig: &bootstrapv1.ClusterConfiguration{ 1199 DNS: bootstrapv1.DNS{ 1200 ImageMeta: bootstrapv1.ImageMeta{ 1201 ImageTag: "1.7.2-foobar.1", 1202 }, 1203 }, 1204 }, 1205 kubernetesVersion: semver.MustParse("1.25.0"), 1206 expectedInfo: coreDNSInfo{ 1207 CurrentMajorMinorPatch: "1.6.2", 1208 FromImageTag: "1.6.2", 1209 TargetMajorMinorPatch: "1.7.2", 1210 FromImage: imageSomeFolder162, 1211 ToImage: "registry.k8s.io/some-folder/coredns:1.7.2-foobar.1", 1212 ToImageTag: "1.7.2-foobar.1", 1213 }, 1214 }, 1215 { 1216 name: "rename to coredns/coredns when upgrading to coredns=1.8.0 and kubernetesVersion=1.22.16", 1217 // 1.22.16 uses k8s.gcr.io as default registry. Thus the registry doesn't get changed as 1218 // FromImage is already using k8s.gcr.io. 1219 objs: []client.Object{newCoreDNSInfoDeploymentWithimage("k8s.gcr.io/coredns:1.6.2"), cm}, 1220 clusterConfig: &bootstrapv1.ClusterConfiguration{ 1221 DNS: bootstrapv1.DNS{ 1222 ImageMeta: bootstrapv1.ImageMeta{ 1223 ImageTag: "1.8.0", 1224 }, 1225 }, 1226 }, 1227 kubernetesVersion: semver.MustParse("1.22.16"), 1228 expectedInfo: coreDNSInfo{ 1229 CurrentMajorMinorPatch: "1.6.2", 1230 FromImageTag: "1.6.2", 1231 TargetMajorMinorPatch: "1.8.0", 1232 FromImage: "k8s.gcr.io/coredns:1.6.2", 1233 ToImage: "k8s.gcr.io/coredns/coredns:1.8.0", 1234 ToImageTag: "1.8.0", 1235 }, 1236 }, 1237 { 1238 name: "rename to coredns/coredns when upgrading to coredns=1.8.0 and kubernetesVersion=1.22.17", 1239 // 1.22.17 has registry.k8s.io as default registry. Thus the registry gets changed as 1240 // FromImage is using k8s.gcr.io. 1241 objs: []client.Object{newCoreDNSInfoDeploymentWithimage("k8s.gcr.io/coredns:1.6.2"), cm}, 1242 clusterConfig: &bootstrapv1.ClusterConfiguration{ 1243 DNS: bootstrapv1.DNS{ 1244 ImageMeta: bootstrapv1.ImageMeta{ 1245 ImageTag: "1.8.0", 1246 }, 1247 }, 1248 }, 1249 kubernetesVersion: semver.MustParse("1.22.17"), 1250 expectedInfo: coreDNSInfo{ 1251 CurrentMajorMinorPatch: "1.6.2", 1252 FromImageTag: "1.6.2", 1253 TargetMajorMinorPatch: "1.8.0", 1254 FromImage: "k8s.gcr.io/coredns:1.6.2", 1255 ToImage: "registry.k8s.io/coredns/coredns:1.8.0", 1256 ToImageTag: "1.8.0", 1257 }, 1258 }, 1259 { 1260 name: "rename to coredns/coredns when upgrading to coredns=1.8.0 and kubernetesVersion=1.26.0", 1261 // 1.26.0 uses registry.k8s.io as default registry. Thus the registry doesn't get changed as 1262 // FromImage is already using registry.k8s.io. 1263 objs: []client.Object{newCoreDNSInfoDeploymentWithimage("registry.k8s.io/coredns:1.6.2"), cm}, 1264 clusterConfig: &bootstrapv1.ClusterConfiguration{ 1265 DNS: bootstrapv1.DNS{ 1266 ImageMeta: bootstrapv1.ImageMeta{ 1267 ImageTag: "1.8.0", 1268 }, 1269 }, 1270 }, 1271 kubernetesVersion: semver.MustParse("1.26.0"), 1272 expectedInfo: coreDNSInfo{ 1273 CurrentMajorMinorPatch: "1.6.2", 1274 FromImageTag: "1.6.2", 1275 TargetMajorMinorPatch: "1.8.0", 1276 FromImage: "registry.k8s.io/coredns:1.6.2", 1277 ToImage: "registry.k8s.io/coredns/coredns:1.8.0", 1278 ToImageTag: "1.8.0", 1279 }, 1280 }, 1281 { 1282 name: "patches ImageRepository to registry.k8s.io if it's set on neither global nor DNS-level and kubernetesVersion >= v1.22.17 and rename to coredns/coredns", 1283 // 1.22.17 has registry.k8s.io as default registry. Thus the registry gets changed as 1284 // FromImage is using k8s.gcr.io. 1285 objs: []client.Object{newCoreDNSInfoDeploymentWithimage(image162), cm}, 1286 clusterConfig: &bootstrapv1.ClusterConfiguration{ 1287 DNS: bootstrapv1.DNS{ 1288 ImageMeta: bootstrapv1.ImageMeta{ 1289 ImageTag: "1.8.0", 1290 }, 1291 }, 1292 }, 1293 kubernetesVersion: semver.MustParse("1.22.17"), 1294 expectedInfo: coreDNSInfo{ 1295 CurrentMajorMinorPatch: "1.6.2", 1296 FromImageTag: "1.6.2", 1297 TargetMajorMinorPatch: "1.8.0", 1298 FromImage: image162, 1299 ToImage: "registry.k8s.io/coredns/coredns:1.8.0", 1300 ToImageTag: "1.8.0", 1301 }, 1302 }, 1303 { 1304 name: "patches ImageRepository to registry.k8s.io if it's set on neither global nor DNS-level and kubernetesVersion >= v1.25 and rename to coredns/coredns", 1305 objs: []client.Object{newCoreDNSInfoDeploymentWithimage(image162), cm}, 1306 clusterConfig: &bootstrapv1.ClusterConfiguration{ 1307 DNS: bootstrapv1.DNS{ 1308 ImageMeta: bootstrapv1.ImageMeta{ 1309 ImageTag: "1.8.0", 1310 }, 1311 }, 1312 }, 1313 kubernetesVersion: semver.MustParse("1.25.0"), 1314 expectedInfo: coreDNSInfo{ 1315 CurrentMajorMinorPatch: "1.6.2", 1316 FromImageTag: "1.6.2", 1317 TargetMajorMinorPatch: "1.8.0", 1318 FromImage: image162, 1319 ToImage: "registry.k8s.io/coredns/coredns:1.8.0", 1320 ToImageTag: "1.8.0", 1321 }, 1322 }, 1323 { 1324 name: "returns error if unable to find coredns config map", 1325 objs: []client.Object{newCoreDNSInfoDeploymentWithimage(imageSomeFolder162)}, 1326 clusterConfig: clusterConfig, 1327 expectErr: true, 1328 }, 1329 { 1330 name: "returns error if unable to find coredns deployment", 1331 objs: []client.Object{cm}, 1332 clusterConfig: clusterConfig, 1333 expectErr: true, 1334 }, 1335 { 1336 name: "returns error if coredns deployment doesn't have coredns container", 1337 objs: []client.Object{emptyDepl, cm}, 1338 clusterConfig: clusterConfig, 1339 expectErr: true, 1340 }, 1341 { 1342 name: "returns error if unable to find coredns corefile", 1343 objs: []client.Object{newCoreDNSInfoDeploymentWithimage(imageSomeFolder162), emptycm}, 1344 clusterConfig: clusterConfig, 1345 expectErr: true, 1346 }, 1347 { 1348 name: "returns error if unable to parse the container image", 1349 objs: []client.Object{newCoreDNSInfoDeploymentWithimage("k8s.gcr.io/asd:1123/asd:coredns:1.6.1"), cm}, 1350 clusterConfig: clusterConfig, 1351 expectErr: true, 1352 }, 1353 { 1354 name: "returns error if container image has not tag", 1355 objs: []client.Object{newCoreDNSInfoDeploymentWithimage("k8s.gcr.io/coredns"), cm}, 1356 clusterConfig: clusterConfig, 1357 expectErr: true, 1358 }, 1359 { 1360 name: "returns error if unable to semver parse container image", 1361 objs: []client.Object{newCoreDNSInfoDeploymentWithimage("k8s.gcr.io/coredns:v1X6.2"), cm}, 1362 clusterConfig: clusterConfig, 1363 expectErr: true, 1364 }, 1365 { 1366 name: "returns error if unable to semver parse dns image tag", 1367 objs: []client.Object{newCoreDNSInfoDeploymentWithimage(imageSomeFolder162), cm}, 1368 clusterConfig: badImgTagDNS, 1369 expectErr: true, 1370 }, 1371 } 1372 for i := range tests { 1373 tt := tests[i] 1374 t.Run(tt.name, func(t *testing.T) { 1375 g := NewWithT(t) 1376 fakeClient := fake.NewClientBuilder().WithObjects(tt.objs...).Build() 1377 w := &Workload{ 1378 Client: fakeClient, 1379 } 1380 1381 var actualDepl *appsv1.Deployment 1382 for _, o := range tt.objs { 1383 if d, ok := o.(*appsv1.Deployment); ok { 1384 actualDepl = d 1385 break 1386 } 1387 } 1388 1389 actualInfo, err := w.getCoreDNSInfo(ctx, tt.clusterConfig, tt.kubernetesVersion) 1390 if tt.expectErr { 1391 g.Expect(err).To(HaveOccurred()) 1392 return 1393 } 1394 g.Expect(err).ToNot(HaveOccurred()) 1395 tt.expectedInfo.Corefile = expectedCorefile 1396 tt.expectedInfo.Deployment = actualDepl 1397 1398 g.Expect(actualInfo).To(BeComparableTo(&tt.expectedInfo)) 1399 }) 1400 } 1401 }) 1402 } 1403 1404 func TestUpdateCoreDNSImageInfoInKubeadmConfigMap(t *testing.T) { 1405 tests := []struct { 1406 name string 1407 clusterConfigurationData string 1408 newDNS bootstrapv1.DNS 1409 wantClusterConfiguration string 1410 }{ 1411 { 1412 name: "it should set the DNS image config", 1413 clusterConfigurationData: utilyaml.Raw(` 1414 apiVersion: kubeadm.k8s.io/v1beta2 1415 kind: ClusterConfiguration 1416 `), 1417 newDNS: bootstrapv1.DNS{ 1418 ImageMeta: bootstrapv1.ImageMeta{ 1419 ImageRepository: "example.com/k8s", 1420 ImageTag: "v1.2.3", 1421 }, 1422 }, 1423 wantClusterConfiguration: utilyaml.Raw(` 1424 apiServer: {} 1425 apiVersion: kubeadm.k8s.io/v1beta2 1426 controllerManager: {} 1427 dns: 1428 imageRepository: example.com/k8s 1429 imageTag: v1.2.3 1430 etcd: {} 1431 kind: ClusterConfiguration 1432 networking: {} 1433 scheduler: {} 1434 `), 1435 }, 1436 } 1437 for i := range tests { 1438 tt := tests[i] 1439 t.Run(tt.name, func(t *testing.T) { 1440 g := NewWithT(t) 1441 fakeClient := fake.NewClientBuilder().WithObjects(&corev1.ConfigMap{ 1442 ObjectMeta: metav1.ObjectMeta{ 1443 Name: kubeadmConfigKey, 1444 Namespace: metav1.NamespaceSystem, 1445 }, 1446 Data: map[string]string{ 1447 clusterConfigurationKey: tt.clusterConfigurationData, 1448 }, 1449 }).Build() 1450 1451 w := &Workload{ 1452 Client: fakeClient, 1453 } 1454 err := w.UpdateClusterConfiguration(ctx, semver.MustParse("1.19.1"), w.updateCoreDNSImageInfoInKubeadmConfigMap(&tt.newDNS)) 1455 g.Expect(err).ToNot(HaveOccurred()) 1456 1457 var actualConfig corev1.ConfigMap 1458 g.Expect(w.Client.Get( 1459 ctx, 1460 client.ObjectKey{Name: kubeadmConfigKey, Namespace: metav1.NamespaceSystem}, 1461 &actualConfig, 1462 )).To(Succeed()) 1463 g.Expect(actualConfig.Data[clusterConfigurationKey]).Should(Equal(tt.wantClusterConfiguration), cmp.Diff(tt.wantClusterConfiguration, actualConfig.Data[clusterConfigurationKey])) 1464 }) 1465 } 1466 } 1467 1468 func TestUpdateCoreDNSDeployment(t *testing.T) { 1469 depl := &appsv1.Deployment{ 1470 ObjectMeta: metav1.ObjectMeta{ 1471 Name: coreDNSKey, 1472 Namespace: metav1.NamespaceSystem, 1473 }, 1474 Spec: appsv1.DeploymentSpec{ 1475 Template: corev1.PodTemplateSpec{ 1476 ObjectMeta: metav1.ObjectMeta{ 1477 Name: coreDNSKey, 1478 }, 1479 Spec: corev1.PodSpec{ 1480 Containers: []corev1.Container{{ 1481 Name: coreDNSKey, 1482 Image: "k8s.gcr.io/coredns:1.6.2", 1483 Args: []string{"-conf", "/etc/coredns/Corefile"}, 1484 }}, 1485 Volumes: []corev1.Volume{{ 1486 Name: "config-volume", 1487 VolumeSource: corev1.VolumeSource{ 1488 ConfigMap: &corev1.ConfigMapVolumeSource{ 1489 LocalObjectReference: corev1.LocalObjectReference{ 1490 Name: coreDNSKey, 1491 }, 1492 Items: []corev1.KeyToPath{{ 1493 Key: corefileBackupKey, 1494 Path: corefileKey, 1495 }}, 1496 }, 1497 }, 1498 }}, 1499 }, 1500 }, 1501 }, 1502 } 1503 1504 tests := []struct { 1505 name string 1506 objs []client.Object 1507 info *coreDNSInfo 1508 expectErr bool 1509 }{ 1510 { 1511 name: "patches coredns deployment successfully", 1512 objs: []client.Object{depl}, 1513 info: &coreDNSInfo{ 1514 Deployment: depl.DeepCopy(), 1515 Corefile: "updated-core-file", 1516 FromImage: "k8s.gcr.io/coredns:1.6.2", 1517 ToImage: "myrepo/mycoredns:1.7.2-foobar.1", 1518 CurrentMajorMinorPatch: "1.6.2", 1519 TargetMajorMinorPatch: "1.7.2", 1520 }, 1521 }, 1522 { 1523 name: "returns error if patch fails", 1524 objs: []client.Object{}, 1525 info: &coreDNSInfo{ 1526 Deployment: depl.DeepCopy(), 1527 Corefile: "updated-core-file", 1528 FromImage: "k8s.gcr.io/coredns:1.6.2", 1529 ToImage: "myrepo/mycoredns:1.7.2-foobar.1", 1530 CurrentMajorMinorPatch: "1.6.2", 1531 TargetMajorMinorPatch: "1.7.2", 1532 }, 1533 expectErr: true, 1534 }, 1535 { 1536 name: "deployment is nil for some reason", 1537 info: &coreDNSInfo{ 1538 Deployment: nil, 1539 Corefile: "updated-core-file", 1540 FromImage: "k8s.gcr.io/coredns:1.6.2", 1541 ToImage: "myrepo/mycoredns:1.7.2-foobar.1", 1542 CurrentMajorMinorPatch: "1.6.2", 1543 TargetMajorMinorPatch: "1.7.2", 1544 }, 1545 expectErr: true, 1546 }, 1547 } 1548 1549 for _, tt := range tests { 1550 t.Run(tt.name, func(t *testing.T) { 1551 g := NewWithT(t) 1552 fakeClient := fake.NewClientBuilder().WithObjects(tt.objs...).Build() 1553 1554 w := &Workload{ 1555 Client: fakeClient, 1556 } 1557 1558 err := w.updateCoreDNSDeployment(ctx, tt.info, semver.Version{Major: 1, Minor: 26, Patch: 0}) 1559 if tt.expectErr { 1560 g.Expect(err).To(HaveOccurred()) 1561 return 1562 } 1563 g.Expect(err).ToNot(HaveOccurred()) 1564 1565 expectedVolume := corev1.Volume{ 1566 Name: "config-volume", 1567 VolumeSource: corev1.VolumeSource{ 1568 ConfigMap: &corev1.ConfigMapVolumeSource{ 1569 LocalObjectReference: corev1.LocalObjectReference{ 1570 Name: coreDNSKey, 1571 }, 1572 Items: []corev1.KeyToPath{{ 1573 Key: corefileKey, 1574 Path: corefileKey, 1575 }}, 1576 }, 1577 }, 1578 } 1579 1580 var actualDeployment appsv1.Deployment 1581 g.Expect(fakeClient.Get(ctx, client.ObjectKey{Name: coreDNSKey, Namespace: metav1.NamespaceSystem}, &actualDeployment)).To(Succeed()) 1582 // ensure the image is updated and the volumes point to the corefile 1583 g.Expect(actualDeployment.Spec.Template.Spec.Containers[0].Image).To(Equal(tt.info.ToImage)) 1584 g.Expect(actualDeployment.Spec.Template.Spec.Volumes).To(ConsistOf(expectedVolume)) 1585 }) 1586 } 1587 } 1588 1589 func TestPatchCoreDNSDeploymentTolerations(t *testing.T) { 1590 oldControlPlaneToleration := corev1.Toleration{ 1591 Key: oldControlPlaneTaint, 1592 Effect: corev1.TaintEffectNoSchedule, 1593 } 1594 controlPlaneToleration := corev1.Toleration{ 1595 Key: controlPlaneTaint, 1596 Effect: corev1.TaintEffectNoSchedule, 1597 } 1598 1599 tests := []struct { 1600 name string 1601 currentTolerations []corev1.Toleration 1602 kubernetesVersion semver.Version 1603 expectedTolerations []corev1.Toleration 1604 }{ 1605 { 1606 name: "adds both tolerations for Kubernetes v1.25", 1607 currentTolerations: []corev1.Toleration{}, 1608 kubernetesVersion: semver.Version{Major: 1, Minor: 25, Patch: 0}, 1609 expectedTolerations: []corev1.Toleration{ 1610 controlPlaneToleration, 1611 oldControlPlaneToleration, 1612 }, 1613 }, 1614 { 1615 name: "adds only new toleration for Kubernetes v1.26", 1616 currentTolerations: []corev1.Toleration{}, 1617 kubernetesVersion: semver.Version{Major: 1, Minor: 26, Patch: 0}, 1618 expectedTolerations: []corev1.Toleration{ 1619 controlPlaneToleration, 1620 }, 1621 }, 1622 { 1623 name: "adds both tolerations for Kubernetes v1.25 and preserves additional tolerations", 1624 currentTolerations: []corev1.Toleration{ 1625 { 1626 Key: "my-special.custom/taint", 1627 Effect: corev1.TaintEffectNoExecute, 1628 Value: "aValue", 1629 }, 1630 }, 1631 kubernetesVersion: semver.Version{Major: 1, Minor: 25, Patch: 0}, 1632 expectedTolerations: []corev1.Toleration{ 1633 controlPlaneToleration, 1634 oldControlPlaneToleration, 1635 { 1636 Key: "my-special.custom/taint", 1637 Effect: corev1.TaintEffectNoExecute, 1638 Value: "aValue", 1639 }, 1640 }, 1641 }, 1642 { 1643 name: "ensures only new toleration is set for Kubernetes v1.26, drops old toleration and preserves additional tolerations", 1644 currentTolerations: []corev1.Toleration{ 1645 oldControlPlaneToleration, 1646 { 1647 Key: "my-special.custom/taint", 1648 Effect: corev1.TaintEffectNoExecute, 1649 Value: "aValue", 1650 }, 1651 }, 1652 kubernetesVersion: semver.Version{Major: 1, Minor: 25, Patch: 0}, 1653 expectedTolerations: []corev1.Toleration{ 1654 controlPlaneToleration, 1655 oldControlPlaneToleration, 1656 { 1657 Key: "my-special.custom/taint", 1658 Effect: corev1.TaintEffectNoExecute, 1659 Value: "aValue", 1660 }, 1661 }, 1662 }, 1663 } 1664 1665 for _, tt := range tests { 1666 t.Run(tt.name, func(t *testing.T) { 1667 g := NewWithT(t) 1668 1669 d := &appsv1.Deployment{ 1670 Spec: appsv1.DeploymentSpec{ 1671 Template: corev1.PodTemplateSpec{ 1672 Spec: corev1.PodSpec{ 1673 Tolerations: tt.currentTolerations, 1674 }, 1675 }, 1676 }, 1677 } 1678 1679 patchCoreDNSDeploymentTolerations(d, tt.kubernetesVersion) 1680 1681 g.Expect(d.Spec.Template.Spec.Tolerations).To(BeComparableTo(tt.expectedTolerations)) 1682 }) 1683 } 1684 } 1685 1686 type fakeMigrator struct { 1687 migrateCalled bool 1688 migrateErr error 1689 migratedCorefile string 1690 } 1691 1692 func (m *fakeMigrator) Migrate(_, _, _ string, _ bool) (string, error) { 1693 m.migrateCalled = true 1694 if m.migrateErr != nil { 1695 return "", m.migrateErr 1696 } 1697 return m.migratedCorefile, nil 1698 } 1699 1700 func newCoreDNSInfoDeploymentWithimage(image string) *appsv1.Deployment { 1701 return &appsv1.Deployment{ 1702 TypeMeta: metav1.TypeMeta{ 1703 Kind: "Deployment", 1704 APIVersion: "apps/v1", 1705 }, 1706 ObjectMeta: metav1.ObjectMeta{ 1707 Name: coreDNSKey, 1708 Namespace: metav1.NamespaceSystem, 1709 }, 1710 Spec: appsv1.DeploymentSpec{ 1711 Template: corev1.PodTemplateSpec{ 1712 ObjectMeta: metav1.ObjectMeta{ 1713 Name: coreDNSKey, 1714 }, 1715 Spec: corev1.PodSpec{ 1716 Containers: []corev1.Container{{ 1717 Name: coreDNSKey, 1718 Image: image, 1719 }}, 1720 }, 1721 }, 1722 }, 1723 } 1724 }