github.com/pingcap/tiflow@v0.0.0-20240520035814-5bf52d54e205/pkg/upstream/topo.go (about) 1 // Copyright 2024 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 upstream 15 16 import ( 17 "context" 18 "strconv" 19 "strings" 20 "time" 21 22 "github.com/pingcap/log" 23 "github.com/pingcap/tidb-dashboard/util/distro" 24 "github.com/pingcap/tidb-dashboard/util/netutil" 25 "github.com/pingcap/tidb/pkg/domain/infosync" 26 "github.com/pingcap/tiflow/pkg/errors" 27 clientv3 "go.etcd.io/etcd/client/v3" 28 "go.uber.org/zap" 29 ) 30 31 const ( 32 // topologyTiCDC is /topology/ticdc/{clusterID}/{ip:port}. 33 topologyTiCDC = "/topology/ticdc/%s/%s" 34 // topologyTiDB is /topology/tidb/{ip:port}. 35 // Refer to https://github.com/pingcap/tidb/blob/release-7.5/pkg/domain/infosync/info.go#L78-L79. 36 topologyTiDB = infosync.TopologyInformationPath 37 topologyTiDBTTL = infosync.TopologySessionTTL 38 // defaultTimeout is the default timeout for etcd and mysql operations. 39 defaultTimeout = time.Second * 2 40 ) 41 42 type tidbInstance struct { 43 IP string 44 Port uint 45 } 46 47 // fetchTiDBTopology parses the TiDB topology from etcd. 48 func fetchTiDBTopology(ctx context.Context, etcdClient *clientv3.Client) ([]tidbInstance, error) { 49 ctx2, cancel := context.WithTimeout(ctx, defaultTimeout) 50 defer cancel() 51 52 resp, err := etcdClient.Get(ctx2, topologyTiDB, clientv3.WithPrefix()) 53 if err != nil { 54 return nil, errors.ErrPDEtcdAPIError.Wrap(err) 55 } 56 57 nodesAlive := make(map[string]struct{}, len(resp.Kvs)) 58 nodesInfo := make(map[string]*tidbInstance, len(resp.Kvs)) 59 60 for _, kv := range resp.Kvs { 61 key := string(kv.Key) 62 if !strings.HasPrefix(key, topologyTiDB) { 63 continue 64 } 65 // remainingKey looks like `ip:port/info` or `ip:port/ttl`. 66 remainingKey := strings.TrimPrefix(key[len(topologyTiDB):], "/") 67 keyParts := strings.Split(remainingKey, "/") 68 if len(keyParts) != 2 { 69 log.Warn("Ignored invalid topology key", zap.String("component", distro.R().TiDB), zap.String("key", key)) 70 continue 71 } 72 73 switch keyParts[1] { 74 case "info": 75 address := keyParts[0] 76 hostname, port, err := netutil.ParseHostAndPortFromAddress(address) 77 if err != nil { 78 log.Warn("Ignored invalid tidb topology info entry", 79 zap.String("key", key), 80 zap.String("value", string(kv.Value)), 81 zap.Error(err)) 82 continue 83 } 84 nodesInfo[keyParts[0]] = &tidbInstance{ 85 IP: hostname, 86 Port: port, 87 } 88 case "ttl": 89 alive, err := parseTiDBAliveness(kv.Value) 90 if !alive || err != nil { 91 log.Warn("Ignored invalid tidb topology TTL entry", 92 zap.String("key", key), 93 zap.String("value", string(kv.Value)), 94 zap.Error(err)) 95 continue 96 } 97 nodesAlive[keyParts[0]] = struct{}{} 98 } 99 } 100 101 nodes := make([]tidbInstance, 0) 102 for addr, info := range nodesInfo { 103 if _, ok := nodesAlive[addr]; ok { 104 nodes = append(nodes, *info) 105 } 106 } 107 return nodes, nil 108 } 109 110 func parseTiDBAliveness(value []byte) (bool, error) { 111 unixTimestampNano, err := strconv.ParseUint(string(value), 10, 64) 112 if err != nil { 113 return false, errors.ErrUnmarshalFailed.Wrap(err) 114 } 115 t := time.Unix(0, int64(unixTimestampNano)) 116 if time.Since(t) > topologyTiDBTTL*time.Second { 117 return false, nil 118 } 119 return true, nil 120 }