Skip to content
This repository was archived by the owner on Dec 12, 2025. It is now read-only.

Commit f2b3dc8

Browse files
authored
CLOUDP-59033: MongoDB URI Added To Status (#27)
1 parent be21806 commit f2b3dc8

File tree

11 files changed

+193
-38
lines changed

11 files changed

+193
-38
lines changed

deploy/crds/mongodb.com_mongodbs_crd.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,14 @@ spec:
5151
type: object
5252
status:
5353
description: MongoDBStatus defines the observed state of MongoDB
54+
properties:
55+
mongoUri:
56+
type: string
57+
phase:
58+
type: string
59+
required:
60+
- mongoUri
61+
- phase
5462
type: object
5563
type: object
5664
version: v1

pkg/apis/mongodb/v1/mongodb_types.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,16 @@ import (
99

1010
type Type string
1111

12-
var (
12+
const (
1313
ReplicaSet Type = "ReplicaSet"
1414
)
1515

16+
type Phase string
17+
18+
const (
19+
Running Phase = "Running"
20+
)
21+
1622
// MongoDBSpec defines the desired state of MongoDB
1723
type MongoDBSpec struct {
1824
// Members is the number of members in the replica set
@@ -31,6 +37,8 @@ type MongoDBSpec struct {
3137

3238
// MongoDBStatus defines the observed state of MongoDB
3339
type MongoDBStatus struct {
40+
MongoURI string `json:"mongoUri"`
41+
Phase Phase `json:"phase"`
3442
}
3543

3644
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@@ -46,6 +54,11 @@ type MongoDB struct {
4654
Status MongoDBStatus `json:"status,omitempty"`
4755
}
4856

57+
func (m *MongoDB) UpdateSuccess() {
58+
m.Status.MongoURI = m.MongoURI()
59+
m.Status.Phase = Running
60+
}
61+
4962
// MongoURI returns a mongo uri which can be used to connect to this deployment
5063
func (m MongoDB) MongoURI() string {
5164
members := make([]string, m.Spec.Members)

pkg/controller/mongodb/mongodb_controller.go

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"io/ioutil"
88
"os"
9+
"time"
910

1011
mdbv1 "github.com/mongodb/mongodb-kubernetes-operator/pkg/apis/mongodb/v1"
1112
"github.com/mongodb/mongodb-kubernetes-operator/pkg/automationconfig"
@@ -20,6 +21,7 @@ import (
2021
"k8s.io/apimachinery/pkg/api/errors"
2122
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2223
"k8s.io/apimachinery/pkg/runtime"
24+
"k8s.io/apimachinery/pkg/types"
2325
"sigs.k8s.io/controller-runtime/pkg/controller"
2426
"sigs.k8s.io/controller-runtime/pkg/handler"
2527
"sigs.k8s.io/controller-runtime/pkg/manager"
@@ -124,19 +126,48 @@ func (r *ReplicaSetReconciler) Reconcile(request reconcile.Request) (reconcile.R
124126

125127
sts, err := buildStatefulSet(mdb)
126128
if err != nil {
127-
log.Warnf("error building StatefulSet: %s", err)
129+
log.Infof("Error building StatefulSet: %s", err)
128130
return reconcile.Result{}, nil
129131
}
130132

131133
if err = r.client.CreateOrUpdate(&sts); err != nil {
132-
log.Warnf("error creating/updating StatefulSet: %s", err)
134+
log.Infof("Error creating/updating StatefulSet: %s", err)
133135
return reconcile.Result{}, err
136+
} else {
137+
log.Infof("StatefulSet successfully Created/Updated")
138+
}
139+
140+
log.Debugf("waiting for StatefulSet %s/%s to reach ready state", mdb.Namespace, mdb.Name)
141+
set := appsv1.StatefulSet{}
142+
if err := r.client.Get(context.TODO(), types.NamespacedName{Name: mdb.Name, Namespace: mdb.Namespace}, &set); err != nil {
143+
log.Infof("Error getting StatefulSet: %s", err)
144+
return reconcile.Result{}, err
145+
}
146+
147+
if !statefulset.IsReady(set) {
148+
log.Infof("Stateful Set has not yet reached the ready state, requeuing reconciliation")
149+
return reconcile.Result{RequeueAfter: time.Second * 10}, nil
150+
}
151+
152+
log.Infof("Stateful Set reached ready state!")
153+
154+
if err := r.updateStatusSuccess(&mdb); err != nil {
155+
log.Infof("Error updating the status of the MongoDB resource: %+v", err)
156+
return reconcile.Result{}, nil
134157
}
135158

136159
log.Info("Successfully finished reconciliation", "MongoDB.Spec:", mdb.Spec, "MongoDB.Status", mdb.Status)
137160
return reconcile.Result{}, nil
138161
}
139162

163+
func (r ReplicaSetReconciler) updateStatusSuccess(mdb *mdbv1.MongoDB) error {
164+
mdb.UpdateSuccess()
165+
if err := r.client.Status().Update(context.TODO(), mdb); err != nil {
166+
return fmt.Errorf("error updating status: %+v", err)
167+
}
168+
return nil
169+
}
170+
140171
func (r ReplicaSetReconciler) ensureAutomationConfig(mdb mdbv1.MongoDB) error {
141172
cm, err := r.buildAutomationConfigConfigMap(mdb)
142173
if err != nil {

pkg/kube/client/client.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package client
22

33
import (
44
"context"
5+
"reflect"
56

67
"k8s.io/apimachinery/pkg/api/errors"
78
"k8s.io/apimachinery/pkg/runtime"
9+
"k8s.io/apimachinery/pkg/types"
810
k8sClient "sigs.k8s.io/controller-runtime/pkg/client"
911
)
1012

@@ -26,12 +28,22 @@ type client struct {
2628
// CreateOrUpdate will either Create the runtime.Object if it doesn't exist, or Update it
2729
// if it does
2830
func (c client) CreateOrUpdate(obj runtime.Object) error {
29-
err := c.Create(context.TODO(), obj)
30-
if errors.IsAlreadyExists(err) {
31-
return c.Update(context.TODO(), obj)
32-
}
31+
objCopy := obj.DeepCopyObject()
32+
err := c.Get(context.TODO(), namespacedNameFromObject(obj), objCopy)
3333
if err != nil {
34+
if errors.IsNotFound(err) {
35+
return c.Create(context.TODO(), obj)
36+
}
3437
return err
3538
}
3639
return c.Update(context.TODO(), obj)
3740
}
41+
42+
func namespacedNameFromObject(obj runtime.Object) types.NamespacedName {
43+
ns := reflect.ValueOf(obj).Elem().FieldByName("Namespace").String()
44+
name := reflect.ValueOf(obj).Elem().FieldByName("Name").String()
45+
return types.NamespacedName{
46+
Name: name,
47+
Namespace: ns,
48+
}
49+
}

pkg/kube/client/mocked_client.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@ package client
22

33
import (
44
"context"
5+
"reflect"
6+
7+
appsv1 "k8s.io/api/apps/v1"
58
"k8s.io/apimachinery/pkg/api/errors"
69
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
710
"k8s.io/apimachinery/pkg/runtime"
8-
"reflect"
911
k8sClient "sigs.k8s.io/controller-runtime/pkg/client"
1012
)
1113

@@ -55,10 +57,21 @@ func (m *mockedClient) Create(_ context.Context, obj runtime.Object, _ ...k8sCli
5557
return alreadyExistsError()
5658
}
5759

60+
switch v := obj.(type) {
61+
case *appsv1.StatefulSet:
62+
onStatefulsetUpdate(v)
63+
}
64+
5865
relevantMap[objKey] = obj
5966
return nil
6067
}
6168

69+
// onStatefulsetUpdate configures the statefulset to be in the running state.
70+
func onStatefulsetUpdate(set *appsv1.StatefulSet) {
71+
set.Status.UpdatedReplicas = *set.Spec.Replicas
72+
set.Status.ReadyReplicas = *set.Spec.Replicas
73+
}
74+
6275
func (m *mockedClient) List(_ context.Context, _ runtime.Object, _ ...k8sClient.ListOption) error {
6376
return nil
6477
}
@@ -86,5 +99,5 @@ func (m *mockedClient) DeleteAllOf(_ context.Context, _ runtime.Object, _ ...k8s
8699
}
87100

88101
func (m *mockedClient) Status() k8sClient.StatusWriter {
89-
return nil
102+
return m
90103
}

pkg/kube/statefulset/statefulset.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package statefulset
22

33
import (
4+
appsv1 "k8s.io/api/apps/v1"
45
corev1 "k8s.io/api/core/v1"
56
)
67

@@ -70,3 +71,10 @@ func WithReadOnly(readonly bool) func(*corev1.VolumeMount) {
7071
v.ReadOnly = readonly
7172
}
7273
}
74+
75+
func IsReady(sts appsv1.StatefulSet) bool {
76+
replicas := *sts.Spec.Replicas
77+
allUpdated := replicas == sts.Status.UpdatedReplicas
78+
allReady := replicas == sts.Status.ReadyReplicas
79+
return allUpdated && allReady
80+
}

test/e2e/e2eutil.go

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ import (
66
"testing"
77
"time"
88

9-
"k8s.io/apimachinery/pkg/api/errors"
10-
119
"github.com/mongodb/mongodb-kubernetes-operator/pkg/apis"
1210
mdbv1 "github.com/mongodb/mongodb-kubernetes-operator/pkg/apis/mongodb/v1"
1311
f "github.com/operator-framework/operator-sdk/pkg/test"
@@ -29,15 +27,17 @@ func RegisterTypesWithFramework(newTypes ...runtime.Object) error {
2927
return nil
3028
}
3129

32-
func CreateOrUpdateMongoDB(mdb *mdbv1.MongoDB, ctx *f.TestCtx) error {
33-
err := f.Global.Client.Get(context.TODO(), types.NamespacedName{Name: mdb.Name, Namespace: mdb.Namespace}, &mdbv1.MongoDB{})
30+
// UpdateMongoDBResource applies the provided function to the most recent version of the MongoDB resource
31+
// and retries when there are conflicts
32+
func UpdateMongoDBResource(original *mdbv1.MongoDB, updateFunc func(*mdbv1.MongoDB)) error {
33+
err := f.Global.Client.Get(context.TODO(), types.NamespacedName{Name: original.Name, Namespace: original.Namespace}, original)
3434
if err != nil {
35-
if errors.IsNotFound(err) {
36-
return f.Global.Client.Create(context.TODO(), mdb, &f.CleanupOptions{TestContext: ctx})
37-
}
3835
return err
3936
}
40-
return f.Global.Client.Update(context.TODO(), mdb)
37+
38+
updateFunc(original)
39+
40+
return f.Global.Client.Update(context.TODO(), original)
4141
}
4242

4343
// waitForConfigMapToExist waits until a ConfigMap of the given name exists
@@ -47,6 +47,27 @@ func WaitForConfigMapToExist(cmName string, retryInterval, timeout time.Duration
4747
return cm, waitForRuntimeObjectToExist(cmName, retryInterval, timeout, &cm)
4848
}
4949

50+
// WaitForMongoDBToReachPhase waits until the given MongoDB resource reaches the expected phase
51+
func WaitForMongoDBToReachPhase(t *testing.T, mdb *mdbv1.MongoDB, phase mdbv1.Phase, retryInterval, timeout time.Duration) error {
52+
return waitForMongoDBCondition(mdb, retryInterval, timeout, func(db mdbv1.MongoDB) bool {
53+
t.Logf("current phase: %s, waiting for phase: %s", db.Status.Phase, phase)
54+
return db.Status.Phase == phase
55+
})
56+
}
57+
58+
// waitForMongoDBCondition polls and waits for a given condition to be true
59+
func waitForMongoDBCondition(mdb *mdbv1.MongoDB, retryInterval, timeout time.Duration, condition func(mdbv1.MongoDB) bool) error {
60+
mdbNew := mdbv1.MongoDB{}
61+
return wait.Poll(retryInterval, timeout, func() (done bool, err error) {
62+
err = f.Global.Client.Get(context.TODO(), types.NamespacedName{Name: mdb.Name, Namespace: f.Global.Namespace}, &mdbNew)
63+
if err != nil {
64+
return false, err
65+
}
66+
ready := condition(mdbNew)
67+
return ready, nil
68+
})
69+
}
70+
5071
// waitForStatefulSetToExist waits until a StatefulSet of the given name exists
5172
// using the provided retryInterval and timeout
5273
func WaitForStatefulSetToExist(stsName string, retryInterval, timeout time.Duration) (appsv1.StatefulSet, error) {

test/e2e/mongodbtests/mongodbtests.go

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import (
66
"testing"
77
"time"
88

9+
"k8s.io/apimachinery/pkg/types"
10+
911
"k8s.io/apimachinery/pkg/util/wait"
1012

1113
mdbv1 "github.com/mongodb/mongodb-kubernetes-operator/pkg/apis/mongodb/v1"
@@ -32,6 +34,17 @@ func StatefulSetIsReady(mdb *mdbv1.MongoDB) func(t *testing.T) {
3234
}
3335
}
3436

37+
// MongoDBReachesRunningPhase ensure the MongoDB resource reaches the Running phase
38+
func MongoDBReachesRunningPhase(mdb *mdbv1.MongoDB) func(t *testing.T) {
39+
return func(t *testing.T) {
40+
err := e2eutil.WaitForMongoDBToReachPhase(t, mdb, mdbv1.Running, time.Second*15, time.Minute*5)
41+
if err != nil {
42+
t.Fatal(err)
43+
}
44+
t.Logf("MongoDB %s/%s is Running!", mdb.Namespace, mdb.Name)
45+
}
46+
}
47+
3548
func AutomationConfigConfigMapExists(mdb *mdbv1.MongoDB) func(t *testing.T) {
3649
return func(t *testing.T) {
3750
cm, err := e2eutil.WaitForConfigMapToExist(mdb.ConfigMapName(), time.Second*5, time.Minute*1)
@@ -44,10 +57,10 @@ func AutomationConfigConfigMapExists(mdb *mdbv1.MongoDB) func(t *testing.T) {
4457
}
4558
}
4659

47-
// CreateOrUpdateResource creates the MongoDB resource if it doesn't exist, or updates it otherwise
48-
func CreateOrUpdateResource(mdb *mdbv1.MongoDB, ctx *f.TestCtx) func(*testing.T) {
60+
// CreateMongoDBResource creates the MongoDB resource
61+
func CreateMongoDBResource(mdb *mdbv1.MongoDB, ctx *f.TestCtx) func(*testing.T) {
4962
return func(t *testing.T) {
50-
if err := e2eutil.CreateOrUpdateMongoDB(mdb, ctx); err != nil {
63+
if err := f.Global.Client.Create(context.TODO(), mdb, &f.CleanupOptions{TestContext: ctx}); err != nil {
5164
t.Fatal(err)
5265
}
5366
t.Logf("Created MongoDB resource %s/%s", mdb.Name, mdb.Namespace)
@@ -78,7 +91,29 @@ func BasicConnectivity(mdb *mdbv1.MongoDB) func(t *testing.T) {
7891
if err := Connect(mdb); err != nil {
7992
t.Fatal(fmt.Sprintf("Error connecting to MongoDB deployment: %+v", err))
8093
}
81-
t.Logf("successfully connected to MongoDB deployment")
94+
}
95+
}
96+
97+
// Status compares the given status to the actual status of the MongoDB resource
98+
func Status(mdb *mdbv1.MongoDB, expectedStatus mdbv1.MongoDBStatus) func(t *testing.T) {
99+
return func(t *testing.T) {
100+
if err := f.Global.Client.Get(context.TODO(), types.NamespacedName{Name: mdb.Name, Namespace: mdb.Namespace}, mdb); err != nil {
101+
t.Fatal(fmt.Errorf("error getting MongoDB resource: %+v", err))
102+
}
103+
assert.Equal(t, expectedStatus, mdb.Status)
104+
}
105+
}
106+
107+
// Scale update the MongoDB with a new number of members and updates the resource
108+
func Scale(mdb *mdbv1.MongoDB, newMembers int) func(*testing.T) {
109+
return func(t *testing.T) {
110+
t.Logf("Scaling Mongodb %s, to %d members", mdb.Name, newMembers)
111+
err := e2eutil.UpdateMongoDBResource(mdb, func(db *mdbv1.MongoDB) {
112+
db.Spec.Members = newMembers
113+
})
114+
if err != nil {
115+
t.Fatal(err)
116+
}
82117
}
83118
}
84119

@@ -91,7 +126,7 @@ func Connect(mdb *mdbv1.MongoDB) error {
91126
return err
92127
}
93128

94-
return wait.Poll(time.Second*5, time.Minute*2, func() (done bool, err error) {
129+
return wait.Poll(time.Second*1, time.Second*30, func() (done bool, err error) {
95130
collection := mongoClient.Database("testing").Collection("numbers")
96131
_, err = collection.InsertOne(ctx, bson.M{"name": "pi", "value": 3.14159})
97132
if err != nil {
@@ -101,17 +136,6 @@ func Connect(mdb *mdbv1.MongoDB) error {
101136
})
102137
}
103138

104-
// Scale update the MongoDB with a new number of members and updates the resource
105-
func Scale(mdb *mdbv1.MongoDB, newMembers int, ctx *f.TestCtx) func(*testing.T) {
106-
return func(t *testing.T) {
107-
mdb.Spec.Members = newMembers
108-
t.Logf("Scaling Mongodb %s, to %d members", mdb.Name, mdb.Spec.Members)
109-
if err := e2eutil.CreateOrUpdateMongoDB(mdb, ctx); err != nil {
110-
t.Fatal(err)
111-
}
112-
}
113-
}
114-
115139
// IsReachableDuring periodically tests connectivity to the provided MongoDB resource
116140
// during execution of the provided functions. This function can be used to ensure
117141
// The MongoDB is up throughout the test.

test/e2e/replica_set/replica_set_test.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,14 @@ func TestReplicaSet(t *testing.T) {
2323
}
2424

2525
mdb := e2eutil.NewTestMongoDB()
26-
t.Run("Create MongoDB Resource", mongodbtests.CreateOrUpdateResource(&mdb, ctx))
26+
t.Run("Create MongoDB Resource", mongodbtests.CreateMongoDBResource(&mdb, ctx))
2727
t.Run("Config Map Was Correctly Created", mongodbtests.AutomationConfigConfigMapExists(&mdb))
2828
t.Run("Stateful Set Reaches Ready State", mongodbtests.StatefulSetIsReady(&mdb))
29+
t.Run("MongoDB Reaches Running Phase", mongodbtests.MongoDBReachesRunningPhase(&mdb))
2930
t.Run("Test Basic Connectivity", mongodbtests.BasicConnectivity(&mdb))
31+
t.Run("Test Status Was Updated", mongodbtests.Status(&mdb,
32+
mdbv1.MongoDBStatus{
33+
MongoURI: mdb.MongoURI(),
34+
Phase: mdbv1.Running,
35+
}))
3036
}

0 commit comments

Comments
 (0)