github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/tests/integration/connection_test.go (about)

     1  //go:build integration
     2  // +build integration
     3  
     4  package integration
     5  
     6  import (
     7  	"context"
     8  	"crypto/tls"
     9  	"fmt"
    10  	"os"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/stretchr/testify/require"
    15  	"github.com/ydb-platform/ydb-go-genproto/Ydb_Discovery_V1"
    16  	"github.com/ydb-platform/ydb-go-genproto/Ydb_Export_V1"
    17  	"github.com/ydb-platform/ydb-go-genproto/Ydb_Scripting_V1"
    18  	"github.com/ydb-platform/ydb-go-genproto/protos/Ydb"
    19  	"github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Discovery"
    20  	"github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Export"
    21  	"github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Operations"
    22  	"github.com/ydb-platform/ydb-go-genproto/protos/Ydb_Scripting"
    23  	"google.golang.org/grpc"
    24  	grpcCodes "google.golang.org/grpc/codes"
    25  	"google.golang.org/grpc/metadata"
    26  	"google.golang.org/protobuf/proto"
    27  	"google.golang.org/protobuf/types/known/durationpb"
    28  
    29  	"github.com/ydb-platform/ydb-go-sdk/v3"
    30  	"github.com/ydb-platform/ydb-go-sdk/v3/config"
    31  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/meta"
    32  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest"
    33  	"github.com/ydb-platform/ydb-go-sdk/v3/log"
    34  	"github.com/ydb-platform/ydb-go-sdk/v3/retry"
    35  	"github.com/ydb-platform/ydb-go-sdk/v3/trace"
    36  )
    37  
    38  //nolint:gocyclo
    39  func TestConnection(t *testing.T) {
    40  	const sumColumn = "sum"
    41  	var (
    42  		userAgent     = "connection user agent"
    43  		requestType   = "connection request type"
    44  		checkMetadata = func(ctx context.Context) {
    45  			md, has := metadata.FromOutgoingContext(ctx)
    46  			if !has {
    47  				t.Fatalf("no medatada")
    48  			}
    49  			userAgents := md.Get(meta.HeaderUserAgent)
    50  			if len(userAgents) == 0 {
    51  				t.Fatalf("no user agent")
    52  			}
    53  			if userAgents[0] != userAgent {
    54  				t.Fatalf("unknown user agent: %s", userAgents[0])
    55  			}
    56  			requestTypes := md.Get(meta.HeaderRequestType)
    57  			if len(requestTypes) == 0 {
    58  				t.Fatalf("no request type")
    59  			}
    60  			if requestTypes[0] != requestType {
    61  				t.Fatalf("unknown request type: %s", requestTypes[0])
    62  			}
    63  			traceIDs := md.Get(meta.HeaderTraceID)
    64  			if len(traceIDs) == 0 {
    65  				t.Fatalf("no traceIDs")
    66  			}
    67  			if len(traceIDs[0]) == 0 {
    68  				t.Fatalf("no traceID")
    69  			}
    70  		}
    71  		ctx = xtest.Context(t)
    72  	)
    73  
    74  	db, err := ydb.Open(ctx,
    75  		"", // corner case for check replacement of endpoint+database+secure
    76  		ydb.WithConnectionString(os.Getenv("YDB_CONNECTION_STRING")),
    77  		ydb.WithAccessTokenCredentials(
    78  			os.Getenv("YDB_ACCESS_TOKEN_CREDENTIALS"),
    79  		),
    80  		ydb.With(
    81  			config.WithOperationTimeout(time.Second*2),
    82  			config.WithOperationCancelAfter(time.Second*2),
    83  		),
    84  		ydb.WithConnectionTTL(time.Millisecond*10000),
    85  		ydb.WithMinTLSVersion(tls.VersionTLS10),
    86  		ydb.WithLogger(
    87  			newLoggerWithMinLevel(t, log.WARN),
    88  			trace.MatchDetails(`ydb\.(driver|discovery|retry|scheme).*`),
    89  		),
    90  		ydb.WithUserAgent(userAgent),
    91  		ydb.WithRequestsType(requestType),
    92  		ydb.With(
    93  			config.WithGrpcOptions(
    94  				grpc.WithUnaryInterceptor(func(
    95  					ctx context.Context,
    96  					method string,
    97  					req, reply interface{},
    98  					cc *grpc.ClientConn,
    99  					invoker grpc.UnaryInvoker,
   100  					opts ...grpc.CallOption,
   101  				) error {
   102  					checkMetadata(ctx)
   103  					return invoker(ctx, method, req, reply, cc, opts...)
   104  				}),
   105  				grpc.WithStreamInterceptor(func(
   106  					ctx context.Context,
   107  					desc *grpc.StreamDesc,
   108  					cc *grpc.ClientConn,
   109  					method string,
   110  					streamer grpc.Streamer,
   111  					opts ...grpc.CallOption,
   112  				) (grpc.ClientStream, error) {
   113  					checkMetadata(ctx)
   114  					return streamer(ctx, desc, cc, method, opts...)
   115  				}),
   116  			),
   117  		),
   118  	)
   119  	if err != nil {
   120  		t.Fatal(err)
   121  	}
   122  	defer func() {
   123  		// cleanup connection
   124  		if e := db.Close(ctx); e != nil {
   125  			t.Fatalf("close failed: %+v", e)
   126  		}
   127  	}()
   128  	t.Run("discovery.WhoAmI", func(t *testing.T) {
   129  		if err = retry.Retry(ctx, func(ctx context.Context) (err error) {
   130  			discoveryClient := Ydb_Discovery_V1.NewDiscoveryServiceClient(ydb.GRPCConn(db))
   131  			response, err := discoveryClient.WhoAmI(
   132  				ctx,
   133  				&Ydb_Discovery.WhoAmIRequest{IncludeGroups: true},
   134  			)
   135  			if err != nil {
   136  				return err
   137  			}
   138  			var result Ydb_Discovery.WhoAmIResult
   139  			err = proto.Unmarshal(response.GetOperation().GetResult().GetValue(), &result)
   140  			if err != nil {
   141  				return
   142  			}
   143  			return nil
   144  		}, retry.WithIdempotent(true)); err != nil {
   145  			t.Fatalf("Execute failed: %v", err)
   146  		}
   147  	})
   148  	t.Run("scripting.ExecuteYql", func(t *testing.T) {
   149  		if err = retry.Retry(ctx, func(ctx context.Context) (err error) {
   150  			scriptingClient := Ydb_Scripting_V1.NewScriptingServiceClient(ydb.GRPCConn(db))
   151  			response, err := scriptingClient.ExecuteYql(
   152  				ctx,
   153  				&Ydb_Scripting.ExecuteYqlRequest{Script: "SELECT 1+100 AS sum"},
   154  			)
   155  			if err != nil {
   156  				return err
   157  			}
   158  			var result Ydb_Scripting.ExecuteYqlResult
   159  			err = proto.Unmarshal(response.GetOperation().GetResult().GetValue(), &result)
   160  			if err != nil {
   161  				return
   162  			}
   163  			if len(result.GetResultSets()) != 1 {
   164  				return fmt.Errorf(
   165  					"unexpected result sets count: %d",
   166  					len(result.GetResultSets()),
   167  				)
   168  			}
   169  			if len(result.GetResultSets()[0].GetColumns()) != 1 {
   170  				return fmt.Errorf(
   171  					"unexpected colums count: %d",
   172  					len(result.GetResultSets()[0].GetColumns()),
   173  				)
   174  			}
   175  			if result.GetResultSets()[0].GetColumns()[0].GetName() != sumColumn {
   176  				return fmt.Errorf(
   177  					"unexpected colum name: %s",
   178  					result.GetResultSets()[0].GetColumns()[0].GetName(),
   179  				)
   180  			}
   181  			if len(result.GetResultSets()[0].GetRows()) != 1 {
   182  				return fmt.Errorf(
   183  					"unexpected rows count: %d",
   184  					len(result.GetResultSets()[0].GetRows()),
   185  				)
   186  			}
   187  			if result.GetResultSets()[0].GetRows()[0].GetItems()[0].GetInt32Value() != 101 {
   188  				return fmt.Errorf(
   189  					"unexpected result of select: %d",
   190  					result.GetResultSets()[0].GetRows()[0].GetInt64Value(),
   191  				)
   192  			}
   193  			return nil
   194  		}, retry.WithIdempotent(true)); err != nil {
   195  			t.Fatalf("Execute failed: %v", err)
   196  		}
   197  	})
   198  	t.Run("scripting.StreamExecuteYql", func(t *testing.T) {
   199  		if err = retry.Retry(ctx, func(ctx context.Context) (err error) {
   200  			scriptingClient := Ydb_Scripting_V1.NewScriptingServiceClient(ydb.GRPCConn(db))
   201  			client, err := scriptingClient.StreamExecuteYql(
   202  				ctx,
   203  				&Ydb_Scripting.ExecuteYqlRequest{Script: "SELECT 1+100 AS sum"},
   204  			)
   205  			if err != nil {
   206  				return err
   207  			}
   208  			response, err := client.Recv()
   209  			if err != nil {
   210  				return err
   211  			}
   212  			if len(response.GetResult().GetResultSet().GetColumns()) != 1 {
   213  				return fmt.Errorf(
   214  					"unexpected colums count: %d",
   215  					len(response.GetResult().GetResultSet().GetColumns()),
   216  				)
   217  			}
   218  			if response.GetResult().GetResultSet().GetColumns()[0].GetName() != sumColumn {
   219  				return fmt.Errorf(
   220  					"unexpected colum name: %s",
   221  					response.GetResult().GetResultSet().GetColumns()[0].GetName(),
   222  				)
   223  			}
   224  			if len(response.GetResult().GetResultSet().GetRows()) != 1 {
   225  				return fmt.Errorf(
   226  					"unexpected rows count: %d",
   227  					len(response.GetResult().GetResultSet().GetRows()),
   228  				)
   229  			}
   230  			if response.GetResult().GetResultSet().GetRows()[0].GetItems()[0].GetInt32Value() != 101 {
   231  				return fmt.Errorf(
   232  					"unexpected result of select: %d",
   233  					response.GetResult().GetResultSet().GetRows()[0].GetInt64Value(),
   234  				)
   235  			}
   236  			return nil
   237  		}, retry.WithIdempotent(true)); err != nil {
   238  			t.Fatalf("Stream execute failed: %v", err)
   239  		}
   240  	})
   241  	t.Run("with.scripting.StreamExecuteYql", func(t *testing.T) {
   242  		var childDB *ydb.Driver
   243  		childDB, err = db.With(
   244  			ctx,
   245  			ydb.WithDialTimeout(time.Second*5),
   246  		)
   247  		if err != nil {
   248  			t.Fatalf("failed to open sub-connection: %v", err)
   249  		}
   250  		defer func() {
   251  			_ = childDB.Close(ctx)
   252  		}()
   253  		if err = retry.Retry(ctx, func(ctx context.Context) (err error) {
   254  			scriptingClient := Ydb_Scripting_V1.NewScriptingServiceClient(ydb.GRPCConn(childDB))
   255  			client, err := scriptingClient.StreamExecuteYql(
   256  				ctx,
   257  				&Ydb_Scripting.ExecuteYqlRequest{Script: "SELECT 1+100 AS sum"},
   258  			)
   259  			if err != nil {
   260  				return err
   261  			}
   262  			response, err := client.Recv()
   263  			if err != nil {
   264  				return err
   265  			}
   266  			if len(response.GetResult().GetResultSet().GetColumns()) != 1 {
   267  				return fmt.Errorf(
   268  					"unexpected colums count: %d",
   269  					len(response.GetResult().GetResultSet().GetColumns()),
   270  				)
   271  			}
   272  			if response.GetResult().GetResultSet().GetColumns()[0].GetName() != sumColumn {
   273  				return fmt.Errorf(
   274  					"unexpected colum name: %s",
   275  					response.GetResult().GetResultSet().GetColumns()[0].GetName(),
   276  				)
   277  			}
   278  			if len(response.GetResult().GetResultSet().GetRows()) != 1 {
   279  				return fmt.Errorf(
   280  					"unexpected rows count: %d",
   281  					len(response.GetResult().GetResultSet().GetRows()),
   282  				)
   283  			}
   284  			if response.GetResult().GetResultSet().GetRows()[0].GetItems()[0].GetInt32Value() != 101 {
   285  				return fmt.Errorf(
   286  					"unexpected result of select: %d",
   287  					response.GetResult().GetResultSet().GetRows()[0].GetInt64Value(),
   288  				)
   289  			}
   290  			return nil
   291  		}, retry.WithIdempotent(true)); err != nil {
   292  			t.Fatalf("Stream execute failed: %v", err)
   293  		}
   294  	})
   295  	t.Run("export.ExportToS3", func(t *testing.T) {
   296  		if err = retry.Retry(ctx, func(ctx context.Context) (err error) {
   297  			exportClient := Ydb_Export_V1.NewExportServiceClient(ydb.GRPCConn(db))
   298  			response, err := exportClient.ExportToS3(
   299  				ctx,
   300  				&Ydb_Export.ExportToS3Request{
   301  					OperationParams: &Ydb_Operations.OperationParams{
   302  						OperationTimeout: durationpb.New(time.Second),
   303  						CancelAfter:      durationpb.New(time.Second),
   304  					},
   305  					Settings: &Ydb_Export.ExportToS3Settings{},
   306  				},
   307  			)
   308  			if err != nil {
   309  				return err
   310  			}
   311  			if response.GetOperation().GetStatus() != Ydb.StatusIds_BAD_REQUEST {
   312  				return fmt.Errorf(
   313  					"operation must be BAD_REQUEST: %s",
   314  					response.GetOperation().GetStatus().String(),
   315  				)
   316  			}
   317  			return nil
   318  		}, retry.WithIdempotent(true)); err != nil {
   319  			t.Fatalf("check export failed: %v", err)
   320  		}
   321  	})
   322  }
   323  
   324  func TestZeroDialTimeout(t *testing.T) {
   325  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
   326  	defer cancel()
   327  
   328  	var traceID string
   329  
   330  	db, err := ydb.Open(
   331  		ctx,
   332  		"grpc://non-existent.com:2135/some",
   333  		ydb.WithDialTimeout(0),
   334  		ydb.With(
   335  			config.WithGrpcOptions(
   336  				grpc.WithUnaryInterceptor(func(
   337  					ctx context.Context,
   338  					method string,
   339  					req, reply interface{},
   340  					cc *grpc.ClientConn,
   341  					invoker grpc.UnaryInvoker,
   342  					opts ...grpc.CallOption,
   343  				) error {
   344  					md, has := metadata.FromOutgoingContext(ctx)
   345  					if !has {
   346  						t.Fatalf("no medatada")
   347  					}
   348  					traceIDs := md.Get(meta.HeaderTraceID)
   349  					if len(traceIDs) == 0 {
   350  						t.Fatalf("no traceIDs")
   351  					}
   352  					traceID = traceIDs[0]
   353  					return invoker(ctx, method, req, reply, cc, opts...)
   354  				}),
   355  			),
   356  		),
   357  	)
   358  
   359  	require.Error(t, err)
   360  	require.ErrorContains(t, err, traceID)
   361  	require.Nil(t, db)
   362  	if !ydb.IsTransportError(err, grpcCodes.DeadlineExceeded) {
   363  		require.ErrorIs(t, err, context.DeadlineExceeded)
   364  	}
   365  }
   366  
   367  func TestClusterDiscoveryRetry(t *testing.T) {
   368  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
   369  	defer cancel()
   370  
   371  	counter := 0
   372  
   373  	db, err := ydb.Open(ctx,
   374  		"grpc://non-existent.com:2135/some",
   375  		ydb.WithDialTimeout(time.Second),
   376  		ydb.WithTraceDriver(trace.Driver{
   377  			OnBalancerClusterDiscoveryAttempt: func(info trace.DriverBalancerClusterDiscoveryAttemptStartInfo) func(
   378  				trace.DriverBalancerClusterDiscoveryAttemptDoneInfo,
   379  			) {
   380  				counter++
   381  				return nil
   382  			},
   383  		}),
   384  	)
   385  	t.Logf("attempts: %d", counter)
   386  	t.Logf("err: %v", err)
   387  	require.Error(t, err)
   388  	require.Nil(t, db)
   389  	if !ydb.IsTransportError(err, grpcCodes.DeadlineExceeded) {
   390  		require.ErrorIs(t, err, context.DeadlineExceeded)
   391  	}
   392  	require.Greater(t, counter, 1)
   393  }