Unverified Commit 140d7076 authored by randy's avatar randy Committed by GitHub

Etcd Client Improve (#51)

* bulk insertion

* bulk insertion

* do with revision

* NewConfigClientWithErr return err

* fix error

* fix ci error

* add ut

* code block format and modify comment

* code block format and modify comment

* check suffix

* Update client.go
Co-authored-by: 's avatarXin.Zh <dragoncharlie@foxmail.com>
parent 0091e2a6
...@@ -20,6 +20,7 @@ package gxetcd ...@@ -20,6 +20,7 @@ package gxetcd
import ( import (
"context" "context"
"log" "log"
"strings"
"sync" "sync"
"time" "time"
) )
...@@ -36,10 +37,26 @@ var ( ...@@ -36,10 +37,26 @@ var (
ErrNilETCDV3Client = perrors.New("etcd raw client is nil") // full describe the ERR ErrNilETCDV3Client = perrors.New("etcd raw client is nil") // full describe the ERR
// ErrKVPairNotFound not found key // ErrKVPairNotFound not found key
ErrKVPairNotFound = perrors.New("k/v pair not found") ErrKVPairNotFound = perrors.New("k/v pair not found")
// ErrKVListSizeIllegal k/v list empty or not equal size
ErrKVListSizeIllegal = perrors.New("k/v List is empty or kList's size is not equal to the size of vList")
// ErrCompareFail txn compare fail
ErrCompareFail = perrors.New("txn compare fail")
// ErrRevision revision when error
ErrRevision int64 = -1
) )
// NewConfigClient create new Client // NewConfigClient create new Client
func NewConfigClient(opts ...Option) *Client { func NewConfigClient(opts ...Option) *Client {
newClient, err := NewConfigClientWithErr(opts...)
if err != nil {
log.Printf("new etcd client = error{%v}", err)
}
return newClient
}
// NewConfigClientWithErr create new Client,error
func NewConfigClientWithErr(opts ...Option) (*Client, error) {
options := &Options{ options := &Options{
Heartbeat: 1, // default Heartbeat Heartbeat: 1, // default Heartbeat
} }
...@@ -52,7 +69,8 @@ func NewConfigClient(opts ...Option) *Client { ...@@ -52,7 +69,8 @@ func NewConfigClient(opts ...Option) *Client {
log.Printf("new etcd client (Name{%s}, etcd addresses{%v}, Timeout{%d}) = error{%v}", log.Printf("new etcd client (Name{%s}, etcd addresses{%v}, Timeout{%d}) = error{%v}",
options.Name, options.Endpoints, options.Timeout, err) options.Name, options.Endpoints, options.Timeout, err)
} }
return newClient
return newClient, err
} }
// Client represents etcd client Configuration // Client represents etcd client Configuration
...@@ -211,40 +229,97 @@ func (c *Client) GetEndPoints() []string { ...@@ -211,40 +229,97 @@ func (c *Client) GetEndPoints() []string {
return c.endpoints return c.endpoints
} }
// if k not exist will put k/v in etcd, otherwise return nil // if k not exist will put k/v in etcd, otherwise return ErrCompareFail
func (c *Client) put(k string, v string, opts ...clientv3.OpOption) error { func (c *Client) create(k string, v string, opts ...clientv3.OpOption) error {
rawClient := c.GetRawClient() rawClient := c.GetRawClient()
if rawClient == nil { if rawClient == nil {
return ErrNilETCDV3Client return ErrNilETCDV3Client
} }
_, err := rawClient.Txn(c.ctx). resp, err := rawClient.Txn(c.ctx).
If(clientv3.Compare(clientv3.Version(k), "<", 1)). If(clientv3.Compare(clientv3.CreateRevision(k), "=", 0)).
Then(clientv3.OpPut(k, v, opts...)). Then(clientv3.OpPut(k, v, opts...)).
Commit() Commit()
return err if err != nil {
return err
}
if !resp.Succeeded {
return ErrCompareFail
}
return nil
} }
// if k not exist will put k/v in etcd // if k in bulk insertion not exist all, then put all k/v in etcd, otherwise return error
// if k is already exist in etcd, replace it func (c *Client) batchCreate(kList []string, vList []string, opts ...clientv3.OpOption) error {
func (c *Client) update(k string, v string, opts ...clientv3.OpOption) error {
rawClient := c.GetRawClient() rawClient := c.GetRawClient()
if rawClient == nil {
return ErrNilETCDV3Client
}
kLen := len(kList)
vLen := len(vList)
if kLen == 0 || vLen == 0 || kLen != vLen {
return ErrKVListSizeIllegal
}
var cs []clientv3.Cmp
var ops []clientv3.Op
for i, k := range kList {
v := vList[i]
cs = append(cs, clientv3.Compare(clientv3.CreateRevision(k), "=", 0))
ops = append(ops, clientv3.OpPut(k, v, opts...))
}
resp, err := rawClient.Txn(c.ctx).
If(cs...).
Then(ops...).
Commit()
if err != nil {
return err
}
if !resp.Succeeded {
return ErrCompareFail
}
return nil
}
// put k/v in etcd, if fail return error
func (c *Client) put(k string, v string, opts ...clientv3.OpOption) error {
rawClient := c.GetRawClient()
if rawClient == nil {
return ErrNilETCDV3Client
}
_, err := rawClient.Put(c.ctx, k, v, opts...)
return err
}
// put k/v in etcd when ModRevision equal with rev, if not return ErrCompareFail or other err
func (c *Client) updateWithRev(k string, v string, rev int64, opts ...clientv3.OpOption) error {
rawClient := c.GetRawClient()
if rawClient == nil { if rawClient == nil {
return ErrNilETCDV3Client return ErrNilETCDV3Client
} }
_, err := rawClient.Txn(c.ctx). resp, err := rawClient.Txn(c.ctx).
If(clientv3.Compare(clientv3.Version(k), "!=", -1)). If(clientv3.Compare(clientv3.ModRevision(k), "=", rev)).
Then(clientv3.OpPut(k, v, opts...)). Then(clientv3.OpPut(k, v, opts...)).
Commit() Commit()
return err if err != nil {
return err
}
if !resp.Succeeded {
return ErrCompareFail
}
return nil
} }
func (c *Client) delete(k string) error { func (c *Client) delete(k string) error {
rawClient := c.GetRawClient() rawClient := c.GetRawClient()
if rawClient == nil { if rawClient == nil {
return ErrNilETCDV3Client return ErrNilETCDV3Client
} }
...@@ -253,29 +328,28 @@ func (c *Client) delete(k string) error { ...@@ -253,29 +328,28 @@ func (c *Client) delete(k string) error {
return err return err
} }
func (c *Client) get(k string) (string, error) { // getValAndRev get value and revision
func (c *Client) getValAndRev(k string) (string, int64, error) {
rawClient := c.GetRawClient() rawClient := c.GetRawClient()
if rawClient == nil { if rawClient == nil {
return "", ErrNilETCDV3Client return "", ErrRevision, ErrNilETCDV3Client
} }
resp, err := rawClient.Get(c.ctx, k) resp, err := rawClient.Get(c.ctx, k)
if err != nil { if err != nil {
return "", err return "", ErrRevision, err
} }
if len(resp.Kvs) == 0 { if len(resp.Kvs) == 0 {
return "", ErrKVPairNotFound return "", ErrRevision, ErrKVPairNotFound
} }
return string(resp.Kvs[0].Value), nil return string(resp.Kvs[0].Value), resp.Header.Revision, nil
} }
// CleanKV delete all key and value // CleanKV delete all key and value
func (c *Client) CleanKV() error { func (c *Client) CleanKV() error {
rawClient := c.GetRawClient() rawClient := c.GetRawClient()
if rawClient == nil { if rawClient == nil {
return ErrNilETCDV3Client return ErrNilETCDV3Client
} }
...@@ -287,11 +361,14 @@ func (c *Client) CleanKV() error { ...@@ -287,11 +361,14 @@ func (c *Client) CleanKV() error {
// GetChildren return node children // GetChildren return node children
func (c *Client) GetChildren(k string) ([]string, []string, error) { func (c *Client) GetChildren(k string) ([]string, []string, error) {
rawClient := c.GetRawClient() rawClient := c.GetRawClient()
if rawClient == nil { if rawClient == nil {
return nil, nil, ErrNilETCDV3Client return nil, nil, ErrNilETCDV3Client
} }
if !strings.HasSuffix(k, "/") {
k += "/"
}
resp, err := rawClient.Get(c.ctx, k, clientv3.WithPrefix()) resp, err := rawClient.Get(c.ctx, k, clientv3.WithPrefix())
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
...@@ -310,29 +387,18 @@ func (c *Client) GetChildren(k string) ([]string, []string, error) { ...@@ -310,29 +387,18 @@ func (c *Client) GetChildren(k string) ([]string, []string, error) {
return kList, vList, nil return kList, vList, nil
} }
func (c *Client) watchWithPrefix(prefix string) (clientv3.WatchChan, error) { // watchWithOption watch
func (c *Client) watchWithOption(k string, opts ...clientv3.OpOption) (clientv3.WatchChan, error) {
rawClient := c.GetRawClient() rawClient := c.GetRawClient()
if rawClient == nil { if rawClient == nil {
return nil, ErrNilETCDV3Client return nil, ErrNilETCDV3Client
} }
return rawClient.Watch(c.ctx, prefix, clientv3.WithPrefix()), nil return rawClient.Watch(c.ctx, k, opts...), nil
}
func (c *Client) watch(k string) (clientv3.WatchChan, error) {
rawClient := c.GetRawClient()
if rawClient == nil {
return nil, ErrNilETCDV3Client
}
return rawClient.Watch(c.ctx, k), nil
} }
func (c *Client) keepAliveKV(k string, v string) error { func (c *Client) keepAliveKV(k string, v string) error {
rawClient := c.GetRawClient() rawClient := c.GetRawClient()
if rawClient == nil { if rawClient == nil {
return ErrNilETCDV3Client return ErrNilETCDV3Client
} }
...@@ -376,13 +442,25 @@ func (c *Client) Valid() bool { ...@@ -376,13 +442,25 @@ func (c *Client) Valid() bool {
// Create key value ... // Create key value ...
func (c *Client) Create(k string, v string) error { func (c *Client) Create(k string, v string) error {
err := c.put(k, v) err := c.create(k, v)
return perrors.WithMessagef(err, "put k/v (key: %s value %s)", k, v) return perrors.WithMessagef(err, "put k/v (key: %s value %s)", k, v)
} }
// BatchCreate bulk insertion
func (c *Client) BatchCreate(kList []string, vList []string) error {
err := c.batchCreate(kList, vList)
return perrors.WithMessagef(err, "batch put k/v error ")
}
// Update key value ... // Update key value ...
func (c *Client) Update(k, v string) error { func (c *Client) Update(k, v string) error {
err := c.update(k, v) err := c.put(k, v)
return perrors.WithMessagef(err, "Update k/v (key: %s value %s)", k, v)
}
// Update key value ...
func (c *Client) UpdateWithRev(k, v string, rev int64, opts ...clientv3.OpOption) error {
err := c.updateWithRev(k, v, rev, opts...)
return perrors.WithMessagef(err, "Update k/v (key: %s value %s)", k, v) return perrors.WithMessagef(err, "Update k/v (key: %s value %s)", k, v)
} }
...@@ -404,20 +482,36 @@ func (c *Client) GetChildrenKVList(k string) ([]string, []string, error) { ...@@ -404,20 +482,36 @@ func (c *Client) GetChildrenKVList(k string) ([]string, []string, error) {
return kList, vList, perrors.WithMessagef(err, "get key children (key %s)", k) return kList, vList, perrors.WithMessagef(err, "get key children (key %s)", k)
} }
// GetValAndRev gets value and revision by @k
func (c *Client) GetValAndRev(k string) (string, int64, error) {
v, rev, err := c.getValAndRev(k)
return v, rev, perrors.WithMessagef(err, "get key value (key %s)", k)
}
// Get gets value by @k // Get gets value by @k
func (c *Client) Get(k string) (string, error) { func (c *Client) Get(k string) (string, error) {
v, err := c.get(k) v, _, err := c.getValAndRev(k)
return v, perrors.WithMessagef(err, "get key value (key %s)", k) return v, perrors.WithMessagef(err, "get key value (key %s)", k)
} }
// Watch watches on spec key // Watch watches on spec key
func (c *Client) Watch(k string) (clientv3.WatchChan, error) { func (c *Client) Watch(k string) (clientv3.WatchChan, error) {
wc, err := c.watch(k) wc, err := c.watchWithOption(k)
return wc, perrors.WithMessagef(err, "watch prefix (key %s)", k) return wc, perrors.WithMessagef(err, "watch (key %s)", k)
} }
// WatchWithPrefix watches on spec prefix // WatchWithPrefix watches on spec prefix
func (c *Client) WatchWithPrefix(prefix string) (clientv3.WatchChan, error) { func (c *Client) WatchWithPrefix(prefix string) (clientv3.WatchChan, error) {
wc, err := c.watchWithPrefix(prefix) if !strings.HasSuffix(prefix, "/") {
prefix += "/"
}
wc, err := c.watchWithOption(prefix, clientv3.WithPrefix())
return wc, perrors.WithMessagef(err, "watch prefix (key %s)", prefix) return wc, perrors.WithMessagef(err, "watch prefix (key %s)", prefix)
} }
// Watch watches on spc key with OpOption
func (c *Client) WatchWithOption(k string, opts ...clientv3.OpOption) (clientv3.WatchChan, error) {
wc, err := c.watchWithOption(k, opts...)
return wc, perrors.WithMessagef(err, "watch (key %s)", k)
}
...@@ -48,15 +48,15 @@ var tests = []struct { ...@@ -48,15 +48,15 @@ var tests = []struct {
{input: struct { {input: struct {
k string k string
v string v string
}{k: "name", v: "scott.wang"}}, }{k: "name/name", v: "scott.wang"}},
{input: struct { {input: struct {
k string k string
v string v string
}{k: "namePrefix", v: "prefix.scott.wang"}}, }{k: "name/namePrefix", v: "prefix.scott.wang"}},
{input: struct { {input: struct {
k string k string
v string v string
}{k: "namePrefix1", v: "prefix1.scott.wang"}}, }{k: "name/namePrefix1", v: "prefix1.scott.wang"}},
{input: struct { {input: struct {
k string k string
v string v string
...@@ -64,7 +64,8 @@ var tests = []struct { ...@@ -64,7 +64,8 @@ var tests = []struct {
} }
// test dataset prefix // test dataset prefix
const prefix = "name" const prefixKey = "name/"
const keyPrefix = "name/name"
type ClientTestSuite struct { type ClientTestSuite struct {
suite.Suite suite.Suite
...@@ -118,10 +119,10 @@ func (suite *ClientTestSuite) TearDownSuite() { ...@@ -118,10 +119,10 @@ func (suite *ClientTestSuite) TearDownSuite() {
} }
func (suite *ClientTestSuite) setUpClient() *Client { func (suite *ClientTestSuite) setUpClient() *Client {
c, err := NewClient(suite.etcdConfig.name, c, err := NewConfigClientWithErr(WithName(suite.etcdConfig.name),
suite.etcdConfig.endpoints, WithEndpoints(suite.etcdConfig.endpoints...),
suite.etcdConfig.timeout, WithTimeout(suite.etcdConfig.timeout),
suite.etcdConfig.heartbeat) WithHeartbeat(suite.etcdConfig.heartbeat))
if err != nil { if err != nil {
suite.T().Fatal(err) suite.T().Fatal(err)
} }
...@@ -204,6 +205,82 @@ func (suite *ClientTestSuite) TestClientCreateKV() { ...@@ -204,6 +205,82 @@ func (suite *ClientTestSuite) TestClientCreateKV() {
} }
} }
func (suite *ClientTestSuite) TestBatchClientCreateKV() {
tests := tests
c := suite.client
t := suite.T()
defer suite.client.Close()
for _, tc := range tests {
k := tc.input.k
v := tc.input.v
expect := tc.input.v
kList := make([]string, 0, 1)
vList := make([]string, 0, 1)
kList = append(kList, k)
vList = append(vList, v)
if err := c.BatchCreate(kList, vList); err != nil {
t.Fatal(err)
}
value, err := c.Get(k)
if err != nil {
t.Fatal(err)
}
if value != expect {
t.Fatalf("expect %v but get %v", expect, value)
}
}
}
func (suite *ClientTestSuite) TestBatchClientGetValAndRevKV() {
tests := tests
c := suite.client
t := suite.T()
defer suite.client.Close()
for _, tc := range tests {
k := tc.input.k
v := tc.input.v
expect := tc.input.v
kList := make([]string, 0, 1)
vList := make([]string, 0, 1)
kList = append(kList, k)
vList = append(vList, v)
if err := c.BatchCreate(kList, vList); err != nil {
t.Fatal(err)
}
value, revision, err := c.getValAndRev(k)
if err != nil {
t.Fatal(err)
}
err = c.UpdateWithRev(k, k, revision)
if err != nil {
t.Fatal(err)
}
err = c.Update(k, k)
if err != nil {
t.Fatal(err)
}
if value != expect {
t.Fatalf("expect %v but get %v", expect, value)
}
}
}
func (suite *ClientTestSuite) TestClientDeleteKV() { func (suite *ClientTestSuite) TestClientDeleteKV() {
tests := tests tests := tests
c := suite.client c := suite.client
...@@ -250,7 +327,7 @@ func (suite *ClientTestSuite) TestClientGetChildrenKVList() { ...@@ -250,7 +327,7 @@ func (suite *ClientTestSuite) TestClientGetChildrenKVList() {
k := tc.input.k k := tc.input.k
v := tc.input.v v := tc.input.v
if strings.Contains(k, prefix) { if strings.Contains(k, prefixKey) {
expectKList = append(expectKList, k) expectKList = append(expectKList, k)
expectVList = append(expectVList, v) expectVList = append(expectVList, v)
} }
...@@ -260,7 +337,7 @@ func (suite *ClientTestSuite) TestClientGetChildrenKVList() { ...@@ -260,7 +337,7 @@ func (suite *ClientTestSuite) TestClientGetChildrenKVList() {
} }
} }
kList, vList, err := c.GetChildrenKVList(prefix) kList, vList, err := c.GetChildrenKVList(prefixKey)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
...@@ -297,7 +374,7 @@ func (suite *ClientTestSuite) TestClientWatch() { ...@@ -297,7 +374,7 @@ func (suite *ClientTestSuite) TestClientWatch() {
c.Close() c.Close()
}() }()
wc, err := c.watch(prefix) wc, err := c.WatchWithOption(keyPrefix)
if err != nil { if err != nil {
assert.Error(t, err) assert.Error(t, err)
} }
...@@ -338,7 +415,7 @@ func (suite *ClientTestSuite) TestClientRegisterTemp() { ...@@ -338,7 +415,7 @@ func (suite *ClientTestSuite) TestClientRegisterTemp() {
}() }()
completePath := path.Join("scott", "wang") completePath := path.Join("scott", "wang")
wc, err := observeC.watch(completePath) wc, err := observeC.watchWithOption(completePath)
if err != nil { if err != nil {
assert.Error(t, err) assert.Error(t, err)
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment