Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
a6b9d48
RS Controller refactor fixed merge conflicts
Julien-Ben Oct 1, 2025
4196529
Fix and improve certificates handling
Julien-Ben Oct 1, 2025
1eead94
Pass current agent auth mode
Julien-Ben Oct 1, 2025
e913144
currentAgentAuthMode in deploymentOptions
Julien-Ben Oct 1, 2025
74cc89a
PrepareScaleDown without need for sts
Julien-Ben Oct 1, 2025
dd4756f
Pass configmap to publishAutomationConfigFirstRS directly
Julien-Ben Oct 1, 2025
c3ef579
Lint
Julien-Ben Oct 1, 2025
e41d24a
Commit TODOs
Julien-Ben Oct 1, 2025
c89617b
Remove TODOs
Julien-Ben Oct 1, 2025
eb78918
Lint
Julien-Ben Oct 1, 2025
073032b
Edge case TLS disabled and rs scaled
Julien-Ben Oct 1, 2025
8c4f229
Remove unused lines
Julien-Ben Oct 1, 2025
691d804
Import order
Julien-Ben Oct 1, 2025
31daeae
Fix comment
Julien-Ben Oct 1, 2025
310030e
Run applySearchOverrides early
Julien-Ben Oct 1, 2025
82467d6
Revert "Edge case TLS disabled and rs scaled"
Julien-Ben Oct 3, 2025
c7268d1
Revert to controller gen 0.18
Julien-Ben Oct 3, 2025
e302990
Fix edge case
Julien-Ben Oct 3, 2025
02920f8
TestPublishAutomationConfigFirstRS
Julien-Ben Oct 7, 2025
580927f
Remove old comment
Julien-Ben Oct 7, 2025
ea14853
TestCreateMongodProcessesFromMongoDB
Julien-Ben Oct 7, 2025
1376b5d
TestBuildFromMongoDBWithReplicas
Julien-Ben Oct 7, 2025
3c30758
Revert unrelated changed
Julien-Ben Oct 7, 2025
e2d2e33
Lint`
Julien-Ben Oct 7, 2025
1c1446a
Merge branch 'master' into jben/rs-controller-refactor-clean-rebase
Julien-Ben Oct 7, 2025
96e6dc0
Update doc
Julien-Ben Oct 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion api/v1/mdb/mongodb_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -1037,7 +1037,7 @@ func (a *Authentication) IsOIDCEnabled() bool {
return stringutil.Contains(a.GetModes(), util.OIDC)
}

// GetModes returns the modes of the Authentication instance of an empty
// GetModes returns the modes of the Authentication instance, or an empty
// list if it is nil
func (a *Authentication) GetModes() []string {
if a == nil {
Expand Down
12 changes: 12 additions & 0 deletions controllers/om/process/om_process.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,18 @@ func CreateMongodProcessesWithLimit(mongoDBImage string, forceEnterprise bool, s
return processes
}

// CreateMongodProcessesFromMongoDB creates mongod processes directly from MongoDB resource without StatefulSet
func CreateMongodProcessesFromMongoDB(mongoDBImage string, forceEnterprise bool, mdb *mdbv1.MongoDB, limit int, fcv string, tlsCertPath string) []om.Process {
hostnames, names := dns.GetDNSNames(mdb.Name, mdb.ServiceName(), mdb.Namespace, mdb.Spec.GetClusterDomain(), limit, mdb.Spec.DbCommonSpec.GetExternalDomain())
processes := make([]om.Process, len(hostnames))

for idx, hostname := range hostnames {
processes[idx] = om.NewMongodProcess(names[idx], hostname, mongoDBImage, forceEnterprise, mdb.Spec.GetAdditionalMongodConfig(), &mdb.Spec, tlsCertPath, mdb.Annotations, fcv)
}

return processes
}

// CreateMongodProcessesWithLimitMulti creates the process array for automationConfig based on MultiCluster CR spec
func CreateMongodProcessesWithLimitMulti(mongoDBImage string, forceEnterprise bool, mrs mdbmultiv1.MongoDBMultiCluster, certFileName string) ([]om.Process, error) {
hostnames := make([]string, 0)
Expand Down
230 changes: 230 additions & 0 deletions controllers/om/process/om_process_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
package process

import (
"testing"

"github.com/stretchr/testify/assert"

mdbv1 "github.com/mongodb/mongodb-kubernetes/api/v1/mdb"
"github.com/mongodb/mongodb-kubernetes/pkg/util/maputil"
)

const (
defaultMongoDBImage = "mongodb/mongodb-enterprise-server:7.0.0"
defaultFCV = "7.0"
defaultNamespace = "test-namespace"
)

func TestCreateMongodProcessesFromMongoDB(t *testing.T) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test and TestCreateMongodProcessesFromMongoDB_AdditionalConfig seem to be testing om.NewMongodProcess (already has own unit tests) and dns.GetDNSNames (doesn't have own unit tests).

I think it is better to unit-test om.NewMongodProcess and dns.GetDNSNames separately. Once you have unit tests for these building blocks you wouldn't need to test the integration so thoroughly.

This also would (indirectly) cover WaitForRsAgentsToRegisterByResource and other places where these functions used.

tests := []struct {
name string
mdb *mdbv1.MongoDB
limit int
mongoDBImage string
forceEnterprise bool
fcv string
tlsCertPath string
expectedCount int
expectedHostnames []string
expectedNames []string
}{
{
name: "3-member replica set",
mdb: baseReplicaSet("test-rs", 3),
limit: 3,
mongoDBImage: defaultMongoDBImage,
forceEnterprise: false,
fcv: defaultFCV,
expectedCount: 3,
expectedHostnames: []string{
"test-rs-0.test-rs-svc.test-namespace.svc.cluster.local",
"test-rs-1.test-rs-svc.test-namespace.svc.cluster.local",
"test-rs-2.test-rs-svc.test-namespace.svc.cluster.local",
},
expectedNames: []string{"test-rs-0", "test-rs-1", "test-rs-2"},
},
{
name: "Single member replica set",
mdb: baseReplicaSet("single-rs", 1),
limit: 1,
mongoDBImage: defaultMongoDBImage,
forceEnterprise: false,
fcv: defaultFCV,
expectedCount: 1,
expectedHostnames: []string{
"single-rs-0.single-rs-svc.test-namespace.svc.cluster.local",
},
expectedNames: []string{"single-rs-0"},
},
{
name: "Limit less than members (scale up in progress)",
mdb: baseReplicaSet("scale-up-rs", 5),
limit: 3,
mongoDBImage: defaultMongoDBImage,
forceEnterprise: false,
fcv: defaultFCV,
expectedCount: 3,
expectedHostnames: []string{
"scale-up-rs-0.scale-up-rs-svc.test-namespace.svc.cluster.local",
"scale-up-rs-1.scale-up-rs-svc.test-namespace.svc.cluster.local",
"scale-up-rs-2.scale-up-rs-svc.test-namespace.svc.cluster.local",
},
expectedNames: []string{"scale-up-rs-0", "scale-up-rs-1", "scale-up-rs-2"},
},
{
name: "Limit greater than members (scale down in progress)",
mdb: baseReplicaSet("scale-down-rs", 3),
limit: 5,
mongoDBImage: defaultMongoDBImage,
forceEnterprise: false,
fcv: defaultFCV,
expectedCount: 5,
expectedHostnames: []string{
"scale-down-rs-0.scale-down-rs-svc.test-namespace.svc.cluster.local",
"scale-down-rs-1.scale-down-rs-svc.test-namespace.svc.cluster.local",
"scale-down-rs-2.scale-down-rs-svc.test-namespace.svc.cluster.local",
"scale-down-rs-3.scale-down-rs-svc.test-namespace.svc.cluster.local",
"scale-down-rs-4.scale-down-rs-svc.test-namespace.svc.cluster.local",
},
expectedNames: []string{"scale-down-rs-0", "scale-down-rs-1", "scale-down-rs-2", "scale-down-rs-3", "scale-down-rs-4"},
},
{
name: "Limit zero creates empty slice",
mdb: baseReplicaSet("empty-rs", 3),
limit: 0,
mongoDBImage: defaultMongoDBImage,
forceEnterprise: false,
fcv: defaultFCV,
expectedCount: 0,
expectedHostnames: []string{},
expectedNames: []string{},
},
{
name: "Custom cluster domain",
mdb: func() *mdbv1.MongoDB {
rs := baseReplicaSet("custom-domain-rs", 2)
rs.Spec.ClusterDomain = "my-cluster.local"
return rs
}(),

limit: 2,
mongoDBImage: defaultMongoDBImage,
forceEnterprise: false,
fcv: defaultFCV,
expectedCount: 2,
expectedHostnames: []string{
"custom-domain-rs-0.custom-domain-rs-svc.test-namespace.svc.my-cluster.local",
"custom-domain-rs-1.custom-domain-rs-svc.test-namespace.svc.my-cluster.local",
},
expectedNames: []string{"custom-domain-rs-0", "custom-domain-rs-1"},
},
{
name: "Different namespace",
mdb: func() *mdbv1.MongoDB {
rs := baseReplicaSet("other-ns-rs", 2)
rs.Namespace = "production"
return rs
}(),
limit: 2,
mongoDBImage: defaultMongoDBImage,
forceEnterprise: false,
fcv: defaultFCV,
expectedCount: 2,
expectedHostnames: []string{
"other-ns-rs-0.other-ns-rs-svc.production.svc.cluster.local",
"other-ns-rs-1.other-ns-rs-svc.production.svc.cluster.local",
},
expectedNames: []string{"other-ns-rs-0", "other-ns-rs-1"},
},
{
name: "With TLS cert path",
mdb: func() *mdbv1.MongoDB {
rs := baseReplicaSet("tls-rs", 2)
rs.Spec.Security = &mdbv1.Security{
TLSConfig: &mdbv1.TLSConfig{Enabled: true},
}
return rs
}(),
limit: 2,
mongoDBImage: defaultMongoDBImage,
forceEnterprise: false,
fcv: defaultFCV,
tlsCertPath: "/path/to/cert.pem",
expectedCount: 2,
expectedHostnames: []string{
"tls-rs-0.tls-rs-svc.test-namespace.svc.cluster.local",
"tls-rs-1.tls-rs-svc.test-namespace.svc.cluster.local",
},
expectedNames: []string{"tls-rs-0", "tls-rs-1"},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
processes := CreateMongodProcessesFromMongoDB(
tt.mongoDBImage,
tt.forceEnterprise,
tt.mdb,
tt.limit,
tt.fcv,
tt.tlsCertPath,
)

assert.Equal(t, tt.expectedCount, len(processes), "Process count mismatch")

for i, process := range processes {
assert.Equal(t, tt.expectedNames[i], process.Name(), "Process name mismatch at index %d", i)
assert.Equal(t, tt.expectedHostnames[i], process.HostName(), "Hostname mismatch at index %d", i)
assert.Equal(t, tt.fcv, process.FeatureCompatibilityVersion(), "FCV mismatch at index %d", i)

if tt.tlsCertPath != "" {
tlsConfig := process.TLSConfig()
assert.NotNil(t, tlsConfig, "TLS config should not be nil when cert path is provided")
assert.Equal(t, tt.tlsCertPath, tlsConfig["certificateKeyFile"], "TLS cert path mismatch at index %d", i)
}
}
})
}
}

func TestCreateMongodProcessesFromMongoDB_AdditionalConfig(t *testing.T) {
config := mdbv1.NewAdditionalMongodConfig("storage.engine", "inMemory").
AddOption("replication.oplogSizeMB", 2048)

mdb := mdbv1.NewReplicaSetBuilder().
SetName("config-rs").
SetNamespace(defaultNamespace).
SetMembers(2).
SetVersion("7.0.0").
SetFCVersion(defaultFCV).
SetAdditionalConfig(config).
Build()

processes := CreateMongodProcessesFromMongoDB(
defaultMongoDBImage,
false,
mdb,
2,
defaultFCV,
"",
)

assert.Len(t, processes, 2)

for i, process := range processes {
assert.Equal(t, "inMemory", maputil.ReadMapValueAsInterface(process.Args(), "storage", "engine"),
"Storage engine mismatch at index %d", i)
assert.Equal(t, 2048, maputil.ReadMapValueAsInterface(process.Args(), "replication", "oplogSizeMB"),
"OplogSizeMB mismatch at index %d", i)
}
}

func baseReplicaSet(name string, members int) *mdbv1.MongoDB {
return mdbv1.NewReplicaSetBuilder().
SetName(name).
SetNamespace(defaultNamespace).
SetMembers(members).
SetVersion("7.0.0").
SetFCVersion(defaultFCV).
Build()
}
31 changes: 12 additions & 19 deletions controllers/om/replicaset/om_replicaset.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@ func BuildFromStatefulSetWithReplicas(mongoDBImage string, forceEnterprise bool,
return rsWithProcesses
}

// BuildFromMongoDBWithReplicas returns a replica set that can be set in the Automation Config
// based on the given MongoDB resource directly without requiring a StatefulSet.
func BuildFromMongoDBWithReplicas(mongoDBImage string, forceEnterprise bool, mdb *mdbv1.MongoDB, replicas int, fcv string, tlsCertPath string) om.ReplicaSetWithProcesses {
members := process.CreateMongodProcessesFromMongoDB(mongoDBImage, forceEnterprise, mdb, replicas, fcv, tlsCertPath)
replicaSet := om.NewReplicaSet(mdb.Name, mdb.Spec.GetMongoDBVersion())
rsWithProcesses := om.NewReplicaSetWithProcesses(replicaSet, members, mdb.Spec.GetMemberOptions())
rsWithProcesses.SetHorizons(mdb.Spec.GetHorizonConfig())
return rsWithProcesses
}

// PrepareScaleDownFromMap performs additional steps necessary to make sure removed members are not primary (so no
// election happens and replica set is available) (see
// https://jira.mongodb.org/browse/HELP-3818?focusedCommentId=1548348 for more details)
Expand Down Expand Up @@ -65,30 +75,13 @@ func PrepareScaleDownFromMap(omClient om.Connection, rsMembers map[string][]stri
log.Debugw("Marked replica set members as non-voting", "replica set with members", rsMembers)
}

// TODO practice shows that automation agents can get stuck on setting db to "disabled" also it seems that this process
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment was old (<2022)

// works correctly without explicit disabling - feel free to remove this code after some time when it is clear
// that everything works correctly without disabling

// Stage 2. Set disabled to true
//err = omClient.ReadUpdateDeployment(
// func(d om.Deployment) error {
// d.DisableProcesses(allProcesses)
// return nil
// },
//)
//
//if err != nil {
// return errors.New(fmt.Sprintf("Unable to set disabled to true, hosts: %v, err: %w", allProcesses, err))
//}
//log.Debugw("Disabled processes", "processes", allProcesses)

log.Infow("Performed some preliminary steps to support scale down", "hosts", processes)

return nil
}

func PrepareScaleDownFromStatefulSet(omClient om.Connection, statefulSet appsv1.StatefulSet, rs *mdbv1.MongoDB, log *zap.SugaredLogger) error {
_, podNames := dns.GetDnsForStatefulSetReplicasSpecified(statefulSet, rs.Spec.GetClusterDomain(), rs.Status.Members, nil)
func PrepareScaleDownFromMongoDB(omClient om.Connection, rs *mdbv1.MongoDB, log *zap.SugaredLogger) error {
_, podNames := dns.GetDNSNames(rs.Name, rs.ServiceName(), rs.Namespace, rs.Spec.GetClusterDomain(), rs.Status.Members, rs.Spec.DbCommonSpec.GetExternalDomain())
podNames = podNames[scale.ReplicasThisReconciliation(rs):rs.Status.Members]

if len(podNames) != 1 {
Expand Down
Loading