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  }