Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 58 additions & 31 deletions etcc.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -31,68 +31,95 @@ 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
return &Etcc{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)
func (c *Etcc) Mkdir(dir string) error {
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)
func (c *Etcc) SetWithTTL(key, value string, ttl time.Duration) error {
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 {
func (c *Etcc) Set(key, value string) error {
return c.SetWithTTL(key, value, time.Duration(0))
}

func (c *Etcd) Get(key string) (string, error) {
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
func (c *Etcc) Get(key string) (string, error) {
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 *Etcc) Rm(key string) error {
ctx, cancel := timeoutContext()
defer cancel()
_, e := c.api.Delete(ctx, key, &client.DeleteOptions{Recursive: true})
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 *Etcc) 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
}
43 changes: 38 additions & 5 deletions etcc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ 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 (
c *Etcd
c *Etcc
)

func init() {
Expand All @@ -38,10 +38,10 @@ func init() {
Must(e)
}

func TestEtcdBasicSession(t *testing.T) {
func TestEtccBasicSession(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"))
Expand All @@ -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) {
Expand All @@ -82,3 +82,36 @@ func TestSetWithTTL(t *testing.T) {
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"))
assert.Nil(c.Rm("/home/yi/a"))
{
ns, e := c.Ls("/home/yi")
assert.Nil(e)
assert.Equal(1, len(ns))
assert.Equal("/home/yi/b", ns[0])
}

assert.Nil(c.Rm("/home"))
}