From bd0230152a2c9e4718a30a5a16627d6dea9005d1 Mon Sep 17 00:00:00 2001 From: Yecheng Fu Date: Mon, 20 Jul 2020 14:11:41 +0800 Subject: [PATCH] use a generic way to test methods of PD Client --- pkg/controller/tidb_control.go | 2 +- pkg/pdapi/fake_pdapi.go | 252 ++++++++++++ pkg/pdapi/pd_control.go | 135 +++++++ pkg/pdapi/pdapi.go | 362 +----------------- pkg/pdapi/pdapi_test.go | 167 +++++++- pkg/{httputil => util/http}/httputil.go | 6 - .../proxiedtidbclient/proxiedtidbclient.go | 2 +- 7 files changed, 561 insertions(+), 365 deletions(-) create mode 100644 pkg/pdapi/fake_pdapi.go create mode 100644 pkg/pdapi/pd_control.go rename pkg/{httputil => util/http}/httputil.go (91%) diff --git a/pkg/controller/tidb_control.go b/pkg/controller/tidb_control.go index 8cd974c3064..11cd13e962e 100644 --- a/pkg/controller/tidb_control.go +++ b/pkg/controller/tidb_control.go @@ -23,8 +23,8 @@ import ( "time" "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1" - "github.com/pingcap/tidb-operator/pkg/httputil" "github.com/pingcap/tidb-operator/pkg/util" + httputil "github.com/pingcap/tidb-operator/pkg/util/http" "github.com/pingcap/tidb/config" v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/pdapi/fake_pdapi.go b/pkg/pdapi/fake_pdapi.go new file mode 100644 index 00000000000..b6b25d7cf5c --- /dev/null +++ b/pkg/pdapi/fake_pdapi.go @@ -0,0 +1,252 @@ +// 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 pdapi + +import ( + "fmt" + + "github.com/pingcap/kvproto/pkg/metapb" + "github.com/pingcap/kvproto/pkg/pdpb" +) + +type ActionType string + +const ( + GetHealthActionType ActionType = "GetHealth" + GetConfigActionType ActionType = "GetConfig" + GetClusterActionType ActionType = "GetCluster" + GetMembersActionType ActionType = "GetMembers" + GetStoresActionType ActionType = "GetStores" + GetTombStoneStoresActionType ActionType = "GetTombStoneStores" + GetStoreActionType ActionType = "GetStore" + DeleteStoreActionType ActionType = "DeleteStore" + SetStoreStateActionType ActionType = "SetStoreState" + DeleteMemberByIDActionType ActionType = "DeleteMemberByID" + DeleteMemberActionType ActionType = "DeleteMember " + SetStoreLabelsActionType ActionType = "SetStoreLabels" + UpdateReplicationActionType ActionType = "UpdateReplicationConfig" + BeginEvictLeaderActionType ActionType = "BeginEvictLeader" + EndEvictLeaderActionType ActionType = "EndEvictLeader" + GetEvictLeaderSchedulersActionType ActionType = "GetEvictLeaderSchedulers" + GetPDLeaderActionType ActionType = "GetPDLeader" + TransferPDLeaderActionType ActionType = "TransferPDLeader" +) + +type NotFoundReaction struct { + actionType ActionType +} + +func (nfr *NotFoundReaction) Error() string { + return fmt.Sprintf("not found %s reaction. Please add the reaction", nfr.actionType) +} + +type Action struct { + ID uint64 + Name string + Labels map[string]string + Replication PDReplicationConfig +} + +type Reaction func(action *Action) (interface{}, error) + +// FakePDClient implements a fake version of PDClient. +type FakePDClient struct { + reactions map[ActionType]Reaction +} + +func NewFakePDClient() *FakePDClient { + return &FakePDClient{reactions: map[ActionType]Reaction{}} +} + +func (pc *FakePDClient) AddReaction(actionType ActionType, reaction Reaction) { + pc.reactions[actionType] = reaction +} + +// fakeAPI is a small helper for fake API calls +func (pc *FakePDClient) fakeAPI(actionType ActionType, action *Action) (interface{}, error) { + if reaction, ok := pc.reactions[actionType]; ok { + result, err := reaction(action) + if err != nil { + return nil, err + } + return result, nil + } + return nil, &NotFoundReaction{actionType} +} + +func (pc *FakePDClient) GetHealth() (*HealthInfo, error) { + action := &Action{} + result, err := pc.fakeAPI(GetHealthActionType, action) + if err != nil { + return nil, err + } + return result.(*HealthInfo), nil +} + +func (pc *FakePDClient) GetConfig() (*PDConfigFromAPI, error) { + action := &Action{} + result, err := pc.fakeAPI(GetConfigActionType, action) + if err != nil { + return nil, err + } + return result.(*PDConfigFromAPI), nil +} + +func (pc *FakePDClient) GetCluster() (*metapb.Cluster, error) { + action := &Action{} + result, err := pc.fakeAPI(GetClusterActionType, action) + if err != nil { + return nil, err + } + return result.(*metapb.Cluster), nil +} + +func (pc *FakePDClient) GetMembers() (*MembersInfo, error) { + action := &Action{} + result, err := pc.fakeAPI(GetMembersActionType, action) + if err != nil { + return nil, err + } + return result.(*MembersInfo), nil +} + +func (pc *FakePDClient) GetStores() (*StoresInfo, error) { + action := &Action{} + result, err := pc.fakeAPI(GetStoresActionType, action) + if err != nil { + return nil, err + } + return result.(*StoresInfo), nil +} + +func (pc *FakePDClient) GetTombStoneStores() (*StoresInfo, error) { + action := &Action{} + result, err := pc.fakeAPI(GetTombStoneStoresActionType, action) + if err != nil { + return nil, err + } + return result.(*StoresInfo), nil +} + +func (pc *FakePDClient) GetStore(id uint64) (*StoreInfo, error) { + action := &Action{ + ID: id, + } + result, err := pc.fakeAPI(GetStoreActionType, action) + if err != nil { + return nil, err + } + return result.(*StoreInfo), nil +} + +func (pc *FakePDClient) DeleteStore(id uint64) error { + if reaction, ok := pc.reactions[DeleteStoreActionType]; ok { + action := &Action{ID: id} + _, err := reaction(action) + return err + } + return nil +} + +func (pc *FakePDClient) SetStoreState(id uint64, state string) error { + if reaction, ok := pc.reactions[SetStoreStateActionType]; ok { + action := &Action{ID: id} + _, err := reaction(action) + return err + } + return nil +} + +func (pc *FakePDClient) DeleteMemberByID(id uint64) error { + if reaction, ok := pc.reactions[DeleteMemberByIDActionType]; ok { + action := &Action{ID: id} + _, err := reaction(action) + return err + } + return nil +} + +func (pc *FakePDClient) DeleteMember(name string) error { + if reaction, ok := pc.reactions[DeleteMemberActionType]; ok { + action := &Action{Name: name} + _, err := reaction(action) + return err + } + return nil +} + +// SetStoreLabels sets TiKV labels +func (pc *FakePDClient) SetStoreLabels(storeID uint64, labels map[string]string) (bool, error) { + if reaction, ok := pc.reactions[SetStoreLabelsActionType]; ok { + action := &Action{ID: storeID, Labels: labels} + result, err := reaction(action) + return result.(bool), err + } + return true, nil +} + +// UpdateReplicationConfig updates the replication config +func (pc *FakePDClient) UpdateReplicationConfig(config PDReplicationConfig) error { + if reaction, ok := pc.reactions[UpdateReplicationActionType]; ok { + action := &Action{Replication: config} + _, err := reaction(action) + return err + } + return nil +} + +func (pc *FakePDClient) BeginEvictLeader(storeID uint64) error { + if reaction, ok := pc.reactions[BeginEvictLeaderActionType]; ok { + action := &Action{ID: storeID} + _, err := reaction(action) + return err + } + return nil +} + +func (pc *FakePDClient) EndEvictLeader(storeID uint64) error { + if reaction, ok := pc.reactions[EndEvictLeaderActionType]; ok { + action := &Action{ID: storeID} + _, err := reaction(action) + return err + } + return nil +} + +func (pc *FakePDClient) GetEvictLeaderSchedulers() ([]string, error) { + if reaction, ok := pc.reactions[GetEvictLeaderSchedulersActionType]; ok { + action := &Action{} + result, err := reaction(action) + return result.([]string), err + } + return nil, nil +} + +func (pc *FakePDClient) GetPDLeader() (*pdpb.Member, error) { + if reaction, ok := pc.reactions[GetPDLeaderActionType]; ok { + action := &Action{} + result, err := reaction(action) + return result.(*pdpb.Member), err + } + return nil, nil +} + +func (pc *FakePDClient) TransferPDLeader(memberName string) error { + if reaction, ok := pc.reactions[TransferPDLeaderActionType]; ok { + action := &Action{Name: memberName} + _, err := reaction(action) + return err + } + return nil +} diff --git a/pkg/pdapi/pd_control.go b/pkg/pdapi/pd_control.go new file mode 100644 index 00000000000..b80f0b0da33 --- /dev/null +++ b/pkg/pdapi/pd_control.go @@ -0,0 +1,135 @@ +// Copyright 2018 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 pdapi + +import ( + "crypto/tls" + "fmt" + "net/http" + "sync" + + "k8s.io/client-go/kubernetes" + "k8s.io/klog" +) + +// Namespace is a newtype of a string +type Namespace string + +// PDControlInterface is an interface that knows how to manage and get tidb cluster's PD client +type PDControlInterface interface { + // GetPDClient provides PDClient of the tidb cluster. + GetPDClient(namespace Namespace, tcName string, tlsEnabled bool) PDClient + // GetPDEtcdClient provides PD etcd Client of the tidb cluster. + GetPDEtcdClient(namespace Namespace, tcName string, tlsEnabled bool) (PDEtcdClient, error) +} + +// defaultPDControl is the default implementation of PDControlInterface. +type defaultPDControl struct { + mutex sync.Mutex + etcdmutex sync.Mutex + kubeCli kubernetes.Interface + pdClients map[string]PDClient + pdEtcdClients map[string]PDEtcdClient +} + +// NewDefaultPDControl returns a defaultPDControl instance +func NewDefaultPDControl(kubeCli kubernetes.Interface) PDControlInterface { + return &defaultPDControl{kubeCli: kubeCli, pdClients: map[string]PDClient{}, pdEtcdClients: map[string]PDEtcdClient{}} +} + +func (pdc *defaultPDControl) GetPDEtcdClient(namespace Namespace, tcName string, tlsEnabled bool) (PDEtcdClient, error) { + pdc.etcdmutex.Lock() + defer pdc.etcdmutex.Unlock() + + var tlsConfig *tls.Config + var err error + + if tlsEnabled { + tlsConfig, err = GetTLSConfig(pdc.kubeCli, namespace, tcName, nil) + if err != nil { + klog.Errorf("Unable to get tls config for tidb cluster %q, pd etcd client may not work: %v", tcName, err) + return nil, err + } + return NewPdEtcdClient(PDEtcdClientURL(namespace, tcName), DefaultTimeout, tlsConfig) + } + key := pdEtcdClientKey(namespace, tcName) + if _, ok := pdc.pdEtcdClients[key]; !ok { + pdetcdClient, err := NewPdEtcdClient(PDEtcdClientURL(namespace, tcName), DefaultTimeout, nil) + if err != nil { + return nil, err + } + pdc.pdEtcdClients[key] = pdetcdClient + } + return pdc.pdEtcdClients[key], nil +} + +// GetPDClient provides a PDClient of real pd cluster,if the PDClient not existing, it will create new one. +func (pdc *defaultPDControl) GetPDClient(namespace Namespace, tcName string, tlsEnabled bool) PDClient { + pdc.mutex.Lock() + defer pdc.mutex.Unlock() + + var tlsConfig *tls.Config + var err error + var scheme = "http" + + if tlsEnabled { + scheme = "https" + tlsConfig, err = GetTLSConfig(pdc.kubeCli, namespace, tcName, nil) + if err != nil { + klog.Errorf("Unable to get tls config for tidb cluster %q, pd client may not work: %v", tcName, err) + return &pdClient{url: PdClientURL(namespace, tcName, scheme), httpClient: &http.Client{Timeout: DefaultTimeout}} + } + + return NewPDClient(PdClientURL(namespace, tcName, scheme), DefaultTimeout, tlsConfig) + } + + key := pdClientKey(scheme, namespace, tcName) + if _, ok := pdc.pdClients[key]; !ok { + pdc.pdClients[key] = NewPDClient(PdClientURL(namespace, tcName, scheme), DefaultTimeout, nil) + } + return pdc.pdClients[key] +} + +// pdClientKey returns the pd client key +func pdClientKey(scheme string, namespace Namespace, clusterName string) string { + return fmt.Sprintf("%s.%s.%s", scheme, clusterName, string(namespace)) +} + +func pdEtcdClientKey(namespace Namespace, clusterName string) string { + return fmt.Sprintf("%s.%s", clusterName, string(namespace)) +} + +// pdClientUrl builds the url of pd client +func PdClientURL(namespace Namespace, clusterName string, scheme string) string { + return fmt.Sprintf("%s://%s-pd.%s:2379", scheme, clusterName, string(namespace)) +} + +func PDEtcdClientURL(namespace Namespace, clusterName string) string { + return fmt.Sprintf("%s-pd.%s:2379", clusterName, string(namespace)) +} + +// FakePDControl implements a fake version of PDControlInterface. +type FakePDControl struct { + defaultPDControl +} + +func NewFakePDControl(kubeCli kubernetes.Interface) *FakePDControl { + return &FakePDControl{ + defaultPDControl{kubeCli: kubeCli, pdClients: map[string]PDClient{}}, + } +} + +func (fpc *FakePDControl) SetPDClient(namespace Namespace, tcName string, pdclient PDClient) { + fpc.defaultPDControl.pdClients[pdClientKey("http", namespace, tcName)] = pdclient +} diff --git a/pkg/pdapi/pdapi.go b/pkg/pdapi/pdapi.go index 08c6fbc23da..e347ed62ec0 100644 --- a/pkg/pdapi/pdapi.go +++ b/pkg/pdapi/pdapi.go @@ -21,15 +21,14 @@ import ( "io/ioutil" "net/http" "strings" - "sync" "time" "github.com/pingcap/kvproto/pkg/metapb" "github.com/pingcap/kvproto/pkg/pdpb" "github.com/pingcap/pd/pkg/typeutil" - "github.com/pingcap/tidb-operator/pkg/httputil" "github.com/pingcap/tidb-operator/pkg/util" "github.com/pingcap/tidb-operator/pkg/util/crypto" + httputil "github.com/pingcap/tidb-operator/pkg/util/http" types "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/client-go/kubernetes" "k8s.io/klog" @@ -40,31 +39,6 @@ const ( evictSchedulerLeader = "evict-leader-scheduler" ) -// Namespace is a newtype of a string -type Namespace string - -// PDControlInterface is an interface that knows how to manage and get tidb cluster's PD client -type PDControlInterface interface { - // GetPDClient provides PDClient of the tidb cluster. - GetPDClient(Namespace, string, bool) PDClient - // GetPDEtcdClient provides PD etcd Client of the tidb cluster. - GetPDEtcdClient(namespace Namespace, tcName string, tlsEnabled bool) (PDEtcdClient, error) -} - -// defaultPDControl is the default implementation of PDControlInterface. -type defaultPDControl struct { - mutex sync.Mutex - etcdmutex sync.Mutex - kubeCli kubernetes.Interface - pdClients map[string]PDClient - pdEtcdClients map[string]PDEtcdClient -} - -// NewDefaultPDControl returns a defaultPDControl instance -func NewDefaultPDControl(kubeCli kubernetes.Interface) PDControlInterface { - return &defaultPDControl{kubeCli: kubeCli, pdClients: map[string]PDClient{}, pdEtcdClients: map[string]PDEtcdClient{}} -} - // GetTLSConfig returns *tls.Config for given TiDB cluster. // It loads in-cluster root ca if caCert is empty. func GetTLSConfig(kubeCli kubernetes.Interface, namespace Namespace, tcName string, caCert []byte) (*tls.Config, error) { @@ -77,77 +51,6 @@ func GetTLSConfig(kubeCli kubernetes.Interface, namespace Namespace, tcName stri return crypto.LoadTlsConfigFromSecret(secret, caCert) } -func (pdc *defaultPDControl) GetPDEtcdClient(namespace Namespace, tcName string, tlsEnabled bool) (PDEtcdClient, error) { - pdc.etcdmutex.Lock() - defer pdc.etcdmutex.Unlock() - - var tlsConfig *tls.Config - var err error - - if tlsEnabled { - tlsConfig, err = GetTLSConfig(pdc.kubeCli, namespace, tcName, nil) - if err != nil { - klog.Errorf("Unable to get tls config for tidb cluster %q, pd etcd client may not work: %v", tcName, err) - return nil, err - } - return NewPdEtcdClient(PDEtcdClientURL(namespace, tcName), DefaultTimeout, tlsConfig) - } - key := pdEtcdClientKey(namespace, tcName) - if _, ok := pdc.pdEtcdClients[key]; !ok { - pdetcdClient, err := NewPdEtcdClient(PDEtcdClientURL(namespace, tcName), DefaultTimeout, nil) - if err != nil { - return nil, err - } - pdc.pdEtcdClients[key] = pdetcdClient - } - return pdc.pdEtcdClients[key], nil -} - -// GetPDClient provides a PDClient of real pd cluster,if the PDClient not existing, it will create new one. -func (pdc *defaultPDControl) GetPDClient(namespace Namespace, tcName string, tlsEnabled bool) PDClient { - pdc.mutex.Lock() - defer pdc.mutex.Unlock() - - var tlsConfig *tls.Config - var err error - var scheme = "http" - - if tlsEnabled { - scheme = "https" - tlsConfig, err = GetTLSConfig(pdc.kubeCli, namespace, tcName, nil) - if err != nil { - klog.Errorf("Unable to get tls config for tidb cluster %q, pd client may not work: %v", tcName, err) - return &pdClient{url: PdClientURL(namespace, tcName, scheme), httpClient: &http.Client{Timeout: DefaultTimeout}} - } - - return NewPDClient(PdClientURL(namespace, tcName, scheme), DefaultTimeout, tlsConfig) - } - - key := pdClientKey(scheme, namespace, tcName) - if _, ok := pdc.pdClients[key]; !ok { - pdc.pdClients[key] = NewPDClient(PdClientURL(namespace, tcName, scheme), DefaultTimeout, nil) - } - return pdc.pdClients[key] -} - -// pdClientKey returns the pd client key -func pdClientKey(scheme string, namespace Namespace, clusterName string) string { - return fmt.Sprintf("%s.%s.%s", scheme, clusterName, string(namespace)) -} - -func pdEtcdClientKey(namespace Namespace, clusterName string) string { - return fmt.Sprintf("%s.%s", clusterName, string(namespace)) -} - -// pdClientUrl builds the url of pd client -func PdClientURL(namespace Namespace, clusterName string, scheme string) string { - return fmt.Sprintf("%s://%s-pd.%s:2379", scheme, clusterName, string(namespace)) -} - -func PDEtcdClientURL(namespace Namespace, clusterName string) string { - return fmt.Sprintf("%s-pd.%s:2379", clusterName, string(namespace)) -} - // PDClient provides pd server's api type PDClient interface { // GetHealth returns the PD's health info @@ -345,8 +248,7 @@ func (pc *pdClient) GetMembers() (*MembersInfo, error) { return members, nil } -func (pc *pdClient) GetStores() (*StoresInfo, error) { - apiURL := fmt.Sprintf("%s/%s", pc.url, storesPrefix) +func (pc *pdClient) getStores(apiURL string) (*StoresInfo, error) { body, err := httputil.GetBodyOK(pc.httpClient, apiURL) if err != nil { return nil, err @@ -359,18 +261,12 @@ func (pc *pdClient) GetStores() (*StoresInfo, error) { return storesInfo, nil } +func (pc *pdClient) GetStores() (*StoresInfo, error) { + return pc.getStores(fmt.Sprintf("%s/%s", pc.url, storesPrefix)) +} + func (pc *pdClient) GetTombStoneStores() (*StoresInfo, error) { - apiURL := fmt.Sprintf("%s/%s?state=%d", pc.url, storesPrefix, metapb.StoreState_Tombstone) - body, err := httputil.GetBodyOK(pc.httpClient, apiURL) - if err != nil { - return nil, err - } - storesInfo := &StoresInfo{} - err = json.Unmarshal(body, storesInfo) - if err != nil { - return nil, err - } - return storesInfo, nil + return pc.getStores(fmt.Sprintf("%s/%s?state=%d", pc.url, storesPrefix, metapb.StoreState_Tombstone)) } func (pc *pdClient) GetStore(storeID uint64) (*StoreInfo, error) { @@ -750,247 +646,3 @@ func getLeaderEvictSchedulerInfo(storeID uint64) *schedulerInfo { func getLeaderEvictSchedulerStr(storeID uint64) string { return fmt.Sprintf("%s-%d", "evict-leader-scheduler", storeID) } - -type FakePDControl struct { - defaultPDControl -} - -func NewFakePDControl(kubeCli kubernetes.Interface) *FakePDControl { - return &FakePDControl{ - defaultPDControl{kubeCli: kubeCli, pdClients: map[string]PDClient{}}, - } -} - -func (fpc *FakePDControl) SetPDClient(namespace Namespace, tcName string, pdclient PDClient) { - fpc.defaultPDControl.pdClients[pdClientKey("http", namespace, tcName)] = pdclient -} - -type ActionType string - -const ( - GetHealthActionType ActionType = "GetHealth" - GetConfigActionType ActionType = "GetConfig" - GetClusterActionType ActionType = "GetCluster" - GetMembersActionType ActionType = "GetMembers" - GetStoresActionType ActionType = "GetStores" - GetTombStoneStoresActionType ActionType = "GetTombStoneStores" - GetStoreActionType ActionType = "GetStore" - DeleteStoreActionType ActionType = "DeleteStore" - SetStoreStateActionType ActionType = "SetStoreState" - DeleteMemberByIDActionType ActionType = "DeleteMemberByID" - DeleteMemberActionType ActionType = "DeleteMember " - SetStoreLabelsActionType ActionType = "SetStoreLabels" - UpdateReplicationActionType ActionType = "UpdateReplicationConfig" - BeginEvictLeaderActionType ActionType = "BeginEvictLeader" - EndEvictLeaderActionType ActionType = "EndEvictLeader" - GetEvictLeaderSchedulersActionType ActionType = "GetEvictLeaderSchedulers" - GetPDLeaderActionType ActionType = "GetPDLeader" - TransferPDLeaderActionType ActionType = "TransferPDLeader" -) - -type NotFoundReaction struct { - actionType ActionType -} - -func (nfr *NotFoundReaction) Error() string { - return fmt.Sprintf("not found %s reaction. Please add the reaction", nfr.actionType) -} - -type Action struct { - ID uint64 - Name string - Labels map[string]string - Replication PDReplicationConfig -} - -type Reaction func(action *Action) (interface{}, error) - -type FakePDClient struct { - reactions map[ActionType]Reaction -} - -func NewFakePDClient() *FakePDClient { - return &FakePDClient{reactions: map[ActionType]Reaction{}} -} - -func (pc *FakePDClient) AddReaction(actionType ActionType, reaction Reaction) { - pc.reactions[actionType] = reaction -} - -// fakeAPI is a small helper for fake API calls -func (pc *FakePDClient) fakeAPI(actionType ActionType, action *Action) (interface{}, error) { - if reaction, ok := pc.reactions[actionType]; ok { - result, err := reaction(action) - if err != nil { - return nil, err - } - return result, nil - } - return nil, &NotFoundReaction{actionType} -} - -func (pc *FakePDClient) GetHealth() (*HealthInfo, error) { - action := &Action{} - result, err := pc.fakeAPI(GetHealthActionType, action) - if err != nil { - return nil, err - } - return result.(*HealthInfo), nil -} - -func (pc *FakePDClient) GetConfig() (*PDConfigFromAPI, error) { - action := &Action{} - result, err := pc.fakeAPI(GetConfigActionType, action) - if err != nil { - return nil, err - } - return result.(*PDConfigFromAPI), nil -} - -func (pc *FakePDClient) GetCluster() (*metapb.Cluster, error) { - action := &Action{} - result, err := pc.fakeAPI(GetClusterActionType, action) - if err != nil { - return nil, err - } - return result.(*metapb.Cluster), nil -} - -func (pc *FakePDClient) GetMembers() (*MembersInfo, error) { - action := &Action{} - result, err := pc.fakeAPI(GetMembersActionType, action) - if err != nil { - return nil, err - } - return result.(*MembersInfo), nil -} - -func (pc *FakePDClient) GetStores() (*StoresInfo, error) { - action := &Action{} - result, err := pc.fakeAPI(GetStoresActionType, action) - if err != nil { - return nil, err - } - return result.(*StoresInfo), nil -} - -func (pc *FakePDClient) GetTombStoneStores() (*StoresInfo, error) { - action := &Action{} - result, err := pc.fakeAPI(GetTombStoneStoresActionType, action) - if err != nil { - return nil, err - } - return result.(*StoresInfo), nil -} - -func (pc *FakePDClient) GetStore(id uint64) (*StoreInfo, error) { - action := &Action{ - ID: id, - } - result, err := pc.fakeAPI(GetStoreActionType, action) - if err != nil { - return nil, err - } - return result.(*StoreInfo), nil -} - -func (pc *FakePDClient) DeleteStore(id uint64) error { - if reaction, ok := pc.reactions[DeleteStoreActionType]; ok { - action := &Action{ID: id} - _, err := reaction(action) - return err - } - return nil -} - -func (pc *FakePDClient) SetStoreState(id uint64, state string) error { - if reaction, ok := pc.reactions[SetStoreStateActionType]; ok { - action := &Action{ID: id} - _, err := reaction(action) - return err - } - return nil -} - -func (pc *FakePDClient) DeleteMemberByID(id uint64) error { - if reaction, ok := pc.reactions[DeleteMemberByIDActionType]; ok { - action := &Action{ID: id} - _, err := reaction(action) - return err - } - return nil -} - -func (pc *FakePDClient) DeleteMember(name string) error { - if reaction, ok := pc.reactions[DeleteMemberActionType]; ok { - action := &Action{Name: name} - _, err := reaction(action) - return err - } - return nil -} - -// SetStoreLabels sets TiKV labels -func (pc *FakePDClient) SetStoreLabels(storeID uint64, labels map[string]string) (bool, error) { - if reaction, ok := pc.reactions[SetStoreLabelsActionType]; ok { - action := &Action{ID: storeID, Labels: labels} - result, err := reaction(action) - return result.(bool), err - } - return true, nil -} - -// UpdateReplicationConfig updates the replication config -func (pc *FakePDClient) UpdateReplicationConfig(config PDReplicationConfig) error { - if reaction, ok := pc.reactions[UpdateReplicationActionType]; ok { - action := &Action{Replication: config} - _, err := reaction(action) - return err - } - return nil -} - -func (pc *FakePDClient) BeginEvictLeader(storeID uint64) error { - if reaction, ok := pc.reactions[BeginEvictLeaderActionType]; ok { - action := &Action{ID: storeID} - _, err := reaction(action) - return err - } - return nil -} - -func (pc *FakePDClient) EndEvictLeader(storeID uint64) error { - if reaction, ok := pc.reactions[EndEvictLeaderActionType]; ok { - action := &Action{ID: storeID} - _, err := reaction(action) - return err - } - return nil -} - -func (pc *FakePDClient) GetEvictLeaderSchedulers() ([]string, error) { - if reaction, ok := pc.reactions[GetEvictLeaderSchedulersActionType]; ok { - action := &Action{} - result, err := reaction(action) - return result.([]string), err - } - return nil, nil -} - -func (pc *FakePDClient) GetPDLeader() (*pdpb.Member, error) { - if reaction, ok := pc.reactions[GetPDLeaderActionType]; ok { - action := &Action{} - result, err := reaction(action) - return result.(*pdpb.Member), err - } - return nil, nil -} - -func (pc *FakePDClient) TransferPDLeader(memberName string) error { - if reaction, ok := pc.reactions[TransferPDLeaderActionType]; ok { - action := &Action{Name: memberName} - _, err := reaction(action) - return err - } - return nil -} diff --git a/pkg/pdapi/pdapi_test.go b/pkg/pdapi/pdapi_test.go index d8da8d3c38e..8f31a68f391 100644 --- a/pkg/pdapi/pdapi_test.go +++ b/pkg/pdapi/pdapi_test.go @@ -21,6 +21,7 @@ import ( "io/ioutil" "net/http" "net/http/httptest" + "reflect" "testing" . "github.com/onsi/gomega" @@ -33,8 +34,7 @@ const ( ) func getClientServer(h func(http.ResponseWriter, *http.Request)) *httptest.Server { - srv := httptest.NewServer(http.HandlerFunc(h)) - return srv + return httptest.NewServer(http.HandlerFunc(h)) } func TestHealth(t *testing.T) { @@ -648,3 +648,166 @@ func readJSON(r io.ReadCloser, data interface{}) error { return nil } + +func checkNoError(t *testing.T, results []reflect.Value) { + lastVal := results[len(results)-1].Interface() + v, ok := lastVal.(error) + if !ok { + return + } + if v != nil { + t.Errorf("expects no error, got %v", v) + } +} + +func checkError(t *testing.T, results []reflect.Value) { + lastVal := results[len(results)-1].Interface() + if v, ok := lastVal.(error); !ok || v == nil { + t.Errorf("expects error, got nil") + } +} + +// TestGeneric is a generic test to test methods of PD Client. +func TestGeneric(t *testing.T) { + tests := []struct { + name string + method string + args []reflect.Value + resp []byte + statusCode int + wantMethod string + wantPath string + wantQuery string + checkResult func(t *testing.T, results []reflect.Value) + }{ + { + name: "GetTombStoneStores", + method: "GetTombStoneStores", + resp: []byte(`{ + "count": 1, + "stores": [ + { + "store": { + }, + "status": { + } + } + ] +} +`), + statusCode: http.StatusOK, + wantMethod: "GET", + wantPath: fmt.Sprintf("/%s", storesPrefix), + wantQuery: fmt.Sprintf("state=%d", metapb.StoreState_Tombstone), + checkResult: checkNoError, + }, + { + name: "UpdateReplicationConfig", + method: "UpdateReplicationConfig", + args: []reflect.Value{ + reflect.ValueOf(PDReplicationConfig{}), + }, + resp: []byte(``), + statusCode: http.StatusOK, + wantMethod: "POST", + wantPath: fmt.Sprintf("/%s", pdReplicationPrefix), + checkResult: checkNoError, + }, + { + name: "BeginEvictLeader", + method: "BeginEvictLeader", + args: []reflect.Value{ + reflect.ValueOf(uint64(1)), + }, + statusCode: http.StatusOK, + wantMethod: "POST", + wantPath: fmt.Sprintf("/%s", schedulersPrefix), + checkResult: checkNoError, + }, + { + name: "EndEvictLeader", + method: "EndEvictLeader", + args: []reflect.Value{ + reflect.ValueOf(uint64(1)), + }, + statusCode: http.StatusNotFound, + wantMethod: "DELETE", + wantPath: fmt.Sprintf("/%s/evict-leader-scheduler-1", schedulersPrefix), + checkResult: checkNoError, + }, + { + name: "GetEvictLeaderSchedulers", + method: "GetEvictLeaderSchedulers", + resp: []byte(` +[ + "evict-leader-scheduler-1" +] +`), + statusCode: http.StatusOK, + wantMethod: "GET", + wantPath: fmt.Sprintf("/%s", schedulersPrefix), + checkResult: checkNoError, + }, + // TODO test the fix https://github.com/pingcap/tidb-operator/pull/2809 + // { + // name: "GetEvictLeaderSchedulers for the new PD versions", + // method: "GetEvictLeaderSchedulers", + // }, + { + name: "GetPDLeader", + method: "GetPDLeader", + resp: []byte(` +{ + "name": "pd-leader", + "member_id": 1 +} +`), + statusCode: http.StatusOK, + wantMethod: "GET", + wantPath: fmt.Sprintf("/%s", pdLeaderPrefix), + checkResult: checkNoError, + }, + { + name: "TransferPDLeader", + method: "TransferPDLeader", + args: []reflect.Value{ + reflect.ValueOf("foo"), + }, + resp: []byte(` +`), + + statusCode: http.StatusOK, + wantMethod: "POST", + wantPath: fmt.Sprintf("/%s/%s", pdLeaderTransferPrefix, "foo"), + checkResult: checkNoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + g := NewGomegaWithT(t) + server := getClientServer(func(w http.ResponseWriter, request *http.Request) { + g.Expect(request.Method).To(Equal(tt.wantMethod), "check method") + g.Expect(request.URL.Path).To(Equal(tt.wantPath), "check path") + g.Expect(request.URL.RawQuery).To(Equal(tt.wantQuery), "check query") + + w.Header().Set("Content-Type", ContentTypeJSON) + w.WriteHeader(tt.statusCode) + w.Write(tt.resp) + }) + defer server.Close() + + pdClient := NewPDClient(server.URL, DefaultTimeout, &tls.Config{}) + args := []reflect.Value{ + reflect.ValueOf(pdClient), + } + args = append(args, tt.args...) + method, ok := reflect.TypeOf(pdClient).MethodByName(tt.method) + if !ok { + t.Fatalf("method %q not found", tt.method) + } + results := method.Func.Call(args) + tt.checkResult(t, results) + }) + } +} diff --git a/pkg/httputil/httputil.go b/pkg/util/http/httputil.go similarity index 91% rename from pkg/httputil/httputil.go rename to pkg/util/http/httputil.go index 7e1b303add0..0733c18ac02 100644 --- a/pkg/httputil/httputil.go +++ b/pkg/util/http/httputil.go @@ -22,12 +22,6 @@ import ( "k8s.io/klog" ) -const ( - k8sCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" - clientCert = "/var/lib/tls/client.crt" - clientKey = "/var/lib/tls/client.key" -) - // DeferClose captures and prints the error from closing (if an error occurs). // This is designed to be used in a defer statement. func DeferClose(c io.Closer) { diff --git a/tests/e2e/util/proxiedtidbclient/proxiedtidbclient.go b/tests/e2e/util/proxiedtidbclient/proxiedtidbclient.go index ecbe7ed9182..914fb6c96df 100644 --- a/tests/e2e/util/proxiedtidbclient/proxiedtidbclient.go +++ b/tests/e2e/util/proxiedtidbclient/proxiedtidbclient.go @@ -25,7 +25,7 @@ import ( "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1" "github.com/pingcap/tidb-operator/pkg/controller" - "github.com/pingcap/tidb-operator/pkg/httputil" + httputil "github.com/pingcap/tidb-operator/pkg/util/http" "github.com/pingcap/tidb-operator/tests/e2e/util/portforward" "github.com/pingcap/tidb/config" )