Skip to content

Commit 446c8b3

Browse files
authored
CLOUDP-338399: External mongod for Search (#308)
# Summary This pull request introduces support for using external MongoDB deployments as sources for `MongoDBSearch` resources, in addition to the previously supported operator-managed deployments. The changes include updates to the CRD API, controller logic, and supporting code to handle configuration, validation, and reconciliation for external sources. Additionally, new end-to-end (e2e) test tasks are added to ensure this functionality is covered. **External MongoDB Source Support** * Added new fields to the `MongoDBSource` spec (`ExternalMongoDBSource` and supporting types) to allow specifying external MongoDB deployments, including host/port, credentials, and TLS settings. * Added logic to distinguish between operator-managed and external sources throughout the controller and resource handling code, including the `IsExternalMongoDBSource` method and updates to resource reference logic. **Controller and Reconciliation Logic Updates** * Refactored the `SearchSourceDBResource` interface and its implementations to support both internal and external MongoDB sources, including connection details and version validation. * Updated reconciliation logic to handle external sources, including skipping version validation and resource watching when not applicable. * Adjusted service and StatefulSet construction to use new abstraction for host seeds and service accounts. **Testing and CI** * Added new e2e test tasks for external MongoDB source scenarios (`e2e_search_external_basic` and `e2e_search_external_tls`) and included them in the appropriate task groups to ensure coverage in CI. * Updated tests and validation helpers to use the new interface methods and logic. These changes collectively enable `MongoDBSearch` resources to work with both operator-managed and external MongoDB deployments, increasing flexibility for users and improving the robustness of the operator. ## Proof of Work Tests pass
1 parent 093dc57 commit 446c8b3

15 files changed

+777
-46
lines changed

.evergreen-tasks.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1290,3 +1290,13 @@ tasks:
12901290
tags: ["patch-run"]
12911291
commands:
12921292
- func: "e2e_test"
1293+
1294+
- name: e2e_search_external_basic
1295+
tags: [ "patch-run" ]
1296+
commands:
1297+
- func: "e2e_test"
1298+
1299+
- name: e2e_search_external_tls
1300+
tags: [ "patch-run" ]
1301+
commands:
1302+
- func: "e2e_test"

.evergreen.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,8 @@ task_groups:
646646
- e2e_community_replicaset_scale
647647
- e2e_search_community_basic
648648
- e2e_search_community_tls
649+
- e2e_search_external_basic
650+
- e2e_search_external_tls
649651

650652
# This is the task group that contains all the tests run in the e2e_mdb_kind_ubuntu_cloudqa build variant
651653
- name: e2e_mdb_kind_cloudqa_task_group

api/v1/search/mongodbsearch_types.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,26 @@ type MongoDBSource struct {
5252
// +optional
5353
MongoDBResourceRef *userv1.MongoDBResourceRef `json:"mongodbResourceRef,omitempty"`
5454
// +optional
55+
ExternalMongoDBSource *ExternalMongoDBSource `json:"external,omitempty"`
56+
// +optional
5557
PasswordSecretRef *userv1.SecretKeyRef `json:"passwordSecretRef,omitempty"`
5658
// +optional
5759
Username *string `json:"username,omitempty"`
5860
}
5961

62+
type ExternalMongoDBSource struct {
63+
HostAndPorts []string `json:"hostAndPorts,omitempty"`
64+
KeyFileSecretKeyRef *userv1.SecretKeyRef `json:"keyFileSecretRef,omitempty"` // This is the mongod credential used to connect to the external MongoDB deployment
65+
// +optional
66+
TLS *ExternalMongodTLS `json:"tls,omitempty"` // TLS configuration for the external MongoDB deployment
67+
}
68+
69+
type ExternalMongodTLS struct {
70+
Enabled bool `json:"enabled"`
71+
// +optional
72+
CA *corev1.LocalObjectReference `json:"ca,omitempty"`
73+
}
74+
6075
type Security struct {
6176
// +optional
6277
TLS TLS `json:"tls"`
@@ -170,13 +185,17 @@ func (s *MongoDBSearch) GetOwnerReferences() []metav1.OwnerReference {
170185
return []metav1.OwnerReference{ownerReference}
171186
}
172187

173-
func (s *MongoDBSearch) GetMongoDBResourceRef() userv1.MongoDBResourceRef {
188+
func (s *MongoDBSearch) GetMongoDBResourceRef() *userv1.MongoDBResourceRef {
189+
if s.IsExternalMongoDBSource() {
190+
return nil
191+
}
192+
174193
mdbResourceRef := userv1.MongoDBResourceRef{Namespace: s.Namespace, Name: s.Name}
175194
if s.Spec.Source != nil && s.Spec.Source.MongoDBResourceRef != nil && s.Spec.Source.MongoDBResourceRef.Name != "" {
176195
mdbResourceRef.Name = s.Spec.Source.MongoDBResourceRef.Name
177196
}
178197

179-
return mdbResourceRef
198+
return &mdbResourceRef
180199
}
181200

182201
func (s *MongoDBSearch) GetMongotPort() int32 {
@@ -201,3 +220,7 @@ func (s *MongoDBSearch) TLSOperatorSecretNamespacedName() types.NamespacedName {
201220
func (s *MongoDBSearch) GetMongotHealthCheckPort() int32 {
202221
return MongotDefautHealthCheckPort
203222
}
223+
224+
func (s *MongoDBSearch) IsExternalMongoDBSource() bool {
225+
return s.Spec.Source != nil && s.Spec.Source.ExternalMongoDBSource != nil
226+
}

api/v1/search/zz_generated.deepcopy.go

Lines changed: 55 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

config/crd/bases/mongodb.com_mongodbsearch.yaml

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,51 @@ spec:
182182
type: object
183183
source:
184184
properties:
185+
external:
186+
properties:
187+
hostAndPorts:
188+
items:
189+
type: string
190+
type: array
191+
keyFileSecretRef:
192+
description: |-
193+
SecretKeyRef is a reference to a value in a given secret in the same
194+
namespace. Based on:
195+
https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.15/#secretkeyselector-v1-core
196+
properties:
197+
key:
198+
type: string
199+
name:
200+
type: string
201+
required:
202+
- name
203+
type: object
204+
tls:
205+
properties:
206+
ca:
207+
description: |-
208+
LocalObjectReference contains enough information to let you locate the
209+
referenced object inside the same namespace.
210+
properties:
211+
name:
212+
default: ""
213+
description: |-
214+
Name of the referent.
215+
This field is effectively required, but due to backwards compatibility is
216+
allowed to be empty. Instances of this type with an empty value here are
217+
almost certainly wrong.
218+
TODO: Add other useful fields. apiVersion, kind, uid?
219+
More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names
220+
TODO: Drop `kubebuilder:default` when controller-gen doesn't need it https://github.com/kubernetes-sigs/kubebuilder/issues/3896.
221+
type: string
222+
type: object
223+
x-kubernetes-map-type: atomic
224+
enabled:
225+
type: boolean
226+
required:
227+
- enabled
228+
type: object
229+
type: object
185230
mongodbResourceRef:
186231
properties:
187232
name:
@@ -209,8 +254,8 @@ spec:
209254
type: object
210255
statefulSet:
211256
description: |-
212-
StatefulSetConfiguration holds the optional custom StatefulSet
213-
that should be merged into the operator created one.
257+
StatefulSetSpec which the operator will apply to the MongoDB Search StatefulSet at the end of the reconcile loop. Use to provide necessary customizations,
258+
which aren't exposed as fields in the MongoDBSearch.spec.
214259
properties:
215260
metadata:
216261
description: StatefulSetMetadataWrapper is a wrapper around Labels

controllers/operator/mongodbsearch_controller.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,31 +51,46 @@ func (r *MongoDBSearchReconciler) Reconcile(ctx context.Context, request reconci
5151
return result, err
5252
}
5353

54-
sourceResource, err := getSourceMongoDBForSearch(ctx, r.kubeClient, mdbSearch)
54+
sourceResource, mdbc, err := getSourceMongoDBForSearch(ctx, r.kubeClient, mdbSearch)
5555
if err != nil {
5656
return reconcile.Result{RequeueAfter: time.Second * util.RetryTimeSec}, err
5757
}
5858

59-
r.mdbcWatcher.Watch(ctx, sourceResource.NamespacedName(), request.NamespacedName)
59+
if mdbc != nil {
60+
r.mdbcWatcher.Watch(ctx, mdbc.NamespacedName(), request.NamespacedName)
61+
}
6062

6163
reconcileHelper := search_controller.NewMongoDBSearchReconcileHelper(kubernetesClient.NewClient(r.kubeClient), mdbSearch, sourceResource, r.operatorSearchConfig)
6264

6365
return reconcileHelper.Reconcile(ctx, log).ReconcileResult()
6466
}
6567

66-
func getSourceMongoDBForSearch(ctx context.Context, kubeClient client.Client, search *searchv1.MongoDBSearch) (search_controller.SearchSourceDBResource, error) {
68+
func getSourceMongoDBForSearch(ctx context.Context, kubeClient client.Client, search *searchv1.MongoDBSearch) (search_controller.SearchSourceDBResource, *mdbcv1.MongoDBCommunity, error) {
69+
if search.IsExternalMongoDBSource() {
70+
return search_controller.NewSearchSourceDBResourceFromExternal(search.Namespace, search.Spec.Source.ExternalMongoDBSource), nil, nil
71+
}
72+
6773
sourceMongoDBResourceRef := search.GetMongoDBResourceRef()
74+
if sourceMongoDBResourceRef == nil {
75+
return nil, nil, xerrors.New("MongoDBSearch source MongoDB resource reference is not set")
76+
}
77+
6878
mdbcName := types.NamespacedName{Namespace: search.GetNamespace(), Name: sourceMongoDBResourceRef.Name}
6979
mdbc := &mdbcv1.MongoDBCommunity{}
7080
if err := kubeClient.Get(ctx, mdbcName, mdbc); err != nil {
71-
return nil, xerrors.Errorf("error getting MongoDBCommunity %s: %w", mdbcName, err)
81+
return nil, nil, xerrors.Errorf("error getting MongoDBCommunity %s: %w", mdbcName, err)
7282
}
73-
return search_controller.NewSearchSourceDBResourceFromMongoDBCommunity(mdbc), nil
83+
return search_controller.NewSearchSourceDBResourceFromMongoDBCommunity(mdbc), mdbc, nil
7484
}
7585

7686
func mdbcSearchIndexBuilder(rawObj client.Object) []string {
7787
mdbSearch := rawObj.(*searchv1.MongoDBSearch)
78-
return []string{mdbSearch.GetMongoDBResourceRef().Namespace + "/" + mdbSearch.GetMongoDBResourceRef().Name}
88+
resourceRef := mdbSearch.GetMongoDBResourceRef()
89+
if resourceRef == nil {
90+
return []string{}
91+
}
92+
93+
return []string{resourceRef.Namespace + "/" + resourceRef.Name}
7994
}
8095

8196
func AddMongoDBSearchController(ctx context.Context, mgr manager.Manager, operatorSearchConfig search_controller.OperatorSearchConfig) error {

controllers/search_controller/mongodbsearch_reconcile_helper.go

Lines changed: 10 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ import (
77
"fmt"
88
"strings"
99

10-
"github.com/blang/semver"
1110
"github.com/ghodss/yaml"
1211
"go.uber.org/zap"
1312
"golang.org/x/xerrors"
@@ -82,7 +81,7 @@ func (r *MongoDBSearchReconcileHelper) reconcile(ctx context.Context, log *zap.S
8281
log = log.With("MongoDBSearch", r.mdbSearch.NamespacedName())
8382
log.Infof("Reconciling MongoDBSearch")
8483

85-
if err := ValidateSearchSource(r.db); err != nil {
84+
if err := r.db.ValidateMongoDBVersion(); err != nil {
8685
return workflow.Failed(err)
8786
}
8887

@@ -123,7 +122,7 @@ func (r *MongoDBSearchReconcileHelper) reconcile(ctx context.Context, log *zap.S
123122
return workflow.Failed(err)
124123
}
125124

126-
if statefulSetStatus := statefulset.GetStatefulSetStatus(ctx, r.db.NamespacedName().Namespace, r.mdbSearch.StatefulSetNamespacedName().Name, r.client); !statefulSetStatus.IsOK() {
125+
if statefulSetStatus := statefulset.GetStatefulSetStatus(ctx, r.mdbSearch.Namespace, r.mdbSearch.StatefulSetNamespacedName().Name, r.client); !statefulSetStatus.IsOK() {
127126
return statefulSetStatus
128127
}
129128

@@ -334,10 +333,7 @@ func buildSearchHeadlessService(search *searchv1.MongoDBSearch) corev1.Service {
334333

335334
func createMongotConfig(search *searchv1.MongoDBSearch, db SearchSourceDBResource) mongot.Modification {
336335
return func(config *mongot.Config) {
337-
var hostAndPorts []string
338-
for i := range db.Members() {
339-
hostAndPorts = append(hostAndPorts, fmt.Sprintf("%s-%d.%s.%s.svc.cluster.local:%d", db.Name(), i, db.DatabaseServiceName(), db.GetNamespace(), db.DatabasePort()))
340-
}
336+
hostAndPorts := db.HostSeeds()
341337

342338
config.SyncSource = mongot.ConfigSyncSource{
343339
ReplicaSet: mongot.ConfigReplicaSet{
@@ -395,23 +391,17 @@ func mongotHostAndPort(search *searchv1.MongoDBSearch) string {
395391
return fmt.Sprintf("%s.%s.svc.cluster.local:%d", svcName.Name, svcName.Namespace, search.GetMongotPort())
396392
}
397393

398-
func ValidateSearchSource(db SearchSourceDBResource) error {
399-
version, err := semver.ParseTolerant(db.GetMongoDBVersion())
400-
if err != nil {
401-
return xerrors.Errorf("error parsing MongoDB version '%s': %w", db.GetMongoDBVersion(), err)
402-
} else if version.LT(semver.MustParse("8.0.10")) {
403-
return xerrors.New("MongoDB version must be 8.0.10 or higher")
394+
func (r *MongoDBSearchReconcileHelper) ValidateSingleMongoDBSearchForSearchSource(ctx context.Context) error {
395+
if r.mdbSearch.Spec.Source != nil && r.mdbSearch.Spec.Source.ExternalMongoDBSource != nil {
396+
return nil
404397
}
405398

406-
return nil
407-
}
408-
409-
func (r *MongoDBSearchReconcileHelper) ValidateSingleMongoDBSearchForSearchSource(ctx context.Context) error {
399+
ref := r.mdbSearch.GetMongoDBResourceRef()
410400
searchList := &searchv1.MongoDBSearchList{}
411401
if err := r.client.List(ctx, searchList, &client.ListOptions{
412-
FieldSelector: fields.OneTermEqualSelector(MongoDBSearchIndexFieldName, r.db.GetNamespace()+"/"+r.db.Name()),
402+
FieldSelector: fields.OneTermEqualSelector(MongoDBSearchIndexFieldName, ref.Namespace+"/"+ref.Name),
413403
}); err != nil {
414-
return xerrors.Errorf("Error listing MongoDBSearch resources for search source '%s': %w", r.db.Name(), err)
404+
return xerrors.Errorf("Error listing MongoDBSearch resources for search source '%s': %w", ref.Name, err)
415405
}
416406

417407
if len(searchList.Items) > 1 {
@@ -420,7 +410,7 @@ func (r *MongoDBSearchReconcileHelper) ValidateSingleMongoDBSearchForSearchSourc
420410
resourceNames[i] = search.Name
421411
}
422412
return xerrors.Errorf(
423-
"Found multiple MongoDBSearch resources for search source '%s': %s", r.db.Name(),
413+
"Found multiple MongoDBSearch resources for search source '%s': %s", ref.Name,
424414
strings.Join(resourceNames, ", "),
425415
)
426416
}

controllers/search_controller/mongodbsearch_reconcile_helper_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func TestMongoDBSearchReconcileHelper_ValidateSearchSource(t *testing.T) {
6464
for _, c := range cases {
6565
t.Run(c.name, func(t *testing.T) {
6666
db := NewSearchSourceDBResourceFromMongoDBCommunity(&c.mdbc)
67-
err := ValidateSearchSource(db)
67+
err := db.ValidateMongoDBVersion()
6868
if c.expectedError == "" {
6969
assert.NoError(t, err)
7070
} else {

0 commit comments

Comments
 (0)