...

Source file src/go.mongodb.org/mongo-driver/mongo/integration/sessions_mongocryptd_prose_test.go

Documentation: go.mongodb.org/mongo-driver/mongo/integration

     1  // Copyright (C) MongoDB, Inc. 2023-present.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License"); you may
     4  // not use this file except in compliance with the License. You may obtain
     5  // a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
     6  
     7  package integration
     8  
     9  import (
    10  	"context"
    11  	"fmt"
    12  	"net"
    13  	"net/url"
    14  	"os/exec"
    15  	"strconv"
    16  	"testing"
    17  
    18  	"go.mongodb.org/mongo-driver/bson"
    19  	"go.mongodb.org/mongo-driver/event"
    20  	"go.mongodb.org/mongo-driver/internal/assert"
    21  	"go.mongodb.org/mongo-driver/internal/require"
    22  	"go.mongodb.org/mongo-driver/mongo"
    23  	"go.mongodb.org/mongo-driver/mongo/integration/mtest"
    24  	"go.mongodb.org/mongo-driver/mongo/options"
    25  	"go.mongodb.org/mongo-driver/x/bsonx/bsoncore"
    26  )
    27  
    28  type mongocryptdProcess struct {
    29  	cmd *exec.Cmd
    30  }
    31  
    32  // start will start a mongocryptd server in the background on the OS.
    33  func (p *mongocryptdProcess) start(port int) error {
    34  	args := []string{
    35  		"mongocryptd",
    36  		"--port", strconv.Itoa(port),
    37  	}
    38  
    39  	p.cmd = exec.Command(args[0], args[1:]...) //nolint:gosec
    40  	p.cmd.Stderr = p.cmd.Stdout
    41  
    42  	return p.cmd.Start()
    43  }
    44  
    45  // close will kill the underlying process on the command.
    46  func (p *mongocryptdProcess) close() error {
    47  	if p.cmd.Process == nil {
    48  		return nil
    49  	}
    50  
    51  	if err := p.cmd.Process.Kill(); err != nil {
    52  		return fmt.Errorf("failed to terminate mongocryptd process: %w", err)
    53  	}
    54  
    55  	// Release instead of wait to avoid blocking in CI.
    56  	err := p.cmd.Process.Release()
    57  	if err != nil {
    58  		return fmt.Errorf("failed to release mongocryptd: %w", err)
    59  	}
    60  
    61  	return nil
    62  }
    63  
    64  // newTestSessionMongocryptdProseClient will Create the client using the mongo
    65  // API rather than mtest. mtest will attempt to create a collection as
    66  // a database operation, which will not work on a mongocryptd server. A
    67  // mongocryptd server does not support operations on a database.
    68  func newTestSessionMongocryptdProseClient(mt *mtest.T) *mongo.Client {
    69  	const mongocryptdPort = 27022
    70  
    71  	// Monitor the lsid value on commands. If an operation run in any
    72  	// subtests contains an lsid, then the Go Driver wire message
    73  	// construction has incorrectly interpreted that
    74  	// LogicalSessionTimeoutMinutes was returned by the server on handshake.
    75  	cmdMonitor := &event.CommandMonitor{
    76  		Started: func(_ context.Context, evt *event.CommandStartedEvent) {
    77  			_, err := evt.Command.LookupErr("lsid")
    78  			assert.ErrorIs(mt, err, bsoncore.ErrElementNotFound)
    79  		},
    80  	}
    81  
    82  	uri := &url.URL{
    83  		Scheme: "mongodb",
    84  		Host:   net.JoinHostPort("localhost", strconv.Itoa(mongocryptdPort)),
    85  	}
    86  
    87  	proc := mongocryptdProcess{}
    88  
    89  	// Start a mongocryptd server.
    90  	err := proc.start(mongocryptdPort)
    91  	require.NoError(mt, err, "failed to create a mongocryptd process: %v", err)
    92  
    93  	mt.Cleanup(func() {
    94  		err := proc.close()
    95  		require.NoError(mt, err, "failed to close mongocryptd: %v", err)
    96  	})
    97  
    98  	clientOpts := options.
    99  		Client().
   100  		ApplyURI(uri.String()).
   101  		SetMonitor(cmdMonitor)
   102  
   103  	ctx := context.Background()
   104  
   105  	client, err := mongo.Connect(ctx, clientOpts)
   106  	require.NoError(mt, err, "could not connect to mongocryptd: %v", err)
   107  
   108  	return client
   109  
   110  }
   111  
   112  func TestSessionsMongocryptdProse(t *testing.T) {
   113  	mtOpts := mtest.NewOptions().
   114  		MinServerVersion("4.2").
   115  		Topologies(mtest.ReplicaSet, mtest.Sharded).
   116  		CreateCollection(false).
   117  		CreateClient(false)
   118  
   119  	// Create a new instance of mtest (MongoDB testing framework) for this
   120  	// test and configure it to control server versions.
   121  	mt := mtest.New(t, mtOpts)
   122  
   123  	proseTest18 := "18. implicit session is ignored if connection does not support sessions"
   124  	mt.RunOpts(proseTest18, mtOpts, func(mt *mtest.T) {
   125  		client := newTestSessionMongocryptdProseClient(mt)
   126  
   127  		mt.Cleanup(func() {
   128  			err := client.Disconnect(context.Background())
   129  			require.NoError(mt, err, "mongocryptd client could not disconnect: %v", err)
   130  		})
   131  
   132  		coll := client.Database("db").Collection("coll")
   133  
   134  		// Send a read command to the server (e.g., findOne), ignoring
   135  		// any errors from the server response
   136  		mt.RunOpts("read", mtOpts, func(_ *mtest.T) {
   137  			_ = coll.FindOne(context.Background(), bson.D{{"x", 1}})
   138  		})
   139  
   140  		// Send a write command to the server (e.g., insertOne),
   141  		// ignoring any errors from the server response
   142  		mt.RunOpts("write", mtOpts, func(_ *mtest.T) {
   143  			_, _ = coll.InsertOne(context.Background(), bson.D{{"x", 1}})
   144  		})
   145  	})
   146  
   147  	proseTest19 := "19. explicit session raises an error if connection does not support sessions"
   148  	mt.RunOpts(proseTest19, mtOpts, func(mt *mtest.T) {
   149  		client := newTestSessionMongocryptdProseClient(mt)
   150  
   151  		mt.Cleanup(func() {
   152  			err := client.Disconnect(context.Background())
   153  			require.NoError(mt, err, "mongocryptd client could not disconnect: %v", err)
   154  		})
   155  
   156  		// Create a new explicit session by calling startSession (this
   157  		// MUST NOT error).
   158  		session, err := client.StartSession()
   159  		require.NoError(mt, err, "expected error to be nil, got %v", err)
   160  
   161  		defer session.EndSession(context.Background())
   162  
   163  		sessionCtx := mongo.NewSessionContext(context.TODO(), session)
   164  
   165  		err = session.StartTransaction()
   166  		require.NoError(mt, err, "expected error to be nil, got %v", err)
   167  
   168  		coll := client.Database("db").Collection("coll")
   169  
   170  		// Attempt to send a read command to the server (e.g., findOne)
   171  		// with the explicit session passed in.
   172  		mt.RunOpts("read", mtOpts, func(mt *mtest.T) {
   173  			// Assert that a client-side error is generated
   174  			// indicating that sessions are not supported
   175  			res := coll.FindOne(sessionCtx, bson.D{{"x", 1}})
   176  			assert.EqualError(mt, res.Err(), "current topology does not support sessions")
   177  		})
   178  
   179  		// Attempt to send a write command to the server (e.g.,
   180  		// ``insertOne``) with the explicit session passed in.
   181  		mt.RunOpts("write", mtOpts, func(mt *mtest.T) {
   182  			// Assert that a client-side error is generated
   183  			// indicating that sessions are not supported.
   184  			res := coll.FindOne(sessionCtx, bson.D{{"x", 1}})
   185  			assert.EqualError(mt, res.Err(), "current topology does not support sessions")
   186  		})
   187  	})
   188  }
   189  

View as plain text