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 }