diff --git a/common/common.go b/common/common.go index 65e7347e82c..1eb9372df4a 100644 --- a/common/common.go +++ b/common/common.go @@ -17,7 +17,11 @@ limitations under the License. package common import ( + "fmt" + "maps" "os" + "slices" + "strings" ) const ( @@ -57,6 +61,31 @@ func InfraNodeSelector() map[string]string { } } +// NamespaceNodeSelectorAnnotation builds the openshift.io/node-selector value for the +// default GitOps namespace. runOnInfra uses the infra role; GitopsService nodeSelector +// entries are merged. An empty result means the annotation should be removed. +func NamespaceNodeSelectorAnnotation(runOnInfra bool, nodeSelector map[string]string) string { + if !runOnInfra && len(nodeSelector) == 0 { + return "" + } + if runOnInfra && len(nodeSelector) == 0 { + return InfraNodeSelectorAnnotationValue + } + sel := maps.Clone(nodeSelector) + if sel == nil { + sel = map[string]string{} + } + if runOnInfra { + sel[InfraNodeLabelSelector] = "" + } + keys := slices.Sorted(maps.Keys(sel)) + parts := make([]string, 0, len(keys)) + for _, k := range keys { + parts = append(parts, fmt.Sprintf("%s=%s", k, sel[k])) + } + return strings.Join(parts, ",") +} + func StringFromEnv(env string, defaultValue string) string { if str := os.Getenv(env); str != "" { return str diff --git a/common/common_test.go b/common/common_test.go new file mode 100644 index 00000000000..0d6b8252d14 --- /dev/null +++ b/common/common_test.go @@ -0,0 +1,36 @@ +package common + +import "testing" + +func TestNamespaceNodeSelectorAnnotation(t *testing.T) { + tests := map[string]struct { + runOnInfra bool + nodeSelector map[string]string + want string + }{ + "neither": { + want: "", + }, + "runOnInfra only keeps exact infra value": { + runOnInfra: true, + want: InfraNodeSelectorAnnotationValue, + }, + "custom selector": { + nodeSelector: map[string]string{"foo": "bar"}, + want: "foo=bar", + }, + "runOnInfra plus custom merges": { + runOnInfra: true, + nodeSelector: map[string]string{"foo": "bar"}, + want: "foo=bar,node-role.kubernetes.io/infra=", + }, + } + for name, tt := range tests { + t.Run(name, func(t *testing.T) { + got := NamespaceNodeSelectorAnnotation(tt.runOnInfra, tt.nodeSelector) + if got != tt.want { + t.Fatalf("got %q, want %q", got, tt.want) + } + }) + } +} diff --git a/controllers/gitopsservice_controller.go b/controllers/gitopsservice_controller.go index 37bc08080dc..714a1f24290 100644 --- a/controllers/gitopsservice_controller.go +++ b/controllers/gitopsservice_controller.go @@ -23,6 +23,7 @@ import ( "os" "reflect" "strings" + "time" "github.com/go-logr/logr" version "github.com/hashicorp/go-version" @@ -79,6 +80,10 @@ const ( PodSecurityLabelSyncLabel = "security.openshift.io/scc.podSecurityLabelSync" PodSecurityLabelSyncLabelValue = "true" kamResourceName = "kam" + // nodeSelectorRestartedAtAnnotation is bumped on default-instance workloads when + // openshift.io/node-selector on the namespace changes, so existing pods are replaced + // and pick up the new selector at admission. + nodeSelectorRestartedAtAnnotation = "gitops.openshift.io/node-selector-restartedAt" ) // SetupWithManager sets up the controller with the Manager. @@ -276,7 +281,7 @@ func (r *ReconcileGitopsService) Reconcile(ctx context.Context, request reconcil if err != nil { if errors.IsNotFound(err) { reqLogger.Info("Creating a new Namespace", "Name", namespace) - ensureInfraNodeSelectorAnnotation(namespaceRef, instance.Spec.RunOnInfra) + ensureInfraNodeSelectorAnnotation(namespaceRef, instance.Spec.RunOnInfra, instance.Spec.NodeSelector) err = r.Client.Create(ctx, namespaceRef) if err != nil { return reconcile.Result{}, err @@ -285,12 +290,18 @@ func (r *ReconcileGitopsService) Reconcile(ctx context.Context, request reconcil return reconcile.Result{}, err } } else { - if ensureNamespaceMetadata(namespaceRef, instance.Spec.RunOnInfra) { + changed, selectorChanged := ensureNamespaceMetadata(namespaceRef, instance.Spec.RunOnInfra, instance.Spec.NodeSelector) + if changed { err = r.Client.Update(context.TODO(), namespaceRef) if err != nil { return reconcile.Result{}, err } } + if selectorChanged { + if err := r.restartWorkloadsForNodeSelector(namespaceRef.Name, reqLogger); err != nil { + return reconcile.Result{}, err + } + } } // Create/reconcile the default Argo CD instance @@ -492,7 +503,7 @@ func (r *ReconcileGitopsService) reconcileDefaultArgoCDInstance(instance *pipeli if err != nil { if errors.IsNotFound(err) { reqLogger.Info("Creating a new Namespace", "Name", argocdNS.Name) - ensureInfraNodeSelectorAnnotation(argocdNS, instance.Spec.RunOnInfra) + ensureInfraNodeSelectorAnnotation(argocdNS, instance.Spec.RunOnInfra, instance.Spec.NodeSelector) err = r.Client.Create(context.TODO(), argocdNS) if err != nil { return reconcile.Result{}, err @@ -522,12 +533,18 @@ func (r *ReconcileGitopsService) reconcileDefaultArgoCDInstance(instance *pipeli } } - if ensureNamespaceMetadata(argocdNS, instance.Spec.RunOnInfra) { + changed, selectorChanged := ensureNamespaceMetadata(argocdNS, instance.Spec.RunOnInfra, instance.Spec.NodeSelector) + if changed { err = r.Client.Update(context.TODO(), argocdNS) if err != nil { return reconcile.Result{}, err } } + if selectorChanged { + if err := r.restartWorkloadsForNodeSelector(argocdNS.Name, reqLogger); err != nil { + return reconcile.Result{}, err + } + } } @@ -536,15 +553,9 @@ func (r *ReconcileGitopsService) reconcileDefaultArgoCDInstance(instance *pipeli return reconcile.Result{}, err } - if len(instance.Spec.NodeSelector) > 0 { - if defaultArgoCDInstance.Spec.NodePlacement == nil { - defaultArgoCDInstance.Spec.NodePlacement = &argoapp.ArgoCDNodePlacementSpec{ - NodeSelector: instance.Spec.NodeSelector, - } - } else { - defaultArgoCDInstance.Spec.NodePlacement.NodeSelector = argocdutil.AppendStringMap(defaultArgoCDInstance.Spec.NodePlacement.NodeSelector, instance.Spec.NodeSelector) - } - } + // Node selectors for the default instance are applied on the openshift-gitops + // namespace (openshift.io/node-selector), not on ArgoCD spec.nodePlacement. + // GitopsService still owns NodePlacement.Tolerations for taints. if len(instance.Spec.Tolerations) > 0 { if defaultArgoCDInstance.Spec.NodePlacement == nil { defaultArgoCDInstance.Spec.NodePlacement = &argoapp.ArgoCDNodePlacementSpec{ @@ -620,13 +631,13 @@ func (r *ReconcileGitopsService) reconcileDefaultArgoCDInstance(instance *pipeli changed = true } - // if user is patching nodePlacement through GitopsService CR, then existingArgoCD NodePlacement is updated. + // GitopsService owns the default ArgoCD instance. Sync NodePlacement from + // GitopsService (tolerations only; node selectors use the namespace annotation). if defaultArgoCDInstance.Spec.NodePlacement != nil { if !reflect.DeepEqual(existingArgoCD.Spec.NodePlacement, defaultArgoCDInstance.Spec.NodePlacement) { existingArgoCD.Spec.NodePlacement = defaultArgoCDInstance.Spec.NodePlacement changed = true } - // Handle the case where NodePlacement should be removed } else if existingArgoCD.Spec.NodePlacement != nil { existingArgoCD.Spec.NodePlacement = defaultArgoCDInstance.Spec.NodePlacement changed = true @@ -1133,13 +1144,53 @@ func policyRuleForBackendServiceClusterRole() []rbacv1.PolicyRule { } } -func ensureNamespaceMetadata(namespace *corev1.Namespace, runOnInfra bool) bool { - changed := false +func ensureNamespaceMetadata(namespace *corev1.Namespace, runOnInfra bool, nodeSelector map[string]string) (changed bool, nodeSelectorChanged bool) { labelUpdate, _ := ensurePodSecurityLabels(namespace) - changed = changed || labelUpdate - annotationUpdate, _ := ensureInfraNodeSelectorAnnotation(namespace, runOnInfra) - changed = changed || annotationUpdate - return changed + annotationUpdate, _ := ensureInfraNodeSelectorAnnotation(namespace, runOnInfra, nodeSelector) + return labelUpdate || annotationUpdate, annotationUpdate +} + +func (r *ReconcileGitopsService) restartWorkloadsForNodeSelector(namespace string, reqLogger logr.Logger) error { + now := time.Now().UTC().Format(time.RFC3339) + reqLogger.Info("Restarting workloads so pods pick up namespace node-selector changes", "Namespace", namespace) + + deploys := &appsv1.DeploymentList{} + if err := r.Client.List(context.TODO(), deploys, client.InNamespace(namespace)); err != nil { + return err + } + for i := range deploys.Items { + d := &deploys.Items[i] + // Backend deployment already rolls via its own pod-template nodeSelector sync. + if d.Name == serviceName { + continue + } + if err := r.patchPodTemplateRestartedAt(d, &d.Spec.Template, now); err != nil { + return err + } + } + + statefuls := &appsv1.StatefulSetList{} + if err := r.Client.List(context.TODO(), statefuls, client.InNamespace(namespace)); err != nil { + return err + } + for i := range statefuls.Items { + ss := &statefuls.Items[i] + if err := r.patchPodTemplateRestartedAt(ss, &ss.Spec.Template, now); err != nil { + return err + } + } + return nil +} + +func (r *ReconcileGitopsService) patchPodTemplateRestartedAt(obj client.Object, template *corev1.PodTemplateSpec, now string) error { + if template.Annotations == nil { + template.Annotations = map[string]string{} + } + if template.Annotations[nodeSelectorRestartedAtAnnotation] == now { + return nil + } + template.Annotations[nodeSelectorRestartedAtAnnotation] = now + return r.Client.Update(context.TODO(), obj) } func ensurePodSecurityLabels(namespace *corev1.Namespace) (bool, *corev1.Namespace) { @@ -1153,20 +1204,22 @@ func ensurePodSecurityLabels(namespace *corev1.Namespace) (bool, *corev1.Namespa return false, namespace } -func ensureInfraNodeSelectorAnnotation(namespace *corev1.Namespace, runOnInfra bool) (bool, *corev1.Namespace) { +func ensureInfraNodeSelectorAnnotation(namespace *corev1.Namespace, runOnInfra bool, nodeSelector map[string]string) (bool, *corev1.Namespace) { if namespace.Annotations == nil { namespace.Annotations = make(map[string]string) } - if runOnInfra { - if namespace.Annotations[common.InfraNodeSelectorAnnotation] != common.InfraNodeSelectorAnnotationValue { - namespace.Annotations[common.InfraNodeSelectorAnnotation] = common.InfraNodeSelectorAnnotationValue - return true, namespace - } - } else { - if namespace.Annotations[common.InfraNodeSelectorAnnotation] == common.InfraNodeSelectorAnnotationValue { - delete(namespace.Annotations, common.InfraNodeSelectorAnnotation) - return true, namespace + want := common.NamespaceNodeSelectorAnnotation(runOnInfra, nodeSelector) + got := namespace.Annotations[common.InfraNodeSelectorAnnotation] + if want == "" { + if got == "" { + return false, namespace } + delete(namespace.Annotations, common.InfraNodeSelectorAnnotation) + return true, namespace } - return false, namespace + if got == want { + return false, namespace + } + namespace.Annotations[common.InfraNodeSelectorAnnotation] = want + return true, namespace } diff --git a/controllers/gitopsservice_controller_test.go b/controllers/gitopsservice_controller_test.go index 1d938d62ed8..f1f02c74501 100644 --- a/controllers/gitopsservice_controller_test.go +++ b/controllers/gitopsservice_controller_test.go @@ -219,8 +219,6 @@ func TestReconcileDefaultForArgoCDNodeplacement(t *testing.T) { s := scheme.Scheme addKnownTypesToScheme(s) - var err error - gitopsService := &pipelinesv1alpha1.GitopsService{ ObjectMeta: v1.ObjectMeta{ Name: serviceName, @@ -256,18 +254,103 @@ func TestReconcileDefaultForArgoCDNodeplacement(t *testing.T) { }, } - err = fakeClient.Create(context.TODO(), existingArgoCD) + err := fakeClient.Create(context.TODO(), existingArgoCD) + assertNoError(t, err) + + _, err = reconciler.Reconcile(context.TODO(), newRequest("test", "test")) + assertNoError(t, err) + + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: common.ArgoCDInstanceName, Namespace: serviceNamespace}, + existingArgoCD) + assertNoError(t, err) + // Node selectors are applied on the namespace, not on the default ArgoCD CR. + assert.Assert(t, existingArgoCD.Spec.NodePlacement == nil) + + ns := &corev1.Namespace{} + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: serviceNamespace}, ns) + assertNoError(t, err) + assert.Equal(t, ns.Annotations[common.InfraNodeSelectorAnnotation], "foo=bar") +} + +func TestReconcileDefaultArgoCDNodePlacementClearsWhenGitopsServiceCleared(t *testing.T) { + logf.SetLogger(argocd.ZapLogger(true)) + s := scheme.Scheme + addKnownTypesToScheme(s) + + gitopsService := &pipelinesv1alpha1.GitopsService{ + ObjectMeta: v1.ObjectMeta{ + Name: serviceName, + }, + Spec: pipelinesv1alpha1.GitopsServiceSpec{ + NodeSelector: map[string]string{ + "key1": "value1", + }, + Tolerations: deploymentDefaultTolerations(), + }, + } + + fakeClient := fake.NewFakeClient(gitopsService) + reconciler := newReconcileGitOpsService(fakeClient, s) + + existingArgoCD := &argoapp.ArgoCD{ + ObjectMeta: v1.ObjectMeta{ + Name: serviceNamespace, + Namespace: serviceNamespace, + }, + Spec: argoapp.ArgoCDSpec{ + Server: argoapp.ArgoCDServerSpec{ + Route: argoapp.ArgoCDRouteSpec{ + Enabled: true, + }, + }, + ApplicationSet: &argoapp.ArgoCDApplicationSet{}, + SSO: &argoapp.ArgoCDSSOSpec{ + Provider: "dex", + Dex: &argoapp.ArgoCDDexSpec{ + Config: "test-config", + }, + }, + }, + } + + err := fakeClient.Create(context.TODO(), existingArgoCD) assertNoError(t, err) _, err = reconciler.Reconcile(context.TODO(), newRequest("test", "test")) assertNoError(t, err) - // verify whether existingArgoCD NodePlacement is updated when user is patching nodePlacement through GitopsService CR err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: common.ArgoCDInstanceName, Namespace: serviceNamespace}, existingArgoCD) assertNoError(t, err) assert.Check(t, existingArgoCD.Spec.NodePlacement != nil) - assert.DeepEqual(t, existingArgoCD.Spec.NodePlacement.NodeSelector, gitopsService.Spec.NodeSelector) + assert.Assert(t, existingArgoCD.Spec.NodePlacement.NodeSelector == nil) + assert.DeepEqual(t, existingArgoCD.Spec.NodePlacement.Tolerations, deploymentDefaultTolerations()) + + ns := &corev1.Namespace{} + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: serviceNamespace}, ns) + assertNoError(t, err) + assert.Equal(t, ns.Annotations[common.InfraNodeSelectorAnnotation], "key1=value1") + + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: serviceName}, gitopsService) + assertNoError(t, err) + gitopsService.Spec.NodeSelector = nil + gitopsService.Spec.Tolerations = nil + err = fakeClient.Update(context.TODO(), gitopsService) + assertNoError(t, err) + + _, err = reconciler.Reconcile(context.TODO(), newRequest("test", "test")) + assertNoError(t, err) + + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: common.ArgoCDInstanceName, Namespace: serviceNamespace}, + existingArgoCD) + assertNoError(t, err) + assert.Assert(t, existingArgoCD.Spec.NodePlacement == nil) + + ns = &corev1.Namespace{} + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: serviceNamespace}, ns) + assertNoError(t, err) + _, hasAnnotation := ns.Annotations[common.InfraNodeSelectorAnnotation] + assert.Assert(t, !hasAnnotation) } // If the DISABLE_DEFAULT_ARGOCD_INSTANCE is set, ensure that the default ArgoCD instance is not created. @@ -902,6 +985,59 @@ func TestReconcile_InfrastructureNode_AnnotationRemoved(t *testing.T) { assert.Assert(t, !hasAnnotation) } +func TestReconcile_InfrastructureNode_RestartsExistingWorkloads(t *testing.T) { + logf.SetLogger(argocd.ZapLogger(true)) + s := scheme.Scheme + addKnownTypesToScheme(s) + + ns := &corev1.Namespace{ObjectMeta: v1.ObjectMeta{Name: serviceNamespace}} + gitopsService := &pipelinesv1alpha1.GitopsService{ + ObjectMeta: v1.ObjectMeta{Name: serviceName}, + Spec: pipelinesv1alpha1.GitopsServiceSpec{ + RunOnInfra: true, + }, + } + server := &appsv1.Deployment{ + ObjectMeta: v1.ObjectMeta{Name: "openshift-gitops-server", Namespace: serviceNamespace}, + Spec: appsv1.DeploymentSpec{ + Selector: &v1.LabelSelector{MatchLabels: map[string]string{"app": "server"}}, + Template: corev1.PodTemplateSpec{ + ObjectMeta: v1.ObjectMeta{Labels: map[string]string{"app": "server"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "server", Image: "test"}}}, + }, + }, + } + controller := &appsv1.StatefulSet{ + ObjectMeta: v1.ObjectMeta{Name: "openshift-gitops-application-controller", Namespace: serviceNamespace}, + Spec: appsv1.StatefulSetSpec{ + Selector: &v1.LabelSelector{MatchLabels: map[string]string{"app": "controller"}}, + Template: corev1.PodTemplateSpec{ + ObjectMeta: v1.ObjectMeta{Labels: map[string]string{"app": "controller"}}, + Spec: corev1.PodSpec{Containers: []corev1.Container{{Name: "controller", Image: "test"}}}, + }, + }, + } + + fakeClient := fake.NewFakeClient(gitopsService, ns, server, controller) + reconciler := newReconcileGitOpsService(fakeClient, s) + + _, err := reconciler.Reconcile(context.TODO(), newRequest("test", "test")) + assertNoError(t, err) + + updatedNS := &corev1.Namespace{} + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: serviceNamespace}, updatedNS) + assertNoError(t, err) + assert.Equal(t, updatedNS.Annotations[common.InfraNodeSelectorAnnotation], common.InfraNodeSelectorAnnotationValue) + + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: server.Name, Namespace: serviceNamespace}, server) + assertNoError(t, err) + assert.Assert(t, server.Spec.Template.Annotations[nodeSelectorRestartedAtAnnotation] != "") + + err = fakeClient.Get(context.TODO(), types.NamespacedName{Name: controller.Name, Namespace: serviceNamespace}, controller) + assertNoError(t, err) + assert.Assert(t, controller.Spec.Template.Annotations[nodeSelectorRestartedAtAnnotation] != "") +} + func TestReconcile_PSSLabels(t *testing.T) { logf.SetLogger(argocd.ZapLogger(true)) s := scheme.Scheme diff --git a/test/openshift/e2e/ginkgo/sequential/1-071_validate_node_selectors_test.go b/test/openshift/e2e/ginkgo/sequential/1-071_validate_node_selectors_test.go index 7e7556aa921..c2655091863 100644 --- a/test/openshift/e2e/ginkgo/sequential/1-071_validate_node_selectors_test.go +++ b/test/openshift/e2e/ginkgo/sequential/1-071_validate_node_selectors_test.go @@ -68,19 +68,27 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { gs.Spec.NodeSelector["key1"] = "value1" }) - By("ensuring Deployments and StatefulSets pick up the change we made to nodeSelector in GitOpsService CR") + By("ensuring the openshift-gitops namespace annotation picks up GitOpsService nodeSelector") + openshiftGitopsNS := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "openshift-gitops"}} + Eventually(openshiftGitopsNS).Should(namespaceFixture.HaveAnnotation(common.InfraNodeSelectorAnnotation, "key1=value1")) - for _, deploymentName := range deploymentNameList { + By("ensuring the GitOps Service backend Deployment still has nodeSelector on the pod template") + clusterDepl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: "cluster", Namespace: "openshift-gitops"}} + Eventually(clusterDepl).Should(deploymentFixture.HaveTemplateSpecNodeSelector(map[string]string{ + "kubernetes.io/os": "linux", + "key1": "value1", + })) + + By("ensuring Argo CD workload templates keep the default nodeSelector (namespace annotation schedules them)") + argoCDDeploymentNames := []string{"openshift-gitops-server", "openshift-gitops-repo-server", "openshift-gitops-dex-server", "openshift-gitops-redis"} + for _, deploymentName := range argoCDDeploymentNames { depl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: deploymentName, Namespace: "openshift-gitops"}} Eventually(depl).Should(deploymentFixture.HaveTemplateSpecNodeSelector(map[string]string{ "kubernetes.io/os": "linux", - "key1": "value1", })) } - Eventually(appControllerSS).Should(statefulsetFixture.HaveTemplateSpecNodeSelector(map[string]string{ "kubernetes.io/os": "linux", - "key1": "value1", })) By("enabling runOnInfra and setting various tolerations on GitOpsService") @@ -93,17 +101,26 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { Value: "reserved"}} }) - By("ensuring the openshift-gitops namespace has the infra node-selector annotation") - openshiftGitopsNS := &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: "openshift-gitops"}} - Eventually(openshiftGitopsNS).Should(namespaceFixture.HaveAnnotation(common.InfraNodeSelectorAnnotation, common.InfraNodeSelectorAnnotationValue)) + By("ensuring the openshift-gitops namespace annotation merges infra and custom selectors") + Eventually(openshiftGitopsNS).Should(namespaceFixture.HaveAnnotation(common.InfraNodeSelectorAnnotation, + common.NamespaceNodeSelectorAnnotation(true, map[string]string{"key1": "value1"}))) - By("ensuring Deployments and StatefulSets still have the custom nodeSelector and tolerations") + By("ensuring the GitOps Service backend Deployment still has the custom nodeSelector and tolerations") + Eventually(clusterDepl).Should(deploymentFixture.HaveTemplateSpecNodeSelector(map[string]string{ + "kubernetes.io/os": "linux", + "key1": "value1", + })) + Eventually(clusterDepl).Should(deploymentFixture.HaveTolerations([]corev1.Toleration{{ + Effect: "NoSchedule", + Key: "infra", + Value: "reserved", + }})) - for _, deploymentName := range deploymentNameList { + By("ensuring Argo CD workloads get GitopsService tolerations, with node selection via the namespace annotation") + for _, deploymentName := range argoCDDeploymentNames { depl := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{Name: deploymentName, Namespace: "openshift-gitops"}} Eventually(depl).Should(deploymentFixture.HaveTemplateSpecNodeSelector(map[string]string{ "kubernetes.io/os": "linux", - "key1": "value1", })) Eventually(depl).Should(deploymentFixture.HaveTolerations([]corev1.Toleration{{ Effect: "NoSchedule", @@ -111,10 +128,8 @@ var _ = Describe("GitOps Operator Sequential E2E Tests", func() { Value: "reserved", }})) } - Eventually(appControllerSS).Should(statefulsetFixture.HaveTemplateSpecNodeSelector(map[string]string{ "kubernetes.io/os": "linux", - "key1": "value1", })) Eventually(appControllerSS).Should(statefulsetFixture.HaveTolerations([]corev1.Toleration{{ Effect: "NoSchedule",