From 8c2bb020328c1b80966681759dc8b2723a1e86f5 Mon Sep 17 00:00:00 2001 From: Hu# Date: Thu, 20 Jun 2024 17:22:18 +0800 Subject: [PATCH 1/4] tests/real_cluster: refine reboot cluster (#8311) close tikv/pd#8310 Signed-off-by: husharp --- tests/integrations/client/http_client_test.go | 24 ++++++---- tests/integrations/realcluster/deploy.sh | 2 + .../realcluster/reboot_pd_test.go | 48 +++++++++---------- 3 files changed, 40 insertions(+), 34 deletions(-) diff --git a/tests/integrations/client/http_client_test.go b/tests/integrations/client/http_client_test.go index f4a48dcd63e..229a658639d 100644 --- a/tests/integrations/client/http_client_test.go +++ b/tests/integrations/client/http_client_test.go @@ -569,22 +569,26 @@ func (suite *httpClientTestSuite) TestSetStoreLabels() { defer cancel() resp, err := client.GetStores(ctx) re.NoError(err) - setStore := resp.Stores[0] - re.Empty(setStore.Store.Labels, nil) + re.NotEmpty(resp.Stores) + firstStore := resp.Stores[0] + re.Empty(firstStore.Store.Labels, nil) storeLabels := map[string]string{ "zone": "zone1", } - err = client.SetStoreLabels(ctx, 1, storeLabels) + err = client.SetStoreLabels(ctx, firstStore.Store.ID, storeLabels) re.NoError(err) - resp, err = client.GetStores(ctx) + getResp, err := client.GetStore(ctx, uint64(firstStore.Store.ID)) re.NoError(err) - for _, store := range resp.Stores { - if store.Store.ID == setStore.Store.ID { - for _, label := range store.Store.Labels { - re.Equal(label.Value, storeLabels[label.Key]) - } - } + + labelsMap := make(map[string]string) + for _, label := range getResp.Store.Labels { + re.NotNil(label) + labelsMap[label.Key] = label.Value + } + + for key, value := range storeLabels { + re.Equal(value, labelsMap[key]) } } diff --git a/tests/integrations/realcluster/deploy.sh b/tests/integrations/realcluster/deploy.sh index 8cce60e8ee6..31bf17655f8 100755 --- a/tests/integrations/realcluster/deploy.sh +++ b/tests/integrations/realcluster/deploy.sh @@ -23,6 +23,8 @@ if [ ! -d "bin" ] || [ ! -e "bin/tikv-server" ] && [ ! -e "bin/tidb-server" ] && --pd.binpath ./bin/pd-server \ > $CUR_PATH/playground.log 2>&1 & else + # CI will download the binaries in the prepare phase. + # ref https://github.com/PingCAP-QE/ci/blob/387e9e533b365174962ccb1959442a7070f9cd66/pipelines/tikv/pd/latest/pull_integration_realcluster_test.groovy#L55-L68 color-green "using existing binaries..." $TIUP_BIN_DIR playground nightly --kv 3 --tiflash 1 --db 1 --pd 3 --without-monitor \ --pd.binpath ./bin/pd-server --kv.binpath ./bin/tikv-server --db.binpath ./bin/tidb-server --tiflash.binpath ./bin/tiflash --tag pd_test \ diff --git a/tests/integrations/realcluster/reboot_pd_test.go b/tests/integrations/realcluster/reboot_pd_test.go index 8e99b0822f0..b8914e87bd8 100644 --- a/tests/integrations/realcluster/reboot_pd_test.go +++ b/tests/integrations/realcluster/reboot_pd_test.go @@ -38,37 +38,37 @@ func TestReloadLabel(t *testing.T) { re := require.New(t) ctx := context.Background() - resp, _ := pdHTTPCli.GetStores(ctx) - setStore := resp.Stores[0] + resp, err := pdHTTPCli.GetStores(ctx) + re.NoError(err) + re.NotEmpty(resp.Stores) + firstStore := resp.Stores[0] // TiFlash labels will be ["engine": "tiflash"] - storeLabel := map[string]string{ + // So we need to merge the labels + storeLabels := map[string]string{ "zone": "zone1", } - for _, label := range setStore.Store.Labels { - storeLabel[label.Key] = label.Value + for _, label := range firstStore.Store.Labels { + storeLabels[label.Key] = label.Value } - err := pdHTTPCli.SetStoreLabels(ctx, setStore.Store.ID, storeLabel) - re.NoError(err) + re.NoError(pdHTTPCli.SetStoreLabels(ctx, firstStore.Store.ID, storeLabels)) - resp, err = pdHTTPCli.GetStores(ctx) - re.NoError(err) - for _, store := range resp.Stores { - if store.Store.ID == setStore.Store.ID { - for _, label := range store.Store.Labels { - re.Equal(label.Value, storeLabel[label.Key]) - } - } - } + checkLabelsAreEqual := func() { + resp, err := pdHTTPCli.GetStore(ctx, uint64(firstStore.Store.ID)) + re.NoError(err) - restartTiUP() + labelsMap := make(map[string]string) + for _, label := range resp.Store.Labels { + re.NotNil(label) + labelsMap[label.Key] = label.Value + } - resp, err = pdHTTPCli.GetStores(ctx) - re.NoError(err) - for _, store := range resp.Stores { - if store.Store.ID == setStore.Store.ID { - for _, label := range store.Store.Labels { - re.Equal(label.Value, storeLabel[label.Key]) - } + for key, value := range storeLabels { + re.Equal(value, labelsMap[key]) } } + // Check the label is set + checkLabelsAreEqual() + // Restart TiUP to reload the label + restartTiUP() + checkLabelsAreEqual() } From 049de1761e5623827932864235cd2fbb5d2698ba Mon Sep 17 00:00:00 2001 From: you06 Date: Thu, 20 Jun 2024 20:50:49 +0900 Subject: [PATCH 2/4] api: client and server support `BatchScanRegions` (#8300) close tikv/pd#8307, ref pingcap/tidb#53850 Add `BatchScanRegions` interface for pd-client. Signed-off-by: you06 Co-authored-by: JmPotato --- client/client.go | 114 +++++++++++++++++++- client/go.mod | 2 +- client/go.sum | 4 +- client/http/types.go | 37 +------ client/metrics.go | 4 + go.mod | 3 +- go.sum | 8 +- pkg/core/basic_cluster.go | 8 ++ pkg/core/region.go | 36 +++++++ server/grpc_service.go | 79 ++++++++++++++ tests/integrations/client/client_test.go | 132 ++++++++++++++++++++++- tests/integrations/go.mod | 2 +- tests/integrations/go.sum | 8 +- tools/go.mod | 2 +- tools/go.sum | 8 +- tools/pd-api-bench/cases/cases.go | 1 + 16 files changed, 392 insertions(+), 56 deletions(-) diff --git a/client/client.go b/client/client.go index 1c8ef3cafe8..92cbd3d523f 100644 --- a/client/client.go +++ b/client/client.go @@ -17,7 +17,9 @@ package pd import ( "context" "crypto/tls" + "encoding/hex" "fmt" + "net/url" "runtime/trace" "strings" "sync" @@ -85,11 +87,18 @@ type RPCClient interface { GetPrevRegion(ctx context.Context, key []byte, opts ...GetRegionOption) (*Region, error) // GetRegionByID gets a region and its leader Peer from PD by id. GetRegionByID(ctx context.Context, regionID uint64, opts ...GetRegionOption) (*Region, error) + // Deprecated: use BatchScanRegions instead. // ScanRegions gets a list of regions, starts from the region that contains key. - // Limit limits the maximum number of regions returned. + // Limit limits the maximum number of regions returned. It returns all the regions in the given range if limit <= 0. // If a region has no leader, corresponding leader will be placed by a peer // with empty value (PeerID is 0). ScanRegions(ctx context.Context, key, endKey []byte, limit int, opts ...GetRegionOption) ([]*Region, error) + // BatchScanRegions gets a list of regions, starts from the region that contains key. + // Limit limits the maximum number of regions returned. It returns all the regions in the given ranges if limit <= 0. + // If a region has no leader, corresponding leader will be placed by a peer + // with empty value (PeerID is 0). + // The returned regions are flattened, even there are key ranges located in the same region, only one region will be returned. + BatchScanRegions(ctx context.Context, keyRanges []KeyRange, limit int, opts ...GetRegionOption) ([]*Region, error) // GetStore gets a store from PD by store id. // The store may expire later. Caller is responsible for caching and taking care // of store change. @@ -337,6 +346,38 @@ type SecurityOption struct { SSLKEYBytes []byte } +// KeyRange defines a range of keys in bytes. +type KeyRange struct { + StartKey []byte + EndKey []byte +} + +// NewKeyRange creates a new key range structure with the given start key and end key bytes. +// Notice: the actual encoding of the key range is not specified here. It should be either UTF-8 or hex. +// - UTF-8 means the key has already been encoded into a string with UTF-8 encoding, like: +// []byte{52 56 54 53 54 99 54 99 54 102 50 48 53 55 54 102 55 50 54 99 54 52}, which will later be converted to "48656c6c6f20576f726c64" +// by using `string()` method. +// - Hex means the key is just a raw hex bytes without encoding to a UTF-8 string, like: +// []byte{72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100}, which will later be converted to "48656c6c6f20576f726c64" +// by using `hex.EncodeToString()` method. +func NewKeyRange(startKey, endKey []byte) *KeyRange { + return &KeyRange{startKey, endKey} +} + +// EscapeAsUTF8Str returns the URL escaped key strings as they are UTF-8 encoded. +func (r *KeyRange) EscapeAsUTF8Str() (startKeyStr, endKeyStr string) { + startKeyStr = url.QueryEscape(string(r.StartKey)) + endKeyStr = url.QueryEscape(string(r.EndKey)) + return +} + +// EscapeAsHexStr returns the URL escaped key strings as they are hex encoded. +func (r *KeyRange) EscapeAsHexStr() (startKeyStr, endKeyStr string) { + startKeyStr = url.QueryEscape(hex.EncodeToString(r.StartKey)) + endKeyStr = url.QueryEscape(hex.EncodeToString(r.EndKey)) + return +} + // NewClient creates a PD client. func NewClient( svrAddrs []string, security SecurityOption, opts ...ClientOption, @@ -1094,6 +1135,7 @@ func (c *client) ScanRegions(ctx context.Context, key, endKey []byte, limit int, if serviceClient == nil { return nil, errs.ErrClientGetProtoClient } + //nolint:staticcheck resp, err := pdpb.NewPDClient(serviceClient.GetClientConn()).ScanRegions(cctx, req) failpoint.Inject("responseNil", func() { resp = nil @@ -1103,6 +1145,7 @@ func (c *client) ScanRegions(ctx context.Context, key, endKey []byte, limit int, if protoClient == nil { return nil, errs.ErrClientGetProtoClient } + //nolint:staticcheck resp, err = protoClient.ScanRegions(cctx, req) } @@ -1113,6 +1156,74 @@ func (c *client) ScanRegions(ctx context.Context, key, endKey []byte, limit int, return handleRegionsResponse(resp), nil } +func (c *client) BatchScanRegions(ctx context.Context, ranges []KeyRange, limit int, opts ...GetRegionOption) ([]*Region, error) { + if span := opentracing.SpanFromContext(ctx); span != nil && span.Tracer() != nil { + span = span.Tracer().StartSpan("pdclient.BatchScanRegions", opentracing.ChildOf(span.Context())) + defer span.Finish() + } + start := time.Now() + defer func() { cmdDurationBatchScanRegions.Observe(time.Since(start).Seconds()) }() + + var cancel context.CancelFunc + scanCtx := ctx + if _, ok := ctx.Deadline(); !ok { + scanCtx, cancel = context.WithTimeout(ctx, c.option.timeout) + defer cancel() + } + options := &GetRegionOp{} + for _, opt := range opts { + opt(options) + } + pbRanges := make([]*pdpb.KeyRange, 0, len(ranges)) + for _, r := range ranges { + pbRanges = append(pbRanges, &pdpb.KeyRange{StartKey: r.StartKey, EndKey: r.EndKey}) + } + req := &pdpb.BatchScanRegionsRequest{ + Header: c.requestHeader(), + NeedBuckets: options.needBuckets, + Ranges: pbRanges, + Limit: int32(limit), + } + serviceClient, cctx := c.getRegionAPIClientAndContext(scanCtx, options.allowFollowerHandle && c.option.getEnableFollowerHandle()) + if serviceClient == nil { + return nil, errs.ErrClientGetProtoClient + } + resp, err := pdpb.NewPDClient(serviceClient.GetClientConn()).BatchScanRegions(cctx, req) + failpoint.Inject("responseNil", func() { + resp = nil + }) + if serviceClient.NeedRetry(resp.GetHeader().GetError(), err) { + protoClient, cctx := c.getClientAndContext(scanCtx) + if protoClient == nil { + return nil, errs.ErrClientGetProtoClient + } + resp, err = protoClient.BatchScanRegions(cctx, req) + } + + if err = c.respForErr(cmdFailedDurationBatchScanRegions, start, err, resp.GetHeader()); err != nil { + return nil, err + } + + return handleBatchRegionsResponse(resp), nil +} + +func handleBatchRegionsResponse(resp *pdpb.BatchScanRegionsResponse) []*Region { + regions := make([]*Region, 0, len(resp.GetRegions())) + for _, r := range resp.GetRegions() { + region := &Region{ + Meta: r.Region, + Leader: r.Leader, + PendingPeers: r.PendingPeers, + Buckets: r.Buckets, + } + for _, p := range r.DownPeers { + region.DownPeers = append(region.DownPeers, p.Peer) + } + regions = append(regions, region) + } + return regions +} + func handleRegionsResponse(resp *pdpb.ScanRegionsResponse) []*Region { var regions []*Region if len(resp.GetRegions()) == 0 { @@ -1131,6 +1242,7 @@ func handleRegionsResponse(resp *pdpb.ScanRegionsResponse) []*Region { Meta: r.Region, Leader: r.Leader, PendingPeers: r.PendingPeers, + Buckets: r.Buckets, } for _, p := range r.DownPeers { region.DownPeers = append(region.DownPeers, p.Peer) diff --git a/client/go.mod b/client/go.mod index 6baa2f112f4..475cf716125 100644 --- a/client/go.mod +++ b/client/go.mod @@ -10,7 +10,7 @@ require ( github.com/opentracing/opentracing-go v1.2.0 github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c github.com/pingcap/failpoint v0.0.0-20210918120811-547c13e3eb00 - github.com/pingcap/kvproto v0.0.0-20231222062942-c0c73f41d0b2 + github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4 github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 github.com/prometheus/client_golang v1.18.0 github.com/stretchr/testify v1.8.2 diff --git a/client/go.sum b/client/go.sum index 54942bb0bb8..620f70007a7 100644 --- a/client/go.sum +++ b/client/go.sum @@ -46,8 +46,8 @@ github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c h1:xpW9bvK+HuuTm github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c/go.mod h1:X2r9ueLEUZgtx2cIogM0v4Zj5uvvzhuuiu7Pn8HzMPg= github.com/pingcap/failpoint v0.0.0-20210918120811-547c13e3eb00 h1:C3N3itkduZXDZFh4N3vQ5HEtld3S+Y+StULhWVvumU0= github.com/pingcap/failpoint v0.0.0-20210918120811-547c13e3eb00/go.mod h1:4qGtCB0QK0wBzKtFEGDhxXnSnbQApw1gc9siScUl8ew= -github.com/pingcap/kvproto v0.0.0-20231222062942-c0c73f41d0b2 h1:364A6VCS+l0oHBKZKotX9LzmfEtIO/NTccTIQcPp3Ug= -github.com/pingcap/kvproto v0.0.0-20231222062942-c0c73f41d0b2/go.mod h1:rXxWk2UnwfUhLXha1jxRWPADw9eMZGWEWCg92Tgmb/8= +github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4 h1:6aIKNB2YGAec4IUDLw6G2eDECiGiufZcgEbZSCELBx0= +github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4/go.mod h1:rXxWk2UnwfUhLXha1jxRWPADw9eMZGWEWCg92Tgmb/8= github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 h1:HR/ylkkLmGdSSDaD8IDP+SZrdhV1Kibl9KrHxJ9eciw= github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= diff --git a/client/http/types.go b/client/http/types.go index ab624049436..55f9b65caad 100644 --- a/client/http/types.go +++ b/client/http/types.go @@ -15,15 +15,14 @@ package http import ( - "encoding/hex" "encoding/json" "fmt" - "net/url" "time" "github.com/pingcap/kvproto/pkg/encryptionpb" "github.com/pingcap/kvproto/pkg/keyspacepb" "github.com/pingcap/kvproto/pkg/pdpb" + pd "github.com/tikv/pd/client" ) // ClusterState saves some cluster state information. @@ -43,37 +42,11 @@ type State struct { StartTimestamp int64 `json:"start_timestamp"` } -// KeyRange defines a range of keys in bytes. -type KeyRange struct { - startKey []byte - endKey []byte -} - -// NewKeyRange creates a new key range structure with the given start key and end key bytes. -// Notice: the actual encoding of the key range is not specified here. It should be either UTF-8 or hex. -// - UTF-8 means the key has already been encoded into a string with UTF-8 encoding, like: -// []byte{52 56 54 53 54 99 54 99 54 102 50 48 53 55 54 102 55 50 54 99 54 52}, which will later be converted to "48656c6c6f20576f726c64" -// by using `string()` method. -// - Hex means the key is just a raw hex bytes without encoding to a UTF-8 string, like: -// []byte{72, 101, 108, 108, 111, 32, 87, 111, 114, 108, 100}, which will later be converted to "48656c6c6f20576f726c64" -// by using `hex.EncodeToString()` method. -func NewKeyRange(startKey, endKey []byte) *KeyRange { - return &KeyRange{startKey, endKey} -} +// KeyRange alias pd.KeyRange to avoid break client compatibility. +type KeyRange = pd.KeyRange -// EscapeAsUTF8Str returns the URL escaped key strings as they are UTF-8 encoded. -func (r *KeyRange) EscapeAsUTF8Str() (startKeyStr, endKeyStr string) { - startKeyStr = url.QueryEscape(string(r.startKey)) - endKeyStr = url.QueryEscape(string(r.endKey)) - return -} - -// EscapeAsHexStr returns the URL escaped key strings as they are hex encoded. -func (r *KeyRange) EscapeAsHexStr() (startKeyStr, endKeyStr string) { - startKeyStr = url.QueryEscape(hex.EncodeToString(r.startKey)) - endKeyStr = url.QueryEscape(hex.EncodeToString(r.endKey)) - return -} +// NewKeyRange alias pd.NewKeyRange to avoid break client compatibility. +var NewKeyRange = pd.NewKeyRange // NOTICE: the structures below are copied from the PD API definitions. // Please make sure the consistency if any change happens to the PD API. diff --git a/client/metrics.go b/client/metrics.go index 1895306eca2..e0b29fb8bcc 100644 --- a/client/metrics.go +++ b/client/metrics.go @@ -128,6 +128,7 @@ var ( cmdDurationGetPrevRegion prometheus.Observer cmdDurationGetRegionByID prometheus.Observer cmdDurationScanRegions prometheus.Observer + cmdDurationBatchScanRegions prometheus.Observer cmdDurationGetStore prometheus.Observer cmdDurationGetAllStores prometheus.Observer cmdDurationUpdateGCSafePoint prometheus.Observer @@ -151,6 +152,7 @@ var ( cmdFailDurationGetPrevRegion prometheus.Observer cmdFailedDurationGetRegionByID prometheus.Observer cmdFailedDurationScanRegions prometheus.Observer + cmdFailedDurationBatchScanRegions prometheus.Observer cmdFailedDurationGetStore prometheus.Observer cmdFailedDurationGetAllStores prometheus.Observer cmdFailedDurationUpdateGCSafePoint prometheus.Observer @@ -174,6 +176,7 @@ func initCmdDurations() { cmdDurationGetPrevRegion = cmdDuration.WithLabelValues("get_prev_region") cmdDurationGetRegionByID = cmdDuration.WithLabelValues("get_region_byid") cmdDurationScanRegions = cmdDuration.WithLabelValues("scan_regions") + cmdDurationBatchScanRegions = cmdDuration.WithLabelValues("batch_scan_regions") cmdDurationGetStore = cmdDuration.WithLabelValues("get_store") cmdDurationGetAllStores = cmdDuration.WithLabelValues("get_all_stores") cmdDurationUpdateGCSafePoint = cmdDuration.WithLabelValues("update_gc_safe_point") @@ -197,6 +200,7 @@ func initCmdDurations() { cmdFailDurationGetPrevRegion = cmdFailedDuration.WithLabelValues("get_prev_region") cmdFailedDurationGetRegionByID = cmdFailedDuration.WithLabelValues("get_region_byid") cmdFailedDurationScanRegions = cmdFailedDuration.WithLabelValues("scan_regions") + cmdFailedDurationBatchScanRegions = cmdFailedDuration.WithLabelValues("batch_scan_regions") cmdFailedDurationGetStore = cmdFailedDuration.WithLabelValues("get_store") cmdFailedDurationGetAllStores = cmdFailedDuration.WithLabelValues("get_all_stores") cmdFailedDurationUpdateGCSafePoint = cmdFailedDuration.WithLabelValues("update_gc_safe_point") diff --git a/go.mod b/go.mod index 90c5639c936..35e064a59b0 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/pingcap/errcode v0.3.0 github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c github.com/pingcap/failpoint v0.0.0-20210918120811-547c13e3eb00 - github.com/pingcap/kvproto v0.0.0-20240403065636-c699538f7aa1 + github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4 github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 github.com/pingcap/sysutil v1.0.1-0.20230407040306-fb007c5aff21 github.com/pingcap/tidb-dashboard v0.0.0-20240326110213-9768844ff5d7 @@ -113,7 +113,6 @@ require ( github.com/goccy/go-json v0.10.2 // indirect github.com/golang-jwt/jwt v3.2.2+incompatible // indirect github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/pprof v0.0.0-20211122183932-1daafda22083 // indirect diff --git a/go.sum b/go.sum index 6ec1baa72c4..69a7ffc5187 100644 --- a/go.sum +++ b/go.sum @@ -189,8 +189,8 @@ github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EO github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v0.0.0-20180814211427-aa810b61a9c7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -371,8 +371,8 @@ github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c/go.mod h1:X2r9ue github.com/pingcap/failpoint v0.0.0-20210918120811-547c13e3eb00 h1:C3N3itkduZXDZFh4N3vQ5HEtld3S+Y+StULhWVvumU0= github.com/pingcap/failpoint v0.0.0-20210918120811-547c13e3eb00/go.mod h1:4qGtCB0QK0wBzKtFEGDhxXnSnbQApw1gc9siScUl8ew= github.com/pingcap/kvproto v0.0.0-20191211054548-3c6b38ea5107/go.mod h1:WWLmULLO7l8IOcQG+t+ItJ3fEcrL5FxF0Wu+HrMy26w= -github.com/pingcap/kvproto v0.0.0-20240403065636-c699538f7aa1 h1:vDWWJKU6ztczn24XixahtLwcnJ15DOtSRIRM3jVtZNU= -github.com/pingcap/kvproto v0.0.0-20240403065636-c699538f7aa1/go.mod h1:rXxWk2UnwfUhLXha1jxRWPADw9eMZGWEWCg92Tgmb/8= +github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4 h1:6aIKNB2YGAec4IUDLw6G2eDECiGiufZcgEbZSCELBx0= +github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4/go.mod h1:rXxWk2UnwfUhLXha1jxRWPADw9eMZGWEWCg92Tgmb/8= github.com/pingcap/log v0.0.0-20210625125904-98ed8e2eb1c7/go.mod h1:8AanEdAHATuRurdGxZXBz0At+9avep+ub7U1AGYLIMM= github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 h1:HR/ylkkLmGdSSDaD8IDP+SZrdhV1Kibl9KrHxJ9eciw= github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4= diff --git a/pkg/core/basic_cluster.go b/pkg/core/basic_cluster.go index 2392b7ddac6..ea78c4ccf9c 100644 --- a/pkg/core/basic_cluster.go +++ b/pkg/core/basic_cluster.go @@ -97,6 +97,7 @@ type RegionSetInformer interface { GetAdjacentRegions(region *RegionInfo) (*RegionInfo, *RegionInfo) ScanRegions(startKey, endKey []byte, limit int) []*RegionInfo GetRegionByKey(regionKey []byte) *RegionInfo + BatchScanRegions(keyRanges *KeyRanges, limit int) []*RegionInfo } // StoreSetInformer provides access to a shared informer of stores. @@ -140,6 +141,13 @@ type KeyRanges struct { krs []*KeyRange } +// NewKeyRangesWithSize creates a KeyRanges with the hint size. +func NewKeyRangesWithSize(size int) *KeyRanges { + return &KeyRanges{ + krs: make([]*KeyRange, 0, size), + } +} + // Append appends a KeyRange. func (rs *KeyRanges) Append(startKey, endKey []byte) { rs.krs = append(rs.krs, &KeyRange{ diff --git a/pkg/core/region.go b/pkg/core/region.go index df4cfc17be2..2a2e02fafe8 100644 --- a/pkg/core/region.go +++ b/pkg/core/region.go @@ -1816,6 +1816,42 @@ func (r *RegionsInfo) ScanRegions(startKey, endKey []byte, limit int) []*RegionI return res } +// BatchScanRegions scans regions in given key pairs, returns at most `limit` regions. +// limit <= 0 means no limit. +// The given key pairs should be non-overlapping. +func (r *RegionsInfo) BatchScanRegions(keyRanges *KeyRanges, limit int) []*RegionInfo { + r.t.RLock() + defer r.t.RUnlock() + + krs := keyRanges.Ranges() + res := make([]*RegionInfo, 0, len(krs)) + var lastRegion *RegionInfo + for _, keyRange := range krs { + if limit > 0 && len(res) >= limit { + return res + } + if lastRegion != nil { + if lastRegion.Contains(keyRange.EndKey) { + continue + } else if lastRegion.Contains(keyRange.StartKey) { + keyRange.StartKey = lastRegion.GetEndKey() + } + } + r.tree.scanRange(keyRange.StartKey, func(region *RegionInfo) bool { + if len(keyRange.EndKey) > 0 && bytes.Compare(region.GetStartKey(), keyRange.EndKey) >= 0 { + return false + } + if limit > 0 && len(res) >= limit { + return false + } + lastRegion = region + res = append(res, region) + return true + }) + } + return res +} + // ScanRegionWithIterator scans from the first region containing or behind start key, // until iterator returns false. func (r *RegionsInfo) ScanRegionWithIterator(startKey []byte, iterator func(region *RegionInfo) bool) { diff --git a/server/grpc_service.go b/server/grpc_service.go index e16fa4a8d4f..d3f58dfe1ab 100644 --- a/server/grpc_service.go +++ b/server/grpc_service.go @@ -15,6 +15,7 @@ package server import ( + "bytes" "context" "fmt" "io" @@ -1569,6 +1570,7 @@ func (s *GrpcServer) GetRegionByID(ctx context.Context, request *pdpb.GetRegionB }, nil } +// Deprecated: use BatchScanRegions instead. // ScanRegions implements gRPC PDServer. func (s *GrpcServer) ScanRegions(ctx context.Context, request *pdpb.ScanRegionsRequest) (*pdpb.ScanRegionsResponse, error) { if s.GetServiceMiddlewarePersistOptions().IsGRPCRateLimitEnabled() { @@ -1627,6 +1629,83 @@ func (s *GrpcServer) ScanRegions(ctx context.Context, request *pdpb.ScanRegionsR return resp, nil } +// BatchScanRegions implements gRPC PDServer. +func (s *GrpcServer) BatchScanRegions(ctx context.Context, request *pdpb.BatchScanRegionsRequest) (*pdpb.BatchScanRegionsResponse, error) { + if s.GetServiceMiddlewarePersistOptions().IsGRPCRateLimitEnabled() { + fName := currentFunction() + limiter := s.GetGRPCRateLimiter() + if done, err := limiter.Allow(fName); err == nil { + defer done() + } else { + return &pdpb.BatchScanRegionsResponse{ + Header: s.wrapErrorToHeader(pdpb.ErrorType_UNKNOWN, err.Error()), + }, nil + } + } + fn := func(ctx context.Context, client *grpc.ClientConn) (any, error) { + return pdpb.NewPDClient(client).BatchScanRegions(ctx, request) + } + followerHandle := new(bool) + if rsp, err := s.unaryFollowerMiddleware(ctx, request, fn, followerHandle); err != nil { + return nil, err + } else if rsp != nil { + return rsp.(*pdpb.BatchScanRegionsResponse), nil + } + + var rc *cluster.RaftCluster + if *followerHandle { + rc = s.cluster + if !rc.GetRegionSyncer().IsRunning() { + return &pdpb.BatchScanRegionsResponse{Header: s.regionNotFound()}, nil + } + } else { + rc = s.GetRaftCluster() + if rc == nil { + return &pdpb.BatchScanRegionsResponse{Header: s.notBootstrappedHeader()}, nil + } + } + needBucket := request.GetNeedBuckets() && !*followerHandle && rc.GetStoreConfig().IsEnableRegionBucket() + limit := request.GetLimit() + // cast to core.KeyRanges and check the validation. + keyRanges := core.NewKeyRangesWithSize(len(request.GetRanges())) + reqRanges := request.GetRanges() + for i, reqRange := range reqRanges { + if i > 0 { + if bytes.Compare(reqRange.StartKey, reqRanges[i-1].EndKey) < 0 { + return &pdpb.BatchScanRegionsResponse{Header: s.wrapErrorToHeader(pdpb.ErrorType_UNKNOWN, "invalid key range, ranges overlapped")}, nil + } + } + if len(reqRange.EndKey) > 0 && bytes.Compare(reqRange.StartKey, reqRange.EndKey) > 0 { + return &pdpb.BatchScanRegionsResponse{Header: s.wrapErrorToHeader(pdpb.ErrorType_UNKNOWN, "invalid key range, start key > end key")}, nil + } + keyRanges.Append(reqRange.StartKey, reqRange.EndKey) + } + res := rc.BatchScanRegions(keyRanges, int(limit)) + regions := make([]*pdpb.Region, 0, len(res)) + for _, r := range res { + leader := r.GetLeader() + if leader == nil { + leader = &metapb.Peer{} + } + var buckets *metapb.Buckets + if needBucket { + buckets = r.GetBuckets() + } + regions = append(regions, &pdpb.Region{ + Region: r.GetMeta(), + Leader: leader, + DownPeers: r.GetDownPeers(), + PendingPeers: r.GetPendingPeers(), + Buckets: buckets, + }) + } + if *followerHandle && len(regions) == 0 { + return &pdpb.BatchScanRegionsResponse{Header: s.regionNotFound()}, nil + } + resp := &pdpb.BatchScanRegionsResponse{Header: s.header(), Regions: regions} + return resp, nil +} + // AskSplit implements gRPC PDServer. func (s *GrpcServer) AskSplit(ctx context.Context, request *pdpb.AskSplitRequest) (*pdpb.AskSplitResponse, error) { if s.GetServiceMiddlewarePersistOptions().IsGRPCRateLimitEnabled() { diff --git a/tests/integrations/client/client_test.go b/tests/integrations/client/client_test.go index 65acd897726..e2d34f2c96f 100644 --- a/tests/integrations/client/client_test.go +++ b/tests/integrations/client/client_test.go @@ -736,11 +736,11 @@ func (suite *followerForwardAndHandleTestSuite) TestGetTsoByFollowerForwarding1( checkTS(re, cli, lastTS) re.NoError(failpoint.Enable("github.com/tikv/pd/client/responseNil", "return(true)")) - regions, err := cli.ScanRegions(ctx, []byte(""), []byte(""), 100) + regions, err := cli.BatchScanRegions(ctx, []pd.KeyRange{{StartKey: []byte(""), EndKey: []byte("")}}, 100) re.NoError(err) re.Empty(regions) re.NoError(failpoint.Disable("github.com/tikv/pd/client/responseNil")) - regions, err = cli.ScanRegions(ctx, []byte(""), []byte(""), 100) + regions, err = cli.BatchScanRegions(ctx, []pd.KeyRange{{StartKey: []byte(""), EndKey: []byte("")}}, 100) re.NoError(err) re.Len(regions, 1) } @@ -1412,7 +1412,7 @@ func (suite *clientTestSuite) TestScanRegions() { // Wait for region heartbeats. testutil.Eventually(re, func() bool { - scanRegions, err := suite.client.ScanRegions(context.Background(), []byte{0}, nil, 10) + scanRegions, err := suite.client.BatchScanRegions(context.Background(), []pd.KeyRange{{StartKey: []byte{0}, EndKey: nil}}, 10) return err == nil && len(scanRegions) == 10 }) @@ -1430,7 +1430,7 @@ func (suite *clientTestSuite) TestScanRegions() { t := suite.T() check := func(start, end []byte, limit int, expect []*metapb.Region) { - scanRegions, err := suite.client.ScanRegions(context.Background(), start, end, limit) + scanRegions, err := suite.client.BatchScanRegions(context.Background(), []pd.KeyRange{{StartKey: start, EndKey: end}}, limit) re.NoError(err) re.Len(scanRegions, len(expect)) t.Log("scanRegions", scanRegions) @@ -1999,3 +1999,127 @@ func waitLeaderChange(re *require.Assertions, cluster *tests.TestCluster, old st }) return leader } + +func (suite *clientTestSuite) TestBatchScanRegions() { + re := suite.Require() + regionLen := 10 + regions := make([]*metapb.Region, 0, regionLen) + for i := 0; i < regionLen; i++ { + regionID := regionIDAllocator.alloc() + r := &metapb.Region{ + Id: regionID, + RegionEpoch: &metapb.RegionEpoch{ + ConfVer: 1, + Version: 1, + }, + StartKey: []byte{byte(i)}, + EndKey: []byte{byte(i + 1)}, + Peers: peers, + } + regions = append(regions, r) + req := &pdpb.RegionHeartbeatRequest{ + Header: newHeader(suite.srv), + Region: r, + Leader: peers[0], + } + err := suite.regionHeartbeat.Send(req) + re.NoError(err) + } + + // Wait for region heartbeats. + testutil.Eventually(re, func() bool { + scanRegions, err := suite.client.BatchScanRegions(context.Background(), []pd.KeyRange{{StartKey: []byte{0}, EndKey: nil}}, 10) + return err == nil && len(scanRegions) == 10 + }) + + // Set leader of region3 to nil. + region3 := core.NewRegionInfo(regions[3], nil) + suite.srv.GetRaftCluster().HandleRegionHeartbeat(region3) + + // Add down peer for region4. + region4 := core.NewRegionInfo(regions[4], regions[4].Peers[0], core.WithDownPeers([]*pdpb.PeerStats{{Peer: regions[4].Peers[1]}})) + suite.srv.GetRaftCluster().HandleRegionHeartbeat(region4) + + // Add pending peers for region5. + region5 := core.NewRegionInfo(regions[5], regions[5].Peers[0], core.WithPendingPeers([]*metapb.Peer{regions[5].Peers[1], regions[5].Peers[2]})) + suite.srv.GetRaftCluster().HandleRegionHeartbeat(region5) + + // Add buckets for region6. + region6 := core.NewRegionInfo(regions[6], regions[6].Peers[0], core.SetBuckets(&metapb.Buckets{RegionId: regions[6].Id, Version: 2})) + suite.srv.GetRaftCluster().HandleRegionHeartbeat(region6) + + t := suite.T() + check := func(ranges []pd.KeyRange, limit int, expect []*metapb.Region) { + for _, bucket := range []bool{false, true} { + var opts []pd.GetRegionOption + if bucket { + opts = append(opts, pd.WithBuckets()) + } + scanRegions, err := suite.client.BatchScanRegions(context.Background(), ranges, limit, opts...) + re.NoError(err) + re.Len(scanRegions, len(expect)) + t.Log("scanRegions", scanRegions) + t.Log("expect", expect) + for i := range expect { + re.Equal(expect[i], scanRegions[i].Meta) + + if scanRegions[i].Meta.GetId() == region3.GetID() { + re.Equal(&metapb.Peer{}, scanRegions[i].Leader) + } else { + re.Equal(expect[i].Peers[0], scanRegions[i].Leader) + } + + if scanRegions[i].Meta.GetId() == region4.GetID() { + re.Equal([]*metapb.Peer{expect[i].Peers[1]}, scanRegions[i].DownPeers) + } + + if scanRegions[i].Meta.GetId() == region5.GetID() { + re.Equal([]*metapb.Peer{expect[i].Peers[1], expect[i].Peers[2]}, scanRegions[i].PendingPeers) + } + + if scanRegions[i].Meta.GetId() == region6.GetID() { + if !bucket { + re.Nil(scanRegions[i].Buckets) + } else { + re.Equal(scanRegions[i].Buckets, region6.GetBuckets()) + } + } + } + } + } + + // valid ranges + check([]pd.KeyRange{{StartKey: []byte{0}, EndKey: nil}}, 10, regions) + check([]pd.KeyRange{{StartKey: []byte{1}, EndKey: nil}}, 5, regions[1:6]) + check([]pd.KeyRange{ + {StartKey: []byte{0}, EndKey: []byte{1}}, + {StartKey: []byte{2}, EndKey: []byte{3}}, + {StartKey: []byte{4}, EndKey: []byte{5}}, + {StartKey: []byte{6}, EndKey: []byte{7}}, + {StartKey: []byte{8}, EndKey: []byte{9}}, + }, 10, []*metapb.Region{regions[0], regions[2], regions[4], regions[6], regions[8]}) + check([]pd.KeyRange{ + {StartKey: []byte{0}, EndKey: []byte{1}}, + {StartKey: []byte{2}, EndKey: []byte{3}}, + {StartKey: []byte{4}, EndKey: []byte{5}}, + {StartKey: []byte{6}, EndKey: []byte{7}}, + {StartKey: []byte{8}, EndKey: []byte{9}}, + }, 3, []*metapb.Region{regions[0], regions[2], regions[4]}) + check([]pd.KeyRange{ + {StartKey: []byte{0}, EndKey: []byte{0, 1}}, // non-continuous ranges in a region + {StartKey: []byte{0, 2}, EndKey: []byte{0, 3}}, + {StartKey: []byte{0, 3}, EndKey: []byte{0, 4}}, + {StartKey: []byte{0, 5}, EndKey: []byte{0, 6}}, + {StartKey: []byte{0, 7}, EndKey: []byte{3}}, + {StartKey: []byte{4}, EndKey: []byte{5}}, + }, 2, []*metapb.Region{regions[0], regions[1]}) + + // invalid ranges + _, err := suite.client.BatchScanRegions(context.Background(), []pd.KeyRange{{StartKey: []byte{1}, EndKey: []byte{0}}}, 10) + re.Error(err, "invalid key range, start key > end key") + _, err = suite.client.BatchScanRegions(context.Background(), []pd.KeyRange{ + {StartKey: []byte{0}, EndKey: []byte{2}}, + {StartKey: []byte{1}, EndKey: []byte{3}}, + }, 10) + re.Error(err, "invalid key range, ranges overlapped") +} diff --git a/tests/integrations/go.mod b/tests/integrations/go.mod index 7d07b668c80..3ad8e602a1c 100644 --- a/tests/integrations/go.mod +++ b/tests/integrations/go.mod @@ -14,7 +14,7 @@ require ( github.com/go-sql-driver/mysql v1.7.0 github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c - github.com/pingcap/kvproto v0.0.0-20240403065636-c699538f7aa1 + github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4 github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 github.com/prometheus/client_golang v1.19.0 github.com/prometheus/client_model v0.6.0 diff --git a/tests/integrations/go.sum b/tests/integrations/go.sum index 0701b42aea7..8e63acdb8cb 100644 --- a/tests/integrations/go.sum +++ b/tests/integrations/go.sum @@ -184,8 +184,8 @@ github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EO github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v0.0.0-20180814211427-aa810b61a9c7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -368,8 +368,8 @@ github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c/go.mod h1:X2r9ue github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c h1:CgbKAHto5CQgWM9fSBIvaxsJHuGP0uM74HXtv3MyyGQ= github.com/pingcap/failpoint v0.0.0-20220801062533-2eaa32854a6c/go.mod h1:4qGtCB0QK0wBzKtFEGDhxXnSnbQApw1gc9siScUl8ew= github.com/pingcap/kvproto v0.0.0-20191211054548-3c6b38ea5107/go.mod h1:WWLmULLO7l8IOcQG+t+ItJ3fEcrL5FxF0Wu+HrMy26w= -github.com/pingcap/kvproto v0.0.0-20240403065636-c699538f7aa1 h1:vDWWJKU6ztczn24XixahtLwcnJ15DOtSRIRM3jVtZNU= -github.com/pingcap/kvproto v0.0.0-20240403065636-c699538f7aa1/go.mod h1:rXxWk2UnwfUhLXha1jxRWPADw9eMZGWEWCg92Tgmb/8= +github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4 h1:6aIKNB2YGAec4IUDLw6G2eDECiGiufZcgEbZSCELBx0= +github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4/go.mod h1:rXxWk2UnwfUhLXha1jxRWPADw9eMZGWEWCg92Tgmb/8= github.com/pingcap/log v0.0.0-20210625125904-98ed8e2eb1c7/go.mod h1:8AanEdAHATuRurdGxZXBz0At+9avep+ub7U1AGYLIMM= github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 h1:HR/ylkkLmGdSSDaD8IDP+SZrdhV1Kibl9KrHxJ9eciw= github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4= diff --git a/tools/go.mod b/tools/go.mod index eb2c279e7fa..85b1559b952 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -22,7 +22,7 @@ require ( github.com/mattn/go-shellwords v1.0.12 github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c github.com/pingcap/failpoint v0.0.0-20210918120811-547c13e3eb00 - github.com/pingcap/kvproto v0.0.0-20240403065636-c699538f7aa1 + github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4 github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 github.com/pkg/errors v0.9.1 github.com/prometheus/client_golang v1.19.0 diff --git a/tools/go.sum b/tools/go.sum index 535ea668b97..e5692fa4312 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -182,8 +182,8 @@ github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EO github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903 h1:LbsanbbD6LieFkXbj9YNNBupiGHJgFeLpO0j0Fza1h8= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v0.0.0-20180814211427-aa810b61a9c7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -365,8 +365,8 @@ github.com/pingcap/errors v0.11.5-0.20211224045212-9687c2b0f87c/go.mod h1:X2r9ue github.com/pingcap/failpoint v0.0.0-20210918120811-547c13e3eb00 h1:C3N3itkduZXDZFh4N3vQ5HEtld3S+Y+StULhWVvumU0= github.com/pingcap/failpoint v0.0.0-20210918120811-547c13e3eb00/go.mod h1:4qGtCB0QK0wBzKtFEGDhxXnSnbQApw1gc9siScUl8ew= github.com/pingcap/kvproto v0.0.0-20191211054548-3c6b38ea5107/go.mod h1:WWLmULLO7l8IOcQG+t+ItJ3fEcrL5FxF0Wu+HrMy26w= -github.com/pingcap/kvproto v0.0.0-20240403065636-c699538f7aa1 h1:vDWWJKU6ztczn24XixahtLwcnJ15DOtSRIRM3jVtZNU= -github.com/pingcap/kvproto v0.0.0-20240403065636-c699538f7aa1/go.mod h1:rXxWk2UnwfUhLXha1jxRWPADw9eMZGWEWCg92Tgmb/8= +github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4 h1:6aIKNB2YGAec4IUDLw6G2eDECiGiufZcgEbZSCELBx0= +github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4/go.mod h1:rXxWk2UnwfUhLXha1jxRWPADw9eMZGWEWCg92Tgmb/8= github.com/pingcap/log v0.0.0-20210625125904-98ed8e2eb1c7/go.mod h1:8AanEdAHATuRurdGxZXBz0At+9avep+ub7U1AGYLIMM= github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 h1:HR/ylkkLmGdSSDaD8IDP+SZrdhV1Kibl9KrHxJ9eciw= github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4= diff --git a/tools/pd-api-bench/cases/cases.go b/tools/pd-api-bench/cases/cases.go index 72986df5ed8..18d5c8732e6 100644 --- a/tools/pd-api-bench/cases/cases.go +++ b/tools/pd-api-bench/cases/cases.go @@ -352,6 +352,7 @@ func (c *scanRegions) Unary(ctx context.Context, cli pd.Client) error { random := rand.Intn(upperBound) startID := c.regionSample*random*4 + 1 endID := c.regionSample*(random+1)*4 + 1 + //nolint:staticcheck _, err := cli.ScanRegions(ctx, generateKeyForSimulator(startID), generateKeyForSimulator(endID), c.regionSample) if err != nil { return err From 3b051d727d02dae34d3a8b7f7b373165ef621e5f Mon Sep 17 00:00:00 2001 From: Sparkle <1284531+baurine@users.noreply.github.com> Date: Fri, 21 Jun 2024 13:34:18 +0800 Subject: [PATCH 3/4] chore(dashboard): update TiDB Dashboard to v8.2.0-91f6c281 [master] (#8315) ref tikv/pd#4257 Signed-off-by: baurine <2008.hbl@gmail.com> --- go.mod | 2 +- go.sum | 4 ++-- scripts/dashboard-version | 2 +- tests/integrations/go.mod | 2 +- tests/integrations/go.sum | 4 ++-- tools/go.mod | 2 +- tools/go.sum | 4 ++-- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/go.mod b/go.mod index 35e064a59b0..1ef14f416e8 100644 --- a/go.mod +++ b/go.mod @@ -37,7 +37,7 @@ require ( github.com/pingcap/kvproto v0.0.0-20240620063548-118a4cab53e4 github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 github.com/pingcap/sysutil v1.0.1-0.20230407040306-fb007c5aff21 - github.com/pingcap/tidb-dashboard v0.0.0-20240326110213-9768844ff5d7 + github.com/pingcap/tidb-dashboard v0.0.0-20240612100141-91f6c281e441 github.com/prometheus/client_golang v1.19.0 github.com/prometheus/common v0.51.1 github.com/sasha-s/go-deadlock v0.2.0 diff --git a/go.sum b/go.sum index 69a7ffc5187..659cd116e9c 100644 --- a/go.sum +++ b/go.sum @@ -378,8 +378,8 @@ github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 h1:HR/ylkkLmGdSSDaD8 github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4= github.com/pingcap/sysutil v1.0.1-0.20230407040306-fb007c5aff21 h1:QV6jqlfOkh8hqvEAgwBZa+4bSgO0EeKC7s5c6Luam2I= github.com/pingcap/sysutil v1.0.1-0.20230407040306-fb007c5aff21/go.mod h1:QYnjfA95ZaMefyl1NO8oPtKeb8pYUdnDVhQgf+qdpjM= -github.com/pingcap/tidb-dashboard v0.0.0-20240326110213-9768844ff5d7 h1:eFu98FbfJB7PKWOtkaV6YNXXJWqDhczQX56j/iucgU4= -github.com/pingcap/tidb-dashboard v0.0.0-20240326110213-9768844ff5d7/go.mod h1:ucZBRz52icb23T/5Z4CsuUHmarYiin7p2MeiVBe+o8c= +github.com/pingcap/tidb-dashboard v0.0.0-20240612100141-91f6c281e441 h1:01flLztcoWBeT5pe69Q8LAB2Hty0s9Rqc3RvHU4AQK8= +github.com/pingcap/tidb-dashboard v0.0.0-20240612100141-91f6c281e441/go.mod h1:ucZBRz52icb23T/5Z4CsuUHmarYiin7p2MeiVBe+o8c= github.com/pingcap/tipb v0.0.0-20220718022156-3e2483c20a9e h1:FBaTXU8C3xgt/drM58VHxojHo/QoG1oPsgWTGvaSpO4= github.com/pingcap/tipb v0.0.0-20220718022156-3e2483c20a9e/go.mod h1:A7mrd7WHBl1o63LE2bIBGEJMTNWXqhgmYiOvMLxozfs= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= diff --git a/scripts/dashboard-version b/scripts/dashboard-version index 9b2a3898256..08a22137df5 100644 --- a/scripts/dashboard-version +++ b/scripts/dashboard-version @@ -1,3 +1,3 @@ # This file is updated by running scripts/update-dashboard.sh # Don't edit it manullay -8.0.0-9768844f +8.2.0-91f6c281 diff --git a/tests/integrations/go.mod b/tests/integrations/go.mod index 3ad8e602a1c..8a570d52458 100644 --- a/tests/integrations/go.mod +++ b/tests/integrations/go.mod @@ -125,7 +125,7 @@ require ( github.com/phf/go-queue v0.0.0-20170504031614-9abe38d0371d // indirect github.com/pingcap/errcode v0.3.0 // indirect github.com/pingcap/sysutil v1.0.1-0.20230407040306-fb007c5aff21 // indirect - github.com/pingcap/tidb-dashboard v0.0.0-20240326110213-9768844ff5d7 // indirect + github.com/pingcap/tidb-dashboard v0.0.0-20240612100141-91f6c281e441 // indirect github.com/pingcap/tipb v0.0.0-20220718022156-3e2483c20a9e // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect diff --git a/tests/integrations/go.sum b/tests/integrations/go.sum index 8e63acdb8cb..c88919f6571 100644 --- a/tests/integrations/go.sum +++ b/tests/integrations/go.sum @@ -375,8 +375,8 @@ github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 h1:HR/ylkkLmGdSSDaD8 github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4= github.com/pingcap/sysutil v1.0.1-0.20230407040306-fb007c5aff21 h1:QV6jqlfOkh8hqvEAgwBZa+4bSgO0EeKC7s5c6Luam2I= github.com/pingcap/sysutil v1.0.1-0.20230407040306-fb007c5aff21/go.mod h1:QYnjfA95ZaMefyl1NO8oPtKeb8pYUdnDVhQgf+qdpjM= -github.com/pingcap/tidb-dashboard v0.0.0-20240326110213-9768844ff5d7 h1:eFu98FbfJB7PKWOtkaV6YNXXJWqDhczQX56j/iucgU4= -github.com/pingcap/tidb-dashboard v0.0.0-20240326110213-9768844ff5d7/go.mod h1:ucZBRz52icb23T/5Z4CsuUHmarYiin7p2MeiVBe+o8c= +github.com/pingcap/tidb-dashboard v0.0.0-20240612100141-91f6c281e441 h1:01flLztcoWBeT5pe69Q8LAB2Hty0s9Rqc3RvHU4AQK8= +github.com/pingcap/tidb-dashboard v0.0.0-20240612100141-91f6c281e441/go.mod h1:ucZBRz52icb23T/5Z4CsuUHmarYiin7p2MeiVBe+o8c= github.com/pingcap/tipb v0.0.0-20220718022156-3e2483c20a9e h1:FBaTXU8C3xgt/drM58VHxojHo/QoG1oPsgWTGvaSpO4= github.com/pingcap/tipb v0.0.0-20220718022156-3e2483c20a9e/go.mod h1:A7mrd7WHBl1o63LE2bIBGEJMTNWXqhgmYiOvMLxozfs= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= diff --git a/tools/go.mod b/tools/go.mod index 85b1559b952..f424f12458e 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -127,7 +127,7 @@ require ( github.com/phf/go-queue v0.0.0-20170504031614-9abe38d0371d // indirect github.com/pingcap/errcode v0.3.0 // indirect github.com/pingcap/sysutil v1.0.1-0.20230407040306-fb007c5aff21 // indirect - github.com/pingcap/tidb-dashboard v0.0.0-20240326110213-9768844ff5d7 // indirect + github.com/pingcap/tidb-dashboard v0.0.0-20240612100141-91f6c281e441 // indirect github.com/pingcap/tipb v0.0.0-20220718022156-3e2483c20a9e // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/power-devops/perfstat v0.0.0-20221212215047-62379fc7944b // indirect diff --git a/tools/go.sum b/tools/go.sum index e5692fa4312..c2656b3e656 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -372,8 +372,8 @@ github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3 h1:HR/ylkkLmGdSSDaD8 github.com/pingcap/log v1.1.1-0.20221110025148-ca232912c9f3/go.mod h1:DWQW5jICDR7UJh4HtxXSM20Churx4CQL0fwL/SoOSA4= github.com/pingcap/sysutil v1.0.1-0.20230407040306-fb007c5aff21 h1:QV6jqlfOkh8hqvEAgwBZa+4bSgO0EeKC7s5c6Luam2I= github.com/pingcap/sysutil v1.0.1-0.20230407040306-fb007c5aff21/go.mod h1:QYnjfA95ZaMefyl1NO8oPtKeb8pYUdnDVhQgf+qdpjM= -github.com/pingcap/tidb-dashboard v0.0.0-20240326110213-9768844ff5d7 h1:eFu98FbfJB7PKWOtkaV6YNXXJWqDhczQX56j/iucgU4= -github.com/pingcap/tidb-dashboard v0.0.0-20240326110213-9768844ff5d7/go.mod h1:ucZBRz52icb23T/5Z4CsuUHmarYiin7p2MeiVBe+o8c= +github.com/pingcap/tidb-dashboard v0.0.0-20240612100141-91f6c281e441 h1:01flLztcoWBeT5pe69Q8LAB2Hty0s9Rqc3RvHU4AQK8= +github.com/pingcap/tidb-dashboard v0.0.0-20240612100141-91f6c281e441/go.mod h1:ucZBRz52icb23T/5Z4CsuUHmarYiin7p2MeiVBe+o8c= github.com/pingcap/tipb v0.0.0-20220718022156-3e2483c20a9e h1:FBaTXU8C3xgt/drM58VHxojHo/QoG1oPsgWTGvaSpO4= github.com/pingcap/tipb v0.0.0-20220718022156-3e2483c20a9e/go.mod h1:A7mrd7WHBl1o63LE2bIBGEJMTNWXqhgmYiOvMLxozfs= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= From 41e63768870fd86aaf78f499503a601249a7db39 Mon Sep 17 00:00:00 2001 From: Hu# Date: Fri, 21 Jun 2024 14:31:18 +0800 Subject: [PATCH 4/4] tools/simulator: support deleting a specified store (#8246) ref tikv/pd#8135 Signed-off-by: husharp Co-authored-by: ti-chi-bot[bot] <108142056+ti-chi-bot[bot]@users.noreply.github.com> --- conf/simconfig.toml | 8 +- .../simulator/cases/balance_leader.go | 21 ++++- .../simulator/cases/balance_region.go | 28 ++++-- tools/pd-simulator/simulator/cases/cases.go | 2 +- .../cases/diagnose_label_isolation.go | 93 ++++++++++++------- .../simulator/cases/diagnose_rule.go | 52 +++++++---- .../pd-simulator/simulator/cases/hot_read.go | 38 ++++++-- .../pd-simulator/simulator/cases/hot_write.go | 49 +++++++--- .../simulator/cases/import_data.go | 22 +++-- .../simulator/cases/makeup_down_replica.go | 2 +- .../simulator/cases/region_merge.go | 17 +++- .../simulator/cases/region_split.go | 11 ++- tools/pd-simulator/simulator/drive.go | 6 +- tools/pd-simulator/simulator/event.go | 26 ++++-- 14 files changed, 258 insertions(+), 117 deletions(-) diff --git a/conf/simconfig.toml b/conf/simconfig.toml index 428ee61e508..c0edb182652 100644 --- a/conf/simconfig.toml +++ b/conf/simconfig.toml @@ -1,8 +1,10 @@ # PD Simulator Configuration -[tick] ## the tick interval when starting PD inside (default: "100ms") sim-tick-interval = "100ms" +total-store = 10 +total-region = 10000 +case-name = "balance-leader" [store] ## the capacity size of a new store in GB (default: 1024) @@ -11,8 +13,8 @@ store-capacity = 1024 store-available = 1024 ## the io rate of a new store in MB/s (default: 40) store-io-per-second = 40 -## the version of a new store (default: "2.1.0") -store-version = "2.1.0" +## the version of a new store (default: "8.1.0") +store-version = "8.1.0" ## the meaning of these configurations below are similar with config.toml [server] diff --git a/tools/pd-simulator/simulator/cases/balance_leader.go b/tools/pd-simulator/simulator/cases/balance_leader.go index fd9028bc91a..1dad09850a5 100644 --- a/tools/pd-simulator/simulator/cases/balance_leader.go +++ b/tools/pd-simulator/simulator/cases/balance_leader.go @@ -28,12 +28,15 @@ func newBalanceLeader(config *sc.SimConfig) *Case { totalStore := config.TotalStore totalRegion := config.TotalRegion + allStores := make(map[uint64]struct{}, totalStore) replica := int(config.ServerConfig.Replication.MaxReplicas) for i := 0; i < totalStore; i++ { + id := simutil.IDAllocator.NextID() simCase.Stores = append(simCase.Stores, &Store{ - ID: simutil.IDAllocator.NextID(), + ID: id, Status: metapb.StoreState_Up, }) + allStores[id] = struct{}{} } leaderStoreID := simCase.Stores[totalStore-1].ID @@ -58,10 +61,18 @@ func newBalanceLeader(config *sc.SimConfig) *Case { }) } - simCase.Checker = func(regions *core.RegionsInfo, _ []info.StoreStats) bool { - for i := 1; i <= totalStore; i++ { - leaderCount := regions.GetStoreLeaderCount(uint64(i)) - if !isUniform(leaderCount, totalRegion/totalStore) { + simCase.Checker = func(stores []*metapb.Store, regions *core.RegionsInfo, _ []info.StoreStats) bool { + for _, store := range stores { + if store.NodeState == metapb.NodeState_Removed { + delete(allStores, store.GetId()) + } + } + if len(allStores) == 0 { + return false + } + for storeID := range allStores { + leaderCount := regions.GetStoreLeaderCount(storeID) + if !isUniform(leaderCount, totalRegion/len(allStores)) { return false } } diff --git a/tools/pd-simulator/simulator/cases/balance_region.go b/tools/pd-simulator/simulator/cases/balance_region.go index 82a7ac2d704..8798a656fd7 100644 --- a/tools/pd-simulator/simulator/cases/balance_region.go +++ b/tools/pd-simulator/simulator/cases/balance_region.go @@ -30,6 +30,7 @@ func newRedundantBalanceRegion(config *sc.SimConfig) *Case { totalStore := config.TotalStore totalRegion := config.TotalRegion replica := int(config.ServerConfig.Replication.MaxReplicas) + allStores := make(map[uint64]struct{}, totalStore) for i := 0; i < totalStore; i++ { s := &Store{ @@ -40,6 +41,7 @@ func newRedundantBalanceRegion(config *sc.SimConfig) *Case { s.HasExtraUsedSpace = true } simCase.Stores = append(simCase.Stores, s) + allStores[s.ID] = struct{}{} } for i := 0; i < totalRegion; i++ { @@ -57,21 +59,27 @@ func newRedundantBalanceRegion(config *sc.SimConfig) *Case { }) } - storesLastUpdateTime := make([]int64, totalStore+1) - storeLastAvailable := make([]uint64, totalStore+1) - simCase.Checker = func(_ *core.RegionsInfo, stats []info.StoreStats) bool { + storesLastUpdateTime := make(map[uint64]int64, totalStore) + storeLastAvailable := make(map[uint64]uint64, totalStore) + simCase.Checker = func(stores []*metapb.Store, _ *core.RegionsInfo, stats []info.StoreStats) bool { + for _, store := range stores { + if store.NodeState == metapb.NodeState_Removed { + delete(allStores, store.GetId()) + } + } + curTime := time.Now().Unix() - for i := 1; i <= totalStore; i++ { - available := stats[i].GetAvailable() - if curTime-storesLastUpdateTime[i] > 60 { - if storeLastAvailable[i] != available { + for storeID := range allStores { + available := stats[storeID].GetAvailable() + if curTime-storesLastUpdateTime[storeID] > 60 { + if storeLastAvailable[storeID] != available { return false } - if stats[i].ToCompactionSize != 0 { + if stats[storeID].ToCompactionSize != 0 { return false } - storesLastUpdateTime[i] = curTime - storeLastAvailable[i] = available + storesLastUpdateTime[storeID] = curTime + storeLastAvailable[storeID] = available } else { return false } diff --git a/tools/pd-simulator/simulator/cases/cases.go b/tools/pd-simulator/simulator/cases/cases.go index c4e2f999978..238b54c935a 100644 --- a/tools/pd-simulator/simulator/cases/cases.go +++ b/tools/pd-simulator/simulator/cases/cases.go @@ -45,7 +45,7 @@ type Region struct { } // CheckerFunc checks if the scheduler is finished. -type CheckerFunc func(*core.RegionsInfo, []info.StoreStats) bool +type CheckerFunc func([]*metapb.Store, *core.RegionsInfo, []info.StoreStats) bool // Case represents a test suite for simulator. type Case struct { diff --git a/tools/pd-simulator/simulator/cases/diagnose_label_isolation.go b/tools/pd-simulator/simulator/cases/diagnose_label_isolation.go index 09037136608..9fe65a3d56a 100644 --- a/tools/pd-simulator/simulator/cases/diagnose_label_isolation.go +++ b/tools/pd-simulator/simulator/cases/diagnose_label_isolation.go @@ -33,6 +33,7 @@ func newLabelNotMatch1(_ *sc.SimConfig) *Case { num1, num2 := 3, 1 storeNum, regionNum := num1+num2, 200 + allStores := make(map[uint64]struct{}, storeNum+1) for i := 0; i < num1; i++ { id := IDAllocator.nextID() simCase.Stores = append(simCase.Stores, &Store{ @@ -40,11 +41,14 @@ func newLabelNotMatch1(_ *sc.SimConfig) *Case { Status: metapb.StoreState_Up, Labels: []*metapb.StoreLabel{{Key: "host", Value: fmt.Sprintf("host%d", id)}}, }) + allStores[id] = struct{}{} } + id := IDAllocator.nextID() simCase.Stores = append(simCase.Stores, &Store{ - ID: IDAllocator.nextID(), + ID: id, Status: metapb.StoreState_Up, }) + allStores[id] = struct{}{} for i := 0; i < regionNum; i++ { peers := []*metapb.Peer{ @@ -61,24 +65,30 @@ func newLabelNotMatch1(_ *sc.SimConfig) *Case { }) } - storesLastUpdateTime := make([]int64, storeNum+1) - storeLastAvailable := make([]uint64, storeNum+1) - simCase.Checker = func(_ *core.RegionsInfo, stats []info.StoreStats) bool { + storesLastUpdateTime := make(map[uint64]int64, storeNum+1) + storeLastAvailable := make(map[uint64]uint64, storeNum+1) + simCase.Checker = func(stores []*metapb.Store, _ *core.RegionsInfo, stats []info.StoreStats) bool { + for _, store := range stores { + if store.NodeState == metapb.NodeState_Removed { + delete(allStores, store.GetId()) + } + } + res := true curTime := time.Now().Unix() storesAvailable := make([]uint64, 0, storeNum+1) - for i := 1; i <= storeNum; i++ { - available := stats[i].GetAvailable() + for storeID := range allStores { + available := stats[storeID].GetAvailable() storesAvailable = append(storesAvailable, available) - if curTime-storesLastUpdateTime[i] > 360 { - if storeLastAvailable[i] != available { + if curTime-storesLastUpdateTime[storeID] > 360 { + if storeLastAvailable[storeID] != available { res = false } - if stats[i].ToCompactionSize != 0 { + if stats[storeID].ToCompactionSize != 0 { res = false } - storesLastUpdateTime[i] = curTime - storeLastAvailable[i] = available + storesLastUpdateTime[storeID] = curTime + storeLastAvailable[storeID] = available } else { res = false } @@ -95,6 +105,7 @@ func newLabelIsolation1(_ *sc.SimConfig) *Case { num1, num2 := 2, 2 storeNum, regionNum := num1+num2, 300 + allStores := make(map[uint64]struct{}, storeNum+1) for i := 0; i < num1; i++ { id := IDAllocator.nextID() simCase.Stores = append(simCase.Stores, &Store{ @@ -102,14 +113,16 @@ func newLabelIsolation1(_ *sc.SimConfig) *Case { Status: metapb.StoreState_Up, Labels: []*metapb.StoreLabel{{Key: "host", Value: fmt.Sprintf("host%d", id)}}, }) + allStores[id] = struct{}{} } - id := IDAllocator.GetID() + 1 for i := 0; i < num2; i++ { + id := IDAllocator.nextID() simCase.Stores = append(simCase.Stores, &Store{ - ID: IDAllocator.nextID(), + ID: id, Status: metapb.StoreState_Up, Labels: []*metapb.StoreLabel{{Key: "host", Value: fmt.Sprintf("host%d", id)}}, }) + allStores[id] = struct{}{} } for i := 0; i < regionNum; i++ { @@ -127,24 +140,30 @@ func newLabelIsolation1(_ *sc.SimConfig) *Case { }) } - storesLastUpdateTime := make([]int64, storeNum+1) - storeLastAvailable := make([]uint64, storeNum+1) - simCase.Checker = func(_ *core.RegionsInfo, stats []info.StoreStats) bool { + storesLastUpdateTime := make(map[uint64]int64, storeNum) + storeLastAvailable := make(map[uint64]uint64, storeNum) + simCase.Checker = func(stores []*metapb.Store, _ *core.RegionsInfo, stats []info.StoreStats) bool { + for _, store := range stores { + if store.NodeState == metapb.NodeState_Removed { + delete(allStores, store.GetId()) + } + } + res := true curTime := time.Now().Unix() storesAvailable := make([]uint64, 0, storeNum+1) - for i := 1; i <= storeNum; i++ { - available := stats[i].GetAvailable() + for storeID := range allStores { + available := stats[storeID].GetAvailable() storesAvailable = append(storesAvailable, available) - if curTime-storesLastUpdateTime[i] > 360 { - if storeLastAvailable[i] != available { + if curTime-storesLastUpdateTime[storeID] > 360 { + if storeLastAvailable[storeID] != available { res = false } - if stats[i].ToCompactionSize != 0 { + if stats[storeID].ToCompactionSize != 0 { res = false } - storesLastUpdateTime[i] = curTime - storeLastAvailable[i] = available + storesLastUpdateTime[storeID] = curTime + storeLastAvailable[storeID] = available } else { res = false } @@ -160,12 +179,14 @@ func newLabelIsolation2(_ *sc.SimConfig) *Case { simCase.Labels = []string{"dc", "zone", "host"} storeNum, regionNum := 5, 200 + allStores := make(map[uint64]struct{}, storeNum) for i := 0; i < storeNum; i++ { id := IDAllocator.nextID() simCase.Stores = append(simCase.Stores, &Store{ ID: id, Status: metapb.StoreState_Up, }) + allStores[id] = struct{}{} } simCase.Stores[0].Labels = []*metapb.StoreLabel{{Key: "dc", Value: "dc1"}, {Key: "zone", Value: "zone1"}, {Key: "host", Value: "host1"}} simCase.Stores[1].Labels = []*metapb.StoreLabel{{Key: "dc", Value: "dc1"}, {Key: "zone", Value: "zone1"}, {Key: "host", Value: "host2"}} @@ -188,24 +209,30 @@ func newLabelIsolation2(_ *sc.SimConfig) *Case { }) } - storesLastUpdateTime := make([]int64, storeNum+1) - storeLastAvailable := make([]uint64, storeNum+1) - simCase.Checker = func(_ *core.RegionsInfo, stats []info.StoreStats) bool { + storesLastUpdateTime := make(map[uint64]int64, storeNum) + storeLastAvailable := make(map[uint64]uint64, storeNum) + simCase.Checker = func(stores []*metapb.Store, _ *core.RegionsInfo, stats []info.StoreStats) bool { + for _, store := range stores { + if store.NodeState == metapb.NodeState_Removed { + delete(allStores, store.GetId()) + } + } + res := true curTime := time.Now().Unix() storesAvailable := make([]uint64, 0, storeNum+1) - for i := 1; i <= storeNum; i++ { - available := stats[i].GetAvailable() + for storeID := range allStores { + available := stats[storeID].GetAvailable() storesAvailable = append(storesAvailable, available) - if curTime-storesLastUpdateTime[i] > 360 { - if storeLastAvailable[i] != available { + if curTime-storesLastUpdateTime[storeID] > 360 { + if storeLastAvailable[storeID] != available { res = false } - if stats[i].ToCompactionSize != 0 { + if stats[storeID].ToCompactionSize != 0 { res = false } - storesLastUpdateTime[i] = curTime - storeLastAvailable[i] = available + storesLastUpdateTime[storeID] = curTime + storeLastAvailable[storeID] = available } else { res = false } diff --git a/tools/pd-simulator/simulator/cases/diagnose_rule.go b/tools/pd-simulator/simulator/cases/diagnose_rule.go index 2cd11b9624a..26f563297ae 100644 --- a/tools/pd-simulator/simulator/cases/diagnose_rule.go +++ b/tools/pd-simulator/simulator/cases/diagnose_rule.go @@ -65,12 +65,14 @@ func newRule1(_ *sc.SimConfig) *Case { }) storeNum, regionNum := 9, 300 + allStores := make(map[uint64]struct{}, storeNum) for i := 0; i < storeNum; i++ { id := IDAllocator.nextID() simCase.Stores = append(simCase.Stores, &Store{ ID: id, Status: metapb.StoreState_Up, }) + allStores[id] = struct{}{} } simCase.Stores[0].Labels = []*metapb.StoreLabel{{Key: "region", Value: "region2"}, {Key: "idc", Value: "idc1"}} simCase.Stores[1].Labels = []*metapb.StoreLabel{{Key: "region", Value: "region2"}, {Key: "idc", Value: "idc1"}} @@ -100,24 +102,30 @@ func newRule1(_ *sc.SimConfig) *Case { }) } - storesLastUpdateTime := make([]int64, storeNum+1) - storeLastAvailable := make([]uint64, storeNum+1) - simCase.Checker = func(_ *core.RegionsInfo, stats []info.StoreStats) bool { + storesLastUpdateTime := make(map[uint64]int64, storeNum) + storeLastAvailable := make(map[uint64]uint64, storeNum) + simCase.Checker = func(stores []*metapb.Store, _ *core.RegionsInfo, stats []info.StoreStats) bool { + for _, store := range stores { + if store.NodeState == metapb.NodeState_Removed { + delete(allStores, store.GetId()) + } + } + res := true curTime := time.Now().Unix() storesAvailable := make([]uint64, 0, storeNum+1) - for i := 1; i <= storeNum; i++ { - available := stats[i].GetAvailable() + for storeID := range allStores { + available := stats[storeID].GetAvailable() storesAvailable = append(storesAvailable, available) - if curTime-storesLastUpdateTime[i] > 360 { - if storeLastAvailable[i] != available { + if curTime-storesLastUpdateTime[storeID] > 360 { + if storeLastAvailable[storeID] != available { res = false } - if stats[i].ToCompactionSize != 0 { + if stats[storeID].ToCompactionSize != 0 { res = false } - storesLastUpdateTime[i] = curTime - storeLastAvailable[i] = available + storesLastUpdateTime[storeID] = curTime + storeLastAvailable[storeID] = available } else { res = false } @@ -150,12 +158,14 @@ func newRule2(_ *sc.SimConfig) *Case { }) storeNum, regionNum := 6, 300 + allStores := make(map[uint64]struct{}, storeNum) for i := 0; i < storeNum; i++ { id := IDAllocator.nextID() simCase.Stores = append(simCase.Stores, &Store{ ID: id, Status: metapb.StoreState_Up, }) + allStores[id] = struct{}{} } simCase.Stores[0].Labels = []*metapb.StoreLabel{{Key: "region", Value: "region1"}} simCase.Stores[1].Labels = []*metapb.StoreLabel{{Key: "region", Value: "region1"}} @@ -181,22 +191,28 @@ func newRule2(_ *sc.SimConfig) *Case { storesLastUpdateTime := make([]int64, storeNum+1) storeLastAvailable := make([]uint64, storeNum+1) - simCase.Checker = func(_ *core.RegionsInfo, stats []info.StoreStats) bool { + simCase.Checker = func(stores []*metapb.Store, _ *core.RegionsInfo, stats []info.StoreStats) bool { + for _, store := range stores { + if store.NodeState == metapb.NodeState_Removed { + delete(allStores, store.GetId()) + } + } + res := true curTime := time.Now().Unix() storesAvailable := make([]uint64, 0, storeNum+1) - for i := 1; i <= storeNum; i++ { - available := stats[i].GetAvailable() + for storeID := range allStores { + available := stats[storeID].GetAvailable() storesAvailable = append(storesAvailable, available) - if curTime-storesLastUpdateTime[i] > 360 { - if storeLastAvailable[i] != available { + if curTime-storesLastUpdateTime[storeID] > 360 { + if storeLastAvailable[storeID] != available { res = false } - if stats[i].ToCompactionSize != 0 { + if stats[storeID].ToCompactionSize != 0 { res = false } - storesLastUpdateTime[i] = curTime - storeLastAvailable[i] = available + storesLastUpdateTime[storeID] = curTime + storeLastAvailable[storeID] = available } else { res = false } diff --git a/tools/pd-simulator/simulator/cases/hot_read.go b/tools/pd-simulator/simulator/cases/hot_read.go index d154886b0a4..7f4d93fb43b 100644 --- a/tools/pd-simulator/simulator/cases/hot_read.go +++ b/tools/pd-simulator/simulator/cases/hot_read.go @@ -15,6 +15,8 @@ package cases import ( + "fmt" + "github.com/docker/go-units" "github.com/pingcap/kvproto/pkg/metapb" "github.com/tikv/pd/pkg/core" @@ -23,18 +25,22 @@ import ( "github.com/tikv/pd/tools/pd-simulator/simulator/simutil" ) +var hotReadStore uint64 = 1 + func newHotRead(config *sc.SimConfig) *Case { var simCase Case totalStore := config.TotalStore totalRegion := config.TotalRegion replica := int(config.ServerConfig.Replication.MaxReplicas) - + allStores := make(map[uint64]struct{}, totalStore) // Initialize the cluster for i := 0; i < totalStore; i++ { + id := simutil.IDAllocator.NextID() simCase.Stores = append(simCase.Stores, &Store{ - ID: simutil.IDAllocator.NextID(), + ID: id, Status: metapb.StoreState_Up, }) + allStores[id] = struct{}{} } for i := 0; i < totalRegion; i++ { @@ -54,12 +60,18 @@ func newHotRead(config *sc.SimConfig) *Case { }) } + // select the first store as hot read store + for store := range allStores { + hotReadStore = store + break + } + // Events description - // select regions on store 1 as hot read regions. + // select regions on `hotReadStore` as hot read regions. selectRegionNum := 4 * totalStore readFlow := make(map[uint64]int64, selectRegionNum) for _, r := range simCase.Regions { - if r.Leader.GetStoreId() == 1 { + if r.Leader.GetStoreId() == hotReadStore { readFlow[r.ID] = 128 * units.MiB if len(readFlow) == selectRegionNum { break @@ -72,15 +84,25 @@ func newHotRead(config *sc.SimConfig) *Case { } simCase.Events = []EventDescriptor{e} // Checker description - simCase.Checker = func(regions *core.RegionsInfo, _ []info.StoreStats) bool { - leaderCount := make([]int, totalStore) + simCase.Checker = func(stores []*metapb.Store, regions *core.RegionsInfo, _ []info.StoreStats) bool { + for _, store := range stores { + if store.NodeState == metapb.NodeState_Removed { + if store.Id == hotReadStore { + simutil.Logger.Error(fmt.Sprintf("hot store %d is removed", hotReadStore)) + return true + } + delete(allStores, store.GetId()) + } + } + + leaderCount := make(map[uint64]int, len(allStores)) for id := range readFlow { leaderStore := regions.GetRegion(id).GetLeader().GetStoreId() - leaderCount[int(leaderStore-1)]++ + leaderCount[leaderStore]++ } // check count diff < 2. - var min, max int + var min, max uint64 for i := range leaderCount { if leaderCount[i] > leaderCount[max] { max = i diff --git a/tools/pd-simulator/simulator/cases/hot_write.go b/tools/pd-simulator/simulator/cases/hot_write.go index e73ca6f3ce3..8f08264590d 100644 --- a/tools/pd-simulator/simulator/cases/hot_write.go +++ b/tools/pd-simulator/simulator/cases/hot_write.go @@ -15,6 +15,7 @@ package cases import ( + "fmt" "github.com/docker/go-units" "github.com/pingcap/kvproto/pkg/metapb" "github.com/tikv/pd/pkg/core" @@ -23,18 +24,22 @@ import ( "github.com/tikv/pd/tools/pd-simulator/simulator/simutil" ) +var hotWriteStore uint64 = 1 + func newHotWrite(config *sc.SimConfig) *Case { var simCase Case totalStore := config.TotalStore totalRegion := config.TotalRegion replica := int(config.ServerConfig.Replication.MaxReplicas) - + allStores := make(map[uint64]struct{}, totalStore) // Initialize the cluster for i := 0; i < totalStore; i++ { + id := simutil.IDAllocator.NextID() simCase.Stores = append(simCase.Stores, &Store{ - ID: simutil.IDAllocator.NextID(), + ID: id, Status: metapb.StoreState_Up, }) + allStores[id] = struct{}{} } for i := 0; i < totalRegion; i++ { @@ -54,14 +59,20 @@ func newHotWrite(config *sc.SimConfig) *Case { }) } + // select the first store as hot write store. + for store := range allStores { + hotWriteStore = store + break + } + // Events description - // select regions on store 1 as hot write regions. - selectStoreNum := totalStore - writeFlow := make(map[uint64]int64, selectStoreNum) + // select regions on `hotWriteStore` as hot write regions. + selectRegionNum := totalStore + writeFlow := make(map[uint64]int64, selectRegionNum) for _, r := range simCase.Regions { - if r.Leader.GetStoreId() == 1 { + if r.Leader.GetStoreId() == hotWriteStore { writeFlow[r.ID] = 2 * units.MiB - if len(writeFlow) == selectStoreNum { + if len(writeFlow) == selectRegionNum { break } } @@ -70,23 +81,31 @@ func newHotWrite(config *sc.SimConfig) *Case { e.Step = func(int64) map[uint64]int64 { return writeFlow } - simCase.Events = []EventDescriptor{e} - // Checker description - simCase.Checker = func(regions *core.RegionsInfo, _ []info.StoreStats) bool { - leaderCount := make([]int, totalStore) - peerCount := make([]int, totalStore) + simCase.Checker = func(stores []*metapb.Store, regions *core.RegionsInfo, _ []info.StoreStats) bool { + for _, store := range stores { + if store.NodeState == metapb.NodeState_Removed { + if store.Id == hotWriteStore { + simutil.Logger.Error(fmt.Sprintf("hot store %d is removed", hotReadStore)) + return true + } + delete(allStores, store.GetId()) + } + } + + leaderCount := make(map[uint64]int, len(allStores)) + peerCount := make(map[uint64]int, totalStore) for id := range writeFlow { region := regions.GetRegion(id) - leaderCount[int(region.GetLeader().GetStoreId()-1)]++ + leaderCount[region.GetLeader().GetStoreId()]++ for _, p := range region.GetPeers() { - peerCount[int(p.GetStoreId()-1)]++ + peerCount[p.GetStoreId()]++ } } // check count diff <= 2. - var minLeader, maxLeader, minPeer, maxPeer int + var minLeader, maxLeader, minPeer, maxPeer uint64 for i := range leaderCount { if leaderCount[i] > leaderCount[maxLeader] { maxLeader = i diff --git a/tools/pd-simulator/simulator/cases/import_data.go b/tools/pd-simulator/simulator/cases/import_data.go index b9f448a6cf6..e37aadcfeba 100644 --- a/tools/pd-simulator/simulator/cases/import_data.go +++ b/tools/pd-simulator/simulator/cases/import_data.go @@ -36,13 +36,15 @@ func newImportData(config *sc.SimConfig) *Case { totalStore := config.TotalStore totalRegion := config.TotalRegion replica := int(config.ServerConfig.Replication.MaxReplicas) - + allStores := make(map[uint64]struct{}, totalStore) // Initialize the cluster for i := 0; i < totalStore; i++ { + id := simutil.IDAllocator.NextID() simCase.Stores = append(simCase.Stores, &Store{ - ID: IDAllocator.nextID(), + ID: id, Status: metapb.StoreState_Up, }) + allStores[id] = struct{}{} } for i := 0; i < totalRegion; i++ { @@ -83,7 +85,13 @@ func newImportData(config *sc.SimConfig) *Case { checkCount := uint64(0) var newRegionCount [][3]int var allRegionCount [][3]int - simCase.Checker = func(regions *core.RegionsInfo, _ []info.StoreStats) bool { + simCase.Checker = func(stores []*metapb.Store, regions *core.RegionsInfo, _ []info.StoreStats) bool { + for _, store := range stores { + if store.NodeState == metapb.NodeState_Removed { + delete(allStores, store.GetId()) + } + } + leaderDist := make(map[uint64]int) peerDist := make(map[uint64]int) leaderTotal := 0 @@ -115,9 +123,9 @@ func newImportData(config *sc.SimConfig) *Case { tableLeaderLog = fmt.Sprintf("%s [store %d]:%.2f%%", tableLeaderLog, storeID, float64(leaderCount)/float64(leaderTotal)*100) } } - for storeID := 1; storeID <= 10; storeID++ { - if peerCount, ok := peerDist[uint64(storeID)]; ok { - newRegionCount = append(newRegionCount, [3]int{storeID, int(checkCount), peerCount}) + for storeID := range allStores { + if peerCount, ok := peerDist[storeID]; ok { + newRegionCount = append(newRegionCount, [3]int{int(storeID), int(checkCount), peerCount}) tablePeerLog = fmt.Sprintf("%s [store %d]:%.2f%%", tablePeerLog, storeID, float64(peerCount)/float64(peerTotal)*100) } } @@ -126,7 +134,7 @@ func newImportData(config *sc.SimConfig) *Case { totalPeerLog := fmt.Sprintf("%d peer:", regionTotal*3) isEnd := false var regionProps []float64 - for storeID := uint64(1); storeID <= 10; storeID++ { + for storeID := range allStores { totalLeaderLog = fmt.Sprintf("%s [store %d]:%.2f%%", totalLeaderLog, storeID, float64(regions.GetStoreLeaderCount(storeID))/float64(regionTotal)*100) regionProp := float64(regions.GetStoreRegionCount(storeID)) / float64(regionTotal*3) * 100 regionProps = append(regionProps, regionProp) diff --git a/tools/pd-simulator/simulator/cases/makeup_down_replica.go b/tools/pd-simulator/simulator/cases/makeup_down_replica.go index a5ee63e71a0..ede3c4ba083 100644 --- a/tools/pd-simulator/simulator/cases/makeup_down_replica.go +++ b/tools/pd-simulator/simulator/cases/makeup_down_replica.go @@ -69,7 +69,7 @@ func newMakeupDownReplicas(config *sc.SimConfig) *Case { } simCase.Events = []EventDescriptor{e} - simCase.Checker = func(regions *core.RegionsInfo, _ []info.StoreStats) bool { + simCase.Checker = func(_ []*metapb.Store, regions *core.RegionsInfo, _ []info.StoreStats) bool { if !down { return false } diff --git a/tools/pd-simulator/simulator/cases/region_merge.go b/tools/pd-simulator/simulator/cases/region_merge.go index 8097565d1a7..3d050070203 100644 --- a/tools/pd-simulator/simulator/cases/region_merge.go +++ b/tools/pd-simulator/simulator/cases/region_merge.go @@ -28,12 +28,15 @@ func newRegionMerge(config *sc.SimConfig) *Case { totalStore := config.TotalStore totalRegion := config.TotalRegion replica := int(config.ServerConfig.Replication.MaxReplicas) + allStores := make(map[uint64]struct{}, totalStore) for i := 0; i < totalStore; i++ { + id := simutil.IDAllocator.NextID() simCase.Stores = append(simCase.Stores, &Store{ - ID: simutil.IDAllocator.NextID(), + ID: id, Status: metapb.StoreState_Up, }) + allStores[id] = struct{}{} } for i := 0; i < totalRegion; i++ { @@ -54,10 +57,16 @@ func newRegionMerge(config *sc.SimConfig) *Case { } // Checker description mergeRatio := 4 // when max-merge-region-size is 20, per region will reach 40MB - simCase.Checker = func(regions *core.RegionsInfo, _ []info.StoreStats) bool { + simCase.Checker = func(stores []*metapb.Store, regions *core.RegionsInfo, _ []info.StoreStats) bool { + for _, store := range stores { + if store.NodeState == metapb.NodeState_Removed { + delete(allStores, store.GetId()) + } + } + currentPeerCount := 0 - for i := 1; i <= totalStore; i++ { - currentPeerCount += regions.GetStoreRegionCount(uint64(i)) + for storeID := range allStores { + currentPeerCount += regions.GetStoreRegionCount(storeID) } return isUniform(currentPeerCount, totalRegion*replica/mergeRatio) } diff --git a/tools/pd-simulator/simulator/cases/region_split.go b/tools/pd-simulator/simulator/cases/region_split.go index 7b712f4dc48..b158541e5cc 100644 --- a/tools/pd-simulator/simulator/cases/region_split.go +++ b/tools/pd-simulator/simulator/cases/region_split.go @@ -25,12 +25,15 @@ import ( func newRegionSplit(config *sc.SimConfig) *Case { var simCase Case totalStore := config.TotalStore + allStores := make(map[uint64]struct{}, totalStore) for i := 0; i < totalStore; i++ { + id := uint64(i) simCase.Stores = append(simCase.Stores, &Store{ - ID: uint64(i), + ID: id, Status: metapb.StoreState_Up, }) + allStores[id] = struct{}{} } peers := []*metapb.Peer{ {Id: 4, StoreId: 1}, @@ -55,9 +58,9 @@ func newRegionSplit(config *sc.SimConfig) *Case { simCase.Events = []EventDescriptor{e} // Checker description - simCase.Checker = func(regions *core.RegionsInfo, _ []info.StoreStats) bool { - for i := 1; i <= totalStore; i++ { - peerCount := regions.GetStoreRegionCount(uint64(i)) + simCase.Checker = func(_ []*metapb.Store, regions *core.RegionsInfo, _ []info.StoreStats) bool { + for storeID := range allStores { + peerCount := regions.GetStoreRegionCount(storeID) if peerCount < 5 { return false } diff --git a/tools/pd-simulator/simulator/drive.go b/tools/pd-simulator/simulator/drive.go index 0296710b705..e6c953dab87 100644 --- a/tools/pd-simulator/simulator/drive.go +++ b/tools/pd-simulator/simulator/drive.go @@ -179,16 +179,18 @@ func (d *Driver) Tick() { // Check checks if the simulation is completed. func (d *Driver) Check() bool { length := uint64(len(d.conn.Nodes) + 1) - for index := range d.conn.Nodes { + var stores []*metapb.Store + for index, s := range d.conn.Nodes { if index >= length { length = index + 1 } + stores = append(stores, s.Store) } stats := make([]info.StoreStats, length) for index, node := range d.conn.Nodes { stats[index] = *node.stats } - return d.simCase.Checker(d.raftEngine.regionsInfo, stats) + return d.simCase.Checker(stores, d.raftEngine.regionsInfo, stats) } // Start starts all nodes. diff --git a/tools/pd-simulator/simulator/event.go b/tools/pd-simulator/simulator/event.go index 8e01a8f5f40..7b818a27b22 100644 --- a/tools/pd-simulator/simulator/event.go +++ b/tools/pd-simulator/simulator/event.go @@ -19,6 +19,7 @@ import ( "fmt" "math/rand" "net/http" + "strconv" "sync" "github.com/pingcap/kvproto/pkg/metapb" @@ -74,7 +75,12 @@ func (e *eventHandler) createEvent(w http.ResponseWriter, r *http.Request) { e.er.addEvent(&AddNode{}) return case "down-node": - e.er.addEvent(&DownNode{}) + id := r.URL.Query().Get("node-id") + var ID int + if len(id) != 0 { + ID, _ = strconv.Atoi(id) + } + e.er.addEvent(&DownNode{ID: ID}) return default: } @@ -202,17 +208,25 @@ func (*AddNode) Run(raft *RaftEngine, _ int64) bool { } // DownNode deletes nodes. -type DownNode struct{} +type DownNode struct { + ID int +} // Run implements the event interface. -func (*DownNode) Run(raft *RaftEngine, _ int64) bool { - nodes := raft.conn.getNodes() +func (e *DownNode) Run(raft *RaftEngine, _ int64) bool { + nodes := raft.conn.Nodes if len(nodes) == 0 { simutil.Logger.Error("can not find any node") return false } - i := rand.Intn(len(nodes)) - node := nodes[i] + var node *Node + if e.ID == 0 { + arrNodes := raft.conn.getNodes() + i := rand.Intn(len(arrNodes)) + node = nodes[arrNodes[i].Store.GetId()] + } else { + node = nodes[uint64(e.ID)] + } if node == nil { simutil.Logger.Error("node is not existed", zap.Uint64("node-id", node.Id)) return false