-
Notifications
You must be signed in to change notification settings - Fork 25
CLOUDP-342325 Basic enterprise search support #309
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
cbd8b1f
ef4880a
bd72b91
799a8dc
22b0670
f783f5d
aca9ee3
30b5a53
28f8911
fbfc18f
1d5ee48
30e9048
734e3b1
ef50c01
46f721b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,12 +4,16 @@ import ( | |
| "context" | ||
| "fmt" | ||
|
|
||
| "github.com/blang/semver" | ||
| "go.uber.org/zap" | ||
| "golang.org/x/xerrors" | ||
| "k8s.io/apimachinery/pkg/api/errors" | ||
| "k8s.io/apimachinery/pkg/fields" | ||
| "k8s.io/apimachinery/pkg/runtime" | ||
| "k8s.io/apimachinery/pkg/types" | ||
| "sigs.k8s.io/controller-runtime/pkg/client" | ||
| "sigs.k8s.io/controller-runtime/pkg/controller" | ||
| "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" | ||
| "sigs.k8s.io/controller-runtime/pkg/event" | ||
| "sigs.k8s.io/controller-runtime/pkg/handler" | ||
| "sigs.k8s.io/controller-runtime/pkg/manager" | ||
|
|
@@ -22,6 +26,7 @@ import ( | |
|
|
||
| mdbv1 "github.com/mongodb/mongodb-kubernetes/api/v1/mdb" | ||
| rolev1 "github.com/mongodb/mongodb-kubernetes/api/v1/role" | ||
| searchv1 "github.com/mongodb/mongodb-kubernetes/api/v1/search" | ||
| mdbstatus "github.com/mongodb/mongodb-kubernetes/api/v1/status" | ||
| "github.com/mongodb/mongodb-kubernetes/controllers/om" | ||
| "github.com/mongodb/mongodb-kubernetes/controllers/om/backup" | ||
|
|
@@ -39,6 +44,7 @@ import ( | |
| "github.com/mongodb/mongodb-kubernetes/controllers/operator/recovery" | ||
| "github.com/mongodb/mongodb-kubernetes/controllers/operator/watch" | ||
| "github.com/mongodb/mongodb-kubernetes/controllers/operator/workflow" | ||
| "github.com/mongodb/mongodb-kubernetes/controllers/search_controller" | ||
| mcoConstruct "github.com/mongodb/mongodb-kubernetes/mongodb-community-operator/controllers/construct" | ||
| "github.com/mongodb/mongodb-kubernetes/mongodb-community-operator/pkg/kube/annotations" | ||
| "github.com/mongodb/mongodb-kubernetes/mongodb-community-operator/pkg/kube/configmap" | ||
|
|
@@ -52,6 +58,7 @@ import ( | |
| "github.com/mongodb/mongodb-kubernetes/pkg/util/architectures" | ||
| "github.com/mongodb/mongodb-kubernetes/pkg/util/env" | ||
| util_int "github.com/mongodb/mongodb-kubernetes/pkg/util/int" | ||
| "github.com/mongodb/mongodb-kubernetes/pkg/util/maputil" | ||
| "github.com/mongodb/mongodb-kubernetes/pkg/vault" | ||
| "github.com/mongodb/mongodb-kubernetes/pkg/vault/vaultwatcher" | ||
| ) | ||
|
|
@@ -219,6 +226,8 @@ func (r *ReconcileMongoDbReplicaSet) Reconcile(ctx context.Context, request reco | |
| return r.updateStatus(ctx, rs, workflow.Failed(xerrors.Errorf("Failed to reconcileHostnameOverrideConfigMap: %w", err)), log) | ||
| } | ||
|
|
||
| shouldMirrorKeyfile := r.applySearchOverrides(ctx, rs, log) | ||
|
|
||
| sts := construct.DatabaseStatefulSet(*rs, rsConfig, log) | ||
| if status := r.ensureRoles(ctx, rs.Spec.DbCommonSpec, r.enableClusterMongoDBRoles, conn, kube.ObjectKeyFromApiObject(rs), log); !status.IsOK() { | ||
| return r.updateStatus(ctx, rs, status, log) | ||
|
|
@@ -238,7 +247,7 @@ func (r *ReconcileMongoDbReplicaSet) Reconcile(ctx context.Context, request reco | |
| // See CLOUDP-189433 and CLOUDP-229222 for more details. | ||
| if recovery.ShouldTriggerRecovery(rs.Status.Phase != mdbstatus.PhaseRunning, rs.Status.LastTransition) { | ||
| 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) | ||
| automationConfigStatus := r.updateOmDeploymentRs(ctx, conn, rs.Status.Members, rs, sts, log, caFilePath, agentCertSecretSelector, prometheusCertHash, true).OnErrorPrepend("Failed to create/update (Ops Manager reconciliation phase):") | ||
| 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):") | ||
| deploymentError := create.DatabaseInKubernetes(ctx, r.client, *rs, sts, rsConfig, log) | ||
| if deploymentError != nil { | ||
| log.Errorf("Recovery failed because of deployment errors, %w", deploymentError) | ||
|
|
@@ -254,7 +263,7 @@ func (r *ReconcileMongoDbReplicaSet) Reconcile(ctx context.Context, request reco | |
| } | ||
| status = workflow.RunInGivenOrder(publishAutomationConfigFirst(ctx, r.client, *rs, lastSpec, rsConfig, log), | ||
| func() workflow.Status { | ||
| return r.updateOmDeploymentRs(ctx, conn, rs.Status.Members, rs, sts, log, caFilePath, agentCertSecretSelector, prometheusCertHash, false).OnErrorPrepend("Failed to create/update (Ops Manager reconciliation phase):") | ||
| 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):") | ||
| }, | ||
| func() workflow.Status { | ||
| workflowStatus := create.HandlePVCResize(ctx, r.client, &sts, log) | ||
|
|
@@ -408,14 +417,27 @@ func AddReplicaSetController(ctx context.Context, mgr manager.Manager, imageUrls | |
| zap.S().Errorf("Failed to watch for vault secret changes: %w", err) | ||
| } | ||
| } | ||
|
|
||
| err = c.Watch(source.Kind(mgr.GetCache(), &searchv1.MongoDBSearch{}, | ||
| handler.TypedEnqueueRequestsFromMapFunc(func(ctx context.Context, search *searchv1.MongoDBSearch) []reconcile.Request { | ||
| source := search.GetMongoDBResourceRef() | ||
| if source == nil { | ||
| return []reconcile.Request{} | ||
| } | ||
| return []reconcile.Request{{NamespacedName: types.NamespacedName{Namespace: source.Namespace, Name: source.Name}}} | ||
| }))) | ||
| if err != nil { | ||
| return err | ||
| } | ||
|
|
||
| zap.S().Infof("Registered controller %s", util.MongoDbReplicaSetController) | ||
|
|
||
| return nil | ||
| } | ||
|
|
||
| // updateOmDeploymentRs performs OM registration operation for the replicaset. So the changes will be finally propagated | ||
| // to automation agents in containers | ||
| 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 { | ||
| 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 { | ||
| log.Debug("Entering UpdateOMDeployments") | ||
| // Only "concrete" RS members should be observed | ||
| // - 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 | |
|
|
||
| err = conn.ReadUpdateDeployment( | ||
| func(d om.Deployment) error { | ||
| if shouldMirrorKeyfileForMongot { | ||
| if err := r.mirrorKeyfileIntoSecretForMongot(ctx, d, rs, log); err != nil { | ||
| return err | ||
| } | ||
| } | ||
| return ReconcileReplicaSetAC(ctx, d, rs.Spec.DbCommonSpec, lastRsConfig.ToMap(), rs.Name, replicaSet, caFilePath, internalClusterPath, &p, log) | ||
| }, | ||
| log, | ||
|
|
@@ -609,3 +636,70 @@ func getAllHostsRs(set appsv1.StatefulSet, clusterName string, membersCount int, | |
| hostnames, _ := dns.GetDnsForStatefulSetReplicasSpecified(set, clusterName, membersCount, externalDomain) | ||
| return hostnames | ||
| } | ||
|
|
||
| func (r *ReconcileMongoDbReplicaSet) applySearchOverrides(ctx context.Context, rs *mdbv1.MongoDB, log *zap.SugaredLogger) bool { | ||
| search := r.lookupCorrespondingSearchResource(ctx, rs, log) | ||
| if search == nil { | ||
| log.Debugf("No MongoDBSearch resource found, skipping search overrides") | ||
| return false | ||
| } | ||
|
|
||
| log.Infof("Applying search overrides from MongoDBSearch %s", search.NamespacedName()) | ||
|
|
||
| if rs.Spec.AdditionalMongodConfig == nil { | ||
| rs.Spec.AdditionalMongodConfig = mdbv1.NewEmptyAdditionalMongodConfig() | ||
| } | ||
| searchMongodConfig := search_controller.GetMongodConfigParameters(search) | ||
| rs.Spec.AdditionalMongodConfig.AddOption("setParameter", searchMongodConfig["setParameter"]) | ||
|
|
||
| mdbVersion, err := semver.ParseTolerant(rs.Spec.Version) | ||
| if err != nil { | ||
| 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) | ||
| } else if semver.MustParse("8.2.0").GT(mdbVersion) { | ||
| log.Infof("Polyfilling the searchCoordinator role for MongoDB %s", rs.Spec.Version) | ||
|
|
||
| if rs.Spec.Security == nil { | ||
| rs.Spec.Security = &mdbv1.Security{} | ||
| } | ||
| rs.Spec.Security.Roles = append(rs.Spec.Security.Roles, search_controller.SearchCoordinatorRole()) | ||
| } | ||
|
|
||
| return true | ||
| } | ||
|
|
||
| func (r *ReconcileMongoDbReplicaSet) mirrorKeyfileIntoSecretForMongot(ctx context.Context, d om.Deployment, rs *mdbv1.MongoDB, log *zap.SugaredLogger) error { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if we could make it part of the search reconciler. It feels a bit awkward to have this special code for making the search reconciler work properly. I feel we should limit changes only to the setParameters. We know the secret name and we can look and mirror it when we need it while reconciling MongoDBSearch resource.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. After reviewing the whole PR I think it's not even necessary as we already have mirroring on the search side in controllers/search_controller/mongodbsearch_reconcile_helper.go:142
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to read the keyfile contents from Ops Manager. Unfortunately in Enterprise the only copy of the keyfile is in the automation config in Ops Manager, unlike in Community where the Operator generates the keyfile and stores it in a secret we can just mount in mongot. I considered mirroring the keyfile into a secret inside the search reconciler, but then we'd need to establish the Ops Manager connection there, so I felt the replica set reconciler would be a smaller pain. FWIW we don't mirror the keyfile on the search side - the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I believe this behavior should be consistent with community in this regard, i.e. we should never store any secret in AC without having it stored as a secret as well. I've asked this on slack and we have other instances of such behavior. Now the question is:
|
||
| keyfileContents := maputil.ReadMapValueAsString(d, "auth", "key") | ||
| keyfileSecret := &corev1.Secret{ObjectMeta: metav1.ObjectMeta{Name: fmt.Sprintf("%s-keyfile", rs.Name), Namespace: rs.Namespace}} | ||
|
|
||
| log.Infof("Mirroring the replicaset %s's keyfile into the secret %s", rs.ObjectKey(), kube.ObjectKeyFromApiObject(keyfileSecret)) | ||
|
|
||
| _, err := controllerutil.CreateOrUpdate(ctx, r.client, keyfileSecret, func() error { | ||
| keyfileSecret.StringData = map[string]string{"keyfile": keyfileContents} | ||
| return controllerutil.SetOwnerReference(rs, keyfileSecret, r.client.Scheme()) | ||
| }) | ||
| if err != nil { | ||
| return xerrors.Errorf("Failed to mirror the replicaset's keyfile into a secret: %w", err) | ||
| } else { | ||
| return nil | ||
| } | ||
| } | ||
|
|
||
| func (r *ReconcileMongoDbReplicaSet) lookupCorrespondingSearchResource(ctx context.Context, rs *mdbv1.MongoDB, log *zap.SugaredLogger) *searchv1.MongoDBSearch { | ||
| var search *searchv1.MongoDBSearch | ||
| searchList := &searchv1.MongoDBSearchList{} | ||
| if err := r.client.List(ctx, searchList, &client.ListOptions{ | ||
| FieldSelector: fields.OneTermEqualSelector(search_controller.MongoDBSearchIndexFieldName, rs.GetNamespace()+"/"+rs.GetName()), | ||
lsierant marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| }); err != nil { | ||
| log.Debugf("Failed to list MongoDBSearch resources: %v", err) | ||
| } | ||
| // this validates that there is exactly one MongoDBSearch pointing to this resource, | ||
| // and that this resource passes search validations. If either fails, proceed without a search target | ||
| // for the mongod automation config. | ||
| if len(searchList.Items) == 1 { | ||
| searchSource := search_controller.NewEnterpriseResourceSearchSource(rs) | ||
| if searchSource.Validate() == nil { | ||
| search = &searchList.Items[0] | ||
| } | ||
| } | ||
| return search | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.