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 }