Skip to content

Commit 5b8065c

Browse files
author
Divjot Arora
authored
GODRIVER-1593 Implement a proxy dialer for integration tests (#378)
1 parent 3446ea5 commit 5b8065c

File tree

11 files changed

+683
-160
lines changed

11 files changed

+683
-160
lines changed

.evergreen/config.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -838,6 +838,20 @@ tasks:
838838
AUTH: "auth"
839839
SSL: "ssl"
840840

841+
- name: test-standalone-auth-nossl
842+
tags: ["test", "standalone", "authssl"]
843+
commands:
844+
- func: bootstrap-mongo-orchestration
845+
vars:
846+
TOPOLOGY: "server"
847+
AUTH: "auth"
848+
SSL: "nossl"
849+
- func: run-tests
850+
vars:
851+
TOPOLOGY: "server"
852+
AUTH: "auth"
853+
SSL: "nossl"
854+
841855
- name: test-standalone-auth-ssl-snappy-compression
842856
tags: ["test", "standalone", "authssl", "compression", "snappy"]
843857
commands:
@@ -1367,6 +1381,20 @@ tasks:
13671381
SSL: "ssl"
13681382
MONGO_GO_DRIVER_COMPRESSOR: "zstd"
13691383

1384+
- name: test-sharded-auth-nossl
1385+
tags: ["test", "sharded", "authssl"]
1386+
commands:
1387+
- func: bootstrap-mongo-orchestration
1388+
vars:
1389+
TOPOLOGY: "sharded_cluster"
1390+
AUTH: "auth"
1391+
SSL: "nossl"
1392+
- func: run-tests
1393+
vars:
1394+
TOPOLOGY: "sharded_cluster"
1395+
AUTH: "auth"
1396+
SSL: "nossl"
1397+
13701398
- name: test-enterprise-auth-plain
13711399
tags: ["test", "enterprise-auth"]
13721400
commands:

mongo/integration/client_test.go

Lines changed: 13 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,10 @@
77
package integration
88

99
import (
10-
"bytes"
11-
"context"
1210
"fmt"
13-
"io"
14-
"net"
1511
"path"
1612
"reflect"
1713
"strings"
18-
"sync"
1914
"testing"
2015
"time"
2116

@@ -29,7 +24,6 @@ import (
2924
"go.mongodb.org/mongo-driver/mongo/readpref"
3025
"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
3126
"go.mongodb.org/mongo-driver/x/mongo/driver"
32-
"go.mongodb.org/mongo-driver/x/mongo/driver/drivertest"
3327
"go.mongodb.org/mongo-driver/x/mongo/driver/wiremessage"
3428
)
3529

@@ -381,37 +375,31 @@ func TestClient(t *testing.T) {
381375
})
382376

383377
testAppName := "foo"
384-
hosts := options.Client().ApplyURI(mt.ConnString()).Hosts
385-
appNameProxyDialer := newProxyDialer()
386-
appNameDialerOpts := options.Client().
387-
SetDialer(appNameProxyDialer).
388-
SetHosts(hosts[:1]).
389-
SetDirect(true).
378+
appNameClientOpts := options.Client().
390379
SetAppName(testAppName)
391380
appNameMtOpts := mtest.NewOptions().
392-
ClientOptions(appNameDialerOpts).
393-
Topologies(mtest.Single).
394-
Auth(false) // Can't run with auth because the proxy dialer won't work with TLS enabled.
381+
ClientType(mtest.Proxy).
382+
ClientOptions(appNameClientOpts).
383+
Topologies(mtest.Single)
395384
mt.RunOpts("app name is always sent", appNameMtOpts, func(mt *mtest.T) {
396385
err := mt.Client.Ping(mtest.Background, mtest.PrimaryRp)
397386
assert.Nil(mt, err, "Ping error: %v", err)
398387

399-
msgPairs := appNameProxyDialer.messages
388+
msgPairs := mt.GetProxiedMessages()
400389
assert.True(mt, len(msgPairs) >= 2, "expected at least 2 events sent, got %v", len(msgPairs))
401390

402391
// First two messages should be connection handshakes: one for the heartbeat connection and the other for the
403392
// application connection.
404393
for idx, pair := range msgPairs[:2] {
405-
cmd, err := drivertest.GetCommandFromQueryWireMessage(pair.sent)
406-
assert.Nil(mt, err, "GetCommandFromQueryWireMessage error at index %d: %v", idx, err)
407-
heartbeatCmdName := cmd.Index(0).Key()
408-
assert.Equal(mt, "isMaster", heartbeatCmdName,
409-
"expected command name isMaster at index %d, got %v", idx, heartbeatCmdName)
410-
411-
appNameVal, err := cmd.LookupErr("client", "application", "name")
412-
assert.Nil(mt, err, "expected command %s at index %d to contain app name", cmd, idx)
394+
assert.Equal(mt, pair.CommandName, "isMaster", "expected command name isMaster at index %d, got %s", idx,
395+
pair.CommandName)
396+
397+
sent := pair.Sent
398+
appNameVal, err := sent.Command.LookupErr("client", "application", "name")
399+
assert.Nil(mt, err, "expected command %s at index %d to contain app name", sent.Command, idx)
413400
appName := appNameVal.StringValue()
414-
assert.Equal(mt, testAppName, appName, "expected app name %v at index %d, got %v", testAppName, idx, appName)
401+
assert.Equal(mt, testAppName, appName, "expected app name %v at index %d, got %v", testAppName, idx,
402+
appName)
415403
}
416404
})
417405

@@ -446,101 +434,3 @@ type proxyMessage struct {
446434
sent wiremessage.WireMessage
447435
received wiremessage.WireMessage
448436
}
449-
450-
// proxyDialer is a ContextDialer implementation that wraps a net.Dialer and records the messages sent and received
451-
// using connections created through it.
452-
type proxyDialer struct {
453-
*net.Dialer
454-
sync.Mutex
455-
messages []proxyMessage
456-
sentMap sync.Map
457-
}
458-
459-
var _ options.ContextDialer = (*proxyDialer)(nil)
460-
461-
func newProxyDialer() *proxyDialer {
462-
return &proxyDialer{
463-
Dialer: &net.Dialer{Timeout: 30 * time.Second},
464-
}
465-
}
466-
467-
// DialContext creates a new proxyConnection.
468-
func (p *proxyDialer) DialContext(ctx context.Context, network, address string) (net.Conn, error) {
469-
netConn, err := p.Dialer.DialContext(ctx, network, address)
470-
if err != nil {
471-
return netConn, err
472-
}
473-
474-
proxy := &proxyConn{
475-
Conn: netConn,
476-
dialer: p,
477-
currentReading: bytes.NewBuffer(nil),
478-
}
479-
return proxy, nil
480-
}
481-
482-
// storeSentMessage stores a copy of the wire message being sent to the server.
483-
func (p *proxyDialer) storeSentMessage(msg []byte) {
484-
p.Lock()
485-
defer p.Unlock()
486-
487-
msgCopy := make(wiremessage.WireMessage, len(msg))
488-
copy(msgCopy, msg)
489-
490-
_, requestID, _, _, _, _ := wiremessage.ReadHeader(msgCopy)
491-
p.sentMap.Store(requestID, msgCopy)
492-
}
493-
494-
// storeReceivedMessage stores a copy of the wire message being received from the server.
495-
func (p *proxyDialer) storeReceivedMessage(msg []byte) {
496-
p.Lock()
497-
defer p.Unlock()
498-
499-
msgCopy := make(wiremessage.WireMessage, len(msg))
500-
copy(msgCopy, msg)
501-
502-
_, _, responseTo, _, _, _ := wiremessage.ReadHeader(msgCopy)
503-
sentMsg, _ := p.sentMap.Load(responseTo)
504-
p.sentMap.Delete(responseTo)
505-
506-
proxyMsg := proxyMessage{
507-
sent: sentMsg.(wiremessage.WireMessage),
508-
received: msgCopy,
509-
}
510-
p.messages = append(p.messages, proxyMsg)
511-
}
512-
513-
// proxyConn is a net.Conn that wraps a network connection. All messages sent/received through a proxyConn are stored
514-
// in the associated proxyDialer and are forwarded over the wrapped connection.
515-
type proxyConn struct {
516-
net.Conn
517-
dialer *proxyDialer
518-
currentReading *bytes.Buffer // The current message being read.
519-
}
520-
521-
// Write stores the given message in the proxyDialer associated with this connection and forwards the message to the
522-
// server.
523-
func (pc *proxyConn) Write(msg []byte) (n int, err error) {
524-
pc.dialer.storeSentMessage(msg)
525-
return pc.Conn.Write(msg)
526-
}
527-
528-
// Read reads the message from the server into the given buffer and stores the read message in the proxyDialer
529-
// associated with this connection.
530-
func (pc *proxyConn) Read(buffer []byte) (int, error) {
531-
n, err := pc.Conn.Read(buffer)
532-
if err != nil {
533-
return n, err
534-
}
535-
536-
_, err = io.Copy(pc.currentReading, bytes.NewReader(buffer))
537-
if err != nil {
538-
return 0, fmt.Errorf("error copying to mock: %v", err)
539-
}
540-
if len(buffer) != 4 {
541-
pc.dialer.storeReceivedMessage(pc.currentReading.Bytes())
542-
pc.currentReading.Reset()
543-
}
544-
545-
return n, err
546-
}

mongo/integration/mtest/mongotest.go

Lines changed: 29 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -86,14 +86,15 @@ type T struct {
8686
mockDeployment *mockDeployment // nil if the test is not being run against a mock
8787
mockResponses []bson.D
8888
createdColls []*Collection // collections created in this test
89+
proxyDialer *proxyDialer
8990
dbName, collName string
9091
failPointNames []string
9192
minServerVersion string
9293
maxServerVersion string
9394
validTopologies []TopologyKind
9495
auth *bool
95-
ssl *bool
9696
enterprise *bool
97+
ssl *bool
9798
collCreateOpts bson.D
9899
connsCheckedOut int // net number of connections checked out during test execution
99100

@@ -342,6 +343,15 @@ func (t *T) FilterFailedEvents(filter func(*event.CommandFailedEvent) bool) {
342343
t.failed = newEvents
343344
}
344345

346+
// GetProxiedMessages returns the messages proxied to the server by the test. If the client type is not Proxy, this
347+
// returns nil.
348+
func (t *T) GetProxiedMessages() []*ProxyMessage {
349+
if t.proxyDialer == nil {
350+
return nil
351+
}
352+
return t.proxyDialer.messages
353+
}
354+
345355
// ClearEvents clears the existing command monitoring events.
346356
func (t *T) ClearEvents() {
347357
t.started = t.started[:0]
@@ -580,14 +590,6 @@ func (t *T) createTestClient() {
580590

581591
var err error
582592
switch t.clientType {
583-
case Default:
584-
// only specify URI if the deployment is not set to avoid setting topology/server options along with the deployment
585-
var uriOpts *options.ClientOptions
586-
if clientOpts.Deployment == nil {
587-
uriOpts = options.Client().ApplyURI(testContext.connString.Original)
588-
}
589-
// Specify the URI-based options first so the test can override them.
590-
t.Client, err = mongo.NewClient(uriOpts, clientOpts)
591593
case Pinned:
592594
// pin to first mongos
593595
pinnedHostList := []string{testContext.connString.Hosts[0]}
@@ -599,6 +601,24 @@ func (t *T) createTestClient() {
599601
t.mockDeployment = newMockDeployment()
600602
clientOpts.Deployment = t.mockDeployment
601603
t.Client, err = mongo.NewClient(clientOpts)
604+
case Proxy:
605+
t.proxyDialer = newProxyDialer()
606+
clientOpts.SetDialer(t.proxyDialer)
607+
608+
// After setting the Dialer, fall-through to the Default case to apply the correct URI
609+
fallthrough
610+
case Default:
611+
// Use a different set of options to specify the URI because clientOpts may already have a URI or host seedlist
612+
// specified.
613+
var uriOpts *options.ClientOptions
614+
if clientOpts.Deployment == nil {
615+
// Only specify URI if the deployment is not set to avoid setting topology/server options along with the
616+
// deployment.
617+
uriOpts = options.Client().ApplyURI(testContext.connString.Original)
618+
}
619+
620+
// Pass in uriOpts first so clientOpts wins if there are any conflicting settings.
621+
t.Client, err = mongo.NewClient(uriOpts, clientOpts)
602622
}
603623
if err != nil {
604624
t.Fatalf("error creating client: %v", err)

mongo/integration/mtest/options.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ const (
3333
Pinned
3434
// Mock specifies a client that communicates with a mock deployment.
3535
Mock
36+
// Proxy specifies a client that proxies messages to the server and also stores parsed copies. The proxied
37+
// messages can be retrieved via T.GetProxiedMessages or T.GetRawProxiedMessages.
38+
Proxy
39+
)
40+
41+
var (
42+
falseBool = false
3643
)
3744

3845
// RunOnBlock describes a constraint for a test.
@@ -125,10 +132,15 @@ func (op *Options) DatabaseName(dbName string) *Options {
125132
}
126133

127134
// ClientType specifies the type of client that should be created for a test. This option will be propagated to all
128-
// sub-tests.
135+
// sub-tests. If the provided ClientType is Proxy, the SSL(false) option will be also be added because the internal
136+
// proxy dialer and connection types do not support SSL.
129137
func (op *Options) ClientType(ct ClientType) *Options {
130138
op.optFuncs = append(op.optFuncs, func(t *T) {
131139
t.clientType = ct
140+
141+
if ct == Proxy {
142+
t.ssl = &falseBool
143+
}
132144
})
133145
return op
134146
}

0 commit comments

Comments
 (0)