@@ -4,12 +4,16 @@ import (
44 "context"
55 "fmt"
66
7+ "github.com/blang/semver"
78 "go.uber.org/zap"
89 "golang.org/x/xerrors"
910 "k8s.io/apimachinery/pkg/api/errors"
11+ "k8s.io/apimachinery/pkg/fields"
1012 "k8s.io/apimachinery/pkg/runtime"
13+ "k8s.io/apimachinery/pkg/types"
1114 "sigs.k8s.io/controller-runtime/pkg/client"
1215 "sigs.k8s.io/controller-runtime/pkg/controller"
16+ "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
1317 "sigs.k8s.io/controller-runtime/pkg/event"
1418 "sigs.k8s.io/controller-runtime/pkg/handler"
1519 "sigs.k8s.io/controller-runtime/pkg/manager"
@@ -22,6 +26,7 @@ import (
2226
2327 mdbv1 "github.com/mongodb/mongodb-kubernetes/api/v1/mdb"
2428 rolev1 "github.com/mongodb/mongodb-kubernetes/api/v1/role"
29+ searchv1 "github.com/mongodb/mongodb-kubernetes/api/v1/search"
2530 mdbstatus "github.com/mongodb/mongodb-kubernetes/api/v1/status"
2631 "github.com/mongodb/mongodb-kubernetes/controllers/om"
2732 "github.com/mongodb/mongodb-kubernetes/controllers/om/backup"
@@ -39,6 +44,7 @@ import (
3944 "github.com/mongodb/mongodb-kubernetes/controllers/operator/recovery"
4045 "github.com/mongodb/mongodb-kubernetes/controllers/operator/watch"
4146 "github.com/mongodb/mongodb-kubernetes/controllers/operator/workflow"
47+ "github.com/mongodb/mongodb-kubernetes/controllers/search_controller"
4248 mcoConstruct "github.com/mongodb/mongodb-kubernetes/mongodb-community-operator/controllers/construct"
4349 "github.com/mongodb/mongodb-kubernetes/mongodb-community-operator/pkg/kube/annotations"
4450 "github.com/mongodb/mongodb-kubernetes/mongodb-community-operator/pkg/kube/configmap"
@@ -52,6 +58,7 @@ import (
5258 "github.com/mongodb/mongodb-kubernetes/pkg/util/architectures"
5359 "github.com/mongodb/mongodb-kubernetes/pkg/util/env"
5460 util_int "github.com/mongodb/mongodb-kubernetes/pkg/util/int"
61+ "github.com/mongodb/mongodb-kubernetes/pkg/util/maputil"
5562 "github.com/mongodb/mongodb-kubernetes/pkg/vault"
5663 "github.com/mongodb/mongodb-kubernetes/pkg/vault/vaultwatcher"
5764)
@@ -219,6 +226,8 @@ func (r *ReconcileMongoDbReplicaSet) Reconcile(ctx context.Context, request reco
219226 return r .updateStatus (ctx , rs , workflow .Failed (xerrors .Errorf ("Failed to reconcileHostnameOverrideConfigMap: %w" , err )), log )
220227 }
221228
229+ shouldMirrorKeyfile := r .applySearchOverrides (ctx , rs , log )
230+
222231 sts := construct .DatabaseStatefulSet (* rs , rsConfig , log )
223232 if status := r .ensureRoles (ctx , rs .Spec .DbCommonSpec , r .enableClusterMongoDBRoles , conn , kube .ObjectKeyFromApiObject (rs ), log ); ! status .IsOK () {
224233 return r .updateStatus (ctx , rs , status , log )
@@ -238,7 +247,7 @@ func (r *ReconcileMongoDbReplicaSet) Reconcile(ctx context.Context, request reco
238247 // See CLOUDP-189433 and CLOUDP-229222 for more details.
239248 if recovery .ShouldTriggerRecovery (rs .Status .Phase != mdbstatus .PhaseRunning , rs .Status .LastTransition ) {
240249 log .Warnf ("Triggering Automatic Recovery. The MongoDB resource %s/%s is in %s state since %s" , rs .Namespace , rs .Name , rs .Status .Phase , rs .Status .LastTransition )
241- automationConfigStatus := r .updateOmDeploymentRs (ctx , conn , rs .Status .Members , rs , sts , log , caFilePath , agentCertSecretSelector , prometheusCertHash , true ).OnErrorPrepend ("Failed to create/update (Ops Manager reconciliation phase):" )
250+ automationConfigStatus := r .updateOmDeploymentRs (ctx , conn , rs .Status .Members , rs , sts , log , caFilePath , agentCertSecretSelector , prometheusCertHash , true , shouldMirrorKeyfile ).OnErrorPrepend ("Failed to create/update (Ops Manager reconciliation phase):" )
242251 deploymentError := create .DatabaseInKubernetes (ctx , r .client , * rs , sts , rsConfig , log )
243252 if deploymentError != nil {
244253 log .Errorf ("Recovery failed because of deployment errors, %w" , deploymentError )
@@ -254,7 +263,7 @@ func (r *ReconcileMongoDbReplicaSet) Reconcile(ctx context.Context, request reco
254263 }
255264 status = workflow .RunInGivenOrder (publishAutomationConfigFirst (ctx , r .client , * rs , lastSpec , rsConfig , log ),
256265 func () workflow.Status {
257- return r .updateOmDeploymentRs (ctx , conn , rs .Status .Members , rs , sts , log , caFilePath , agentCertSecretSelector , prometheusCertHash , false ).OnErrorPrepend ("Failed to create/update (Ops Manager reconciliation phase):" )
266+ return r .updateOmDeploymentRs (ctx , conn , rs .Status .Members , rs , sts , log , caFilePath , agentCertSecretSelector , prometheusCertHash , false , shouldMirrorKeyfile ).OnErrorPrepend ("Failed to create/update (Ops Manager reconciliation phase):" )
258267 },
259268 func () workflow.Status {
260269 workflowStatus := create .HandlePVCResize (ctx , r .client , & sts , log )
@@ -408,14 +417,27 @@ func AddReplicaSetController(ctx context.Context, mgr manager.Manager, imageUrls
408417 zap .S ().Errorf ("Failed to watch for vault secret changes: %w" , err )
409418 }
410419 }
420+
421+ err = c .Watch (source .Kind (mgr .GetCache (), & searchv1.MongoDBSearch {},
422+ handler .TypedEnqueueRequestsFromMapFunc (func (ctx context.Context , search * searchv1.MongoDBSearch ) []reconcile.Request {
423+ source := search .GetMongoDBResourceRef ()
424+ if source == nil {
425+ return []reconcile.Request {}
426+ }
427+ return []reconcile.Request {{NamespacedName : types.NamespacedName {Namespace : source .Namespace , Name : source .Name }}}
428+ })))
429+ if err != nil {
430+ return err
431+ }
432+
411433 zap .S ().Infof ("Registered controller %s" , util .MongoDbReplicaSetController )
412434
413435 return nil
414436}
415437
416438// updateOmDeploymentRs performs OM registration operation for the replicaset. So the changes will be finally propagated
417439// to automation agents in containers
418- func (r * ReconcileMongoDbReplicaSet ) updateOmDeploymentRs (ctx context.Context , conn om.Connection , membersNumberBefore int , rs * mdbv1.MongoDB , set appsv1.StatefulSet , log * zap.SugaredLogger , caFilePath string , agentCertSecretSelector corev1.SecretKeySelector , prometheusCertHash string , isRecovering bool ) workflow.Status {
440+ func (r * ReconcileMongoDbReplicaSet ) updateOmDeploymentRs (ctx context.Context , conn om.Connection , membersNumberBefore int , rs * mdbv1.MongoDB , set appsv1.StatefulSet , log * zap.SugaredLogger , caFilePath string , agentCertSecretSelector corev1.SecretKeySelector , prometheusCertHash string , isRecovering bool , shouldMirrorKeyfileForMongot bool ) workflow.Status {
419441 log .Debug ("Entering UpdateOMDeployments" )
420442 // Only "concrete" RS members should be observed
421443 // - if scaling down, let's observe only members that will remain after scale-down operation
@@ -469,6 +491,11 @@ func (r *ReconcileMongoDbReplicaSet) updateOmDeploymentRs(ctx context.Context, c
469491
470492 err = conn .ReadUpdateDeployment (
471493 func (d om.Deployment ) error {
494+ if shouldMirrorKeyfileForMongot {
495+ if err := r .mirrorKeyfileIntoSecretForMongot (ctx , d , rs , log ); err != nil {
496+ return err
497+ }
498+ }
472499 return ReconcileReplicaSetAC (ctx , d , rs .Spec .DbCommonSpec , lastRsConfig .ToMap (), rs .Name , replicaSet , caFilePath , internalClusterPath , & p , log )
473500 },
474501 log ,
@@ -609,3 +636,70 @@ func getAllHostsRs(set appsv1.StatefulSet, clusterName string, membersCount int,
609636 hostnames , _ := dns .GetDnsForStatefulSetReplicasSpecified (set , clusterName , membersCount , externalDomain )
610637 return hostnames
611638}
639+
640+ func (r * ReconcileMongoDbReplicaSet ) applySearchOverrides (ctx context.Context , rs * mdbv1.MongoDB , log * zap.SugaredLogger ) bool {
641+ search := r .lookupCorrespondingSearchResource (ctx , rs , log )
642+ if search == nil {
643+ log .Debugf ("No MongoDBSearch resource found, skipping search overrides" )
644+ return false
645+ }
646+
647+ log .Infof ("Applying search overrides from MongoDBSearch %s" , search .NamespacedName ())
648+
649+ if rs .Spec .AdditionalMongodConfig == nil {
650+ rs .Spec .AdditionalMongodConfig = mdbv1 .NewEmptyAdditionalMongodConfig ()
651+ }
652+ searchMongodConfig := search_controller .GetMongodConfigParameters (search )
653+ rs .Spec .AdditionalMongodConfig .AddOption ("setParameter" , searchMongodConfig ["setParameter" ])
654+
655+ mdbVersion , err := semver .ParseTolerant (rs .Spec .Version )
656+ if err != nil {
657+ log .Warnf ("Failed to parse MongoDB version %q: %w. Proceeding without the automatic creation of the searchCoordinator role that's necessary for MongoDB <8.2" , rs .Spec .Version , err )
658+ } else if semver .MustParse ("8.2.0" ).GT (mdbVersion ) {
659+ log .Infof ("Polyfilling the searchCoordinator role for MongoDB %s" , rs .Spec .Version )
660+
661+ if rs .Spec .Security == nil {
662+ rs .Spec .Security = & mdbv1.Security {}
663+ }
664+ rs .Spec .Security .Roles = append (rs .Spec .Security .Roles , search_controller .SearchCoordinatorRole ())
665+ }
666+
667+ return true
668+ }
669+
670+ func (r * ReconcileMongoDbReplicaSet ) mirrorKeyfileIntoSecretForMongot (ctx context.Context , d om.Deployment , rs * mdbv1.MongoDB , log * zap.SugaredLogger ) error {
671+ keyfileContents := maputil .ReadMapValueAsString (d , "auth" , "key" )
672+ keyfileSecret := & corev1.Secret {ObjectMeta : metav1.ObjectMeta {Name : fmt .Sprintf ("%s-keyfile" , rs .Name ), Namespace : rs .Namespace }}
673+
674+ log .Infof ("Mirroring the replicaset %s's keyfile into the secret %s" , rs .ObjectKey (), kube .ObjectKeyFromApiObject (keyfileSecret ))
675+
676+ _ , err := controllerutil .CreateOrUpdate (ctx , r .client , keyfileSecret , func () error {
677+ keyfileSecret .StringData = map [string ]string {"keyfile" : keyfileContents }
678+ return controllerutil .SetOwnerReference (rs , keyfileSecret , r .client .Scheme ())
679+ })
680+ if err != nil {
681+ return xerrors .Errorf ("Failed to mirror the replicaset's keyfile into a secret: %w" , err )
682+ } else {
683+ return nil
684+ }
685+ }
686+
687+ func (r * ReconcileMongoDbReplicaSet ) lookupCorrespondingSearchResource (ctx context.Context , rs * mdbv1.MongoDB , log * zap.SugaredLogger ) * searchv1.MongoDBSearch {
688+ var search * searchv1.MongoDBSearch
689+ searchList := & searchv1.MongoDBSearchList {}
690+ if err := r .client .List (ctx , searchList , & client.ListOptions {
691+ FieldSelector : fields .OneTermEqualSelector (search_controller .MongoDBSearchIndexFieldName , rs .GetNamespace ()+ "/" + rs .GetName ()),
692+ }); err != nil {
693+ log .Debugf ("Failed to list MongoDBSearch resources: %v" , err )
694+ }
695+ // this validates that there is exactly one MongoDBSearch pointing to this resource,
696+ // and that this resource passes search validations. If either fails, proceed without a search target
697+ // for the mongod automation config.
698+ if len (searchList .Items ) == 1 {
699+ searchSource := search_controller .NewEnterpriseResourceSearchSource (rs )
700+ if searchSource .Validate () == nil {
701+ search = & searchList .Items [0 ]
702+ }
703+ }
704+ return search
705+ }
0 commit comments