github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/version/check.go (about)

     1  // Copyright 2021 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  	"encoding/json"
    19  	"fmt"
    20  	"io"
    21  	"regexp"
    22  	"strings"
    23  	"time"
    24  
    25  	"github.com/coreos/go-semver/semver"
    26  	"github.com/pingcap/errors"
    27  	"github.com/pingcap/kvproto/pkg/metapb"
    28  	"github.com/pingcap/log"
    29  	"github.com/pingcap/tidb/pkg/util/engine"
    30  	cerror "github.com/pingcap/tiflow/pkg/errors"
    31  	"github.com/pingcap/tiflow/pkg/httputil"
    32  	"github.com/pingcap/tiflow/pkg/retry"
    33  	"github.com/pingcap/tiflow/pkg/security"
    34  	pd "github.com/tikv/pd/client"
    35  	"go.uber.org/zap"
    36  )
    37  
    38  var (
    39  	// minPDVersion is the version of the minimal compatible PD.
    40  	// The min version should be 7.x because we adapt to tidb concurrency ddl implementations.
    41  	minPDVersion = semver.New("7.1.0-alpha")
    42  	// maxPDVersion is the version of the maximum compatible PD.
    43  	// Compatible versions are in [minPDVersion, maxPDVersion)
    44  	maxPDVersion = semver.New("10.0.0")
    45  
    46  	// MinTiKVVersion is the version of the minimal compatible TiKV.
    47  	// The min version should be 7.x because we adapt to tidb concurrency ddl implementations.
    48  	MinTiKVVersion = semver.New("7.1.0-alpha")
    49  	// maxTiKVVersion is the version of the maximum compatible TiKV.
    50  	// Compatible versions are in [MinTiKVVersion, maxTiKVVersion)
    51  	maxTiKVVersion = semver.New("10.0.0")
    52  
    53  	// CaptureInfo.Version is added since v4.0.11,
    54  	// we use the minimal release version as default.
    55  	defaultTiCDCVersion = semver.New("4.0.1")
    56  
    57  	// MinTiCDCVersion is the version of the minimal allowed TiCDC version.
    58  	MinTiCDCVersion = semver.New("6.3.0-alpha")
    59  	// MaxTiCDCVersion is the version of the maximum allowed TiCDC version.
    60  	// for version `x.y.z`, max allowed `x+2.0.0`
    61  	MaxTiCDCVersion = semver.New("10.0.0-alpha")
    62  )
    63  
    64  var versionHash = regexp.MustCompile("-[0-9]+-g[0-9a-f]{7,}(-dev)?")
    65  
    66  // SanitizeVersion remove the prefix "v" and suffix git hash.
    67  func SanitizeVersion(v string) string {
    68  	if v == "" {
    69  		return v
    70  	}
    71  	v = versionHash.ReplaceAllLiteralString(v, "")
    72  	v = strings.TrimSuffix(v, "-fips")
    73  	v = strings.TrimSuffix(v, "-dirty")
    74  	return strings.TrimPrefix(v, "v")
    75  }
    76  
    77  var checkClusterVersionRetryTimes = 10
    78  
    79  // CheckClusterVersion check TiKV and PD version.
    80  // need only one PD alive and match the cdc version.
    81  func CheckClusterVersion(
    82  	ctx context.Context, client pd.Client, pdAddrs []string,
    83  	credential *security.Credential, errorTiKVIncompat bool,
    84  ) error {
    85  	err := CheckStoreVersion(ctx, client, 0 /* check all TiKV */)
    86  	if err != nil {
    87  		if errorTiKVIncompat {
    88  			return err
    89  		}
    90  		log.Warn("check TiKV version failed", zap.Error(err))
    91  	}
    92  
    93  	for _, pdAddr := range pdAddrs {
    94  		// check pd version with retry, if the pdAddr is a service or lb address
    95  		// the http client may connect to an unhealthy PD that returns 503
    96  		err = retry.Do(ctx, func() error {
    97  			return checkPDVersion(ctx, pdAddr, credential)
    98  		}, retry.WithBackoffBaseDelay(time.Millisecond.Milliseconds()*10),
    99  			retry.WithBackoffMaxDelay(time.Second.Milliseconds()),
   100  			retry.WithMaxTries(uint64(checkClusterVersionRetryTimes)),
   101  			retry.WithIsRetryableErr(cerror.IsRetryableError))
   102  		if err == nil {
   103  			break
   104  		}
   105  	}
   106  
   107  	return err
   108  }
   109  
   110  // CheckTiCDCVersion return true if all cdc instance have valid semantic version
   111  // the whole cluster only allow at most 2 different version instances
   112  // and should in the range [MinTiCDCVersion, MaxTiCDCVersion)
   113  func CheckTiCDCVersion(versions map[string]struct{}) error {
   114  	if len(versions) <= 1 {
   115  		return nil
   116  	}
   117  	if len(versions) >= 3 {
   118  		arg := fmt.Sprintf("all running cdc instance belong to %d different versions, "+
   119  			"it's not allowed", len(versions))
   120  		return cerror.ErrVersionIncompatible.GenWithStackByArgs(arg)
   121  	}
   122  
   123  	ver := &semver.Version{}
   124  	for v := range versions {
   125  		if err := ver.Set(SanitizeVersion(v)); err != nil {
   126  			return cerror.WrapError(cerror.ErrNewSemVersion, err)
   127  		}
   128  		if ver.Compare(*MinTiCDCVersion) < 0 {
   129  			arg := fmt.Sprintf("TiCDC %s is not supported, the minimal compatible version is %s",
   130  				SanitizeVersion(v), MinTiCDCVersion)
   131  			return cerror.ErrVersionIncompatible.GenWithStackByArgs(arg)
   132  		}
   133  		if ver.Compare(*MaxTiCDCVersion) >= 0 {
   134  			arg := fmt.Sprintf("TiCDC %s is not supported, only support version less than %s",
   135  				SanitizeVersion(v), MaxTiCDCVersion)
   136  			return cerror.ErrVersionIncompatible.GenWithStackByArgs(arg)
   137  		}
   138  	}
   139  	return nil
   140  }
   141  
   142  // checkPDVersion check PD version.
   143  func checkPDVersion(ctx context.Context, pdAddr string, credential *security.Credential) error {
   144  	// See more: https://github.com/pingcap/pd/blob/v4.0.0-rc.1/server/api/version.go
   145  	pdVer := struct {
   146  		Version string `json:"version"`
   147  	}{}
   148  
   149  	httpClient, err := httputil.NewClient(credential)
   150  	if err != nil {
   151  		return err
   152  	}
   153  
   154  	ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
   155  	defer cancel()
   156  	resp, err := httpClient.Get(ctx, fmt.Sprintf("%s/pd/api/v1/version", pdAddr))
   157  	if err != nil {
   158  		return cerror.ErrCheckClusterVersionFromPD.GenWithStackByArgs(err)
   159  	}
   160  	defer resp.Body.Close()
   161  
   162  	content, err := io.ReadAll(resp.Body)
   163  	if err != nil || resp.StatusCode < 200 || resp.StatusCode >= 300 {
   164  		var arg string
   165  		if err != nil {
   166  			arg = fmt.Sprintf("%s %s %s", resp.Status, content, err)
   167  		} else {
   168  			arg = fmt.Sprintf("%s %s", resp.Status, content)
   169  		}
   170  		return cerror.ErrCheckClusterVersionFromPD.GenWithStackByArgs(arg)
   171  	}
   172  
   173  	err = json.Unmarshal(content, &pdVer)
   174  	if err != nil {
   175  		return cerror.ErrCheckClusterVersionFromPD.GenWithStackByArgs(err)
   176  	}
   177  
   178  	ver, err := semver.NewVersion(SanitizeVersion(pdVer.Version))
   179  	if err != nil {
   180  		err = errors.Annotate(err, "invalid PD version")
   181  		return cerror.WrapError(cerror.ErrNewSemVersion, err)
   182  	}
   183  
   184  	minOrd := ver.Compare(*minPDVersion)
   185  	if minOrd < 0 {
   186  		arg := fmt.Sprintf("PD %s is not supported, the minimal compatible version is %s",
   187  			SanitizeVersion(pdVer.Version), minPDVersion)
   188  		return cerror.ErrVersionIncompatible.GenWithStackByArgs(arg)
   189  	}
   190  	maxOrd := ver.Compare(*maxPDVersion)
   191  	if maxOrd >= 0 {
   192  		arg := fmt.Sprintf("PD %s is not supported, only support version less than %s",
   193  			SanitizeVersion(pdVer.Version), maxPDVersion)
   194  		return cerror.ErrVersionIncompatible.GenWithStackByArgs(arg)
   195  	}
   196  	return nil
   197  }
   198  
   199  // CheckStoreVersion checks whether the given TiKV is compatible with this CDC.
   200  // If storeID is 0, it checks all TiKV.
   201  func CheckStoreVersion(ctx context.Context, client pd.Client, storeID uint64) error {
   202  	var stores []*metapb.Store
   203  	var err error
   204  	if storeID == 0 {
   205  		stores, err = client.GetAllStores(ctx, pd.WithExcludeTombstone())
   206  	} else {
   207  		stores = make([]*metapb.Store, 1)
   208  		stores[0], err = client.GetStore(ctx, storeID)
   209  	}
   210  	if err != nil {
   211  		return cerror.WrapError(cerror.ErrGetAllStoresFailed, err)
   212  	}
   213  
   214  	for _, s := range stores {
   215  		if engine.IsTiFlash(s) {
   216  			continue
   217  		}
   218  
   219  		ver, err := semver.NewVersion(SanitizeVersion(s.Version))
   220  		if err != nil {
   221  			err = errors.Annotate(err, "invalid TiKV version")
   222  			return cerror.WrapError(cerror.ErrNewSemVersion, err)
   223  		}
   224  		minOrd := ver.Compare(*MinTiKVVersion)
   225  		if minOrd < 0 {
   226  			arg := fmt.Sprintf("TiKV %s is not supported, the minimal compatible version is %s",
   227  				SanitizeVersion(s.Version), MinTiKVVersion)
   228  			return cerror.ErrVersionIncompatible.GenWithStackByArgs(arg)
   229  		}
   230  		maxOrd := ver.Compare(*maxTiKVVersion)
   231  		if maxOrd >= 0 {
   232  			arg := fmt.Sprintf("TiKV %s is not supported, only support version less than %s",
   233  				SanitizeVersion(s.Version), maxTiKVVersion)
   234  			return cerror.ErrVersionIncompatible.GenWithStackByArgs(arg)
   235  		}
   236  	}
   237  	return nil
   238  }
   239  
   240  // TiCDCClusterVersion is the version of TiCDC cluster
   241  type TiCDCClusterVersion struct {
   242  	*semver.Version
   243  }
   244  
   245  // LessThan500RC returns true if th cluster version is less than 5.0.0-rc
   246  func (v *TiCDCClusterVersion) LessThan500RC() bool {
   247  	// we assume the unknown version to be the latest version
   248  	return v.Version == nil || !v.LessThan(*semver.New("5.0.0-rc"))
   249  }
   250  
   251  // ShouldEnableUnifiedSorterByDefault returns whether Unified Sorter should be enabled by default
   252  func (v *TiCDCClusterVersion) ShouldEnableUnifiedSorterByDefault() bool {
   253  	if v.Version == nil {
   254  		// we assume the unknown version to be the latest version
   255  		return true
   256  	}
   257  	// x >= 4.0.13 AND x != 5.0.0-rc
   258  	if v.String() == "5.0.0-rc" {
   259  		return false
   260  	}
   261  	return !v.LessThan(*semver.New("4.0.13")) || (v.Major == 4 && v.Minor == 0 && v.Patch == 13)
   262  }
   263  
   264  // ShouldRunCliWithOpenAPI returns whether to run cmd cli with open api
   265  func (v *TiCDCClusterVersion) ShouldRunCliWithOpenAPI() bool {
   266  	// we assume the unknown version to be the latest version
   267  	if v.Version == nil {
   268  		return true
   269  	}
   270  
   271  	return !v.LessThan(*semver.New("6.2.0")) || (v.Major == 6 && v.Minor == 2 && v.Patch == 0)
   272  }
   273  
   274  // ticdcClusterVersionUnknown is a read-only variable to represent the unknown cluster version
   275  var ticdcClusterVersionUnknown = TiCDCClusterVersion{}
   276  
   277  // GetTiCDCClusterVersion returns the version of ticdc cluster
   278  func GetTiCDCClusterVersion(captureVersion []string) (TiCDCClusterVersion, error) {
   279  	if len(captureVersion) == 0 {
   280  		return ticdcClusterVersionUnknown, nil
   281  	}
   282  	var minVer *semver.Version
   283  	for _, versionStr := range captureVersion {
   284  		var ver *semver.Version
   285  		var err error
   286  		if versionStr != "" {
   287  			ver, err = semver.NewVersion(SanitizeVersion(versionStr))
   288  		} else {
   289  			ver = defaultTiCDCVersion
   290  		}
   291  		if err != nil {
   292  			err = errors.Annotate(err, "invalid CDC cluster version")
   293  			return ticdcClusterVersionUnknown, cerror.WrapError(cerror.ErrNewSemVersion, err)
   294  		}
   295  		if minVer == nil || ver.Compare(*minVer) < 0 {
   296  			minVer = ver
   297  		}
   298  	}
   299  	return TiCDCClusterVersion{minVer}, nil
   300  }