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 }