github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/sink/codec/avro/confluent_schema_registry_test.go (about)

     1  // Copyright 2020 PingCAP, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package avro
    15  
    16  import (
    17  	"bytes"
    18  	"context"
    19  	"net/http"
    20  	"sync"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/linkedin/goavro/v2"
    25  	"github.com/stretchr/testify/require"
    26  )
    27  
    28  func getTestingContext() context.Context {
    29  	// nolint:govet
    30  	ctx, _ := context.WithTimeout(context.Background(), time.Second*3)
    31  	return ctx
    32  }
    33  
    34  func TestSchemaRegistry(t *testing.T) {
    35  	startHTTPInterceptForTestingRegistry()
    36  	defer stopHTTPInterceptForTestingRegistry()
    37  
    38  	ctx := getTestingContext()
    39  	manager, err := NewConfluentSchemaManager(ctx, "http://127.0.0.1:8081", nil)
    40  	require.NoError(t, err)
    41  
    42  	topic := "cdctest"
    43  
    44  	err = manager.ClearRegistry(ctx, topic)
    45  	require.NoError(t, err)
    46  
    47  	_, err = manager.Lookup(ctx, topic, schemaID{confluentSchemaID: 1})
    48  	require.Regexp(t, `.*not\sfound.*`, err)
    49  
    50  	codec, err := goavro.NewCodec(`{
    51         "type": "record",
    52         "name": "test",
    53         "fields":
    54           [
    55             {
    56               "type": "string",
    57               "name": "field1"
    58             }
    59            ]
    60       }`)
    61  	require.NoError(t, err)
    62  
    63  	schemaID, err := manager.Register(ctx, topic, codec.Schema())
    64  	require.NoError(t, err)
    65  
    66  	codec2, err := manager.Lookup(ctx, topic, schemaID)
    67  	require.NoError(t, err)
    68  	require.Equal(t, codec.CanonicalSchema(), codec2.CanonicalSchema())
    69  
    70  	codec, err = goavro.NewCodec(`{
    71         "type": "record",
    72         "name": "test",
    73         "fields":
    74           [
    75             {
    76               "type": "string",
    77               "name": "field1"
    78             },
    79             {
    80               "type": [
    81        			"null",
    82        			"string"
    83               ],
    84               "default": null,
    85               "name": "field2"
    86             }
    87            ]
    88       }`)
    89  	require.NoError(t, err)
    90  	schemaID, err = manager.Register(ctx, topic, codec.Schema())
    91  	require.NoError(t, err)
    92  
    93  	codec2, err = manager.Lookup(ctx, topic, schemaID)
    94  	require.NoError(t, err)
    95  	require.Equal(t, codec.CanonicalSchema(), codec2.CanonicalSchema())
    96  }
    97  
    98  func TestSchemaRegistryBad(t *testing.T) {
    99  	startHTTPInterceptForTestingRegistry()
   100  	defer stopHTTPInterceptForTestingRegistry()
   101  
   102  	ctx := getTestingContext()
   103  	_, err := NewConfluentSchemaManager(ctx, "http://127.0.0.1:808", nil)
   104  	require.Error(t, err)
   105  
   106  	_, err = NewConfluentSchemaManager(ctx, "https://127.0.0.1:8080", nil)
   107  	require.Error(t, err)
   108  }
   109  
   110  func TestSchemaRegistryIdempotent(t *testing.T) {
   111  	startHTTPInterceptForTestingRegistry()
   112  	defer stopHTTPInterceptForTestingRegistry()
   113  
   114  	ctx := getTestingContext()
   115  	manager, err := NewConfluentSchemaManager(ctx, "http://127.0.0.1:8081", nil)
   116  	require.NoError(t, err)
   117  
   118  	topic := "cdctest"
   119  
   120  	for i := 0; i < 20; i++ {
   121  		err = manager.ClearRegistry(ctx, topic)
   122  		require.NoError(t, err)
   123  	}
   124  
   125  	codec, err := goavro.NewCodec(`{
   126         "type": "record",
   127         "name": "test",
   128         "fields":
   129           [
   130             {
   131               "type": "string",
   132               "name": "field1"
   133             },
   134             {
   135               "type": [
   136        			"null",
   137        			"string"
   138               ],
   139               "default": null,
   140               "name": "field2"
   141             }
   142            ]
   143       }`)
   144  	require.NoError(t, err)
   145  
   146  	id := 0
   147  	for i := 0; i < 20; i++ {
   148  		id1, err := manager.Register(ctx, topic, codec.Schema())
   149  		require.NoError(t, err)
   150  		require.True(t, id == 0 || id == id1.confluentSchemaID)
   151  		id = id1.confluentSchemaID
   152  	}
   153  }
   154  
   155  func TestGetCachedOrRegister(t *testing.T) {
   156  	startHTTPInterceptForTestingRegistry()
   157  	defer stopHTTPInterceptForTestingRegistry()
   158  
   159  	ctx := getTestingContext()
   160  	manager, err := NewConfluentSchemaManager(ctx, "http://127.0.0.1:8081", nil)
   161  	require.NoError(t, err)
   162  
   163  	called := 0
   164  	// nolint:unparam
   165  	// NOTICE:This is a function parameter definition, so it cannot be modified.
   166  	schemaGen := func() (string, error) {
   167  		called++
   168  		return `{
   169         "type": "record",
   170         "name": "test1",
   171         "fields":
   172           [
   173             {
   174               "type": "string",
   175               "name": "field1"
   176             },
   177             {
   178               "type": [
   179        			"null",
   180        			"string"
   181               ],
   182               "default": null,
   183               "name": "field2"
   184             }
   185            ]
   186       }`, nil
   187  	}
   188  	topic := "cdctest"
   189  
   190  	codec, header, err := manager.GetCachedOrRegister(ctx, topic, 1, schemaGen)
   191  	require.NoError(t, err)
   192  	cID, err := getConfluentSchemaIDFromHeader(header)
   193  	require.NoError(t, err)
   194  	require.Greater(t, cID, uint32(0))
   195  	require.NotNil(t, codec)
   196  	require.Equal(t, 1, called)
   197  
   198  	codec1, _, err := manager.GetCachedOrRegister(ctx, topic, 1, schemaGen)
   199  	require.NoError(t, err)
   200  	require.True(t, codec == codec1) // check identity
   201  	require.Equal(t, 1, called)
   202  
   203  	codec2, _, err := manager.GetCachedOrRegister(ctx, topic, 2, schemaGen)
   204  	require.NoError(t, err)
   205  	require.NotEqual(t, codec, codec2)
   206  	require.Equal(t, 2, called)
   207  
   208  	schemaGen = func() (string, error) {
   209  		return `{
   210         "type": "record",
   211         "name": "test1",
   212         "fields":
   213           [
   214             {
   215               "type": "string",
   216               "name": "field1"
   217             },
   218             {
   219               "type": [
   220        			"null",
   221        			"string"
   222               ],
   223               "default": null,
   224               "name": "field2"
   225             }
   226            ]
   227       }`, nil
   228  	}
   229  
   230  	var wg sync.WaitGroup
   231  	for i := 0; i < 20; i++ {
   232  		finalI := i
   233  		wg.Add(1)
   234  		go func() {
   235  			defer wg.Done()
   236  			for j := 0; j < 100; j++ {
   237  				codec, header, err := manager.GetCachedOrRegister(
   238  					ctx,
   239  					topic,
   240  					uint64(finalI),
   241  					schemaGen,
   242  				)
   243  				require.NoError(t, err)
   244  				cID, err := getConfluentSchemaIDFromHeader(header)
   245  				require.NoError(t, err)
   246  				require.Greater(t, cID, uint32(0))
   247  				require.NotNil(t, codec)
   248  			}
   249  		}()
   250  	}
   251  	wg.Wait()
   252  }
   253  
   254  func TestHTTPRetry(t *testing.T) {
   255  	startHTTPInterceptForTestingRegistry()
   256  	defer stopHTTPInterceptForTestingRegistry()
   257  
   258  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
   259  	defer cancel()
   260  
   261  	payload := []byte("test")
   262  	req, err := http.NewRequestWithContext(ctx,
   263  		"POST", "http://127.0.0.1:8081/may-fail", bytes.NewReader(payload))
   264  	require.NoError(t, err)
   265  
   266  	resp, err := httpRetry(ctx, nil, req)
   267  	require.NoError(t, err)
   268  	require.Equal(t, 200, resp.StatusCode)
   269  	_ = resp.Body.Close()
   270  }