Skip to content

Commit

Permalink
Fix issues for the Unix listener (FerretDB#1397)
Browse files Browse the repository at this point in the history
  • Loading branch information
chilagrow authored Nov 18, 2022
1 parent 1a1885e commit 4f53809
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 67 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ vendor/
*.rpm
*.tar
website/build/
tmp/

# test coverage results, generated files like commit.txt, etc.
*.txt
Expand Down
41 changes: 31 additions & 10 deletions integration/commands_administration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1046,9 +1046,11 @@ func TestCommandsAdministrationWhatsMyURI(t *testing.T) {
databaseName := s.Collection.Database().Name()
collectionName := s.Collection.Name()

// only check port number on TCP connection, no need to check on Unix socket
isTCP := s.IsTCP(t)

// setup second client connection to check that `whatsmyuri` returns different ports
uri := fmt.Sprintf("mongodb://127.0.0.1:%d/", s.Port)
client2, err := mongo.Connect(s.Ctx, options.Client().ApplyURI(uri))
client2, err := mongo.Connect(s.Ctx, options.Client().ApplyURI(s.MongoDBURI))
require.NoError(t, err)
defer client2.Disconnect(s.Ctx)
collection2 := client2.Database(databaseName).Collection(collectionName)
Expand All @@ -1061,15 +1063,34 @@ func TestCommandsAdministrationWhatsMyURI(t *testing.T) {
require.NoError(t, err)

doc := ConvertDocument(t, actual)
assert.Equal(t, float64(1), must.NotFail(doc.Get("ok")))
keys := doc.Keys()
values := doc.Values()

var ok float64
var you string

for i, k := range keys {
switch k {
case "ok":
ok = values[i].(float64)
case "you":
you = values[i].(string)
}
}

// record ports to compare that they are not equal for two different clients.
_, port, err := net.SplitHostPort(must.NotFail(doc.Get("you")).(string))
require.NoError(t, err)
assert.NotEmpty(t, port)
ports = append(ports, port)
assert.Equal(t, float64(1), ok)

if isTCP {
// record ports to compare that they are not equal for two different clients.
_, port, err := net.SplitHostPort(you)
require.NoError(t, err)
assert.NotEmpty(t, port)
ports = append(ports, port)
}
}

require.Equal(t, 2, len(ports))
assert.NotEqual(t, ports[0], ports[1])
if isTCP {
require.Equal(t, 2, len(ports))
assert.NotEqual(t, ports[0], ports[1])
}
}
41 changes: 35 additions & 6 deletions integration/commands_diagnostic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (

"github.com/FerretDB/FerretDB/integration/setup"
"github.com/FerretDB/FerretDB/integration/shareddata"
"github.com/FerretDB/FerretDB/internal/util/must"
)

func TestCommandsDiagnosticGetLog(t *testing.T) {
Expand Down Expand Up @@ -192,7 +191,12 @@ func TestCommandsDiagnosticExplain(t *testing.T) {
setup.SkipForTigrisWithReason(t, "https://github.com/FerretDB/FerretDB/issues/1253")

t.Parallel()
ctx, collection := setup.Setup(t, shareddata.Scalars, shareddata.Composites)
s := setup.SetupWithOpts(t, &setup.SetupOpts{
Providers: []shareddata.Provider{shareddata.Scalars, shareddata.Composites},
})
ctx, collection := s.Ctx, s.Collection

isTCP := s.IsTCP(t)

for name, tc := range map[string]struct {
query bson.D
Expand Down Expand Up @@ -230,11 +234,36 @@ func TestCommandsDiagnosticExplain(t *testing.T) {
assert.Equal(t, tc.command, explainResult["command"])

serverInfo := ConvertDocument(t, explainResult["serverInfo"].(bson.D))
keys := serverInfo.Keys()
values := serverInfo.Values()

var host string
var port int32
var gitVersion string
var version string

for i, k := range keys {
switch k {
case "host":
host = values[i].(string)
case "port":
port = values[i].(int32)
case "gitVersion":
gitVersion = values[i].(string)
case "version":
version = values[i].(string)
}
}

assert.NotEmpty(t, host)

// only check port number on TCP, not on Unix socket
if isTCP {
assert.NotEmpty(t, port)
}

assert.NotEmpty(t, must.NotFail(serverInfo.Get("host")))
assert.NotEmpty(t, must.NotFail(serverInfo.Get("port")))
assert.NotEmpty(t, must.NotFail(serverInfo.Get("gitVersion")))
assert.Regexp(t, `^6\.0\.`, must.NotFail(serverInfo.Get("version")))
assert.NotEmpty(t, gitVersion)
assert.Regexp(t, `^6\.0\.`, version)

assert.NotEmpty(t, explainResult["queryPlanner"])
assert.IsType(t, bson.D{}, explainResult["queryPlanner"])
Expand Down
73 changes: 38 additions & 35 deletions integration/setup/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ func SkipForPostgresWithReason(tb testing.TB, reason string) {
}

// setupListener starts in-process FerretDB server that runs until ctx is done,
// and returns listening port number.
func setupListener(tb testing.TB, ctx context.Context, logger *zap.Logger) (*state.Provider, int) {
// and returns listening MongoDB URI.
func setupListener(tb testing.TB, ctx context.Context, logger *zap.Logger, preferUnixSocket bool) (*state.Provider, string) {
tb.Helper()

p, err := state.NewProvider("")
Expand Down Expand Up @@ -124,9 +124,10 @@ func setupListener(tb testing.TB, ctx context.Context, logger *zap.Logger) (*sta
mode = clientconn.DiffNormalMode
}

listenUnix := listenUnix(tb)
l := clientconn.NewListener(&clientconn.NewListenerOpts{
ListenAddr: "127.0.0.1:0",
ListenUnix: listenUnix(tb),
ListenUnix: listenUnix,
ProxyAddr: proxyAddr,
Mode: mode,
Metrics: metrics,
Expand All @@ -153,55 +154,57 @@ func setupListener(tb testing.TB, ctx context.Context, logger *zap.Logger) (*sta
h.Close()
})

// use Unix socket if preferred and possible
if preferUnixSocket && listenUnix != "" {
// TODO https://github.com/FerretDB/FerretDB/issues/1507
u := &url.URL{
Scheme: "mongodb",
Host: l.Unix().String(),
}

uri := u.String()
logger.Info("Listener started", zap.String("handler", *handlerF), zap.String("uri", uri))

return p, uri
}

port := l.Addr().(*net.TCPAddr).Port
logger.Info("Listener started", zap.String("handler", *handlerF), zap.Int("port", port))
uri := buildMongoDBURI(tb, port)
logger.Info("Listener started", zap.String("handler", *handlerF), zap.String("uri", uri))

return p, port
return p, uri
}

// setupClient returns MongoDB client for database on 127.0.0.1:port.
func setupClient(tb testing.TB, ctx context.Context, port int) *mongo.Client {
tb.Helper()

// buildMongoDBURI builds MongoDB URI with given TCP port number.
func buildMongoDBURI(tb testing.TB, port int) string {
require.Greater(tb, port, 0)
require.Less(tb, port, 65536)

// those options should not affect anything except tests speed
v := url.Values{
// TODO: Test fails occurred on some platforms due to i/o timeout.
// Needs more investigation.
//
//"connectTimeoutMS": []string{"5000"},
//"serverSelectionTimeoutMS": []string{"5000"},
//"socketTimeoutMS": []string{"5000"},
//"heartbeatFrequencyMS": []string{"30000"},

//"minPoolSize": []string{"1"},
//"maxPoolSize": []string{"1"},
//"maxConnecting": []string{"1"},
//"maxIdleTimeMS": []string{"0"},

//"directConnection": []string{"true"},
//"appName": []string{tb.Name()},
// TODO https://github.com/FerretDB/FerretDB/issues/1507
u := &url.URL{
Scheme: "mongodb",
Host: fmt.Sprintf("127.0.0.1:%d", port),
Path: "/",
}

u := url.URL{
Scheme: "mongodb",
Host: fmt.Sprintf("127.0.0.1:%d", port),
Path: "/",
RawQuery: v.Encode(),
}
client, err := mongo.Connect(ctx, options.Client().ApplyURI(u.String()))
require.NoError(tb, err)
return u.String()
}

err = client.Ping(ctx, nil)
// setupClient returns MongoDB client for database on given MongoDB URI.
func setupClient(tb testing.TB, ctx context.Context, uri string) *mongo.Client {
tb.Helper()

client, err := mongo.Connect(ctx, options.Client().ApplyURI(uri))
require.NoError(tb, err)

tb.Cleanup(func() {
err = client.Disconnect(ctx)
require.NoError(tb, err)
})

err = client.Ping(ctx, nil)
require.NoError(tb, err)

return client
}

Expand Down
57 changes: 52 additions & 5 deletions integration/setup/common_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,62 @@
package setup

import (
"crypto/rand"
"math/big"
"os"
"path/filepath"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// chars used for generating random string.
// MongoDB Unix socket path with upper case letter does not
// work hence using lower case alphanumeric.
const chars = "abcdefghijklmnopqrstuvwxyz0123456789"

// getRandomString returns a random string in lower case alphanumeric of given length.
// It is intended for generating random for integration testing,
// but not recommended for reusing it for other purpose.
func getRandomString(tb testing.TB, length int) string {
b := make([]byte, length)
for i := range b {
num, err := rand.Int(rand.Reader, big.NewInt(int64(len(chars))))
require.NoError(tb, err)

b[i] = chars[num.Int64()]
}

return string(b)
}

// listenUnix returns temporary Unix domain socket path for that test.
func listenUnix(tb testing.TB) string {
// The commented out code does not generate valid Unix domain socket path on macOS (at least).
// Maybe the argument is too long?
// TODO https://github.com/FerretDB/FerretDB/issues/1295
// return filepath.Join(tb.TempDir(), "ferretdb.sock")
// generate random string of length 20 for the directory name.
dirName := getRandomString(tb, 20)

// on mac, temp dir is length 49 and like /var/folders/9p/cc9b8krs2zd1x9qx89fs1sjw0000gn/t/.
// on linux, temp dir is length 5 and like /tmp/.
tmp := os.TempDir()
basePath := filepath.Join(tmp, dirName)

// The path must exist.
err := os.MkdirAll(basePath, os.ModePerm)
require.NoError(tb, err)

socketPath := filepath.Join(basePath, "ferretdb.sock")

if len(socketPath) >= 104 {
// This is a way to fail fast before creating a client for this socket.
// Unix socket path must be less than 104 chars for mac, 108 for linux.
tb.Fatalf("listen Unix socket path too long len: %d, path: %s", len(socketPath), socketPath)
}

tb.Cleanup(func() {
err := os.RemoveAll(basePath)
assert.NoError(tb, err)
})

return ""
return socketPath
}
27 changes: 20 additions & 7 deletions integration/setup/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ package setup

import (
"context"
"net/url"
"testing"

"github.com/stretchr/testify/require"
Expand Down Expand Up @@ -49,7 +50,7 @@ type SetupOpts struct {
type SetupResult struct {
Ctx context.Context
Collection *mongo.Collection
Port uint16
MongoDBURI string
StateProvider *state.Provider
}

Expand All @@ -72,25 +73,26 @@ func SetupWithOpts(tb testing.TB, opts *SetupOpts) *SetupResult {
logger := testutil.Logger(tb, level)

var stateProvider *state.Provider
var uri string
port := *targetPortF
if port == 0 {
// TODO check targetUnixSocketF, setup Unix socket-only listener if true.
// TODO https://github.com/FerretDB/FerretDB/issues/1295
_ = *targetUnixSocketF
stateProvider, port = setupListener(tb, ctx, logger)
targetUnixSocket := *targetUnixSocketF
stateProvider, uri = setupListener(tb, ctx, logger, targetUnixSocket)
} else {
uri = buildMongoDBURI(tb, port)
}

// register cleanup function after setupListener registers its own to preserve full logs
tb.Cleanup(cancel)

collection := setupCollection(tb, ctx, setupClient(tb, ctx, port), opts)
collection := setupCollection(tb, ctx, setupClient(tb, ctx, uri), opts)

level.SetLevel(*logLevelF)

return &SetupResult{
Ctx: ctx,
Collection: collection,
Port: uint16(port),
MongoDBURI: uri,
StateProvider: stateProvider,
}
}
Expand All @@ -105,6 +107,17 @@ func Setup(tb testing.TB, providers ...shareddata.Provider) (context.Context, *m
return s.Ctx, s.Collection
}

// IsTCP returns true if uri contains a valid port number.
func (s *SetupResult) IsTCP(tb testing.TB) bool {
path, err := url.PathUnescape(s.MongoDBURI)
require.NoError(tb, err)

u, err := url.Parse(path)
require.NoError(tb, err)

return u.Port() != ""
}

// setupCollection setups a single collection for all compatible providers, if they are present.
func setupCollection(tb testing.TB, ctx context.Context, client *mongo.Client, opts *SetupOpts) *mongo.Collection {
tb.Helper()
Expand Down
Loading

0 comments on commit 4f53809

Please sign in to comment.