github.com/ydb-platform/ydb-go-sdk/v3@v3.89.2/tests/integration/topic_client_test.go (about) 1 //go:build integration 2 // +build integration 3 4 package integration 5 6 import ( 7 "context" 8 "io" 9 "os" 10 "path" 11 "strings" 12 "testing" 13 "time" 14 15 "github.com/stretchr/testify/require" 16 "google.golang.org/grpc" 17 18 ydb "github.com/ydb-platform/ydb-go-sdk/v3" 19 "github.com/ydb-platform/ydb-go-sdk/v3/config" 20 "github.com/ydb-platform/ydb-go-sdk/v3/internal/version" 21 "github.com/ydb-platform/ydb-go-sdk/v3/internal/xtest" 22 "github.com/ydb-platform/ydb-go-sdk/v3/topic/topicoptions" 23 "github.com/ydb-platform/ydb-go-sdk/v3/topic/topictypes" 24 "github.com/ydb-platform/ydb-go-sdk/v3/topic/topicwriter" 25 ) 26 27 const defaultConnectionString = "grpc://localhost:2136/local" 28 29 const commonConsumerName = "consumer" 30 31 func TestTopicCreateDrop(t *testing.T) { 32 ctx := xtest.Context(t) 33 db := connect(t) 34 topicPath := db.Name() + "/testtopic" 35 36 _ = db.Topic().Drop(ctx, topicPath) 37 err := db.Topic().Create(ctx, topicPath, 38 topicoptions.CreateWithConsumer( 39 topictypes.Consumer{ 40 Name: "test", 41 }, 42 ), 43 ) 44 require.NoError(t, err) 45 46 _, err = db.Topic().Describe(ctx, topicPath) 47 require.NoError(t, err) 48 49 err = db.Topic().Drop(ctx, topicPath) 50 require.NoError(t, err) 51 } 52 53 func TestTopicDescribe(t *testing.T) { 54 ctx := xtest.Context(t) 55 db := connect(t) 56 topicName := "test-topic-" + t.Name() 57 58 var ( 59 supportedCodecs = []topictypes.Codec{topictypes.CodecRaw, topictypes.CodecGzip} 60 minActivePartitions = int64(2) 61 // partitionCountLimit = int64(5) LOGBROKER-7800 62 retentionPeriod = time.Hour 63 writeSpeed = int64(1023) 64 burstBytes = int64(222) 65 consumers = []topictypes.Consumer{ 66 { 67 Name: "c1", 68 Important: false, 69 SupportedCodecs: []topictypes.Codec{topictypes.CodecRaw, topictypes.CodecGzip}, 70 ReadFrom: time.Date(2022, 9, 11, 10, 1, 2, 0, time.UTC), 71 }, 72 { 73 Name: "c2", 74 SupportedCodecs: []topictypes.Codec{}, 75 ReadFrom: time.Date(2021, 1, 2, 3, 4, 5, 0, time.UTC), 76 }, 77 } 78 ) 79 80 _ = db.Topic().Drop(ctx, topicName) 81 err := db.Topic().Create(ctx, topicName, 82 topicoptions.CreateWithSupportedCodecs(supportedCodecs...), 83 topicoptions.CreateWithMinActivePartitions(minActivePartitions), 84 // topicoptions.CreateWithPartitionCountLimit(partitionCountLimit), LOGBROKER-7800 85 topicoptions.CreateWithRetentionPeriod(retentionPeriod), 86 // topicoptions.CreateWithRetentionStorageMB(...) - incompatible with retention period 87 topicoptions.CreateWithPartitionWriteSpeedBytesPerSecond(writeSpeed), 88 topicoptions.CreateWithPartitionWriteBurstBytes(burstBytes), 89 topicoptions.CreateWithConsumer(consumers...), 90 // topicoptions.CreateWithMeteringMode(topictypes.MeteringModeRequestUnits), - work with serverless only 91 ) 92 require.NoError(t, err) 93 94 topicDesc, err := db.Topic().Describe(ctx, topicName) 95 require.NoError(t, err) 96 97 expected := topictypes.TopicDescription{ 98 Path: topicName, 99 PartitionSettings: topictypes.PartitionSettings{ 100 MinActivePartitions: minActivePartitions, 101 // PartitionCountLimit: partitionCountLimit, LOGBROKER-7800 102 }, 103 Partitions: []topictypes.PartitionInfo{ 104 { 105 PartitionID: 0, 106 Active: true, 107 }, 108 { 109 PartitionID: 1, 110 Active: true, 111 }, 112 }, 113 RetentionPeriod: retentionPeriod, 114 RetentionStorageMB: 0, 115 SupportedCodecs: supportedCodecs, 116 PartitionWriteBurstBytes: burstBytes, 117 PartitionWriteSpeedBytesPerSecond: writeSpeed, 118 Attributes: nil, 119 Consumers: consumers, 120 MeteringMode: topictypes.MeteringModeUnspecified, 121 } 122 123 requireAndCleanSubset := func(checked *map[string]string, subset *map[string]string) { 124 t.Helper() 125 for k, subValue := range *subset { 126 checkedValue, ok := (*checked)[k] 127 require.True(t, ok, k) 128 require.Equal(t, subValue, checkedValue) 129 } 130 *checked = nil 131 *subset = nil 132 } 133 134 requireAndCleanSubset(&topicDesc.Attributes, &expected.Attributes) 135 136 for i := range expected.Consumers { 137 requireAndCleanSubset(&topicDesc.Consumers[i].Attributes, &expected.Consumers[i].Attributes) 138 } 139 140 require.Equal(t, expected, topicDesc) 141 } 142 143 func TestDescribeTopicConsumer(t *testing.T) { 144 ctx := xtest.Context(t) 145 db := connect(t) 146 topicName := "test-topic-" + t.Name() 147 setImportant := true 148 if os.Getenv("YDB_VERSION") != "nightly" && version.Lt(os.Getenv("YDB_VERSION"), "24.1.1") { 149 setImportant = false 150 } 151 var ( 152 supportedCodecs = []topictypes.Codec{topictypes.CodecRaw, topictypes.CodecGzip} 153 minActivePartitions = int64(2) 154 consumers = []topictypes.Consumer{ 155 { 156 Name: "c1", 157 Important: setImportant, 158 SupportedCodecs: []topictypes.Codec{topictypes.CodecRaw, topictypes.CodecGzip}, 159 ReadFrom: time.Date(2022, 9, 11, 10, 1, 2, 0, time.UTC), 160 }, 161 { 162 Name: "c2", 163 Important: false, 164 SupportedCodecs: []topictypes.Codec{}, 165 ReadFrom: time.Date(2021, 1, 2, 3, 4, 5, 0, time.UTC), 166 }, 167 } 168 ) 169 170 _ = db.Topic().Drop(ctx, topicName) 171 err := db.Topic().Create(ctx, topicName, 172 topicoptions.CreateWithSupportedCodecs(supportedCodecs...), 173 topicoptions.CreateWithMinActivePartitions(minActivePartitions), 174 topicoptions.CreateWithConsumer(consumers...), 175 ) 176 require.NoError(t, err) 177 178 consumer, err := db.Topic().DescribeTopicConsumer(ctx, topicName, "c1", topicoptions.IncludeConsumerStats()) 179 require.NoError(t, err) 180 181 zeroTime := time.Time{} 182 zeroDuration := time.Duration(0) 183 expectedConsumerDesc := topictypes.TopicConsumerDescription{ 184 Path: path.Join(topicName, "c1"), 185 Consumer: topictypes.Consumer{ 186 Name: "c1", 187 Important: setImportant, 188 SupportedCodecs: []topictypes.Codec{topictypes.CodecRaw, topictypes.CodecGzip}, 189 ReadFrom: time.Date(2022, 9, 11, 10, 1, 2, 0, time.UTC), 190 Attributes: map[string]string{ 191 "_service_type": "data-streams", 192 }, 193 }, 194 Partitions: []topictypes.DescribeConsumerPartitionInfo{ 195 { 196 PartitionID: 0, 197 Active: true, 198 PartitionStats: topictypes.PartitionStats{ 199 PartitionsOffset: topictypes.OffsetRange{}, 200 StoreSizeBytes: 0, 201 LastWriteTime: nil, 202 MaxWriteTimeLag: &zeroDuration, 203 BytesWritten: topictypes.MultipleWindowsStat{}, 204 }, 205 PartitionConsumerStats: topictypes.PartitionConsumerStats{ 206 LastReadOffset: 0, 207 CommittedOffset: 0, 208 ReadSessionID: "", 209 PartitionReadSessionCreateTime: &zeroTime, 210 LastReadTime: &zeroTime, 211 MaxReadTimeLag: &zeroDuration, 212 MaxWriteTimeLag: &zeroDuration, 213 BytesRead: topictypes.MultipleWindowsStat{}, 214 ReaderName: "", 215 }, 216 }, 217 { 218 PartitionID: 1, 219 Active: true, 220 PartitionStats: topictypes.PartitionStats{ 221 PartitionsOffset: topictypes.OffsetRange{}, 222 StoreSizeBytes: 0, 223 LastWriteTime: nil, 224 MaxWriteTimeLag: &zeroDuration, 225 BytesWritten: topictypes.MultipleWindowsStat{}, 226 }, 227 PartitionConsumerStats: topictypes.PartitionConsumerStats{ 228 LastReadOffset: 0, 229 CommittedOffset: 0, 230 ReadSessionID: "", 231 PartitionReadSessionCreateTime: &zeroTime, 232 LastReadTime: &zeroTime, 233 MaxReadTimeLag: &zeroDuration, 234 MaxWriteTimeLag: &zeroDuration, 235 BytesRead: topictypes.MultipleWindowsStat{}, 236 ReaderName: "", 237 }, 238 }, 239 }, 240 } 241 242 requireAndCleanSubset := func(checked *map[string]string, subset *map[string]string) { 243 t.Helper() 244 for k, subValue := range *subset { 245 checkedValue, ok := (*checked)[k] 246 require.True(t, ok, k) 247 require.Equal(t, subValue, checkedValue) 248 } 249 *checked = nil 250 *subset = nil 251 } 252 253 requireAndCleanSubset(&consumer.Consumer.Attributes, &expectedConsumerDesc.Consumer.Attributes) 254 255 require.Equal(t, expectedConsumerDesc, consumer) 256 } 257 258 func TestSchemeList(t *testing.T) { 259 ctx := xtest.Context(t) 260 db := connect(t) 261 262 topicPath := createTopic(ctx, t, db) 263 list, err := db.Scheme().ListDirectory(ctx, db.Name()) 264 require.NoError(t, err) 265 266 topicName := path.Base(topicPath) 267 268 hasTopic := false 269 for _, e := range list.Children { 270 if e.IsTopic() && topicName == e.Name { 271 hasTopic = true 272 } 273 } 274 require.True(t, hasTopic) 275 } 276 277 func TestReaderWithoutConsumer(t *testing.T) { 278 t.Run("OK", func(t *testing.T) { 279 if version.Lt(os.Getenv("YDB_VERSION"), "24.1") { 280 t.Skip("Read topic without consumer implemented since YDB 24.1, test ran for '" + os.Getenv("YDB_VERSION") + "'") 281 } 282 scope := newScope(t) 283 ctx := scope.Ctx 284 285 reader1, err := scope.Driver().Topic().StartReader( 286 "", 287 topicoptions.ReadSelectors{ 288 { 289 Path: scope.TopicPath(), 290 Partitions: []int64{0}, 291 }, 292 }, 293 topicoptions.WithReaderWithoutConsumer(false), 294 ) 295 require.NoError(t, err) 296 297 reader2, err := scope.Driver().Topic().StartReader( 298 "", 299 topicoptions.ReadSelectors{ 300 { 301 Path: scope.TopicPath(), 302 Partitions: []int64{0}, 303 }, 304 }, 305 topicoptions.WithReaderWithoutConsumer(false), 306 ) 307 require.NoError(t, err) 308 309 err = scope.TopicWriter().Write(ctx, topicwriter.Message{Data: strings.NewReader("123")}) 310 require.NoError(t, err) 311 312 msg1, err := reader1.ReadMessage(ctx) 313 require.NoError(t, err) 314 require.Equal(t, int64(1), msg1.SeqNo) 315 316 msg1data, err := io.ReadAll(msg1) 317 require.NoError(t, err) 318 require.Equal(t, "123", string(msg1data)) 319 320 msg2, err := reader2.ReadMessage(ctx) 321 require.NoError(t, err) 322 require.Equal(t, int64(1), msg2.SeqNo) 323 324 msg2data, err := io.ReadAll(msg2) 325 require.NoError(t, err) 326 require.Equal(t, "123", string(msg2data)) 327 328 _ = reader1.Close(ctx) 329 _ = reader2.Close(ctx) 330 }) 331 t.Run("NoNameNoOptionErr", func(t *testing.T) { 332 scope := newScope(t) 333 topicReader, err := scope.Driver().Topic().StartReader("", topicoptions.ReadTopic(scope.TopicPath())) 334 require.Error(t, err) 335 require.Nil(t, topicReader) 336 }) 337 t.Run("NameAndOption", func(t *testing.T) { 338 scope := newScope(t) 339 topicReader, err := scope.Driver().Topic().StartReader( 340 scope.TopicConsumerName(), 341 topicoptions.ReadTopic(scope.TopicPath()), 342 topicoptions.WithReaderWithoutConsumer(false), 343 ) 344 require.Error(t, err) 345 require.Nil(t, topicReader) 346 }) 347 } 348 349 func connect(t testing.TB, opts ...ydb.Option) *ydb.Driver { 350 return connectWithLogOption(t, false, opts...) 351 } 352 353 func connectWithGrpcLogging(t testing.TB, opts ...ydb.Option) *ydb.Driver { 354 return connectWithLogOption(t, true, opts...) 355 } 356 357 func connectWithLogOption(t testing.TB, logGRPC bool, opts ...ydb.Option) *ydb.Driver { 358 connectionString := defaultConnectionString 359 if cs := os.Getenv("YDB_CONNECTION_STRING"); cs != "" { 360 connectionString = cs 361 } 362 363 var grpcOptions []grpc.DialOption 364 const needLogGRPCMessages = true 365 if logGRPC { 366 grpcOptions = append(grpcOptions, 367 grpc.WithChainUnaryInterceptor(xtest.NewGrpcLogger(t).UnaryClientInterceptor), 368 grpc.WithChainStreamInterceptor(xtest.NewGrpcLogger(t).StreamClientInterceptor), 369 ) 370 } 371 372 ydbOpts := []ydb.Option{ 373 ydb.WithDialTimeout(time.Second), 374 ydb.WithAccessTokenCredentials(os.Getenv("YDB_ACCESS_TOKEN_CREDENTIALS")), 375 ydb.With(config.WithGrpcOptions(grpcOptions...)), 376 } 377 ydbOpts = append(ydbOpts, opts...) 378 379 db, err := ydb.Open(context.Background(), connectionString, ydbOpts...) 380 require.NoError(t, err) 381 382 t.Cleanup(func() { 383 _ = db.Close(context.Background()) 384 }) 385 return db 386 }