From 45c758608abeaa2c5126879c8d71d9128ade7cbb Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Wed, 3 Sep 2025 10:50:32 +0300 Subject: [PATCH 01/13] K8SPS-378 set RaftEnabledSingleNode when orchestrator size is configured to one --- pkg/controller/ps/controller.go | 13 +++++++++++++ pkg/orchestrator/orchestrator.go | 7 ++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/pkg/controller/ps/controller.go b/pkg/controller/ps/controller.go index 0672291d1..4fb12c208 100644 --- a/pkg/controller/ps/controller.go +++ b/pkg/controller/ps/controller.go @@ -801,6 +801,19 @@ func (r *PerconaServerMySQLReconciler) reconcileOrchestrator(ctx context.Context } else { oldPeers := util.Difference(existingNodes, raftNodes) + if cr.Spec.Orchestrator.Size == 1 && len(oldPeers) > 0 { + log.Info("Scaling down to single node, enabling single node mode first", "oldPeers", oldPeers) + + cmData, err := orchestrator.ConfigMapData(cr) + if err != nil { + return errors.Wrap(err, "get ConfigMap data for single node") + } + + if err := k8s.EnsureObjectWithHash(ctx, r.Client, cr, orchestrator.ConfigMap(cr, cmData), r.Scheme); err != nil { + return errors.Wrap(err, "update ConfigMap for single node") + } + } + for _, peer := range oldPeers { p := peer g.Go(func() error { diff --git a/pkg/orchestrator/orchestrator.go b/pkg/orchestrator/orchestrator.go index 05f587027..9d5bfc3af 100644 --- a/pkg/orchestrator/orchestrator.go +++ b/pkg/orchestrator/orchestrator.go @@ -482,6 +482,11 @@ func orcConfig(cr *apiv1alpha1.PerconaServerMySQL) (string, error) { config := make(map[string]interface{}, 0) config["RaftNodes"] = RaftNodes(cr) + + if cr.Spec.Orchestrator.Size == 1 { + config["RaftEnabledSingleNode"] = true + } + configJson, err := json.Marshal(config) if err != nil { return "", errors.Wrap(err, "marshal orchestrator raft nodes to json") @@ -491,7 +496,7 @@ func orcConfig(cr *apiv1alpha1.PerconaServerMySQL) (string, error) { } func ConfigMapData(cr *apiv1alpha1.PerconaServerMySQL) (map[string]string, error) { - cmData := make(map[string]string, 0) + cmData := make(map[string]string) config, err := orcConfig(cr) if err != nil { From aebd4e62a825c95e9e2c691ca03a52bb75ffe9d5 Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Thu, 4 Sep 2025 14:15:04 +0300 Subject: [PATCH 02/13] remove unused function --- pkg/orchestrator/orchestrator.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pkg/orchestrator/orchestrator.go b/pkg/orchestrator/orchestrator.go index 8f2a2aa99..c80502c6d 100644 --- a/pkg/orchestrator/orchestrator.go +++ b/pkg/orchestrator/orchestrator.go @@ -93,10 +93,6 @@ func FQDN(cr *apiv1alpha1.PerconaServerMySQL, idx int) string { return fmt.Sprintf("%s.%s.svc", PodName(cr, idx), cr.Namespace) } -func APIHost(cr *apiv1alpha1.PerconaServerMySQL) string { - return fmt.Sprintf("http://%s:%d", FQDN(cr, 0), defaultWebPort) -} - // Labels returns labels of orchestrator func Labels(cr *apiv1alpha1.PerconaServerMySQL) map[string]string { return util.SSMapMerge(cr.GlobalLabels(), cr.OrchestratorSpec().Labels, MatchLabels(cr)) From e5f9f0294a1819a6d181bac7561bbef7552f9dcc Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Fri, 5 Sep 2025 15:15:04 +0300 Subject: [PATCH 03/13] restart orch when config changes --- pkg/controller/ps/controller.go | 16 ++++++++++++++++ pkg/orchestrator/orchestrator.go | 1 + 2 files changed, 17 insertions(+) diff --git a/pkg/controller/ps/controller.go b/pkg/controller/ps/controller.go index 4664c71ca..511085b7c 100644 --- a/pkg/controller/ps/controller.go +++ b/pkg/controller/ps/controller.go @@ -809,9 +809,25 @@ func (r *PerconaServerMySQLReconciler) reconcileOrchestrator(ctx context.Context return errors.Wrap(err, "get ConfigMap data for single node") } + configMap := orchestrator.ConfigMap(cr, cmData) + if err := k8s.EnsureObjectWithHash(ctx, r.Client, cr, orchestrator.ConfigMap(cr, cmData), r.Scheme); err != nil { return errors.Wrap(err, "update ConfigMap for single node") } + + configMapHash, err := k8s.ObjectHash(configMap) + if err != nil { + return errors.Wrap(err, "calculate config map hash") + } + + sts := &appsv1.StatefulSet{} + if err := r.Client.Get(ctx, orchestrator.NamespacedName(cr), sts); err != nil { + return errors.Wrap(err, "get orchestrator sts") + } + + if err := k8s.RolloutRestart(ctx, r.Client, sts, naming.AnnotationLastConfigHash, configMapHash); err != nil { + return errors.Wrap(err, "restart orchestrator for config change") + } } for _, peer := range oldPeers { diff --git a/pkg/orchestrator/orchestrator.go b/pkg/orchestrator/orchestrator.go index c80502c6d..6a78977f4 100644 --- a/pkg/orchestrator/orchestrator.go +++ b/pkg/orchestrator/orchestrator.go @@ -489,6 +489,7 @@ func orcConfig(cr *apiv1alpha1.PerconaServerMySQL) (string, error) { config["RaftNodes"] = RaftNodes(cr) + config["RaftEnabledSingleNode"] = false if cr.Spec.Orchestrator.Size == 1 { config["RaftEnabledSingleNode"] = true } From d66efd3f44b7a5b5d2e2aa59066ac9b0194f3aee Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Fri, 5 Sep 2025 15:16:05 +0300 Subject: [PATCH 04/13] wiring fix --- config/manager/cluster/kustomization.yaml | 2 +- config/manager/kustomization.yaml | 2 +- deploy/bundle.yaml | 2 +- deploy/cr.yaml | 10 +++++----- deploy/cw-bundle.yaml | 2 +- deploy/cw-operator.yaml | 2 +- deploy/operator.yaml | 2 +- pkg/controller/ps/controller.go | 2 +- 8 files changed, 12 insertions(+), 12 deletions(-) diff --git a/config/manager/cluster/kustomization.yaml b/config/manager/cluster/kustomization.yaml index 43d283c3e..530e7c3a8 100644 --- a/config/manager/cluster/kustomization.yaml +++ b/config/manager/cluster/kustomization.yaml @@ -13,4 +13,4 @@ kind: Kustomization images: - name: perconalab/percona-server-mysql-operator newName: perconalab/percona-server-mysql-operator - newTag: main + newTag: K8SPS-378-5 diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index f9fa17fd8..fc99c7ab1 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -16,4 +16,4 @@ images: newTag: main - name: perconalab/percona-server-mysql-operator newName: perconalab/percona-server-mysql-operator - newTag: main + newTag: K8SPS-378-5 diff --git a/deploy/bundle.yaml b/deploy/bundle.yaml index 6a78b846d..2e8741728 100644 --- a/deploy/bundle.yaml +++ b/deploy/bundle.yaml @@ -13461,7 +13461,7 @@ spec: fieldPath: metadata.namespace - name: DISABLE_TELEMETRY value: "false" - image: perconalab/percona-server-mysql-operator:main + image: perconalab/percona-server-mysql-operator:K8SPS-378-5 imagePullPolicy: Always livenessProbe: httpGet: diff --git a/deploy/cr.yaml b/deploy/cr.yaml index 6cfc73d5d..24100d825 100644 --- a/deploy/cr.yaml +++ b/deploy/cr.yaml @@ -13,10 +13,10 @@ spec: # service.beta.kubernetes.io/aws-load-balancer-type: nlb # labels: # rack: rack-22 -# unsafeFlags: + unsafeFlags: # mysqlSize: false # orchestrator: false -# orchestratorSize: false + orchestratorSize: true # proxy: false # proxySize: false # pause: false @@ -56,7 +56,7 @@ spec: # group: cert-manager.io mysql: - clusterType: group-replication + clusterType: async autoRecovery: true image: perconalab/percona-server-mysql-operator:main-psmysql8.0 imagePullPolicy: Always @@ -446,7 +446,7 @@ spec: # - 10.0.0.0/8 orchestrator: - enabled: false + enabled: true image: perconalab/percona-server-mysql-operator:main-orchestrator imagePullPolicy: Always # runtimeClassName: image-rc @@ -473,7 +473,7 @@ spec: # memory: 200M # cpu: 200m - size: 3 + size: 1 affinity: antiAffinityTopologyKey: "kubernetes.io/hostname" diff --git a/deploy/cw-bundle.yaml b/deploy/cw-bundle.yaml index 05ebb14d7..fdae98c09 100644 --- a/deploy/cw-bundle.yaml +++ b/deploy/cw-bundle.yaml @@ -13471,7 +13471,7 @@ spec: value: "" - name: DISABLE_TELEMETRY value: "false" - image: perconalab/percona-server-mysql-operator:main + image: perconalab/percona-server-mysql-operator:K8SPS-378-5 imagePullPolicy: Always livenessProbe: httpGet: diff --git a/deploy/cw-operator.yaml b/deploy/cw-operator.yaml index d6527b616..f97fef8f4 100644 --- a/deploy/cw-operator.yaml +++ b/deploy/cw-operator.yaml @@ -48,7 +48,7 @@ spec: value: "" - name: DISABLE_TELEMETRY value: "false" - image: perconalab/percona-server-mysql-operator:main + image: perconalab/percona-server-mysql-operator:K8SPS-378-5 imagePullPolicy: Always livenessProbe: httpGet: diff --git a/deploy/operator.yaml b/deploy/operator.yaml index 43f942675..a19b906d3 100644 --- a/deploy/operator.yaml +++ b/deploy/operator.yaml @@ -51,7 +51,7 @@ spec: fieldPath: metadata.namespace - name: DISABLE_TELEMETRY value: "false" - image: perconalab/percona-server-mysql-operator:main + image: perconalab/percona-server-mysql-operator:K8SPS-378-5 imagePullPolicy: Always livenessProbe: httpGet: diff --git a/pkg/controller/ps/controller.go b/pkg/controller/ps/controller.go index 123542590..069bd6c5c 100644 --- a/pkg/controller/ps/controller.go +++ b/pkg/controller/ps/controller.go @@ -812,7 +812,7 @@ func (r *PerconaServerMySQLReconciler) reconcileOrchestrator(ctx context.Context configMap := orchestrator.ConfigMap(cr, cmData) - if err := k8s.EnsureObjectWithHash(ctx, r.Client, cr, orchestrator.ConfigMap(cr, cmData), r.Scheme); err != nil { + if err := k8s.EnsureObjectWithHash(ctx, r.Client, cr, configMap, r.Scheme); err != nil { return errors.Wrap(err, "update ConfigMap for single node") } From 222a341f64624a05ea826dcb122e9ce5baedd8b7 Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Fri, 5 Sep 2025 17:32:43 +0300 Subject: [PATCH 05/13] apply the cm rollout changes regardless of the size --- pkg/controller/ps/controller.go | 47 ++++++++++++--------------------- 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/pkg/controller/ps/controller.go b/pkg/controller/ps/controller.go index 069bd6c5c..25aa3387a 100644 --- a/pkg/controller/ps/controller.go +++ b/pkg/controller/ps/controller.go @@ -766,7 +766,9 @@ func (r *PerconaServerMySQLReconciler) reconcileOrchestrator(ctx context.Context return errors.Wrap(err, "get ConfigMap data") } - if err := k8s.EnsureObjectWithHash(ctx, r.Client, cr, orchestrator.ConfigMap(cr, cmData), r.Scheme); err != nil { + configMap := orchestrator.ConfigMap(cr, cmData) + + if err := k8s.EnsureObjectWithHash(ctx, r.Client, cr, configMap, r.Scheme); err != nil { return errors.Wrap(err, "reconcile ConfigMap") } @@ -775,6 +777,20 @@ func (r *PerconaServerMySQLReconciler) reconcileOrchestrator(ctx context.Context return errors.Wrap(err, "ensure component") } + configMapHash, err := k8s.ObjectHash(configMap) + if err != nil { + return errors.Wrap(err, "calculate config map hash") + } + + sts := &appsv1.StatefulSet{} + if err := r.Client.Get(ctx, orchestrator.NamespacedName(cr), sts); err != nil { + return errors.Wrap(err, "get orchestrator sts") + } + + if err := k8s.RolloutRestart(ctx, r.Client, sts, naming.AnnotationLastConfigHash, configMapHash); err != nil { + return errors.Wrap(err, "restart orchestrator for config change") + } + raftNodes := orchestrator.RaftNodes(cr) if len(existingNodes) == 0 || len(existingNodes) == len(raftNodes) { return nil @@ -802,35 +818,6 @@ func (r *PerconaServerMySQLReconciler) reconcileOrchestrator(ctx context.Context } else { oldPeers := util.Difference(existingNodes, raftNodes) - if cr.Spec.Orchestrator.Size == 1 && len(oldPeers) > 0 { - log.Info("Scaling down to single node, enabling single node mode first", "oldPeers", oldPeers) - - cmData, err := orchestrator.ConfigMapData(cr) - if err != nil { - return errors.Wrap(err, "get ConfigMap data for single node") - } - - configMap := orchestrator.ConfigMap(cr, cmData) - - if err := k8s.EnsureObjectWithHash(ctx, r.Client, cr, configMap, r.Scheme); err != nil { - return errors.Wrap(err, "update ConfigMap for single node") - } - - configMapHash, err := k8s.ObjectHash(configMap) - if err != nil { - return errors.Wrap(err, "calculate config map hash") - } - - sts := &appsv1.StatefulSet{} - if err := r.Client.Get(ctx, orchestrator.NamespacedName(cr), sts); err != nil { - return errors.Wrap(err, "get orchestrator sts") - } - - if err := k8s.RolloutRestart(ctx, r.Client, sts, naming.AnnotationLastConfigHash, configMapHash); err != nil { - return errors.Wrap(err, "restart orchestrator for config change") - } - } - for _, peer := range oldPeers { p := peer g.Go(func() error { From 97e72a0c6916a02b0659fc016ea492287175facc Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Mon, 8 Sep 2025 10:36:03 +0300 Subject: [PATCH 06/13] fix images --- config/manager/cluster/kustomization.yaml | 2 +- config/manager/kustomization.yaml | 2 +- deploy/bundle.yaml | 2 +- deploy/cw-bundle.yaml | 2 +- deploy/cw-operator.yaml | 2 +- deploy/operator.yaml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/manager/cluster/kustomization.yaml b/config/manager/cluster/kustomization.yaml index 530e7c3a8..43d283c3e 100644 --- a/config/manager/cluster/kustomization.yaml +++ b/config/manager/cluster/kustomization.yaml @@ -13,4 +13,4 @@ kind: Kustomization images: - name: perconalab/percona-server-mysql-operator newName: perconalab/percona-server-mysql-operator - newTag: K8SPS-378-5 + newTag: main diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index fc99c7ab1..f9fa17fd8 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -16,4 +16,4 @@ images: newTag: main - name: perconalab/percona-server-mysql-operator newName: perconalab/percona-server-mysql-operator - newTag: K8SPS-378-5 + newTag: main diff --git a/deploy/bundle.yaml b/deploy/bundle.yaml index 2e8741728..6a78b846d 100644 --- a/deploy/bundle.yaml +++ b/deploy/bundle.yaml @@ -13461,7 +13461,7 @@ spec: fieldPath: metadata.namespace - name: DISABLE_TELEMETRY value: "false" - image: perconalab/percona-server-mysql-operator:K8SPS-378-5 + image: perconalab/percona-server-mysql-operator:main imagePullPolicy: Always livenessProbe: httpGet: diff --git a/deploy/cw-bundle.yaml b/deploy/cw-bundle.yaml index fdae98c09..05ebb14d7 100644 --- a/deploy/cw-bundle.yaml +++ b/deploy/cw-bundle.yaml @@ -13471,7 +13471,7 @@ spec: value: "" - name: DISABLE_TELEMETRY value: "false" - image: perconalab/percona-server-mysql-operator:K8SPS-378-5 + image: perconalab/percona-server-mysql-operator:main imagePullPolicy: Always livenessProbe: httpGet: diff --git a/deploy/cw-operator.yaml b/deploy/cw-operator.yaml index f97fef8f4..d6527b616 100644 --- a/deploy/cw-operator.yaml +++ b/deploy/cw-operator.yaml @@ -48,7 +48,7 @@ spec: value: "" - name: DISABLE_TELEMETRY value: "false" - image: perconalab/percona-server-mysql-operator:K8SPS-378-5 + image: perconalab/percona-server-mysql-operator:main imagePullPolicy: Always livenessProbe: httpGet: diff --git a/deploy/operator.yaml b/deploy/operator.yaml index a19b906d3..43f942675 100644 --- a/deploy/operator.yaml +++ b/deploy/operator.yaml @@ -51,7 +51,7 @@ spec: fieldPath: metadata.namespace - name: DISABLE_TELEMETRY value: "false" - image: perconalab/percona-server-mysql-operator:K8SPS-378-5 + image: perconalab/percona-server-mysql-operator:main imagePullPolicy: Always livenessProbe: httpGet: From aa6645e07949e9cf7cf5ada791e0e5d47d1cabdf Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Mon, 8 Sep 2025 10:39:04 +0300 Subject: [PATCH 07/13] revert default cr --- deploy/cr.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/deploy/cr.yaml b/deploy/cr.yaml index 24100d825..6cfc73d5d 100644 --- a/deploy/cr.yaml +++ b/deploy/cr.yaml @@ -13,10 +13,10 @@ spec: # service.beta.kubernetes.io/aws-load-balancer-type: nlb # labels: # rack: rack-22 - unsafeFlags: +# unsafeFlags: # mysqlSize: false # orchestrator: false - orchestratorSize: true +# orchestratorSize: false # proxy: false # proxySize: false # pause: false @@ -56,7 +56,7 @@ spec: # group: cert-manager.io mysql: - clusterType: async + clusterType: group-replication autoRecovery: true image: perconalab/percona-server-mysql-operator:main-psmysql8.0 imagePullPolicy: Always @@ -446,7 +446,7 @@ spec: # - 10.0.0.0/8 orchestrator: - enabled: true + enabled: false image: perconalab/percona-server-mysql-operator:main-orchestrator imagePullPolicy: Always # runtimeClassName: image-rc @@ -473,7 +473,7 @@ spec: # memory: 200M # cpu: 200m - size: 1 + size: 3 affinity: antiAffinityTopologyKey: "kubernetes.io/hostname" From 81a40bea5dac8e34dc78bc0eeae46b952d990fa7 Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Mon, 8 Sep 2025 10:45:55 +0300 Subject: [PATCH 08/13] make linter happy --- pkg/controller/ps/controller.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/controller/ps/controller.go b/pkg/controller/ps/controller.go index 25aa3387a..57a702b86 100644 --- a/pkg/controller/ps/controller.go +++ b/pkg/controller/ps/controller.go @@ -783,7 +783,7 @@ func (r *PerconaServerMySQLReconciler) reconcileOrchestrator(ctx context.Context } sts := &appsv1.StatefulSet{} - if err := r.Client.Get(ctx, orchestrator.NamespacedName(cr), sts); err != nil { + if err := r.Get(ctx, orchestrator.NamespacedName(cr), sts); err != nil { return errors.Wrap(err, "get orchestrator sts") } From 864aaa653efec03b90bb27e5a5368e8fca461de1 Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Tue, 9 Sep 2025 18:38:39 +0300 Subject: [PATCH 09/13] reconcile the sts on the next cycle --- pkg/controller/ps/controller.go | 25 ++++++++++++++----------- pkg/orchestrator/orchestrator.go | 8 +++++--- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/pkg/controller/ps/controller.go b/pkg/controller/ps/controller.go index cc6abd3c7..e01dbcde7 100644 --- a/pkg/controller/ps/controller.go +++ b/pkg/controller/ps/controller.go @@ -736,6 +736,20 @@ func (r *PerconaServerMySQLReconciler) reconcileOrchestrator(ctx context.Context return errors.Wrap(err, "get config map") } + cmData, err := orchestrator.ConfigMapData(cr) + if err != nil { + return errors.Wrap(err, "get ConfigMap data") + } + + configMap := orchestrator.ConfigMap(cr, cmData) + if !reflect.DeepEqual(cmap.Data, cmData) { + if err := k8s.EnsureObjectWithHash(ctx, r.Client, cr, configMap, r.Scheme); err != nil { + return errors.Wrap(err, "reconcile ConfigMap") + } + log.Info("ConfigMap updated", "name", configMap.Name, "data", configMap.Data) + return nil + } + existingNodes := make([]string, 0) if !k8serrors.IsNotFound(err) { cfg, ok := cmap.Data[orchestrator.ConfigFileName] @@ -758,17 +772,6 @@ func (r *PerconaServerMySQLReconciler) reconcileOrchestrator(ctx context.Context } } - cmData, err := orchestrator.ConfigMapData(cr) - if err != nil { - return errors.Wrap(err, "get ConfigMap data") - } - - configMap := orchestrator.ConfigMap(cr, cmData) - - if err := k8s.EnsureObjectWithHash(ctx, r.Client, cr, configMap, r.Scheme); err != nil { - return errors.Wrap(err, "reconcile ConfigMap") - } - component := orchestrator.Component(*cr) if err := k8s.EnsureComponent(ctx, r.Client, &component); err != nil { return errors.Wrap(err, "ensure component") diff --git a/pkg/orchestrator/orchestrator.go b/pkg/orchestrator/orchestrator.go index 6a78977f4..be15c805c 100644 --- a/pkg/orchestrator/orchestrator.go +++ b/pkg/orchestrator/orchestrator.go @@ -489,9 +489,11 @@ func orcConfig(cr *apiv1alpha1.PerconaServerMySQL) (string, error) { config["RaftNodes"] = RaftNodes(cr) - config["RaftEnabledSingleNode"] = false - if cr.Spec.Orchestrator.Size == 1 { - config["RaftEnabledSingleNode"] = true + if cr.CompareVersion("0.12.0") >= 0 { + config["RaftEnabledSingleNode"] = false + if cr.Spec.Orchestrator.Size == 1 { + config["RaftEnabledSingleNode"] = true + } } configJson, err := json.Marshal(config) From 125df3b55ebb84cd6181b861ca559e1b1952d3ca Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Wed, 10 Sep 2025 11:09:34 +0300 Subject: [PATCH 10/13] config map hash to annotations --- pkg/controller/ps/controller.go | 18 ++---------------- pkg/orchestrator/component.go | 15 ++++++++++++++- pkg/orchestrator/orchestrator.go | 9 +++++++-- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/pkg/controller/ps/controller.go b/pkg/controller/ps/controller.go index e01dbcde7..8da12e28c 100644 --- a/pkg/controller/ps/controller.go +++ b/pkg/controller/ps/controller.go @@ -777,20 +777,6 @@ func (r *PerconaServerMySQLReconciler) reconcileOrchestrator(ctx context.Context return errors.Wrap(err, "ensure component") } - configMapHash, err := k8s.ObjectHash(configMap) - if err != nil { - return errors.Wrap(err, "calculate config map hash") - } - - sts := &appsv1.StatefulSet{} - if err := r.Get(ctx, orchestrator.NamespacedName(cr), sts); err != nil { - return errors.Wrap(err, "get orchestrator sts") - } - - if err := k8s.RolloutRestart(ctx, r.Client, sts, naming.AnnotationLastConfigHash, configMapHash); err != nil { - return errors.Wrap(err, "restart orchestrator for config change") - } - raftNodes := orchestrator.RaftNodes(cr) if len(existingNodes) == 0 || len(existingNodes) == len(raftNodes) { return nil @@ -919,7 +905,7 @@ func (r *PerconaServerMySQLReconciler) reconcileReplication(ctx context.Context, sts := &appsv1.StatefulSet{} // no need to set init image since we're just getting obj from API - if err := r.Get(ctx, client.ObjectKeyFromObject(orchestrator.StatefulSet(cr, "", "")), sts); err != nil { + if err := r.Get(ctx, client.ObjectKeyFromObject(orchestrator.StatefulSet(cr, "", "", "")), sts); err != nil { return client.IgnoreNotFound(err) } @@ -1204,7 +1190,7 @@ func (r *PerconaServerMySQLReconciler) cleanupOrchestrator(ctx context.Context, orcExposer := orchestrator.Exposer(*cr) if !cr.OrchestratorEnabled() { - if err := r.Delete(ctx, orchestrator.StatefulSet(cr, "", "")); err != nil && !k8serrors.IsNotFound(err) { + if err := r.Delete(ctx, orchestrator.StatefulSet(cr, "", "", "")); err != nil && !k8serrors.IsNotFound(err) { return errors.Wrap(err, "failed to delete orchestrator statefulset") } diff --git a/pkg/orchestrator/component.go b/pkg/orchestrator/component.go index 90fd6ed34..6f03a5090 100644 --- a/pkg/orchestrator/component.go +++ b/pkg/orchestrator/component.go @@ -4,6 +4,7 @@ import ( "context" "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" "sigs.k8s.io/controller-runtime/pkg/client" apiv1alpha1 "github.com/percona/percona-server-mysql-operator/api/v1alpha1" @@ -44,10 +45,22 @@ func (c *Component) Object(ctx context.Context, cl client.Client) (client.Object return nil, errors.Wrap(err, "get init image") } + configMap := &corev1.ConfigMap{} + configMapName := client.ObjectKey{Name: ConfigMapName(cr), Namespace: cr.Namespace} + configHash := "" + if err := cl.Get(ctx, configMapName, configMap); err == nil { + configHash, err = k8s.ObjectHash(configMap) + if err != nil { + return nil, errors.Wrap(err, "calculate config map hash") + } + } else if client.IgnoreNotFound(err) != nil { + return nil, errors.Wrap(err, "get config map") + } + tlsHash, err := k8s.GetTLSHash(ctx, cl, cr) if err != nil { return nil, errors.Wrapf(err, "get tls hash") } - return StatefulSet(cr, initImage, tlsHash), nil + return StatefulSet(cr, initImage, configHash, tlsHash), nil } diff --git a/pkg/orchestrator/orchestrator.go b/pkg/orchestrator/orchestrator.go index be15c805c..f8962c2da 100644 --- a/pkg/orchestrator/orchestrator.go +++ b/pkg/orchestrator/orchestrator.go @@ -102,12 +102,17 @@ func MatchLabels(cr *apiv1alpha1.PerconaServerMySQL) map[string]string { return cr.Labels(AppName, naming.ComponentOrchestrator) } -func StatefulSet(cr *apiv1alpha1.PerconaServerMySQL, initImage, tlsHash string) *appsv1.StatefulSet { +func StatefulSet(cr *apiv1alpha1.PerconaServerMySQL, initImage, configHash, tlsHash string) *appsv1.StatefulSet { selector := MatchLabels(cr) spec := cr.OrchestratorSpec() Replicas := spec.Size - annotations := make(map[string]string, 0) + annotations := make(map[string]string) + if cr.CompareVersion("0.12.0") >= 0 { + if configHash != "" { + annotations[string(naming.AnnotationConfigHash)] = configHash + } + } if tlsHash != "" { annotations[string(naming.AnnotationTLSHash)] = tlsHash } From 522d667f29ce3609a67224afcf0509e5080bd9d5 Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Wed, 10 Sep 2025 11:16:26 +0300 Subject: [PATCH 11/13] update unit tests --- pkg/orchestrator/orchestrator_test.go | 32 +++++++++++++++------------ 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/pkg/orchestrator/orchestrator_test.go b/pkg/orchestrator/orchestrator_test.go index 26e387ff2..cd1b2e5cd 100644 --- a/pkg/orchestrator/orchestrator_test.go +++ b/pkg/orchestrator/orchestrator_test.go @@ -14,9 +14,12 @@ import ( ) func TestStatefulSet(t *testing.T) { - const ns = "orc-ns" - const initImage = "init-image" - const tlsHash = "tls-hash" + const ( + ns = "orc-ns" + initImage = "init-image" + tlsHash = "tls-hash" + configHash = "config-hash" + ) cr := readDefaultCluster(t, "cluster", ns) if err := cr.CheckNSetDefaults(t.Context(), &platform.ServerVersion{ @@ -28,7 +31,7 @@ func TestStatefulSet(t *testing.T) { t.Run("object meta", func(t *testing.T) { cluster := cr.DeepCopy() - sts := StatefulSet(cluster, initImage, tlsHash) + sts := StatefulSet(cluster, initImage, configHash, tlsHash) assert.NotNil(t, sts) assert.Equal(t, "cluster-orc", sts.Name) @@ -46,7 +49,7 @@ func TestStatefulSet(t *testing.T) { t.Run("defaults", func(t *testing.T) { cluster := cr.DeepCopy() - sts := StatefulSet(cluster, initImage, tlsHash) + sts := StatefulSet(cluster, initImage, configHash, tlsHash) assert.Equal(t, int32(3), *sts.Spec.Replicas) initContainers := sts.Spec.Template.Spec.InitContainers @@ -54,7 +57,8 @@ func TestStatefulSet(t *testing.T) { assert.Equal(t, initImage, initContainers[0].Image) assert.Equal(t, map[string]string{ - "percona.com/last-applied-tls": tlsHash, + "percona.com/configuration-hash": configHash, + "percona.com/last-applied-tls": tlsHash, }, sts.Spec.Template.Annotations) }) @@ -62,19 +66,19 @@ func TestStatefulSet(t *testing.T) { cluster := cr.DeepCopy() cluster.Spec.Orchestrator.TerminationGracePeriodSeconds = nil - sts := StatefulSet(cluster, initImage, tlsHash) + sts := StatefulSet(cluster, initImage, configHash, tlsHash) assert.Equal(t, int64(600), *sts.Spec.Template.Spec.TerminationGracePeriodSeconds) cluster.Spec.Orchestrator.TerminationGracePeriodSeconds = ptr.To(int64(30)) - sts = StatefulSet(cluster, initImage, tlsHash) + sts = StatefulSet(cluster, initImage, configHash, tlsHash) assert.Equal(t, int64(30), *sts.Spec.Template.Spec.TerminationGracePeriodSeconds) }) t.Run("image pull secrets", func(t *testing.T) { cluster := cr.DeepCopy() - sts := StatefulSet(cluster, initImage, tlsHash) + sts := StatefulSet(cluster, initImage, configHash, tlsHash) assert.Equal(t, []corev1.LocalObjectReference(nil), sts.Spec.Template.Spec.ImagePullSecrets) imagePullSecrets := []corev1.LocalObjectReference{ @@ -87,26 +91,26 @@ func TestStatefulSet(t *testing.T) { } cluster.Spec.Orchestrator.ImagePullSecrets = imagePullSecrets - sts = StatefulSet(cluster, initImage, tlsHash) + sts = StatefulSet(cluster, initImage, configHash, tlsHash) assert.Equal(t, imagePullSecrets, sts.Spec.Template.Spec.ImagePullSecrets) }) t.Run("runtime class name", func(t *testing.T) { cluster := cr.DeepCopy() - sts := StatefulSet(cluster, initImage, tlsHash) + sts := StatefulSet(cluster, initImage, configHash, tlsHash) var e *string assert.Equal(t, e, sts.Spec.Template.Spec.RuntimeClassName) const runtimeClassName = "runtimeClassName" cluster.Spec.Orchestrator.RuntimeClassName = ptr.To(runtimeClassName) - sts = StatefulSet(cluster, initImage, tlsHash) + sts = StatefulSet(cluster, initImage, configHash, tlsHash) assert.Equal(t, runtimeClassName, *sts.Spec.Template.Spec.RuntimeClassName) }) t.Run("tolerations", func(t *testing.T) { cluster := cr.DeepCopy() - sts := StatefulSet(cluster, initImage, tlsHash) + sts := StatefulSet(cluster, initImage, configHash, tlsHash) assert.Equal(t, []corev1.Toleration(nil), sts.Spec.Template.Spec.Tolerations) tolerations := []corev1.Toleration{ @@ -120,7 +124,7 @@ func TestStatefulSet(t *testing.T) { } cluster.Spec.Orchestrator.Tolerations = tolerations - sts = StatefulSet(cluster, initImage, tlsHash) + sts = StatefulSet(cluster, initImage, configHash, tlsHash) assert.Equal(t, tolerations, sts.Spec.Template.Spec.Tolerations) }) } From 83128c8ffb5f3ce0cf434dcc434dfc83e92953d1 Mon Sep 17 00:00:00 2001 From: George Kechagias Date: Wed, 10 Sep 2025 12:12:05 +0300 Subject: [PATCH 12/13] fixes --- pkg/controller/ps/controller_test.go | 6 +++++- pkg/orchestrator/component.go | 3 ++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pkg/controller/ps/controller_test.go b/pkg/controller/ps/controller_test.go index fccd85998..272f6fdae 100644 --- a/pkg/controller/ps/controller_test.go +++ b/pkg/controller/ps/controller_test.go @@ -26,6 +26,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" gs "github.com/onsi/gomega/gstruct" + "github.com/percona/percona-server-mysql-operator/pkg/version" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" policyv1 "k8s.io/api/policy/v1" @@ -344,7 +345,7 @@ var _ = Describe("PodDisruptionBudget", Ordered, func() { Context("Check default cluster", Ordered, func() { cr, err := readDefaultCR(crName, ns) - cr.Spec.CRVersion = "0.12.0" + cr.Spec.CRVersion = version.Version() It("should prepare reconciler", func() { r = reconciler() Expect(err).To(Succeed()) @@ -411,6 +412,9 @@ var _ = Describe("PodDisruptionBudget", Ordered, func() { It("should reconcile", func() { _, err := r.Reconcile(ctx, ctrl.Request{NamespacedName: crNamespacedName}) Expect(err).NotTo(HaveOccurred()) + // reconcile and a second time cause the orchestrator needs 2 cycles + _, err = r.Reconcile(ctx, ctrl.Request{NamespacedName: crNamespacedName}) + Expect(err).NotTo(HaveOccurred()) }) It("should check PodDisruptionBudget for MySQL", func() { pdb := &policyv1.PodDisruptionBudget{ diff --git a/pkg/orchestrator/component.go b/pkg/orchestrator/component.go index 6f03a5090..e4beec96f 100644 --- a/pkg/orchestrator/component.go +++ b/pkg/orchestrator/component.go @@ -5,6 +5,7 @@ import ( "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" + k8serrors "k8s.io/apimachinery/pkg/api/errors" "sigs.k8s.io/controller-runtime/pkg/client" apiv1alpha1 "github.com/percona/percona-server-mysql-operator/api/v1alpha1" @@ -53,7 +54,7 @@ func (c *Component) Object(ctx context.Context, cl client.Client) (client.Object if err != nil { return nil, errors.Wrap(err, "calculate config map hash") } - } else if client.IgnoreNotFound(err) != nil { + } else if !k8serrors.IsNotFound(err) { return nil, errors.Wrap(err, "get config map") } From b4c85e661e078dea88eda6bb0f8895ee644dfbfc Mon Sep 17 00:00:00 2001 From: Viacheslav Sarzhan Date: Wed, 10 Sep 2025 21:24:57 +0300 Subject: [PATCH 13/13] fix scaling test --- e2e-tests/tests/scaling/08-assert.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e-tests/tests/scaling/08-assert.yaml b/e2e-tests/tests/scaling/08-assert.yaml index 63ec28ceb..b62d7076e 100644 --- a/e2e-tests/tests/scaling/08-assert.yaml +++ b/e2e-tests/tests/scaling/08-assert.yaml @@ -1,6 +1,6 @@ apiVersion: kuttl.dev/v1beta1 kind: TestAssert -timeout: 120 +timeout: 200 --- kind: StatefulSet apiVersion: apps/v1