github.com/argoproj-labs/argocd-operator@v0.10.0/controllers/argocd/statefulset_test.go (about) 1 package argocd 2 3 import ( 4 "context" 5 "fmt" 6 "testing" 7 "time" 8 9 resourcev1 "k8s.io/apimachinery/pkg/api/resource" 10 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 11 "k8s.io/apimachinery/pkg/runtime" 12 13 argoprojv1alpha1 "github.com/argoproj-labs/argocd-operator/api/v1alpha1" 14 argoproj "github.com/argoproj-labs/argocd-operator/api/v1beta1" 15 "github.com/argoproj-labs/argocd-operator/controllers/argoutil" 16 17 "github.com/google/go-cmp/cmp" 18 appsv1 "k8s.io/api/apps/v1" 19 corev1 "k8s.io/api/core/v1" 20 21 "github.com/argoproj-labs/argocd-operator/common" 22 23 "github.com/stretchr/testify/assert" 24 25 "k8s.io/apimachinery/pkg/types" 26 "sigs.k8s.io/controller-runtime/pkg/client" 27 logf "sigs.k8s.io/controller-runtime/pkg/log" 28 ) 29 30 var ( 31 testRedisImage = "redis" 32 testRedisImageVersion = "test" 33 ) 34 35 func controllerDefaultVolumes() []corev1.Volume { 36 volumes := []corev1.Volume{ 37 { 38 Name: "argocd-repo-server-tls", 39 VolumeSource: corev1.VolumeSource{ 40 Secret: &corev1.SecretVolumeSource{ 41 SecretName: common.ArgoCDRepoServerTLSSecretName, 42 Optional: boolPtr(true), 43 }, 44 }, 45 }, 46 { 47 Name: common.ArgoCDRedisServerTLSSecretName, 48 VolumeSource: corev1.VolumeSource{ 49 Secret: &corev1.SecretVolumeSource{ 50 SecretName: common.ArgoCDRedisServerTLSSecretName, 51 Optional: boolPtr(true), 52 }, 53 }, 54 }, 55 } 56 return volumes 57 } 58 59 func controllerDefaultVolumeMounts() []corev1.VolumeMount { 60 mounts := []corev1.VolumeMount{ 61 { 62 Name: "argocd-repo-server-tls", 63 MountPath: "/app/config/controller/tls", 64 }, 65 { 66 Name: common.ArgoCDRedisServerTLSSecretName, 67 MountPath: "/app/config/controller/tls/redis", 68 }, 69 } 70 return mounts 71 } 72 73 func TestReconcileArgoCD_reconcileRedisStatefulSet_HA_disabled(t *testing.T) { 74 logf.SetLogger(ZapLogger(true)) 75 76 a := makeTestArgoCD() 77 78 resObjs := []client.Object{a} 79 subresObjs := []client.Object{a} 80 runtimeObjs := []runtime.Object{} 81 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 82 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 83 r := makeTestReconciler(cl, sch) 84 85 s := newStatefulSetWithSuffix("redis-ha-server", "redis", a) 86 87 assert.NoError(t, r.reconcileRedisStatefulSet(a)) 88 // resource Creation should fail as HA was disabled 89 assert.Errorf(t, r.Client.Get(context.TODO(), types.NamespacedName{Name: s.Name, Namespace: a.Namespace}, s), "not found") 90 } 91 92 func TestReconcileArgoCD_reconcileRedisStatefulSet_HA_enabled(t *testing.T) { 93 logf.SetLogger(ZapLogger(true)) 94 95 a := makeTestArgoCD() 96 97 resObjs := []client.Object{a} 98 subresObjs := []client.Object{a} 99 runtimeObjs := []runtime.Object{} 100 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 101 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 102 r := makeTestReconciler(cl, sch) 103 104 s := newStatefulSetWithSuffix("redis-ha-server", "redis", a) 105 106 a.Spec.HA.Enabled = true 107 // test resource is Created when HA is enabled 108 assert.NoError(t, r.reconcileRedisStatefulSet(a)) 109 assert.NoError(t, r.Client.Get(context.TODO(), types.NamespacedName{Name: s.Name, Namespace: a.Namespace}, s)) 110 111 // test resource is Updated on reconciliation 112 a.Spec.Redis.Image = testRedisImage 113 a.Spec.Redis.Version = testRedisImageVersion 114 newResources := corev1.ResourceRequirements{ 115 Requests: corev1.ResourceList{ 116 corev1.ResourceMemory: resourcev1.MustParse("256Mi"), 117 corev1.ResourceCPU: resourcev1.MustParse("500m"), 118 }, 119 Limits: corev1.ResourceList{ 120 corev1.ResourceMemory: resourcev1.MustParse("512Mi"), 121 corev1.ResourceCPU: resourcev1.MustParse("1"), 122 }, 123 } 124 a.Spec.HA.Resources = &newResources 125 assert.NoError(t, r.reconcileRedisStatefulSet(a)) 126 assert.NoError(t, r.Client.Get(context.TODO(), types.NamespacedName{Name: s.Name, Namespace: a.Namespace}, s)) 127 for _, container := range s.Spec.Template.Spec.Containers { 128 assert.Equal(t, container.Image, fmt.Sprintf("%s:%s", testRedisImage, testRedisImageVersion)) 129 assert.Equal(t, container.Resources, newResources) 130 } 131 assert.Equal(t, s.Spec.Template.Spec.InitContainers[0].Resources, newResources) 132 133 // test resource is Deleted, when HA is disabled 134 a.Spec.HA.Enabled = false 135 assert.NoError(t, r.reconcileRedisStatefulSet(a)) 136 assert.Errorf(t, r.Client.Get(context.TODO(), types.NamespacedName{Name: s.Name, Namespace: a.Namespace}, s), "not found") 137 } 138 139 func TestReconcileArgoCD_reconcileApplicationController(t *testing.T) { 140 logf.SetLogger(ZapLogger(true)) 141 a := makeTestArgoCD() 142 143 resObjs := []client.Object{a} 144 subresObjs := []client.Object{a} 145 runtimeObjs := []runtime.Object{} 146 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 147 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 148 r := makeTestReconciler(cl, sch) 149 150 assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a, false)) 151 152 ss := &appsv1.StatefulSet{} 153 assert.NoError(t, r.Client.Get( 154 context.TODO(), 155 types.NamespacedName{ 156 Name: "argocd-application-controller", 157 Namespace: a.Namespace, 158 }, 159 ss)) 160 command := ss.Spec.Template.Spec.Containers[0].Command 161 want := []string{ 162 "argocd-application-controller", 163 "--operation-processors", "10", 164 "--redis", "argocd-redis.argocd.svc.cluster.local:6379", 165 "--repo-server", "argocd-repo-server.argocd.svc.cluster.local:8081", 166 "--status-processors", "20", 167 "--kubectl-parallelism-limit", "10", 168 "--loglevel", "info", 169 "--logformat", "text"} 170 if diff := cmp.Diff(want, command); diff != "" { 171 t.Fatalf("reconciliation failed:\n%s", diff) 172 } 173 wantVolumes := controllerDefaultVolumes() 174 if diff := cmp.Diff(wantVolumes, ss.Spec.Template.Spec.Volumes); diff != "" { 175 t.Fatalf("reconciliation failed:\n%s", diff) 176 } 177 wantVolumeMounts := controllerDefaultVolumeMounts() 178 if diff := cmp.Diff(wantVolumeMounts, ss.Spec.Template.Spec.Containers[0].VolumeMounts); diff != "" { 179 t.Fatalf("reconciliation failed:\n%s", diff) 180 } 181 } 182 183 func TestReconcileArgoCD_reconcileApplicationController_withRedisTLS(t *testing.T) { 184 logf.SetLogger(ZapLogger(true)) 185 a := makeTestArgoCD() 186 187 resObjs := []client.Object{a} 188 subresObjs := []client.Object{a} 189 runtimeObjs := []runtime.Object{} 190 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 191 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 192 r := makeTestReconciler(cl, sch) 193 194 assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a, true)) 195 196 ss := &appsv1.StatefulSet{} 197 assert.NoError(t, r.Client.Get( 198 context.TODO(), 199 types.NamespacedName{ 200 Name: "argocd-application-controller", 201 Namespace: a.Namespace, 202 }, 203 ss)) 204 command := ss.Spec.Template.Spec.Containers[0].Command 205 want := []string{ 206 "argocd-application-controller", 207 "--operation-processors", "10", 208 "--redis", "argocd-redis.argocd.svc.cluster.local:6379", 209 "--redis-use-tls", 210 "--redis-ca-certificate", "/app/config/controller/tls/redis/tls.crt", 211 "--repo-server", "argocd-repo-server.argocd.svc.cluster.local:8081", 212 "--status-processors", "20", 213 "--kubectl-parallelism-limit", "10", 214 "--loglevel", "info", 215 "--logformat", "text"} 216 if diff := cmp.Diff(want, command); diff != "" { 217 t.Fatalf("reconciliation failed:\n%s", diff) 218 } 219 } 220 221 func TestReconcileArgoCD_reconcileApplicationController_withUpdate(t *testing.T) { 222 logf.SetLogger(ZapLogger(true)) 223 a := makeTestArgoCD() 224 225 resObjs := []client.Object{a} 226 subresObjs := []client.Object{a} 227 runtimeObjs := []runtime.Object{} 228 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 229 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 230 r := makeTestReconciler(cl, sch) 231 232 assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a, false)) 233 234 a = makeTestArgoCD(controllerProcessors(30)) 235 assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a, false)) 236 237 ss := &appsv1.StatefulSet{} 238 assert.NoError(t, r.Client.Get( 239 context.TODO(), 240 types.NamespacedName{ 241 Name: "argocd-application-controller", 242 Namespace: a.Namespace, 243 }, 244 ss)) 245 command := ss.Spec.Template.Spec.Containers[0].Command 246 want := []string{ 247 "argocd-application-controller", 248 "--operation-processors", "10", 249 "--redis", "argocd-redis.argocd.svc.cluster.local:6379", 250 "--repo-server", "argocd-repo-server.argocd.svc.cluster.local:8081", 251 "--status-processors", "30", 252 "--kubectl-parallelism-limit", "10", 253 "--loglevel", "info", 254 "--logformat", "text"} 255 if diff := cmp.Diff(want, command); diff != "" { 256 t.Fatalf("reconciliation failed:\n%s", diff) 257 } 258 } 259 260 func TestReconcileArgoCD_reconcileApplicationController_withUpgrade(t *testing.T) { 261 logf.SetLogger(ZapLogger(true)) 262 a := makeTestArgoCD() 263 264 resObjs := []client.Object{a} 265 subresObjs := []client.Object{a} 266 runtimeObjs := []runtime.Object{} 267 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 268 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 269 r := makeTestReconciler(cl, sch) 270 271 deploy := newDeploymentWithSuffix("application-controller", "application-controller", a) 272 assert.NoError(t, r.Client.Create(context.TODO(), deploy)) 273 274 assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a, false)) 275 err := r.Client.Get(context.TODO(), types.NamespacedName{Name: deploy.Name, Namespace: deploy.Namespace}, deploy) 276 assert.Errorf(t, err, "not found") 277 } 278 279 func TestReconcileArgoCD_reconcileApplicationController_withResources(t *testing.T) { 280 logf.SetLogger(ZapLogger(true)) 281 a := makeTestArgoCDWithResources(func(a *argoproj.ArgoCD) { 282 a.Spec.Import = &argoproj.ArgoCDImportSpec{ 283 Name: "testimport", 284 } 285 }) 286 ex := argoprojv1alpha1.ArgoCDExport{ 287 ObjectMeta: metav1.ObjectMeta{ 288 Name: "testimport", 289 Namespace: a.Namespace, 290 }, 291 Spec: argoprojv1alpha1.ArgoCDExportSpec{ 292 Storage: &argoprojv1alpha1.ArgoCDExportStorageSpec{}, 293 }, 294 } 295 296 resObjs := []client.Object{a, &ex} 297 subresObjs := []client.Object{a, &ex} 298 runtimeObjs := []runtime.Object{} 299 sch := makeTestReconcilerScheme(argoproj.AddToScheme, argoprojv1alpha1.AddToScheme) 300 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 301 r := makeTestReconciler(cl, sch) 302 303 assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a, false)) 304 305 ss := &appsv1.StatefulSet{} 306 assert.NoError(t, r.Client.Get( 307 context.TODO(), 308 types.NamespacedName{ 309 Name: "argocd-application-controller", 310 Namespace: a.Namespace, 311 }, 312 ss)) 313 314 testResources := corev1.ResourceRequirements{ 315 Requests: corev1.ResourceList{ 316 corev1.ResourceMemory: resourcev1.MustParse("1024Mi"), 317 corev1.ResourceCPU: resourcev1.MustParse("1000m"), 318 }, 319 Limits: corev1.ResourceList{ 320 corev1.ResourceMemory: resourcev1.MustParse("2048Mi"), 321 corev1.ResourceCPU: resourcev1.MustParse("2000m"), 322 }, 323 } 324 rsC := ss.Spec.Template.Spec.Containers[0].Resources 325 assert.True(t, testResources.Requests.Cpu().Equal(*rsC.Requests.Cpu())) 326 assert.True(t, testResources.Requests.Memory().Equal(*rsC.Requests.Memory())) 327 assert.True(t, testResources.Limits.Cpu().Equal(*rsC.Limits.Cpu())) 328 assert.True(t, testResources.Limits.Memory().Equal(*rsC.Limits.Memory())) 329 330 // Negative test - differing limits and requests 331 testResources = corev1.ResourceRequirements{ 332 Requests: corev1.ResourceList{ 333 corev1.ResourceMemory: resourcev1.MustParse("2024Mi"), 334 corev1.ResourceCPU: resourcev1.MustParse("2000m"), 335 }, 336 Limits: corev1.ResourceList{ 337 corev1.ResourceMemory: resourcev1.MustParse("3048Mi"), 338 corev1.ResourceCPU: resourcev1.MustParse("1000m"), 339 }, 340 } 341 assert.False(t, testResources.Requests.Cpu().Equal(*rsC.Requests.Cpu())) 342 assert.False(t, testResources.Requests.Memory().Equal(*rsC.Requests.Memory())) 343 assert.False(t, testResources.Limits.Cpu().Equal(*rsC.Limits.Cpu())) 344 assert.False(t, testResources.Limits.Memory().Equal(*rsC.Limits.Memory())) 345 } 346 347 func TestReconcileArgoCD_reconcileApplicationController_withSharding(t *testing.T) { 348 logf.SetLogger(ZapLogger(true)) 349 350 tests := []struct { 351 sharding argoproj.ArgoCDApplicationControllerShardSpec 352 replicas int32 353 vars []corev1.EnvVar 354 }{ 355 { 356 sharding: argoproj.ArgoCDApplicationControllerShardSpec{ 357 Enabled: false, 358 Replicas: 3, 359 }, 360 replicas: 1, 361 vars: []corev1.EnvVar{ 362 {Name: "HOME", Value: "/home/argocd"}, 363 }, 364 }, 365 { 366 sharding: argoproj.ArgoCDApplicationControllerShardSpec{ 367 Enabled: true, 368 Replicas: 1, 369 }, 370 replicas: 1, 371 vars: []corev1.EnvVar{ 372 {Name: "ARGOCD_CONTROLLER_REPLICAS", Value: "1"}, 373 {Name: "HOME", Value: "/home/argocd"}, 374 }, 375 }, 376 { 377 sharding: argoproj.ArgoCDApplicationControllerShardSpec{ 378 Enabled: true, 379 Replicas: 3, 380 }, 381 replicas: 3, 382 vars: []corev1.EnvVar{ 383 {Name: "ARGOCD_CONTROLLER_REPLICAS", Value: "3"}, 384 {Name: "HOME", Value: "/home/argocd"}, 385 }, 386 }, 387 } 388 389 for _, st := range tests { 390 a := makeTestArgoCD(func(a *argoproj.ArgoCD) { 391 a.Spec.Controller.Sharding = st.sharding 392 }) 393 394 resObjs := []client.Object{a} 395 subresObjs := []client.Object{a} 396 runtimeObjs := []runtime.Object{} 397 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 398 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 399 r := makeTestReconciler(cl, sch) 400 401 assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a, false)) 402 403 ss := &appsv1.StatefulSet{} 404 assert.NoError(t, r.Client.Get( 405 context.TODO(), 406 types.NamespacedName{ 407 Name: "argocd-application-controller", 408 Namespace: a.Namespace, 409 }, 410 ss)) 411 412 env := ss.Spec.Template.Spec.Containers[0].Env 413 rep := ss.Spec.Replicas 414 415 diffEnv := cmp.Diff(env, st.vars) 416 diffRep := cmp.Diff(rep, &st.replicas) 417 418 if diffEnv != "" { 419 t.Fatalf("Reconciliation of EnvVars failed:\n%s", diffEnv) 420 } 421 422 if diffRep != "" { 423 t.Fatalf("Reconciliation of Replicas failed:\n%s", diffRep) 424 } 425 } 426 } 427 428 func TestReconcileArgoCD_reconcileApplicationController_withAppSync(t *testing.T) { 429 430 expectedEnv := []corev1.EnvVar{ 431 {Name: "ARGOCD_RECONCILIATION_TIMEOUT", Value: "600s"}, 432 {Name: "HOME", Value: "/home/argocd"}, 433 } 434 435 a := makeTestArgoCD(func(a *argoproj.ArgoCD) { 436 a.Spec.Controller.AppSync = &metav1.Duration{Duration: time.Minute * 10} 437 }) 438 439 resObjs := []client.Object{a} 440 subresObjs := []client.Object{a} 441 runtimeObjs := []runtime.Object{} 442 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 443 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 444 r := makeTestReconciler(cl, sch) 445 446 assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a, false)) 447 448 ss := &appsv1.StatefulSet{} 449 assert.NoError(t, r.Client.Get( 450 context.TODO(), 451 types.NamespacedName{ 452 Name: "argocd-application-controller", 453 Namespace: a.Namespace, 454 }, 455 ss)) 456 457 env := ss.Spec.Template.Spec.Containers[0].Env 458 459 diffEnv := cmp.Diff(env, expectedEnv) 460 461 if diffEnv != "" { 462 t.Fatalf("Reconciliation of EnvVars failed:\n%s", diffEnv) 463 } 464 } 465 466 func TestReconcileArgoCD_reconcileApplicationController_withEnv(t *testing.T) { 467 468 expectedEnv := []corev1.EnvVar{ 469 {Name: "CUSTOM_ENV_VAR", Value: "custom-value"}, 470 {Name: "HOME", Value: "/home/argocd"}, 471 } 472 473 a := makeTestArgoCD(func(a *argoproj.ArgoCD) { 474 // Assuming spec.controller.env is a slice 475 a.Spec.Controller.Env = []corev1.EnvVar{ 476 {Name: "CUSTOM_ENV_VAR", Value: "custom-value"}, 477 } 478 }) 479 480 resObjs := []client.Object{a} 481 subresObjs := []client.Object{a} 482 runtimeObjs := []runtime.Object{} 483 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 484 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 485 r := makeTestReconciler(cl, sch) 486 487 assert.NoError(t, r.reconcileApplicationControllerStatefulSet(a, false)) 488 489 ss := &appsv1.StatefulSet{} 490 assert.NoError(t, r.Client.Get( 491 context.TODO(), 492 types.NamespacedName{ 493 Name: "argocd-application-controller", 494 Namespace: a.Namespace, 495 }, 496 ss)) 497 498 env := ss.Spec.Template.Spec.Containers[0].Env 499 500 diffEnv := cmp.Diff(env, expectedEnv) 501 502 if diffEnv != "" { 503 t.Fatalf("Reconciliation of EnvVars failed:\n%s", diffEnv) 504 } 505 } 506 507 func Test_UpdateNodePlacementStateful(t *testing.T) { 508 509 ss := &appsv1.StatefulSet{ 510 ObjectMeta: metav1.ObjectMeta{ 511 Name: "argocd-sample-server", 512 Namespace: testNamespace, 513 }, 514 Spec: appsv1.StatefulSetSpec{ 515 Template: corev1.PodTemplateSpec{ 516 Spec: corev1.PodSpec{ 517 NodeSelector: map[string]string{ 518 "test_key1": "test_value1", 519 "test_key2": "test_value2", 520 }, 521 Tolerations: []corev1.Toleration{ 522 { 523 Key: "test_key1", 524 Value: "test_value1", 525 Effect: corev1.TaintEffectNoSchedule, 526 }, 527 }, 528 }, 529 }, 530 }, 531 } 532 ss2 := &appsv1.StatefulSet{ 533 ObjectMeta: metav1.ObjectMeta{ 534 Name: "argocd-sample-server", 535 Namespace: testNamespace, 536 }, 537 Spec: appsv1.StatefulSetSpec{ 538 Template: corev1.PodTemplateSpec{ 539 Spec: corev1.PodSpec{ 540 NodeSelector: map[string]string{ 541 "test_key1": "test_value1", 542 }, 543 Tolerations: []corev1.Toleration{ 544 { 545 Key: "test_key1", 546 Value: "test_value1", 547 Effect: corev1.TaintEffectNoExecute, 548 }, 549 }, 550 }, 551 }, 552 }, 553 } 554 expectedChange := false 555 actualChange := false 556 updateNodePlacementStateful(ss, ss, &actualChange) 557 if actualChange != expectedChange { 558 t.Fatalf("updateNodePlacement failed, value of changed: %t", actualChange) 559 } 560 updateNodePlacementStateful(ss, ss2, &actualChange) 561 if actualChange == expectedChange { 562 t.Fatalf("updateNodePlacement failed, value of changed: %t", actualChange) 563 } 564 } 565 566 func Test_ContainsValidImage(t *testing.T) { 567 568 a := makeTestArgoCD() 569 po := &corev1.Pod{ 570 ObjectMeta: metav1.ObjectMeta{ 571 Labels: map[string]string{ 572 common.ArgoCDKeyName: fmt.Sprintf("%s-%s", a.Name, "application-controller"), 573 }, 574 }, 575 } 576 objs := []client.Object{ 577 po, 578 a, 579 } 580 581 runtimeObjs := []runtime.Object{} 582 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 583 cl := makeTestReconcilerClient(sch, objs, objs, runtimeObjs) 584 r := makeTestReconciler(cl, sch) 585 586 if containsInvalidImage(a, r) { 587 t.Fatalf("containsInvalidImage failed, got true, expected false") 588 } 589 590 } 591 592 func TestReconcileArgoCD_reconcileApplicationController_withDynamicSharding(t *testing.T) { 593 logf.SetLogger(ZapLogger(true)) 594 595 tests := []struct { 596 sharding argoproj.ArgoCDApplicationControllerShardSpec 597 expectedReplicas int32 598 vars []corev1.EnvVar 599 }{ 600 { 601 sharding: argoproj.ArgoCDApplicationControllerShardSpec{ 602 Enabled: false, 603 Replicas: 1, 604 DynamicScalingEnabled: boolPtr(true), 605 MinShards: 2, 606 MaxShards: 4, 607 ClustersPerShard: 1, 608 }, 609 expectedReplicas: 3, 610 }, 611 { 612 // Replicas less than minimum shards 613 sharding: argoproj.ArgoCDApplicationControllerShardSpec{ 614 Enabled: false, 615 Replicas: 1, 616 DynamicScalingEnabled: boolPtr(true), 617 MinShards: 1, 618 MaxShards: 4, 619 ClustersPerShard: 3, 620 }, 621 expectedReplicas: 1, 622 }, 623 { 624 // Replicas more than maximum shards 625 sharding: argoproj.ArgoCDApplicationControllerShardSpec{ 626 Enabled: false, 627 Replicas: 1, 628 DynamicScalingEnabled: boolPtr(true), 629 MinShards: 1, 630 MaxShards: 2, 631 ClustersPerShard: 1, 632 }, 633 expectedReplicas: 2, 634 }, 635 } 636 637 for _, st := range tests { 638 a := makeTestArgoCD(func(a *argoproj.ArgoCD) { 639 a.Spec.Controller.Sharding = st.sharding 640 }) 641 642 clusterSecret1 := argoutil.NewSecretWithSuffix(a, "cluster1") 643 clusterSecret1.Labels = map[string]string{common.ArgoCDSecretTypeLabel: "cluster"} 644 645 clusterSecret2 := argoutil.NewSecretWithSuffix(a, "cluster2") 646 clusterSecret2.Labels = map[string]string{common.ArgoCDSecretTypeLabel: "cluster"} 647 648 clusterSecret3 := argoutil.NewSecretWithSuffix(a, "cluster3") 649 clusterSecret3.Labels = map[string]string{common.ArgoCDSecretTypeLabel: "cluster"} 650 651 resObjs := []client.Object{a} 652 subresObjs := []client.Object{a} 653 runtimeObjs := []runtime.Object{} 654 sch := makeTestReconcilerScheme(argoproj.AddToScheme) 655 cl := makeTestReconcilerClient(sch, resObjs, subresObjs, runtimeObjs) 656 r := makeTestReconciler(cl, sch) 657 658 assert.NoError(t, r.Client.Create(context.TODO(), clusterSecret1)) 659 assert.NoError(t, r.Client.Create(context.TODO(), clusterSecret2)) 660 assert.NoError(t, r.Client.Create(context.TODO(), clusterSecret3)) 661 662 replicas := r.getApplicationControllerReplicaCount(a) 663 664 assert.Equal(t, int32(st.expectedReplicas), replicas) 665 666 } 667 }