Skip to content

Commit 374cd48

Browse files
authored
Test connection string works when mounted into pod (mongodb#882)
1 parent 93c8667 commit 374cd48

File tree

12 files changed

+187
-12
lines changed

12 files changed

+187
-12
lines changed

.action_templates/jobs/tests.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,5 @@ tests:
5252
distro: ubi
5353
- test-name: replica_set_custom_persistent_volume
5454
distro: ubi
55+
- test-name: replica_set_mount_connection_string
56+
distro: ubi

.github/workflows/e2e-fork.yml

+2
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ jobs:
134134
distro: ubi
135135
- test-name: replica_set_custom_persistent_volume
136136
distro: ubi
137+
- test-name: replica_set_mount_connection_string
138+
distro: ubi
137139
steps:
138140
# template: .action_templates/steps/cancel-previous.yaml
139141
- name: Cancel Previous Runs

.github/workflows/e2e.yml

+2
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,8 @@ jobs:
140140
distro: ubi
141141
- test-name: replica_set_custom_persistent_volume
142142
distro: ubi
143+
- test-name: replica_set_mount_connection_string
144+
distro: ubi
143145
steps:
144146
# template: .action_templates/steps/cancel-previous.yaml
145147
- name: Cancel Previous Runs

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -94,3 +94,5 @@ Dockerfile.ubi-*
9494
Dockerfile.ubuntu-*
9595

9696
diagnostics
97+
98+
!test/test-app/Dockerfile

api/v1/mongodbcommunity_types.go

+6-4
Original file line numberDiff line numberDiff line change
@@ -668,25 +668,26 @@ func (m MongoDBCommunity) AutomationConfigArbitersThisReconciliation() int {
668668

669669
// MongoURI returns a mongo uri which can be used to connect to this deployment
670670
func (m MongoDBCommunity) MongoURI(clusterDomain string) string {
671-
return fmt.Sprintf("mongodb://%s", strings.Join(m.Hosts(clusterDomain), ","))
671+
return fmt.Sprintf("mongodb://%s/?replicaSet=%s", strings.Join(m.Hosts(clusterDomain), ","), m.Name)
672672
}
673673

674674
// MongoSRVURI returns a mongo srv uri which can be used to connect to this deployment
675675
func (m MongoDBCommunity) MongoSRVURI(clusterDomain string) string {
676676
if clusterDomain == "" {
677677
clusterDomain = defaultClusterDomain
678678
}
679-
return fmt.Sprintf("mongodb+srv://%s.%s.svc.%s", m.ServiceName(), m.Namespace, clusterDomain)
679+
return fmt.Sprintf("mongodb+srv://%s.%s.svc.%s/?replicaSet=%s", m.ServiceName(), m.Namespace, clusterDomain, m.Name)
680680
}
681681

682682
// MongoAuthUserURI returns a mongo uri which can be used to connect to this deployment
683683
// and includes the authentication data for the user
684684
func (m MongoDBCommunity) MongoAuthUserURI(user scram.User, password string, clusterDomain string) string {
685-
return fmt.Sprintf("mongodb://%s:%s@%s/%s?ssl=%t",
685+
return fmt.Sprintf("mongodb://%s:%s@%s/%s?replicaSet=%s&ssl=%t",
686686
url.QueryEscape(user.Username),
687687
url.QueryEscape(password),
688688
strings.Join(m.Hosts(clusterDomain), ","),
689689
user.Database,
690+
m.Name,
690691
m.Spec.Security.TLS.Enabled)
691692
}
692693

@@ -696,13 +697,14 @@ func (m MongoDBCommunity) MongoAuthUserSRVURI(user scram.User, password string,
696697
if clusterDomain == "" {
697698
clusterDomain = defaultClusterDomain
698699
}
699-
return fmt.Sprintf("mongodb+srv://%s:%s@%s.%s.svc.%s/%s?ssl=%t",
700+
return fmt.Sprintf("mongodb+srv://%s:%s@%s.%s.svc.%s/%s?replicaSet=%s&ssl=%t",
700701
url.QueryEscape(user.Username),
701702
url.QueryEscape(password),
702703
m.ServiceName(),
703704
m.Namespace,
704705
clusterDomain,
705706
user.Database,
707+
m.Name,
706708
m.Spec.Security.TLS.Enabled)
707709
}
708710

api/v1/mongodbcommunity_types_test.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -9,20 +9,20 @@ import (
99

1010
func TestMongoDB_MongoURI(t *testing.T) {
1111
mdb := newReplicaSet(2, "my-rs", "my-namespace")
12-
assert.Equal(t, mdb.MongoURI(""), "mongodb://my-rs-0.my-rs-svc.my-namespace.svc.cluster.local:27017,my-rs-1.my-rs-svc.my-namespace.svc.cluster.local:27017")
13-
assert.Equal(t, mdb.MongoURI("my.cluster"), "mongodb://my-rs-0.my-rs-svc.my-namespace.svc.my.cluster:27017,my-rs-1.my-rs-svc.my-namespace.svc.my.cluster:27017")
12+
assert.Equal(t, mdb.MongoURI(""), "mongodb://my-rs-0.my-rs-svc.my-namespace.svc.cluster.local:27017,my-rs-1.my-rs-svc.my-namespace.svc.cluster.local:27017/?replicaSet=my-rs")
13+
assert.Equal(t, mdb.MongoURI("my.cluster"), "mongodb://my-rs-0.my-rs-svc.my-namespace.svc.my.cluster:27017,my-rs-1.my-rs-svc.my-namespace.svc.my.cluster:27017/?replicaSet=my-rs")
1414
mdb = newReplicaSet(1, "my-single-rs", "my-single-namespace")
15-
assert.Equal(t, mdb.MongoURI(""), "mongodb://my-single-rs-0.my-single-rs-svc.my-single-namespace.svc.cluster.local:27017")
16-
assert.Equal(t, mdb.MongoURI("my.cluster"), "mongodb://my-single-rs-0.my-single-rs-svc.my-single-namespace.svc.my.cluster:27017")
15+
assert.Equal(t, mdb.MongoURI(""), "mongodb://my-single-rs-0.my-single-rs-svc.my-single-namespace.svc.cluster.local:27017/?replicaSet=my-single-rs")
16+
assert.Equal(t, mdb.MongoURI("my.cluster"), "mongodb://my-single-rs-0.my-single-rs-svc.my-single-namespace.svc.my.cluster:27017/?replicaSet=my-single-rs")
1717
mdb = newReplicaSet(5, "my-big-rs", "my-big-namespace")
18-
assert.Equal(t, mdb.MongoURI(""), "mongodb://my-big-rs-0.my-big-rs-svc.my-big-namespace.svc.cluster.local:27017,my-big-rs-1.my-big-rs-svc.my-big-namespace.svc.cluster.local:27017,my-big-rs-2.my-big-rs-svc.my-big-namespace.svc.cluster.local:27017,my-big-rs-3.my-big-rs-svc.my-big-namespace.svc.cluster.local:27017,my-big-rs-4.my-big-rs-svc.my-big-namespace.svc.cluster.local:27017")
19-
assert.Equal(t, mdb.MongoURI("my.cluster"), "mongodb://my-big-rs-0.my-big-rs-svc.my-big-namespace.svc.my.cluster:27017,my-big-rs-1.my-big-rs-svc.my-big-namespace.svc.my.cluster:27017,my-big-rs-2.my-big-rs-svc.my-big-namespace.svc.my.cluster:27017,my-big-rs-3.my-big-rs-svc.my-big-namespace.svc.my.cluster:27017,my-big-rs-4.my-big-rs-svc.my-big-namespace.svc.my.cluster:27017")
18+
assert.Equal(t, mdb.MongoURI(""), "mongodb://my-big-rs-0.my-big-rs-svc.my-big-namespace.svc.cluster.local:27017,my-big-rs-1.my-big-rs-svc.my-big-namespace.svc.cluster.local:27017,my-big-rs-2.my-big-rs-svc.my-big-namespace.svc.cluster.local:27017,my-big-rs-3.my-big-rs-svc.my-big-namespace.svc.cluster.local:27017,my-big-rs-4.my-big-rs-svc.my-big-namespace.svc.cluster.local:27017/?replicaSet=my-big-rs")
19+
assert.Equal(t, mdb.MongoURI("my.cluster"), "mongodb://my-big-rs-0.my-big-rs-svc.my-big-namespace.svc.my.cluster:27017,my-big-rs-1.my-big-rs-svc.my-big-namespace.svc.my.cluster:27017,my-big-rs-2.my-big-rs-svc.my-big-namespace.svc.my.cluster:27017,my-big-rs-3.my-big-rs-svc.my-big-namespace.svc.my.cluster:27017,my-big-rs-4.my-big-rs-svc.my-big-namespace.svc.my.cluster:27017/?replicaSet=my-big-rs")
2020
mdb = newReplicaSet(2, "my-rs", "my-namespace")
2121
mdb.Spec.AdditionalMongodConfig.Object = map[string]interface{}{
2222
"net.port": 40333.,
2323
}
24-
assert.Equal(t, mdb.MongoURI(""), "mongodb://my-rs-0.my-rs-svc.my-namespace.svc.cluster.local:40333,my-rs-1.my-rs-svc.my-namespace.svc.cluster.local:40333")
25-
assert.Equal(t, mdb.MongoURI("my.cluster"), "mongodb://my-rs-0.my-rs-svc.my-namespace.svc.my.cluster:40333,my-rs-1.my-rs-svc.my-namespace.svc.my.cluster:40333")
24+
assert.Equal(t, mdb.MongoURI(""), "mongodb://my-rs-0.my-rs-svc.my-namespace.svc.cluster.local:40333,my-rs-1.my-rs-svc.my-namespace.svc.cluster.local:40333/?replicaSet=my-rs")
25+
assert.Equal(t, mdb.MongoURI("my.cluster"), "mongodb://my-rs-0.my-rs-svc.my-namespace.svc.my.cluster:40333,my-rs-1.my-rs-svc.my-namespace.svc.my.cluster:40333/?replicaSet=my-rs")
2626
}
2727

2828
func TestMongodConfiguration(t *testing.T) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
package replica_set_mount_connection_string
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"github.com/mongodb/mongodb-kubernetes-operator/test/e2e/util/wait"
7+
"github.com/stretchr/testify/assert"
8+
corev1 "k8s.io/api/core/v1"
9+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
10+
"os"
11+
"testing"
12+
"time"
13+
14+
. "github.com/mongodb/mongodb-kubernetes-operator/test/e2e/util/mongotester"
15+
16+
e2eutil "github.com/mongodb/mongodb-kubernetes-operator/test/e2e"
17+
"github.com/mongodb/mongodb-kubernetes-operator/test/e2e/mongodbtests"
18+
setup "github.com/mongodb/mongodb-kubernetes-operator/test/e2e/setup"
19+
)
20+
21+
func TestMain(m *testing.M) {
22+
code, err := e2eutil.RunTest(m)
23+
if err != nil {
24+
fmt.Println(err)
25+
}
26+
os.Exit(code)
27+
}
28+
29+
// createPythonTestPod creates a pod with a simple python app which connects to a MongoDB database
30+
// using the connection string referenced within a given secret key.
31+
func createPythonTestPod(idx int, namespace, secretName, secretKey string) corev1.Pod {
32+
return corev1.Pod{
33+
ObjectMeta: metav1.ObjectMeta{
34+
Name: fmt.Sprintf("test-pod-%d", idx),
35+
Namespace: namespace,
36+
},
37+
Spec: corev1.PodSpec{
38+
RestartPolicy: corev1.RestartPolicyNever,
39+
Containers: []corev1.Container{
40+
{
41+
Name: "python-app",
42+
Image: "quay.io/mongodb/mongodb-kubernetes-operator-test-app:1.0.0",
43+
Command: []string{"python", "main.py"},
44+
WorkingDir: "/app",
45+
Env: []corev1.EnvVar{
46+
{
47+
Name: "CONNECTION_STRING",
48+
ValueFrom: &corev1.EnvVarSource{
49+
SecretKeyRef: &corev1.SecretKeySelector{
50+
LocalObjectReference: corev1.LocalObjectReference{
51+
Name: secretName,
52+
},
53+
Key: secretKey,
54+
},
55+
},
56+
},
57+
},
58+
},
59+
},
60+
},
61+
}
62+
}
63+
64+
func TestMountConnectionString(t *testing.T) {
65+
ctx := setup.Setup(t)
66+
defer ctx.Teardown()
67+
68+
mdb, user := e2eutil.NewTestMongoDB(ctx, "mdb0", "")
69+
scramUser := mdb.GetScramUsers()[0]
70+
71+
_, err := setup.GeneratePasswordForUser(ctx, user, "")
72+
if err != nil {
73+
t.Fatal(err)
74+
}
75+
76+
tester, err := FromResource(t, mdb)
77+
if err != nil {
78+
t.Fatal(err)
79+
}
80+
81+
t.Run("Create MongoDB Resource", mongodbtests.CreateMongoDBResource(&mdb, ctx))
82+
t.Run("Basic tests", mongodbtests.BasicFunctionality(&mdb))
83+
t.Run("Keyfile authentication is configured", tester.HasKeyfileAuth(3))
84+
t.Run("Test Basic Connectivity", tester.ConnectivitySucceeds())
85+
t.Run("Test SRV Connectivity", tester.ConnectivitySucceeds(WithURI(mdb.MongoSRVURI("")), WithoutTls(), WithReplicaSet((mdb.Name))))
86+
t.Run("Test Basic Connectivity with generated connection string secret",
87+
tester.ConnectivitySucceeds(WithURI(mongodbtests.GetConnectionStringForUser(mdb, scramUser))))
88+
t.Run("Test SRV Connectivity with generated connection string secret",
89+
tester.ConnectivitySucceeds(WithURI(mongodbtests.GetSrvConnectionStringForUser(mdb, scramUser))))
90+
t.Run("Ensure Authentication", tester.EnsureAuthenticationIsConfigured(3))
91+
t.Run("AutomationConfig has the correct version", mongodbtests.AutomationConfigVersionHasTheExpectedVersion(&mdb, 1))
92+
93+
t.Run("Application Pod can connect to MongoDB using the generated standard connection string.", func(t *testing.T) {
94+
testPod := createPythonTestPod(0, mdb.Namespace, fmt.Sprintf("%s-admin-%s", mdb.Name, user.Name), "connectionString.standard")
95+
err := e2eutil.TestClient.Create(context.TODO(), &testPod, &e2eutil.CleanupOptions{
96+
TestContext: ctx,
97+
})
98+
assert.NoError(t, err)
99+
assert.NoError(t, wait.ForPodPhase(t, time.Minute*5, testPod, corev1.PodSucceeded))
100+
})
101+
102+
t.Run("Application Pod can connect to MongoDB using the generated secret SRV connection string", func(t *testing.T) {
103+
testPod := createPythonTestPod(1, mdb.Namespace, fmt.Sprintf("%s-admin-%s", mdb.Name, user.Name), "connectionString.standardSrv")
104+
err := e2eutil.TestClient.Create(context.TODO(), &testPod, &e2eutil.CleanupOptions{
105+
TestContext: ctx,
106+
})
107+
assert.NoError(t, err)
108+
assert.NoError(t, wait.ForPodPhase(t, time.Minute*5, testPod, corev1.PodSucceeded))
109+
})
110+
}

test/e2e/util/wait/wait.go

+11
Original file line numberDiff line numberDiff line change
@@ -181,6 +181,17 @@ func ForPodReadiness(t *testing.T, isReady bool, containerName string, timeout t
181181
})
182182
}
183183

184+
func ForPodPhase(t *testing.T, timeout time.Duration, pod corev1.Pod, podPhase corev1.PodPhase) error {
185+
return wait.Poll(time.Second*3, timeout, func() (done bool, err error) {
186+
err = e2eutil.TestClient.Get(context.TODO(), types.NamespacedName{Name: pod.Name, Namespace: pod.Namespace}, &pod)
187+
if err != nil {
188+
return false, err
189+
}
190+
t.Logf("Current phase %s, expected phase %s", pod.Status.Phase, podPhase)
191+
return pod.Status.Phase == podPhase, nil
192+
})
193+
}
194+
184195
// waitForRuntimeObjectToExist waits until a runtime.Object of the given name exists
185196
// using the provided retryInterval and timeout provided.
186197
func waitForRuntimeObjectToExist(name string, retryInterval, timeout time.Duration, obj client.Object, namespace string) error {

test/test-app/Dockerfile

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
FROM python:3.8-slim-buster
2+
3+
WORKDIR /app
4+
5+
COPY requirements.txt requirements.txt
6+
7+
RUN pip3 install -r requirements.txt
8+
9+
COPY main.py main.py
10+
11+
CMD [ "python3", "/app/main.py"]

test/test-app/README.md

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
This Dockerfile is a for an image which can be found
2+
[here](https://quay.io/repository/mongodb/mongodb-kubernetes-operator-test-app)
3+
4+
The E2E tests use this to ensure that the secret generated by the operator can be mounted into an application pod and
5+
used to successfully connect with a Mongo client.
6+
7+
```bash
8+
docker build . -t quay.io/mongodb/mongodb-kubernetes-operator-test-app:1.0.0
9+
```

test/test-app/main.py

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import os
2+
import sys
3+
4+
from pymongo import MongoClient
5+
6+
7+
def main() -> int:
8+
connection_string = os.getenv("CONNECTION_STRING")
9+
print(f"Using connection String: {connection_string}")
10+
client = MongoClient(connection_string)
11+
12+
try:
13+
client.testing.col.insert_one({})
14+
except Exception as e:
15+
print(f"Error inserting document {e}")
16+
return 1
17+
18+
return 0
19+
20+
21+
if __name__ == "__main__":
22+
sys.exit(main())

test/test-app/requirements.txt

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
PyMongo==4.0.1
2+
dnspython==2.2.0

0 commit comments

Comments
 (0)