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  }