From 4461031b25c11e5b29d77d465c058b936127aac2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ege=20G=C3=BCne=C5=9F?= Date: Wed, 17 Dec 2025 16:21:05 +0300 Subject: [PATCH] K8SPSMDB-1527: Cluster can't be ready before PBM is ready --- pkg/apis/psmdb/v1/psmdb_types.go | 64 ++-- pkg/controller/perconaservermongodb/backup.go | 119 ------- .../perconaservermongodb/conditions.go | 12 + pkg/controller/perconaservermongodb/pbm.go | 165 ++++++++- pkg/controller/perconaservermongodb/status.go | 16 +- .../perconaservermongodb/status_test.go | 313 +++++++++++++++++- 6 files changed, 515 insertions(+), 174 deletions(-) create mode 100644 pkg/controller/perconaservermongodb/conditions.go diff --git a/pkg/apis/psmdb/v1/psmdb_types.go b/pkg/apis/psmdb/v1/psmdb_types.go index 587c5d493c..7685c7bf2e 100644 --- a/pkg/apis/psmdb/v1/psmdb_types.go +++ b/pkg/apis/psmdb/v1/psmdb_types.go @@ -339,9 +339,13 @@ const ( ConditionUnknown ConditionStatus = "Unknown" ) -// ConditionTypePendingSmartUpdate is a condition type set on PSMDBCluster when a smart update is required -// but has not yet started. For e.g., if a backup/restore is running at the same time as a smart update is triggered. -const ConditionTypePendingSmartUpdate AppState = "pendingSmartUpdate" +const ( + // ConditionTypePendingSmartUpdate is a condition type set on PSMDBCluster when a smart update is required + // but has not yet started. For e.g., if a backup/restore is running at the same time as a smart update is triggered. + ConditionTypePendingSmartUpdate AppState = "pendingSmartUpdate" + + ConditionTypePBMReady AppState = "PBMReady" +) type ClusterCondition struct { Status ConditionStatus `json:"status"` @@ -369,6 +373,33 @@ func (s *PerconaServerMongoDBStatus) IsStatusConditionTrue(conditionType AppStat return cond.Status == ConditionTrue } +func (s *PerconaServerMongoDBStatus) AddCondition(c ClusterCondition) { + existingCondition := s.FindCondition(c.Type) + if existingCondition == nil { + if c.LastTransitionTime.IsZero() { + c.LastTransitionTime = metav1.NewTime(time.Now()) + } + s.Conditions = append(s.Conditions, c) + return + } + + if existingCondition.Status != c.Status { + existingCondition.Status = c.Status + if !c.LastTransitionTime.IsZero() { + existingCondition.LastTransitionTime = c.LastTransitionTime + } else { + existingCondition.LastTransitionTime = metav1.NewTime(time.Now()) + } + } + + if existingCondition.Reason != c.Reason { + existingCondition.Reason = c.Reason + } + if existingCondition.Message != c.Message { + existingCondition.Message = c.Message + } +} + type PMMSpec struct { Enabled bool `json:"enabled,omitempty"` ServerHost string `json:"serverHost,omitempty"` @@ -1439,33 +1470,6 @@ func (s *PerconaServerMongoDBStatus) RemoveCondition(conditionType AppState) { } } -func (s *PerconaServerMongoDBStatus) AddCondition(c ClusterCondition) { - existingCondition := s.FindCondition(c.Type) - if existingCondition == nil { - if c.LastTransitionTime.IsZero() { - c.LastTransitionTime = metav1.NewTime(time.Now()) - } - s.Conditions = append(s.Conditions, c) - return - } - - if existingCondition.Status != c.Status { - existingCondition.Status = c.Status - if !c.LastTransitionTime.IsZero() { - existingCondition.LastTransitionTime = c.LastTransitionTime - } else { - existingCondition.LastTransitionTime = metav1.NewTime(time.Now()) - } - } - - if existingCondition.Reason != c.Reason { - existingCondition.Reason = c.Reason - } - if existingCondition.Message != c.Message { - existingCondition.Message = c.Message - } -} - // GetExternalNodes returns all external nodes for all replsets func (cr *PerconaServerMongoDB) GetExternalNodes() []*ExternalNode { extNodes := make([]*ExternalNode, 0) diff --git a/pkg/controller/perconaservermongodb/backup.go b/pkg/controller/perconaservermongodb/backup.go index 9aabd3d07e..752cfa9847 100644 --- a/pkg/controller/perconaservermongodb/backup.go +++ b/pkg/controller/perconaservermongodb/backup.go @@ -1,10 +1,8 @@ package perconaservermongodb import ( - "bytes" "container/heap" "context" - "strings" "time" "github.com/pkg/errors" @@ -19,12 +17,9 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "github.com/percona/percona-backup-mongodb/pbm/defs" - pbmVersion "github.com/percona/percona-backup-mongodb/pbm/version" api "github.com/percona/percona-server-mongodb-operator/pkg/apis/psmdb/v1" - "github.com/percona/percona-server-mongodb-operator/pkg/k8s" "github.com/percona/percona-server-mongodb-operator/pkg/naming" - "github.com/percona/percona-server-mongodb-operator/pkg/psmdb" "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/backup" ) @@ -403,117 +398,3 @@ func isPodUpToDate(pod *corev1.Pod, stsRevision, image string) bool { return true } - -func (r *ReconcilePerconaServerMongoDB) reconcileBackupVersion(ctx context.Context, cr *api.PerconaServerMongoDB) error { - log := logf.FromContext(ctx) - - if !cr.Spec.Backup.Enabled { - return nil - } - - if cr.Status.State != api.AppStateReady { - return nil - } - - if cr.Status.BackupVersion != "" && cr.Status.BackupImage == cr.Spec.Backup.Image { - return nil - } - - if len(cr.Spec.Replsets) < 1 { - return errors.New("no replsets found") - } - - var rs *api.ReplsetSpec - for _, r := range cr.Spec.Replsets { - rs = r - break - } - - stsName := naming.MongodStatefulSetName(cr, rs) - sts := psmdb.NewStatefulSet(stsName, cr.Namespace) - err := r.client.Get(ctx, client.ObjectKeyFromObject(sts), sts) - if err != nil { - return errors.Wrapf(err, "get statefulset/%s", stsName) - } - - matchLabels := naming.RSLabels(cr, rs) - label, ok := sts.Labels[naming.LabelKubernetesComponent] - if ok { - matchLabels[naming.LabelKubernetesComponent] = label - } - - podList := corev1.PodList{} - if err := r.client.List(ctx, - &podList, - &client.ListOptions{ - Namespace: cr.Namespace, - LabelSelector: labels.SelectorFromSet(matchLabels), - }, - ); err != nil { - return errors.Wrap(err, "get pod list") - } - - var pod *corev1.Pod - for _, p := range podList.Items { - if !k8s.IsPodReady(p) { - continue - } - - if !isPodUpToDate(&p, sts.Status.UpdateRevision, cr.Spec.Backup.Image) { - continue - } - - pod = &p - break - } - if pod == nil { - log.V(1).Error(nil, "no ready pods to get pbm-agent version") - return nil - } - - stdout := &bytes.Buffer{} - stderr := &bytes.Buffer{} - cmd := []string{"pbm-agent", "version", "--short"} - - err = r.clientcmd.Exec(ctx, pod, naming.ContainerBackupAgent, cmd, nil, stdout, stderr, false) - if err != nil { - return errors.Wrap(err, "get pbm-agent version") - } - - // PBM v2.9.0 and above prints version to stderr, below prints it to stdout - stdoutStr := strings.TrimSpace(stdout.String()) - stderrStr := strings.TrimSpace(stderr.String()) - if stdoutStr != "" && stderrStr != "" { - log.V(1).Info("pbm-agent version found in both stdout and stderr; using stdout", - "stdout", stdoutStr, "stderr", stderrStr) - cr.Status.BackupVersion = stdoutStr - } else if stdoutStr != "" { - cr.Status.BackupVersion = stdoutStr - } else if stderrStr != "" { - cr.Status.BackupVersion = stderrStr - } else { - return errors.New("pbm-agent version not found in stdout or stderr") - } - - cr.Status.BackupImage = cr.Spec.Backup.Image - - log.Info("pbm-agent version", - "pod", pod.Name, - "image", cr.Status.BackupImage, - "version", cr.Status.BackupVersion) - - pbmInfo := pbmVersion.Current() - - compare, err := cr.ComparePBMAgentVersion(pbmInfo.Version) - if err != nil { - return errors.Wrap(err, "compare pbm-agent version with go module") - } - - if compare != 0 { - log.Info("pbm-agent version is different than the go module, this might create problems", - "pbmAgentVersion", cr.Status.BackupVersion, - "goModuleVersion", pbmInfo.Version) - } - - return nil -} diff --git a/pkg/controller/perconaservermongodb/conditions.go b/pkg/controller/perconaservermongodb/conditions.go new file mode 100644 index 0000000000..8496966370 --- /dev/null +++ b/pkg/controller/perconaservermongodb/conditions.go @@ -0,0 +1,12 @@ +package perconaservermongodb + +import ( + "context" + + psmdbv1 "github.com/percona/percona-server-mongodb-operator/pkg/apis/psmdb/v1" +) + +func (r *ReconcilePerconaServerMongoDB) updateCondition(ctx context.Context, cr *psmdbv1.PerconaServerMongoDB, c psmdbv1.ClusterCondition) error { + cr.Status.AddCondition(c) + return r.writeStatus(ctx, cr) +} diff --git a/pkg/controller/perconaservermongodb/pbm.go b/pkg/controller/perconaservermongodb/pbm.go index c7cc54c59c..eed3b01068 100644 --- a/pkg/controller/perconaservermongodb/pbm.go +++ b/pkg/controller/perconaservermongodb/pbm.go @@ -1,6 +1,7 @@ package perconaservermongodb import ( + "bytes" "context" "encoding/json" "fmt" @@ -12,13 +13,19 @@ import ( "github.com/pkg/errors" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/x/bsonx/bsoncore" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" logf "sigs.k8s.io/controller-runtime/pkg/log" "github.com/percona/percona-backup-mongodb/pbm/config" + pbmVersion "github.com/percona/percona-backup-mongodb/pbm/version" psmdbv1 "github.com/percona/percona-server-mongodb-operator/pkg/apis/psmdb/v1" "github.com/percona/percona-server-mongodb-operator/pkg/k8s" + "github.com/percona/percona-server-mongodb-operator/pkg/naming" + "github.com/percona/percona-server-mongodb-operator/pkg/psmdb" "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/backup" ) @@ -58,10 +65,6 @@ func (r *ReconcilePerconaServerMongoDB) reconcilePBMConfig(ctx context.Context, return nil } - if cr.Status.State != psmdbv1.AppStateReady { - return nil - } - // Restore will resync the storage. We shouldn't change config during the restore isRestoring, err := r.isRestoreRunning(ctx, cr) if err != nil { @@ -78,6 +81,22 @@ func (r *ReconcilePerconaServerMongoDB) reconcilePBMConfig(ctx context.Context, return nil } + for rs, status := range cr.Status.Replsets { + if !status.Initialized { + log.V(1).Info("waiting for replset to be initialized", "replset", rs) + return nil + } + + if rs == psmdbv1.ConfigReplSetName { + continue + } + + if cr.Spec.Sharding.Enabled && status.AddedAsShard == nil || !*status.AddedAsShard { + log.V(1).Info("waiting for replset to be added as shard", "replset", rs) + return nil + } + } + main, err := backup.GetPBMConfig(ctx, r.client, cr, mainStg) if err != nil { return errors.Wrap(err, "get config") @@ -144,6 +163,15 @@ func (r *ReconcilePerconaServerMongoDB) reconcilePBMConfig(ctx context.Context, return nil } + err = r.updateCondition(ctx, cr, psmdbv1.ClusterCondition{ + Type: psmdbv1.ConditionTypePBMReady, + Reason: "PBMConfigurationIsChanged", + Status: psmdbv1.ConditionFalse, + }) + if err != nil { + return errors.Wrapf(err, "update %s condition", psmdbv1.ConditionTypePBMReady) + } + log.Info("configuration changed or resync is needed", "oldHash", cr.Status.BackupConfigHash, "newHash", hash) if err := pbm.GetNSetConfig(ctx, r.client, cr); err != nil { @@ -153,13 +181,22 @@ func (r *ReconcilePerconaServerMongoDB) reconcilePBMConfig(ctx context.Context, if isResyncNeeded(currentCfg, &main) { log.Info("main storage changed. starting resync", "old", currentCfg.Storage, "new", main.Storage) - if err := pbm.ResyncMainStorage(ctx); err != nil { + if err := pbm.ResyncMainStorageAndWait(ctx); err != nil { return errors.Wrap(err, "resync") } } cr.Status.BackupConfigHash = hash + err = r.updateCondition(ctx, cr, psmdbv1.ClusterCondition{ + Type: psmdbv1.ConditionTypePBMReady, + Reason: "PBMConfigurationIsUpToDate", + Status: psmdbv1.ConditionTrue, + }) + if err != nil { + return errors.Wrapf(err, "update %s condition", psmdbv1.ConditionTypePBMReady) + } + return nil } @@ -664,3 +701,121 @@ func (r *ReconcilePerconaServerMongoDB) resyncPBMIfNeeded(ctx context.Context, c return nil } + +func (r *ReconcilePerconaServerMongoDB) reconcileBackupVersion(ctx context.Context, cr *psmdbv1.PerconaServerMongoDB) error { + log := logf.FromContext(ctx) + + if !cr.Spec.Backup.Enabled { + return nil + } + + if cr.Status.BackupVersion != "" && cr.Status.BackupImage == cr.Spec.Backup.Image { + return nil + } + + if cr.Status.Ready < 1 { + return nil + } + + if len(cr.Spec.Replsets) < 1 { + return errors.New("no replsets found") + } + + var rs *psmdbv1.ReplsetSpec + for _, r := range cr.Spec.Replsets { + rs = r + break + } + + stsName := naming.MongodStatefulSetName(cr, rs) + sts := psmdb.NewStatefulSet(stsName, cr.Namespace) + err := r.client.Get(ctx, client.ObjectKeyFromObject(sts), sts) + if err != nil { + return errors.Wrapf(err, "get statefulset/%s", stsName) + } + + matchLabels := naming.RSLabels(cr, rs) + label, ok := sts.Labels[naming.LabelKubernetesComponent] + if ok { + matchLabels[naming.LabelKubernetesComponent] = label + } + + podList := corev1.PodList{} + if err := r.client.List(ctx, + &podList, + &client.ListOptions{ + Namespace: cr.Namespace, + LabelSelector: labels.SelectorFromSet(matchLabels), + }, + ); err != nil { + return errors.Wrap(err, "get pod list") + } + + var pod *corev1.Pod + for _, p := range podList.Items { + if !k8s.IsPodReady(p) { + continue + } + + if !isContainerAndPodRunning(p, naming.ContainerBackupAgent) { + continue + } + + if !isPodUpToDate(&p, sts.Status.UpdateRevision, cr.Spec.Backup.Image) { + continue + } + + pod = &p + break + } + if pod == nil { + log.V(1).Error(nil, "no ready pods to get pbm-agent version") + return nil + } + + stdout := &bytes.Buffer{} + stderr := &bytes.Buffer{} + cmd := []string{"pbm-agent", "version", "--short"} + + err = r.clientcmd.Exec(ctx, pod, naming.ContainerBackupAgent, cmd, nil, stdout, stderr, false) + if err != nil { + return errors.Wrap(err, "get pbm-agent version") + } + + // PBM v2.9.0 and above prints version to stderr, below prints it to stdout + stdoutStr := strings.TrimSpace(stdout.String()) + stderrStr := strings.TrimSpace(stderr.String()) + if stdoutStr != "" && stderrStr != "" { + log.V(1).Info("pbm-agent version found in both stdout and stderr; using stdout", + "stdout", stdoutStr, "stderr", stderrStr) + cr.Status.BackupVersion = stdoutStr + } else if stdoutStr != "" { + cr.Status.BackupVersion = stdoutStr + } else if stderrStr != "" { + cr.Status.BackupVersion = stderrStr + } else { + return errors.New("pbm-agent version not found in stdout or stderr") + } + + cr.Status.BackupImage = cr.Spec.Backup.Image + + log.Info("pbm-agent version", + "pod", pod.Name, + "image", cr.Status.BackupImage, + "version", cr.Status.BackupVersion) + + pbmInfo := pbmVersion.Current() + + compare, err := cr.ComparePBMAgentVersion(pbmInfo.Version) + if err != nil { + return errors.Wrap(err, "compare pbm-agent version with go module") + } + + if compare != 0 { + log.Info("pbm-agent version is different than the go module, this might create problems", + "pbmAgentVersion", cr.Status.BackupVersion, + "goModuleVersion", pbmInfo.Version) + } + + return nil +} diff --git a/pkg/controller/perconaservermongodb/status.go b/pkg/controller/perconaservermongodb/status.go index f1d3e35ab7..a987ff178e 100644 --- a/pkg/controller/perconaservermongodb/status.go +++ b/pkg/controller/perconaservermongodb/status.go @@ -199,17 +199,28 @@ func (r *ReconcilePerconaServerMongoDB) updateStatus(ctx context.Context, cr *ap } cr.Status.Host = host + pbmInProgress := cr.Spec.Backup.Enabled && + (cr.Status.BackupVersion == "" || + (len(cr.Spec.Backup.Storages) > 0 && cr.Status.BackupConfigHash == "")) + state := api.AppStateInit switch { - case replsetsStopping > 0 || (cr.Spec.Sharding.Enabled && cr.Status.Mongos.Status == api.AppStateStopping) || cr.ObjectMeta.DeletionTimestamp != nil: + case replsetsStopping > 0 || + (cr.Spec.Sharding.Enabled && cr.Status.Mongos.Status == api.AppStateStopping) || + cr.ObjectMeta.DeletionTimestamp != nil: + state = api.AppStateStopping case replsetsPaused == len(repls): state = api.AppStatePaused if cr.Spec.Sharding.Enabled && cr.Status.Mongos.Status != api.AppStatePaused { state = api.AppStateStopping } - case !inProgress && replsetsReady == len(repls) && clusterState == api.AppStateReady && cr.Status.Host != "": + case clusterState == api.AppStateReady && + replsetsReady == len(repls) && + !inProgress && !pbmInProgress && + cr.Status.Host != "": + state = api.AppStateReady if cr.Spec.Sharding.Enabled && cr.Status.Mongos.Status != api.AppStateReady { @@ -219,6 +230,7 @@ func (r *ReconcilePerconaServerMongoDB) updateStatus(ctx context.Context, cr *ap if state != api.AppStateReady { log.V(1).Info("Cluster is not ready", + "pbmInProgress", pbmInProgress, "upgradeInProgress", inProgress, "replsetsReady", replsetsReady, "clusterState", clusterState, diff --git a/pkg/controller/perconaservermongodb/status_test.go b/pkg/controller/perconaservermongodb/status_test.go index 5cda8eb8ec..a030040035 100644 --- a/pkg/controller/perconaservermongodb/status_test.go +++ b/pkg/controller/perconaservermongodb/status_test.go @@ -2,8 +2,11 @@ package perconaservermongodb import ( "context" + "fmt" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" @@ -14,10 +17,10 @@ import ( mcs "sigs.k8s.io/mcs-api/pkg/apis/v1alpha1" api "github.com/percona/percona-server-mongodb-operator/pkg/apis/psmdb/v1" + "github.com/percona/percona-server-mongodb-operator/pkg/naming" fakeBackup "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/backup/fake" faketls "github.com/percona/percona-server-mongodb-operator/pkg/psmdb/tls/fake" "github.com/percona/percona-server-mongodb-operator/pkg/version" - "github.com/stretchr/testify/assert" ) // creates a fake client to mock API calls with the mock objects @@ -47,30 +50,304 @@ func buildFakeClient(objs ...client.Object) *ReconcilePerconaServerMongoDB { } } -func TestUpdateStatus(t *testing.T) { - cr := &api.PerconaServerMongoDB{ - ObjectMeta: metav1.ObjectMeta{Name: "psmdb-mock", Namespace: "psmdb"}, - Spec: api.PerconaServerMongoDBSpec{ - CRVersion: "1.12.0", - Replsets: []*api.ReplsetSpec{{Name: "rs0", Size: 3}, {Name: "rs1", Size: 3}}, - Sharding: api.Sharding{Enabled: true, Mongos: &api.MongosSpec{Size: 3}}, - TLS: &api.TLSSpec{ - Mode: api.TLSModePrefer, +func mockReadyReplsetSts(name, namespace, crName, rsName, component string, replicas int32) *appsv1.StatefulSet { + return &appsv1.StatefulSet{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: map[string]string{ + naming.LabelKubernetesInstance: crName, + naming.LabelKubernetesReplset: rsName, + naming.LabelKubernetesComponent: component, }, }, + Status: appsv1.StatefulSetStatus{ + ReadyReplicas: replicas, + UpdatedReplicas: replicas, + CurrentReplicas: replicas, + AvailableReplicas: replicas, + }, } +} - rs0 := &appsv1.StatefulSet{ObjectMeta: metav1.ObjectMeta{Name: "psmdb-mock-rs0", Namespace: "psmdb"}} - rs1 := &appsv1.StatefulSet{ObjectMeta: metav1.ObjectMeta{Name: "psmdb-mock-rs1", Namespace: "psmdb"}} - - r := buildFakeClient(cr, rs0, rs1) +func mockReadyReplsetPod(name, namespace, crName, rsName, component string) *corev1.Pod { + return &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + Labels: map[string]string{ + naming.LabelKubernetesName: "percona-server-mongodb", + naming.LabelKubernetesManagedBy: "percona-server-mongodb-operator", + naming.LabelKubernetesPartOf: "percona-server-mongodb", + naming.LabelKubernetesInstance: crName, + naming.LabelKubernetesReplset: rsName, + naming.LabelKubernetesComponent: component, + }, + }, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + Conditions: []corev1.PodCondition{ + { + Type: corev1.ContainersReady, + Status: corev1.ConditionTrue, + }, + }, + }, + } +} - if err := r.updateStatus(context.TODO(), cr, nil, api.AppStateInit); err != nil { - t.Error(err) +func TestUpdateStatus(t *testing.T) { + tests := []struct { + name string + cr *api.PerconaServerMongoDB + reconcileErr error + clusterState api.AppState + expectedState api.AppState + runtimeObjs []client.Object + }{ + { + name: "single replset-initializing", + cr: &api.PerconaServerMongoDB{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-name", + Namespace: "some-namespace", + }, + Spec: api.PerconaServerMongoDBSpec{ + Replsets: []*api.ReplsetSpec{ + { + Name: "rs0", + Size: 3, + }, + }, + Sharding: api.Sharding{ + Enabled: false, + }, + }, + }, + clusterState: api.AppStateInit, + expectedState: api.AppStateInit, + }, + { + name: "single replset-reconcile error", + cr: &api.PerconaServerMongoDB{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-name", + Namespace: "some-namespace", + }, + Spec: api.PerconaServerMongoDBSpec{ + Replsets: []*api.ReplsetSpec{ + { + Name: "rs0", + Size: 3, + }, + }, + Sharding: api.Sharding{ + Enabled: false, + }, + }, + }, + reconcileErr: fmt.Errorf("test"), + clusterState: api.AppStateInit, + expectedState: api.AppStateError, + }, + { + name: "single replset-ready", + cr: &api.PerconaServerMongoDB{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-name", + Namespace: "some-namespace", + }, + Spec: api.PerconaServerMongoDBSpec{ + Replsets: []*api.ReplsetSpec{ + { + Name: "rs0", + Size: 3, + }, + }, + Sharding: api.Sharding{ + Enabled: false, + }, + Backup: api.BackupSpec{ + Enabled: false, + }, + }, + Status: api.PerconaServerMongoDBStatus{ + Host: "some-name-rs0.some-namespace", + }, + }, + clusterState: api.AppStateReady, + expectedState: api.AppStateReady, + runtimeObjs: []client.Object{ + mockReadyReplsetSts("some-name-rs0", "some-namespace", "some-name", "rs0", "mongod", 3), + mockReadyReplsetPod("some-name-rs0-0", "some-namespace", "some-name", "rs0", "mongod"), + mockReadyReplsetPod("some-name-rs0-1", "some-namespace", "some-name", "rs0", "mongod"), + mockReadyReplsetPod("some-name-rs0-2", "some-namespace", "some-name", "rs0", "mongod"), + }, + }, + { + name: "single replset-backup version is empty", + cr: &api.PerconaServerMongoDB{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-name", + Namespace: "some-namespace", + }, + Spec: api.PerconaServerMongoDBSpec{ + Replsets: []*api.ReplsetSpec{ + { + Name: "rs0", + Size: 3, + }, + }, + Sharding: api.Sharding{ + Enabled: false, + }, + Backup: api.BackupSpec{ + Enabled: true, + }, + }, + Status: api.PerconaServerMongoDBStatus{ + Host: "some-name-rs0.some-namespace", + }, + }, + clusterState: api.AppStateReady, + expectedState: api.AppStateInit, + runtimeObjs: []client.Object{ + mockReadyReplsetSts("some-name-rs0", "some-namespace", "some-name", "rs0", "mongod", 3), + mockReadyReplsetPod("some-name-rs0-0", "some-namespace", "some-name", "rs0", "mongod"), + mockReadyReplsetPod("some-name-rs0-1", "some-namespace", "some-name", "rs0", "mongod"), + mockReadyReplsetPod("some-name-rs0-2", "some-namespace", "some-name", "rs0", "mongod"), + }, + }, + { + name: "single replset-no storages", + cr: &api.PerconaServerMongoDB{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-name", + Namespace: "some-namespace", + }, + Spec: api.PerconaServerMongoDBSpec{ + Replsets: []*api.ReplsetSpec{ + { + Name: "rs0", + Size: 3, + }, + }, + Sharding: api.Sharding{ + Enabled: false, + }, + Backup: api.BackupSpec{ + Enabled: true, + }, + }, + Status: api.PerconaServerMongoDBStatus{ + Host: "some-name-rs0.some-namespace", + BackupVersion: "2.12.0", + }, + }, + clusterState: api.AppStateReady, + expectedState: api.AppStateReady, + runtimeObjs: []client.Object{ + mockReadyReplsetSts("some-name-rs0", "some-namespace", "some-name", "rs0", "mongod", 3), + mockReadyReplsetPod("some-name-rs0-0", "some-namespace", "some-name", "rs0", "mongod"), + mockReadyReplsetPod("some-name-rs0-1", "some-namespace", "some-name", "rs0", "mongod"), + mockReadyReplsetPod("some-name-rs0-2", "some-namespace", "some-name", "rs0", "mongod"), + }, + }, + { + name: "single replset-backup config hash is empty", + cr: &api.PerconaServerMongoDB{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-name", + Namespace: "some-namespace", + }, + Spec: api.PerconaServerMongoDBSpec{ + Replsets: []*api.ReplsetSpec{ + { + Name: "rs0", + Size: 3, + }, + }, + Sharding: api.Sharding{ + Enabled: false, + }, + Backup: api.BackupSpec{ + Enabled: true, + Storages: map[string]api.BackupStorageSpec{ + "s3": api.BackupStorageSpec{ + S3: api.BackupStorageS3Spec{ + Bucket: "operator-testing", + }, + }, + }, + }, + }, + Status: api.PerconaServerMongoDBStatus{ + Host: "some-name-rs0.some-namespace", + BackupVersion: "2.12.0", + }, + }, + clusterState: api.AppStateReady, + expectedState: api.AppStateInit, + runtimeObjs: []client.Object{ + mockReadyReplsetSts("some-name-rs0", "some-namespace", "some-name", "rs0", "mongod", 3), + mockReadyReplsetPod("some-name-rs0-0", "some-namespace", "some-name", "rs0", "mongod"), + mockReadyReplsetPod("some-name-rs0-1", "some-namespace", "some-name", "rs0", "mongod"), + mockReadyReplsetPod("some-name-rs0-2", "some-namespace", "some-name", "rs0", "mongod"), + }, + }, + { + name: "single replset-PBM is ready", + cr: &api.PerconaServerMongoDB{ + ObjectMeta: metav1.ObjectMeta{ + Name: "some-name", + Namespace: "some-namespace", + }, + Spec: api.PerconaServerMongoDBSpec{ + Replsets: []*api.ReplsetSpec{ + { + Name: "rs0", + Size: 3, + }, + }, + Sharding: api.Sharding{ + Enabled: false, + }, + Backup: api.BackupSpec{ + Enabled: true, + Storages: map[string]api.BackupStorageSpec{ + "s3": api.BackupStorageSpec{ + S3: api.BackupStorageS3Spec{ + Bucket: "operator-testing", + }, + }, + }, + }, + }, + Status: api.PerconaServerMongoDBStatus{ + Host: "some-name-rs0.some-namespace", + BackupVersion: "2.12.0", + BackupConfigHash: "9a8a1b4b11b0605c99c5f7575a5c83be0a2567115984ee0046b50f80a3503eb5", + }, + }, + clusterState: api.AppStateReady, + expectedState: api.AppStateReady, + runtimeObjs: []client.Object{ + mockReadyReplsetSts("some-name-rs0", "some-namespace", "some-name", "rs0", "mongod", 3), + mockReadyReplsetPod("some-name-rs0-0", "some-namespace", "some-name", "rs0", "mongod"), + mockReadyReplsetPod("some-name-rs0-1", "some-namespace", "some-name", "rs0", "mongod"), + mockReadyReplsetPod("some-name-rs0-2", "some-namespace", "some-name", "rs0", "mongod"), + }, + }, } - if cr.Status.State != api.AppStateInit { - t.Errorf("cr.Status.State got %#v, want %#v", cr.Status.State, api.AppStateInit) + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + objs := append([]client.Object{tt.cr}, tt.runtimeObjs...) + r := buildFakeClient(objs...) + err := r.updateStatus(t.Context(), tt.cr, tt.reconcileErr, tt.clusterState) + require.NoError(t, err) + assert.Equal(t, tt.expectedState, tt.cr.Status.State) + }) } }