Browse Source

Merge pull request #4306 from heyitsanthony/v3-client

replace raw v3 grpc connections with clientv3.Client
Anthony Romano 10 years ago
parent
commit
163812246f

+ 98 - 0
clientv3/client.go

@@ -0,0 +1,98 @@
+// Copyright 2016 CoreOS, 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,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package clientv3
+
+import (
+	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
+	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
+)
+
+type Client struct {
+	// KV is the keyvalue API for the client's connection.
+	KV pb.KVClient
+	// Lease is the lease API for the client's connection.
+	Lease pb.LeaseClient
+	// Watch is the watch API for the client's connection.
+	Watch pb.WatchClient
+	// Cluster is the cluster API for the client's connection.
+	Cluster pb.ClusterClient
+
+	conn *grpc.ClientConn
+	cfg  Config
+}
+
+type Config struct {
+	// Endpoints is a list of URLs
+	Endpoints []string
+
+	// TODO TLS options
+}
+
+// New creates a new etcdv3 client from a given configuration.
+func New(cfg Config) (*Client, error) {
+	conn, err := cfg.dialEndpoints()
+	if err != nil {
+		return nil, err
+	}
+	client := newClient(conn)
+	client.cfg = cfg
+	return client, nil
+}
+
+// NewFromURL creates a new etcdv3 client from a URL.
+func NewFromURL(url string) (*Client, error) {
+	return New(Config{Endpoints: []string{url}})
+}
+
+// NewFromConn creates a new etcdv3 client from an established grpc Connection.
+func NewFromConn(conn *grpc.ClientConn) *Client {
+	return newClient(conn)
+}
+
+// Clone creates a copy of client with the old connection and new API clients.
+func (c *Client) Clone() *Client {
+	cl := newClient(c.conn)
+	cl.cfg = c.cfg
+	return cl
+}
+
+// Close shuts down the client's etcd connections.
+func (c *Client) Close() error {
+	return c.conn.Close()
+}
+
+func newClient(conn *grpc.ClientConn) *Client {
+	return &Client{
+		KV:      pb.NewKVClient(conn),
+		Lease:   pb.NewLeaseClient(conn),
+		Watch:   pb.NewWatchClient(conn),
+		Cluster: pb.NewClusterClient(conn),
+		conn:    conn,
+	}
+}
+
+func (cfg *Config) dialEndpoints() (*grpc.ClientConn, error) {
+	var err error
+	for _, ep := range cfg.Endpoints {
+		// TODO: enable grpc.WithTransportCredentials(creds)
+		conn, curErr := grpc.Dial(ep, grpc.WithInsecure())
+		if err != nil {
+			err = curErr
+		} else {
+			return conn, nil
+		}
+	}
+	return nil, err
+}

+ 3 - 2
contrib/recipes/barrier.go

@@ -16,6 +16,7 @@ package recipe
 
 
 import (
 import (
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
+	"github.com/coreos/etcd/clientv3"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	"github.com/coreos/etcd/storage/storagepb"
 	"github.com/coreos/etcd/storage/storagepb"
 )
 )
@@ -23,11 +24,11 @@ import (
 // Barrier creates a key in etcd to block processes, then deletes the key to
 // Barrier creates a key in etcd to block processes, then deletes the key to
 // release all blocked processes.
 // release all blocked processes.
 type Barrier struct {
 type Barrier struct {
-	client *EtcdClient
+	client *clientv3.Client
 	key    string
 	key    string
 }
 }
 
 
-func NewBarrier(client *EtcdClient, key string) *Barrier {
+func NewBarrier(client *clientv3.Client, key string) *Barrier {
 	return &Barrier{client, key}
 	return &Barrier{client, key}
 }
 }
 
 

+ 4 - 19
contrib/recipes/client.go

@@ -18,7 +18,6 @@ import (
 	"errors"
 	"errors"
 
 
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
-	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	spb "github.com/coreos/etcd/storage/storagepb"
 	spb "github.com/coreos/etcd/storage/storagepb"
 )
 )
@@ -28,22 +27,8 @@ var (
 	ErrWaitMismatch = errors.New("unexpected wait result")
 	ErrWaitMismatch = errors.New("unexpected wait result")
 )
 )
 
 
-type EtcdClient struct {
-	conn  *grpc.ClientConn
-	KV    pb.KVClient
-	Lease pb.LeaseClient
-	Watch pb.WatchClient
-}
-
-func NewEtcdClient(conn *grpc.ClientConn) *EtcdClient {
-	kv := pb.NewKVClient(conn)
-	lease := pb.NewLeaseClient(conn)
-	watch := pb.NewWatchClient(conn)
-	return &EtcdClient{conn, kv, lease, watch}
-}
-
 // deleteRevKey deletes a key by revision, returning false if key is missing
 // deleteRevKey deletes a key by revision, returning false if key is missing
-func (ec *EtcdClient) deleteRevKey(key string, rev int64) (bool, error) {
+func deleteRevKey(kvc pb.KVClient, key string, rev int64) (bool, error) {
 	cmp := &pb.Compare{
 	cmp := &pb.Compare{
 		Result:      pb.Compare_EQUAL,
 		Result:      pb.Compare_EQUAL,
 		Target:      pb.Compare_MOD,
 		Target:      pb.Compare_MOD,
@@ -52,7 +37,7 @@ func (ec *EtcdClient) deleteRevKey(key string, rev int64) (bool, error) {
 	}
 	}
 	req := &pb.RequestUnion{Request: &pb.RequestUnion_RequestDeleteRange{
 	req := &pb.RequestUnion{Request: &pb.RequestUnion_RequestDeleteRange{
 		RequestDeleteRange: &pb.DeleteRangeRequest{Key: []byte(key)}}}
 		RequestDeleteRange: &pb.DeleteRangeRequest{Key: []byte(key)}}}
-	txnresp, err := ec.KV.Txn(
+	txnresp, err := kvc.Txn(
 		context.TODO(),
 		context.TODO(),
 		&pb.TxnRequest{
 		&pb.TxnRequest{
 			Compare: []*pb.Compare{cmp},
 			Compare: []*pb.Compare{cmp},
@@ -67,9 +52,9 @@ func (ec *EtcdClient) deleteRevKey(key string, rev int64) (bool, error) {
 	return true, nil
 	return true, nil
 }
 }
 
 
-func (ec *EtcdClient) claimFirstKey(kvs []*spb.KeyValue) (*spb.KeyValue, error) {
+func claimFirstKey(kvc pb.KVClient, kvs []*spb.KeyValue) (*spb.KeyValue, error) {
 	for _, kv := range kvs {
 	for _, kv := range kvs {
-		ok, err := ec.deleteRevKey(string(kv.Key), kv.ModRevision)
+		ok, err := deleteRevKey(kvc, string(kv.Key), kv.ModRevision)
 		if err != nil {
 		if err != nil {
 			return nil, err
 			return nil, err
 		} else if ok {
 		} else if ok {

+ 10 - 9
contrib/recipes/key.go

@@ -20,23 +20,24 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
+	"github.com/coreos/etcd/clientv3"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	"github.com/coreos/etcd/lease"
 	"github.com/coreos/etcd/lease"
 )
 )
 
 
 // Key is a key/revision pair created by the client and stored on etcd
 // Key is a key/revision pair created by the client and stored on etcd
 type RemoteKV struct {
 type RemoteKV struct {
-	client *EtcdClient
+	client *clientv3.Client
 	key    string
 	key    string
 	rev    int64
 	rev    int64
 	val    string
 	val    string
 }
 }
 
 
-func NewKey(client *EtcdClient, key string, leaseID lease.LeaseID) (*RemoteKV, error) {
+func NewKey(client *clientv3.Client, key string, leaseID lease.LeaseID) (*RemoteKV, error) {
 	return NewKV(client, key, "", leaseID)
 	return NewKV(client, key, "", leaseID)
 }
 }
 
 
-func NewKV(client *EtcdClient, key, val string, leaseID lease.LeaseID) (*RemoteKV, error) {
+func NewKV(client *clientv3.Client, key, val string, leaseID lease.LeaseID) (*RemoteKV, error) {
 	rev, err := putNewKV(client, key, val, leaseID)
 	rev, err := putNewKV(client, key, val, leaseID)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -44,7 +45,7 @@ func NewKV(client *EtcdClient, key, val string, leaseID lease.LeaseID) (*RemoteK
 	return &RemoteKV{client, key, rev, val}, nil
 	return &RemoteKV{client, key, rev, val}, nil
 }
 }
 
 
-func GetRemoteKV(client *EtcdClient, key string) (*RemoteKV, error) {
+func GetRemoteKV(client *clientv3.Client, key string) (*RemoteKV, error) {
 	resp, err := client.KV.Range(
 	resp, err := client.KV.Range(
 		context.TODO(),
 		context.TODO(),
 		&pb.RangeRequest{Key: []byte(key)},
 		&pb.RangeRequest{Key: []byte(key)},
@@ -65,11 +66,11 @@ func GetRemoteKV(client *EtcdClient, key string) (*RemoteKV, error) {
 		val:    val}, nil
 		val:    val}, nil
 }
 }
 
 
-func NewUniqueKey(client *EtcdClient, prefix string) (*RemoteKV, error) {
+func NewUniqueKey(client *clientv3.Client, prefix string) (*RemoteKV, error) {
 	return NewUniqueKV(client, prefix, "", 0)
 	return NewUniqueKV(client, prefix, "", 0)
 }
 }
 
 
-func NewUniqueKV(client *EtcdClient, prefix string, val string, leaseID lease.LeaseID) (*RemoteKV, error) {
+func NewUniqueKV(client *clientv3.Client, prefix string, val string, leaseID lease.LeaseID) (*RemoteKV, error) {
 	for {
 	for {
 		newKey := fmt.Sprintf("%s/%v", prefix, time.Now().UnixNano())
 		newKey := fmt.Sprintf("%s/%v", prefix, time.Now().UnixNano())
 		rev, err := putNewKV(client, newKey, val, 0)
 		rev, err := putNewKV(client, newKey, val, 0)
@@ -84,7 +85,7 @@ func NewUniqueKV(client *EtcdClient, prefix string, val string, leaseID lease.Le
 
 
 // putNewKV attempts to create the given key, only succeeding if the key did
 // putNewKV attempts to create the given key, only succeeding if the key did
 // not yet exist.
 // not yet exist.
-func putNewKV(ec *EtcdClient, key, val string, leaseID lease.LeaseID) (int64, error) {
+func putNewKV(ec *clientv3.Client, key, val string, leaseID lease.LeaseID) (int64, error) {
 	cmp := &pb.Compare{
 	cmp := &pb.Compare{
 		Result:      pb.Compare_EQUAL,
 		Result:      pb.Compare_EQUAL,
 		Target:      pb.Compare_VERSION,
 		Target:      pb.Compare_VERSION,
@@ -110,13 +111,13 @@ func putNewKV(ec *EtcdClient, key, val string, leaseID lease.LeaseID) (int64, er
 }
 }
 
 
 // NewSequentialKV allocates a new sequential key-value pair at <prefix>/nnnnn
 // NewSequentialKV allocates a new sequential key-value pair at <prefix>/nnnnn
-func NewSequentialKV(client *EtcdClient, prefix, val string) (*RemoteKV, error) {
+func NewSequentialKV(client *clientv3.Client, prefix, val string) (*RemoteKV, error) {
 	return newSequentialKV(client, prefix, val, 0)
 	return newSequentialKV(client, prefix, val, 0)
 }
 }
 
 
 // newSequentialKV allocates a new sequential key <prefix>/nnnnn with a given
 // newSequentialKV allocates a new sequential key <prefix>/nnnnn with a given
 // value and lease.  Note: a bookkeeping node __<prefix> is also allocated.
 // value and lease.  Note: a bookkeeping node __<prefix> is also allocated.
-func newSequentialKV(client *EtcdClient, prefix, val string, leaseID lease.LeaseID) (*RemoteKV, error) {
+func newSequentialKV(client *clientv3.Client, prefix, val string, leaseID lease.LeaseID) (*RemoteKV, error) {
 	resp, err := NewRange(client, prefix).LastKey()
 	resp, err := NewRange(client, prefix).LastKey()
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err

+ 4 - 3
contrib/recipes/mutex.go

@@ -17,17 +17,18 @@ package recipe
 import (
 import (
 	"sync"
 	"sync"
 
 
+	"github.com/coreos/etcd/clientv3"
 	"github.com/coreos/etcd/storage/storagepb"
 	"github.com/coreos/etcd/storage/storagepb"
 )
 )
 
 
 // Mutex implements the sync Locker interface with etcd
 // Mutex implements the sync Locker interface with etcd
 type Mutex struct {
 type Mutex struct {
-	client *EtcdClient
+	client *clientv3.Client
 	key    string
 	key    string
 	myKey  *RemoteKV
 	myKey  *RemoteKV
 }
 }
 
 
-func NewMutex(client *EtcdClient, key string) *Mutex {
+func NewMutex(client *clientv3.Client, key string) *Mutex {
 	return &Mutex{client, key, nil}
 	return &Mutex{client, key, nil}
 }
 }
 
 
@@ -80,6 +81,6 @@ func (lm *lockerMutex) Unlock() {
 	}
 	}
 }
 }
 
 
-func NewLocker(client *EtcdClient, key string) sync.Locker {
+func NewLocker(client *clientv3.Client, key string) sync.Locker {
 	return &lockerMutex{NewMutex(client, key)}
 	return &lockerMutex{NewMutex(client, key)}
 }
 }

+ 5 - 4
contrib/recipes/priority_queue.go

@@ -17,17 +17,18 @@ package recipe
 import (
 import (
 	"fmt"
 	"fmt"
 
 
+	"github.com/coreos/etcd/clientv3"
 	"github.com/coreos/etcd/storage/storagepb"
 	"github.com/coreos/etcd/storage/storagepb"
 )
 )
 
 
 // PriorityQueue implements a multi-reader, multi-writer distributed queue.
 // PriorityQueue implements a multi-reader, multi-writer distributed queue.
 type PriorityQueue struct {
 type PriorityQueue struct {
-	client *EtcdClient
+	client *clientv3.Client
 	key    string
 	key    string
 }
 }
 
 
 // NewPriorityQueue creates an etcd priority queue.
 // NewPriorityQueue creates an etcd priority queue.
-func NewPriorityQueue(client *EtcdClient, key string) *PriorityQueue {
+func NewPriorityQueue(client *clientv3.Client, key string) *PriorityQueue {
 	return &PriorityQueue{client, key + "/"}
 	return &PriorityQueue{client, key + "/"}
 }
 }
 
 
@@ -47,7 +48,7 @@ func (q *PriorityQueue) Dequeue() (string, error) {
 		return "", err
 		return "", err
 	}
 	}
 
 
-	kv, err := q.client.claimFirstKey(resp.Kvs)
+	kv, err := claimFirstKey(q.client.KV, resp.Kvs)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	} else if kv != nil {
 	} else if kv != nil {
@@ -67,7 +68,7 @@ func (q *PriorityQueue) Dequeue() (string, error) {
 		return "", err
 		return "", err
 	}
 	}
 
 
-	ok, err := q.client.deleteRevKey(string(ev.Kv.Key), ev.Kv.ModRevision)
+	ok, err := deleteRevKey(q.client.KV, string(ev.Kv.Key), ev.Kv.ModRevision)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	} else if !ok {
 	} else if !ok {

+ 5 - 4
contrib/recipes/queue.go

@@ -15,16 +15,17 @@
 package recipe
 package recipe
 
 
 import (
 import (
+	"github.com/coreos/etcd/clientv3"
 	"github.com/coreos/etcd/storage/storagepb"
 	"github.com/coreos/etcd/storage/storagepb"
 )
 )
 
 
 // Queue implements a multi-reader, multi-writer distributed queue.
 // Queue implements a multi-reader, multi-writer distributed queue.
 type Queue struct {
 type Queue struct {
-	client    *EtcdClient
+	client    *clientv3.Client
 	keyPrefix string
 	keyPrefix string
 }
 }
 
 
-func NewQueue(client *EtcdClient, keyPrefix string) *Queue {
+func NewQueue(client *clientv3.Client, keyPrefix string) *Queue {
 	return &Queue{client, keyPrefix}
 	return &Queue{client, keyPrefix}
 }
 }
 
 
@@ -42,7 +43,7 @@ func (q *Queue) Dequeue() (string, error) {
 		return "", err
 		return "", err
 	}
 	}
 
 
-	kv, err := q.client.claimFirstKey(resp.Kvs)
+	kv, err := claimFirstKey(q.client.KV, resp.Kvs)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	} else if kv != nil {
 	} else if kv != nil {
@@ -62,7 +63,7 @@ func (q *Queue) Dequeue() (string, error) {
 		return "", err
 		return "", err
 	}
 	}
 
 
-	ok, err := q.client.deleteRevKey(string(ev.Kv.Key), ev.Kv.ModRevision)
+	ok, err := deleteRevKey(q.client.KV, string(ev.Kv.Key), ev.Kv.ModRevision)
 	if err != nil {
 	if err != nil {
 		return "", err
 		return "", err
 	} else if !ok {
 	} else if !ok {

+ 3 - 2
contrib/recipes/range.go

@@ -16,6 +16,7 @@ package recipe
 
 
 import (
 import (
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
+	"github.com/coreos/etcd/clientv3"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 )
 )
 
 
@@ -26,11 +27,11 @@ type Range struct {
 	keyEnd []byte
 	keyEnd []byte
 }
 }
 
 
-func NewRange(client *EtcdClient, key string) *Range {
+func NewRange(client *clientv3.Client, key string) *Range {
 	return NewRangeRev(client, key, 0)
 	return NewRangeRev(client, key, 0)
 }
 }
 
 
-func NewRangeRev(client *EtcdClient, key string, rev int64) *Range {
+func NewRangeRev(client *clientv3.Client, key string, rev int64) *Range {
 	return &Range{client.KV, []byte(key), rev, prefixEnd(key)}
 	return &Range{client.KV, []byte(key), rev, prefixEnd(key)}
 }
 }
 
 

+ 3 - 2
contrib/recipes/rwmutex.go

@@ -15,16 +15,17 @@
 package recipe
 package recipe
 
 
 import (
 import (
+	"github.com/coreos/etcd/clientv3"
 	"github.com/coreos/etcd/storage/storagepb"
 	"github.com/coreos/etcd/storage/storagepb"
 )
 )
 
 
 type RWMutex struct {
 type RWMutex struct {
-	client *EtcdClient
+	client *clientv3.Client
 	key    string
 	key    string
 	myKey  *RemoteKV
 	myKey  *RemoteKV
 }
 }
 
 
-func NewRWMutex(client *EtcdClient, key string) *RWMutex {
+func NewRWMutex(client *clientv3.Client, key string) *RWMutex {
 	return &RWMutex{client, key, nil}
 	return &RWMutex{client, key, nil}
 }
 }
 
 

+ 3 - 2
contrib/recipes/stm.go

@@ -16,12 +16,13 @@ package recipe
 
 
 import (
 import (
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
+	"github.com/coreos/etcd/clientv3"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 )
 )
 
 
 // STM implements software transactional memory over etcd
 // STM implements software transactional memory over etcd
 type STM struct {
 type STM struct {
-	client *EtcdClient
+	client *clientv3.Client
 	// rset holds the read key's value and revision of read
 	// rset holds the read key's value and revision of read
 	rset map[string]*RemoteKV
 	rset map[string]*RemoteKV
 	// wset holds the write key and its value
 	// wset holds the write key and its value
@@ -32,7 +33,7 @@ type STM struct {
 }
 }
 
 
 // NewSTM creates new transaction loop for a given apply function.
 // NewSTM creates new transaction loop for a given apply function.
-func NewSTM(client *EtcdClient, apply func(*STM) error) <-chan error {
+func NewSTM(client *clientv3.Client, apply func(*STM) error) <-chan error {
 	s := &STM{client: client, apply: apply}
 	s := &STM{client: client, apply: apply}
 	errc := make(chan error, 1)
 	errc := make(chan error, 1)
 	go func() {
 	go func() {

+ 6 - 5
contrib/recipes/watch.go

@@ -16,6 +16,7 @@ package recipe
 
 
 import (
 import (
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
+	"github.com/coreos/etcd/clientv3"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	"github.com/coreos/etcd/storage"
 	"github.com/coreos/etcd/storage"
 	"github.com/coreos/etcd/storage/storagepb"
 	"github.com/coreos/etcd/storage/storagepb"
@@ -30,15 +31,15 @@ type Watcher struct {
 	lastErr error
 	lastErr error
 }
 }
 
 
-func NewWatcher(c *EtcdClient, key string, rev int64) (*Watcher, error) {
+func NewWatcher(c *clientv3.Client, key string, rev int64) (*Watcher, error) {
 	return newWatcher(c, key, rev, false)
 	return newWatcher(c, key, rev, false)
 }
 }
 
 
-func NewPrefixWatcher(c *EtcdClient, prefix string, rev int64) (*Watcher, error) {
+func NewPrefixWatcher(c *clientv3.Client, prefix string, rev int64) (*Watcher, error) {
 	return newWatcher(c, prefix, rev, true)
 	return newWatcher(c, prefix, rev, true)
 }
 }
 
 
-func newWatcher(c *EtcdClient, key string, rev int64, isPrefix bool) (*Watcher, error) {
+func newWatcher(c *clientv3.Client, key string, rev int64, isPrefix bool) (*Watcher, error) {
 	ctx, cancel := context.WithCancel(context.Background())
 	ctx, cancel := context.WithCancel(context.Background())
 	w, err := c.Watch.Watch(ctx)
 	w, err := c.Watch.Watch(ctx)
 	if err != nil {
 	if err != nil {
@@ -134,7 +135,7 @@ func (w *Watcher) waitEvents(evs []storagepb.Event_EventType) (*storagepb.Event,
 }
 }
 
 
 // WaitEvents waits on a key until it observes the given events and returns the final one.
 // WaitEvents waits on a key until it observes the given events and returns the final one.
-func WaitEvents(c *EtcdClient, key string, rev int64, evs []storagepb.Event_EventType) (*storagepb.Event, error) {
+func WaitEvents(c *clientv3.Client, key string, rev int64, evs []storagepb.Event_EventType) (*storagepb.Event, error) {
 	w, err := NewWatcher(c, key, rev)
 	w, err := NewWatcher(c, key, rev)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
@@ -143,7 +144,7 @@ func WaitEvents(c *EtcdClient, key string, rev int64, evs []storagepb.Event_Even
 	return w.waitEvents(evs)
 	return w.waitEvents(evs)
 }
 }
 
 
-func WaitPrefixEvents(c *EtcdClient, prefix string, rev int64, evs []storagepb.Event_EventType) (*storagepb.Event, error) {
+func WaitPrefixEvents(c *clientv3.Client, prefix string, rev int64, evs []storagepb.Event_EventType) (*storagepb.Event, error) {
 	w, err := NewPrefixWatcher(c, prefix, rev)
 	w, err := NewPrefixWatcher(c, prefix, rev)
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err

+ 1 - 13
etcdctlv3/command/compaction_command.go

@@ -20,7 +20,6 @@ import (
 
 
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
-	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 )
 )
 
 
@@ -44,17 +43,6 @@ func compactionCommandFunc(cmd *cobra.Command, args []string) {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
 
 
-	endpoint, err := cmd.Flags().GetString("endpoint")
-	if err != nil {
-		ExitWithError(ExitError, err)
-	}
-	// TODO: enable grpc.WithTransportCredentials(creds)
-	conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
-	if err != nil {
-		ExitWithError(ExitBadConnection, err)
-	}
-	kv := pb.NewKVClient(conn)
 	req := &pb.CompactionRequest{Revision: rev}
 	req := &pb.CompactionRequest{Revision: rev}
-
-	kv.Compact(context.Background(), req)
+	mustClient(cmd).KV.Compact(context.Background(), req)
 }
 }

+ 1 - 13
etcdctlv3/command/delete_range_command.go

@@ -19,7 +19,6 @@ import (
 
 
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
-	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 )
 )
 
 
@@ -44,19 +43,8 @@ func deleteRangeCommandFunc(cmd *cobra.Command, args []string) {
 		rangeEnd = []byte(args[1])
 		rangeEnd = []byte(args[1])
 	}
 	}
 
 
-	endpoint, err := cmd.Flags().GetString("endpoint")
-	if err != nil {
-		ExitWithError(ExitError, err)
-	}
-	// TODO: enable grpc.WithTransportCredentials(creds)
-	conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
-	if err != nil {
-		ExitWithError(ExitBadConnection, err)
-	}
-	kv := pb.NewKVClient(conn)
 	req := &pb.DeleteRangeRequest{Key: key, RangeEnd: rangeEnd}
 	req := &pb.DeleteRangeRequest{Key: key, RangeEnd: rangeEnd}
-
-	kv.DeleteRange(context.Background(), req)
+	mustClient(cmd).KV.DeleteRange(context.Background(), req)
 
 
 	if rangeEnd != nil {
 	if rangeEnd != nil {
 		fmt.Printf("range [%s, %s) is deleted\n", string(key), string(rangeEnd))
 		fmt.Printf("range [%s, %s) is deleted\n", string(key), string(rangeEnd))

+ 17 - 0
etcdctlv3/command/global.go

@@ -14,8 +14,25 @@
 
 
 package command
 package command
 
 
+import (
+	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
+	"github.com/coreos/etcd/clientv3"
+)
+
 // GlobalFlags are flags that defined globally
 // GlobalFlags are flags that defined globally
 // and are inherited to all sub-commands.
 // and are inherited to all sub-commands.
 type GlobalFlags struct {
 type GlobalFlags struct {
 	Endpoints string
 	Endpoints string
 }
 }
+
+func mustClient(cmd *cobra.Command) *clientv3.Client {
+	endpoint, err := cmd.Flags().GetString("endpoint")
+	if err != nil {
+		ExitWithError(ExitError, err)
+	}
+	client, err := clientv3.NewFromURL(endpoint)
+	if err != nil {
+		ExitWithError(ExitBadConnection, err)
+	}
+	return client
+}

+ 3 - 36
etcdctlv3/command/lease_command.go

@@ -23,7 +23,6 @@ import (
 
 
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
-	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 )
 )
 
 
@@ -64,19 +63,8 @@ func leaseCreateCommandFunc(cmd *cobra.Command, args []string) {
 		ExitWithError(ExitBadArgs, fmt.Errorf("bad TTL (%v)", err))
 		ExitWithError(ExitBadArgs, fmt.Errorf("bad TTL (%v)", err))
 	}
 	}
 
 
-	endpoint, err := cmd.Flags().GetString("endpoint")
-	if err != nil {
-		ExitWithError(ExitError, err)
-	}
-	// TODO: enable grpc.WithTransportCredentials(creds)
-	conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
-	if err != nil {
-		ExitWithError(ExitBadConnection, err)
-	}
-	lease := pb.NewLeaseClient(conn)
-
 	req := &pb.LeaseCreateRequest{TTL: ttl}
 	req := &pb.LeaseCreateRequest{TTL: ttl}
-	resp, err := lease.LeaseCreate(context.Background(), req)
+	resp, err := mustClient(cmd).Lease.LeaseCreate(context.Background(), req)
 	if err != nil {
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "failed to create lease (%v)\n", err)
 		fmt.Fprintf(os.Stderr, "failed to create lease (%v)\n", err)
 		return
 		return
@@ -107,19 +95,8 @@ func leaseRevokeCommandFunc(cmd *cobra.Command, args []string) {
 		ExitWithError(ExitBadArgs, fmt.Errorf("bad lease ID arg (%v), expecting ID in Hex", err))
 		ExitWithError(ExitBadArgs, fmt.Errorf("bad lease ID arg (%v), expecting ID in Hex", err))
 	}
 	}
 
 
-	endpoint, err := cmd.Flags().GetString("endpoint")
-	if err != nil {
-		ExitWithError(ExitError, err)
-	}
-	// TODO: enable grpc.WithTransportCredentials(creds)
-	conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
-	if err != nil {
-		ExitWithError(ExitBadConnection, err)
-	}
-	lease := pb.NewLeaseClient(conn)
-
 	req := &pb.LeaseRevokeRequest{ID: id}
 	req := &pb.LeaseRevokeRequest{ID: id}
-	_, err = lease.LeaseRevoke(context.Background(), req)
+	_, err = mustClient(cmd).Lease.LeaseRevoke(context.Background(), req)
 	if err != nil {
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "failed to revoke lease (%v)\n", err)
 		fmt.Fprintf(os.Stderr, "failed to revoke lease (%v)\n", err)
 		return
 		return
@@ -150,17 +127,7 @@ func leaseKeepAliveCommandFunc(cmd *cobra.Command, args []string) {
 		ExitWithError(ExitBadArgs, fmt.Errorf("bad lease ID arg (%v), expecting ID in Hex", err))
 		ExitWithError(ExitBadArgs, fmt.Errorf("bad lease ID arg (%v), expecting ID in Hex", err))
 	}
 	}
 
 
-	endpoint, err := cmd.Flags().GetString("endpoint")
-	if err != nil {
-		ExitWithError(ExitError, err)
-	}
-	// TODO: enable grpc.WithTransportCredentials(creds)
-	conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
-	if err != nil {
-		ExitWithError(ExitBadConnection, err)
-	}
-	lease := pb.NewLeaseClient(conn)
-	kStream, err := lease.LeaseKeepAlive(context.TODO())
+	kStream, err := mustClient(cmd).Lease.LeaseKeepAlive(context.TODO())
 	if err != nil {
 	if err != nil {
 		ExitWithError(ExitBadConnection, err)
 		ExitWithError(ExitBadConnection, err)
 	}
 	}

+ 7 - 49
etcdctlv3/command/member_command.go

@@ -21,7 +21,6 @@ import (
 
 
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
-	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 )
 )
 
 
@@ -109,18 +108,8 @@ func memberAddCommandFunc(cmd *cobra.Command, args []string) {
 
 
 	urls := strings.Split(memberPeerURLs, ",")
 	urls := strings.Split(memberPeerURLs, ",")
 
 
-	endpoint, err := cmd.Flags().GetString("endpoint")
-	if err != nil {
-		ExitWithError(ExitError, err)
-	}
-	// TODO: enable grpc.WithTransportCredentials(creds)
-	conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
-	if err != nil {
-		ExitWithError(ExitBadConnection, err)
-	}
-	mc := pb.NewClusterClient(conn)
-
-	resp, err := mc.MemberAdd(context.TODO(), &pb.MemberAddRequest{PeerURLs: urls})
+	req := &pb.MemberAddRequest{PeerURLs: urls}
+	resp, err := mustClient(cmd).Cluster.MemberAdd(context.TODO(), req)
 	if err != nil {
 	if err != nil {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
@@ -139,18 +128,8 @@ func memberRemoveCommandFunc(cmd *cobra.Command, args []string) {
 		ExitWithError(ExitBadArgs, fmt.Errorf("bad member ID arg (%v), expecting ID in Hex", err))
 		ExitWithError(ExitBadArgs, fmt.Errorf("bad member ID arg (%v), expecting ID in Hex", err))
 	}
 	}
 
 
-	endpoint, err := cmd.Flags().GetString("endpoint")
-	if err != nil {
-		ExitWithError(ExitError, err)
-	}
-	// TODO: enable grpc.WithTransportCredentials(creds)
-	conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
-	if err != nil {
-		ExitWithError(ExitBadConnection, err)
-	}
-	mc := pb.NewClusterClient(conn)
-
-	resp, err := mc.MemberRemove(context.TODO(), &pb.MemberRemoveRequest{ID: uint64(id)})
+	req := &pb.MemberRemoveRequest{ID: uint64(id)}
+	resp, err := mustClient(cmd).Cluster.MemberRemove(context.TODO(), req)
 	if err != nil {
 	if err != nil {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
@@ -175,18 +154,8 @@ func memberUpdateCommandFunc(cmd *cobra.Command, args []string) {
 
 
 	urls := strings.Split(memberPeerURLs, ",")
 	urls := strings.Split(memberPeerURLs, ",")
 
 
-	endpoint, err := cmd.Flags().GetString("endpoint")
-	if err != nil {
-		ExitWithError(ExitError, err)
-	}
-	// TODO: enable grpc.WithTransportCredentials(creds)
-	conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
-	if err != nil {
-		ExitWithError(ExitBadConnection, err)
-	}
-	mc := pb.NewClusterClient(conn)
-
-	resp, err := mc.MemberUpdate(context.TODO(), &pb.MemberUpdateRequest{ID: uint64(id), PeerURLs: urls})
+	req := &pb.MemberUpdateRequest{ID: uint64(id), PeerURLs: urls}
+	resp, err := mustClient(cmd).Cluster.MemberUpdate(context.TODO(), req)
 	if err != nil {
 	if err != nil {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}
@@ -196,18 +165,7 @@ func memberUpdateCommandFunc(cmd *cobra.Command, args []string) {
 
 
 // memberListCommandFunc executes the "member list" command.
 // memberListCommandFunc executes the "member list" command.
 func memberListCommandFunc(cmd *cobra.Command, args []string) {
 func memberListCommandFunc(cmd *cobra.Command, args []string) {
-	endpoint, err := cmd.Flags().GetString("endpoint")
-	if err != nil {
-		ExitWithError(ExitError, err)
-	}
-	// TODO: enable grpc.WithTransportCredentials(creds)
-	conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
-	if err != nil {
-		ExitWithError(ExitBadConnection, err)
-	}
-	mc := pb.NewClusterClient(conn)
-
-	resp, err := mc.MemberList(context.TODO(), &pb.MemberListRequest{})
+	resp, err := mustClient(cmd).Cluster.MemberList(context.TODO(), &pb.MemberListRequest{})
 	if err != nil {
 	if err != nil {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}

+ 1 - 13
etcdctlv3/command/put_command.go

@@ -20,7 +20,6 @@ import (
 
 
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
-	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 )
 )
 
 
@@ -62,18 +61,7 @@ func putCommandFunc(cmd *cobra.Command, args []string) {
 	key := []byte(args[0])
 	key := []byte(args[0])
 	value := []byte(args[1])
 	value := []byte(args[1])
 
 
-	endpoint, err := cmd.Flags().GetString("endpoint")
-	if err != nil {
-		ExitWithError(ExitError, err)
-	}
-	// TODO: enable grpc.WithTransportCredentials(creds)
-	conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
-	if err != nil {
-		ExitWithError(ExitBadConnection, err)
-	}
-	kv := pb.NewKVClient(conn)
 	req := &pb.PutRequest{Key: key, Value: value, Lease: id}
 	req := &pb.PutRequest{Key: key, Value: value, Lease: id}
-
-	kv.Put(context.Background(), req)
+	mustClient(cmd).KV.Put(context.Background(), req)
 	fmt.Printf("%s %s\n", key, value)
 	fmt.Printf("%s %s\n", key, value)
 }
 }

+ 4 - 13
etcdctlv3/command/range_command.go

@@ -20,7 +20,6 @@ import (
 
 
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
-	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 )
 )
 
 
@@ -55,11 +54,6 @@ func rangeCommandFunc(cmd *cobra.Command, args []string) {
 		rangeEnd = []byte(args[1])
 		rangeEnd = []byte(args[1])
 	}
 	}
 
 
-	endpoint, err := cmd.Flags().GetString("endpoint")
-	if err != nil {
-		ExitWithError(ExitError, err)
-	}
-
 	sortByOrder := pb.RangeRequest_NONE
 	sortByOrder := pb.RangeRequest_NONE
 	sortOrder := strings.ToUpper(rangeSortOrder)
 	sortOrder := strings.ToUpper(rangeSortOrder)
 	switch {
 	switch {
@@ -92,12 +86,6 @@ func rangeCommandFunc(cmd *cobra.Command, args []string) {
 		ExitWithError(ExitBadFeature, fmt.Errorf("bad sort target %v", rangeSortTarget))
 		ExitWithError(ExitBadFeature, fmt.Errorf("bad sort target %v", rangeSortTarget))
 	}
 	}
 
 
-	// TODO: enable grpc.WithTransportCredentials(creds)
-	conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
-	if err != nil {
-		ExitWithError(ExitBadConnection, err)
-	}
-	kv := pb.NewKVClient(conn)
 	req := &pb.RangeRequest{
 	req := &pb.RangeRequest{
 		Key:        key,
 		Key:        key,
 		RangeEnd:   rangeEnd,
 		RangeEnd:   rangeEnd,
@@ -105,7 +93,10 @@ func rangeCommandFunc(cmd *cobra.Command, args []string) {
 		SortTarget: sortByTarget,
 		SortTarget: sortByTarget,
 		Limit:      int64(rangeLimit),
 		Limit:      int64(rangeLimit),
 	}
 	}
-	resp, err := kv.Range(context.Background(), req)
+	resp, err := mustClient(cmd).KV.Range(context.Background(), req)
+	if err != nil {
+		ExitWithError(ExitError, err)
+	}
 	for _, kv := range resp.Kvs {
 	for _, kv := range resp.Kvs {
 		fmt.Printf("%s %s\n", string(kv.Key), string(kv.Value))
 		fmt.Printf("%s %s\n", string(kv.Key), string(kv.Value))
 	}
 	}

+ 1 - 13
etcdctlv3/command/txn_command.go

@@ -23,7 +23,6 @@ import (
 
 
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
-	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 )
 )
 
 
@@ -50,18 +49,7 @@ func txnCommandFunc(cmd *cobra.Command, args []string) {
 		next = next(txn, reader)
 		next = next(txn, reader)
 	}
 	}
 
 
-	endpoint, err := cmd.Flags().GetString("endpoint")
-	if err != nil {
-		ExitWithError(ExitError, err)
-	}
-	// TODO: enable grpc.WithTransportCredentials(creds)
-	conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
-	if err != nil {
-		ExitWithError(ExitBadConnection, err)
-	}
-	kv := pb.NewKVClient(conn)
-
-	resp, err := kv.Txn(context.Background(), txn)
+	resp, err := mustClient(cmd).KV.Txn(context.Background(), txn)
 	if err != nil {
 	if err != nil {
 		ExitWithError(ExitError, err)
 		ExitWithError(ExitError, err)
 	}
 	}

+ 1 - 13
etcdctlv3/command/watch_command.go

@@ -24,7 +24,6 @@ import (
 
 
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
-	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 )
 )
 
 
@@ -39,18 +38,7 @@ func NewWatchCommand() *cobra.Command {
 
 
 // watchCommandFunc executes the "watch" command.
 // watchCommandFunc executes the "watch" command.
 func watchCommandFunc(cmd *cobra.Command, args []string) {
 func watchCommandFunc(cmd *cobra.Command, args []string) {
-	endpoint, err := cmd.Flags().GetString("endpoint")
-	if err != nil {
-		ExitWithError(ExitInvalidInput, err)
-	}
-	// TODO: enable grpc.WithTransportCredentials(creds)
-	conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
-	if err != nil {
-		ExitWithError(ExitBadConnection, err)
-	}
-
-	wAPI := pb.NewWatchClient(conn)
-	wStream, err := wAPI.Watch(context.TODO())
+	wStream, err := mustClient(cmd).Watch.Watch(context.TODO())
 	if err != nil {
 	if err != nil {
 		ExitWithError(ExitBadConnection, err)
 		ExitWithError(ExitBadConnection, err)
 	}
 	}

+ 8 - 3
integration/cluster_test.go

@@ -32,6 +32,7 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/coreos/etcd/client"
 	"github.com/coreos/etcd/client"
+	"github.com/coreos/etcd/clientv3"
 	"github.com/coreos/etcd/etcdserver"
 	"github.com/coreos/etcd/etcdserver"
 	"github.com/coreos/etcd/etcdserver/api/v3rpc"
 	"github.com/coreos/etcd/etcdserver/api/v3rpc"
 	"github.com/coreos/etcd/etcdserver/etcdhttp"
 	"github.com/coreos/etcd/etcdserver/etcdhttp"
@@ -729,8 +730,8 @@ func (m *member) listenGRPC() error {
 	return nil
 	return nil
 }
 }
 
 
-// newGrpcClient creates a new grpc client connection to the member
-func NewGRPCClient(m *member) (*grpc.ClientConn, error) {
+// NewClientV3 creates a new grpc client connection to the member
+func NewClientV3(m *member) (*clientv3.Client, error) {
 	if m.grpcAddr == "" {
 	if m.grpcAddr == "" {
 		return nil, fmt.Errorf("member not configured for grpc")
 		return nil, fmt.Errorf("member not configured for grpc")
 	}
 	}
@@ -738,7 +739,11 @@ func NewGRPCClient(m *member) (*grpc.ClientConn, error) {
 		return net.Dial("unix", a)
 		return net.Dial("unix", a)
 	}
 	}
 	unixdialer := grpc.WithDialer(f)
 	unixdialer := grpc.WithDialer(f)
-	return grpc.Dial(m.grpcAddr, grpc.WithInsecure(), unixdialer)
+	conn, err := grpc.Dial(m.grpcAddr, grpc.WithInsecure(), unixdialer)
+	if err != nil {
+		return nil, err
+	}
+	return clientv3.NewFromConn(conn), nil
 }
 }
 
 
 // Clone returns a member with the same server configuration. The returned
 // Clone returns a member with the same server configuration. The returned

+ 8 - 8
integration/v3_barrier_test.go

@@ -17,27 +17,27 @@ import (
 	"testing"
 	"testing"
 	"time"
 	"time"
 
 
-	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
+	"github.com/coreos/etcd/clientv3"
 	"github.com/coreos/etcd/contrib/recipes"
 	"github.com/coreos/etcd/contrib/recipes"
 	"github.com/coreos/etcd/pkg/testutil"
 	"github.com/coreos/etcd/pkg/testutil"
 )
 )
 
 
 func TestBarrierSingleNode(t *testing.T) {
 func TestBarrierSingleNode(t *testing.T) {
 	defer testutil.AfterTest(t)
 	defer testutil.AfterTest(t)
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
-	testBarrier(t, 5, func() *grpc.ClientConn { return clus.conns[0] })
+	testBarrier(t, 5, func() *clientv3.Client { return clus.clients[0] })
 }
 }
 
 
 func TestBarrierMultiNode(t *testing.T) {
 func TestBarrierMultiNode(t *testing.T) {
 	defer testutil.AfterTest(t)
 	defer testutil.AfterTest(t)
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
-	testBarrier(t, 5, func() *grpc.ClientConn { return clus.RandConn() })
+	testBarrier(t, 5, func() *clientv3.Client { return clus.RandClient() })
 }
 }
 
 
-func testBarrier(t *testing.T, waiters int, chooseConn func() *grpc.ClientConn) {
-	b := recipe.NewBarrier(recipe.NewEtcdClient(chooseConn()), "test-barrier")
+func testBarrier(t *testing.T, waiters int, chooseClient func() *clientv3.Client) {
+	b := recipe.NewBarrier(chooseClient(), "test-barrier")
 	if err := b.Hold(); err != nil {
 	if err := b.Hold(); err != nil {
 		t.Fatalf("could not hold barrier (%v)", err)
 		t.Fatalf("could not hold barrier (%v)", err)
 	}
 	}
@@ -48,7 +48,7 @@ func testBarrier(t *testing.T, waiters int, chooseConn func() *grpc.ClientConn)
 	donec := make(chan struct{})
 	donec := make(chan struct{})
 	for i := 0; i < waiters; i++ {
 	for i := 0; i < waiters; i++ {
 		go func() {
 		go func() {
-			br := recipe.NewBarrier(recipe.NewEtcdClient(chooseConn()), "test-barrier")
+			br := recipe.NewBarrier(chooseClient(), "test-barrier")
 			if err := br.Wait(); err != nil {
 			if err := br.Wait(); err != nil {
 				t.Fatalf("could not wait on barrier (%v)", err)
 				t.Fatalf("could not wait on barrier (%v)", err)
 			}
 			}

+ 55 - 60
integration/v3_grpc_test.go

@@ -24,7 +24,7 @@ import (
 	"time"
 	"time"
 
 
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
-	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
+	"github.com/coreos/etcd/clientv3"
 	"github.com/coreos/etcd/etcdserver/api/v3rpc"
 	"github.com/coreos/etcd/etcdserver/api/v3rpc"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
 	"github.com/coreos/etcd/lease"
 	"github.com/coreos/etcd/lease"
@@ -34,47 +34,47 @@ import (
 
 
 type clusterV3 struct {
 type clusterV3 struct {
 	*cluster
 	*cluster
-	conns []*grpc.ClientConn
+	clients []*clientv3.Client
 }
 }
 
 
-// newClusterGRPC returns a launched cluster with a grpc client connection
+// newClusterV3 returns a launched cluster with a grpc client connection
 // for each cluster member.
 // for each cluster member.
-func newClusterGRPC(t *testing.T, cfg *clusterConfig) *clusterV3 {
+func newClusterV3(t *testing.T, cfg *clusterConfig) *clusterV3 {
 	cfg.useV3 = true
 	cfg.useV3 = true
 	cfg.useGRPC = true
 	cfg.useGRPC = true
 	clus := &clusterV3{cluster: NewClusterByConfig(t, cfg)}
 	clus := &clusterV3{cluster: NewClusterByConfig(t, cfg)}
 	for _, m := range clus.Members {
 	for _, m := range clus.Members {
-		conn, err := NewGRPCClient(m)
+		client, err := NewClientV3(m)
 		if err != nil {
 		if err != nil {
 			t.Fatal(err)
 			t.Fatal(err)
 		}
 		}
-		clus.conns = append(clus.conns, conn)
+		clus.clients = append(clus.clients, client)
 	}
 	}
 	clus.Launch(t)
 	clus.Launch(t)
 	return clus
 	return clus
 }
 }
 
 
 func (c *clusterV3) Terminate(t *testing.T) {
 func (c *clusterV3) Terminate(t *testing.T) {
-	for _, conn := range c.conns {
-		if err := conn.Close(); err != nil {
+	for _, client := range c.clients {
+		if err := client.Close(); err != nil {
 			t.Error(err)
 			t.Error(err)
 		}
 		}
 	}
 	}
 	c.cluster.Terminate(t)
 	c.cluster.Terminate(t)
 }
 }
 
 
-func (c *clusterV3) RandConn() *grpc.ClientConn {
-	return c.conns[rand.Intn(len(c.conns))]
+func (c *clusterV3) RandClient() *clientv3.Client {
+	return c.clients[rand.Intn(len(c.clients))]
 }
 }
 
 
 // TestV3PutOverwrite puts a key with the v3 api to a random cluster member,
 // TestV3PutOverwrite puts a key with the v3 api to a random cluster member,
 // overwrites it, then checks that the change was applied.
 // overwrites it, then checks that the change was applied.
 func TestV3PutOverwrite(t *testing.T) {
 func TestV3PutOverwrite(t *testing.T) {
 	defer testutil.AfterTest(t)
 	defer testutil.AfterTest(t)
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
 
 
-	kvc := pb.NewKVClient(clus.RandConn())
+	kvc := clus.RandClient().KV
 	key := []byte("foo")
 	key := []byte("foo")
 	reqput := &pb.PutRequest{Key: key, Value: []byte("bar")}
 	reqput := &pb.PutRequest{Key: key, Value: []byte("bar")}
 
 
@@ -115,10 +115,10 @@ func TestV3PutOverwrite(t *testing.T) {
 
 
 func TestV3TxnTooManyOps(t *testing.T) {
 func TestV3TxnTooManyOps(t *testing.T) {
 	defer testutil.AfterTest(t)
 	defer testutil.AfterTest(t)
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
 
 
-	kvc := pb.NewKVClient(clus.RandConn())
+	kvc := clus.RandClient().KV
 
 
 	addCompareOps := func(txn *pb.TxnRequest) {
 	addCompareOps := func(txn *pb.TxnRequest) {
 		txn.Compare = append(txn.Compare,
 		txn.Compare = append(txn.Compare,
@@ -173,10 +173,10 @@ func TestV3TxnTooManyOps(t *testing.T) {
 // TestV3PutMissingLease ensures that a Put on a key with a bogus lease fails.
 // TestV3PutMissingLease ensures that a Put on a key with a bogus lease fails.
 func TestV3PutMissingLease(t *testing.T) {
 func TestV3PutMissingLease(t *testing.T) {
 	defer testutil.AfterTest(t)
 	defer testutil.AfterTest(t)
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
 
 
-	kvc := pb.NewKVClient(clus.RandConn())
+	kvc := clus.RandClient().KV
 	key := []byte("foo")
 	key := []byte("foo")
 	preq := &pb.PutRequest{Key: key, Lease: 123456}
 	preq := &pb.PutRequest{Key: key, Lease: 123456}
 	tests := []func(){
 	tests := []func(){
@@ -290,8 +290,8 @@ func TestV3DeleteRange(t *testing.T) {
 	}
 	}
 
 
 	for i, tt := range tests {
 	for i, tt := range tests {
-		clus := newClusterGRPC(t, &clusterConfig{size: 3})
-		kvc := pb.NewKVClient(clus.RandConn())
+		clus := newClusterV3(t, &clusterConfig{size: 3})
+		kvc := clus.RandClient().KV
 
 
 		ks := tt.keySet
 		ks := tt.keySet
 		for j := range ks {
 		for j := range ks {
@@ -336,10 +336,10 @@ func TestV3DeleteRange(t *testing.T) {
 // TestV3TxnInvaildRange tests txn
 // TestV3TxnInvaildRange tests txn
 func TestV3TxnInvaildRange(t *testing.T) {
 func TestV3TxnInvaildRange(t *testing.T) {
 	defer testutil.AfterTest(t)
 	defer testutil.AfterTest(t)
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
 
 
-	kvc := pb.NewKVClient(clus.RandConn())
+	kvc := clus.RandClient().KV
 	preq := &pb.PutRequest{Key: []byte("foo"), Value: []byte("bar")}
 	preq := &pb.PutRequest{Key: []byte("foo"), Value: []byte("bar")}
 
 
 	for i := 0; i < 3; i++ {
 	for i := 0; i < 3; i++ {
@@ -553,9 +553,9 @@ func TestV3WatchFromCurrentRevision(t *testing.T) {
 	}
 	}
 
 
 	for i, tt := range tests {
 	for i, tt := range tests {
-		clus := newClusterGRPC(t, &clusterConfig{size: 3})
+		clus := newClusterV3(t, &clusterConfig{size: 3})
 
 
-		wAPI := pb.NewWatchClient(clus.RandConn())
+		wAPI := clus.RandClient().Watch
 		ctx, cancel := context.WithCancel(context.Background())
 		ctx, cancel := context.WithCancel(context.Background())
 		defer cancel()
 		defer cancel()
 		wStream, err := wAPI.Watch(ctx)
 		wStream, err := wAPI.Watch(ctx)
@@ -569,7 +569,7 @@ func TestV3WatchFromCurrentRevision(t *testing.T) {
 
 
 		go func() {
 		go func() {
 			for _, k := range tt.putKeys {
 			for _, k := range tt.putKeys {
-				kvc := pb.NewKVClient(clus.RandConn())
+				kvc := clus.RandClient().KV
 				req := &pb.PutRequest{Key: []byte(k), Value: []byte("bar")}
 				req := &pb.PutRequest{Key: []byte(k), Value: []byte("bar")}
 				if _, err := kvc.Put(context.TODO(), req); err != nil {
 				if _, err := kvc.Put(context.TODO(), req); err != nil {
 					t.Fatalf("#%d: couldn't put key (%v)", i, err)
 					t.Fatalf("#%d: couldn't put key (%v)", i, err)
@@ -629,12 +629,11 @@ func TestV3WatchCancelUnsynced(t *testing.T) {
 }
 }
 
 
 func testV3WatchCancel(t *testing.T, startRev int64) {
 func testV3WatchCancel(t *testing.T, startRev int64) {
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
-	wAPI := pb.NewWatchClient(clus.RandConn())
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 
 
 	ctx, cancel := context.WithCancel(context.Background())
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 	defer cancel()
-	wStream, errW := wAPI.Watch(ctx)
+	wStream, errW := clus.RandClient().Watch.Watch(ctx)
 	if errW != nil {
 	if errW != nil {
 		t.Fatalf("wAPI.Watch error: %v", errW)
 		t.Fatalf("wAPI.Watch error: %v", errW)
 	}
 	}
@@ -669,7 +668,7 @@ func testV3WatchCancel(t *testing.T, startRev int64) {
 		t.Errorf("cresp.Canceled got = %v, want = true", cresp.Canceled)
 		t.Errorf("cresp.Canceled got = %v, want = true", cresp.Canceled)
 	}
 	}
 
 
-	kvc := pb.NewKVClient(clus.RandConn())
+	kvc := clus.RandClient().KV
 	if _, err := kvc.Put(context.TODO(), &pb.PutRequest{Key: []byte("foo"), Value: []byte("bar")}); err != nil {
 	if _, err := kvc.Put(context.TODO(), &pb.PutRequest{Key: []byte("foo"), Value: []byte("bar")}); err != nil {
 		t.Errorf("couldn't put key (%v)", err)
 		t.Errorf("couldn't put key (%v)", err)
 	}
 	}
@@ -698,13 +697,12 @@ func TestV3WatchMultipleWatchersUnsynced(t *testing.T) {
 // that matches all watchers, and another key that matches only
 // that matches all watchers, and another key that matches only
 // one watcher to test if it receives expected events.
 // one watcher to test if it receives expected events.
 func testV3WatchMultipleWatchers(t *testing.T, startRev int64) {
 func testV3WatchMultipleWatchers(t *testing.T, startRev int64) {
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
-	wAPI := pb.NewWatchClient(clus.RandConn())
-	kvc := pb.NewKVClient(clus.RandConn())
+	clus := newClusterV3(t, &clusterConfig{size: 3})
+	kvc := clus.RandClient().KV
 
 
 	ctx, cancel := context.WithCancel(context.Background())
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 	defer cancel()
-	wStream, errW := wAPI.Watch(ctx)
+	wStream, errW := clus.RandClient().Watch.Watch(ctx)
 	if errW != nil {
 	if errW != nil {
 		t.Fatalf("wAPI.Watch error: %v", errW)
 		t.Fatalf("wAPI.Watch error: %v", errW)
 	}
 	}
@@ -801,12 +799,11 @@ func TestV3WatchMultipleEventsTxnUnsynced(t *testing.T) {
 
 
 // testV3WatchMultipleEventsTxn tests Watch APIs when it receives multiple events.
 // testV3WatchMultipleEventsTxn tests Watch APIs when it receives multiple events.
 func testV3WatchMultipleEventsTxn(t *testing.T, startRev int64) {
 func testV3WatchMultipleEventsTxn(t *testing.T, startRev int64) {
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 
 
-	wAPI := pb.NewWatchClient(clus.RandConn())
 	ctx, cancel := context.WithCancel(context.Background())
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 	defer cancel()
-	wStream, wErr := wAPI.Watch(ctx)
+	wStream, wErr := clus.RandClient().Watch.Watch(ctx)
 	if wErr != nil {
 	if wErr != nil {
 		t.Fatalf("wAPI.Watch error: %v", wErr)
 		t.Fatalf("wAPI.Watch error: %v", wErr)
 	}
 	}
@@ -818,7 +815,7 @@ func testV3WatchMultipleEventsTxn(t *testing.T, startRev int64) {
 		t.Fatalf("wStream.Send error: %v", err)
 		t.Fatalf("wStream.Send error: %v", err)
 	}
 	}
 
 
-	kvc := pb.NewKVClient(clus.RandConn())
+	kvc := clus.RandClient().KV
 	txn := pb.TxnRequest{}
 	txn := pb.TxnRequest{}
 	for i := 0; i < 3; i++ {
 	for i := 0; i < 3; i++ {
 		ru := &pb.RequestUnion{}
 		ru := &pb.RequestUnion{}
@@ -885,10 +882,10 @@ func (evs eventsSortByKey) Less(i, j int) bool { return bytes.Compare(evs[i].Kv.
 
 
 func TestV3WatchMultipleEventsPutUnsynced(t *testing.T) {
 func TestV3WatchMultipleEventsPutUnsynced(t *testing.T) {
 	defer testutil.AfterTest(t)
 	defer testutil.AfterTest(t)
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
 
 
-	kvc := pb.NewKVClient(clus.RandConn())
+	kvc := clus.RandClient().KV
 
 
 	if _, err := kvc.Put(context.TODO(), &pb.PutRequest{Key: []byte("foo0"), Value: []byte("bar")}); err != nil {
 	if _, err := kvc.Put(context.TODO(), &pb.PutRequest{Key: []byte("foo0"), Value: []byte("bar")}); err != nil {
 		t.Fatalf("couldn't put key (%v)", err)
 		t.Fatalf("couldn't put key (%v)", err)
@@ -897,10 +894,9 @@ func TestV3WatchMultipleEventsPutUnsynced(t *testing.T) {
 		t.Fatalf("couldn't put key (%v)", err)
 		t.Fatalf("couldn't put key (%v)", err)
 	}
 	}
 
 
-	wAPI := pb.NewWatchClient(clus.RandConn())
 	ctx, cancel := context.WithCancel(context.Background())
 	ctx, cancel := context.WithCancel(context.Background())
 	defer cancel()
 	defer cancel()
-	wStream, wErr := wAPI.Watch(ctx)
+	wStream, wErr := clus.RandClient().Watch.Watch(ctx)
 	if wErr != nil {
 	if wErr != nil {
 		t.Fatalf("wAPI.Watch error: %v", wErr)
 		t.Fatalf("wAPI.Watch error: %v", wErr)
 	}
 	}
@@ -975,9 +971,9 @@ func TestV3WatchMultipleStreamsUnsynced(t *testing.T) {
 
 
 // testV3WatchMultipleStreams tests multiple watchers on the same key on multiple streams.
 // testV3WatchMultipleStreams tests multiple watchers on the same key on multiple streams.
 func testV3WatchMultipleStreams(t *testing.T, startRev int64) {
 func testV3WatchMultipleStreams(t *testing.T, startRev int64) {
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
-	wAPI := pb.NewWatchClient(clus.RandConn())
-	kvc := pb.NewKVClient(clus.RandConn())
+	clus := newClusterV3(t, &clusterConfig{size: 3})
+	wAPI := clus.RandClient().Watch
+	kvc := clus.RandClient().KV
 
 
 	streams := make([]pb.Watch_WatchClient, 5)
 	streams := make([]pb.Watch_WatchClient, 5)
 	for i := range streams {
 	for i := range streams {
@@ -1199,9 +1195,9 @@ func TestV3RangeRequest(t *testing.T) {
 	}
 	}
 
 
 	for i, tt := range tests {
 	for i, tt := range tests {
-		clus := newClusterGRPC(t, &clusterConfig{size: 3})
+		clus := newClusterV3(t, &clusterConfig{size: 3})
 		for _, k := range tt.putKeys {
 		for _, k := range tt.putKeys {
-			kvc := pb.NewKVClient(clus.RandConn())
+			kvc := clus.RandClient().KV
 			req := &pb.PutRequest{Key: []byte(k), Value: []byte("bar")}
 			req := &pb.PutRequest{Key: []byte(k), Value: []byte("bar")}
 			if _, err := kvc.Put(context.TODO(), req); err != nil {
 			if _, err := kvc.Put(context.TODO(), req); err != nil {
 				t.Fatalf("#%d: couldn't put key (%v)", i, err)
 				t.Fatalf("#%d: couldn't put key (%v)", i, err)
@@ -1209,7 +1205,7 @@ func TestV3RangeRequest(t *testing.T) {
 		}
 		}
 
 
 		for j, req := range tt.reqs {
 		for j, req := range tt.reqs {
-			kvc := pb.NewKVClient(clus.RandConn())
+			kvc := clus.RandClient().KV
 			resp, err := kvc.Range(context.TODO(), &req)
 			resp, err := kvc.Range(context.TODO(), &req)
 			if err != nil {
 			if err != nil {
 				t.Errorf("#%d.%d: Range error: %v", i, j, err)
 				t.Errorf("#%d.%d: Range error: %v", i, j, err)
@@ -1244,7 +1240,7 @@ func TestV3RangeRequest(t *testing.T) {
 func TestV3LeaseRevoke(t *testing.T) {
 func TestV3LeaseRevoke(t *testing.T) {
 	defer testutil.AfterTest(t)
 	defer testutil.AfterTest(t)
 	testLeaseRemoveLeasedKey(t, func(clus *clusterV3, leaseID int64) error {
 	testLeaseRemoveLeasedKey(t, func(clus *clusterV3, leaseID int64) error {
-		lc := pb.NewLeaseClient(clus.RandConn())
+		lc := clus.RandClient().Lease
 		_, err := lc.LeaseRevoke(context.TODO(), &pb.LeaseRevokeRequest{ID: leaseID})
 		_, err := lc.LeaseRevoke(context.TODO(), &pb.LeaseRevokeRequest{ID: leaseID})
 		return err
 		return err
 	})
 	})
@@ -1253,11 +1249,11 @@ func TestV3LeaseRevoke(t *testing.T) {
 // TestV3LeaseCreateById ensures leases may be created by a given id.
 // TestV3LeaseCreateById ensures leases may be created by a given id.
 func TestV3LeaseCreateByID(t *testing.T) {
 func TestV3LeaseCreateByID(t *testing.T) {
 	defer testutil.AfterTest(t)
 	defer testutil.AfterTest(t)
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
 
 
 	// create fixed lease
 	// create fixed lease
-	lresp, err := pb.NewLeaseClient(clus.RandConn()).LeaseCreate(
+	lresp, err := clus.RandClient().Lease.LeaseCreate(
 		context.TODO(),
 		context.TODO(),
 		&pb.LeaseCreateRequest{ID: 1, TTL: 1})
 		&pb.LeaseCreateRequest{ID: 1, TTL: 1})
 	if err != nil {
 	if err != nil {
@@ -1268,7 +1264,7 @@ func TestV3LeaseCreateByID(t *testing.T) {
 	}
 	}
 
 
 	// create duplicate fixed lease
 	// create duplicate fixed lease
-	lresp, err = pb.NewLeaseClient(clus.RandConn()).LeaseCreate(
+	lresp, err = clus.RandClient().Lease.LeaseCreate(
 		context.TODO(),
 		context.TODO(),
 		&pb.LeaseCreateRequest{ID: 1, TTL: 1})
 		&pb.LeaseCreateRequest{ID: 1, TTL: 1})
 	if err != nil {
 	if err != nil {
@@ -1279,7 +1275,7 @@ func TestV3LeaseCreateByID(t *testing.T) {
 	}
 	}
 
 
 	// create fresh fixed lease
 	// create fresh fixed lease
-	lresp, err = pb.NewLeaseClient(clus.RandConn()).LeaseCreate(
+	lresp, err = clus.RandClient().Lease.LeaseCreate(
 		context.TODO(),
 		context.TODO(),
 		&pb.LeaseCreateRequest{ID: 2, TTL: 1})
 		&pb.LeaseCreateRequest{ID: 2, TTL: 1})
 	if err != nil {
 	if err != nil {
@@ -1297,10 +1293,9 @@ func TestV3LeaseExpire(t *testing.T) {
 	testLeaseRemoveLeasedKey(t, func(clus *clusterV3, leaseID int64) error {
 	testLeaseRemoveLeasedKey(t, func(clus *clusterV3, leaseID int64) error {
 		// let lease lapse; wait for deleted key
 		// let lease lapse; wait for deleted key
 
 
-		wAPI := pb.NewWatchClient(clus.RandConn())
 		ctx, cancel := context.WithCancel(context.Background())
 		ctx, cancel := context.WithCancel(context.Background())
 		defer cancel()
 		defer cancel()
-		wStream, err := wAPI.Watch(ctx)
+		wStream, err := clus.RandClient().Watch.Watch(ctx)
 		if err != nil {
 		if err != nil {
 			return err
 			return err
 		}
 		}
@@ -1348,7 +1343,7 @@ func TestV3LeaseExpire(t *testing.T) {
 func TestV3LeaseKeepAlive(t *testing.T) {
 func TestV3LeaseKeepAlive(t *testing.T) {
 	defer testutil.AfterTest(t)
 	defer testutil.AfterTest(t)
 	testLeaseRemoveLeasedKey(t, func(clus *clusterV3, leaseID int64) error {
 	testLeaseRemoveLeasedKey(t, func(clus *clusterV3, leaseID int64) error {
-		lc := pb.NewLeaseClient(clus.RandConn())
+		lc := clus.RandClient().Lease
 		lreq := &pb.LeaseKeepAliveRequest{ID: leaseID}
 		lreq := &pb.LeaseKeepAliveRequest{ID: leaseID}
 		ctx, cancel := context.WithCancel(context.Background())
 		ctx, cancel := context.WithCancel(context.Background())
 		defer cancel()
 		defer cancel()
@@ -1381,13 +1376,13 @@ func TestV3LeaseKeepAlive(t *testing.T) {
 // client to confirm it's visible to the whole cluster.
 // client to confirm it's visible to the whole cluster.
 func TestV3LeaseExists(t *testing.T) {
 func TestV3LeaseExists(t *testing.T) {
 	defer testutil.AfterTest(t)
 	defer testutil.AfterTest(t)
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
 
 
 	// create lease
 	// create lease
 	ctx0, cancel0 := context.WithCancel(context.Background())
 	ctx0, cancel0 := context.WithCancel(context.Background())
 	defer cancel0()
 	defer cancel0()
-	lresp, err := pb.NewLeaseClient(clus.RandConn()).LeaseCreate(
+	lresp, err := clus.RandClient().Lease.LeaseCreate(
 		ctx0,
 		ctx0,
 		&pb.LeaseCreateRequest{TTL: 30})
 		&pb.LeaseCreateRequest{TTL: 30})
 	if err != nil {
 	if err != nil {
@@ -1400,7 +1395,7 @@ func TestV3LeaseExists(t *testing.T) {
 	// confirm keepalive
 	// confirm keepalive
 	ctx1, cancel1 := context.WithCancel(context.Background())
 	ctx1, cancel1 := context.WithCancel(context.Background())
 	defer cancel1()
 	defer cancel1()
-	lac, err := pb.NewLeaseClient(clus.RandConn()).LeaseKeepAlive(ctx1)
+	lac, err := clus.RandClient().Lease.LeaseKeepAlive(ctx1)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}
@@ -1416,7 +1411,7 @@ func TestV3LeaseExists(t *testing.T) {
 // acquireLeaseAndKey creates a new lease and creates an attached key.
 // acquireLeaseAndKey creates a new lease and creates an attached key.
 func acquireLeaseAndKey(clus *clusterV3, key string) (int64, error) {
 func acquireLeaseAndKey(clus *clusterV3, key string) (int64, error) {
 	// create lease
 	// create lease
-	lresp, err := pb.NewLeaseClient(clus.RandConn()).LeaseCreate(
+	lresp, err := clus.RandClient().Lease.LeaseCreate(
 		context.TODO(),
 		context.TODO(),
 		&pb.LeaseCreateRequest{TTL: 1})
 		&pb.LeaseCreateRequest{TTL: 1})
 	if err != nil {
 	if err != nil {
@@ -1427,7 +1422,7 @@ func acquireLeaseAndKey(clus *clusterV3, key string) (int64, error) {
 	}
 	}
 	// attach to key
 	// attach to key
 	put := &pb.PutRequest{Key: []byte(key), Lease: lresp.ID}
 	put := &pb.PutRequest{Key: []byte(key), Lease: lresp.ID}
-	if _, err := pb.NewKVClient(clus.RandConn()).Put(context.TODO(), put); err != nil {
+	if _, err := clus.RandClient().KV.Put(context.TODO(), put); err != nil {
 		return 0, err
 		return 0, err
 	}
 	}
 	return lresp.ID, nil
 	return lresp.ID, nil
@@ -1436,7 +1431,7 @@ func acquireLeaseAndKey(clus *clusterV3, key string) (int64, error) {
 // testLeaseRemoveLeasedKey performs some action while holding a lease with an
 // testLeaseRemoveLeasedKey performs some action while holding a lease with an
 // attached key "foo", then confirms the key is gone.
 // attached key "foo", then confirms the key is gone.
 func testLeaseRemoveLeasedKey(t *testing.T, act func(*clusterV3, int64) error) {
 func testLeaseRemoveLeasedKey(t *testing.T, act func(*clusterV3, int64) error) {
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
 
 
 	leaseID, err := acquireLeaseAndKey(clus, "foo")
 	leaseID, err := acquireLeaseAndKey(clus, "foo")
@@ -1450,7 +1445,7 @@ func testLeaseRemoveLeasedKey(t *testing.T, act func(*clusterV3, int64) error) {
 
 
 	// confirm no key
 	// confirm no key
 	rreq := &pb.RangeRequest{Key: []byte("foo")}
 	rreq := &pb.RangeRequest{Key: []byte("foo")}
-	rresp, err := pb.NewKVClient(clus.RandConn()).Range(context.TODO(), rreq)
+	rresp, err := clus.RandClient().KV.Range(context.TODO(), rreq)
 	if err != nil {
 	if err != nil {
 		t.Fatal(err)
 		t.Fatal(err)
 	}
 	}

+ 15 - 15
integration/v3_lock_test.go

@@ -18,28 +18,28 @@ import (
 	"testing"
 	"testing"
 	"time"
 	"time"
 
 
-	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
+	"github.com/coreos/etcd/clientv3"
 	"github.com/coreos/etcd/contrib/recipes"
 	"github.com/coreos/etcd/contrib/recipes"
 )
 )
 
 
 func TestMutexSingleNode(t *testing.T) {
 func TestMutexSingleNode(t *testing.T) {
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
-	testMutex(t, 5, func() *grpc.ClientConn { return clus.conns[0] })
+	testMutex(t, 5, func() *clientv3.Client { return clus.clients[0] })
 }
 }
 
 
 func TestMutexMultiNode(t *testing.T) {
 func TestMutexMultiNode(t *testing.T) {
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
-	testMutex(t, 5, func() *grpc.ClientConn { return clus.RandConn() })
+	testMutex(t, 5, func() *clientv3.Client { return clus.RandClient() })
 }
 }
 
 
-func testMutex(t *testing.T, waiters int, chooseConn func() *grpc.ClientConn) {
+func testMutex(t *testing.T, waiters int, chooseClient func() *clientv3.Client) {
 	// stream lock acquistions
 	// stream lock acquistions
 	lockedC := make(chan *recipe.Mutex, 1)
 	lockedC := make(chan *recipe.Mutex, 1)
 	for i := 0; i < waiters; i++ {
 	for i := 0; i < waiters; i++ {
 		go func() {
 		go func() {
-			m := recipe.NewMutex(recipe.NewEtcdClient(chooseConn()), "test-mutex")
+			m := recipe.NewMutex(chooseClient(), "test-mutex")
 			if err := m.Lock(); err != nil {
 			if err := m.Lock(); err != nil {
 				t.Fatalf("could not wait on lock (%v)", err)
 				t.Fatalf("could not wait on lock (%v)", err)
 			}
 			}
@@ -68,32 +68,32 @@ func testMutex(t *testing.T, waiters int, chooseConn func() *grpc.ClientConn) {
 
 
 func BenchmarkMutex4Waiters(b *testing.B) {
 func BenchmarkMutex4Waiters(b *testing.B) {
 	// XXX switch tests to use TB interface
 	// XXX switch tests to use TB interface
-	clus := newClusterGRPC(nil, &clusterConfig{size: 3})
+	clus := newClusterV3(nil, &clusterConfig{size: 3})
 	defer clus.Terminate(nil)
 	defer clus.Terminate(nil)
 	for i := 0; i < b.N; i++ {
 	for i := 0; i < b.N; i++ {
-		testMutex(nil, 4, func() *grpc.ClientConn { return clus.RandConn() })
+		testMutex(nil, 4, func() *clientv3.Client { return clus.RandClient() })
 	}
 	}
 }
 }
 
 
 func TestRWMutexSingleNode(t *testing.T) {
 func TestRWMutexSingleNode(t *testing.T) {
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
-	testRWMutex(t, 5, func() *grpc.ClientConn { return clus.conns[0] })
+	testRWMutex(t, 5, func() *clientv3.Client { return clus.clients[0] })
 }
 }
 
 
 func TestRWMutexMultiNode(t *testing.T) {
 func TestRWMutexMultiNode(t *testing.T) {
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
-	testRWMutex(t, 5, func() *grpc.ClientConn { return clus.RandConn() })
+	testRWMutex(t, 5, func() *clientv3.Client { return clus.RandClient() })
 }
 }
 
 
-func testRWMutex(t *testing.T, waiters int, chooseConn func() *grpc.ClientConn) {
+func testRWMutex(t *testing.T, waiters int, chooseClient func() *clientv3.Client) {
 	// stream rwlock acquistions
 	// stream rwlock acquistions
 	rlockedC := make(chan *recipe.RWMutex, 1)
 	rlockedC := make(chan *recipe.RWMutex, 1)
 	wlockedC := make(chan *recipe.RWMutex, 1)
 	wlockedC := make(chan *recipe.RWMutex, 1)
 	for i := 0; i < waiters; i++ {
 	for i := 0; i < waiters; i++ {
 		go func() {
 		go func() {
-			rwm := recipe.NewRWMutex(recipe.NewEtcdClient(chooseConn()), "test-rwmutex")
+			rwm := recipe.NewRWMutex(chooseClient(), "test-rwmutex")
 			if rand.Intn(1) == 0 {
 			if rand.Intn(1) == 0 {
 				if err := rwm.RLock(); err != nil {
 				if err := rwm.RLock(); err != nil {
 					t.Fatalf("could not rlock (%v)", err)
 					t.Fatalf("could not rlock (%v)", err)

+ 11 - 11
integration/v3_queue_test.go

@@ -29,7 +29,7 @@ const (
 
 
 // TestQueueOneReaderOneWriter confirms the queue is FIFO
 // TestQueueOneReaderOneWriter confirms the queue is FIFO
 func TestQueueOneReaderOneWriter(t *testing.T) {
 func TestQueueOneReaderOneWriter(t *testing.T) {
-	clus := newClusterGRPC(t, &clusterConfig{size: 1})
+	clus := newClusterV3(t, &clusterConfig{size: 1})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
 
 
 	done := make(chan struct{})
 	done := make(chan struct{})
@@ -37,7 +37,7 @@ func TestQueueOneReaderOneWriter(t *testing.T) {
 		defer func() {
 		defer func() {
 			done <- struct{}{}
 			done <- struct{}{}
 		}()
 		}()
-		etcdc := recipe.NewEtcdClient(clus.RandConn())
+		etcdc := clus.RandClient()
 		q := recipe.NewQueue(etcdc, "testq")
 		q := recipe.NewQueue(etcdc, "testq")
 		for i := 0; i < 5; i++ {
 		for i := 0; i < 5; i++ {
 			if err := q.Enqueue(fmt.Sprintf("%d", i)); err != nil {
 			if err := q.Enqueue(fmt.Sprintf("%d", i)); err != nil {
@@ -46,7 +46,7 @@ func TestQueueOneReaderOneWriter(t *testing.T) {
 		}
 		}
 	}()
 	}()
 
 
-	etcdc := recipe.NewEtcdClient(clus.RandConn())
+	etcdc := clus.RandClient()
 	q := recipe.NewQueue(etcdc, "testq")
 	q := recipe.NewQueue(etcdc, "testq")
 	for i := 0; i < 5; i++ {
 	for i := 0; i < 5; i++ {
 		s, err := q.Dequeue()
 		s, err := q.Dequeue()
@@ -75,7 +75,7 @@ func TestQueueManyReaderManyWriter(t *testing.T) {
 // BenchmarkQueue benchmarks Queues using many/many readers/writers
 // BenchmarkQueue benchmarks Queues using many/many readers/writers
 func BenchmarkQueue(b *testing.B) {
 func BenchmarkQueue(b *testing.B) {
 	// XXX switch tests to use TB interface
 	// XXX switch tests to use TB interface
-	clus := newClusterGRPC(nil, &clusterConfig{size: 3})
+	clus := newClusterV3(nil, &clusterConfig{size: 3})
 	defer clus.Terminate(nil)
 	defer clus.Terminate(nil)
 	for i := 0; i < b.N; i++ {
 	for i := 0; i < b.N; i++ {
 		testQueueNReaderMWriter(nil, manyQueueClients, manyQueueClients)
 		testQueueNReaderMWriter(nil, manyQueueClients, manyQueueClients)
@@ -84,11 +84,11 @@ func BenchmarkQueue(b *testing.B) {
 
 
 // TestPrQueue tests whether priority queues respect priorities.
 // TestPrQueue tests whether priority queues respect priorities.
 func TestPrQueueOneReaderOneWriter(t *testing.T) {
 func TestPrQueueOneReaderOneWriter(t *testing.T) {
-	clus := newClusterGRPC(t, &clusterConfig{size: 1})
+	clus := newClusterV3(t, &clusterConfig{size: 1})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
 
 
 	// write out five items with random priority
 	// write out five items with random priority
-	etcdc := recipe.NewEtcdClient(clus.RandConn())
+	etcdc := clus.RandClient()
 	q := recipe.NewPriorityQueue(etcdc, "testprq")
 	q := recipe.NewPriorityQueue(etcdc, "testprq")
 	for i := 0; i < 5; i++ {
 	for i := 0; i < 5; i++ {
 		// [0, 2] priority for priority collision to test seq keys
 		// [0, 2] priority for priority collision to test seq keys
@@ -116,7 +116,7 @@ func TestPrQueueOneReaderOneWriter(t *testing.T) {
 }
 }
 
 
 func TestPrQueueManyReaderManyWriter(t *testing.T) {
 func TestPrQueueManyReaderManyWriter(t *testing.T) {
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
 	rqs := newPriorityQueues(clus, manyQueueClients)
 	rqs := newPriorityQueues(clus, manyQueueClients)
 	wqs := newPriorityQueues(clus, manyQueueClients)
 	wqs := newPriorityQueues(clus, manyQueueClients)
@@ -126,7 +126,7 @@ func TestPrQueueManyReaderManyWriter(t *testing.T) {
 // BenchmarkQueue benchmarks Queues using n/n readers/writers
 // BenchmarkQueue benchmarks Queues using n/n readers/writers
 func BenchmarkPrQueueOneReaderOneWriter(b *testing.B) {
 func BenchmarkPrQueueOneReaderOneWriter(b *testing.B) {
 	// XXX switch tests to use TB interface
 	// XXX switch tests to use TB interface
-	clus := newClusterGRPC(nil, &clusterConfig{size: 3})
+	clus := newClusterV3(nil, &clusterConfig{size: 3})
 	defer clus.Terminate(nil)
 	defer clus.Terminate(nil)
 	rqs := newPriorityQueues(clus, 1)
 	rqs := newPriorityQueues(clus, 1)
 	wqs := newPriorityQueues(clus, 1)
 	wqs := newPriorityQueues(clus, 1)
@@ -136,14 +136,14 @@ func BenchmarkPrQueueOneReaderOneWriter(b *testing.B) {
 }
 }
 
 
 func testQueueNReaderMWriter(t *testing.T, n int, m int) {
 func testQueueNReaderMWriter(t *testing.T, n int, m int) {
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
 	testReadersWriters(t, newQueues(clus, n), newQueues(clus, m))
 	testReadersWriters(t, newQueues(clus, n), newQueues(clus, m))
 }
 }
 
 
 func newQueues(clus *clusterV3, n int) (qs []testQueue) {
 func newQueues(clus *clusterV3, n int) (qs []testQueue) {
 	for i := 0; i < n; i++ {
 	for i := 0; i < n; i++ {
-		etcdc := recipe.NewEtcdClient(clus.RandConn())
+		etcdc := clus.RandClient()
 		qs = append(qs, recipe.NewQueue(etcdc, "q"))
 		qs = append(qs, recipe.NewQueue(etcdc, "q"))
 	}
 	}
 	return qs
 	return qs
@@ -151,7 +151,7 @@ func newQueues(clus *clusterV3, n int) (qs []testQueue) {
 
 
 func newPriorityQueues(clus *clusterV3, n int) (qs []testQueue) {
 func newPriorityQueues(clus *clusterV3, n int) (qs []testQueue) {
 	for i := 0; i < n; i++ {
 	for i := 0; i < n; i++ {
-		etcdc := recipe.NewEtcdClient(clus.RandConn())
+		etcdc := clus.RandClient()
 		q := &flatPriorityQueue{recipe.NewPriorityQueue(etcdc, "prq")}
 		q := &flatPriorityQueue{recipe.NewPriorityQueue(etcdc, "prq")}
 		qs = append(qs, q)
 		qs = append(qs, q)
 	}
 	}

+ 7 - 7
integration/v3_stm_test.go

@@ -24,10 +24,10 @@ import (
 
 
 // TestSTMConflict tests that conflicts are retried.
 // TestSTMConflict tests that conflicts are retried.
 func TestSTMConflict(t *testing.T) {
 func TestSTMConflict(t *testing.T) {
-	clus := newClusterGRPC(t, &clusterConfig{size: 3})
+	clus := newClusterV3(t, &clusterConfig{size: 3})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
 
 
-	etcdc := recipe.NewEtcdClient(clus.RandConn())
+	etcdc := clus.RandClient()
 	keys := make([]*recipe.RemoteKV, 5)
 	keys := make([]*recipe.RemoteKV, 5)
 	for i := 0; i < len(keys); i++ {
 	for i := 0; i < len(keys); i++ {
 		rk, err := recipe.NewKV(etcdc, fmt.Sprintf("foo-%d", i), "100", 0)
 		rk, err := recipe.NewKV(etcdc, fmt.Sprintf("foo-%d", i), "100", 0)
@@ -39,7 +39,7 @@ func TestSTMConflict(t *testing.T) {
 
 
 	errc := make([]<-chan error, len(keys))
 	errc := make([]<-chan error, len(keys))
 	for i, rk := range keys {
 	for i, rk := range keys {
-		curEtcdc := recipe.NewEtcdClient(clus.RandConn())
+		curEtcdc := clus.RandClient()
 		srcKey := rk.Key()
 		srcKey := rk.Key()
 		applyf := func(stm *recipe.STM) error {
 		applyf := func(stm *recipe.STM) error {
 			src, err := stm.Get(srcKey)
 			src, err := stm.Get(srcKey)
@@ -89,10 +89,10 @@ func TestSTMConflict(t *testing.T) {
 
 
 // TestSTMPut confirms a STM put on a new key is visible after commit.
 // TestSTMPut confirms a STM put on a new key is visible after commit.
 func TestSTMPutNewKey(t *testing.T) {
 func TestSTMPutNewKey(t *testing.T) {
-	clus := newClusterGRPC(t, &clusterConfig{size: 1})
+	clus := newClusterV3(t, &clusterConfig{size: 1})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
 
 
-	etcdc := recipe.NewEtcdClient(clus.RandConn())
+	etcdc := clus.RandClient()
 	applyf := func(stm *recipe.STM) error {
 	applyf := func(stm *recipe.STM) error {
 		stm.Put("foo", "bar")
 		stm.Put("foo", "bar")
 		return nil
 		return nil
@@ -113,10 +113,10 @@ func TestSTMPutNewKey(t *testing.T) {
 
 
 // TestSTMAbort tests that an aborted txn does not modify any keys.
 // TestSTMAbort tests that an aborted txn does not modify any keys.
 func TestSTMAbort(t *testing.T) {
 func TestSTMAbort(t *testing.T) {
-	clus := newClusterGRPC(t, &clusterConfig{size: 1})
+	clus := newClusterV3(t, &clusterConfig{size: 1})
 	defer clus.Terminate(t)
 	defer clus.Terminate(t)
 
 
-	etcdc := recipe.NewEtcdClient(clus.RandConn())
+	etcdc := clus.RandClient()
 	applyf := func(stm *recipe.STM) error {
 	applyf := func(stm *recipe.STM) error {
 		stm.Put("foo", "baz")
 		stm.Put("foo", "baz")
 		stm.Abort()
 		stm.Abort()

+ 2 - 11
tools/benchmark/cmd/put.go

@@ -24,7 +24,6 @@ import (
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/cheggaaa/pb"
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/cheggaaa/pb"
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
-	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
 	"github.com/coreos/etcd/etcdserver/etcdserverpb"
 	"github.com/coreos/etcd/etcdserver/etcdserverpb"
 )
 )
 
 
@@ -67,22 +66,14 @@ func putFunc(cmd *cobra.Command, args []string) {
 
 
 	k, v := make([]byte, keySize), mustRandBytes(valSize)
 	k, v := make([]byte, keySize), mustRandBytes(valSize)
 
 
-	conns := make([]*grpc.ClientConn, totalConns)
-	for i := range conns {
-		conns[i] = mustCreateConn()
-	}
-
-	clients := make([]etcdserverpb.KVClient, totalClients)
-	for i := range clients {
-		clients[i] = etcdserverpb.NewKVClient(conns[i%int(totalConns)])
-	}
+	clients := mustCreateClients(totalClients, totalConns)
 
 
 	bar.Format("Bom !")
 	bar.Format("Bom !")
 	bar.Start()
 	bar.Start()
 
 
 	for i := range clients {
 	for i := range clients {
 		wg.Add(1)
 		wg.Add(1)
-		go doPut(context.Background(), clients[i], requests)
+		go doPut(context.Background(), clients[i].KV, requests)
 	}
 	}
 
 
 	pdoneC := printReport(results)
 	pdoneC := printReport(results)

+ 2 - 11
tools/benchmark/cmd/range.go

@@ -22,7 +22,6 @@ import (
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/cheggaaa/pb"
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/cheggaaa/pb"
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
-	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
 	"github.com/coreos/etcd/etcdserver/etcdserverpb"
 	"github.com/coreos/etcd/etcdserver/etcdserverpb"
 )
 )
 
 
@@ -59,22 +58,14 @@ func rangeFunc(cmd *cobra.Command, args []string) {
 	requests := make(chan etcdserverpb.RangeRequest, totalClients)
 	requests := make(chan etcdserverpb.RangeRequest, totalClients)
 	bar = pb.New(rangeTotal)
 	bar = pb.New(rangeTotal)
 
 
-	conns := make([]*grpc.ClientConn, totalConns)
-	for i := range conns {
-		conns[i] = mustCreateConn()
-	}
-
-	clients := make([]etcdserverpb.KVClient, totalClients)
-	for i := range clients {
-		clients[i] = etcdserverpb.NewKVClient(conns[i%int(totalConns)])
-	}
+	clients := mustCreateClients(totalClients, totalConns)
 
 
 	bar.Format("Bom !")
 	bar.Format("Bom !")
 	bar.Start()
 	bar.Start()
 
 
 	for i := range clients {
 	for i := range clients {
 		wg.Add(1)
 		wg.Add(1)
-		go doRange(clients[i], requests)
+		go doRange(clients[i].KV, requests)
 	}
 	}
 
 
 	pdoneC := printReport(results)
 	pdoneC := printReport(results)

+ 17 - 4
tools/benchmark/cmd/util.go

@@ -20,7 +20,7 @@ import (
 	"os"
 	"os"
 	"strings"
 	"strings"
 
 
-	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
+	"github.com/coreos/etcd/clientv3"
 )
 )
 
 
 var (
 var (
@@ -29,16 +29,29 @@ var (
 	dialTotal int
 	dialTotal int
 )
 )
 
 
-func mustCreateConn() *grpc.ClientConn {
+func mustCreateConn() *clientv3.Client {
 	eps := strings.Split(endpoints, ",")
 	eps := strings.Split(endpoints, ",")
 	endpoint := eps[dialTotal%len(eps)]
 	endpoint := eps[dialTotal%len(eps)]
 	dialTotal++
 	dialTotal++
-	conn, err := grpc.Dial(endpoint, grpc.WithInsecure())
+	client, err := clientv3.NewFromURL(endpoint)
 	if err != nil {
 	if err != nil {
 		fmt.Fprintf(os.Stderr, "dial error: %v\n", err)
 		fmt.Fprintf(os.Stderr, "dial error: %v\n", err)
 		os.Exit(1)
 		os.Exit(1)
 	}
 	}
-	return conn
+	return client
+}
+
+func mustCreateClients(totalClients, totalConns uint) []*clientv3.Client {
+	conns := make([]*clientv3.Client, totalConns)
+	for i := range conns {
+		conns[i] = mustCreateConn()
+	}
+
+	clients := make([]*clientv3.Client, totalClients)
+	for i := range clients {
+		clients[i] = conns[i%int(totalConns)].Clone()
+	}
+	return clients
 }
 }
 
 
 func mustRandBytes(n int) []byte {
 func mustRandBytes(n int) []byte {

+ 3 - 13
tools/benchmark/cmd/watch.go

@@ -24,7 +24,6 @@ import (
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/cheggaaa/pb"
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/cheggaaa/pb"
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/github.com/spf13/cobra"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
 	"github.com/coreos/etcd/Godeps/_workspace/src/golang.org/x/net/context"
-	"github.com/coreos/etcd/Godeps/_workspace/src/google.golang.org/grpc"
 )
 )
 
 
 // watchCmd represents the watch command
 // watchCmd represents the watch command
@@ -72,20 +71,12 @@ func watchFunc(cmd *cobra.Command, args []string) {
 
 
 	requests := make(chan etcdserverpb.WatchRequest, totalClients)
 	requests := make(chan etcdserverpb.WatchRequest, totalClients)
 
 
-	conns := make([]*grpc.ClientConn, totalConns)
-	for i := range conns {
-		conns[i] = mustCreateConn()
-	}
-
-	clients := make([]etcdserverpb.WatchClient, totalClients)
-	for i := range clients {
-		clients[i] = etcdserverpb.NewWatchClient(conns[i%int(totalConns)])
-	}
+	clients := mustCreateClients(totalClients, totalConns)
 
 
 	streams := make([]etcdserverpb.Watch_WatchClient, watchTotalStreams)
 	streams := make([]etcdserverpb.Watch_WatchClient, watchTotalStreams)
 	var err error
 	var err error
 	for i := range streams {
 	for i := range streams {
-		streams[i], err = clients[i%int(totalClients)].Watch(context.TODO())
+		streams[i], err = clients[i%len(clients)].Watch.Watch(context.TODO())
 		if err != nil {
 		if err != nil {
 			fmt.Fprintln(os.Stderr, "Failed to create watch stream:", err)
 			fmt.Fprintln(os.Stderr, "Failed to create watch stream:", err)
 			os.Exit(1)
 			os.Exit(1)
@@ -124,7 +115,6 @@ func watchFunc(cmd *cobra.Command, args []string) {
 	<-pdoneC
 	<-pdoneC
 
 
 	// put phase
 	// put phase
-	kv := etcdserverpb.NewKVClient(conns[0])
 	// total number of puts * number of watchers on each key
 	// total number of puts * number of watchers on each key
 	eventsTotal := watchPutTotal * (watchTotal / watchedKeyTotal)
 	eventsTotal := watchPutTotal * (watchTotal / watchedKeyTotal)
 
 
@@ -138,7 +128,7 @@ func watchFunc(cmd *cobra.Command, args []string) {
 
 
 	for i := 0; i < watchPutTotal; i++ {
 	for i := 0; i < watchPutTotal; i++ {
 		wg.Add(1)
 		wg.Add(1)
-		go doPut(context.TODO(), kv, putreqc)
+		go doPut(context.TODO(), clients[i%len(clients)].KV, putreqc)
 	}
 	}
 
 
 	pdoneC = printRate(results)
 	pdoneC = printRate(results)