github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/version/check_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 version 15 16 import ( 17 "context" 18 "fmt" 19 "net/http" 20 "net/http/httptest" 21 "net/url" 22 "testing" 23 "time" 24 25 "github.com/coreos/go-semver/semver" 26 "github.com/pingcap/kvproto/pkg/metapb" 27 "github.com/pingcap/tidb/pkg/util/engine" 28 "github.com/pingcap/tiflow/pkg/httputil" 29 "github.com/stretchr/testify/assert" 30 "github.com/stretchr/testify/require" 31 pd "github.com/tikv/pd/client" 32 "github.com/tikv/pd/pkg/utils/tempurl" 33 ) 34 35 type mockPDClient struct { 36 pd.Client 37 getAllStores func() []*metapb.Store 38 getPDVersion func() string 39 getStatusCode func() int 40 } 41 42 func (m *mockPDClient) GetAllStores(ctx context.Context, opts ...pd.GetStoreOption) ([]*metapb.Store, error) { 43 if m.getAllStores != nil { 44 return m.getAllStores(), nil 45 } 46 return []*metapb.Store{}, nil 47 } 48 49 func (m *mockPDClient) ServeHTTP(resp http.ResponseWriter, _ *http.Request) { 50 // set status code at first, else will not work 51 if m.getStatusCode != nil { 52 resp.WriteHeader(m.getStatusCode()) 53 } 54 55 if m.getPDVersion != nil { 56 _, _ = resp.Write([]byte(fmt.Sprintf(`{"version":"%s"}`, m.getPDVersion()))) 57 } 58 } 59 60 func TestCheckClusterVersion(t *testing.T) { 61 t.Parallel() 62 mock := mockPDClient{ 63 Client: nil, 64 } 65 pdURL, _ := url.Parse(tempurl.Alloc()) 66 pdAddr := fmt.Sprintf("http://%s", pdURL.Host) 67 pdAddrs := []string{pdAddr} 68 srv := http.Server{Addr: pdURL.Host, Handler: &mock} 69 go func() { 70 //nolint:errcheck 71 srv.ListenAndServe() 72 }() 73 defer srv.Close() 74 cli, err := httputil.NewClient(nil) 75 require.Nil(t, err) 76 for i := 0; i < 20; i++ { 77 time.Sleep(100 * time.Millisecond) 78 resp, err := cli.Get(context.Background(), pdAddr) 79 if err == nil { 80 _ = resp.Body.Close() 81 break 82 } 83 84 assert.Failf(t, "http.Get fail, need retry", "%v", err) 85 if i == 19 { 86 require.FailNowf(t, "TestCheckClusterVersion fail", "http server timeout:%v", err) 87 } 88 } 89 90 // Check min pd / tikv version 91 { 92 mock.getPDVersion = func() string { 93 return minPDVersion.String() 94 } 95 mock.getAllStores = func() []*metapb.Store { 96 return []*metapb.Store{{Version: MinTiKVVersion.String()}} 97 } 98 err := CheckClusterVersion(context.Background(), &mock, pdAddrs, nil, true) 99 require.Nil(t, err) 100 } 101 102 // check pd / tikv with the maximum allowed version 103 { 104 mock.getPDVersion = func() string { 105 return "7.9.9" 106 } 107 mock.getAllStores = func() []*metapb.Store { 108 return []*metapb.Store{{Version: "v7.9.9"}} 109 } 110 err := CheckClusterVersion(context.Background(), &mock, pdAddrs, nil, true) 111 require.Nil(t, err) 112 } 113 114 // Check invalid PD/TiKV version. 115 { 116 mock.getPDVersion = func() string { 117 return "testPD" 118 } 119 mock.getAllStores = func() []*metapb.Store { 120 return []*metapb.Store{{Version: MinTiKVVersion.String()}} 121 } 122 err := CheckClusterVersion(context.Background(), &mock, pdAddrs, nil, true) 123 require.Regexp(t, ".*invalid PD version.*", err) 124 } 125 126 { 127 mock.getPDVersion = func() string { 128 return minPDVersion.String() 129 } 130 mock.getAllStores = func() []*metapb.Store { 131 return []*metapb.Store{{Version: "testKV"}} 132 } 133 err := CheckClusterVersion(context.Background(), &mock, pdAddrs, nil, true) 134 require.Regexp(t, ".*invalid TiKV version.*", err) 135 } 136 137 // pd version lower than the min supported 138 { 139 mock.getPDVersion = func() string { 140 return `v1.0.0-alpha-271-g824ae7fd` 141 } 142 mock.getAllStores = func() []*metapb.Store { 143 return []*metapb.Store{{Version: MinTiKVVersion.String()}} 144 } 145 err := CheckClusterVersion(context.Background(), &mock, pdAddrs, nil, true) 146 require.Regexp(t, ".*PD .* is not supported.*", err) 147 } 148 149 { 150 mock.getPDVersion = func() string { 151 return maxPDVersion.String() 152 } 153 mock.getAllStores = func() []*metapb.Store { 154 return []*metapb.Store{{Version: MinTiKVVersion.String()}} 155 } 156 err := CheckClusterVersion(context.Background(), &mock, pdAddrs, nil, true) 157 require.Regexp(t, ".*PD .* is not supported.*", err) 158 } 159 160 // tikv version lower than the min supported 161 { 162 mock.getPDVersion = func() string { 163 return minPDVersion.String() 164 } 165 mock.getAllStores = func() []*metapb.Store { 166 // TiKV does not include 'v'. 167 return []*metapb.Store{{Version: `1.0.0-alpha-271-g824ae7fd`}} 168 } 169 err := CheckClusterVersion(context.Background(), &mock, pdAddrs, nil, true) 170 require.Regexp(t, ".*TiKV .* is not supported.*", err) 171 err = CheckClusterVersion(context.Background(), &mock, pdAddrs, nil, false) 172 require.Nil(t, err) 173 } 174 175 // Skip checking TiFlash. 176 { 177 mock.getPDVersion = func() string { 178 return minPDVersion.String() 179 } 180 181 tiflashStore := &metapb.Store{ 182 Version: maxTiKVVersion.String(), 183 Labels: []*metapb.StoreLabel{{Key: "engine", Value: "tiflash"}}, 184 } 185 require.True(t, engine.IsTiFlash(tiflashStore)) 186 mock.getAllStores = func() []*metapb.Store { 187 return []*metapb.Store{tiflashStore} 188 } 189 err := CheckClusterVersion(context.Background(), &mock, pdAddrs, nil, true) 190 require.Nil(t, err) 191 } 192 193 // Check maximum supported TiKV version 194 { 195 mock.getPDVersion = func() string { 196 return minPDVersion.String() 197 } 198 199 mock.getAllStores = func() []*metapb.Store { 200 return []*metapb.Store{{Version: maxTiKVVersion.String()}} 201 } 202 err := CheckClusterVersion(context.Background(), &mock, pdAddrs, nil, true) 203 require.Regexp(t, ".*TiKV .* is not supported.*", err) 204 } 205 206 { 207 mock.getStatusCode = func() int { 208 return http.StatusBadRequest 209 } 210 211 err := CheckClusterVersion(context.Background(), &mock, pdAddrs, nil, false) 212 require.Regexp(t, ".*400 Bad Request.*", err) 213 } 214 215 // check retry success 216 { 217 retryTimes := 0 218 mock.getStatusCode = func() int { 219 if retryTimes < 4 { 220 retryTimes++ 221 return http.StatusServiceUnavailable 222 } 223 return http.StatusOK 224 } 225 226 mock.getPDVersion = func() string { 227 return "7.9.9" 228 } 229 mock.getAllStores = func() []*metapb.Store { 230 return []*metapb.Store{{Version: "v7.9.9"}} 231 } 232 err := CheckClusterVersion(context.Background(), &mock, pdAddrs, nil, true) 233 require.Nil(t, err) 234 } 235 236 // check retry failed 237 { 238 retryTimes := 0 239 mock.getStatusCode = func() int { 240 if retryTimes < checkClusterVersionRetryTimes { 241 retryTimes++ 242 return http.StatusBadRequest 243 } 244 return http.StatusOK 245 } 246 247 err := CheckClusterVersion(context.Background(), &mock, pdAddrs, nil, false) 248 require.Regexp(t, ".*400 Bad Request.*", err) 249 } 250 } 251 252 func TestCompareVersion(t *testing.T) { 253 // build on master branch, `vx.y.z-master` 254 masterVersion := semver.New(SanitizeVersion("v6.3.0-master")) 255 require.Equal(t, 1, masterVersion.Compare(*MinTiCDCVersion)) 256 257 // pre-release version, `vx.y.z-alpha-nightly-yyyymmdd` 258 alphaVersion := semver.New(SanitizeVersion("v6.3.0-alpha-nightly-20220202")) 259 require.Equal(t, 1, alphaVersion.Compare(*MinTiCDCVersion)) 260 261 // release version, `vx.y.z.` 262 releaseVersion := semver.New(SanitizeVersion("v6.3.0")) 263 require.Equal(t, 1, releaseVersion.Compare(*MinTiCDCVersion)) 264 265 // build with uncommitted changes, `vx.y.z-dirty` 266 dirtyVersion := semver.New(SanitizeVersion("v6.3.0-dirty")) 267 require.Equal(t, 1, dirtyVersion.Compare(*MinTiCDCVersion)) 268 require.Equal(t, 0, dirtyVersion.Compare(*semver.New("6.3.0"))) 269 270 dirtyVersionWithFIPS := semver.New(SanitizeVersion("v6.3.0-dirty-fips")) 271 require.Equal(t, 1, dirtyVersionWithFIPS.Compare(*MinTiCDCVersion)) 272 require.Equal(t, 0, dirtyVersionWithFIPS.Compare(*semver.New("6.3.0"))) 273 } 274 275 func TestReleaseSemver(t *testing.T) { 276 cases := []struct{ releaseVersion, releaseSemver string }{ 277 {"None", ""}, 278 {"HEAD", ""}, 279 {"v4.0.5", "4.0.5"}, 280 {"v4.0.2-152-g62d7075-dev", "4.0.2"}, 281 } 282 283 for _, cs := range cases { 284 ReleaseVersion = cs.releaseVersion 285 require.Equal(t, ReleaseSemver(), cs.releaseSemver, "%v", cs) 286 } 287 } 288 289 func TestGetTiCDCClusterVersion(t *testing.T) { 290 t.Parallel() 291 testCases := []struct { 292 captureVersions []string 293 expected TiCDCClusterVersion 294 }{ 295 { 296 captureVersions: []string{}, 297 expected: ticdcClusterVersionUnknown, 298 }, 299 { 300 captureVersions: []string{ 301 "", 302 "", 303 "", 304 }, 305 expected: TiCDCClusterVersion{defaultTiCDCVersion}, 306 }, 307 { 308 captureVersions: []string{ 309 "5.0.1", 310 "4.0.7", 311 "5.0.0-rc", 312 }, 313 expected: TiCDCClusterVersion{semver.New("4.0.7")}, 314 }, 315 { 316 captureVersions: []string{ 317 "5.0.0-rc", 318 }, 319 expected: TiCDCClusterVersion{semver.New("5.0.0-rc")}, 320 }, 321 { 322 captureVersions: []string{ 323 "5.0.0", 324 }, 325 expected: TiCDCClusterVersion{semver.New("5.0.0")}, 326 }, 327 { 328 captureVersions: []string{ 329 "4.1.0", 330 }, 331 expected: TiCDCClusterVersion{semver.New("4.1.0")}, 332 }, 333 { 334 captureVersions: []string{ 335 "4.0.10", 336 }, 337 expected: TiCDCClusterVersion{semver.New("4.0.10")}, 338 }, 339 } 340 for _, tc := range testCases { 341 ver, err := GetTiCDCClusterVersion(tc.captureVersions) 342 require.Nil(t, err) 343 require.Equal(t, ver, tc.expected) 344 } 345 346 invalidTestCase := struct { 347 captureVersions []string 348 expected TiCDCClusterVersion 349 }{ 350 captureVersions: []string{ 351 "", 352 "testCDC", 353 }, 354 expected: ticdcClusterVersionUnknown, 355 } 356 _, err := GetTiCDCClusterVersion(invalidTestCase.captureVersions) 357 require.Regexp(t, ".*invalid CDC cluster version.*", err) 358 } 359 360 func TestTiCDCClusterVersionFeaturesCompatible(t *testing.T) { 361 t.Parallel() 362 ver := TiCDCClusterVersion{semver.New("4.0.10")} 363 require.Equal(t, ver.ShouldEnableUnifiedSorterByDefault(), false) 364 require.Equal(t, ver.LessThan500RC(), false) 365 366 ver = TiCDCClusterVersion{semver.New("4.0.12")} 367 require.Equal(t, ver.ShouldEnableUnifiedSorterByDefault(), false) 368 require.Equal(t, ver.LessThan500RC(), false) 369 370 ver = TiCDCClusterVersion{semver.New("4.0.13")} 371 require.Equal(t, ver.ShouldEnableUnifiedSorterByDefault(), true) 372 require.Equal(t, ver.LessThan500RC(), false) 373 374 ver = TiCDCClusterVersion{semver.New("4.0.13-hotfix")} 375 require.Equal(t, ver.ShouldEnableUnifiedSorterByDefault(), true) 376 require.Equal(t, ver.LessThan500RC(), false) 377 378 ver = TiCDCClusterVersion{semver.New("4.0.14")} 379 require.Equal(t, ver.ShouldEnableUnifiedSorterByDefault(), true) 380 require.Equal(t, ver.LessThan500RC(), false) 381 382 ver = TiCDCClusterVersion{semver.New("5.0.0-rc")} 383 require.Equal(t, ver.ShouldEnableUnifiedSorterByDefault(), false) 384 require.Equal(t, ver.LessThan500RC(), true) 385 386 ver = TiCDCClusterVersion{semver.New("5.0.0")} 387 require.Equal(t, ver.ShouldEnableUnifiedSorterByDefault(), true) 388 require.Equal(t, ver.LessThan500RC(), true) 389 390 ver = TiCDCClusterVersion{semver.New("5.1.0")} 391 require.Equal(t, ver.ShouldEnableUnifiedSorterByDefault(), true) 392 require.Equal(t, ver.LessThan500RC(), true) 393 394 ver = TiCDCClusterVersion{semver.New("5.2.0-alpha")} 395 require.Equal(t, ver.ShouldEnableUnifiedSorterByDefault(), true) 396 require.Equal(t, ver.LessThan500RC(), true) 397 398 ver = TiCDCClusterVersion{semver.New("5.2.0-master")} 399 require.Equal(t, ver.ShouldEnableUnifiedSorterByDefault(), true) 400 require.Equal(t, ver.LessThan500RC(), true) 401 402 require.Equal(t, ticdcClusterVersionUnknown.ShouldEnableUnifiedSorterByDefault(), true) 403 require.Equal(t, ticdcClusterVersionUnknown.LessThan500RC(), true) 404 } 405 406 func TestCheckPDVersionError(t *testing.T) { 407 t.Parallel() 408 409 var resp func(w http.ResponseWriter, r *http.Request) 410 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 411 resp(w, r) 412 })) 413 defer ts.Close() 414 415 resp = func(w http.ResponseWriter, _ *http.Request) { 416 w.WriteHeader(http.StatusInternalServerError) 417 } 418 require.Contains(t, checkPDVersion(context.TODO(), ts.URL, nil).Error(), 419 "[CDC:ErrCheckClusterVersionFromPD]failed to request PD 500 Internal Server Error , please try again later", 420 ) 421 } 422 423 func TestCheckTiCDCVersion(t *testing.T) { 424 t.Parallel() 425 426 // all captures in the cluster in the same version which is the minimum supported one. 427 versions := map[string]struct{}{ 428 "v6.3.0": {}, 429 } 430 require.NoError(t, CheckTiCDCVersion(versions)) 431 432 // 2 different versions both within the range, it's ok 433 versions = map[string]struct{}{ 434 "v6.3.0": {}, 435 "v6.4.0": {}, 436 } 437 require.NoError(t, CheckTiCDCVersion(versions)) 438 439 versions = map[string]struct{}{ 440 "v6.3.0": {}, 441 "v7.9.9": {}, 442 } 443 err := CheckTiCDCVersion(versions) 444 require.NoError(t, err) 445 446 versions = map[string]struct{}{ 447 "v6.3.0": {}, 448 "v6.4.0": {}, 449 "v6.5.0": {}, 450 } 451 err = CheckTiCDCVersion(versions) 452 require.Regexp(t, ".*all running cdc instance belong to 3 different versions.*", err) 453 454 versions = map[string]struct{}{ 455 "v6.3.0": {}, 456 "v8.0.0-alpha": {}, 457 } 458 err = CheckTiCDCVersion(versions) 459 require.NoError(t, err) 460 461 versions = map[string]struct{}{ 462 "v6.3.0": {}, 463 "v10.0.0-alpha": {}, 464 } 465 err = CheckTiCDCVersion(versions) 466 require.Regexp(t, "TiCDC .* not supported, only support version less than.*", err) 467 468 versions = map[string]struct{}{ 469 "v6.3.0": {}, 470 "v10.0.0": {}, 471 } 472 err = CheckTiCDCVersion(versions) 473 require.Regexp(t, "TiCDC .* not supported, only support version less than.*", err) 474 475 versions = map[string]struct{}{ 476 "v6.3.0": {}, 477 "v6.2.9": {}, 478 } 479 err = CheckTiCDCVersion(versions) 480 require.Regexp(t, "TiCDC .* not supported, the minimal compatible version.*", err) 481 482 versions = map[string]struct{}{ 483 "v6.3.0-master": {}, 484 "v7.0.0": {}, 485 } 486 err = CheckTiCDCVersion(versions) 487 require.NoError(t, err) 488 }