diff --git a/config/config.go b/config/config.go index 603ac291099a0..89e4dd27293d0 100644 --- a/config/config.go +++ b/config/config.go @@ -20,6 +20,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net/url" "os" "reflect" "strings" @@ -33,6 +34,7 @@ import ( "github.com/pingcap/tidb/util/logutil" tracing "github.com/uber/jaeger-client-go/config" "go.uber.org/atomic" + "go.uber.org/zap" ) // Config number limitations @@ -157,6 +159,16 @@ func (b *nullableBool) UnmarshalText(text []byte) error { return nil } +func (b nullableBool) MarshalText() ([]byte, error) { + if !b.IsValid { + return []byte(""), nil + } + if b.IsTrue { + return []byte("true"), nil + } + return []byte("false"), nil +} + func (b *nullableBool) UnmarshalJSON(data []byte) error { var err error var v interface{} @@ -864,3 +876,28 @@ const ( OOMActionCancel = "cancel" OOMActionLog = "log" ) + +// ParsePath parses this path. +func ParsePath(path string) (etcdAddrs []string, disableGC bool, err error) { + var u *url.URL + u, err = url.Parse(path) + if err != nil { + err = errors.Trace(err) + return + } + if strings.ToLower(u.Scheme) != "tikv" { + err = errors.Errorf("Uri scheme expected [tikv] but found [%s]", u.Scheme) + logutil.BgLogger().Error("parsePath error", zap.Error(err)) + return + } + switch strings.ToLower(u.Query().Get("disableGC")) { + case "true": + disableGC = true + case "false", "": + default: + err = errors.New("disableGC flag should be true/false") + return + } + etcdAddrs = strings.Split(u.Host, ",") + return +} diff --git a/config/config_handler.go b/config/config_handler.go new file mode 100644 index 0000000000000..e34b8626155d6 --- /dev/null +++ b/config/config_handler.go @@ -0,0 +1,223 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "bytes" + "context" + "fmt" + "sync" + "sync/atomic" + "time" + + "github.com/BurntSushi/toml" + "github.com/pingcap/errors" + "github.com/pingcap/kvproto/pkg/configpb" + "github.com/pingcap/pd/client" + "github.com/pingcap/tidb/util/logutil" + "go.uber.org/zap" +) + +// ConfHandler is used to load and update config online. +// See https://github.com/pingcap/tidb/pull/13660 for more details. +type ConfHandler interface { + Start() + Close() + GetConfig() *Config // read only +} + +// ConfReloadFunc is used to reload the config to make it work. +type ConfReloadFunc func(oldConf, newConf *Config) + +// NewConfHandler creates a new ConfHandler according to the local config. +func NewConfHandler(localConf *Config, reloadFunc ConfReloadFunc) (ConfHandler, error) { + switch defaultConf.Store { + case "tikv": + return newPDConfHandler(localConf, reloadFunc, nil) + default: + return &constantConfHandler{localConf}, nil + } +} + +// constantConfHandler is used in local or debug environment. +// It always returns the constant config initialized at the beginning. +type constantConfHandler struct { + conf *Config +} + +func (cch *constantConfHandler) Start() {} + +func (cch *constantConfHandler) Close() {} + +func (cch *constantConfHandler) GetConfig() *Config { return cch.conf } + +const ( + pdConfHandlerRefreshInterval = 30 * time.Second + tidbComponentName = "tidb" +) + +type pdConfHandler struct { + id string // ip:port + version *configpb.Version + curConf atomic.Value + interval time.Duration + wg sync.WaitGroup + exit chan struct{} + pdConfCli pd.ConfigClient + reloadFunc func(oldConf, newConf *Config) +} + +func newPDConfHandler(localConf *Config, reloadFunc ConfReloadFunc, + newPDCliFunc func([]string, pd.SecurityOption) (pd.ConfigClient, error), // for test +) (*pdConfHandler, error) { + addresses, _, err := ParsePath(localConf.Path) + if err != nil { + return nil, err + } + host := localConf.Host + if localConf.AdvertiseAddress != "" { + host = localConf.AdvertiseAddress + } + id := fmt.Sprintf("%v:%v", host, localConf.Port) + security := localConf.Security + if newPDCliFunc == nil { + newPDCliFunc = pd.NewConfigClient + } + pdCli, err := newPDCliFunc(addresses, pd.SecurityOption{ + CAPath: security.ClusterSSLCA, + CertPath: security.ClusterSSLCert, + KeyPath: security.ClusterSSLKey, + }) + if err != nil { + return nil, err + } + confContent, err := encodeConfig(localConf) + if err != nil { + return nil, err + } + + // register to PD and get the new default config. + // see https://github.com/pingcap/tidb/pull/13660 for more details. + // suppose port and security config items cannot be change online. + status, version, conf, err := pdCli.Create(context.Background(), new(configpb.Version), tidbComponentName, id, confContent) + if err != nil { + logutil.Logger(context.Background()).Warn("register the config to PD error, local config will be used", zap.Error(err)) + } else if status.Code != configpb.StatusCode_OK && status.Code != configpb.StatusCode_WRONG_VERSION { + logutil.Logger(context.Background()).Warn("invalid status when registering the config to PD", zap.String("code", status.Code.String()), zap.String("errmsg", status.Message)) + conf = "" + } + + tmpConf := *localConf // use the local config if the remote config is invalid + newConf := &tmpConf + if conf != "" { + newConf, err = decodeConfig(conf) + if err != nil { + logutil.Logger(context.Background()).Warn("decode remote config error", zap.Error(err)) + newConf = &tmpConf + } else if err := newConf.Valid(); err != nil { + logutil.Logger(context.Background()).Warn("invalid remote config", zap.Error(err)) + newConf = &tmpConf + } + } + + ch := &pdConfHandler{ + id: id, + version: version, + interval: pdConfHandlerRefreshInterval, + exit: make(chan struct{}), + pdConfCli: pdCli, + reloadFunc: reloadFunc, + } + ch.curConf.Store(newConf) + return ch, nil +} + +func (ch *pdConfHandler) Start() { + ch.wg.Add(1) + go ch.run() +} + +func (ch *pdConfHandler) Close() { + close(ch.exit) + ch.wg.Wait() + ch.pdConfCli.Close() +} + +func (ch *pdConfHandler) GetConfig() *Config { + return ch.curConf.Load().(*Config) +} + +func (ch *pdConfHandler) run() { + defer func() { + if r := recover(); r != nil { + logutil.Logger(context.Background()).Error("panic in the recoverable goroutine", + zap.Reflect("r", r), + zap.Stack("stack trace")) + } + ch.wg.Done() + }() + + for { + select { + case <-time.After(ch.interval): + // fetch new config from PD + status, version, newConfContent, err := ch.pdConfCli.Get(context.Background(), ch.version, tidbComponentName, ch.id) + if err != nil { + logutil.Logger(context.Background()).Error("PDConfHandler fetch new config error", zap.Error(err)) + continue + } + if status.Code == configpb.StatusCode_OK { // StatusCode_OK represents the request is successful and there is no change. + continue + } + if status.Code != configpb.StatusCode_WRONG_VERSION { + // StatusCode_WRONG_VERSION represents the request is successful and the config has been updated. + logutil.Logger(context.Background()).Error("PDConfHandler fetch new config PD error", + zap.Int("code", int(status.Code)), zap.String("message", status.Message)) + continue + } + newConf, err := decodeConfig(newConfContent) + if err != nil { + logutil.Logger(context.Background()).Error("PDConfHandler decode config error", zap.Error(err)) + continue + } + if err := newConf.Valid(); err != nil { + logutil.Logger(context.Background()).Error("PDConfHandler invalid config", zap.Error(err)) + continue + } + + ch.reloadFunc(ch.curConf.Load().(*Config), newConf) + ch.curConf.Store(newConf) + logutil.Logger(context.Background()).Info("PDConfHandler update config successfully", + zap.String("fromVersion", ch.version.String()), zap.String("toVersion", version.String())) + ch.version = version + case <-ch.exit: + return + } + } +} + +func encodeConfig(conf *Config) (string, error) { + confBuf := bytes.NewBuffer(nil) + te := toml.NewEncoder(confBuf) + if err := te.Encode(conf); err != nil { + return "", errors.New("encode config error=" + err.Error()) + } + return confBuf.String(), nil +} + +func decodeConfig(content string) (*Config, error) { + c := new(Config) + _, err := toml.Decode(content, c) + return c, err +} diff --git a/config/config_handler_test.go b/config/config_handler_test.go new file mode 100644 index 0000000000000..1c70dde2061f5 --- /dev/null +++ b/config/config_handler_test.go @@ -0,0 +1,132 @@ +// Copyright 2020 PingCAP, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// See the License for the specific language governing permissions and +// limitations under the License. + +package config + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + "time" + + . "github.com/pingcap/check" + "github.com/pingcap/kvproto/pkg/configpb" + "github.com/pingcap/pd/client" +) + +type mockPDConfigClient struct { + status *configpb.Status + version *configpb.Version + confContent atomic.Value + err error +} + +var mockPDConfigClient0 = new(mockPDConfigClient) +var newMockPDConfigClientErr error + +func newMockPDConfigClient([]string, pd.SecurityOption) (pd.ConfigClient, error) { + return mockPDConfigClient0, newMockPDConfigClientErr +} + +func (mc *mockPDConfigClient) GetClusterID(ctx context.Context) uint64 { + return 0 +} + +func (mc *mockPDConfigClient) Create(ctx context.Context, v *configpb.Version, component, componentID, config string) (*configpb.Status, *configpb.Version, string, error) { + return mc.status, mc.version, mc.confContent.Load().(string), mc.err +} + +func (mc *mockPDConfigClient) Get(ctx context.Context, v *configpb.Version, component, componentID string) (*configpb.Status, *configpb.Version, string, error) { + return mc.status, mc.version, mc.confContent.Load().(string), mc.err +} + +func (mc *mockPDConfigClient) Update(ctx context.Context, v *configpb.Version, kind *configpb.ConfigKind, entries []*configpb.ConfigEntry) (*configpb.Status, *configpb.Version, error) { + return nil, nil, nil +} + +func (mc *mockPDConfigClient) Delete(ctx context.Context, v *configpb.Version, kind *configpb.ConfigKind) (*configpb.Status, error) { + return nil, nil +} + +func (mc *mockPDConfigClient) Close() {} + +func (s *testConfigSuite) TestConstantConfHandler(c *C) { + conf := defaultConf + conf.Store = "mock" + ch, err := NewConfHandler(&conf, nil) + c.Assert(err, IsNil) + _, ok := ch.(*constantConfHandler) + c.Assert(ok, IsTrue) + c.Assert(ch.GetConfig(), Equals, &conf) +} + +func (s *testConfigSuite) TestPDConfHandler(c *C) { + conf := defaultConf + + // wrong path + conf.Store = "tikv" + conf.Path = "WRONGPATH" + _, err := newPDConfHandler(&conf, nil, newMockPDConfigClient) + c.Assert(err, NotNil) + + // error when creating PD config client + conf.Path = "tikv://node1:2379" + newMockPDConfigClientErr = fmt.Errorf("") + _, err = newPDConfHandler(&conf, nil, newMockPDConfigClient) + c.Assert(err, NotNil) + + // error when registering + newMockPDConfigClientErr = nil + mockPDConfigClient0.err = fmt.Errorf("") + mockPDConfigClient0.confContent.Store("") + ch, err := newPDConfHandler(&conf, nil, newMockPDConfigClient) + c.Assert(err, IsNil) // the local config will be used + ch.Close() + + // wrong response when registering + mockPDConfigClient0.err = nil + mockPDConfigClient0.status = &configpb.Status{Code: configpb.StatusCode_UNKNOWN} + ch, err = newPDConfHandler(&conf, nil, newMockPDConfigClient) + c.Assert(err, IsNil) + ch.Close() + + // create client successfully + mockPDConfigClient0.status.Code = configpb.StatusCode_WRONG_VERSION + content, _ := encodeConfig(&conf) + mockPDConfigClient0.confContent.Store(content) + ch, err = newPDConfHandler(&conf, nil, newMockPDConfigClient) + c.Assert(err, IsNil) + ch.Close() + + // update log level + wg := sync.WaitGroup{} + wg.Add(1) + mockReloadFunc := func(oldConf, newConf *Config) { + wg.Done() + c.Assert(oldConf.Log.Level, Equals, "info") + c.Assert(newConf.Log.Level, Equals, "debug") + } + ch, err = newPDConfHandler(&conf, mockReloadFunc, newMockPDConfigClient) + c.Assert(err, IsNil) + ch.interval = time.Second + ch.Start() + c.Assert(ch.GetConfig().Log.Level, Equals, "info") + newConf := conf + newConf.Log.Level = "debug" + newContent, _ := encodeConfig(&newConf) + mockPDConfigClient0.confContent.Store(newContent) + wg.Wait() + c.Assert(ch.GetConfig().Log.Level, Equals, "debug") + ch.Close() +} diff --git a/config/config_test.go b/config/config_test.go index 04695ac6c4d3b..454ad5428fe8f 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -404,3 +404,16 @@ func (s *testConfigSuite) TestAllowAutoRandomValid(c *C) { checkValid(false, true, true) checkValid(false, false, true) } + +func (s *testConfigSuite) TestParsePath(c *C) { + etcdAddrs, disableGC, err := ParsePath("tikv://node1:2379,node2:2379") + c.Assert(err, IsNil) + c.Assert(etcdAddrs, DeepEquals, []string{"node1:2379", "node2:2379"}) + c.Assert(disableGC, IsFalse) + + _, _, err = ParsePath("tikv://node1:2379") + c.Assert(err, IsNil) + _, disableGC, err = ParsePath("tikv://node1:2379?disableGC=true") + c.Assert(err, IsNil) + c.Assert(disableGC, IsTrue) +} diff --git a/go.mod b/go.mod index f2f5e6247ed71..2315ac125bcb8 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,7 @@ require ( github.com/pingcap/kvproto v0.0.0-20200108025604-a4dc183d2af5 github.com/pingcap/log v0.0.0-20200117041106-d28c14d3b1cd github.com/pingcap/parser v0.0.0-20200207090844-d65f5147dd9f - github.com/pingcap/pd v1.1.0-beta.0.20191219054547-4d65bbefbc6d + github.com/pingcap/pd v1.1.0-beta.0.20200106144140-f5a7aa985497 github.com/pingcap/sysutil v0.0.0-20191216090214-5f9620d22b3b github.com/pingcap/tidb-tools v3.0.6-0.20191106033616-90632dda3863+incompatible github.com/pingcap/tipb v0.0.0-20200201101609-1a2e9c441455 diff --git a/go.sum b/go.sum index a2c9fcd98c8b1..a7b097355a2e8 100644 --- a/go.sum +++ b/go.sum @@ -185,6 +185,7 @@ github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKw github.com/opentracing/opentracing-go v1.0.2 h1:3jA2P6O1F9UOrWVpwrIo17pu01KWvNWg4X946/Y5Zwg= github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= github.com/pelletier/go-toml v1.3.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo= +github.com/phf/go-queue v0.0.0-20170504031614-9abe38d0371d/go.mod h1:lXfE4PvvTW5xOjO6Mba8zDPyw8M93B6AQ7frTGnMlA8= github.com/pingcap/check v0.0.0-20190102082844-67f458068fc8/go.mod h1:B1+S9LNcuMyLH/4HMTViQOJevkGiik3wW2AN9zb2fNQ= github.com/pingcap/check v0.0.0-20191107115940-caf2b9e6ccf4/go.mod h1:PYMCGwN0JHjoqGr3HrZoD+b8Tgx8bKnArhSq8YVzUMc= github.com/pingcap/check v0.0.0-20191216031241-8a5a85928f12 h1:rfD9v3+ppLPzoQBgZev0qYCpegrwyFx/BUpkApEiKdY= @@ -213,6 +214,9 @@ github.com/pingcap/parser v0.0.0-20200207090844-d65f5147dd9f h1:uUrZ94J2/tsmCXHj github.com/pingcap/parser v0.0.0-20200207090844-d65f5147dd9f/go.mod h1:9v0Edh8IbgjGYW2ArJr19E+bvL8zKahsFp+ixWeId+4= github.com/pingcap/pd v1.1.0-beta.0.20191219054547-4d65bbefbc6d h1:Ui80aiLTyd0EZD56o2tjFRYpHfhazBjtBdKeR8UoTFY= github.com/pingcap/pd v1.1.0-beta.0.20191219054547-4d65bbefbc6d/go.mod h1:CML+b1JVjN+VbDijaIcUSmuPgpDjXEY7UiOx5yDP8eE= +github.com/pingcap/pd v1.1.0-beta.0.20200106144140-f5a7aa985497 h1:FzLErYtcXnSxtC469OuVDlgBbh0trJZzNxw0mNKzyls= +github.com/pingcap/pd v1.1.0-beta.0.20200106144140-f5a7aa985497/go.mod h1:cfT/xu4Zz+Tkq95QrLgEBZ9ikRcgzy4alHqqoaTftqI= +github.com/pingcap/pd v2.1.19+incompatible/go.mod h1:nD3+EoYes4+aNNODO99ES59V83MZSI+dFbhyr667a0E= github.com/pingcap/sysutil v0.0.0-20191216090214-5f9620d22b3b h1:EEyo/SCRswLGuSk+7SB86Ak1p8bS6HL1Mi4Dhyuv6zg= github.com/pingcap/sysutil v0.0.0-20191216090214-5f9620d22b3b/go.mod h1:EB/852NMQ+aRKioCpToQ94Wl7fktV+FNnxf3CX/TTXI= github.com/pingcap/tidb-tools v3.0.6-0.20191106033616-90632dda3863+incompatible h1:H1jg0aDWz2SLRh3hNBo2HFtnuHtudIUvBumU7syRkic= @@ -243,6 +247,8 @@ github.com/prometheus/procfs v0.0.2 h1:6LJUbpNm42llc4HRCuvApCSWB/WfhuNo9K98Q9sNG github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237 h1:HQagqIiBmr8YXawX/le3+O26N+vPPC1PtjaF3mwnook= github.com/remyoudompheng/bigfft v0.0.0-20190728182440-6a916e37a237/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rleungx/pd v1.1.0-beta.0.20191226095457-5d9028d0aa90 h1:SPpOwBQqt25Sn597QgToYConcEn4ncMBTLV/cEZpnk8= +github.com/rleungx/pd v1.1.0-beta.0.20191226095457-5d9028d0aa90/go.mod h1:CML+b1JVjN+VbDijaIcUSmuPgpDjXEY7UiOx5yDP8eE= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/sergi/go-diff v1.0.1-0.20180205163309-da645544ed44/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= diff --git a/store/mockstore/mocktikv/pd.go b/store/mockstore/mocktikv/pd.go index 7fbda80bafc27..6c638d11dee99 100644 --- a/store/mockstore/mocktikv/pd.go +++ b/store/mockstore/mocktikv/pd.go @@ -20,7 +20,7 @@ import ( "github.com/pingcap/kvproto/pkg/metapb" "github.com/pingcap/kvproto/pkg/pdpb" - pd "github.com/pingcap/pd/client" + "github.com/pingcap/pd/client" ) // Use global variables to prevent pdClients from creating duplicate timestamps. @@ -45,6 +45,10 @@ func NewPDClient(cluster *Cluster) pd.Client { } } +func (c *pdClient) ConfigClient() pd.ConfigClient { + return nil +} + func (c *pdClient) GetClusterID(ctx context.Context) uint64 { return 1 } diff --git a/store/tikv/kv.go b/store/tikv/kv.go index a7f684f7116db..a5ebedf7eec04 100644 --- a/store/tikv/kv.go +++ b/store/tikv/kv.go @@ -18,8 +18,6 @@ import ( "crypto/tls" "fmt" "math/rand" - "net/url" - "strings" "sync" "sync/atomic" "time" @@ -27,7 +25,7 @@ import ( "github.com/opentracing/opentracing-go" "github.com/pingcap/errors" "github.com/pingcap/failpoint" - pd "github.com/pingcap/pd/client" + "github.com/pingcap/pd/client" "github.com/pingcap/tidb/config" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/metrics" @@ -79,7 +77,7 @@ func (d Driver) Open(path string) (kv.Storage, error) { security := config.GetGlobalConfig().Security tikvConfig := config.GetGlobalConfig().TiKVClient txnLocalLatches := config.GetGlobalConfig().TxnLocalLatches - etcdAddrs, disableGC, err := parsePath(path) + etcdAddrs, disableGC, err := config.ParsePath(path) if err != nil { return nil, errors.Trace(err) } @@ -434,30 +432,6 @@ func (s *tikvStore) GetTiKVClient() (client Client) { return s.client } -func parsePath(path string) (etcdAddrs []string, disableGC bool, err error) { - var u *url.URL - u, err = url.Parse(path) - if err != nil { - err = errors.Trace(err) - return - } - if strings.ToLower(u.Scheme) != "tikv" { - err = errors.Errorf("Uri scheme expected[tikv] but found [%s]", u.Scheme) - logutil.BgLogger().Error("parsePath error", zap.Error(err)) - return - } - switch strings.ToLower(u.Query().Get("disableGC")) { - case "true": - disableGC = true - case "false", "": - default: - err = errors.New("disableGC flag should be true/false") - return - } - etcdAddrs = strings.Split(u.Host, ",") - return -} - func init() { mc.cache = make(map[string]*tikvStore) rand.Seed(time.Now().UnixNano()) diff --git a/store/tikv/store_test.go b/store/tikv/store_test.go index a95966288bf9c..1c908368f2d6a 100644 --- a/store/tikv/store_test.go +++ b/store/tikv/store_test.go @@ -23,7 +23,7 @@ import ( pb "github.com/pingcap/kvproto/pkg/kvrpcpb" "github.com/pingcap/kvproto/pkg/metapb" "github.com/pingcap/kvproto/pkg/pdpb" - pd "github.com/pingcap/pd/client" + "github.com/pingcap/pd/client" "github.com/pingcap/tidb/kv" "github.com/pingcap/tidb/store/mockoracle" "github.com/pingcap/tidb/store/tikv/tikvrpc" @@ -55,19 +55,6 @@ func (s *testStoreSuiteBase) TearDownTest(c *C) { c.Assert(s.store.Close(), IsNil) } -func (s *testStoreSuite) TestParsePath(c *C) { - etcdAddrs, disableGC, err := parsePath("tikv://node1:2379,node2:2379") - c.Assert(err, IsNil) - c.Assert(etcdAddrs, DeepEquals, []string{"node1:2379", "node2:2379"}) - c.Assert(disableGC, IsFalse) - - _, _, err = parsePath("tikv://node1:2379") - c.Assert(err, IsNil) - _, disableGC, err = parsePath("tikv://node1:2379?disableGC=true") - c.Assert(err, IsNil) - c.Assert(disableGC, IsTrue) -} - func (s *testStoreSuite) TestOracle(c *C) { o := &mockoracle.MockOracle{} s.store.oracle = o @@ -117,6 +104,10 @@ type mockPDClient struct { stop bool } +func (c *mockPDClient) ConfigClient() pd.ConfigClient { + return nil +} + func (c *mockPDClient) enable() { c.Lock() defer c.Unlock()