Skip to content

Commit f983783

Browse files
authored
CLOUDP-119891: Change port of running cluster (#995)
* CLOUDP-119891: Change port of running cluster This patchset improves reconfiguration of MongoDB's ports in the replica set. MongoDB Agent does not support changing port of more than one process at a time in a replica set. Changes: * Introduced ReplicaSetPortManager to determine processes' ports in Service and Automation Config. * Fixed handling nested map format (e.g. net.port) when unmarshalling MongodConfiguration * Improved handling ports both as ints and float64 in MongodConfiguration and AutomationConfig * Set explicit linux/amd64 platform when building agent-image, which fixes local builds on M1 macs. Port change process: * When the port (newPort) is customised, but there aren't any pods yet, then all processes are configured with the newPort in automation config. This works as before. * When the cluster is running and the port is changed, each process need to be configured one by one: * ReplicaSetPortManager changes port in automation config only when all pods reached its goal states. * Until all processes are configured with newPort the service is configured with two ports: oldPort and newPort. * When all pods are changed one by one, the service is reconfigured to have only newPort. closes #945
1 parent 5a0afe2 commit f983783

24 files changed

+983
-59
lines changed

.action_templates/jobs/tests.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,5 @@ tests:
5454
distro: ubi
5555
- test-name: replica_set_mount_connection_string
5656
distro: ubi
57+
- test-name: replica_set_mongod_port_change_with_arbiters
58+
distro: ubi

.github/workflows/e2e-fork.yml

+2
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ jobs:
136136
distro: ubi
137137
- test-name: replica_set_mount_connection_string
138138
distro: ubi
139+
- test-name: replica_set_mongod_port_change_with_arbiters
140+
distro: ubi
139141
steps:
140142
# template: .action_templates/steps/cancel-previous.yaml
141143
- name: Cancel Previous Runs

.github/workflows/e2e.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,7 @@ jobs:
142142
distro: ubi
143143
- test-name: replica_set_mount_connection_string
144144
distro: ubi
145-
- test-name: replica_set_operator_upgrade
145+
- test-name: replica_set_mongod_port_change_with_arbiters
146146
distro: ubi
147147
steps:
148148
# template: .action_templates/steps/cancel-previous.yaml

api/v1/mongodbcommunity_types.go

+36-3
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,20 @@ func (m *MongodConfiguration) UnmarshalJSON(data []byte) error {
311311
m.Object = map[string]interface{}{}
312312
}
313313

314-
return json.Unmarshal(data, &m.Object)
314+
// Handle keys like net.port to be set as nested maps.
315+
// Without this after unmarshalling there is just key "net.port" which is not
316+
// a nested map and methods like GetPort() cannot access the value.
317+
tmpMap := map[string]interface{}{}
318+
err := json.Unmarshal(data, &tmpMap)
319+
if err != nil {
320+
return err
321+
}
322+
323+
for k, v := range tmpMap {
324+
m.SetOption(k, v)
325+
}
326+
327+
return nil
315328
}
316329

317330
func (m *MongodConfiguration) DeepCopy() *MongodConfiguration {
@@ -339,8 +352,24 @@ func (m MongodConfiguration) GetDBDataDir() string {
339352
// GetDBPort returns the port that should be used for the mongod process.
340353
// If port is not specified, the default port of 27017 will be used.
341354
func (m MongodConfiguration) GetDBPort() int {
342-
// When passed as a number "net.port" is unmarshalled into a float64
343-
return int(objx.New(m.Object).Get("net.port").Float64(float64(automationconfig.DefaultDBPort)))
355+
portValue := objx.New(m.Object).Get("net.port")
356+
357+
// Underlying map could be manipulated in code, e.g. via SetDBPort (e.g. in unit tests) - then it will be as int,
358+
// or it could be deserialized from JSON and then integer in an untyped map will be deserialized as float64.
359+
// It's behavior of https://pkg.go.dev/encoding/json#Unmarshal that is converting JSON integers as float64.
360+
if portValue.IsInt() {
361+
return portValue.Int(automationconfig.DefaultDBPort)
362+
} else if portValue.IsFloat64() {
363+
return int(portValue.Float64(float64(automationconfig.DefaultDBPort)))
364+
}
365+
366+
return automationconfig.DefaultDBPort
367+
}
368+
369+
// SetDBPort ensures that port is stored as float64
370+
func (m MongodConfiguration) SetDBPort(port int) MongodConfiguration {
371+
m.SetOption("net.port", float64(port))
372+
return m
344373
}
345374

346375
type MongoDBUser struct {
@@ -747,6 +776,10 @@ func (m MongoDBCommunity) ServiceName() string {
747776
return m.Name + "-svc"
748777
}
749778

779+
func (m MongoDBCommunity) ArbiterNamespacedName() types.NamespacedName {
780+
return types.NamespacedName{Namespace: m.Namespace, Name: m.Name + "-arb"}
781+
}
782+
750783
func (m MongoDBCommunity) AutomationConfigSecretName() string {
751784
return m.Name + "-config"
752785
}

api/v1/mongodbcommunity_types_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package v1
22

33
import (
4+
"encoding/json"
5+
"github.com/stretchr/testify/require"
46
"testing"
57

68
"github.com/stretchr/testify/assert"
@@ -44,6 +46,25 @@ func TestMongodConfiguration(t *testing.T) {
4446
})
4547
}
4648

49+
func TestMongodConfigurationWithNestedMapsAfterUnmarshalling(t *testing.T) {
50+
jsonStr := `
51+
{
52+
"net.port": 40333,
53+
"storage.dbPath": "/other/data/path"
54+
}
55+
`
56+
mc := NewMongodConfiguration()
57+
require.NoError(t, json.Unmarshal([]byte(jsonStr), &mc))
58+
assert.Equal(t, map[string]interface{}{
59+
"net": map[string]interface{}{
60+
"port": 40333.,
61+
},
62+
"storage": map[string]interface{}{
63+
"dbPath": "/other/data/path",
64+
},
65+
}, mc.Object)
66+
}
67+
4768
func TestGetScramCredentialsSecretName(t *testing.T) {
4869
testusers := []struct {
4970
in MongoDBUser

config/crd/bases/mongodbcommunity.mongodb.com_mongodbcommunity.yaml

-6
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,6 @@ kind: CustomResourceDefinition
55
metadata:
66
annotations:
77
controller-gen.kubebuilder.io/version: v0.4.1
8-
service.binding/type: 'mongodb'
9-
service.binding/provider: 'community'
10-
service.binding: 'path={.metadata.name}-{.spec.users[0].db}-{.spec.users[0].name},objectType=Secret'
11-
service.binding/connectionString: 'path={.metadata.name}-{.spec.users[0].db}-{.spec.users[0].name},objectType=Secret,sourceKey=connectionString.standardSrv'
12-
service.binding/username: 'path={.metadata.name}-{.spec.users[0].db}-{.spec.users[0].name},objectType=Secret,sourceKey=username'
13-
service.binding/password: 'path={.metadata.name}-{.spec.users[0].db}-{.spec.users[0].name},objectType=Secret,sourceKey=password'
148
creationTimestamp: null
159
name: mongodbcommunity.mongodbcommunity.mongodb.com
1610
spec:

controllers/mongodb_tls_test.go

+10
Original file line numberDiff line numberDiff line change
@@ -313,3 +313,13 @@ func createTLSSecret(c k8sClient.Client, mdb mdbv1.MongoDBCommunity, crt string,
313313
s := sBuilder.Build()
314314
return c.Create(context.TODO(), &s)
315315
}
316+
317+
func createUserPasswordSecret(c k8sClient.Client, mdb mdbv1.MongoDBCommunity, userPasswordSecretName string, password string) error {
318+
sBuilder := secret.Builder().
319+
SetName(userPasswordSecretName).
320+
SetNamespace(mdb.Namespace).
321+
SetField("password", password)
322+
323+
s := sBuilder.Build()
324+
return c.Create(context.TODO(), &s)
325+
}

controllers/replica_set_controller.go

+44-21
Original file line numberDiff line numberDiff line change
@@ -442,17 +442,20 @@ func (r *ReplicaSetReconciler) deployMongoDBReplicaSet(mdb mdbv1.MongoDBCommunit
442442
// The Service definition is built from the `mdb` resource. If `isArbiter` is set to true, the Service
443443
// will be created for the arbiters Statefulset.
444444
func (r *ReplicaSetReconciler) ensureService(mdb mdbv1.MongoDBCommunity) error {
445-
name := mdb.ServiceName()
445+
processPortManager, err := r.createProcessPortManager(mdb)
446+
if err != nil {
447+
return err
448+
}
446449

447-
svc := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: mdb.Namespace}}
450+
svc := &corev1.Service{ObjectMeta: metav1.ObjectMeta{Name: mdb.ServiceName(), Namespace: mdb.Namespace}}
448451
op, err := controllerutil.CreateOrUpdate(context.TODO(), r.client, svc, func() error {
449452
resourceVersion := svc.ResourceVersion // Save resourceVersion for later
450-
*svc = buildService(mdb)
453+
*svc = r.buildService(mdb, processPortManager)
451454
svc.ResourceVersion = resourceVersion
452455
return nil
453456
})
454457
if err != nil {
455-
r.log.Infof("Cloud not create or patch the service: %s", err)
458+
r.log.Errorf("Could not create or patch the service: %s", err)
456459
return nil
457460
}
458461

@@ -461,12 +464,29 @@ func (r *ReplicaSetReconciler) ensureService(mdb mdbv1.MongoDBCommunity) error {
461464
return err
462465
}
463466

467+
// createProcessPortManager is a helper method for creating new ReplicaSetPortManager.
468+
// ReplicaSetPortManager needs current automation config and current pod state and the code for getting them
469+
// was extracted here as it is used in ensureService and buildAutomationConfig.
470+
func (r *ReplicaSetReconciler) createProcessPortManager(mdb mdbv1.MongoDBCommunity) (*agent.ReplicaSetPortManager, error) {
471+
currentAC, err := automationconfig.ReadFromSecret(r.client, types.NamespacedName{Name: mdb.AutomationConfigSecretName(), Namespace: mdb.Namespace})
472+
if err != nil {
473+
return nil, errors.Errorf("could not read existing automation config: %s", err)
474+
}
475+
476+
currentPodStates, err := agent.GetAllDesiredMembersAndArbitersPodState(mdb.NamespacedName(), r.client, mdb.StatefulSetReplicasThisReconciliation(), mdb.StatefulSetArbitersThisReconciliation(), currentAC.Version, r.log)
477+
if err != nil {
478+
return nil, fmt.Errorf("cannot get all pods goal state: %w", err)
479+
}
480+
481+
return agent.NewReplicaSetPortManager(r.log, mdb.Spec.AdditionalMongodConfig.GetDBPort(), currentPodStates, currentAC.Processes), nil
482+
}
483+
464484
func (r *ReplicaSetReconciler) createOrUpdateStatefulSet(mdb mdbv1.MongoDBCommunity, isArbiter bool) error {
465485
set := appsv1.StatefulSet{}
466486

467487
name := mdb.NamespacedName()
468488
if isArbiter {
469-
name.Name = name.Name + "-arb"
489+
name = mdb.ArbiterNamespacedName()
470490
}
471491

472492
err := r.client.Get(context.TODO(), name, &set)
@@ -528,41 +548,37 @@ func buildAutomationConfig(mdb mdbv1.MongoDBCommunity, auth automationconfig.Aut
528548
SetOptions(automationconfig.Options{DownloadBase: "/var/lib/mongodb-mms-automation"}).
529549
SetAuth(auth).
530550
SetDataDir(mdb.GetMongodConfiguration().GetDBDataDir()).
531-
SetPort(mdb.GetMongodConfiguration().GetDBPort()).
532551
AddModifications(getMongodConfigModification(mdb)).
533552
AddModifications(modifications...).
534553
Build()
535554
}
536555

537556
// buildService creates a Service that will be used for the Replica Set StatefulSet
538557
// that allows all the members of the STS to see each other.
539-
func buildService(mdb mdbv1.MongoDBCommunity) corev1.Service {
558+
func (r *ReplicaSetReconciler) buildService(mdb mdbv1.MongoDBCommunity, portManager *agent.ReplicaSetPortManager) corev1.Service {
540559
label := make(map[string]string)
541560
name := mdb.ServiceName()
542561

543562
label["app"] = name
544563

545-
return service.Builder().
564+
serviceBuilder := service.Builder().
546565
SetName(name).
547566
SetNamespace(mdb.Namespace).
548567
SetSelector(label).
549568
SetLabels(label).
550569
SetServiceType(corev1.ServiceTypeClusterIP).
551570
SetClusterIP("None").
552571
SetPublishNotReadyAddresses(true).
553-
SetOwnerReferences(mdb.GetOwnerReferences()).
554-
AddPort(mongoDBPort(mdb)).
555-
AddPort(prometheusPort(mdb)).
556-
Build()
557-
}
572+
SetOwnerReferences(mdb.GetOwnerReferences())
558573

559-
// mongoDBPort returns a `corev1.ServicePort` to be configured in the StatefulSet
560-
// for this MongoDB resource.
561-
func mongoDBPort(mdb mdbv1.MongoDBCommunity) *corev1.ServicePort {
562-
return &corev1.ServicePort{
563-
Port: int32(mdb.GetMongodConfiguration().GetDBPort()),
564-
Name: "mongodb",
574+
for _, servicePort := range portManager.GetServicePorts() {
575+
tmpServicePort := servicePort
576+
serviceBuilder.AddPort(&tmpServicePort)
565577
}
578+
579+
serviceBuilder.AddPort(prometheusPort(mdb))
580+
581+
return serviceBuilder.Build()
566582
}
567583

568584
// validateSpec checks if the MongoDB resource Spec is valid.
@@ -627,14 +643,21 @@ func (r ReplicaSetReconciler) buildAutomationConfig(mdb mdbv1.MongoDBCommunity)
627643
}
628644
}
629645

646+
processPortManager, err := r.createProcessPortManager(mdb)
647+
if err != nil {
648+
return automationconfig.AutomationConfig{}, err
649+
}
650+
630651
automationConfig, err := buildAutomationConfig(
631652
mdb,
632653
auth,
633654
currentAC,
634655
tlsModification,
635656
customRolesModification,
636657
prometheusModification,
658+
processPortManager.GetPortsModification(),
637659
)
660+
638661
if err != nil {
639662
return automationconfig.AutomationConfig{}, errors.Errorf("could not create an automation config: %s", err)
640663
}
@@ -646,7 +669,7 @@ func (r ReplicaSetReconciler) buildAutomationConfig(mdb mdbv1.MongoDBCommunity)
646669
return automationConfig, nil
647670
}
648671

649-
// overrideToAutomationConfig turns an automation config ovverride from the resource spec into an automation config
672+
// overrideToAutomationConfig turns an automation config override from the resource spec into an automation config
650673
// which can be used to merge.
651674
func overrideToAutomationConfig(override mdbv1.AutomationConfigOverride) automationconfig.AutomationConfig {
652675
var processes []automationconfig.Process
@@ -703,7 +726,7 @@ func buildArbitersModificationFunction(mdb mdbv1.MongoDBCommunity) statefulset.M
703726
return statefulset.Apply(
704727
statefulset.WithReplicas(mdb.StatefulSetArbitersThisReconciliation()),
705728
statefulset.WithServiceName(mdb.ServiceName()),
706-
statefulset.WithName(mdb.Name+"-arb"),
729+
statefulset.WithName(mdb.ArbiterNamespacedName().Name),
707730
)
708731
}
709732

0 commit comments

Comments
 (0)