github.com/whtcorpsinc/milevadb-prod@v0.0.0-20211104133533-f57f4be3b597/causetstore/petri/infosync/info.go (about)

     1  // Copyright 2020 WHTCORPS INC, 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 infosync
    15  
    16  import (
    17  	"bytes"
    18  	"context"
    19  	"encoding/json"
    20  	"fmt"
    21  	"io"
    22  	"io/ioutil"
    23  	"net/http"
    24  	"os"
    25  	"path"
    26  	"strconv"
    27  	"strings"
    28  	"sync/atomic"
    29  	"time"
    30  
    31  	"github.com/whtcorpsinc/BerolinaSQL/allegrosql"
    32  	"github.com/whtcorpsinc/BerolinaSQL/terror"
    33  	"github.com/whtcorpsinc/errors"
    34  	"github.com/whtcorpsinc/failpoint"
    35  	"github.com/whtcorpsinc/milevadb/causetstore/einsteindb/oracle"
    36  	"github.com/whtcorpsinc/milevadb/config"
    37  	"github.com/whtcorpsinc/milevadb/dbs/memristed"
    38  	"github.com/whtcorpsinc/milevadb/dbs/soliton"
    39  	"github.com/whtcorpsinc/milevadb/ekv"
    40  	"github.com/whtcorpsinc/milevadb/errno"
    41  	util2 "github.com/whtcorpsinc/milevadb/soliton"
    42  	"github.com/whtcorpsinc/milevadb/soliton/FIDelapi"
    43  	"github.com/whtcorpsinc/milevadb/soliton/logutil"
    44  	"github.com/whtcorpsinc/milevadb/soliton/replog"
    45  	"github.com/whtcorpsinc/milevadb/soliton/versioninfo"
    46  	"github.com/whtcorpsinc/milevadb/stochastikctx/binloginfo"
    47  	"github.com/whtcorpsinc/milevadb/stochastikctx/variable"
    48  	"github.com/whtcorpsinc/milevadb/tenant"
    49  	"go.etcd.io/etcd/clientv3"
    50  	"go.etcd.io/etcd/clientv3/concurrency"
    51  	"go.uber.org/zap"
    52  )
    53  
    54  const (
    55  	// ServerInformationPath causetstore server information such as IP, port and so on.
    56  	ServerInformationPath = "/milevadb/server/info"
    57  	// ServerMinStartTSPath causetstore the server min start timestamp.
    58  	ServerMinStartTSPath = "/milevadb/server/minstartts"
    59  	// TiFlashBlockSyncProgressPath causetstore the tiflash causet replica sync progress.
    60  	TiFlashBlockSyncProgressPath = "/tiflash/causet/sync"
    61  	// keyOFIDelefaultRetryCnt is the default retry count for etcd causetstore.
    62  	keyOFIDelefaultRetryCnt = 5
    63  	// keyOFIDelefaultTimeout is the default time out for etcd causetstore.
    64  	keyOFIDelefaultTimeout = 1 * time.Second
    65  	// InfoStochastikTTL is the ETCD stochastik's TTL in seconds.
    66  	InfoStochastikTTL = 10 * 60
    67  	// ReportInterval is interval of infoSyncerKeeper reporting min startTS.
    68  	ReportInterval = 30 * time.Second
    69  	// TopologyInformationPath means etcd path for storing topology info.
    70  	TopologyInformationPath = "/topology/milevadb"
    71  	// TopologyStochastikTTL is ttl for topology, ant it's the ETCD stochastik's TTL in seconds.
    72  	TopologyStochastikTTL = 45
    73  	// TopologyTimeToRefresh means time to refresh etcd.
    74  	TopologyTimeToRefresh = 30 * time.Second
    75  	// TopologyPrometheus means address of prometheus.
    76  	TopologyPrometheus = "/topology/prometheus"
    77  	// BlockPrometheusCacheExpiry is the expiry time for prometheus address cache.
    78  	BlockPrometheusCacheExpiry = 10 * time.Second
    79  )
    80  
    81  // ErrPrometheusAddrIsNotSet is the error that Prometheus address is not set in FIDel and etcd
    82  var ErrPrometheusAddrIsNotSet = terror.ClassPetri.New(errno.ErrPrometheusAddrIsNotSet, errno.MyALLEGROSQLErrName[errno.ErrPrometheusAddrIsNotSet])
    83  
    84  // InfoSyncer stores server info to etcd when the milevadb-server starts and delete when milevadb-server shuts down.
    85  type InfoSyncer struct {
    86  	etcdCli            *clientv3.Client
    87  	info               *ServerInfo
    88  	serverInfoPath     string
    89  	minStartTS         uint64
    90  	minStartTSPath     string
    91  	manager            util2.StochastikManager
    92  	stochastik         *concurrency.Stochastik
    93  	topologyStochastik *concurrency.Stochastik
    94  	prometheusAddr     string
    95  	modifyTime         time.Time
    96  }
    97  
    98  // ServerInfo is server static information.
    99  // It will not be uFIDelated when milevadb-server running. So please only put static information in ServerInfo struct.
   100  type ServerInfo struct {
   101  	ServerVersionInfo
   102  	ID             string            `json:"dbs_id"`
   103  	IP             string            `json:"ip"`
   104  	Port           uint              `json:"listening_port"`
   105  	StatusPort     uint              `json:"status_port"`
   106  	Lease          string            `json:"lease"`
   107  	BinlogStatus   string            `json:"binlog_status"`
   108  	StartTimestamp int64             `json:"start_timestamp"`
   109  	Labels         map[string]string `json:"labels"`
   110  }
   111  
   112  // ServerVersionInfo is the server version and git_hash.
   113  type ServerVersionInfo struct {
   114  	Version string `json:"version"`
   115  	GitHash string `json:"git_hash"`
   116  }
   117  
   118  // globalInfoSyncer stores the global infoSyncer.
   119  // Use a global variable for simply the code, use the petri.infoSyncer will have circle import problem in some pkg.
   120  // Use atomic.Value to avoid data race in the test.
   121  var globalInfoSyncer atomic.Value
   122  
   123  func getGlobalInfoSyncer() (*InfoSyncer, error) {
   124  	v := globalInfoSyncer.Load()
   125  	if v == nil {
   126  		return nil, errors.New("infoSyncer is not initialized")
   127  	}
   128  	return v.(*InfoSyncer), nil
   129  }
   130  
   131  func setGlobalInfoSyncer(is *InfoSyncer) {
   132  	globalInfoSyncer.CausetStore(is)
   133  }
   134  
   135  // GlobalInfoSyncerInit return a new InfoSyncer. It is exported for testing.
   136  func GlobalInfoSyncerInit(ctx context.Context, id string, etcdCli *clientv3.Client, skipRegisterToDashBoard bool) (*InfoSyncer, error) {
   137  	is := &InfoSyncer{
   138  		etcdCli:        etcdCli,
   139  		info:           getServerInfo(id),
   140  		serverInfoPath: fmt.Sprintf("%s/%s", ServerInformationPath, id),
   141  		minStartTSPath: fmt.Sprintf("%s/%s", ServerMinStartTSPath, id),
   142  	}
   143  	err := is.init(ctx, skipRegisterToDashBoard)
   144  	if err != nil {
   145  		return nil, err
   146  	}
   147  	setGlobalInfoSyncer(is)
   148  	return is, nil
   149  }
   150  
   151  // Init creates a new etcd stochastik and stores server info to etcd.
   152  func (is *InfoSyncer) init(ctx context.Context, skipRegisterToDashboard bool) error {
   153  	err := is.newStochastikAndStoreServerInfo(ctx, tenant.NewStochastikDefaultRetryCnt)
   154  	if err != nil {
   155  		return err
   156  	}
   157  	if skipRegisterToDashboard {
   158  		return nil
   159  	}
   160  	return is.newTopologyStochastikAndStoreServerInfo(ctx, tenant.NewStochastikDefaultRetryCnt)
   161  }
   162  
   163  // SetStochastikManager set the stochastik manager for InfoSyncer.
   164  func (is *InfoSyncer) SetStochastikManager(manager util2.StochastikManager) {
   165  	is.manager = manager
   166  }
   167  
   168  // GetServerInfo gets self server static information.
   169  func GetServerInfo() (*ServerInfo, error) {
   170  	is, err := getGlobalInfoSyncer()
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  	return is.info, nil
   175  }
   176  
   177  // GetServerInfoByID gets specified server static information from etcd.
   178  func GetServerInfoByID(ctx context.Context, id string) (*ServerInfo, error) {
   179  	is, err := getGlobalInfoSyncer()
   180  	if err != nil {
   181  		return nil, err
   182  	}
   183  	return is.getServerInfoByID(ctx, id)
   184  }
   185  
   186  func (is *InfoSyncer) getServerInfoByID(ctx context.Context, id string) (*ServerInfo, error) {
   187  	if is.etcdCli == nil || id == is.info.ID {
   188  		return is.info, nil
   189  	}
   190  	key := fmt.Sprintf("%s/%s", ServerInformationPath, id)
   191  	infoMap, err := getInfo(ctx, is.etcdCli, key, keyOFIDelefaultRetryCnt, keyOFIDelefaultTimeout)
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  	info, ok := infoMap[id]
   196  	if !ok {
   197  		return nil, errors.Errorf("[info-syncer] get %s failed", key)
   198  	}
   199  	return info, nil
   200  }
   201  
   202  // GetAllServerInfo gets all servers static information from etcd.
   203  func GetAllServerInfo(ctx context.Context) (map[string]*ServerInfo, error) {
   204  	is, err := getGlobalInfoSyncer()
   205  	if err != nil {
   206  		return nil, err
   207  	}
   208  	return is.getAllServerInfo(ctx)
   209  }
   210  
   211  // UFIDelateTiFlashBlockSyncProgress is used to uFIDelate the tiflash causet replica sync progress.
   212  func UFIDelateTiFlashBlockSyncProgress(ctx context.Context, tid int64, progress float64) error {
   213  	is, err := getGlobalInfoSyncer()
   214  	if err != nil {
   215  		return err
   216  	}
   217  	if is.etcdCli == nil {
   218  		return nil
   219  	}
   220  	key := fmt.Sprintf("%s/%v", TiFlashBlockSyncProgressPath, tid)
   221  	return soliton.PutKVToEtcd(ctx, is.etcdCli, keyOFIDelefaultRetryCnt, key, strconv.FormatFloat(progress, 'f', 2, 64))
   222  }
   223  
   224  // DeleteTiFlashBlockSyncProgress is used to delete the tiflash causet replica sync progress.
   225  func DeleteTiFlashBlockSyncProgress(tid int64) error {
   226  	is, err := getGlobalInfoSyncer()
   227  	if err != nil {
   228  		return err
   229  	}
   230  	if is.etcdCli == nil {
   231  		return nil
   232  	}
   233  	key := fmt.Sprintf("%s/%v", TiFlashBlockSyncProgressPath, tid)
   234  	return soliton.DeleteKeyFromEtcd(key, is.etcdCli, keyOFIDelefaultRetryCnt, keyOFIDelefaultTimeout)
   235  }
   236  
   237  // GetTiFlashBlockSyncProgress uses to get all the tiflash causet replica sync progress.
   238  func GetTiFlashBlockSyncProgress(ctx context.Context) (map[int64]float64, error) {
   239  	is, err := getGlobalInfoSyncer()
   240  	if err != nil {
   241  		return nil, err
   242  	}
   243  	progressMap := make(map[int64]float64)
   244  	if is.etcdCli == nil {
   245  		return progressMap, nil
   246  	}
   247  	for i := 0; i < keyOFIDelefaultRetryCnt; i++ {
   248  		resp, err := is.etcdCli.Get(ctx, TiFlashBlockSyncProgressPath+"/", clientv3.WithPrefix())
   249  		if err != nil {
   250  			logutil.BgLogger().Info("get tiflash causet replica sync progress failed, continue checking.", zap.Error(err))
   251  			continue
   252  		}
   253  		for _, ekv := range resp.Ekvs {
   254  			tid, err := strconv.ParseInt(string(ekv.Key[len(TiFlashBlockSyncProgressPath)+1:]), 10, 64)
   255  			if err != nil {
   256  				logutil.BgLogger().Info("invalid tiflash causet replica sync progress key.", zap.String("key", string(ekv.Key)))
   257  				continue
   258  			}
   259  			progress, err := strconv.ParseFloat(string(ekv.Value), 64)
   260  			if err != nil {
   261  				logutil.BgLogger().Info("invalid tiflash causet replica sync progress value.",
   262  					zap.String("key", string(ekv.Key)), zap.String("value", string(ekv.Value)))
   263  				continue
   264  			}
   265  			progressMap[tid] = progress
   266  		}
   267  		break
   268  	}
   269  	return progressMap, nil
   270  }
   271  
   272  func doRequest(ctx context.Context, addrs []string, route, method string, body io.Reader) ([]byte, error) {
   273  	var err error
   274  	var req *http.Request
   275  	var res *http.Response
   276  	for _, addr := range addrs {
   277  		var url string
   278  		if strings.HasPrefix(addr, "http://") {
   279  			url = fmt.Sprintf("%s%s", addr, route)
   280  		} else {
   281  			url = fmt.Sprintf("http://%s%s", addr, route)
   282  		}
   283  
   284  		if ctx != nil {
   285  			req, err = http.NewRequestWithContext(ctx, method, url, body)
   286  		} else {
   287  			req, err = http.NewRequest(method, url, body)
   288  		}
   289  		if err != nil {
   290  			return nil, err
   291  		}
   292  		if body != nil {
   293  			req.Header.Set("Content-Type", "application/json")
   294  		}
   295  
   296  		res, err = http.DefaultClient.Do(req)
   297  		if err == nil {
   298  			defer terror.Call(res.Body.Close)
   299  
   300  			bodyBytes, err := ioutil.ReadAll(res.Body)
   301  			if res.StatusCode != http.StatusOK {
   302  				err = errors.Wrapf(err, "%s", bodyBytes)
   303  			}
   304  			return bodyBytes, err
   305  		}
   306  	}
   307  	return nil, err
   308  }
   309  
   310  // GetPlacementMemrules is used to retrieve memristed rules from FIDel.
   311  func GetPlacementMemrules(ctx context.Context) ([]*memristed.MemruleOp, error) {
   312  	is, err := getGlobalInfoSyncer()
   313  	if err != nil {
   314  		return nil, err
   315  	}
   316  
   317  	if is.etcdCli == nil {
   318  		return nil, nil
   319  	}
   320  
   321  	addrs := is.etcdCli.Endpoints()
   322  
   323  	if len(addrs) == 0 {
   324  		return nil, errors.Errorf("fidel unavailable")
   325  	}
   326  
   327  	res, err := doRequest(ctx, addrs, path.Join(FIDelapi.Config, "rules"), http.MethodGet, nil)
   328  	if err != nil {
   329  		return nil, err
   330  	}
   331  
   332  	var rules []*memristed.MemruleOp
   333  	err = json.Unmarshal(res, &rules)
   334  	if err != nil {
   335  		return nil, err
   336  	}
   337  	return rules, nil
   338  }
   339  
   340  // UFIDelatePlacementMemrules is used to notify FIDel changes of memristed rules.
   341  func UFIDelatePlacementMemrules(ctx context.Context, rules []*memristed.MemruleOp) error {
   342  	if len(rules) == 0 {
   343  		return nil
   344  	}
   345  
   346  	is, err := getGlobalInfoSyncer()
   347  	if err != nil {
   348  		return err
   349  	}
   350  
   351  	if is.etcdCli == nil {
   352  		return nil
   353  	}
   354  
   355  	addrs := is.etcdCli.Endpoints()
   356  
   357  	if len(addrs) == 0 {
   358  		return errors.Errorf("fidel unavailable")
   359  	}
   360  
   361  	b, err := json.Marshal(rules)
   362  	if err != nil {
   363  		return err
   364  	}
   365  
   366  	_, err = doRequest(ctx, addrs, path.Join(FIDelapi.Config, "rules/batch"), http.MethodPost, bytes.NewReader(b))
   367  	return err
   368  }
   369  
   370  func (is *InfoSyncer) getAllServerInfo(ctx context.Context) (map[string]*ServerInfo, error) {
   371  	allInfo := make(map[string]*ServerInfo)
   372  	if is.etcdCli == nil {
   373  		allInfo[is.info.ID] = getServerInfo(is.info.ID)
   374  		return allInfo, nil
   375  	}
   376  	allInfo, err := getInfo(ctx, is.etcdCli, ServerInformationPath, keyOFIDelefaultRetryCnt, keyOFIDelefaultTimeout, clientv3.WithPrefix())
   377  	if err != nil {
   378  		return nil, err
   379  	}
   380  	return allInfo, nil
   381  }
   382  
   383  // storeServerInfo stores self server static information to etcd.
   384  func (is *InfoSyncer) storeServerInfo(ctx context.Context) error {
   385  	if is.etcdCli == nil {
   386  		return nil
   387  	}
   388  	infoBuf, err := json.Marshal(is.info)
   389  	if err != nil {
   390  		return errors.Trace(err)
   391  	}
   392  	str := string(replog.String(infoBuf))
   393  	err = soliton.PutKVToEtcd(ctx, is.etcdCli, keyOFIDelefaultRetryCnt, is.serverInfoPath, str, clientv3.WithLease(is.stochastik.Lease()))
   394  	return err
   395  }
   396  
   397  // RemoveServerInfo remove self server static information from etcd.
   398  func (is *InfoSyncer) RemoveServerInfo() {
   399  	if is.etcdCli == nil {
   400  		return
   401  	}
   402  	err := soliton.DeleteKeyFromEtcd(is.serverInfoPath, is.etcdCli, keyOFIDelefaultRetryCnt, keyOFIDelefaultTimeout)
   403  	if err != nil {
   404  		logutil.BgLogger().Error("remove server info failed", zap.Error(err))
   405  	}
   406  }
   407  
   408  type topologyInfo struct {
   409  	ServerVersionInfo
   410  	StatusPort     uint              `json:"status_port"`
   411  	DeployPath     string            `json:"deploy_path"`
   412  	StartTimestamp int64             `json:"start_timestamp"`
   413  	Labels         map[string]string `json:"labels"`
   414  }
   415  
   416  func (is *InfoSyncer) getTopologyInfo() topologyInfo {
   417  	s, err := os.InterDircublock()
   418  	if err != nil {
   419  		s = ""
   420  	}
   421  	dir := path.Dir(s)
   422  	return topologyInfo{
   423  		ServerVersionInfo: ServerVersionInfo{
   424  			Version: allegrosql.MilevaDBReleaseVersion,
   425  			GitHash: is.info.ServerVersionInfo.GitHash,
   426  		},
   427  		StatusPort:     is.info.StatusPort,
   428  		DeployPath:     dir,
   429  		StartTimestamp: is.info.StartTimestamp,
   430  		Labels:         is.info.Labels,
   431  	}
   432  }
   433  
   434  // StoreTopologyInfo  stores the topology of milevadb to etcd.
   435  func (is *InfoSyncer) StoreTopologyInfo(ctx context.Context) error {
   436  	if is.etcdCli == nil {
   437  		return nil
   438  	}
   439  	topologyInfo := is.getTopologyInfo()
   440  	infoBuf, err := json.Marshal(topologyInfo)
   441  	if err != nil {
   442  		return errors.Trace(err)
   443  	}
   444  	str := string(replog.String(infoBuf))
   445  	key := fmt.Sprintf("%s/%s:%v/info", TopologyInformationPath, is.info.IP, is.info.Port)
   446  	// Note: no lease is required here.
   447  	err = soliton.PutKVToEtcd(ctx, is.etcdCli, keyOFIDelefaultRetryCnt, key, str)
   448  	if err != nil {
   449  		return err
   450  	}
   451  	// Initialize ttl.
   452  	return is.uFIDelateTopologyAliveness(ctx)
   453  }
   454  
   455  // GetMinStartTS get min start timestamp.
   456  // Export for testing.
   457  func (is *InfoSyncer) GetMinStartTS() uint64 {
   458  	return is.minStartTS
   459  }
   460  
   461  // storeMinStartTS stores self server min start timestamp to etcd.
   462  func (is *InfoSyncer) storeMinStartTS(ctx context.Context) error {
   463  	if is.etcdCli == nil {
   464  		return nil
   465  	}
   466  	return soliton.PutKVToEtcd(ctx, is.etcdCli, keyOFIDelefaultRetryCnt, is.minStartTSPath,
   467  		strconv.FormatUint(is.minStartTS, 10),
   468  		clientv3.WithLease(is.stochastik.Lease()))
   469  }
   470  
   471  // RemoveMinStartTS removes self server min start timestamp from etcd.
   472  func (is *InfoSyncer) RemoveMinStartTS() {
   473  	if is.etcdCli == nil {
   474  		return
   475  	}
   476  	err := soliton.DeleteKeyFromEtcd(is.minStartTSPath, is.etcdCli, keyOFIDelefaultRetryCnt, keyOFIDelefaultTimeout)
   477  	if err != nil {
   478  		logutil.BgLogger().Error("remove minStartTS failed", zap.Error(err))
   479  	}
   480  }
   481  
   482  // ReportMinStartTS reports self server min start timestamp to ETCD.
   483  func (is *InfoSyncer) ReportMinStartTS(causetstore ekv.CausetStorage) {
   484  	if is.manager == nil {
   485  		// Server may not start in time.
   486  		return
   487  	}
   488  	pl := is.manager.ShowProcessList()
   489  
   490  	// Calculate the lower limit of the start timestamp to avoid extremely old transaction delaying GC.
   491  	currentVer, err := causetstore.CurrentVersion()
   492  	if err != nil {
   493  		logutil.BgLogger().Error("uFIDelate minStartTS failed", zap.Error(err))
   494  		return
   495  	}
   496  	now := time.Unix(0, oracle.ExtractPhysical(currentVer.Ver)*1e6)
   497  	startTSLowerLimit := variable.GoTimeToTS(now.Add(-time.Duration(ekv.MaxTxnTimeUse) * time.Millisecond))
   498  
   499  	minStartTS := variable.GoTimeToTS(now)
   500  	for _, info := range pl {
   501  		if info.CurTxnStartTS > startTSLowerLimit && info.CurTxnStartTS < minStartTS {
   502  			minStartTS = info.CurTxnStartTS
   503  		}
   504  	}
   505  
   506  	is.minStartTS = minStartTS
   507  	err = is.storeMinStartTS(context.Background())
   508  	if err != nil {
   509  		logutil.BgLogger().Error("uFIDelate minStartTS failed", zap.Error(err))
   510  	}
   511  }
   512  
   513  // Done returns a channel that closes when the info syncer is no longer being refreshed.
   514  func (is *InfoSyncer) Done() <-chan struct{} {
   515  	if is.etcdCli == nil {
   516  		return make(chan struct{}, 1)
   517  	}
   518  	return is.stochastik.Done()
   519  }
   520  
   521  // TopologyDone returns a channel that closes when the topology syncer is no longer being refreshed.
   522  func (is *InfoSyncer) TopologyDone() <-chan struct{} {
   523  	if is.etcdCli == nil {
   524  		return make(chan struct{}, 1)
   525  	}
   526  	return is.topologyStochastik.Done()
   527  }
   528  
   529  // Restart restart the info syncer with new stochastik leaseID and causetstore server info to etcd again.
   530  func (is *InfoSyncer) Restart(ctx context.Context) error {
   531  	return is.newStochastikAndStoreServerInfo(ctx, tenant.NewStochastikDefaultRetryCnt)
   532  }
   533  
   534  // RestartTopology restart the topology syncer with new stochastik leaseID and causetstore server info to etcd again.
   535  func (is *InfoSyncer) RestartTopology(ctx context.Context) error {
   536  	return is.newTopologyStochastikAndStoreServerInfo(ctx, tenant.NewStochastikDefaultRetryCnt)
   537  }
   538  
   539  // newStochastikAndStoreServerInfo creates a new etcd stochastik and stores server info to etcd.
   540  func (is *InfoSyncer) newStochastikAndStoreServerInfo(ctx context.Context, retryCnt int) error {
   541  	if is.etcdCli == nil {
   542  		return nil
   543  	}
   544  	logPrefix := fmt.Sprintf("[Info-syncer] %s", is.serverInfoPath)
   545  	stochastik, err := tenant.NewStochastik(ctx, logPrefix, is.etcdCli, retryCnt, InfoStochastikTTL)
   546  	if err != nil {
   547  		return err
   548  	}
   549  	is.stochastik = stochastik
   550  	binloginfo.RegisterStatusListener(func(status binloginfo.BinlogStatus) error {
   551  		is.info.BinlogStatus = status.String()
   552  		err := is.storeServerInfo(ctx)
   553  		return errors.Trace(err)
   554  	})
   555  	return is.storeServerInfo(ctx)
   556  }
   557  
   558  // newTopologyStochastikAndStoreServerInfo creates a new etcd stochastik and stores server info to etcd.
   559  func (is *InfoSyncer) newTopologyStochastikAndStoreServerInfo(ctx context.Context, retryCnt int) error {
   560  	if is.etcdCli == nil {
   561  		return nil
   562  	}
   563  	logPrefix := fmt.Sprintf("[topology-syncer] %s/%s:%d", TopologyInformationPath, is.info.IP, is.info.Port)
   564  	stochastik, err := tenant.NewStochastik(ctx, logPrefix, is.etcdCli, retryCnt, TopologyStochastikTTL)
   565  	if err != nil {
   566  		return err
   567  	}
   568  
   569  	is.topologyStochastik = stochastik
   570  	return is.StoreTopologyInfo(ctx)
   571  }
   572  
   573  // refreshTopology refreshes etcd topology with ttl stored in "/topology/milevadb/ip:port/ttl".
   574  func (is *InfoSyncer) uFIDelateTopologyAliveness(ctx context.Context) error {
   575  	if is.etcdCli == nil {
   576  		return nil
   577  	}
   578  	key := fmt.Sprintf("%s/%s:%v/ttl", TopologyInformationPath, is.info.IP, is.info.Port)
   579  	return soliton.PutKVToEtcd(ctx, is.etcdCli, keyOFIDelefaultRetryCnt, key,
   580  		fmt.Sprintf("%v", time.Now().UnixNano()),
   581  		clientv3.WithLease(is.topologyStochastik.Lease()))
   582  }
   583  
   584  // GetPrometheusAddr gets prometheus Address
   585  func GetPrometheusAddr() (string, error) {
   586  	is, err := getGlobalInfoSyncer()
   587  	if err != nil {
   588  		return "", err
   589  	}
   590  
   591  	// if the cache of prometheusAddr is over 10s, uFIDelate the prometheusAddr
   592  	if time.Since(is.modifyTime) < BlockPrometheusCacheExpiry {
   593  		return is.prometheusAddr, nil
   594  	}
   595  	return is.getPrometheusAddr()
   596  }
   597  
   598  type prometheus struct {
   599  	IP         string `json:"ip"`
   600  	BinaryPath string `json:"binary_path"`
   601  	Port       int    `json:"port"`
   602  }
   603  
   604  type metricStorage struct {
   605  	FIDelServer struct {
   606  		MetricStorage string `json:"metric-storage"`
   607  	} `json:"fidel-server"`
   608  }
   609  
   610  func (is *InfoSyncer) getPrometheusAddr() (string, error) {
   611  	// Get FIDel servers info.
   612  	FIDelAddrs := is.etcdCli.Endpoints()
   613  	if len(FIDelAddrs) == 0 {
   614  		return "", errors.Errorf("fidel unavailable")
   615  	}
   616  
   617  	// Get prometheus address from FIDelApi.
   618  	var url, res string
   619  	if strings.HasPrefix(FIDelAddrs[0], "http://") {
   620  		url = fmt.Sprintf("%s%s", FIDelAddrs[0], FIDelapi.Config)
   621  	} else {
   622  		url = fmt.Sprintf("http://%s%s", FIDelAddrs[0], FIDelapi.Config)
   623  	}
   624  	resp, err := http.Get(url)
   625  	if err != nil {
   626  		return "", err
   627  	}
   628  	var metricStorage metricStorage
   629  	dec := json.NewCausetDecoder(resp.Body)
   630  	err = dec.Decode(&metricStorage)
   631  	if err != nil {
   632  		return "", err
   633  	}
   634  	res = metricStorage.FIDelServer.MetricStorage
   635  
   636  	// Get prometheus address from etcdApi.
   637  	if res == "" {
   638  		values, err := is.getPrometheusAddrFromEtcd(TopologyPrometheus)
   639  		if err != nil {
   640  			return "", errors.Trace(err)
   641  		}
   642  		if values == "" {
   643  			return "", ErrPrometheusAddrIsNotSet
   644  		}
   645  		var prometheus prometheus
   646  		err = json.Unmarshal([]byte(values), &prometheus)
   647  		if err != nil {
   648  			return "", errors.Trace(err)
   649  		}
   650  		res = fmt.Sprintf("http://%s:%v", prometheus.IP, prometheus.Port)
   651  	}
   652  	is.prometheusAddr = res
   653  	is.modifyTime = time.Now()
   654  	setGlobalInfoSyncer(is)
   655  	return res, nil
   656  }
   657  
   658  func (is *InfoSyncer) getPrometheusAddrFromEtcd(k string) (string, error) {
   659  	ctx, cancel := context.WithTimeout(context.Background(), keyOFIDelefaultTimeout)
   660  	resp, err := is.etcdCli.Get(ctx, k)
   661  	cancel()
   662  	if err != nil {
   663  		return "", errors.Trace(err)
   664  	}
   665  	if len(resp.Ekvs) > 0 {
   666  		return string(resp.Ekvs[0].Value), nil
   667  	}
   668  	return "", nil
   669  }
   670  
   671  // getInfo gets server information from etcd according to the key and opts.
   672  func getInfo(ctx context.Context, etcdCli *clientv3.Client, key string, retryCnt int, timeout time.Duration, opts ...clientv3.OpOption) (map[string]*ServerInfo, error) {
   673  	var err error
   674  	var resp *clientv3.GetResponse
   675  	allInfo := make(map[string]*ServerInfo)
   676  	for i := 0; i < retryCnt; i++ {
   677  		select {
   678  		case <-ctx.Done():
   679  			err = errors.Trace(ctx.Err())
   680  			return nil, err
   681  		default:
   682  		}
   683  		childCtx, cancel := context.WithTimeout(ctx, timeout)
   684  		resp, err = etcdCli.Get(childCtx, key, opts...)
   685  		cancel()
   686  		if err != nil {
   687  			logutil.BgLogger().Info("get key failed", zap.String("key", key), zap.Error(err))
   688  			time.Sleep(200 * time.Millisecond)
   689  			continue
   690  		}
   691  		for _, ekv := range resp.Ekvs {
   692  			info := &ServerInfo{
   693  				BinlogStatus: binloginfo.BinlogStatusUnknown.String(),
   694  			}
   695  			err = json.Unmarshal(ekv.Value, info)
   696  			if err != nil {
   697  				logutil.BgLogger().Info("get key failed", zap.String("key", string(ekv.Key)), zap.ByteString("value", ekv.Value),
   698  					zap.Error(err))
   699  				return nil, errors.Trace(err)
   700  			}
   701  			allInfo[info.ID] = info
   702  		}
   703  		return allInfo, nil
   704  	}
   705  	return nil, errors.Trace(err)
   706  }
   707  
   708  // getServerInfo gets self milevadb server information.
   709  func getServerInfo(id string) *ServerInfo {
   710  	cfg := config.GetGlobalConfig()
   711  	info := &ServerInfo{
   712  		ID:             id,
   713  		IP:             cfg.AdvertiseAddress,
   714  		Port:           cfg.Port,
   715  		StatusPort:     cfg.Status.StatusPort,
   716  		Lease:          cfg.Lease,
   717  		BinlogStatus:   binloginfo.GetStatus().String(),
   718  		StartTimestamp: time.Now().Unix(),
   719  		Labels:         cfg.Labels,
   720  	}
   721  	info.Version = allegrosql.ServerVersion
   722  	info.GitHash = versioninfo.MilevaDBGitHash
   723  
   724  	failpoint.Inject("mockServerInfo", func(val failpoint.Value) {
   725  		if val.(bool) {
   726  			info.StartTimestamp = 1282967700000
   727  			info.Labels = map[string]string{
   728  				"foo": "bar",
   729  			}
   730  		}
   731  	})
   732  
   733  	return info
   734  }