Skip to content

Commit 28b7edd

Browse files
committed
Search: support multiple mongots
1 parent 915b024 commit 28b7edd

36 files changed

+3958
-176
lines changed

.evergreen-tasks.yml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1359,3 +1359,13 @@ tasks:
13591359
tags: [ "patch-run" ]
13601360
commands:
13611361
- func: "e2e_test"
1362+
1363+
- name: e2e_search_sharded_enterprise_external_lb
1364+
tags: [ "patch-run" ]
1365+
commands:
1366+
- func: "e2e_test"
1367+
1368+
- name: e2e_search_sharded_enterprise_external_mongod
1369+
tags: [ "patch-run" ]
1370+
commands:
1371+
- func: "e2e_test"

.evergreen.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -811,6 +811,8 @@ task_groups:
811811
- e2e_search_enterprise_basic
812812
- e2e_search_enterprise_tls
813813
- e2e_search_enterprise_x509_cluster_auth
814+
- e2e_search_sharded_enterprise_external_lb
815+
- e2e_search_sharded_enterprise_external_mongod
814816
<<: *teardown_group
815817

816818
# this task group contains just a one task, which is smoke testing whether the operator
@@ -1318,7 +1320,7 @@ buildvariants:
13181320
display_name: e2e_mdb_kind_ubi_cloudqa
13191321
tags: [ "pr_patch", "staging", "e2e_test_suite", "cloudqa", "cloudqa_non_static" ]
13201322
run_on:
1321-
- ubuntu2404-medium
1323+
- ubuntu2404-large
13221324
<<: *base_no_om_image_dependency
13231325
tasks:
13241326
- name: e2e_mdb_kind_cloudqa_task_group
@@ -1336,7 +1338,7 @@ buildvariants:
13361338
display_name: e2e_static_mdb_kind_ubi_cloudqa
13371339
tags: [ "pr_patch", "staging", "e2e_test_suite", "cloudqa", "static" ]
13381340
run_on:
1339-
- ubuntu2404-medium
1341+
- ubuntu2404-large
13401342
<<: *base_no_om_image_dependency
13411343
tasks:
13421344
- name: e2e_mdb_kind_cloudqa_task_group

api/v1/search/mongodbsearch_types.go

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

33
import (
44
"fmt"
5+
"strings"
56

67
"k8s.io/apimachinery/pkg/runtime/schema"
78
"k8s.io/apimachinery/pkg/types"
@@ -16,6 +17,9 @@ import (
1617
"github.com/mongodb/mongodb-kubernetes/mongodb-community-operator/api/v1/common"
1718
)
1819

20+
// ShardNamePlaceholder is the placeholder used in endpoint templates for sharded clusters
21+
const ShardNamePlaceholder = "{shardName}"
22+
1923
const (
2024
MongotDefaultWireprotoPort int32 = 27027
2125
MongotDefaultGrpcPort int32 = 27028
@@ -51,6 +55,15 @@ type MongoDBSearchSpec struct {
5155
// MongoDB database connection details from which MongoDB Search will synchronize data to build indexes.
5256
// +optional
5357
Source *MongoDBSource `json:"source"`
58+
// Replicas is the number of mongot pods to deploy.
59+
// For ReplicaSet source: this many mongot pods total.
60+
// For Sharded source: this many mongot pods per shard.
61+
// When Replicas > 1, a load balancer configuration (lb.mode: Unmanaged with lb.endpoint)
62+
// is required to distribute traffic across mongot instances.
63+
// +optional
64+
// +kubebuilder:validation:Minimum=1
65+
// +kubebuilder:default=1
66+
Replicas int `json:"replicas,omitempty"`
5467
// StatefulSetSpec which the operator will apply to the MongoDB Search StatefulSet at the end of the reconcile loop. Use to provide necessary customizations,
5568
// which aren't exposed as fields in the MongoDBSearch.spec.
5669
// +optional
@@ -75,6 +88,41 @@ type MongoDBSearchSpec struct {
7588
// `embedding` field of mongot config is generated using the values provided here.
7689
// +optional
7790
AutoEmbedding *EmbeddingConfig `json:"autoEmbedding,omitempty"`
91+
// LoadBalancer configures how mongod/mongos connect to mongot (Managed vs Unmanaged/BYO LB).
92+
// +optional
93+
LoadBalancer *LoadBalancerConfig `json:"lb,omitempty"`
94+
}
95+
96+
// LBMode defines the load balancer mode for Search
97+
// +kubebuilder:validation:Enum=Managed;Unmanaged
98+
type LBMode string
99+
100+
const (
101+
// LBModeManaged indicates operator-managed Envoy load balancer
102+
LBModeManaged LBMode = "Managed"
103+
// LBModeUnmanaged indicates user-provided L7 load balancer (BYO LB)
104+
LBModeUnmanaged LBMode = "Unmanaged"
105+
)
106+
107+
// LoadBalancerConfig configures how mongod/mongos connect to mongot
108+
type LoadBalancerConfig struct {
109+
// Mode specifies the load balancer mode: Managed (operator-managed) or Unmanaged (BYO L7 LB)
110+
// +kubebuilder:validation:Required
111+
Mode LBMode `json:"mode"`
112+
// Endpoint is the LB endpoint for ReplicaSet, or a template for sharded clusters.
113+
// For sharded clusters, use {shardName} as a placeholder substituted with the actual shard name.
114+
// Example: "lb-{shardName}.example.com:27028"
115+
// +optional
116+
Endpoint string `json:"endpoint,omitempty"`
117+
// Envoy contains configuration for operator-managed Envoy load balancer
118+
// +optional
119+
Envoy *EnvoyConfig `json:"envoy,omitempty"`
120+
}
121+
122+
// EnvoyConfig contains configuration for operator-managed Envoy load balancer
123+
// Placeholder for future Envoy configuration options
124+
type EnvoyConfig struct {
125+
// Placeholder for future Envoy configuration
78126
}
79127

80128
type EmbeddingConfig struct {
@@ -403,6 +451,59 @@ func (s *MongoDBSearch) GetPrometheus() *Prometheus {
403451
return s.Spec.Prometheus
404452
}
405453

454+
func (s *MongoDBSearch) IsLBModeUnmanaged() bool {
455+
return s.Spec.LoadBalancer != nil && s.Spec.LoadBalancer.Mode == LBModeUnmanaged
456+
}
457+
458+
// IsReplicaSetUnmanagedLB returns true if this is a ReplicaSet unmanaged LB configuration.
459+
// An endpoint with a template placeholder ({shardName}) is NOT considered a ReplicaSet endpoint.
460+
func (s *MongoDBSearch) IsReplicaSetUnmanagedLB() bool {
461+
return s.IsLBModeUnmanaged() &&
462+
s.Spec.LoadBalancer.Endpoint != "" &&
463+
!s.HasEndpointTemplate()
464+
}
465+
466+
func (s *MongoDBSearch) GetReplicaSetUnmanagedLBEndpoint() string {
467+
if !s.IsReplicaSetUnmanagedLB() {
468+
return ""
469+
}
470+
return s.Spec.LoadBalancer.Endpoint
471+
}
472+
473+
// HasEndpointTemplate returns true if the endpoint contains the {shardName} template placeholder
474+
func (s *MongoDBSearch) HasEndpointTemplate() bool {
475+
if s.Spec.LoadBalancer == nil {
476+
return false
477+
}
478+
return strings.Contains(s.Spec.LoadBalancer.Endpoint, ShardNamePlaceholder)
479+
}
480+
481+
// IsShardedUnmanagedLB returns true if this is a sharded unmanaged LB configuration
482+
// identified by the presence of the {shardName} template placeholder in the endpoint.
483+
func (s *MongoDBSearch) IsShardedUnmanagedLB() bool {
484+
return s.IsUnmanagedLBMode() && s.HasEndpointTemplate()
485+
}
486+
487+
// GetEndpointForShard returns the endpoint for a specific shard by substituting
488+
// the {shardName} placeholder in the endpoint template.
489+
func (s *MongoDBSearch) GetEndpointForShard(shardName string) string {
490+
if !s.IsShardedUnmanagedLB() {
491+
return ""
492+
}
493+
return strings.ReplaceAll(s.Spec.LoadBalancer.Endpoint, ShardNamePlaceholder, shardName)
494+
}
495+
496+
func (s *MongoDBSearch) GetReplicas() int {
497+
if s.Spec.Replicas > 0 {
498+
return s.Spec.Replicas
499+
}
500+
return 1
501+
}
502+
503+
func (s *MongoDBSearch) HasMultipleReplicas() bool {
504+
return s.GetReplicas() > 1
505+
}
506+
406507
func (s *MongoDBSearch) ShardMongotStatefulSetName(shardName string) string {
407508
return fmt.Sprintf("%s-mongot-%s", s.Name, shardName)
408509
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
package search
2+
3+
import (
4+
"errors"
5+
"strings"
6+
7+
v1 "github.com/mongodb/mongodb-kubernetes/api/v1"
8+
)
9+
10+
// ValidateSpec validates the MongoDBSearch spec
11+
func (s *MongoDBSearch) ValidateSpec() error {
12+
for _, res := range s.RunValidations() {
13+
if res.Level == v1.ErrorLevel {
14+
return errors.New(res.Msg)
15+
}
16+
}
17+
return nil
18+
}
19+
20+
// RunValidations runs all validation rules and returns the results
21+
func (s *MongoDBSearch) RunValidations() []v1.ValidationResult {
22+
validators := []func(*MongoDBSearch) v1.ValidationResult{
23+
validateLBConfig,
24+
validateUnmanagedLBConfig,
25+
validateEndpointTemplate,
26+
validateTLSConfig,
27+
}
28+
29+
var results []v1.ValidationResult
30+
for _, validator := range validators {
31+
res := validator(s)
32+
if res.Level > 0 {
33+
results = append(results, res)
34+
}
35+
}
36+
return results
37+
}
38+
39+
// validateLBConfig validates the load balancer configuration
40+
func validateLBConfig(s *MongoDBSearch) v1.ValidationResult {
41+
if s.Spec.LoadBalancer == nil {
42+
// LB config is optional
43+
return v1.ValidationSuccess()
44+
}
45+
46+
// Mode must be specified if LB config is present
47+
if s.Spec.LoadBalancer.Mode == "" {
48+
return v1.ValidationError("spec.lb.mode must be specified when spec.lb is configured")
49+
}
50+
51+
// Mode must be either Managed or Unmanaged
52+
if s.Spec.LoadBalancer.Mode != LBModeManaged && s.Spec.LoadBalancer.Mode != LBModeUnmanaged {
53+
return v1.ValidationError("spec.lb.mode must be either 'Managed' or 'Unmanaged', got '%s'", s.Spec.LoadBalancer.Mode)
54+
}
55+
56+
return v1.ValidationSuccess()
57+
}
58+
59+
// validateUnmanagedLBConfig validates that an endpoint is specified when mode is Unmanaged
60+
func validateUnmanagedLBConfig(s *MongoDBSearch) v1.ValidationResult {
61+
if s.Spec.LoadBalancer == nil || s.Spec.LoadBalancer.Mode != LBModeUnmanaged {
62+
return v1.ValidationSuccess()
63+
}
64+
65+
if s.Spec.LoadBalancer.Endpoint == "" {
66+
return v1.ValidationError("spec.lb.endpoint must be specified when spec.lb.mode is 'Unmanaged'")
67+
}
68+
69+
return v1.ValidationSuccess()
70+
}
71+
72+
// validateEndpointTemplate validates the endpoint template format
73+
func validateEndpointTemplate(s *MongoDBSearch) v1.ValidationResult {
74+
if !s.HasEndpointTemplate() {
75+
return v1.ValidationSuccess()
76+
}
77+
78+
endpoint := s.Spec.LoadBalancer.Endpoint
79+
80+
// Template must contain exactly one {shardName} placeholder
81+
count := strings.Count(endpoint, ShardNamePlaceholder)
82+
if count != 1 {
83+
return v1.ValidationError("spec.lb.endpoint template must contain exactly one %s placeholder, found %d", ShardNamePlaceholder, count)
84+
}
85+
86+
// Template should have some content before or after the placeholder
87+
if endpoint == ShardNamePlaceholder {
88+
return v1.ValidationError("spec.lb.endpoint template must contain more than just the %s placeholder", ShardNamePlaceholder)
89+
}
90+
91+
return v1.ValidationSuccess()
92+
}
93+
94+
// validateTLSConfig validates the TLS configuration
95+
func validateTLSConfig(s *MongoDBSearch) v1.ValidationResult {
96+
if s.Spec.Security.TLS == nil {
97+
return v1.ValidationSuccess()
98+
}
99+
100+
// TLS is valid in all cases:
101+
// 1. CertificateKeySecret.Name is specified - use explicit secret name
102+
// 2. CertsSecretPrefix is specified - use {prefix}-{resourceName}-search-cert
103+
// 3. Both are empty - use default {resourceName}-search-cert
104+
// No validation error needed as we always have a valid fallback
105+
106+
return v1.ValidationSuccess()
107+
}

api/v1/search/zz_generated.deepcopy.go

Lines changed: 40 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: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,30 @@ spec:
7878
required:
7979
- embeddingModelAPIKeySecret
8080
type: object
81+
lb:
82+
description: LoadBalancer configures how mongod/mongos connect to
83+
mongot (Managed vs Unmanaged/BYO LB).
84+
properties:
85+
endpoint:
86+
description: |-
87+
Endpoint is the LB endpoint for ReplicaSet, or a template for sharded clusters.
88+
For sharded clusters, use {shardName} as a placeholder substituted with the actual shard name.
89+
Example: "lb-{shardName}.example.com:27028"
90+
type: string
91+
envoy:
92+
description: Envoy contains configuration for operator-managed
93+
Envoy load balancer
94+
type: object
95+
mode:
96+
description: 'Mode specifies the load balancer mode: Managed (operator-managed)
97+
or Unmanaged (BYO L7 LB)'
98+
enum:
99+
- Managed
100+
- Unmanaged
101+
type: string
102+
required:
103+
- mode
104+
type: object
81105
logLevel:
82106
description: Configure verbosity of mongot logs. Defaults to INFO
83107
if not set.
@@ -148,6 +172,16 @@ spec:
148172
minimum: 0
149173
type: integer
150174
type: object
175+
replicas:
176+
default: 1
177+
description: |-
178+
Replicas is the number of mongot pods to deploy.
179+
For ReplicaSet source: this many mongot pods total.
180+
For Sharded source: this many mongot pods per shard.
181+
When Replicas > 1, a load balancer configuration (lb.mode: Unmanaged with lb.endpoint)
182+
is required to distribute traffic across mongot instances.
183+
minimum: 1
184+
type: integer
151185
resourceRequirements:
152186
description: Configure resource requests and limits for the MongoDB
153187
Search pods.

0 commit comments

Comments
 (0)