k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/kubeadm/app/util/staticpod/utils_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 staticpod 18 19 import ( 20 "io" 21 "os" 22 "path/filepath" 23 "reflect" 24 "sort" 25 "strconv" 26 "strings" 27 "testing" 28 29 v1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 32 kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm" 33 kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants" 34 testutil "k8s.io/kubernetes/cmd/kubeadm/test" 35 ) 36 37 func TestComponentResources(t *testing.T) { 38 a := ComponentResources("250m") 39 if a.Requests == nil { 40 t.Errorf( 41 "failed componentResources, return value was nil", 42 ) 43 } 44 } 45 46 func TestGetAPIServerProbeAddress(t *testing.T) { 47 tests := []struct { 48 desc string 49 endpoint *kubeadmapi.APIEndpoint 50 expected string 51 }{ 52 { 53 desc: "nil endpoint returns 127.0.0.1", 54 expected: "127.0.0.1", 55 }, 56 { 57 desc: "empty AdvertiseAddress endpoint returns 127.0.0.1", 58 endpoint: &kubeadmapi.APIEndpoint{}, 59 expected: "127.0.0.1", 60 }, 61 { 62 desc: "filled in AdvertiseAddress endpoint returns it", 63 endpoint: &kubeadmapi.APIEndpoint{ 64 AdvertiseAddress: "10.10.10.10", 65 }, 66 expected: "10.10.10.10", 67 }, 68 { 69 desc: "filled in ipv6 AdvertiseAddress endpoint returns it", 70 endpoint: &kubeadmapi.APIEndpoint{ 71 AdvertiseAddress: "2001:abcd:bcda::1", 72 }, 73 expected: "2001:abcd:bcda::1", 74 }, 75 { 76 desc: "filled in 0.0.0.0 AdvertiseAddress endpoint returns empty", 77 endpoint: &kubeadmapi.APIEndpoint{ 78 AdvertiseAddress: "0.0.0.0", 79 }, 80 expected: "", 81 }, 82 { 83 desc: "filled in :: AdvertiseAddress endpoint returns empty", 84 endpoint: &kubeadmapi.APIEndpoint{ 85 AdvertiseAddress: "::", 86 }, 87 expected: "", 88 }, 89 } 90 91 for _, test := range tests { 92 t.Run(test.desc, func(t *testing.T) { 93 actual := GetAPIServerProbeAddress(test.endpoint) 94 if actual != test.expected { 95 t.Errorf("Unexpected result from GetAPIServerProbeAddress:\n\texpected: %s\n\tactual: %s", test.expected, actual) 96 } 97 }) 98 } 99 } 100 101 func TestGetControllerManagerProbeAddress(t *testing.T) { 102 tests := []struct { 103 desc string 104 cfg *kubeadmapi.ClusterConfiguration 105 expected string 106 }{ 107 { 108 desc: "no controller manager extra args leads to 127.0.0.1 being used", 109 cfg: &kubeadmapi.ClusterConfiguration{ 110 ControllerManager: kubeadmapi.ControlPlaneComponent{ 111 ExtraArgs: []kubeadmapi.Arg{}, 112 }, 113 }, 114 expected: "127.0.0.1", 115 }, 116 { 117 desc: "setting controller manager extra address arg to something acknowledges it", 118 cfg: &kubeadmapi.ClusterConfiguration{ 119 ControllerManager: kubeadmapi.ControlPlaneComponent{ 120 ExtraArgs: []kubeadmapi.Arg{ 121 {Name: kubeControllerManagerBindAddressArg, Value: "10.10.10.10"}, 122 }, 123 }, 124 }, 125 expected: "10.10.10.10", 126 }, 127 { 128 desc: "setting controller manager extra ipv6 address arg to something acknowledges it", 129 cfg: &kubeadmapi.ClusterConfiguration{ 130 ControllerManager: kubeadmapi.ControlPlaneComponent{ 131 ExtraArgs: []kubeadmapi.Arg{ 132 {Name: kubeControllerManagerBindAddressArg, Value: "2001:abcd:bcda::1"}, 133 }, 134 }, 135 }, 136 expected: "2001:abcd:bcda::1", 137 }, 138 { 139 desc: "setting controller manager extra address arg to 0.0.0.0 returns empty", 140 cfg: &kubeadmapi.ClusterConfiguration{ 141 ControllerManager: kubeadmapi.ControlPlaneComponent{ 142 ExtraArgs: []kubeadmapi.Arg{ 143 {Name: kubeControllerManagerBindAddressArg, Value: "0.0.0.0"}, 144 }, 145 }, 146 }, 147 expected: "", 148 }, 149 { 150 desc: "setting controller manager extra ipv6 address arg to :: returns empty", 151 cfg: &kubeadmapi.ClusterConfiguration{ 152 ControllerManager: kubeadmapi.ControlPlaneComponent{ 153 ExtraArgs: []kubeadmapi.Arg{ 154 {Name: kubeControllerManagerBindAddressArg, Value: "::"}, 155 }, 156 }, 157 }, 158 expected: "", 159 }, 160 } 161 162 for _, test := range tests { 163 t.Run(test.desc, func(t *testing.T) { 164 actual := GetControllerManagerProbeAddress(test.cfg) 165 if actual != test.expected { 166 t.Errorf("Unexpected result from GetControllerManagerProbeAddress:\n\texpected: %s\n\tactual: %s", test.expected, actual) 167 } 168 }) 169 } 170 } 171 172 func TestGetSchedulerProbeAddress(t *testing.T) { 173 tests := []struct { 174 desc string 175 cfg *kubeadmapi.ClusterConfiguration 176 expected string 177 }{ 178 { 179 desc: "no scheduler extra args leads to 127.0.0.1 being used", 180 cfg: &kubeadmapi.ClusterConfiguration{ 181 Scheduler: kubeadmapi.ControlPlaneComponent{ 182 ExtraArgs: []kubeadmapi.Arg{}, 183 }, 184 }, 185 expected: "127.0.0.1", 186 }, 187 { 188 desc: "setting scheduler extra address arg to something acknowledges it", 189 cfg: &kubeadmapi.ClusterConfiguration{ 190 Scheduler: kubeadmapi.ControlPlaneComponent{ 191 ExtraArgs: []kubeadmapi.Arg{ 192 {Name: kubeSchedulerBindAddressArg, Value: "10.10.10.10"}, 193 }, 194 }, 195 }, 196 expected: "10.10.10.10", 197 }, 198 { 199 desc: "setting scheduler extra ipv6 address arg to something acknowledges it", 200 cfg: &kubeadmapi.ClusterConfiguration{ 201 Scheduler: kubeadmapi.ControlPlaneComponent{ 202 ExtraArgs: []kubeadmapi.Arg{ 203 {Name: kubeSchedulerBindAddressArg, Value: "2001:abcd:bcda::1"}, 204 }, 205 }, 206 }, 207 expected: "2001:abcd:bcda::1", 208 }, 209 { 210 desc: "setting scheduler extra ipv6 address arg to 0.0.0.0 returns empty", 211 cfg: &kubeadmapi.ClusterConfiguration{ 212 Scheduler: kubeadmapi.ControlPlaneComponent{ 213 ExtraArgs: []kubeadmapi.Arg{ 214 {Name: kubeSchedulerBindAddressArg, Value: "0.0.0.0"}, 215 }, 216 }, 217 }, 218 expected: "", 219 }, 220 { 221 desc: "setting scheduler extra ipv6 address arg to :: returns empty", 222 cfg: &kubeadmapi.ClusterConfiguration{ 223 Scheduler: kubeadmapi.ControlPlaneComponent{ 224 ExtraArgs: []kubeadmapi.Arg{ 225 {Name: kubeSchedulerBindAddressArg, Value: "::"}, 226 }, 227 }, 228 }, 229 expected: "", 230 }, 231 } 232 233 for _, test := range tests { 234 t.Run(test.desc, func(t *testing.T) { 235 actual := GetSchedulerProbeAddress(test.cfg) 236 if actual != test.expected { 237 t.Errorf("Unexpected result from GetSchedulerProbeAddress:\n\texpected: %s\n\tactual: %s", test.expected, actual) 238 } 239 }) 240 } 241 } 242 func TestGetEtcdProbeEndpoint(t *testing.T) { 243 var tests = []struct { 244 name string 245 cfg *kubeadmapi.Etcd 246 isIPv6 bool 247 expectedHostname string 248 expectedPort int32 249 expectedScheme v1.URIScheme 250 }{ 251 { 252 name: "etcd probe URL from two URLs", 253 cfg: &kubeadmapi.Etcd{ 254 Local: &kubeadmapi.LocalEtcd{ 255 ExtraArgs: []kubeadmapi.Arg{ 256 {Name: "listen-metrics-urls", Value: "https://1.2.3.4:1234,https://4.3.2.1:2381"}, 257 }, 258 }, 259 }, 260 isIPv6: false, 261 expectedHostname: "1.2.3.4", 262 expectedPort: 1234, 263 expectedScheme: v1.URISchemeHTTPS, 264 }, 265 { 266 name: "etcd probe URL with HTTP scheme", 267 cfg: &kubeadmapi.Etcd{ 268 Local: &kubeadmapi.LocalEtcd{ 269 ExtraArgs: []kubeadmapi.Arg{ 270 {Name: "listen-metrics-urls", Value: "http://1.2.3.4:1234"}, 271 }, 272 }, 273 }, 274 isIPv6: false, 275 expectedHostname: "1.2.3.4", 276 expectedPort: 1234, 277 expectedScheme: v1.URISchemeHTTP, 278 }, 279 { 280 name: "etcd probe URL without scheme should result in defaults", 281 cfg: &kubeadmapi.Etcd{ 282 Local: &kubeadmapi.LocalEtcd{ 283 ExtraArgs: []kubeadmapi.Arg{ 284 {Name: "listen-metrics-urls", Value: "1.2.3.4"}, 285 }, 286 }, 287 }, 288 isIPv6: false, 289 expectedHostname: "127.0.0.1", 290 expectedPort: kubeadmconstants.EtcdMetricsPort, 291 expectedScheme: v1.URISchemeHTTP, 292 }, 293 { 294 name: "etcd probe URL without port", 295 cfg: &kubeadmapi.Etcd{ 296 Local: &kubeadmapi.LocalEtcd{ 297 ExtraArgs: []kubeadmapi.Arg{ 298 {Name: "listen-metrics-urls", Value: "https://1.2.3.4"}, 299 }, 300 }, 301 }, 302 isIPv6: false, 303 expectedHostname: "1.2.3.4", 304 expectedPort: kubeadmconstants.EtcdMetricsPort, 305 expectedScheme: v1.URISchemeHTTPS, 306 }, 307 { 308 name: "etcd probe URL from two IPv6 URLs", 309 cfg: &kubeadmapi.Etcd{ 310 Local: &kubeadmapi.LocalEtcd{ 311 ExtraArgs: []kubeadmapi.Arg{ 312 {Name: "listen-metrics-urls", Value: "https://[2001:abcd:bcda::1]:1234,https://[2001:abcd:bcda::2]:2381"}, 313 }, 314 }, 315 }, 316 isIPv6: true, 317 expectedHostname: "2001:abcd:bcda::1", 318 expectedPort: 1234, 319 expectedScheme: v1.URISchemeHTTPS, 320 }, 321 { 322 name: "etcd probe localhost IPv6 URL with HTTP scheme", 323 cfg: &kubeadmapi.Etcd{ 324 Local: &kubeadmapi.LocalEtcd{ 325 ExtraArgs: []kubeadmapi.Arg{ 326 {Name: "listen-metrics-urls", Value: "http://[::1]:1234"}, 327 }, 328 }, 329 }, 330 isIPv6: true, 331 expectedHostname: "::1", 332 expectedPort: 1234, 333 expectedScheme: v1.URISchemeHTTP, 334 }, 335 { 336 name: "etcd probe IPv6 URL with HTTP scheme", 337 cfg: &kubeadmapi.Etcd{ 338 Local: &kubeadmapi.LocalEtcd{ 339 ExtraArgs: []kubeadmapi.Arg{ 340 {Name: "listen-metrics-urls", Value: "http://[2001:abcd:bcda::1]:1234"}, 341 }, 342 }, 343 }, 344 isIPv6: true, 345 expectedHostname: "2001:abcd:bcda::1", 346 expectedPort: 1234, 347 expectedScheme: v1.URISchemeHTTP, 348 }, 349 { 350 name: "etcd probe IPv6 URL without port", 351 cfg: &kubeadmapi.Etcd{ 352 Local: &kubeadmapi.LocalEtcd{ 353 ExtraArgs: []kubeadmapi.Arg{ 354 {Name: "listen-metrics-urls", Value: "https://[2001:abcd:bcda::1]"}, 355 }, 356 }, 357 }, 358 isIPv6: true, 359 expectedHostname: "2001:abcd:bcda::1", 360 expectedPort: kubeadmconstants.EtcdMetricsPort, 361 expectedScheme: v1.URISchemeHTTPS, 362 }, 363 { 364 name: "etcd probe URL from defaults", 365 cfg: &kubeadmapi.Etcd{ 366 Local: &kubeadmapi.LocalEtcd{}, 367 }, 368 isIPv6: false, 369 expectedHostname: "127.0.0.1", 370 expectedPort: kubeadmconstants.EtcdMetricsPort, 371 expectedScheme: v1.URISchemeHTTP, 372 }, 373 { 374 name: "etcd probe URL from defaults if IPv6", 375 cfg: &kubeadmapi.Etcd{ 376 Local: &kubeadmapi.LocalEtcd{}, 377 }, 378 isIPv6: true, 379 expectedHostname: "::1", 380 expectedPort: kubeadmconstants.EtcdMetricsPort, 381 expectedScheme: v1.URISchemeHTTP, 382 }, 383 } 384 for _, rt := range tests { 385 t.Run(rt.name, func(t *testing.T) { 386 hostname, port, scheme := GetEtcdProbeEndpoint(rt.cfg, rt.isIPv6) 387 if hostname != rt.expectedHostname { 388 t.Errorf("%q test case failed:\n\texpected hostname: %s\n\tgot: %s", 389 rt.name, rt.expectedHostname, hostname) 390 } 391 if port != rt.expectedPort { 392 t.Errorf("%q test case failed:\n\texpected port: %d\n\tgot: %d", 393 rt.name, rt.expectedPort, port) 394 } 395 if scheme != rt.expectedScheme { 396 t.Errorf("%q test case failed:\n\texpected scheme: %v\n\tgot: %v", 397 rt.name, rt.expectedScheme, scheme) 398 } 399 }) 400 } 401 } 402 403 func TestComponentPod(t *testing.T) { 404 // priority value for system-node-critical class 405 priority := int32(2000001000) 406 var tests = []struct { 407 name string 408 expected v1.Pod 409 }{ 410 { 411 name: "foo", 412 expected: v1.Pod{ 413 TypeMeta: metav1.TypeMeta{ 414 APIVersion: "v1", 415 Kind: "Pod", 416 }, 417 ObjectMeta: metav1.ObjectMeta{ 418 Name: "foo", 419 Namespace: "kube-system", 420 Labels: map[string]string{"component": "foo", "tier": "control-plane"}, 421 }, 422 Spec: v1.PodSpec{ 423 SecurityContext: &v1.PodSecurityContext{ 424 SeccompProfile: &v1.SeccompProfile{ 425 Type: v1.SeccompProfileTypeRuntimeDefault, 426 }, 427 }, 428 Containers: []v1.Container{ 429 { 430 Name: "foo", 431 }, 432 }, 433 Priority: &priority, 434 PriorityClassName: "system-node-critical", 435 HostNetwork: true, 436 Volumes: []v1.Volume{}, 437 }, 438 }, 439 }, 440 } 441 442 for _, rt := range tests { 443 t.Run(rt.name, func(t *testing.T) { 444 c := v1.Container{Name: rt.name} 445 actual := ComponentPod(c, map[string]v1.Volume{}, nil) 446 if !reflect.DeepEqual(rt.expected, actual) { 447 t.Errorf( 448 "failed componentPod:\n\texpected: %v\n\t actual: %v", 449 rt.expected, 450 actual, 451 ) 452 } 453 }) 454 } 455 } 456 457 func TestNewVolume(t *testing.T) { 458 hostPathDirectoryOrCreate := v1.HostPathDirectoryOrCreate 459 var tests = []struct { 460 name string 461 path string 462 expected v1.Volume 463 pathType *v1.HostPathType 464 }{ 465 { 466 name: "foo", 467 path: "/etc/foo", 468 expected: v1.Volume{ 469 Name: "foo", 470 VolumeSource: v1.VolumeSource{ 471 HostPath: &v1.HostPathVolumeSource{ 472 Path: "/etc/foo", 473 Type: &hostPathDirectoryOrCreate, 474 }, 475 }, 476 }, 477 pathType: &hostPathDirectoryOrCreate, 478 }, 479 } 480 481 for _, rt := range tests { 482 t.Run(rt.name, func(t *testing.T) { 483 actual := NewVolume(rt.name, rt.path, rt.pathType) 484 if !reflect.DeepEqual(actual, rt.expected) { 485 t.Errorf( 486 "failed newVolume:\n\texpected: %v\n\t actual: %v", 487 rt.expected, 488 actual, 489 ) 490 } 491 }) 492 } 493 } 494 495 func TestNewVolumeMount(t *testing.T) { 496 var tests = []struct { 497 name string 498 path string 499 ro bool 500 expected v1.VolumeMount 501 }{ 502 { 503 name: "foo", 504 path: "/etc/foo", 505 ro: false, 506 expected: v1.VolumeMount{ 507 Name: "foo", 508 MountPath: "/etc/foo", 509 ReadOnly: false, 510 }, 511 }, 512 { 513 name: "bar", 514 path: "/etc/foo/bar", 515 ro: true, 516 expected: v1.VolumeMount{ 517 Name: "bar", 518 MountPath: "/etc/foo/bar", 519 ReadOnly: true, 520 }, 521 }, 522 } 523 524 for _, rt := range tests { 525 t.Run(rt.name, func(t *testing.T) { 526 actual := NewVolumeMount(rt.name, rt.path, rt.ro) 527 if !reflect.DeepEqual(actual, rt.expected) { 528 t.Errorf( 529 "failed newVolumeMount:\n\texpected: %v\n\t actual: %v", 530 rt.expected, 531 actual, 532 ) 533 } 534 }) 535 } 536 } 537 func TestVolumeMapToSlice(t *testing.T) { 538 testVolumes := map[string]v1.Volume{ 539 "foo": { 540 Name: "foo", 541 }, 542 "bar": { 543 Name: "bar", 544 }, 545 } 546 volumeSlice := VolumeMapToSlice(testVolumes) 547 if len(volumeSlice) != 2 { 548 t.Errorf("Expected slice length of 1, got %d", len(volumeSlice)) 549 } 550 if volumeSlice[0].Name != "bar" { 551 t.Errorf("Expected first volume name \"bar\", got %s", volumeSlice[0].Name) 552 } 553 if volumeSlice[1].Name != "foo" { 554 t.Errorf("Expected second volume name \"foo\", got %s", volumeSlice[1].Name) 555 } 556 } 557 558 func TestVolumeMountMapToSlice(t *testing.T) { 559 testVolumeMounts := map[string]v1.VolumeMount{ 560 "foo": { 561 Name: "foo", 562 }, 563 "bar": { 564 Name: "bar", 565 }, 566 } 567 volumeMountSlice := VolumeMountMapToSlice(testVolumeMounts) 568 if len(volumeMountSlice) != 2 { 569 t.Errorf("Expected slice length of 1, got %d", len(volumeMountSlice)) 570 } 571 if volumeMountSlice[0].Name != "bar" { 572 t.Errorf("Expected first volume mount name \"bar\", got %s", volumeMountSlice[0].Name) 573 } 574 if volumeMountSlice[1].Name != "foo" { 575 t.Errorf("Expected second volume name \"foo\", got %s", volumeMountSlice[1].Name) 576 } 577 } 578 579 func TestGetExtraParameters(t *testing.T) { 580 var tests = []struct { 581 name string 582 overrides map[string]string 583 defaults map[string]string 584 expected []string 585 }{ 586 { 587 name: "with admission-control default NamespaceLifecycle", 588 overrides: map[string]string{ 589 "admission-control": "NamespaceLifecycle,LimitRanger", 590 }, 591 defaults: map[string]string{ 592 "admission-control": "NamespaceLifecycle", 593 "allow-privileged": "true", 594 }, 595 expected: []string{ 596 "--admission-control=NamespaceLifecycle,LimitRanger", 597 "--allow-privileged=true", 598 }, 599 }, 600 { 601 name: "without admission-control default", 602 overrides: map[string]string{ 603 "admission-control": "NamespaceLifecycle,LimitRanger", 604 }, 605 defaults: map[string]string{ 606 "allow-privileged": "true", 607 }, 608 expected: []string{ 609 "--admission-control=NamespaceLifecycle,LimitRanger", 610 "--allow-privileged=true", 611 }, 612 }, 613 } 614 615 for _, rt := range tests { 616 t.Run(rt.name, func(t *testing.T) { 617 actual := GetExtraParameters(rt.overrides, rt.defaults) 618 sort.Strings(actual) 619 sort.Strings(rt.expected) 620 if !reflect.DeepEqual(actual, rt.expected) { 621 t.Errorf("failed getExtraParameters:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual) 622 } 623 }) 624 } 625 } 626 627 const ( 628 validPod = ` 629 apiVersion: v1 630 kind: Pod 631 metadata: 632 labels: 633 component: etcd 634 tier: control-plane 635 name: etcd 636 namespace: kube-system 637 spec: 638 containers: 639 - image: gcr.io/google_containers/etcd-amd64:3.1.11 640 status: {} 641 ` 642 validPodWithDifferentFieldsOrder = ` 643 apiVersion: v1 644 kind: Pod 645 metadata: 646 labels: 647 tier: control-plane 648 component: etcd 649 name: etcd 650 namespace: kube-system 651 spec: 652 containers: 653 - image: gcr.io/google_containers/etcd-amd64:3.1.11 654 status: {} 655 ` 656 invalidWithDefaultFields = ` 657 apiVersion: v1 658 kind: Pod 659 metadata: 660 labels: 661 tier: control-plane 662 component: etcd 663 name: etcd 664 namespace: kube-system 665 spec: 666 containers: 667 - image: gcr.io/google_containers/etcd-amd64:3.1.11 668 restartPolicy: "Always" 669 status: {} 670 ` 671 672 validPod2 = ` 673 apiVersion: v1 674 kind: Pod 675 metadata: 676 labels: 677 component: etcd 678 tier: control-plane 679 name: etcd 680 namespace: kube-system 681 spec: 682 containers: 683 - image: gcr.io/google_containers/etcd-amd64:3.1.12 684 status: {} 685 ` 686 687 invalidPod = `---{ broken yaml @@@` 688 ) 689 690 func TestReadStaticPodFromDisk(t *testing.T) { 691 tests := []struct { 692 description string 693 podYaml string 694 expectErr bool 695 writeManifest bool 696 }{ 697 { 698 description: "valid pod is marshaled", 699 podYaml: validPod, 700 writeManifest: true, 701 expectErr: false, 702 }, 703 { 704 description: "invalid pod fails to unmarshal", 705 podYaml: invalidPod, 706 writeManifest: true, 707 expectErr: true, 708 }, 709 { 710 description: "non-existent file returns error", 711 podYaml: ``, 712 writeManifest: false, 713 expectErr: true, 714 }, 715 } 716 717 for _, rt := range tests { 718 t.Run(rt.description, func(t *testing.T) { 719 tmpdir := testutil.SetupTempDir(t) 720 defer os.RemoveAll(tmpdir) 721 722 manifestPath := filepath.Join(tmpdir, "pod.yaml") 723 if rt.writeManifest { 724 err := os.WriteFile(manifestPath, []byte(rt.podYaml), 0644) 725 if err != nil { 726 t.Fatalf("Failed to write pod manifest\n%s\n\tfatal error: %v", rt.description, err) 727 } 728 } 729 730 _, actualErr := ReadStaticPodFromDisk(manifestPath) 731 if (actualErr != nil) != rt.expectErr { 732 t.Errorf( 733 "ReadStaticPodFromDisk failed\n%s\n\texpected error: %t\n\tgot: %t\n\tactual error: %v", 734 rt.description, 735 rt.expectErr, 736 (actualErr != nil), 737 actualErr, 738 ) 739 } 740 }) 741 } 742 } 743 744 func TestManifestFilesAreEqual(t *testing.T) { 745 var tests = []struct { 746 description string 747 podYamls []string 748 expectedResult bool 749 expectedDiff string 750 expectErr bool 751 }{ 752 { 753 description: "manifests are equal", 754 podYamls: []string{validPod, validPod}, 755 expectedResult: true, 756 expectErr: false, 757 }, 758 { 759 description: "manifests are equal, ignore different fields order", 760 podYamls: []string{validPod, validPodWithDifferentFieldsOrder}, 761 expectedResult: true, 762 expectErr: false, 763 }, 764 { 765 description: "manifests are not equal", 766 podYamls: []string{validPod, validPod2}, 767 expectedResult: false, 768 expectErr: false, 769 expectedDiff: `@@ -12 +12 @@ 770 - - image: gcr.io/google_containers/etcd-amd64:3.1.11 771 + - image: gcr.io/google_containers/etcd-amd64:3.1.12 772 `, 773 }, 774 { 775 description: "manifests are not equal for adding new defaults", 776 podYamls: []string{validPod, invalidWithDefaultFields}, 777 expectedResult: false, 778 expectErr: false, 779 expectedDiff: `@@ -14,0 +15 @@ 780 + restartPolicy: Always 781 `, 782 }, 783 { 784 description: "first manifest doesn't exist", 785 podYamls: []string{validPod, ""}, 786 expectedResult: false, 787 expectErr: true, 788 }, 789 { 790 description: "second manifest doesn't exist", 791 podYamls: []string{"", validPod}, 792 expectedResult: false, 793 expectErr: true, 794 }, 795 } 796 797 for _, rt := range tests { 798 t.Run(rt.description, func(t *testing.T) { 799 tmpdir := testutil.SetupTempDir(t) 800 defer os.RemoveAll(tmpdir) 801 802 // write 2 manifests 803 for i := 0; i < 2; i++ { 804 if rt.podYamls[i] != "" { 805 manifestPath := filepath.Join(tmpdir, strconv.Itoa(i)+".yaml") 806 err := os.WriteFile(manifestPath, []byte(rt.podYamls[i]), 0644) 807 if err != nil { 808 t.Fatalf("Failed to write manifest file\n%s\n\tfatal error: %v", rt.description, err) 809 } 810 } 811 } 812 813 // compare them 814 result, diff, actualErr := ManifestFilesAreEqual(filepath.Join(tmpdir, "0.yaml"), filepath.Join(tmpdir, "1.yaml")) 815 if result != rt.expectedResult { 816 t.Errorf( 817 "ManifestFilesAreEqual failed\n%s\nexpected result: %t\nactual result: %t", 818 rt.description, 819 rt.expectedResult, 820 result, 821 ) 822 } 823 if (actualErr != nil) != rt.expectErr { 824 t.Errorf( 825 "ManifestFilesAreEqual failed\n%s\n\texpected error: %t\n\tgot: %t\n\tactual error: %v", 826 rt.description, 827 rt.expectErr, 828 (actualErr != nil), 829 actualErr, 830 ) 831 } 832 if !strings.Contains(diff, rt.expectedDiff) { 833 t.Errorf( 834 "ManifestFilesAreEqual diff doesn't expected\n%s\n\texpected diff: %s\n\tactual diff: %s", 835 rt.description, 836 rt.expectedDiff, 837 diff, 838 ) 839 } 840 }) 841 } 842 } 843 844 func TestPatchStaticPod(t *testing.T) { 845 type file struct { 846 name string 847 data string 848 } 849 850 tests := []struct { 851 name string 852 files []*file 853 pod *v1.Pod 854 expectedPod *v1.Pod 855 expectedError bool 856 }{ 857 { 858 name: "valid: patch a kube-apiserver target using a couple of ordered patches", 859 pod: &v1.Pod{ 860 ObjectMeta: metav1.ObjectMeta{ 861 Name: "kube-apiserver", 862 Namespace: "foo", 863 }, 864 }, 865 expectedPod: &v1.Pod{ 866 ObjectMeta: metav1.ObjectMeta{ 867 Name: "kube-apiserver", 868 Namespace: "bar2", 869 }, 870 }, 871 files: []*file{ 872 { 873 name: "kube-apiserver1+merge.json", 874 data: `{"metadata":{"namespace":"bar2"}}`, 875 }, 876 { 877 name: "kube-apiserver0+json.json", 878 data: `[{"op": "replace", "path": "/metadata/namespace", "value": "bar1"}]`, 879 }, 880 }, 881 }, 882 { 883 name: "invalid: unknown patch target name", 884 pod: &v1.Pod{ 885 ObjectMeta: metav1.ObjectMeta{ 886 Name: "foo", 887 Namespace: "bar", 888 }, 889 }, 890 expectedError: true, 891 }, 892 } 893 894 for _, tc := range tests { 895 t.Run(tc.name, func(t *testing.T) { 896 tempDir, err := os.MkdirTemp("", "patch-files") 897 if err != nil { 898 t.Fatal(err) 899 } 900 defer os.RemoveAll(tempDir) 901 902 for _, file := range tc.files { 903 filePath := filepath.Join(tempDir, file.name) 904 err := os.WriteFile(filePath, []byte(file.data), 0644) 905 if err != nil { 906 t.Fatalf("could not write temporary file %q", filePath) 907 } 908 } 909 910 pod, err := PatchStaticPod(tc.pod, tempDir, io.Discard) 911 if (err != nil) != tc.expectedError { 912 t.Fatalf("expected error: %v, got: %v, error: %v", tc.expectedError, (err != nil), err) 913 } 914 if err != nil { 915 return 916 } 917 918 if tc.expectedPod.String() != pod.String() { 919 t.Fatalf("expected object:\n%s\ngot:\n%s", tc.expectedPod.String(), pod.String()) 920 } 921 }) 922 } 923 }