From a7790ef95867380e26c008e58a8bfad15d2424c1 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Fri, 12 Feb 2016 16:51:15 -0800 Subject: [PATCH 1/4] Make Rmdir Rm; Add Ls --- etcd.go | 71 ++++++++++++++++++++++++++++++++++------------------ etcd_test.go | 39 ++++++++++++++++++++++++++--- 2 files changed, 82 insertions(+), 28 deletions(-) diff --git a/etcd.go b/etcd.go index f9366ab..ebb7fbb 100644 --- a/etcd.go +++ b/etcd.go @@ -31,46 +31,45 @@ func New(endpoints string) (*Etcd, error) { eps[i] = u.String() } - tr, e := transport.NewTransport(transport.TLSInfo{}, - 10*time.Second) // timeout = 10sec + tr, e := transport.NewTransport(transport.TLSInfo{}, 10*time.Second) if e != nil { - return nil, fmt.Errorf("transport.NewTransport: %v", e) + return nil, e } c, e := client.New(client.Config{Endpoints: eps, Transport: tr}) if e != nil { - return nil, fmt.Errorf("client.New: %v", e) + return nil, e } - ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout) + ctx, cancel := timeoutContext() e = c.Sync(ctx) cancel() if e != nil { - return nil, fmt.Errorf("(etc)client.Sync: %v", e) + return nil, e } return &Etcd{client.NewKeysAPI(c)}, nil } +func timeoutContext() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), client.DefaultRequestTimeout) +} + // Mkdir creates a directory. The directory could be multiple-level, // like /home/yi/hello. But it must not exist before; otherwise Mkdir // returns an error. func (c *Etcd) Mkdir(dir string) error { - ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout) + ctx, cancel := timeoutContext() defer cancel() - if _, e := c.api.Set(ctx, dir, "", &client.SetOptions{Dir: true, PrevExist: client.PrevNoExist}); e != nil { - return fmt.Errorf("Etcd.Mkdir: %v", e) - } - return nil + _, e := c.api.Set(ctx, dir, "", &client.SetOptions{Dir: true, PrevExist: client.PrevNoExist}) + return e } func (c *Etcd) SetWithTTL(key, value string, ttl time.Duration) error { - ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout) + ctx, cancel := timeoutContext() defer cancel() - if _, e := c.api.Set(ctx, key, value, &client.SetOptions{TTL: ttl}); e != nil { - return fmt.Errorf("Etcd.Set: %v", e) - } - return nil + _, e := c.api.Set(ctx, key, value, &client.SetOptions{TTL: ttl}) + return e } func (c *Etcd) Set(key, value string) error { @@ -78,21 +77,45 @@ func (c *Etcd) Set(key, value string) error { } func (c *Etcd) Get(key string) (string, error) { - ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout) + ctx, cancel := timeoutContext() defer cancel() r, e := c.api.Get(ctx, key, &client.GetOptions{Sort: true}) if e != nil { - return "", fmt.Errorf("Etcd.Get: %v", e) + return "", e } return r.Node.Value, nil } -// Rmdir removes a directory and recursively its all content, like bash command `rm -rf`. -func (c *Etcd) Rmdir(dir string) error { - ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout) +// Rm removes a either a key-value pair or a directory. If it is a +// directory, Rm removes all recursive content as well. +func (c *Etcd) Rm(key string) error { + ctx, cancel := timeoutContext() + defer cancel() + _, e := c.api.Delete(ctx, key, &client.DeleteOptions{Recursive: true}) + return e +} + +func (c *Etcd) Ls(key string) ([]string, error) { + if len(key) == 0 { + key = "/" + } + + ctx, cancel := timeoutContext() defer cancel() - if _, e := c.api.Delete(ctx, dir, &client.DeleteOptions{Dir: true, Recursive: true}); e != nil { - return fmt.Errorf("Etcd.Rmdir: %v", e) + + resp, e := c.api.Get(ctx, key, &client.GetOptions{Sort: false, Recursive: false, Quorum: true}) + + if e != nil { + return nil, e + } + + if !resp.Node.Dir { + return []string{resp.Node.Key}, nil + } + + var keys []string + for _, node := range resp.Node.Nodes { + keys = append(keys, node.Key) } - return nil + return keys, e } diff --git a/etcd_test.go b/etcd_test.go index c9d1cf8..485d261 100644 --- a/etcd_test.go +++ b/etcd_test.go @@ -20,7 +20,7 @@ func ExampleNew() { c.Set("/home/yi/b", "Banana") c.Get("/home/yi/a") c.Get("home/yi/b") - c.Rmdir("/home") + c.Rm("/home") } var ( @@ -41,7 +41,7 @@ func init() { func TestEtcdBasicSession(t *testing.T) { assert := assert.New(t) - c.Rmdir("/home/yi") + c.Rm("/home/yi") assert.Nil(c.Mkdir("/home/yi")) assert.NotNil(c.Mkdir("/home/yi")) @@ -62,7 +62,7 @@ func TestEtcdBasicSession(t *testing.T) { assert.Nil(e) assert.Equal("Aloha", r) - assert.Nil(c.Rmdir("/home")) + assert.Nil(c.Rm("/home")) } func TestSetWithTTL(t *testing.T) { @@ -77,8 +77,39 @@ func TestSetWithTTL(t *testing.T) { // NOTE: it seems that etcd doesn't keep TTL exactly, if we // wait for exactly the TTL time (1000 * timeMillisecond in // this case), we might find the key-value pair still there. - time.Sleep(1100 * time.Millisecond) + time.Sleep(1500 * time.Millisecond) r, e = c.Get("key-ttl") assert.NotNil(e) assert.Equal("", r) } + +func TestLs(t *testing.T) { + assert := assert.New(t) + + c.Rm("/home/yi") + + assert.Nil(c.Mkdir("/home/yi")) + { + ns, e := c.Ls("") + assert.Nil(e) + assert.Equal(1, len(ns)) + assert.Equal("/home", ns[0]) + } + + assert.Nil(c.Set("/home/yi/a", "Apple")) + { + ns, e := c.Ls("/home/yi") + assert.Nil(e) + assert.Equal(1, len(ns)) + assert.Equal("/home/yi/a", ns[0]) + } + + assert.Nil(c.Mkdir("home/yi/b")) + { + ns, e := c.Ls("/home/yi") + assert.Nil(e) + assert.Equal(2, len(ns)) + } + + assert.Nil(c.Rm("/home")) +} From 99b9a4b8ae0596797b06bcfabd392552793c1366 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Fri, 12 Feb 2016 20:59:10 -0800 Subject: [PATCH 2/4] Minior enrichment of TestLs --- etcd_test.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/etcd_test.go b/etcd_test.go index 485d261..c8358f3 100644 --- a/etcd_test.go +++ b/etcd_test.go @@ -105,10 +105,12 @@ func TestLs(t *testing.T) { } assert.Nil(c.Mkdir("home/yi/b")) + assert.Nil(c.Rm("/home/yi/a")) { ns, e := c.Ls("/home/yi") assert.Nil(e) - assert.Equal(2, len(ns)) + assert.Equal(1, len(ns)) + assert.Equal("/home/yi/b", ns[0]) } assert.Nil(c.Rm("/home")) From d674c7239c94ddb5732fc445c35b64efbcfe5043 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Tue, 16 Feb 2016 08:03:31 -0800 Subject: [PATCH 3/4] Document Etcd.Ls --- etcc.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/etcc.go b/etcc.go index dc81968..e7941db 100644 --- a/etcc.go +++ b/etcc.go @@ -95,6 +95,10 @@ func (c *Etcd) Rm(key string) error { return e } +// Ls returns a list if key is a directory, or the key itself +// otherwise. Ls uses quorum=true read. For more about what does +// quorum=true mean, please refer to +// https://github.com/philips/etcd/blob/05bfb369ef1a8d6c56c9eed7e1ec972dccb25492/Documentation/api.md#read-linearization func (c *Etcd) Ls(key string) ([]string, error) { if len(key) == 0 { key = "/" From b3f6924e90b01fcda94c5bdebfb35eeed7608825 Mon Sep 17 00:00:00 2001 From: Yi Wang Date: Tue, 16 Feb 2016 08:04:32 -0800 Subject: [PATCH 4/4] Rename type Etcd into Etcc --- etcc.go | 18 +++++++++--------- etcc_test.go | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/etcc.go b/etcc.go index e7941db..1b0fc93 100644 --- a/etcc.go +++ b/etcc.go @@ -11,13 +11,13 @@ import ( "golang.org/x/net/context" ) -type Etcd struct { +type Etcc struct { api client.KeysAPI } // New trys to connect to etcd server. endpoints must be addreses // delimited by comma, like "http://127.0.0.1:4001,http://127.0.0.1:2379". -func New(endpoints string) (*Etcd, error) { +func New(endpoints string) (*Etcc, error) { eps := strings.Split(endpoints, ",") for i, ep := range eps { u, e := url.Parse(ep) @@ -48,7 +48,7 @@ func New(endpoints string) (*Etcd, error) { return nil, e } - return &Etcd{client.NewKeysAPI(c)}, nil + return &Etcc{client.NewKeysAPI(c)}, nil } func timeoutContext() (context.Context, context.CancelFunc) { @@ -58,25 +58,25 @@ func timeoutContext() (context.Context, context.CancelFunc) { // Mkdir creates a directory. The directory could be multiple-level, // like /home/yi/hello. But it must not exist before; otherwise Mkdir // returns an error. -func (c *Etcd) Mkdir(dir string) error { +func (c *Etcc) Mkdir(dir string) error { ctx, cancel := timeoutContext() defer cancel() _, e := c.api.Set(ctx, dir, "", &client.SetOptions{Dir: true, PrevExist: client.PrevNoExist}) return e } -func (c *Etcd) SetWithTTL(key, value string, ttl time.Duration) error { +func (c *Etcc) SetWithTTL(key, value string, ttl time.Duration) error { ctx, cancel := timeoutContext() defer cancel() _, e := c.api.Set(ctx, key, value, &client.SetOptions{TTL: ttl}) return e } -func (c *Etcd) Set(key, value string) error { +func (c *Etcc) Set(key, value string) error { return c.SetWithTTL(key, value, time.Duration(0)) } -func (c *Etcd) Get(key string) (string, error) { +func (c *Etcc) Get(key string) (string, error) { ctx, cancel := timeoutContext() defer cancel() r, e := c.api.Get(ctx, key, &client.GetOptions{Sort: true}) @@ -88,7 +88,7 @@ func (c *Etcd) Get(key string) (string, error) { // Rm removes a either a key-value pair or a directory. If it is a // directory, Rm removes all recursive content as well. -func (c *Etcd) Rm(key string) error { +func (c *Etcc) Rm(key string) error { ctx, cancel := timeoutContext() defer cancel() _, e := c.api.Delete(ctx, key, &client.DeleteOptions{Recursive: true}) @@ -99,7 +99,7 @@ func (c *Etcd) Rm(key string) error { // otherwise. Ls uses quorum=true read. For more about what does // quorum=true mean, please refer to // https://github.com/philips/etcd/blob/05bfb369ef1a8d6c56c9eed7e1ec972dccb25492/Documentation/api.md#read-linearization -func (c *Etcd) Ls(key string) ([]string, error) { +func (c *Etcc) Ls(key string) ([]string, error) { if len(key) == 0 { key = "/" } diff --git a/etcc_test.go b/etcc_test.go index 081923a..16510a9 100644 --- a/etcc_test.go +++ b/etcc_test.go @@ -24,7 +24,7 @@ func ExampleNew() { } var ( - c *Etcd + c *Etcc ) func init() { @@ -38,7 +38,7 @@ func init() { Must(e) } -func TestEtcdBasicSession(t *testing.T) { +func TestEtccBasicSession(t *testing.T) { assert := assert.New(t) c.Rm("/home/yi")