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  }