Skip to content

Commit 2010b55

Browse files
committed
Merge branch 'master' into anandsyncs/add-certmanager-community-search-snippets
2 parents 86d13a7 + 1f94ac3 commit 2010b55

15 files changed

+533
-383
lines changed

api/v1/mdb/mongodb_types.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,9 @@ func GetLastAdditionalMongodConfigByType(lastSpec *MongoDbSpec, configType Addit
243243

244244
// GetLastAdditionalMongodConfigByType returns the last successfully achieved AdditionalMongodConfigType for the given component.
245245
func (m *MongoDB) GetLastAdditionalMongodConfigByType(configType AdditionalMongodConfigType) (*AdditionalMongodConfig, error) {
246+
if m.Spec.GetResourceType() == ReplicaSet {
247+
panic(errors.Errorf("this method cannot be used from ReplicaSet controller; use non-method GetLastAdditionalMongodConfigByType and pass lastSpec from the deployment state."))
248+
}
246249
if m.Spec.GetResourceType() == ShardedCluster {
247250
panic(errors.Errorf("this method cannot be used from ShardedCluster controller; use non-method GetLastAdditionalMongodConfigByType and pass lastSpec from the deployment state."))
248251
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
kind: other
3+
date: 2025-10-15
4+
---
5+
6+
* Simplified MongoDB Search setup: Removed the custom Search Coordinator polyfill (a piece of compatibility code previously needed to add the required permissions), as MongoDB 8.2.0 and later now include the necessary permissions via the built-in searchCoordinator role.

controllers/om/deployment/testing_utils.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ func CreateFromReplicaSet(mongoDBImage string, forceEnterprise bool, rs *mdb.Mon
2626
), zap.S())
2727
d := om.NewDeployment()
2828

29-
lastConfig, err := rs.GetLastAdditionalMongodConfigByType(mdb.ReplicaSetConfig)
29+
lastConfig, err := mdb.GetLastAdditionalMongodConfigByType(nil, mdb.ReplicaSetConfig)
3030
if err != nil {
3131
panic(err)
3232
}

controllers/operator/mongodbreplicaset_controller.go

Lines changed: 300 additions & 164 deletions
Large diffs are not rendered by default.

controllers/operator/mongodbreplicaset_controller_test.go

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package operator
22

33
import (
44
"context"
5+
"encoding/json"
56
"fmt"
67
"reflect"
78
"testing"
@@ -858,6 +859,130 @@ func TestHandlePVCResize(t *testing.T) {
858859
testPVCFinishedResizing(t, ctx, memberClient, p, reconciledResource, statefulSet, logger)
859860
}
860861

862+
// ===== Test for state and vault annotations handling in replicaset controller =====
863+
864+
// TestReplicaSetAnnotations_WrittenOnSuccess verifies that lastAchievedSpec annotation is written after successful
865+
// reconciliation.
866+
func TestReplicaSetAnnotations_WrittenOnSuccess(t *testing.T) {
867+
ctx := context.Background()
868+
rs := DefaultReplicaSetBuilder().Build()
869+
870+
reconciler, client, _ := defaultReplicaSetReconciler(ctx, nil, "", "", rs)
871+
872+
checkReconcileSuccessful(ctx, t, reconciler, rs, client)
873+
874+
err := client.Get(ctx, rs.ObjectKey(), rs)
875+
require.NoError(t, err)
876+
877+
require.Contains(t, rs.Annotations, util.LastAchievedSpec,
878+
"lastAchievedSpec annotation should be written on successful reconciliation")
879+
880+
var lastSpec mdbv1.MongoDbSpec
881+
err = json.Unmarshal([]byte(rs.Annotations[util.LastAchievedSpec]), &lastSpec)
882+
require.NoError(t, err)
883+
assert.Equal(t, 3, lastSpec.Members)
884+
assert.Equal(t, "4.0.0", lastSpec.Version)
885+
}
886+
887+
// TestReplicaSetAnnotations_NotWrittenOnFailure verifies that lastAchievedSpec annotation
888+
// is NOT written when reconciliation fails.
889+
func TestReplicaSetAnnotations_NotWrittenOnFailure(t *testing.T) {
890+
ctx := context.Background()
891+
rs := DefaultReplicaSetBuilder().Build()
892+
893+
// Setup without credentials secret to cause failure
894+
kubeClient := mock.NewEmptyFakeClientBuilder().
895+
WithObjects(rs).
896+
WithObjects(mock.GetProjectConfigMap(mock.TestProjectConfigMapName, "testProject", "testOrg")).
897+
Build()
898+
899+
reconciler := newReplicaSetReconciler(ctx, kubeClient, nil, "", "", false, false, nil)
900+
901+
_, err := reconciler.Reconcile(ctx, requestFromObject(rs))
902+
require.NoError(t, err, "Reconcile should not return error (error captured in status)")
903+
904+
err = kubeClient.Get(ctx, rs.ObjectKey(), rs)
905+
require.NoError(t, err)
906+
907+
assert.NotEqual(t, status.PhaseRunning, rs.Status.Phase)
908+
909+
assert.NotContains(t, rs.Annotations, util.LastAchievedSpec,
910+
"lastAchievedSpec should NOT be written when reconciliation fails")
911+
}
912+
913+
// TestReplicaSetAnnotations_PreservedOnSubsequentFailure verifies that annotations from a previous successful
914+
// reconciliation are preserved when a later reconciliation fails.
915+
func TestReplicaSetAnnotations_PreservedOnSubsequentFailure(t *testing.T) {
916+
ctx := context.Background()
917+
rs := DefaultReplicaSetBuilder().Build()
918+
919+
kubeClient, omConnectionFactory := mock.NewDefaultFakeClient(rs)
920+
reconciler := newReplicaSetReconciler(ctx, kubeClient, nil, "", "", false, false, omConnectionFactory.GetConnectionFunc)
921+
922+
_, err := reconciler.Reconcile(ctx, requestFromObject(rs))
923+
require.NoError(t, err)
924+
925+
err = kubeClient.Get(ctx, rs.ObjectKey(), rs)
926+
require.NoError(t, err)
927+
require.Contains(t, rs.Annotations, util.LastAchievedSpec)
928+
929+
originalLastAchievedSpec := rs.Annotations[util.LastAchievedSpec]
930+
931+
// Delete credentials to cause failure
932+
credentialsSecret := mock.GetCredentialsSecret("testUser", "testApiKey")
933+
err = kubeClient.Delete(ctx, credentialsSecret)
934+
require.NoError(t, err)
935+
936+
rs.Spec.Members = 5
937+
err = kubeClient.Update(ctx, rs)
938+
require.NoError(t, err)
939+
940+
_, err = reconciler.Reconcile(ctx, requestFromObject(rs))
941+
require.NoError(t, err)
942+
943+
err = kubeClient.Get(ctx, rs.ObjectKey(), rs)
944+
require.NoError(t, err)
945+
946+
assert.Contains(t, rs.Annotations, util.LastAchievedSpec)
947+
assert.NotEqual(t, status.PhaseRunning, rs.Status.Phase)
948+
assert.Equal(t, originalLastAchievedSpec, rs.Annotations[util.LastAchievedSpec],
949+
"lastAchievedSpec should remain unchanged when reconciliation fails")
950+
951+
var lastSpec mdbv1.MongoDbSpec
952+
err = json.Unmarshal([]byte(rs.Annotations[util.LastAchievedSpec]), &lastSpec)
953+
require.NoError(t, err)
954+
assert.Equal(t, 3, lastSpec.Members,
955+
"Should still reflect previous successful state (3 members, not 5)")
956+
}
957+
958+
// TestVaultAnnotations_NotWrittenWhenDisabled verifies that vault annotations are NOT
959+
// written when vault backend is disabled.
960+
func TestVaultAnnotations_NotWrittenWhenDisabled(t *testing.T) {
961+
ctx := context.Background()
962+
rs := DefaultReplicaSetBuilder().Build()
963+
964+
t.Setenv("SECRET_BACKEND", "K8S_SECRET_BACKEND")
965+
966+
reconciler, client, _ := defaultReplicaSetReconciler(ctx, nil, "", "", rs)
967+
968+
checkReconcileSuccessful(ctx, t, reconciler, rs, client)
969+
970+
err := client.Get(ctx, rs.ObjectKey(), rs)
971+
require.NoError(t, err)
972+
973+
require.Contains(t, rs.Annotations, util.LastAchievedSpec,
974+
"lastAchievedSpec should be written even when vault is disabled")
975+
976+
// Vault annotations would be simple secret names like "my-secret": "5"
977+
for key := range rs.Annotations {
978+
if key == util.LastAchievedSpec {
979+
continue
980+
}
981+
assert.NotRegexp(t, "^[a-z0-9-]+$", key,
982+
"Should not have simple secret name annotations when vault disabled - found: %s", key)
983+
}
984+
}
985+
861986
func testPVCFinishedResizing(t *testing.T, ctx context.Context, memberClient kubernetesClient.Client, p *corev1.PersistentVolumeClaim, reconciledResource *mdbv1.MongoDB, statefulSet *appsv1.StatefulSet, logger *zap.SugaredLogger) {
862987
// Simulate that the PVC has finished resizing
863988
setPVCWithUpdatedResource(ctx, t, memberClient, p)

controllers/operator/mongodbsearch_controller_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func newMongoDBCommunity(name, namespace string) *mdbcv1.MongoDBCommunity {
3636
Spec: mdbcv1.MongoDBCommunitySpec{
3737
Type: mdbcv1.ReplicaSet,
3838
Members: 1,
39-
Version: "8.0.10",
39+
Version: "8.2.0",
4040
},
4141
}
4242
}

controllers/searchcontroller/community_search_source.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,8 @@ func (r *CommunitySearchSource) Validate() error {
6464
version, err := semver.ParseTolerant(r.GetMongoDBVersion())
6565
if err != nil {
6666
return xerrors.Errorf("error parsing MongoDB version '%s': %w", r.Spec.Version, err)
67-
} else if version.LT(semver.MustParse("8.0.10")) {
68-
return xerrors.New("MongoDB version must be 8.0.10 or higher")
67+
} else if version.LT(semver.MustParse("8.2.0")) {
68+
return xerrors.New("MongoDB version must be 8.2.0 or higher")
6969
}
7070

7171
foundScram := false

controllers/searchcontroller/community_search_source_test.go

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -50,120 +50,120 @@ func TestCommunitySearchSource_Validate(t *testing.T) {
5050
version: "7.0.0",
5151
authModes: []mdbcv1.AuthMode{"SCRAM-SHA-256"},
5252
expectError: true,
53-
expectedErrMsg: "MongoDB version must be 8.0.10 or higher",
53+
expectedErrMsg: "MongoDB version must be 8.2.0 or higher",
5454
},
5555
{
5656
name: "Version just below minimum",
57-
version: "8.0.9",
57+
version: "8.1.9",
5858
authModes: []mdbcv1.AuthMode{"SCRAM-SHA-256"},
5959
expectError: true,
60-
expectedErrMsg: "MongoDB version must be 8.0.10 or higher",
60+
expectedErrMsg: "MongoDB version must be 8.2.0 or higher",
6161
},
6262
{
6363
name: "Valid minimum version",
64-
version: "8.0.10",
64+
version: "8.2.0",
6565
authModes: []mdbcv1.AuthMode{"SCRAM-SHA-256"},
6666
expectError: false,
6767
},
6868
{
6969
name: "Version above minimum",
70-
version: "8.1.0",
70+
version: "8.3.0",
7171
authModes: []mdbcv1.AuthMode{"SCRAM-SHA-256"},
7272
expectError: false,
7373
},
7474
{
7575
name: "Version with build number",
76-
version: "8.1.0-rc1",
76+
version: "8.3.0-rc1",
7777
authModes: []mdbcv1.AuthMode{"SCRAM-SHA-256"},
7878
expectError: false,
7979
},
8080
// Authentication mode tests - empty/nil cases
8181
{
8282
name: "Empty auth modes",
83-
version: "8.0.10",
83+
version: "8.2.0",
8484
authModes: []mdbcv1.AuthMode{},
8585
expectError: false,
8686
},
8787
{
8888
name: "Nil auth modes",
89-
version: "8.0.10",
89+
version: "8.2.0",
9090
authModes: nil,
9191
expectError: false,
9292
},
9393
{
9494
name: "X509 mode only",
95-
version: "8.0.10",
95+
version: "8.2.0",
9696
authModes: []mdbcv1.AuthMode{"X509"},
9797
expectError: true,
9898
expectedErrMsg: "MongoDBSearch requires SCRAM authentication to be enabled",
9999
},
100100
{
101101
name: "X509 and SCRAM",
102-
version: "8.0.10",
102+
version: "8.2.0",
103103
authModes: []mdbcv1.AuthMode{"X509", "SCRAM-SHA-256"},
104104
expectError: false,
105105
},
106106
{
107107
name: "Multiple auth modes with SCRAM first",
108-
version: "8.0.10",
108+
version: "8.2.0",
109109
authModes: []mdbcv1.AuthMode{"SCRAM-SHA-1", "X509"},
110110
expectError: false,
111111
},
112112
{
113113
name: "Multiple auth modes with SCRAM last",
114-
version: "8.0.10",
114+
version: "8.2.0",
115115
authModes: []mdbcv1.AuthMode{"PLAIN", "X509", "SCRAM-SHA-256"},
116116
expectError: false,
117117
},
118118
{
119119
name: "Multiple non-SCRAM modes",
120-
version: "8.0.10",
120+
version: "8.2.0",
121121
authModes: []mdbcv1.AuthMode{"PLAIN", "X509"},
122122
expectError: true,
123123
expectedErrMsg: "MongoDBSearch requires SCRAM authentication to be enabled",
124124
},
125125
// SCRAM variant tests
126126
{
127127
name: "SCRAM only",
128-
version: "8.0.10",
128+
version: "8.2.0",
129129
authModes: []mdbcv1.AuthMode{"SCRAM"},
130130
expectError: false,
131131
},
132132
{
133133
name: "SCRAM-SHA-1 only",
134-
version: "8.0.10",
134+
version: "8.2.0",
135135
authModes: []mdbcv1.AuthMode{"SCRAM-SHA-1"},
136136
expectError: false,
137137
},
138138
{
139139
name: "SCRAM-SHA-256 only",
140-
version: "8.0.10",
140+
version: "8.2.0",
141141
authModes: []mdbcv1.AuthMode{"SCRAM-SHA-256"},
142142
expectError: false,
143143
},
144144
{
145145
name: "All SCRAM variants",
146-
version: "8.0.10",
146+
version: "8.2.0",
147147
authModes: []mdbcv1.AuthMode{"SCRAM", "SCRAM-SHA-1", "SCRAM-SHA-256"},
148148
expectError: false,
149149
},
150150
// Case-insensitive tests (now supported with ToUpper)
151151
{
152152
name: "Lowercase SCRAM",
153-
version: "8.0.10",
153+
version: "8.2.0",
154154
authModes: []mdbcv1.AuthMode{"scram-sha-256"},
155155
expectError: false,
156156
},
157157
{
158158
name: "Mixed case SCRAM",
159-
version: "8.0.10",
159+
version: "8.2.0",
160160
authModes: []mdbcv1.AuthMode{"Scram-Sha-256"},
161161
expectError: false,
162162
},
163163
// Edge case tests
164164
{
165165
name: "PLAIN only",
166-
version: "8.0.10",
166+
version: "8.2.0",
167167
authModes: []mdbcv1.AuthMode{"PLAIN"},
168168
expectError: true,
169169
expectedErrMsg: "MongoDBSearch requires SCRAM authentication to be enabled",
@@ -174,11 +174,11 @@ func TestCommunitySearchSource_Validate(t *testing.T) {
174174
version: "7.0.0",
175175
authModes: []mdbcv1.AuthMode{"SCRAM-SHA-256"},
176176
expectError: true,
177-
expectedErrMsg: "MongoDB version must be 8.0.10 or higher",
177+
expectedErrMsg: "MongoDB version must be 8.2.0 or higher",
178178
},
179179
{
180180
name: "Valid version with invalid auth",
181-
version: "8.0.10",
181+
version: "8.2.0",
182182
authModes: []mdbcv1.AuthMode{"X509"},
183183
expectError: true,
184184
expectedErrMsg: "MongoDBSearch requires SCRAM authentication to be enabled",
@@ -188,7 +188,7 @@ func TestCommunitySearchSource_Validate(t *testing.T) {
188188
version: "7.0.0",
189189
authModes: []mdbcv1.AuthMode{"X509"},
190190
expectError: true,
191-
expectedErrMsg: "MongoDB version must be 8.0.10 or higher", // Should fail on version first
191+
expectedErrMsg: "MongoDB version must be 8.2.0 or higher", // Should fail on version first
192192
},
193193
}
194194

controllers/searchcontroller/enterprise_search_source.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ func (r EnterpriseResourceSearchSource) Validate() error {
5555
version, err := semver.ParseTolerant(util.StripEnt(r.Spec.GetMongoDBVersion()))
5656
if err != nil {
5757
return xerrors.Errorf("error parsing MongoDB version '%s': %w", r.Spec.GetMongoDBVersion(), err)
58-
} else if version.LT(semver.MustParse("8.0.10")) {
59-
return xerrors.New("MongoDB version must be 8.0.10 or higher")
58+
} else if version.LT(semver.MustParse("8.2.0")) {
59+
return xerrors.New("MongoDB version must be 8.2.0 or higher")
6060
}
6161

6262
if r.Spec.GetTopology() != mdbv1.ClusterTopologySingleCluster {

0 commit comments

Comments
 (0)