package raw_tikv_test import ( "bytes" "context" "fmt" "hash/crc64" "io/ioutil" "net/http" "strings" "testing" "time" "github.com/pingcap/kvproto/pkg/kvrpcpb" "github.com/stretchr/testify/suite" "github.com/tidwall/gjson" "github.com/tikv/client-go/v2/rawkv" "github.com/tikv/client-go/v2/tikv" pd "github.com/tikv/pd/client" "github.com/tikv/pd/client/pkg/caller" ) func TestAPI(t *testing.T) { if !*withTiKV { t.Skip("skipping TestAPI because with-tikv is not enabled") } suite.Run(t, new(apiTestSuite)) } type apiTestSuite struct { suite.Suite client *rawkv.Client clientForCas *rawkv.Client pdClient pd.Client apiVersion kvrpcpb.APIVersion } func getConfig(url string) (string, error) { transport := &http.Transport{} client := http.Client{ Transport: transport, } defer transport.CloseIdleConnections() resp, err := client.Get(url) if err != nil { return "", err } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return "", err } return string(body), nil } func (s *apiTestSuite) getApiVersion(pdCli pd.Client) kvrpcpb.APIVersion { stores, err := pdCli.GetAllStores(context.Background()) s.Nilf(err, "fail to get store, %v", err) for _, store := range stores { resp, err := getConfig(fmt.Sprintf("http://%s/config", store.StatusAddress)) s.Nilf(err, "fail to get config of TiKV store %s: %v", store.StatusAddress, err) v := gjson.Get(resp, "storage.api-version") if v.Type == gjson.Null || v.Uint() != 2 { return kvrpcpb.APIVersion_V1 } } return kvrpcpb.APIVersion_V2 } func (s *apiTestSuite) newRawKVClient(pdCli pd.Client, addrs []string) *rawkv.Client { cli, err := rawkv.NewClientWithOpts(context.Background(), addrs, rawkv.WithAPIVersion(s.apiVersion)) s.Nil(err) return cli } func (s *apiTestSuite) wrapPDClient(pdCli pd.Client) pd.Client { var err error if s.getApiVersion(pdCli) == kvrpcpb.APIVersion_V2 { pdCli, err = tikv.NewCodecPDClientWithKeyspace(tikv.ModeRaw, pdCli, tikv.DefaultKeyspaceName) } s.Nil(err) return pdCli } func (s *apiTestSuite) SetupTest() { addrs := strings.Split(*pdAddrs, ",") pdClient, err := pd.NewClient(caller.TestComponent, addrs, pd.SecurityOption{}) s.Nil(err) s.apiVersion = s.getApiVersion(pdClient) s.pdClient = s.wrapPDClient(pdClient) client := s.newRawKVClient(pdClient, addrs) s.client = client clientForCas := s.newRawKVClient(pdClient, addrs) clientForCas.SetAtomicForCAS(true) s.clientForCas = clientForCas } func withPrefix(prefix, key string) string { return prefix + key } func withPrefixes(prefix string, keys []string) []string { var result []string for i := range keys { result = append(result, withPrefix(prefix, keys[i])) } return result } func (s *apiTestSuite) cleanKeyPrefix(prefix string) { end := append([]byte(prefix), 127) err := s.client.DeleteRange(context.Background(), []byte(prefix), end) s.Nil(err) ks, _ := s.mustScan(prefix, "", "", 10240) s.Empty(ks) } func (s *apiTestSuite) mustPut(prefix string, key string, value string) { err := s.client.Put(context.Background(), []byte(withPrefix(prefix, key)), []byte(value)) s.Nil(err) } func (s *apiTestSuite) mustGet(prefix string, key string) string { v, err := s.client.Get(context.Background(), []byte(withPrefix(prefix, key))) s.Nil(err) return string(v) } func (s *apiTestSuite) mustDelete(prefix string, key string) { err := s.client.Delete(context.Background(), []byte(withPrefix(prefix, key))) s.Nil(err) } func (s *apiTestSuite) mustScanBytes(prefix string, start string, end string, limit int) ([][]byte, [][]byte) { end = withPrefix(prefix, end) end += string([]byte{127}) ks, vs, err := s.client.Scan(context.Background(), []byte(withPrefix(prefix, start)), []byte(end), limit) s.Nil(err) for i := range ks { ks[i] = bytes.TrimPrefix(ks[i], []byte(prefix)) } return ks, vs } func (s *apiTestSuite) mustScan(prefix string, start string, end string, limit int) ([]string, []string) { ks, vs := s.mustScanBytes(prefix, start, end, limit) return toStrings(ks), toStrings(vs) } func (s *apiTestSuite) mustReverseScanBytes(prefix string, start string, end string, limit int) ([][]byte, [][]byte) { ks, vs, err := s.client.ReverseScan(context.Background(), []byte(withPrefix(prefix, start)), []byte(withPrefix(prefix, end)), limit) s.Nil(err) for i := range ks { ks[i] = bytes.TrimPrefix(ks[i], []byte(prefix)) } return ks, vs } func (s *apiTestSuite) mustReverseScan(prefix string, start string, end string, limit int) ([]string, []string) { ks, vs := s.mustReverseScanBytes(prefix, start, end, limit) return toStrings(ks), toStrings(vs) } func (s *apiTestSuite) mustChecksum(prefix string, start string, end string) rawkv.RawChecksum { end = withPrefix(prefix, end) end += string([]byte{127}) checksum, err := s.client.Checksum(context.Background(), []byte(withPrefix(prefix, start)), []byte(end)) s.Nil(err) return checksum } func (s *apiTestSuite) mustDeleteRange(prefix string, start, end string) { if end == "" { end = prefix } end += string([]byte{127}) err := s.client.DeleteRange(context.Background(), []byte(withPrefix(prefix, start)), []byte(withPrefix(prefix, end))) s.Nil(err) } func toStrings(data [][]byte) []string { var ss []string for _, b := range data { ss = append(ss, string(b)) } return ss } func toBytes(data []string) [][]byte { var bs [][]byte for _, s := range data { bs = append(bs, []byte(s)) } return bs } func (s *apiTestSuite) mustBatchPut(prefix string, keys []string, values []string) { s.Equal(len(keys), len(values)) keys = withPrefixes(prefix, keys) err := s.client.BatchPut(context.Background(), toBytes(keys), toBytes(values)) s.Nil(err) } func (s *apiTestSuite) mustBatchGetBytes(prefix string, keys []string) [][]byte { keys = withPrefixes(prefix, keys) vs, err := s.client.BatchGet(context.Background(), toBytes(keys)) s.Nil(err) return vs } func (s *apiTestSuite) mustBatchGet(prefix string, keys []string) []string { return toStrings(s.mustBatchGetBytes(prefix, keys)) } func (s *apiTestSuite) mustBatchDelete(prefix string, keys []string) { keys = withPrefixes(prefix, keys) err := s.client.BatchDelete(context.Background(), toBytes(keys)) s.Nil(err) } func (s *apiTestSuite) mustCASBytes(prefix, key string, old, new []byte) (bool, []byte) { oldValue, success, err := s.clientForCas.CompareAndSwap(context.Background(), []byte(withPrefix(prefix, key)), old, new) s.Nil(err) return success, oldValue } // `old == ""` means "not exist" func (s *apiTestSuite) mustCAS(prefix, key, old, new string) (bool, string) { var oldValue []byte if old != "" { oldValue = []byte(old) } success, oldValue := s.mustCASBytes(prefix, key, oldValue, []byte(new)) return success, string(oldValue) } func (s *apiTestSuite) mustPutWithTTL(prefix, key, value string, ttl uint64) { err := s.client.PutWithTTL(context.Background(), []byte(withPrefix(prefix, key)), []byte(value), ttl) s.Nil(err) } func (s *apiTestSuite) mustGetKeyTTL(prefix, key string) *uint64 { ttl, err := s.client.GetKeyTTL(context.Background(), []byte(withPrefix(prefix, key))) s.Nil(err) return ttl } func (s *apiTestSuite) mustNotExist(prefix string, key string) { v, err := s.client.Get(context.Background(), []byte(withPrefix(prefix, key))) s.Nil(err) s.Nil(v) } func (s *apiTestSuite) mustExist(prefix string, key string) { v, err := s.client.Get(context.Background(), []byte(withPrefix(prefix, key))) s.Nil(err) s.NotNil(v) } func (s *apiTestSuite) mustSplitRegion(prefix string, splitKeys []string) { var keys [][]byte for i := range splitKeys { keys = append(keys, []byte(withPrefix(prefix, splitKeys[i]))) } _, err := s.pdClient.SplitRegions(context.Background(), keys) if err != nil { s.T().Fatalf("failed to split regions: %v", err) } s.Nil(err) } func (s *apiTestSuite) TestSimple() { prefix := "test_simple" s.cleanKeyPrefix(prefix) s.mustNotExist(prefix, "key") s.mustPut(prefix, "key", "value") v := s.mustGet(prefix, "key") s.Equal("value", v) s.mustDelete(prefix, "key") s.mustNotExist(prefix, "key") } func (s *apiTestSuite) TestScan() { prefix := "test_scan" s.cleanKeyPrefix(prefix) var ( keys []string values []string ) for i := 0; i < 20480; i++ { keys = append(keys, fmt.Sprintf("key@%v", i)) values = append(values, fmt.Sprintf("value@%v", i)) } s.mustBatchPut(prefix, keys, values) var splitKeys []string for i := 0; i < 20480; i += 1024 { splitKeys = append(splitKeys, fmt.Sprintf("key@%v", i)) } s.mustSplitRegion(prefix, splitKeys) keys, values = s.mustScan(prefix, keys[0], "", 10240) s.Equal(10240, len(keys)) s.Equal(10240, len(values)) s.Equal(len(keys), len(values)) for i := range keys { s.True(strings.HasPrefix(keys[i], "key@")) s.True(strings.HasPrefix(values[i], "value@")) } } func (s *apiTestSuite) TestReverseScan() { prefix := "test_reverse_scan" s.cleanKeyPrefix(prefix) for i := 0; i < 10; i++ { s.mustPut(prefix, fmt.Sprintf("key:%v", i), fmt.Sprintf("value:%v", i)) } keys, values := s.mustReverseScan(prefix, "key:", "", 5) for i := range keys { s.Equal(fmt.Sprintf("key:%v", i), keys[len(keys)-1-i]) s.Equal(fmt.Sprintf("value:%v", i), values[len(keys)-1-i]) } } func (s *apiTestSuite) TestBatchOp() { prefix := "test_batch_op" ks := []string{"k1", "k2"} s.cleanKeyPrefix(prefix) s.mustBatchPut(prefix, ks, []string{"v1", "v2"}) vs := s.mustBatchGet(prefix, ks) s.Equal("v1", vs[0]) s.Equal("v2", vs[1]) s.mustBatchDelete(prefix, ks) s.mustNotExist(prefix, ks[0]) s.mustNotExist(prefix, ks[1]) } func (s *apiTestSuite) TestCAS() { prefix := "test_cas" s.cleanKeyPrefix(prefix) success, old := s.mustCAS(prefix, "key", "", "hello world") s.True(success) s.Equal("", old) v := s.mustGet(prefix, "key") s.Equal("hello world", v) success, old = s.mustCAS(prefix, "key", "hello", "world") s.False(success) s.Equal("hello world", old) v = s.mustGet(prefix, "key") s.Equal("hello world", v) success, old = s.mustCAS(prefix, "key", "hello world", "world") s.True(success) s.Equal("hello world", old) v = s.mustGet(prefix, "key") s.Equal("world", v) } func (s *apiTestSuite) TestTTL() { prefix := "test_ttl" var ttl uint64 = 2 s.mustPutWithTTL(prefix, "key", "value", ttl) time.Sleep(time.Second * time.Duration(ttl/2)) rest := s.mustGetKeyTTL(prefix, "key") s.NotNil(rest) s.LessOrEqual(*rest, ttl/2) time.Sleep(time.Second * time.Duration(ttl/2)) s.mustNotExist(prefix, "key") rest = s.mustGetKeyTTL(prefix, "key") s.Nil(rest) } func (s *apiTestSuite) TestDeleteRange() { prefix := "test_delete_range" s.cleanKeyPrefix(prefix) var ( keys []string values []string ) for i := 0; i < 20480; i++ { keys = append(keys, fmt.Sprintf("key@%v", i)) values = append(values, fmt.Sprintf("value@%v", i)) } s.mustBatchPut(prefix, keys, values) s.mustSplitRegion(prefix, []string{"key@4096"}) s.mustDeleteRange(prefix, "", "") s.mustNotExist(prefix, "key@0") s.mustNotExist(prefix, "key@1") s.mustNotExist(prefix, "key@2") } func (s *apiTestSuite) TestRawChecksum() { prefix := "test_checksum" s.cleanKeyPrefix(prefix) var ( keys []string values []string ) expect := rawkv.RawChecksum{} digest := crc64.New(crc64.MakeTable(crc64.ECMA)) for i := 0; i < 20480; i++ { key := fmt.Sprintf("key@%v", i) value := fmt.Sprintf("value@%v", i) keys = append(keys, key) values = append(values, value) digest.Reset() digest.Write([]byte(key)) digest.Write([]byte(value)) expect.Crc64Xor ^= digest.Sum64() expect.TotalKvs++ expect.TotalBytes += (uint64)(len(prefix) + len(key) + len(value)) if s.apiVersion == kvrpcpb.APIVersion_V2 { expect.TotalBytes += 4 // 4 bytes key prefix of API v2 } } s.mustBatchPut(prefix, keys, values) checksum := s.mustChecksum(prefix, "", "") s.Equal(expect, checksum) } func (s *apiTestSuite) TestEmptyValue() { prefix := "test_empty_value" s.cleanKeyPrefix(prefix) s.mustNotExist(prefix, "key") verifyEmptyValue := func() { s.mustExist(prefix, "key") // get v := s.mustGet(prefix, "key") s.Empty(v) // batch_get vs := s.mustBatchGetBytes(prefix, []string{"key", "key1"}) s.Equal([][]byte{{}, nil}, vs) // scan keys, values := s.mustScanBytes(prefix, "key", "keyz", 10) s.Equal([][]byte{[]byte("key")}, keys) s.Equal([][]byte{{}}, values) // reverse scan keys, values = s.mustReverseScanBytes(prefix, "keyz", "key", 10) s.Equal([][]byte{[]byte("key")}, keys) s.Equal([][]byte{{}}, values) } verifyNotExist := func() { s.mustNotExist(prefix, "key") // batch_get vs := s.mustBatchGetBytes(prefix, []string{"key", "key1"}) s.Equal([][]byte{nil, nil}, vs) // scan keys, values := s.mustScanBytes(prefix, "key", "keyz", 10) s.Nil(keys) s.Nil(values) // reverse scan keys, values = s.mustReverseScanBytes(prefix, "keyz", "key", 10) s.Nil(keys) s.Nil(values) } // put s.mustPut(prefix, "key", "") verifyEmptyValue() // delete s.mustDelete(prefix, "key") verifyNotExist() // batch_put s.mustBatchPut(prefix, []string{"key"}, []string{""}) verifyEmptyValue() // compare_and_swap, nil -> "" s.mustDelete(prefix, "key") ok, oldVal := s.mustCASBytes(prefix, "key", nil, []byte("")) s.True(ok) s.Nil(oldVal) verifyEmptyValue() // compare_and_swap, "" -> "val" ok, oldVal = s.mustCASBytes(prefix, "key", []byte(""), []byte("val")) s.True(ok) s.Equal([]byte{}, oldVal) } func (s *apiTestSuite) TearDownTest() { if s.client != nil { s.Require().Nil(s.client.Close()) } if s.clientForCas != nil { s.Require().Nil(s.clientForCas.Close()) } if s.pdClient != nil { s.pdClient.Close() } }