github.com/spotahome/redis-operator@v1.2.4/operator/redisfailover/service/generator_test.go (about) 1 package service_test 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/stretchr/testify/assert" 8 "github.com/stretchr/testify/mock" 9 appsv1 "k8s.io/api/apps/v1" 10 corev1 "k8s.io/api/core/v1" 11 "k8s.io/apimachinery/pkg/api/resource" 12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 "k8s.io/apimachinery/pkg/util/intstr" 14 15 redisfailoverv1 "github.com/spotahome/redis-operator/api/redisfailover/v1" 16 "github.com/spotahome/redis-operator/log" 17 "github.com/spotahome/redis-operator/metrics" 18 mK8SService "github.com/spotahome/redis-operator/mocks/service/k8s" 19 rfservice "github.com/spotahome/redis-operator/operator/redisfailover/service" 20 ) 21 22 func TestRedisStatefulSetStorageGeneration(t *testing.T) { 23 configMapName := rfservice.GetRedisName(generateRF()) 24 shutdownConfigMapName := rfservice.GetRedisShutdownConfigMapName(generateRF()) 25 readinesConfigMapName := rfservice.GetRedisReadinessName(generateRF()) 26 executeMode := int32(0744) 27 tests := []struct { 28 name string 29 ownerRefs []metav1.OwnerReference 30 expectedSS appsv1.StatefulSet 31 rfRedisStorage redisfailoverv1.RedisStorage 32 }{ 33 { 34 name: "Default values", 35 expectedSS: appsv1.StatefulSet{ 36 Spec: appsv1.StatefulSetSpec{ 37 Template: corev1.PodTemplateSpec{ 38 Spec: corev1.PodSpec{ 39 Containers: []corev1.Container{ 40 { 41 VolumeMounts: []corev1.VolumeMount{ 42 { 43 Name: "redis-config", 44 MountPath: "/redis", 45 }, 46 { 47 Name: "redis-shutdown-config", 48 MountPath: "/redis-shutdown", 49 }, 50 { 51 Name: "redis-readiness-config", 52 MountPath: "/redis-readiness", 53 }, 54 { 55 Name: "redis-data", 56 MountPath: "/data", 57 }, 58 }, 59 }, 60 }, 61 Volumes: []corev1.Volume{ 62 { 63 Name: "redis-config", 64 VolumeSource: corev1.VolumeSource{ 65 ConfigMap: &corev1.ConfigMapVolumeSource{ 66 LocalObjectReference: corev1.LocalObjectReference{ 67 Name: configMapName, 68 }, 69 }, 70 }, 71 }, 72 { 73 Name: "redis-shutdown-config", 74 VolumeSource: corev1.VolumeSource{ 75 ConfigMap: &corev1.ConfigMapVolumeSource{ 76 LocalObjectReference: corev1.LocalObjectReference{ 77 Name: shutdownConfigMapName, 78 }, 79 DefaultMode: &executeMode, 80 }, 81 }, 82 }, 83 { 84 Name: "redis-readiness-config", 85 VolumeSource: corev1.VolumeSource{ 86 ConfigMap: &corev1.ConfigMapVolumeSource{ 87 LocalObjectReference: corev1.LocalObjectReference{ 88 Name: readinesConfigMapName, 89 }, 90 DefaultMode: &executeMode, 91 }, 92 }, 93 }, 94 { 95 Name: "redis-data", 96 VolumeSource: corev1.VolumeSource{ 97 EmptyDir: &corev1.EmptyDirVolumeSource{}, 98 }, 99 }, 100 }, 101 }, 102 }, 103 }, 104 }, 105 rfRedisStorage: redisfailoverv1.RedisStorage{}, 106 }, 107 { 108 name: "Defined an emptydir with storage on memory", 109 expectedSS: appsv1.StatefulSet{ 110 Spec: appsv1.StatefulSetSpec{ 111 Template: corev1.PodTemplateSpec{ 112 Spec: corev1.PodSpec{ 113 Containers: []corev1.Container{ 114 { 115 VolumeMounts: []corev1.VolumeMount{ 116 { 117 Name: "redis-config", 118 MountPath: "/redis", 119 }, 120 { 121 Name: "redis-shutdown-config", 122 MountPath: "/redis-shutdown", 123 }, 124 { 125 Name: "redis-readiness-config", 126 MountPath: "/redis-readiness", 127 }, 128 { 129 Name: "redis-data", 130 MountPath: "/data", 131 }, 132 }, 133 }, 134 }, 135 Volumes: []corev1.Volume{ 136 { 137 Name: "redis-config", 138 VolumeSource: corev1.VolumeSource{ 139 ConfigMap: &corev1.ConfigMapVolumeSource{ 140 LocalObjectReference: corev1.LocalObjectReference{ 141 Name: configMapName, 142 }, 143 }, 144 }, 145 }, 146 { 147 Name: "redis-shutdown-config", 148 VolumeSource: corev1.VolumeSource{ 149 ConfigMap: &corev1.ConfigMapVolumeSource{ 150 LocalObjectReference: corev1.LocalObjectReference{ 151 Name: shutdownConfigMapName, 152 }, 153 DefaultMode: &executeMode, 154 }, 155 }, 156 }, 157 { 158 Name: "redis-readiness-config", 159 VolumeSource: corev1.VolumeSource{ 160 ConfigMap: &corev1.ConfigMapVolumeSource{ 161 LocalObjectReference: corev1.LocalObjectReference{ 162 Name: readinesConfigMapName, 163 }, 164 DefaultMode: &executeMode, 165 }, 166 }, 167 }, 168 { 169 Name: "redis-data", 170 VolumeSource: corev1.VolumeSource{ 171 EmptyDir: &corev1.EmptyDirVolumeSource{ 172 Medium: corev1.StorageMediumMemory, 173 }, 174 }, 175 }, 176 }, 177 }, 178 }, 179 }, 180 }, 181 rfRedisStorage: redisfailoverv1.RedisStorage{ 182 EmptyDir: &corev1.EmptyDirVolumeSource{ 183 Medium: corev1.StorageMediumMemory, 184 }, 185 }, 186 }, 187 { 188 name: "Defined an persistentvolumeclaim", 189 expectedSS: appsv1.StatefulSet{ 190 Spec: appsv1.StatefulSetSpec{ 191 Template: corev1.PodTemplateSpec{ 192 Spec: corev1.PodSpec{ 193 Containers: []corev1.Container{ 194 { 195 VolumeMounts: []corev1.VolumeMount{ 196 { 197 Name: "redis-config", 198 MountPath: "/redis", 199 }, 200 { 201 Name: "redis-shutdown-config", 202 MountPath: "/redis-shutdown", 203 }, 204 { 205 Name: "redis-readiness-config", 206 MountPath: "/redis-readiness", 207 }, 208 { 209 Name: "pvc-data", 210 MountPath: "/data", 211 }, 212 }, 213 }, 214 }, 215 Volumes: []corev1.Volume{ 216 { 217 Name: "redis-config", 218 VolumeSource: corev1.VolumeSource{ 219 ConfigMap: &corev1.ConfigMapVolumeSource{ 220 LocalObjectReference: corev1.LocalObjectReference{ 221 Name: configMapName, 222 }, 223 }, 224 }, 225 }, 226 { 227 Name: "redis-shutdown-config", 228 VolumeSource: corev1.VolumeSource{ 229 ConfigMap: &corev1.ConfigMapVolumeSource{ 230 LocalObjectReference: corev1.LocalObjectReference{ 231 Name: shutdownConfigMapName, 232 }, 233 DefaultMode: &executeMode, 234 }, 235 }, 236 }, 237 { 238 Name: "redis-readiness-config", 239 VolumeSource: corev1.VolumeSource{ 240 ConfigMap: &corev1.ConfigMapVolumeSource{ 241 LocalObjectReference: corev1.LocalObjectReference{ 242 Name: readinesConfigMapName, 243 }, 244 DefaultMode: &executeMode, 245 }, 246 }, 247 }, 248 }, 249 }, 250 }, 251 VolumeClaimTemplates: []corev1.PersistentVolumeClaim{ 252 { 253 TypeMeta: metav1.TypeMeta{ 254 Kind: "PersistentVolumeClaim", 255 APIVersion: "v1", 256 }, 257 ObjectMeta: metav1.ObjectMeta{ 258 Name: "pvc-data", 259 }, 260 Spec: corev1.PersistentVolumeClaimSpec{ 261 AccessModes: []corev1.PersistentVolumeAccessMode{ 262 "ReadWriteOnce", 263 }, 264 Resources: corev1.ResourceRequirements{ 265 Requests: corev1.ResourceList{ 266 corev1.ResourceStorage: resource.MustParse("1Gi"), 267 }, 268 }, 269 }, 270 }, 271 }, 272 }, 273 }, 274 rfRedisStorage: redisfailoverv1.RedisStorage{ 275 PersistentVolumeClaim: &redisfailoverv1.EmbeddedPersistentVolumeClaim{ 276 EmbeddedObjectMetadata: redisfailoverv1.EmbeddedObjectMetadata{ 277 Name: "pvc-data", 278 }, 279 Spec: corev1.PersistentVolumeClaimSpec{ 280 AccessModes: []corev1.PersistentVolumeAccessMode{ 281 "ReadWriteOnce", 282 }, 283 Resources: corev1.ResourceRequirements{ 284 Requests: corev1.ResourceList{ 285 corev1.ResourceStorage: resource.MustParse("1Gi"), 286 }, 287 }, 288 }, 289 }, 290 }, 291 }, 292 { 293 name: "Defined an persistentvolumeclaim with ownerRefs", 294 ownerRefs: []metav1.OwnerReference{ 295 { 296 Name: "testing", 297 }, 298 }, 299 expectedSS: appsv1.StatefulSet{ 300 Spec: appsv1.StatefulSetSpec{ 301 Template: corev1.PodTemplateSpec{ 302 Spec: corev1.PodSpec{ 303 Containers: []corev1.Container{ 304 { 305 VolumeMounts: []corev1.VolumeMount{ 306 { 307 Name: "redis-config", 308 MountPath: "/redis", 309 }, 310 { 311 Name: "redis-shutdown-config", 312 MountPath: "/redis-shutdown", 313 }, 314 { 315 Name: "redis-readiness-config", 316 MountPath: "/redis-readiness", 317 }, 318 { 319 Name: "pvc-data", 320 MountPath: "/data", 321 }, 322 }, 323 }, 324 }, 325 Volumes: []corev1.Volume{ 326 { 327 Name: "redis-config", 328 VolumeSource: corev1.VolumeSource{ 329 ConfigMap: &corev1.ConfigMapVolumeSource{ 330 LocalObjectReference: corev1.LocalObjectReference{ 331 Name: configMapName, 332 }, 333 }, 334 }, 335 }, 336 { 337 Name: "redis-shutdown-config", 338 VolumeSource: corev1.VolumeSource{ 339 ConfigMap: &corev1.ConfigMapVolumeSource{ 340 LocalObjectReference: corev1.LocalObjectReference{ 341 Name: shutdownConfigMapName, 342 }, 343 DefaultMode: &executeMode, 344 }, 345 }, 346 }, 347 { 348 Name: "redis-readiness-config", 349 VolumeSource: corev1.VolumeSource{ 350 ConfigMap: &corev1.ConfigMapVolumeSource{ 351 LocalObjectReference: corev1.LocalObjectReference{ 352 Name: readinesConfigMapName, 353 }, 354 DefaultMode: &executeMode, 355 }, 356 }, 357 }, 358 }, 359 }, 360 }, 361 VolumeClaimTemplates: []corev1.PersistentVolumeClaim{ 362 { 363 TypeMeta: metav1.TypeMeta{ 364 Kind: "PersistentVolumeClaim", 365 APIVersion: "v1", 366 }, 367 ObjectMeta: metav1.ObjectMeta{ 368 Name: "pvc-data", 369 OwnerReferences: []metav1.OwnerReference{ 370 { 371 Name: "testing", 372 }, 373 }, 374 }, 375 Spec: corev1.PersistentVolumeClaimSpec{ 376 AccessModes: []corev1.PersistentVolumeAccessMode{ 377 "ReadWriteOnce", 378 }, 379 Resources: corev1.ResourceRequirements{ 380 Requests: corev1.ResourceList{ 381 corev1.ResourceStorage: resource.MustParse("1Gi"), 382 }, 383 }, 384 }, 385 }, 386 }, 387 }, 388 }, 389 rfRedisStorage: redisfailoverv1.RedisStorage{ 390 PersistentVolumeClaim: &redisfailoverv1.EmbeddedPersistentVolumeClaim{ 391 EmbeddedObjectMetadata: redisfailoverv1.EmbeddedObjectMetadata{ 392 Name: "pvc-data", 393 }, 394 Spec: corev1.PersistentVolumeClaimSpec{ 395 AccessModes: []corev1.PersistentVolumeAccessMode{ 396 "ReadWriteOnce", 397 }, 398 Resources: corev1.ResourceRequirements{ 399 Requests: corev1.ResourceList{ 400 corev1.ResourceStorage: resource.MustParse("1Gi"), 401 }, 402 }, 403 }, 404 }, 405 }, 406 }, 407 { 408 name: "Defined an persistentvolumeclaim with ownerRefs keeping the pvc", 409 ownerRefs: []metav1.OwnerReference{ 410 { 411 Name: "testing", 412 }, 413 }, 414 expectedSS: appsv1.StatefulSet{ 415 Spec: appsv1.StatefulSetSpec{ 416 Template: corev1.PodTemplateSpec{ 417 Spec: corev1.PodSpec{ 418 Containers: []corev1.Container{ 419 { 420 VolumeMounts: []corev1.VolumeMount{ 421 { 422 Name: "redis-config", 423 MountPath: "/redis", 424 }, 425 { 426 Name: "redis-shutdown-config", 427 MountPath: "/redis-shutdown", 428 }, 429 { 430 Name: "redis-readiness-config", 431 MountPath: "/redis-readiness", 432 }, 433 { 434 Name: "pvc-data", 435 MountPath: "/data", 436 }, 437 }, 438 }, 439 }, 440 Volumes: []corev1.Volume{ 441 { 442 Name: "redis-config", 443 VolumeSource: corev1.VolumeSource{ 444 ConfigMap: &corev1.ConfigMapVolumeSource{ 445 LocalObjectReference: corev1.LocalObjectReference{ 446 Name: configMapName, 447 }, 448 }, 449 }, 450 }, 451 { 452 Name: "redis-shutdown-config", 453 VolumeSource: corev1.VolumeSource{ 454 ConfigMap: &corev1.ConfigMapVolumeSource{ 455 LocalObjectReference: corev1.LocalObjectReference{ 456 Name: shutdownConfigMapName, 457 }, 458 DefaultMode: &executeMode, 459 }, 460 }, 461 }, 462 { 463 Name: "redis-readiness-config", 464 VolumeSource: corev1.VolumeSource{ 465 ConfigMap: &corev1.ConfigMapVolumeSource{ 466 LocalObjectReference: corev1.LocalObjectReference{ 467 Name: readinesConfigMapName, 468 }, 469 DefaultMode: &executeMode, 470 }, 471 }, 472 }, 473 }, 474 }, 475 }, 476 VolumeClaimTemplates: []corev1.PersistentVolumeClaim{ 477 { 478 TypeMeta: metav1.TypeMeta{ 479 Kind: "PersistentVolumeClaim", 480 APIVersion: "v1", 481 }, 482 ObjectMeta: metav1.ObjectMeta{ 483 Name: "pvc-data", 484 }, 485 Spec: corev1.PersistentVolumeClaimSpec{ 486 AccessModes: []corev1.PersistentVolumeAccessMode{ 487 "ReadWriteOnce", 488 }, 489 Resources: corev1.ResourceRequirements{ 490 Requests: corev1.ResourceList{ 491 corev1.ResourceStorage: resource.MustParse("1Gi"), 492 }, 493 }, 494 }, 495 }, 496 }, 497 }, 498 }, 499 rfRedisStorage: redisfailoverv1.RedisStorage{ 500 KeepAfterDeletion: true, 501 PersistentVolumeClaim: &redisfailoverv1.EmbeddedPersistentVolumeClaim{ 502 EmbeddedObjectMetadata: redisfailoverv1.EmbeddedObjectMetadata{ 503 Name: "pvc-data", 504 }, 505 Spec: corev1.PersistentVolumeClaimSpec{ 506 AccessModes: []corev1.PersistentVolumeAccessMode{ 507 "ReadWriteOnce", 508 }, 509 Resources: corev1.ResourceRequirements{ 510 Requests: corev1.ResourceList{ 511 corev1.ResourceStorage: resource.MustParse("1Gi"), 512 }, 513 }, 514 }, 515 }, 516 }, 517 }, 518 } 519 520 for _, test := range tests { 521 assert := assert.New(t) 522 523 // Generate a default RedisFailover and attaching the required storage 524 rf := generateRF() 525 rf.Spec.Redis.Storage = test.rfRedisStorage 526 527 generatedStatefulSet := appsv1.StatefulSet{} 528 529 ms := &mK8SService.Services{} 530 ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil) 531 ms.On("CreateOrUpdateStatefulSet", namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 532 ss := args.Get(1).(*appsv1.StatefulSet) 533 generatedStatefulSet = *ss 534 }).Return(nil) 535 536 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 537 err := client.EnsureRedisStatefulset(rf, nil, test.ownerRefs) 538 539 // Check that the storage-related fields are as spected 540 assert.Equal(test.expectedSS.Spec.Template.Spec.Volumes, generatedStatefulSet.Spec.Template.Spec.Volumes) 541 assert.Equal(test.expectedSS.Spec.Template.Spec.Containers[0].VolumeMounts, generatedStatefulSet.Spec.Template.Spec.Containers[0].VolumeMounts) 542 assert.Equal(test.expectedSS.Spec.VolumeClaimTemplates, generatedStatefulSet.Spec.VolumeClaimTemplates) 543 assert.NoError(err) 544 } 545 } 546 547 func TestRedisStatefulSetCommands(t *testing.T) { 548 tests := []struct { 549 name string 550 givenCommands []string 551 expectedCommands []string 552 }{ 553 { 554 name: "Default values", 555 givenCommands: []string{}, 556 expectedCommands: []string{ 557 "redis-server", 558 "/redis/redis.conf", 559 }, 560 }, 561 { 562 name: "Given commands should be used in redis container", 563 givenCommands: []string{ 564 "test", 565 "command", 566 }, 567 expectedCommands: []string{ 568 "test", 569 "command", 570 }, 571 }, 572 } 573 574 for _, test := range tests { 575 assert := assert.New(t) 576 577 // Generate a default RedisFailover and attaching the required storage 578 rf := generateRF() 579 rf.Spec.Redis.Command = test.givenCommands 580 581 gotCommands := []string{} 582 583 ms := &mK8SService.Services{} 584 ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil) 585 ms.On("CreateOrUpdateStatefulSet", namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 586 ss := args.Get(1).(*appsv1.StatefulSet) 587 gotCommands = ss.Spec.Template.Spec.Containers[0].Command 588 }).Return(nil) 589 590 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 591 err := client.EnsureRedisStatefulset(rf, nil, []metav1.OwnerReference{}) 592 593 assert.Equal(test.expectedCommands, gotCommands) 594 assert.NoError(err) 595 } 596 } 597 598 func TestSentinelDeploymentCommands(t *testing.T) { 599 tests := []struct { 600 name string 601 givenCommands []string 602 expectedCommands []string 603 }{ 604 { 605 name: "Default values", 606 givenCommands: []string{}, 607 expectedCommands: []string{ 608 "redis-server", 609 "/redis/sentinel.conf", 610 "--sentinel", 611 }, 612 }, 613 { 614 name: "Given commands should be used in sentinel container", 615 givenCommands: []string{ 616 "test", 617 "command", 618 }, 619 expectedCommands: []string{ 620 "test", 621 "command", 622 }, 623 }, 624 } 625 626 for _, test := range tests { 627 assert := assert.New(t) 628 629 // Generate a default RedisFailover and attaching the required storage 630 rf := generateRF() 631 rf.Spec.Sentinel.Command = test.givenCommands 632 633 gotCommands := []string{} 634 635 ms := &mK8SService.Services{} 636 ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil) 637 ms.On("CreateOrUpdateDeployment", namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 638 d := args.Get(1).(*appsv1.Deployment) 639 gotCommands = d.Spec.Template.Spec.Containers[0].Command 640 }).Return(nil) 641 642 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 643 err := client.EnsureSentinelDeployment(rf, nil, []metav1.OwnerReference{}) 644 645 assert.Equal(test.expectedCommands, gotCommands) 646 assert.NoError(err) 647 } 648 } 649 650 func TestRedisStatefulSetPodAnnotations(t *testing.T) { 651 tests := []struct { 652 name string 653 givenPodAnnotations map[string]string 654 expectedPodAnnotations map[string]string 655 }{ 656 { 657 name: "PodAnnotations was not defined", 658 givenPodAnnotations: nil, 659 expectedPodAnnotations: nil, 660 }, 661 { 662 name: "PodAnnotations is defined", 663 givenPodAnnotations: map[string]string{ 664 "some": "annotation", 665 "path/to/annotation": "here", 666 }, 667 expectedPodAnnotations: map[string]string{ 668 "some": "annotation", 669 "path/to/annotation": "here", 670 }, 671 }, 672 } 673 674 for _, test := range tests { 675 assert := assert.New(t) 676 677 // Generate a default RedisFailover and attaching the required annotations 678 rf := generateRF() 679 rf.Spec.Redis.PodAnnotations = test.givenPodAnnotations 680 681 gotPodAnnotations := map[string]string{} 682 683 ms := &mK8SService.Services{} 684 ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil) 685 ms.On("CreateOrUpdateStatefulSet", namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 686 ss := args.Get(1).(*appsv1.StatefulSet) 687 gotPodAnnotations = ss.Spec.Template.ObjectMeta.Annotations 688 }).Return(nil) 689 690 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 691 err := client.EnsureRedisStatefulset(rf, nil, []metav1.OwnerReference{}) 692 693 assert.Equal(test.expectedPodAnnotations, gotPodAnnotations) 694 assert.NoError(err) 695 } 696 } 697 698 func TestSentinelDeploymentPodAnnotations(t *testing.T) { 699 tests := []struct { 700 name string 701 givenPodAnnotations map[string]string 702 expectedPodAnnotations map[string]string 703 }{ 704 { 705 name: "PodAnnotations was not defined", 706 givenPodAnnotations: nil, 707 expectedPodAnnotations: nil, 708 }, 709 { 710 name: "PodAnnotations is defined", 711 givenPodAnnotations: map[string]string{ 712 "some": "annotation", 713 "path/to/annotation": "here", 714 }, 715 expectedPodAnnotations: map[string]string{ 716 "some": "annotation", 717 "path/to/annotation": "here", 718 }, 719 }, 720 } 721 722 for _, test := range tests { 723 assert := assert.New(t) 724 725 // Generate a default RedisFailover and attaching the required annotations 726 rf := generateRF() 727 rf.Spec.Sentinel.PodAnnotations = test.givenPodAnnotations 728 729 gotPodAnnotations := map[string]string{} 730 731 ms := &mK8SService.Services{} 732 ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil) 733 ms.On("CreateOrUpdateDeployment", namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 734 d := args.Get(1).(*appsv1.Deployment) 735 gotPodAnnotations = d.Spec.Template.ObjectMeta.Annotations 736 }).Return(nil) 737 738 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 739 err := client.EnsureSentinelDeployment(rf, nil, []metav1.OwnerReference{}) 740 741 assert.Equal(test.expectedPodAnnotations, gotPodAnnotations) 742 assert.NoError(err) 743 } 744 } 745 746 func TestRedisStatefulSetServiceAccountName(t *testing.T) { 747 tests := []struct { 748 name string 749 givenServiceAccountName string 750 expectedServiceAccountName string 751 }{ 752 { 753 name: "ServiceAccountName was not defined", 754 givenServiceAccountName: "", 755 expectedServiceAccountName: "", 756 }, 757 { 758 name: "ServiceAccountName is defined", 759 givenServiceAccountName: "redis-sa", 760 expectedServiceAccountName: "redis-sa", 761 }, 762 } 763 764 for _, test := range tests { 765 assert := assert.New(t) 766 767 // Generate a default RedisFailover and attaching the required Service Account 768 rf := generateRF() 769 rf.Spec.Redis.ServiceAccountName = test.givenServiceAccountName 770 771 gotServiceAccountName := "" 772 773 ms := &mK8SService.Services{} 774 ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil) 775 ms.On("CreateOrUpdateStatefulSet", namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 776 ss := args.Get(1).(*appsv1.StatefulSet) 777 gotServiceAccountName = ss.Spec.Template.Spec.ServiceAccountName 778 }).Return(nil) 779 780 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 781 err := client.EnsureRedisStatefulset(rf, nil, []metav1.OwnerReference{}) 782 783 assert.Equal(test.expectedServiceAccountName, gotServiceAccountName) 784 assert.NoError(err) 785 } 786 } 787 788 func TestSentinelDeploymentServiceAccountName(t *testing.T) { 789 tests := []struct { 790 name string 791 givenServiceAccountName string 792 expectedServiceAccountName string 793 }{ 794 { 795 name: "ServiceAccountName was not defined", 796 givenServiceAccountName: "", 797 expectedServiceAccountName: "", 798 }, 799 { 800 name: "ServiceAccountName is defined", 801 givenServiceAccountName: "sentinel-sa", 802 expectedServiceAccountName: "sentinel-sa", 803 }, 804 } 805 806 for _, test := range tests { 807 assert := assert.New(t) 808 809 // Generate a default RedisFailover and attaching the required Service Account 810 rf := generateRF() 811 rf.Spec.Sentinel.ServiceAccountName = test.givenServiceAccountName 812 813 gotServiceAccountName := "" 814 815 ms := &mK8SService.Services{} 816 ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil) 817 ms.On("CreateOrUpdateDeployment", namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 818 d := args.Get(1).(*appsv1.Deployment) 819 gotServiceAccountName = d.Spec.Template.Spec.ServiceAccountName 820 }).Return(nil) 821 822 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 823 err := client.EnsureSentinelDeployment(rf, nil, []metav1.OwnerReference{}) 824 825 assert.Equal(test.expectedServiceAccountName, gotServiceAccountName) 826 assert.NoError(err) 827 } 828 } 829 830 func TestSentinelService(t *testing.T) { 831 tests := []struct { 832 name string 833 rfName string 834 rfNamespace string 835 rfLabels map[string]string 836 rfAnnotations map[string]string 837 expectedService corev1.Service 838 }{ 839 { 840 name: "with defaults", 841 expectedService: corev1.Service{ 842 ObjectMeta: metav1.ObjectMeta{ 843 Name: sentinelName, 844 Namespace: namespace, 845 Labels: map[string]string{ 846 "app.kubernetes.io/component": "sentinel", 847 "app.kubernetes.io/name": name, 848 "app.kubernetes.io/part-of": "redis-failover", 849 }, 850 OwnerReferences: []metav1.OwnerReference{ 851 { 852 Name: "testing", 853 }, 854 }, 855 }, 856 Spec: corev1.ServiceSpec{ 857 Selector: map[string]string{ 858 "app.kubernetes.io/component": "sentinel", 859 "app.kubernetes.io/name": name, 860 "app.kubernetes.io/part-of": "redis-failover", 861 }, 862 Ports: []corev1.ServicePort{ 863 { 864 Name: "sentinel", 865 Port: 26379, 866 TargetPort: intstr.FromInt(26379), 867 Protocol: "TCP", 868 }, 869 }, 870 }, 871 }, 872 }, 873 { 874 name: "with Name provided", 875 rfName: "custom-name", 876 expectedService: corev1.Service{ 877 ObjectMeta: metav1.ObjectMeta{ 878 Name: "rfs-custom-name", 879 Namespace: namespace, 880 Labels: map[string]string{ 881 "app.kubernetes.io/component": "sentinel", 882 "app.kubernetes.io/name": "custom-name", 883 "app.kubernetes.io/part-of": "redis-failover", 884 }, 885 OwnerReferences: []metav1.OwnerReference{ 886 { 887 Name: "testing", 888 }, 889 }, 890 }, 891 Spec: corev1.ServiceSpec{ 892 Selector: map[string]string{ 893 "app.kubernetes.io/component": "sentinel", 894 "app.kubernetes.io/name": "custom-name", 895 "app.kubernetes.io/part-of": "redis-failover", 896 }, 897 Ports: []corev1.ServicePort{ 898 { 899 Name: "sentinel", 900 Port: 26379, 901 TargetPort: intstr.FromInt(26379), 902 Protocol: "TCP", 903 }, 904 }, 905 }, 906 }, 907 }, 908 { 909 name: "with Namespace provided", 910 rfNamespace: "custom-namespace", 911 expectedService: corev1.Service{ 912 ObjectMeta: metav1.ObjectMeta{ 913 Name: sentinelName, 914 Namespace: "custom-namespace", 915 Labels: map[string]string{ 916 "app.kubernetes.io/component": "sentinel", 917 "app.kubernetes.io/name": name, 918 "app.kubernetes.io/part-of": "redis-failover", 919 }, 920 OwnerReferences: []metav1.OwnerReference{ 921 { 922 Name: "testing", 923 }, 924 }, 925 }, 926 Spec: corev1.ServiceSpec{ 927 Selector: map[string]string{ 928 "app.kubernetes.io/component": "sentinel", 929 "app.kubernetes.io/name": name, 930 "app.kubernetes.io/part-of": "redis-failover", 931 }, 932 Ports: []corev1.ServicePort{ 933 { 934 Name: "sentinel", 935 Port: 26379, 936 TargetPort: intstr.FromInt(26379), 937 Protocol: "TCP", 938 }, 939 }, 940 }, 941 }, 942 }, 943 { 944 name: "with Labels provided", 945 rfLabels: map[string]string{"some": "label"}, 946 expectedService: corev1.Service{ 947 ObjectMeta: metav1.ObjectMeta{ 948 Name: sentinelName, 949 Namespace: namespace, 950 Labels: map[string]string{ 951 "app.kubernetes.io/component": "sentinel", 952 "app.kubernetes.io/name": name, 953 "app.kubernetes.io/part-of": "redis-failover", 954 "some": "label", 955 }, 956 OwnerReferences: []metav1.OwnerReference{ 957 { 958 Name: "testing", 959 }, 960 }, 961 }, 962 Spec: corev1.ServiceSpec{ 963 Selector: map[string]string{ 964 "app.kubernetes.io/component": "sentinel", 965 "app.kubernetes.io/name": name, 966 "app.kubernetes.io/part-of": "redis-failover", 967 }, 968 Ports: []corev1.ServicePort{ 969 { 970 Name: "sentinel", 971 Port: 26379, 972 TargetPort: intstr.FromInt(26379), 973 Protocol: "TCP", 974 }, 975 }, 976 }, 977 }, 978 }, 979 { 980 name: "with Annotations provided", 981 rfAnnotations: map[string]string{"some": "annotation"}, 982 expectedService: corev1.Service{ 983 ObjectMeta: metav1.ObjectMeta{ 984 Name: sentinelName, 985 Namespace: namespace, 986 Labels: map[string]string{ 987 "app.kubernetes.io/component": "sentinel", 988 "app.kubernetes.io/name": name, 989 "app.kubernetes.io/part-of": "redis-failover", 990 }, 991 Annotations: map[string]string{"some": "annotation"}, 992 OwnerReferences: []metav1.OwnerReference{ 993 { 994 Name: "testing", 995 }, 996 }, 997 }, 998 Spec: corev1.ServiceSpec{ 999 Selector: map[string]string{ 1000 "app.kubernetes.io/component": "sentinel", 1001 "app.kubernetes.io/name": name, 1002 "app.kubernetes.io/part-of": "redis-failover", 1003 }, 1004 Ports: []corev1.ServicePort{ 1005 { 1006 Name: "sentinel", 1007 Port: 26379, 1008 TargetPort: intstr.FromInt(26379), 1009 Protocol: "TCP", 1010 }, 1011 }, 1012 }, 1013 }, 1014 }, 1015 } 1016 1017 for _, test := range tests { 1018 t.Run(test.name, func(t *testing.T) { 1019 assert := assert.New(t) 1020 1021 // Generate a default RedisFailover and attaching the required annotations 1022 rf := generateRF() 1023 if test.rfName != "" { 1024 rf.Name = test.rfName 1025 } 1026 if test.rfNamespace != "" { 1027 rf.Namespace = test.rfNamespace 1028 } 1029 rf.Spec.Sentinel.ServiceAnnotations = test.rfAnnotations 1030 1031 generatedService := corev1.Service{} 1032 1033 ms := &mK8SService.Services{} 1034 ms.On("CreateOrUpdateService", rf.Namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 1035 s := args.Get(1).(*corev1.Service) 1036 generatedService = *s 1037 }).Return(nil) 1038 1039 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 1040 err := client.EnsureSentinelService(rf, test.rfLabels, []metav1.OwnerReference{{Name: "testing"}}) 1041 1042 assert.Equal(test.expectedService, generatedService) 1043 assert.NoError(err) 1044 }) 1045 } 1046 } 1047 1048 func TestRedisService(t *testing.T) { 1049 tests := []struct { 1050 name string 1051 rfName string 1052 rfNamespace string 1053 rfLabels map[string]string 1054 rfAnnotations map[string]string 1055 expectedService corev1.Service 1056 }{ 1057 { 1058 name: "with defaults", 1059 expectedService: corev1.Service{ 1060 ObjectMeta: metav1.ObjectMeta{ 1061 Name: redisName, 1062 Namespace: namespace, 1063 Labels: map[string]string{ 1064 "app.kubernetes.io/component": "redis", 1065 "app.kubernetes.io/name": name, 1066 "app.kubernetes.io/part-of": "redis-failover", 1067 }, 1068 Annotations: map[string]string{ 1069 "prometheus.io/scrape": "true", 1070 "prometheus.io/path": "/metrics", 1071 "prometheus.io/port": "http", 1072 }, 1073 OwnerReferences: []metav1.OwnerReference{ 1074 { 1075 Name: "testing", 1076 }, 1077 }, 1078 }, 1079 Spec: corev1.ServiceSpec{ 1080 Type: corev1.ServiceTypeClusterIP, 1081 ClusterIP: corev1.ClusterIPNone, 1082 Selector: map[string]string{ 1083 "app.kubernetes.io/component": "redis", 1084 "app.kubernetes.io/name": name, 1085 "app.kubernetes.io/part-of": "redis-failover", 1086 }, 1087 Ports: []corev1.ServicePort{ 1088 { 1089 Name: "http-metrics", 1090 Port: 9121, 1091 Protocol: corev1.ProtocolTCP, 1092 }, 1093 }, 1094 }, 1095 }, 1096 }, 1097 { 1098 name: "with Name provided", 1099 rfName: "custom-name", 1100 expectedService: corev1.Service{ 1101 ObjectMeta: metav1.ObjectMeta{ 1102 Name: "rfr-custom-name", 1103 Namespace: namespace, 1104 Labels: map[string]string{ 1105 "app.kubernetes.io/component": "redis", 1106 "app.kubernetes.io/name": "custom-name", 1107 "app.kubernetes.io/part-of": "redis-failover", 1108 }, 1109 Annotations: map[string]string{ 1110 "prometheus.io/scrape": "true", 1111 "prometheus.io/path": "/metrics", 1112 "prometheus.io/port": "http", 1113 }, 1114 OwnerReferences: []metav1.OwnerReference{ 1115 { 1116 Name: "testing", 1117 }, 1118 }, 1119 }, 1120 Spec: corev1.ServiceSpec{ 1121 Type: corev1.ServiceTypeClusterIP, 1122 ClusterIP: corev1.ClusterIPNone, 1123 Selector: map[string]string{ 1124 "app.kubernetes.io/component": "redis", 1125 "app.kubernetes.io/name": "custom-name", 1126 "app.kubernetes.io/part-of": "redis-failover", 1127 }, 1128 Ports: []corev1.ServicePort{ 1129 { 1130 Name: "http-metrics", 1131 Port: 9121, 1132 Protocol: corev1.ProtocolTCP, 1133 }, 1134 }, 1135 }, 1136 }, 1137 }, 1138 { 1139 name: "with Namespace provided", 1140 rfNamespace: "custom-namespace", 1141 expectedService: corev1.Service{ 1142 ObjectMeta: metav1.ObjectMeta{ 1143 Name: redisName, 1144 Namespace: "custom-namespace", 1145 Labels: map[string]string{ 1146 "app.kubernetes.io/component": "redis", 1147 "app.kubernetes.io/name": name, 1148 "app.kubernetes.io/part-of": "redis-failover", 1149 }, 1150 Annotations: map[string]string{ 1151 "prometheus.io/scrape": "true", 1152 "prometheus.io/path": "/metrics", 1153 "prometheus.io/port": "http", 1154 }, 1155 OwnerReferences: []metav1.OwnerReference{ 1156 { 1157 Name: "testing", 1158 }, 1159 }, 1160 }, 1161 Spec: corev1.ServiceSpec{ 1162 Type: corev1.ServiceTypeClusterIP, 1163 ClusterIP: corev1.ClusterIPNone, 1164 Selector: map[string]string{ 1165 "app.kubernetes.io/component": "redis", 1166 "app.kubernetes.io/name": name, 1167 "app.kubernetes.io/part-of": "redis-failover", 1168 }, 1169 Ports: []corev1.ServicePort{ 1170 { 1171 Name: "http-metrics", 1172 Port: 9121, 1173 Protocol: corev1.ProtocolTCP, 1174 }, 1175 }, 1176 }, 1177 }, 1178 }, 1179 { 1180 name: "with Labels provided", 1181 rfLabels: map[string]string{"some": "label"}, 1182 expectedService: corev1.Service{ 1183 ObjectMeta: metav1.ObjectMeta{ 1184 Name: redisName, 1185 Namespace: namespace, 1186 Labels: map[string]string{ 1187 "app.kubernetes.io/component": "redis", 1188 "app.kubernetes.io/name": name, 1189 "app.kubernetes.io/part-of": "redis-failover", 1190 "some": "label", 1191 }, 1192 Annotations: map[string]string{ 1193 "prometheus.io/scrape": "true", 1194 "prometheus.io/path": "/metrics", 1195 "prometheus.io/port": "http", 1196 }, 1197 OwnerReferences: []metav1.OwnerReference{ 1198 { 1199 Name: "testing", 1200 }, 1201 }, 1202 }, 1203 Spec: corev1.ServiceSpec{ 1204 Type: corev1.ServiceTypeClusterIP, 1205 ClusterIP: corev1.ClusterIPNone, 1206 Selector: map[string]string{ 1207 "app.kubernetes.io/component": "redis", 1208 "app.kubernetes.io/name": name, 1209 "app.kubernetes.io/part-of": "redis-failover", 1210 }, 1211 Ports: []corev1.ServicePort{ 1212 { 1213 Name: "http-metrics", 1214 Port: 9121, 1215 Protocol: corev1.ProtocolTCP, 1216 }, 1217 }, 1218 }, 1219 }, 1220 }, 1221 { 1222 name: "with Annotations provided", 1223 rfAnnotations: map[string]string{"some": "annotation"}, 1224 expectedService: corev1.Service{ 1225 ObjectMeta: metav1.ObjectMeta{ 1226 Name: redisName, 1227 Namespace: namespace, 1228 Labels: map[string]string{ 1229 "app.kubernetes.io/component": "redis", 1230 "app.kubernetes.io/name": name, 1231 "app.kubernetes.io/part-of": "redis-failover", 1232 }, 1233 Annotations: map[string]string{ 1234 "prometheus.io/scrape": "true", 1235 "prometheus.io/path": "/metrics", 1236 "prometheus.io/port": "http", 1237 "some": "annotation", 1238 }, 1239 OwnerReferences: []metav1.OwnerReference{ 1240 { 1241 Name: "testing", 1242 }, 1243 }, 1244 }, 1245 Spec: corev1.ServiceSpec{ 1246 Type: corev1.ServiceTypeClusterIP, 1247 ClusterIP: corev1.ClusterIPNone, 1248 Selector: map[string]string{ 1249 "app.kubernetes.io/component": "redis", 1250 "app.kubernetes.io/name": name, 1251 "app.kubernetes.io/part-of": "redis-failover", 1252 }, 1253 Ports: []corev1.ServicePort{ 1254 { 1255 Name: "http-metrics", 1256 Port: 9121, 1257 Protocol: corev1.ProtocolTCP, 1258 }, 1259 }, 1260 }, 1261 }, 1262 }, 1263 } 1264 1265 for _, test := range tests { 1266 t.Run(test.name, func(t *testing.T) { 1267 assert := assert.New(t) 1268 1269 // Generate a default RedisFailover and attaching the required annotations 1270 rf := generateRF() 1271 if test.rfName != "" { 1272 rf.Name = test.rfName 1273 } 1274 if test.rfNamespace != "" { 1275 rf.Namespace = test.rfNamespace 1276 } 1277 rf.Spec.Redis.ServiceAnnotations = test.rfAnnotations 1278 1279 generatedService := corev1.Service{} 1280 1281 ms := &mK8SService.Services{} 1282 ms.On("CreateOrUpdateService", rf.Namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 1283 s := args.Get(1).(*corev1.Service) 1284 generatedService = *s 1285 }).Return(nil) 1286 1287 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 1288 err := client.EnsureRedisService(rf, test.rfLabels, []metav1.OwnerReference{{Name: "testing"}}) 1289 1290 assert.Equal(test.expectedService, generatedService) 1291 assert.NoError(err) 1292 }) 1293 } 1294 } 1295 1296 func TestRedisHostNetworkAndDnsPolicy(t *testing.T) { 1297 tests := []struct { 1298 name string 1299 hostNetwork bool 1300 expectedHostNetwork bool 1301 dnsPolicy corev1.DNSPolicy 1302 expectedDnsPolicy corev1.DNSPolicy 1303 }{ 1304 { 1305 name: "Default", 1306 expectedHostNetwork: false, 1307 expectedDnsPolicy: corev1.DNSClusterFirst, 1308 }, 1309 { 1310 name: "Custom", 1311 hostNetwork: true, 1312 expectedHostNetwork: true, 1313 dnsPolicy: corev1.DNSClusterFirstWithHostNet, 1314 expectedDnsPolicy: corev1.DNSClusterFirstWithHostNet, 1315 }, 1316 } 1317 1318 for _, test := range tests { 1319 assert := assert.New(t) 1320 1321 rf := generateRF() 1322 rf.Spec.Redis.HostNetwork = test.hostNetwork 1323 rf.Spec.Redis.DNSPolicy = test.dnsPolicy 1324 1325 var actualHostNetwork bool 1326 var actualDnsPolicy corev1.DNSPolicy 1327 1328 ms := &mK8SService.Services{} 1329 ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil) 1330 ms.On("CreateOrUpdateStatefulSet", namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 1331 ss := args.Get(1).(*appsv1.StatefulSet) 1332 actualHostNetwork = ss.Spec.Template.Spec.HostNetwork 1333 actualDnsPolicy = ss.Spec.Template.Spec.DNSPolicy 1334 }).Return(nil) 1335 1336 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 1337 err := client.EnsureRedisStatefulset(rf, nil, []metav1.OwnerReference{}) 1338 assert.NoError(err) 1339 1340 assert.Equal(test.expectedHostNetwork, actualHostNetwork) 1341 assert.Equal(test.expectedDnsPolicy, actualDnsPolicy) 1342 } 1343 } 1344 1345 func TestSentinelHostNetworkAndDnsPolicy(t *testing.T) { 1346 tests := []struct { 1347 name string 1348 hostNetwork bool 1349 expectedHostNetwork bool 1350 dnsPolicy corev1.DNSPolicy 1351 expectedDnsPolicy corev1.DNSPolicy 1352 }{ 1353 { 1354 name: "Default", 1355 expectedHostNetwork: false, 1356 expectedDnsPolicy: corev1.DNSClusterFirst, 1357 }, 1358 { 1359 name: "Custom", 1360 hostNetwork: true, 1361 expectedHostNetwork: true, 1362 dnsPolicy: corev1.DNSClusterFirstWithHostNet, 1363 expectedDnsPolicy: corev1.DNSClusterFirstWithHostNet, 1364 }, 1365 } 1366 1367 for _, test := range tests { 1368 assert := assert.New(t) 1369 1370 rf := generateRF() 1371 rf.Spec.Sentinel.HostNetwork = test.hostNetwork 1372 rf.Spec.Sentinel.DNSPolicy = test.dnsPolicy 1373 1374 var actualHostNetwork bool 1375 var actualDnsPolicy corev1.DNSPolicy 1376 1377 ms := &mK8SService.Services{} 1378 ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil) 1379 ms.On("CreateOrUpdateDeployment", namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 1380 d := args.Get(1).(*appsv1.Deployment) 1381 actualHostNetwork = d.Spec.Template.Spec.HostNetwork 1382 actualDnsPolicy = d.Spec.Template.Spec.DNSPolicy 1383 }).Return(nil) 1384 1385 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 1386 err := client.EnsureSentinelDeployment(rf, nil, []metav1.OwnerReference{}) 1387 assert.NoError(err) 1388 1389 assert.Equal(test.expectedHostNetwork, actualHostNetwork) 1390 assert.Equal(test.expectedDnsPolicy, actualDnsPolicy) 1391 } 1392 } 1393 1394 func TestRedisImagePullPolicy(t *testing.T) { 1395 tests := []struct { 1396 name string 1397 policy corev1.PullPolicy 1398 exporterPolicy corev1.PullPolicy 1399 expectedPolicy corev1.PullPolicy 1400 expectedExporterPolicy corev1.PullPolicy 1401 }{ 1402 { 1403 name: "Default", 1404 expectedPolicy: corev1.PullAlways, 1405 expectedExporterPolicy: corev1.PullAlways, 1406 }, 1407 { 1408 name: "Custom", 1409 policy: corev1.PullIfNotPresent, 1410 exporterPolicy: corev1.PullNever, 1411 expectedPolicy: corev1.PullIfNotPresent, 1412 expectedExporterPolicy: corev1.PullNever, 1413 }, 1414 } 1415 1416 for _, test := range tests { 1417 assert := assert.New(t) 1418 1419 var policy corev1.PullPolicy 1420 var exporterPolicy corev1.PullPolicy 1421 1422 rf := generateRF() 1423 rf.Spec.Redis.ImagePullPolicy = test.policy 1424 rf.Spec.Redis.Exporter.Enabled = true 1425 rf.Spec.Redis.Exporter.ImagePullPolicy = test.expectedExporterPolicy 1426 1427 ms := &mK8SService.Services{} 1428 ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil) 1429 ms.On("CreateOrUpdateStatefulSet", namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 1430 ss := args.Get(1).(*appsv1.StatefulSet) 1431 policy = ss.Spec.Template.Spec.Containers[0].ImagePullPolicy 1432 exporterPolicy = ss.Spec.Template.Spec.Containers[1].ImagePullPolicy 1433 }).Return(nil) 1434 1435 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 1436 err := client.EnsureRedisStatefulset(rf, nil, []metav1.OwnerReference{}) 1437 1438 assert.NoError(err) 1439 assert.Equal(string(test.expectedPolicy), string(policy)) 1440 assert.Equal(string(test.expectedExporterPolicy), string(exporterPolicy)) 1441 } 1442 } 1443 1444 func TestSentinelImagePullPolicy(t *testing.T) { 1445 tests := []struct { 1446 name string 1447 policy corev1.PullPolicy 1448 expectedPolicy corev1.PullPolicy 1449 expectedConfigPolicy corev1.PullPolicy 1450 }{ 1451 { 1452 name: "Default", 1453 expectedPolicy: corev1.PullAlways, 1454 expectedConfigPolicy: corev1.PullAlways, 1455 }, 1456 { 1457 name: "Custom", 1458 policy: corev1.PullIfNotPresent, 1459 expectedPolicy: corev1.PullIfNotPresent, 1460 expectedConfigPolicy: corev1.PullIfNotPresent, 1461 }, 1462 } 1463 1464 for _, test := range tests { 1465 assert := assert.New(t) 1466 1467 var policy corev1.PullPolicy 1468 var configPolicy corev1.PullPolicy 1469 1470 rf := generateRF() 1471 rf.Spec.Sentinel.ImagePullPolicy = test.policy 1472 1473 ms := &mK8SService.Services{} 1474 ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil) 1475 ms.On("CreateOrUpdateDeployment", namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 1476 d := args.Get(1).(*appsv1.Deployment) 1477 policy = d.Spec.Template.Spec.Containers[0].ImagePullPolicy 1478 configPolicy = d.Spec.Template.Spec.InitContainers[0].ImagePullPolicy 1479 }).Return(nil) 1480 1481 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 1482 err := client.EnsureSentinelDeployment(rf, nil, []metav1.OwnerReference{}) 1483 1484 assert.NoError(err) 1485 assert.Equal(string(test.expectedPolicy), string(policy)) 1486 assert.Equal(string(test.expectedConfigPolicy), string(configPolicy)) 1487 } 1488 } 1489 1490 func TestRedisExtraVolumeMounts(t *testing.T) { 1491 mode := int32(755) 1492 tests := []struct { 1493 name string 1494 expectedVolumes []corev1.Volume 1495 expectedVolumeMounts []corev1.VolumeMount 1496 }{ 1497 { 1498 name: "EmptyDir", 1499 expectedVolumes: []corev1.Volume{ 1500 { 1501 Name: "foo", 1502 VolumeSource: corev1.VolumeSource{ 1503 EmptyDir: &corev1.EmptyDirVolumeSource{}, 1504 }, 1505 }, 1506 }, 1507 expectedVolumeMounts: []corev1.VolumeMount{ 1508 { 1509 Name: "foo", 1510 MountPath: "/mnt/foo", 1511 }, 1512 }, 1513 }, 1514 { 1515 name: "ConfigMap", 1516 expectedVolumes: []corev1.Volume{ 1517 { 1518 Name: "bar", 1519 VolumeSource: corev1.VolumeSource{ 1520 ConfigMap: &corev1.ConfigMapVolumeSource{ 1521 LocalObjectReference: corev1.LocalObjectReference{ 1522 Name: "bar-cm", 1523 }, 1524 DefaultMode: &mode, 1525 }, 1526 }, 1527 }, 1528 }, 1529 expectedVolumeMounts: []corev1.VolumeMount{ 1530 { 1531 Name: "bar", 1532 MountPath: "/mnt/scripts", 1533 }, 1534 }, 1535 }, 1536 } 1537 1538 for _, test := range tests { 1539 assert := assert.New(t) 1540 1541 var extraVolume corev1.Volume 1542 var extraVolumeMount corev1.VolumeMount 1543 1544 rf := generateRF() 1545 rf.Spec.Redis.ExtraVolumes = test.expectedVolumes 1546 rf.Spec.Redis.ExtraVolumeMounts = test.expectedVolumeMounts 1547 1548 ms := &mK8SService.Services{} 1549 ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil) 1550 ms.On("CreateOrUpdateStatefulSet", namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 1551 s := args.Get(1).(*appsv1.StatefulSet) 1552 extraVolume = s.Spec.Template.Spec.Volumes[3] 1553 extraVolumeMount = s.Spec.Template.Spec.Containers[0].VolumeMounts[4] 1554 }).Return(nil) 1555 1556 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 1557 err := client.EnsureRedisStatefulset(rf, nil, []metav1.OwnerReference{}) 1558 1559 assert.NoError(err) 1560 assert.Equal(test.expectedVolumes[0], extraVolume) 1561 assert.Equal(test.expectedVolumeMounts[0], extraVolumeMount) 1562 } 1563 } 1564 1565 func TestSentinelExtraVolumeMounts(t *testing.T) { 1566 mode := int32(755) 1567 tests := []struct { 1568 name string 1569 expectedVolumes []corev1.Volume 1570 expectedVolumeMounts []corev1.VolumeMount 1571 }{ 1572 { 1573 name: "EmptyDir", 1574 expectedVolumes: []corev1.Volume{ 1575 { 1576 Name: "foo", 1577 VolumeSource: corev1.VolumeSource{ 1578 EmptyDir: &corev1.EmptyDirVolumeSource{}, 1579 }, 1580 }, 1581 }, 1582 expectedVolumeMounts: []corev1.VolumeMount{ 1583 { 1584 Name: "foo", 1585 MountPath: "/mnt/foo", 1586 }, 1587 }, 1588 }, 1589 { 1590 name: "ConfigMap", 1591 expectedVolumes: []corev1.Volume{ 1592 { 1593 Name: "bar", 1594 VolumeSource: corev1.VolumeSource{ 1595 ConfigMap: &corev1.ConfigMapVolumeSource{ 1596 LocalObjectReference: corev1.LocalObjectReference{ 1597 Name: "bar-cm", 1598 }, 1599 DefaultMode: &mode, 1600 }, 1601 }, 1602 }, 1603 }, 1604 expectedVolumeMounts: []corev1.VolumeMount{ 1605 { 1606 Name: "bar", 1607 MountPath: "/mnt/scripts", 1608 }, 1609 }, 1610 }, 1611 } 1612 1613 for _, test := range tests { 1614 assert := assert.New(t) 1615 1616 var extraVolume corev1.Volume 1617 var extraVolumeMount corev1.VolumeMount 1618 1619 rf := generateRF() 1620 rf.Spec.Sentinel.ExtraVolumes = test.expectedVolumes 1621 rf.Spec.Sentinel.ExtraVolumeMounts = test.expectedVolumeMounts 1622 1623 ms := &mK8SService.Services{} 1624 ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil) 1625 ms.On("CreateOrUpdateDeployment", namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 1626 d := args.Get(1).(*appsv1.Deployment) 1627 extraVolume = d.Spec.Template.Spec.Volumes[2] 1628 extraVolumeMount = d.Spec.Template.Spec.Containers[0].VolumeMounts[1] 1629 }).Return(nil) 1630 1631 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 1632 err := client.EnsureSentinelDeployment(rf, nil, []metav1.OwnerReference{}) 1633 1634 assert.NoError(err) 1635 assert.Equal(test.expectedVolumes[0], extraVolume) 1636 assert.Equal(test.expectedVolumeMounts[0], extraVolumeMount) 1637 } 1638 } 1639 1640 func TestCustomPort(t *testing.T) { 1641 default_port := int32(6379) 1642 custom_port := int32(12345) 1643 tests := []struct { 1644 name string 1645 port int32 1646 expectedContainerPort []corev1.ContainerPort 1647 }{ 1648 { 1649 name: "Default port", 1650 port: default_port, 1651 expectedContainerPort: []corev1.ContainerPort{ 1652 { 1653 Name: "redis", 1654 ContainerPort: default_port, 1655 Protocol: corev1.ProtocolTCP, 1656 }, 1657 }, 1658 }, 1659 { 1660 name: "Custom port", 1661 port: custom_port, 1662 expectedContainerPort: []corev1.ContainerPort{ 1663 { 1664 Name: "redis", 1665 ContainerPort: custom_port, 1666 Protocol: corev1.ProtocolTCP, 1667 }, 1668 }, 1669 }, 1670 } 1671 1672 for _, test := range tests { 1673 assert := assert.New(t) 1674 1675 var port corev1.ContainerPort 1676 1677 rf := generateRF() 1678 rf.Spec.Redis.Port = test.port 1679 1680 ms := &mK8SService.Services{} 1681 ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil) 1682 ms.On("CreateOrUpdateStatefulSet", namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 1683 s := args.Get(1).(*appsv1.StatefulSet) 1684 port = s.Spec.Template.Spec.Containers[0].Ports[0] 1685 }).Return(nil) 1686 1687 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 1688 err := client.EnsureRedisStatefulset(rf, nil, []metav1.OwnerReference{}) 1689 1690 assert.NoError(err) 1691 assert.Equal(test.expectedContainerPort[0], port) 1692 } 1693 } 1694 1695 func TestRedisEnv(t *testing.T) { 1696 default_port := int32(6379) 1697 tests := []struct { 1698 name string 1699 auth string 1700 expectedRedisEnv []corev1.EnvVar 1701 }{ 1702 { 1703 name: "without auth", 1704 auth: "", 1705 expectedRedisEnv: []corev1.EnvVar{ 1706 { 1707 Name: "REDIS_ADDR", 1708 Value: fmt.Sprintf("redis://127.0.0.1:%[1]v", default_port), 1709 }, 1710 { 1711 Name: "REDIS_PORT", 1712 Value: fmt.Sprintf("%[1]v", default_port), 1713 }, 1714 { 1715 Name: "REDIS_USER", 1716 Value: "default", 1717 }, 1718 }, 1719 }, 1720 { 1721 name: "with auth", 1722 auth: "redis-secret", 1723 expectedRedisEnv: []corev1.EnvVar{ 1724 { 1725 Name: "REDIS_ADDR", 1726 Value: fmt.Sprintf("redis://127.0.0.1:%[1]v", default_port), 1727 }, 1728 { 1729 Name: "REDIS_PORT", 1730 Value: fmt.Sprintf("%[1]v", default_port), 1731 }, 1732 { 1733 Name: "REDIS_USER", 1734 Value: "default", 1735 }, 1736 { 1737 Name: "REDIS_PASSWORD", 1738 ValueFrom: &corev1.EnvVarSource{ 1739 SecretKeyRef: &corev1.SecretKeySelector{ 1740 LocalObjectReference: corev1.LocalObjectReference{ 1741 Name: "redis-secret", 1742 }, 1743 Key: "password", 1744 }, 1745 }, 1746 }, 1747 }, 1748 }, 1749 } 1750 1751 for _, test := range tests { 1752 assert := assert.New(t) 1753 1754 var env []corev1.EnvVar 1755 1756 rf := generateRF() 1757 rf.Spec.Redis.Port = default_port 1758 if test.auth != "" { 1759 rf.Spec.Auth.SecretPath = test.auth 1760 } 1761 1762 ms := &mK8SService.Services{} 1763 ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil) 1764 ms.On("CreateOrUpdateStatefulSet", namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 1765 s := args.Get(1).(*appsv1.StatefulSet) 1766 env = s.Spec.Template.Spec.Containers[0].Env 1767 }).Return(nil) 1768 1769 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 1770 err := client.EnsureRedisStatefulset(rf, nil, []metav1.OwnerReference{}) 1771 1772 assert.NoError(err) 1773 assert.Equal(test.expectedRedisEnv, env) 1774 } 1775 } 1776 1777 func TestRedisStartupProbe(t *testing.T) { 1778 mode := int32(0744) 1779 tests := []struct { 1780 name string 1781 expectedVolume corev1.Volume 1782 expectedVolumeMount corev1.VolumeMount 1783 }{ 1784 { 1785 name: "startup_config", 1786 expectedVolume: corev1.Volume{ 1787 Name: "redis-startup-config", 1788 VolumeSource: corev1.VolumeSource{ 1789 ConfigMap: &corev1.ConfigMapVolumeSource{ 1790 LocalObjectReference: corev1.LocalObjectReference{ 1791 Name: "startup_config", 1792 }, 1793 DefaultMode: &mode, 1794 }, 1795 }, 1796 }, 1797 expectedVolumeMount: corev1.VolumeMount{ 1798 Name: "redis-startup-config", 1799 MountPath: "/redis-startup", 1800 }, 1801 }, 1802 } 1803 1804 for _, test := range tests { 1805 assert := assert.New(t) 1806 1807 var startupVolumes []corev1.Volume 1808 var startupVolumeMounts []corev1.VolumeMount 1809 1810 rf := generateRF() 1811 rf.Spec.Redis.StartupConfigMap = test.name 1812 1813 ms := &mK8SService.Services{} 1814 ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil) 1815 ms.On("CreateOrUpdateStatefulSet", namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 1816 s := args.Get(1).(*appsv1.StatefulSet) 1817 startupVolumes = s.Spec.Template.Spec.Volumes 1818 startupVolumeMounts = s.Spec.Template.Spec.Containers[0].VolumeMounts 1819 }).Return(nil) 1820 1821 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 1822 err := client.EnsureRedisStatefulset(rf, nil, []metav1.OwnerReference{}) 1823 1824 assert.NoError(err) 1825 assert.Contains(startupVolumes, test.expectedVolume) 1826 assert.Contains(startupVolumeMounts, test.expectedVolumeMount) 1827 } 1828 } 1829 1830 func TestSentinelStartupProbe(t *testing.T) { 1831 mode := int32(0744) 1832 tests := []struct { 1833 name string 1834 expectedVolume corev1.Volume 1835 expectedVolumeMount corev1.VolumeMount 1836 }{ 1837 { 1838 name: "startup_config", 1839 expectedVolume: corev1.Volume{ 1840 Name: "sentinel-startup-config", 1841 VolumeSource: corev1.VolumeSource{ 1842 ConfigMap: &corev1.ConfigMapVolumeSource{ 1843 LocalObjectReference: corev1.LocalObjectReference{ 1844 Name: "startup_config", 1845 }, 1846 DefaultMode: &mode, 1847 }, 1848 }, 1849 }, 1850 expectedVolumeMount: corev1.VolumeMount{ 1851 Name: "sentinel-startup-config", 1852 MountPath: "/sentinel-startup", 1853 }, 1854 }, 1855 } 1856 1857 for _, test := range tests { 1858 assert := assert.New(t) 1859 1860 var startupVolumes []corev1.Volume 1861 var startupVolumeMounts []corev1.VolumeMount 1862 1863 rf := generateRF() 1864 rf.Spec.Sentinel.StartupConfigMap = test.name 1865 1866 ms := &mK8SService.Services{} 1867 ms.On("CreateOrUpdatePodDisruptionBudget", namespace, mock.Anything).Once().Return(nil, nil) 1868 ms.On("CreateOrUpdateDeployment", namespace, mock.Anything).Once().Run(func(args mock.Arguments) { 1869 d := args.Get(1).(*appsv1.Deployment) 1870 startupVolumes = d.Spec.Template.Spec.Volumes 1871 startupVolumeMounts = d.Spec.Template.Spec.Containers[0].VolumeMounts 1872 }).Return(nil) 1873 1874 client := rfservice.NewRedisFailoverKubeClient(ms, log.Dummy, metrics.Dummy) 1875 err := client.EnsureSentinelDeployment(rf, nil, []metav1.OwnerReference{}) 1876 1877 assert.NoError(err) 1878 assert.Contains(startupVolumes, test.expectedVolume) 1879 assert.Contains(startupVolumeMounts, test.expectedVolumeMount) 1880 } 1881 }