Compare commits

...

14 Commits

32 changed files with 597 additions and 217 deletions

View File

@ -6,5 +6,5 @@ RUN mkdir -p /var/etcd/
EXPOSE 2379 2380 EXPOSE 2379 2380
# Define default entrypoint. # Define default command.
ENTRYPOINT ["/usr/local/bin/etcd"] CMD ["/usr/local/bin/etcd"]

View File

@ -115,52 +115,52 @@ func NewAuth(c *Client) Auth {
} }
func (auth *auth) AuthEnable(ctx context.Context) (*AuthEnableResponse, error) { func (auth *auth) AuthEnable(ctx context.Context) (*AuthEnableResponse, error) {
resp, err := auth.remote.AuthEnable(ctx, &pb.AuthEnableRequest{}) resp, err := auth.remote.AuthEnable(ctx, &pb.AuthEnableRequest{}, grpc.FailFast(false))
return (*AuthEnableResponse)(resp), toErr(ctx, err) return (*AuthEnableResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) AuthDisable(ctx context.Context) (*AuthDisableResponse, error) { func (auth *auth) AuthDisable(ctx context.Context) (*AuthDisableResponse, error) {
resp, err := auth.remote.AuthDisable(ctx, &pb.AuthDisableRequest{}) resp, err := auth.remote.AuthDisable(ctx, &pb.AuthDisableRequest{}, grpc.FailFast(false))
return (*AuthDisableResponse)(resp), toErr(ctx, err) return (*AuthDisableResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) UserAdd(ctx context.Context, name string, password string) (*AuthUserAddResponse, error) { func (auth *auth) UserAdd(ctx context.Context, name string, password string) (*AuthUserAddResponse, error) {
resp, err := auth.remote.UserAdd(ctx, &pb.AuthUserAddRequest{Name: name, Password: password}) resp, err := auth.remote.UserAdd(ctx, &pb.AuthUserAddRequest{Name: name, Password: password}, grpc.FailFast(false))
return (*AuthUserAddResponse)(resp), toErr(ctx, err) return (*AuthUserAddResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) UserDelete(ctx context.Context, name string) (*AuthUserDeleteResponse, error) { func (auth *auth) UserDelete(ctx context.Context, name string) (*AuthUserDeleteResponse, error) {
resp, err := auth.remote.UserDelete(ctx, &pb.AuthUserDeleteRequest{Name: name}) resp, err := auth.remote.UserDelete(ctx, &pb.AuthUserDeleteRequest{Name: name}, grpc.FailFast(false))
return (*AuthUserDeleteResponse)(resp), toErr(ctx, err) return (*AuthUserDeleteResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) UserChangePassword(ctx context.Context, name string, password string) (*AuthUserChangePasswordResponse, error) { func (auth *auth) UserChangePassword(ctx context.Context, name string, password string) (*AuthUserChangePasswordResponse, error) {
resp, err := auth.remote.UserChangePassword(ctx, &pb.AuthUserChangePasswordRequest{Name: name, Password: password}) resp, err := auth.remote.UserChangePassword(ctx, &pb.AuthUserChangePasswordRequest{Name: name, Password: password}, grpc.FailFast(false))
return (*AuthUserChangePasswordResponse)(resp), toErr(ctx, err) return (*AuthUserChangePasswordResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) UserGrantRole(ctx context.Context, user string, role string) (*AuthUserGrantRoleResponse, error) { func (auth *auth) UserGrantRole(ctx context.Context, user string, role string) (*AuthUserGrantRoleResponse, error) {
resp, err := auth.remote.UserGrantRole(ctx, &pb.AuthUserGrantRoleRequest{User: user, Role: role}) resp, err := auth.remote.UserGrantRole(ctx, &pb.AuthUserGrantRoleRequest{User: user, Role: role}, grpc.FailFast(false))
return (*AuthUserGrantRoleResponse)(resp), toErr(ctx, err) return (*AuthUserGrantRoleResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) UserGet(ctx context.Context, name string) (*AuthUserGetResponse, error) { func (auth *auth) UserGet(ctx context.Context, name string) (*AuthUserGetResponse, error) {
resp, err := auth.remote.UserGet(ctx, &pb.AuthUserGetRequest{Name: name}) resp, err := auth.remote.UserGet(ctx, &pb.AuthUserGetRequest{Name: name}, grpc.FailFast(false))
return (*AuthUserGetResponse)(resp), toErr(ctx, err) return (*AuthUserGetResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) UserList(ctx context.Context) (*AuthUserListResponse, error) { func (auth *auth) UserList(ctx context.Context) (*AuthUserListResponse, error) {
resp, err := auth.remote.UserList(ctx, &pb.AuthUserListRequest{}) resp, err := auth.remote.UserList(ctx, &pb.AuthUserListRequest{}, grpc.FailFast(false))
return (*AuthUserListResponse)(resp), toErr(ctx, err) return (*AuthUserListResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) UserRevokeRole(ctx context.Context, name string, role string) (*AuthUserRevokeRoleResponse, error) { func (auth *auth) UserRevokeRole(ctx context.Context, name string, role string) (*AuthUserRevokeRoleResponse, error) {
resp, err := auth.remote.UserRevokeRole(ctx, &pb.AuthUserRevokeRoleRequest{Name: name, Role: role}) resp, err := auth.remote.UserRevokeRole(ctx, &pb.AuthUserRevokeRoleRequest{Name: name, Role: role}, grpc.FailFast(false))
return (*AuthUserRevokeRoleResponse)(resp), toErr(ctx, err) return (*AuthUserRevokeRoleResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) RoleAdd(ctx context.Context, name string) (*AuthRoleAddResponse, error) { func (auth *auth) RoleAdd(ctx context.Context, name string) (*AuthRoleAddResponse, error) {
resp, err := auth.remote.RoleAdd(ctx, &pb.AuthRoleAddRequest{Name: name}) resp, err := auth.remote.RoleAdd(ctx, &pb.AuthRoleAddRequest{Name: name}, grpc.FailFast(false))
return (*AuthRoleAddResponse)(resp), toErr(ctx, err) return (*AuthRoleAddResponse)(resp), toErr(ctx, err)
} }
@ -170,27 +170,27 @@ func (auth *auth) RoleGrantPermission(ctx context.Context, name string, key, ran
RangeEnd: []byte(rangeEnd), RangeEnd: []byte(rangeEnd),
PermType: authpb.Permission_Type(permType), PermType: authpb.Permission_Type(permType),
} }
resp, err := auth.remote.RoleGrantPermission(ctx, &pb.AuthRoleGrantPermissionRequest{Name: name, Perm: perm}) resp, err := auth.remote.RoleGrantPermission(ctx, &pb.AuthRoleGrantPermissionRequest{Name: name, Perm: perm}, grpc.FailFast(false))
return (*AuthRoleGrantPermissionResponse)(resp), toErr(ctx, err) return (*AuthRoleGrantPermissionResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) RoleGet(ctx context.Context, role string) (*AuthRoleGetResponse, error) { func (auth *auth) RoleGet(ctx context.Context, role string) (*AuthRoleGetResponse, error) {
resp, err := auth.remote.RoleGet(ctx, &pb.AuthRoleGetRequest{Role: role}) resp, err := auth.remote.RoleGet(ctx, &pb.AuthRoleGetRequest{Role: role}, grpc.FailFast(false))
return (*AuthRoleGetResponse)(resp), toErr(ctx, err) return (*AuthRoleGetResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) RoleList(ctx context.Context) (*AuthRoleListResponse, error) { func (auth *auth) RoleList(ctx context.Context) (*AuthRoleListResponse, error) {
resp, err := auth.remote.RoleList(ctx, &pb.AuthRoleListRequest{}) resp, err := auth.remote.RoleList(ctx, &pb.AuthRoleListRequest{}, grpc.FailFast(false))
return (*AuthRoleListResponse)(resp), toErr(ctx, err) return (*AuthRoleListResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) RoleRevokePermission(ctx context.Context, role string, key, rangeEnd string) (*AuthRoleRevokePermissionResponse, error) { func (auth *auth) RoleRevokePermission(ctx context.Context, role string, key, rangeEnd string) (*AuthRoleRevokePermissionResponse, error) {
resp, err := auth.remote.RoleRevokePermission(ctx, &pb.AuthRoleRevokePermissionRequest{Role: role, Key: key, RangeEnd: rangeEnd}) resp, err := auth.remote.RoleRevokePermission(ctx, &pb.AuthRoleRevokePermissionRequest{Role: role, Key: key, RangeEnd: rangeEnd}, grpc.FailFast(false))
return (*AuthRoleRevokePermissionResponse)(resp), toErr(ctx, err) return (*AuthRoleRevokePermissionResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) RoleDelete(ctx context.Context, role string) (*AuthRoleDeleteResponse, error) { func (auth *auth) RoleDelete(ctx context.Context, role string) (*AuthRoleDeleteResponse, error) {
resp, err := auth.remote.RoleDelete(ctx, &pb.AuthRoleDeleteRequest{Role: role}) resp, err := auth.remote.RoleDelete(ctx, &pb.AuthRoleDeleteRequest{Role: role}, grpc.FailFast(false))
return (*AuthRoleDeleteResponse)(resp), toErr(ctx, err) return (*AuthRoleDeleteResponse)(resp), toErr(ctx, err)
} }
@ -208,7 +208,7 @@ type authenticator struct {
} }
func (auth *authenticator) authenticate(ctx context.Context, name string, password string) (*AuthenticateResponse, error) { func (auth *authenticator) authenticate(ctx context.Context, name string, password string) (*AuthenticateResponse, error) {
resp, err := auth.remote.Authenticate(ctx, &pb.AuthenticateRequest{Name: name, Password: password}) resp, err := auth.remote.Authenticate(ctx, &pb.AuthenticateRequest{Name: name, Password: password}, grpc.FailFast(false))
return (*AuthenticateResponse)(resp), toErr(ctx, err) return (*AuthenticateResponse)(resp), toErr(ctx, err)
} }

View File

@ -17,6 +17,7 @@ package clientv3
import ( import (
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc"
) )
type ( type (
@ -51,7 +52,7 @@ func NewCluster(c *Client) Cluster {
func (c *cluster) MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) { func (c *cluster) MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) {
r := &pb.MemberAddRequest{PeerURLs: peerAddrs} r := &pb.MemberAddRequest{PeerURLs: peerAddrs}
resp, err := c.remote.MemberAdd(ctx, r) resp, err := c.remote.MemberAdd(ctx, r, grpc.FailFast(false))
if err == nil { if err == nil {
return (*MemberAddResponse)(resp), nil return (*MemberAddResponse)(resp), nil
} }
@ -63,7 +64,7 @@ func (c *cluster) MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAdd
func (c *cluster) MemberRemove(ctx context.Context, id uint64) (*MemberRemoveResponse, error) { func (c *cluster) MemberRemove(ctx context.Context, id uint64) (*MemberRemoveResponse, error) {
r := &pb.MemberRemoveRequest{ID: id} r := &pb.MemberRemoveRequest{ID: id}
resp, err := c.remote.MemberRemove(ctx, r) resp, err := c.remote.MemberRemove(ctx, r, grpc.FailFast(false))
if err == nil { if err == nil {
return (*MemberRemoveResponse)(resp), nil return (*MemberRemoveResponse)(resp), nil
} }
@ -77,7 +78,7 @@ func (c *cluster) MemberUpdate(ctx context.Context, id uint64, peerAddrs []strin
// it is safe to retry on update. // it is safe to retry on update.
for { for {
r := &pb.MemberUpdateRequest{ID: id, PeerURLs: peerAddrs} r := &pb.MemberUpdateRequest{ID: id, PeerURLs: peerAddrs}
resp, err := c.remote.MemberUpdate(ctx, r) resp, err := c.remote.MemberUpdate(ctx, r, grpc.FailFast(false))
if err == nil { if err == nil {
return (*MemberUpdateResponse)(resp), nil return (*MemberUpdateResponse)(resp), nil
} }
@ -90,7 +91,7 @@ func (c *cluster) MemberUpdate(ctx context.Context, id uint64, peerAddrs []strin
func (c *cluster) MemberList(ctx context.Context) (*MemberListResponse, error) { func (c *cluster) MemberList(ctx context.Context) (*MemberListResponse, error) {
// it is safe to retry on list. // it is safe to retry on list.
for { for {
resp, err := c.remote.MemberList(ctx, &pb.MemberListRequest{}) resp, err := c.remote.MemberList(ctx, &pb.MemberListRequest{}, grpc.FailFast(false))
if err == nil { if err == nil {
return (*MemberListResponse)(resp), nil return (*MemberListResponse)(resp), nil
} }

View File

@ -17,6 +17,7 @@ package clientv3
import ( import (
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc"
) )
type ( type (
@ -100,7 +101,7 @@ func (kv *kv) Delete(ctx context.Context, key string, opts ...OpOption) (*Delete
} }
func (kv *kv) Compact(ctx context.Context, rev int64, opts ...CompactOption) (*CompactResponse, error) { func (kv *kv) Compact(ctx context.Context, rev int64, opts ...CompactOption) (*CompactResponse, error) {
resp, err := kv.remote.Compact(ctx, OpCompact(rev, opts...).toRequest()) resp, err := kv.remote.Compact(ctx, OpCompact(rev, opts...).toRequest(), grpc.FailFast(false))
if err != nil { if err != nil {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
@ -150,21 +151,21 @@ func (kv *kv) do(ctx context.Context, op Op) (OpResponse, error) {
r.SortTarget = pb.RangeRequest_SortTarget(op.sort.Target) r.SortTarget = pb.RangeRequest_SortTarget(op.sort.Target)
} }
resp, err = kv.remote.Range(ctx, r) resp, err = kv.remote.Range(ctx, r, grpc.FailFast(false))
if err == nil { if err == nil {
return OpResponse{get: (*GetResponse)(resp)}, nil return OpResponse{get: (*GetResponse)(resp)}, nil
} }
case tPut: case tPut:
var resp *pb.PutResponse var resp *pb.PutResponse
r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID)} r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID)}
resp, err = kv.remote.Put(ctx, r) resp, err = kv.remote.Put(ctx, r, grpc.FailFast(false))
if err == nil { if err == nil {
return OpResponse{put: (*PutResponse)(resp)}, nil return OpResponse{put: (*PutResponse)(resp)}, nil
} }
case tDeleteRange: case tDeleteRange:
var resp *pb.DeleteRangeResponse var resp *pb.DeleteRangeResponse
r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end} r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end}
resp, err = kv.remote.DeleteRange(ctx, r) resp, err = kv.remote.DeleteRange(ctx, r, grpc.FailFast(false))
if err == nil { if err == nil {
return OpResponse{del: (*DeleteResponse)(resp)}, nil return OpResponse{del: (*DeleteResponse)(resp)}, nil
} }

View File

@ -21,6 +21,7 @@ import (
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc"
) )
type ( type (
@ -129,7 +130,7 @@ func (l *lessor) Grant(ctx context.Context, ttl int64) (*LeaseGrantResponse, err
for { for {
r := &pb.LeaseGrantRequest{TTL: ttl} r := &pb.LeaseGrantRequest{TTL: ttl}
resp, err := l.remote.LeaseGrant(cctx, r) resp, err := l.remote.LeaseGrant(cctx, r, grpc.FailFast(false))
if err == nil { if err == nil {
gresp := &LeaseGrantResponse{ gresp := &LeaseGrantResponse{
ResponseHeader: resp.GetHeader(), ResponseHeader: resp.GetHeader(),
@ -155,7 +156,7 @@ func (l *lessor) Revoke(ctx context.Context, id LeaseID) (*LeaseRevokeResponse,
for { for {
r := &pb.LeaseRevokeRequest{ID: int64(id)} r := &pb.LeaseRevokeRequest{ID: int64(id)}
resp, err := l.remote.LeaseRevoke(cctx, r) resp, err := l.remote.LeaseRevoke(cctx, r, grpc.FailFast(false))
if err == nil { if err == nil {
return (*LeaseRevokeResponse)(resp), nil return (*LeaseRevokeResponse)(resp), nil
@ -261,7 +262,7 @@ func (l *lessor) keepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAlive
cctx, cancel := context.WithCancel(ctx) cctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
stream, err := l.remote.LeaseKeepAlive(cctx) stream, err := l.remote.LeaseKeepAlive(cctx, grpc.FailFast(false))
if err != nil { if err != nil {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
@ -418,7 +419,7 @@ func (l *lessor) getKeepAliveStream() pb.Lease_LeaseKeepAliveClient {
func (l *lessor) newStream() error { func (l *lessor) newStream() error {
sctx, cancel := context.WithCancel(l.stopCtx) sctx, cancel := context.WithCancel(l.stopCtx)
stream, err := l.remote.LeaseKeepAlive(sctx) stream, err := l.remote.LeaseKeepAlive(sctx, grpc.FailFast(false))
if err != nil { if err != nil {
cancel() cancel()
return toErr(sctx, err) return toErr(sctx, err)

View File

@ -19,6 +19,7 @@ import (
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc"
) )
type ( type (
@ -67,7 +68,7 @@ func (m *maintenance) AlarmList(ctx context.Context) (*AlarmResponse, error) {
Alarm: pb.AlarmType_NONE, // all Alarm: pb.AlarmType_NONE, // all
} }
for { for {
resp, err := m.remote.Alarm(ctx, req) resp, err := m.remote.Alarm(ctx, req, grpc.FailFast(false))
if err == nil { if err == nil {
return (*AlarmResponse)(resp), nil return (*AlarmResponse)(resp), nil
} }
@ -100,7 +101,7 @@ func (m *maintenance) AlarmDisarm(ctx context.Context, am *AlarmMember) (*AlarmR
return &ret, nil return &ret, nil
} }
resp, err := m.remote.Alarm(ctx, req) resp, err := m.remote.Alarm(ctx, req, grpc.FailFast(false))
if err == nil { if err == nil {
return (*AlarmResponse)(resp), nil return (*AlarmResponse)(resp), nil
} }
@ -114,7 +115,7 @@ func (m *maintenance) Defragment(ctx context.Context, endpoint string) (*Defragm
} }
defer conn.Close() defer conn.Close()
remote := pb.NewMaintenanceClient(conn) remote := pb.NewMaintenanceClient(conn)
resp, err := remote.Defragment(ctx, &pb.DefragmentRequest{}) resp, err := remote.Defragment(ctx, &pb.DefragmentRequest{}, grpc.FailFast(false))
if err != nil { if err != nil {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
@ -128,7 +129,7 @@ func (m *maintenance) Status(ctx context.Context, endpoint string) (*StatusRespo
} }
defer conn.Close() defer conn.Close()
remote := pb.NewMaintenanceClient(conn) remote := pb.NewMaintenanceClient(conn)
resp, err := remote.Status(ctx, &pb.StatusRequest{}) resp, err := remote.Status(ctx, &pb.StatusRequest{}, grpc.FailFast(false))
if err != nil { if err != nil {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
@ -136,7 +137,7 @@ func (m *maintenance) Status(ctx context.Context, endpoint string) (*StatusRespo
} }
func (m *maintenance) Snapshot(ctx context.Context) (io.ReadCloser, error) { func (m *maintenance) Snapshot(ctx context.Context) (io.ReadCloser, error) {
ss, err := m.remote.Snapshot(ctx, &pb.SnapshotRequest{}) ss, err := m.remote.Snapshot(ctx, &pb.SnapshotRequest{}, grpc.FailFast(false))
if err != nil { if err != nil {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }

View File

@ -19,6 +19,7 @@ import (
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc"
) )
// Txn is the interface that wraps mini-transactions. // Txn is the interface that wraps mini-transactions.
@ -152,7 +153,7 @@ func (txn *txn) Commit() (*TxnResponse, error) {
func (txn *txn) commit() (*TxnResponse, error) { func (txn *txn) commit() (*TxnResponse, error) {
r := &pb.TxnRequest{Compare: txn.cmps, Success: txn.sus, Failure: txn.fas} r := &pb.TxnRequest{Compare: txn.cmps, Success: txn.sus, Failure: txn.fas}
resp, err := txn.kv.remote.Txn(txn.ctx, r) resp, err := txn.kv.remote.Txn(txn.ctx, r, grpc.FailFast(false))
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -23,6 +23,7 @@ import (
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
mvccpb "github.com/coreos/etcd/mvcc/mvccpb" mvccpb "github.com/coreos/etcd/mvcc/mvccpb"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc"
) )
const ( const (
@ -623,7 +624,7 @@ func (w *watchGrpcStream) openWatchClient() (ws pb.Watch_WatchClient, err error)
return nil, err return nil, err
default: default:
} }
if ws, err = w.remote.Watch(w.ctx); ws != nil && err == nil { if ws, err = w.remote.Watch(w.ctx, grpc.FailFast(false)); ws != nil && err == nil {
break break
} }
if isHaltErr(w.ctx, err) { if isHaltErr(w.ctx, err) {

27
cmd/Godeps/Godeps.json generated
View File

@ -234,39 +234,48 @@
}, },
{ {
"ImportPath": "google.golang.org/grpc", "ImportPath": "google.golang.org/grpc",
"Rev": "e78224b060cf3215247b7be455f80ea22e469b66" "Comment": "v1.0.0-6-g02fca89",
"Rev": "02fca896ff5f50c6bbbee0860345a49344b37a03"
}, },
{ {
"ImportPath": "google.golang.org/grpc/codes", "ImportPath": "google.golang.org/grpc/codes",
"Rev": "e78224b060cf3215247b7be455f80ea22e469b66" "Comment": "v1.0.0-6-g02fca89",
"Rev": "02fca896ff5f50c6bbbee0860345a49344b37a03"
}, },
{ {
"ImportPath": "google.golang.org/grpc/credentials", "ImportPath": "google.golang.org/grpc/credentials",
"Rev": "e78224b060cf3215247b7be455f80ea22e469b66" "Comment": "v1.0.0-6-g02fca89",
"Rev": "02fca896ff5f50c6bbbee0860345a49344b37a03"
}, },
{ {
"ImportPath": "google.golang.org/grpc/grpclog", "ImportPath": "google.golang.org/grpc/grpclog",
"Rev": "e78224b060cf3215247b7be455f80ea22e469b66" "Comment": "v1.0.0-6-g02fca89",
"Rev": "02fca896ff5f50c6bbbee0860345a49344b37a03"
}, },
{ {
"ImportPath": "google.golang.org/grpc/internal", "ImportPath": "google.golang.org/grpc/internal",
"Rev": "e78224b060cf3215247b7be455f80ea22e469b66" "Comment": "v1.0.0-6-g02fca89",
"Rev": "02fca896ff5f50c6bbbee0860345a49344b37a03"
}, },
{ {
"ImportPath": "google.golang.org/grpc/metadata", "ImportPath": "google.golang.org/grpc/metadata",
"Rev": "e78224b060cf3215247b7be455f80ea22e469b66" "Comment": "v1.0.0-6-g02fca89",
"Rev": "02fca896ff5f50c6bbbee0860345a49344b37a03"
}, },
{ {
"ImportPath": "google.golang.org/grpc/naming", "ImportPath": "google.golang.org/grpc/naming",
"Rev": "e78224b060cf3215247b7be455f80ea22e469b66" "Comment": "v1.0.0-6-g02fca89",
"Rev": "02fca896ff5f50c6bbbee0860345a49344b37a03"
}, },
{ {
"ImportPath": "google.golang.org/grpc/peer", "ImportPath": "google.golang.org/grpc/peer",
"Rev": "e78224b060cf3215247b7be455f80ea22e469b66" "Comment": "v1.0.0-6-g02fca89",
"Rev": "02fca896ff5f50c6bbbee0860345a49344b37a03"
}, },
{ {
"ImportPath": "google.golang.org/grpc/transport", "ImportPath": "google.golang.org/grpc/transport",
"Rev": "e78224b060cf3215247b7be455f80ea22e469b66" "Comment": "v1.0.0-6-g02fca89",
"Rev": "02fca896ff5f50c6bbbee0860345a49344b37a03"
}, },
{ {
"ImportPath": "gopkg.in/cheggaaa/pb.v1", "ImportPath": "gopkg.in/cheggaaa/pb.v1",

View File

@ -40,7 +40,6 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
"google.golang.org/grpc/naming" "google.golang.org/grpc/naming"
"google.golang.org/grpc/transport"
) )
// Address represents a server the client connects to. // Address represents a server the client connects to.
@ -94,10 +93,10 @@ type Balancer interface {
// instead of blocking. // instead of blocking.
// //
// The function returns put which is called once the rpc has completed or failed. // The function returns put which is called once the rpc has completed or failed.
// put can collect and report RPC stats to a remote load balancer. gRPC internals // put can collect and report RPC stats to a remote load balancer.
// will try to call this again if err is non-nil (unless err is ErrClientConnClosing).
// //
// TODO: Add other non-recoverable errors? // This function should only return the errors Balancer cannot recover by itself.
// gRPC internals will fail the RPC if an error is returned.
Get(ctx context.Context, opts BalancerGetOptions) (addr Address, put func(), err error) Get(ctx context.Context, opts BalancerGetOptions) (addr Address, put func(), err error)
// Notify returns a channel that is used by gRPC internals to watch the addresses // Notify returns a channel that is used by gRPC internals to watch the addresses
// gRPC needs to connect. The addresses might be from a name resolver or remote // gRPC needs to connect. The addresses might be from a name resolver or remote
@ -139,35 +138,40 @@ func RoundRobin(r naming.Resolver) Balancer {
return &roundRobin{r: r} return &roundRobin{r: r}
} }
type addrInfo struct {
addr Address
connected bool
}
type roundRobin struct { type roundRobin struct {
r naming.Resolver r naming.Resolver
w naming.Watcher w naming.Watcher
open []Address // all the addresses the client should potentially connect addrs []*addrInfo // all the addresses the client should potentially connect
mu sync.Mutex mu sync.Mutex
addrCh chan []Address // the channel to notify gRPC internals the list of addresses the client should connect to. addrCh chan []Address // the channel to notify gRPC internals the list of addresses the client should connect to.
connected []Address // all the connected addresses next int // index of the next address to return for Get()
next int // index of the next address to return for Get() waitCh chan struct{} // the channel to block when there is no connected address available
waitCh chan struct{} // the channel to block when there is no connected address available done bool // The Balancer is closed.
done bool // The Balancer is closed.
} }
func (rr *roundRobin) watchAddrUpdates() error { func (rr *roundRobin) watchAddrUpdates() error {
updates, err := rr.w.Next() updates, err := rr.w.Next()
if err != nil { if err != nil {
grpclog.Println("grpc: the naming watcher stops working due to %v.", err) grpclog.Printf("grpc: the naming watcher stops working due to %v.\n", err)
return err return err
} }
rr.mu.Lock() rr.mu.Lock()
defer rr.mu.Unlock() defer rr.mu.Unlock()
for _, update := range updates { for _, update := range updates {
addr := Address{ addr := Address{
Addr: update.Addr, Addr: update.Addr,
Metadata: update.Metadata,
} }
switch update.Op { switch update.Op {
case naming.Add: case naming.Add:
var exist bool var exist bool
for _, v := range rr.open { for _, v := range rr.addrs {
if addr == v { if addr == v.addr {
exist = true exist = true
grpclog.Println("grpc: The name resolver wanted to add an existing address: ", addr) grpclog.Println("grpc: The name resolver wanted to add an existing address: ", addr)
break break
@ -176,12 +180,12 @@ func (rr *roundRobin) watchAddrUpdates() error {
if exist { if exist {
continue continue
} }
rr.open = append(rr.open, addr) rr.addrs = append(rr.addrs, &addrInfo{addr: addr})
case naming.Delete: case naming.Delete:
for i, v := range rr.open { for i, v := range rr.addrs {
if v == addr { if addr == v.addr {
copy(rr.open[i:], rr.open[i+1:]) copy(rr.addrs[i:], rr.addrs[i+1:])
rr.open = rr.open[:len(rr.open)-1] rr.addrs = rr.addrs[:len(rr.addrs)-1]
break break
} }
} }
@ -189,9 +193,11 @@ func (rr *roundRobin) watchAddrUpdates() error {
grpclog.Println("Unknown update.Op ", update.Op) grpclog.Println("Unknown update.Op ", update.Op)
} }
} }
// Make a copy of rr.open and write it onto rr.addrCh so that gRPC internals gets notified. // Make a copy of rr.addrs and write it onto rr.addrCh so that gRPC internals gets notified.
open := make([]Address, len(rr.open), len(rr.open)) open := make([]Address, len(rr.addrs))
copy(open, rr.open) for i, v := range rr.addrs {
open[i] = v.addr
}
if rr.done { if rr.done {
return ErrClientConnClosing return ErrClientConnClosing
} }
@ -202,7 +208,9 @@ func (rr *roundRobin) watchAddrUpdates() error {
func (rr *roundRobin) Start(target string) error { func (rr *roundRobin) Start(target string) error {
if rr.r == nil { if rr.r == nil {
// If there is no name resolver installed, it is not needed to // If there is no name resolver installed, it is not needed to
// do name resolution. In this case, rr.addrCh stays nil. // do name resolution. In this case, target is added into rr.addrs
// as the only address available and rr.addrCh stays nil.
rr.addrs = append(rr.addrs, &addrInfo{addr: Address{Addr: target}})
return nil return nil
} }
w, err := rr.r.Resolve(target) w, err := rr.r.Resolve(target)
@ -221,38 +229,41 @@ func (rr *roundRobin) Start(target string) error {
return nil return nil
} }
// Up appends addr to the end of rr.connected and sends notification if there // Up sets the connected state of addr and sends notification if there are pending
// are pending Get() calls. // Get() calls.
func (rr *roundRobin) Up(addr Address) func(error) { func (rr *roundRobin) Up(addr Address) func(error) {
rr.mu.Lock() rr.mu.Lock()
defer rr.mu.Unlock() defer rr.mu.Unlock()
for _, a := range rr.connected { var cnt int
if a == addr { for _, a := range rr.addrs {
return nil if a.addr == addr {
if a.connected {
return nil
}
a.connected = true
}
if a.connected {
cnt++
} }
} }
rr.connected = append(rr.connected, addr) // addr is only one which is connected. Notify the Get() callers who are blocking.
if len(rr.connected) == 1 { if cnt == 1 && rr.waitCh != nil {
// addr is only one available. Notify the Get() callers who are blocking. close(rr.waitCh)
if rr.waitCh != nil { rr.waitCh = nil
close(rr.waitCh)
rr.waitCh = nil
}
} }
return func(err error) { return func(err error) {
rr.down(addr, err) rr.down(addr, err)
} }
} }
// down removes addr from rr.connected and moves the remaining addrs forward. // down unsets the connected state of addr.
func (rr *roundRobin) down(addr Address, err error) { func (rr *roundRobin) down(addr Address, err error) {
rr.mu.Lock() rr.mu.Lock()
defer rr.mu.Unlock() defer rr.mu.Unlock()
for i, a := range rr.connected { for _, a := range rr.addrs {
if a == addr { if addr == a.addr {
copy(rr.connected[i:], rr.connected[i+1:]) a.connected = false
rr.connected = rr.connected[:len(rr.connected)-1] break
return
} }
} }
} }
@ -266,17 +277,40 @@ func (rr *roundRobin) Get(ctx context.Context, opts BalancerGetOptions) (addr Ad
err = ErrClientConnClosing err = ErrClientConnClosing
return return
} }
if rr.next >= len(rr.connected) {
rr.next = 0 if len(rr.addrs) > 0 {
if rr.next >= len(rr.addrs) {
rr.next = 0
}
next := rr.next
for {
a := rr.addrs[next]
next = (next + 1) % len(rr.addrs)
if a.connected {
addr = a.addr
rr.next = next
rr.mu.Unlock()
return
}
if next == rr.next {
// Has iterated all the possible address but none is connected.
break
}
}
} }
if len(rr.connected) > 0 { if !opts.BlockingWait {
addr = rr.connected[rr.next] if len(rr.addrs) == 0 {
rr.mu.Unlock()
err = fmt.Errorf("there is no address available")
return
}
// Returns the next addr on rr.addrs for failfast RPCs.
addr = rr.addrs[rr.next].addr
rr.next++ rr.next++
rr.mu.Unlock() rr.mu.Unlock()
return return
} }
// There is no address available. Wait on rr.waitCh. // Wait on rr.waitCh for non-failfast RPCs.
// TODO(zhaoq): Handle the case when opts.BlockingWait is false.
if rr.waitCh == nil { if rr.waitCh == nil {
ch = make(chan struct{}) ch = make(chan struct{})
rr.waitCh = ch rr.waitCh = ch
@ -287,7 +321,7 @@ func (rr *roundRobin) Get(ctx context.Context, opts BalancerGetOptions) (addr Ad
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
err = transport.ContextErr(ctx.Err()) err = ctx.Err()
return return
case <-ch: case <-ch:
rr.mu.Lock() rr.mu.Lock()
@ -296,24 +330,35 @@ func (rr *roundRobin) Get(ctx context.Context, opts BalancerGetOptions) (addr Ad
err = ErrClientConnClosing err = ErrClientConnClosing
return return
} }
if len(rr.connected) == 0 {
// The newly added addr got removed by Down() again. if len(rr.addrs) > 0 {
if rr.waitCh == nil { if rr.next >= len(rr.addrs) {
ch = make(chan struct{}) rr.next = 0
rr.waitCh = ch }
} else { next := rr.next
ch = rr.waitCh for {
a := rr.addrs[next]
next = (next + 1) % len(rr.addrs)
if a.connected {
addr = a.addr
rr.next = next
rr.mu.Unlock()
return
}
if next == rr.next {
// Has iterated all the possible address but none is connected.
break
}
} }
rr.mu.Unlock()
continue
} }
if rr.next >= len(rr.connected) { // The newly added addr got removed by Down() again.
rr.next = 0 if rr.waitCh == nil {
ch = make(chan struct{})
rr.waitCh = ch
} else {
ch = rr.waitCh
} }
addr = rr.connected[rr.next]
rr.next++
rr.mu.Unlock() rr.mu.Unlock()
return
} }
} }
} }

View File

@ -101,7 +101,7 @@ func sendRequest(ctx context.Context, codec Codec, compressor Compressor, callHd
// Invoke is called by generated code. Also users can call Invoke directly when it // Invoke is called by generated code. Also users can call Invoke directly when it
// is really needed in their use cases. // is really needed in their use cases.
func Invoke(ctx context.Context, method string, args, reply interface{}, cc *ClientConn, opts ...CallOption) (err error) { func Invoke(ctx context.Context, method string, args, reply interface{}, cc *ClientConn, opts ...CallOption) (err error) {
var c callInfo c := defaultCallInfo
for _, o := range opts { for _, o := range opts {
if err := o.before(&c); err != nil { if err := o.before(&c); err != nil {
return toRPCErr(err) return toRPCErr(err)
@ -155,19 +155,17 @@ func Invoke(ctx context.Context, method string, args, reply interface{}, cc *Cli
t, put, err = cc.getTransport(ctx, gopts) t, put, err = cc.getTransport(ctx, gopts)
if err != nil { if err != nil {
// TODO(zhaoq): Probably revisit the error handling. // TODO(zhaoq): Probably revisit the error handling.
if err == ErrClientConnClosing { if _, ok := err.(*rpcError); ok {
return Errorf(codes.FailedPrecondition, "%v", err) return err
} }
if _, ok := err.(transport.StreamError); ok { if err == errConnClosing {
return toRPCErr(err)
}
if _, ok := err.(transport.ConnectionError); ok {
if c.failFast { if c.failFast {
return toRPCErr(err) return Errorf(codes.Unavailable, "%v", errConnClosing)
} }
continue
} }
// All the remaining cases are treated as retryable. // All the other errors are treated as Internal errors.
continue return Errorf(codes.Internal, "%v", err)
} }
if c.traceInfo.tr != nil { if c.traceInfo.tr != nil {
c.traceInfo.tr.LazyLog(&payload{sent: true, msg: args}, true) c.traceInfo.tr.LazyLog(&payload{sent: true, msg: args}, true)

View File

@ -218,27 +218,26 @@ func Dial(target string, opts ...DialOption) (*ClientConn, error) {
for _, opt := range opts { for _, opt := range opts {
opt(&cc.dopts) opt(&cc.dopts)
} }
// Set defaults.
if cc.dopts.codec == nil { if cc.dopts.codec == nil {
// Set the default codec.
cc.dopts.codec = protoCodec{} cc.dopts.codec = protoCodec{}
} }
if cc.dopts.bs == nil { if cc.dopts.bs == nil {
cc.dopts.bs = DefaultBackoffConfig cc.dopts.bs = DefaultBackoffConfig
} }
if cc.dopts.balancer == nil {
cc.balancer = cc.dopts.balancer cc.dopts.balancer = RoundRobin(nil)
if cc.balancer == nil {
cc.balancer = RoundRobin(nil)
} }
if err := cc.balancer.Start(target); err != nil {
if err := cc.dopts.balancer.Start(target); err != nil {
return nil, err return nil, err
} }
var ( var (
ok bool ok bool
addrs []Address addrs []Address
) )
ch := cc.balancer.Notify() ch := cc.dopts.balancer.Notify()
if ch == nil { if ch == nil {
// There is no name resolver installed. // There is no name resolver installed.
addrs = append(addrs, Address{Addr: target}) addrs = append(addrs, Address{Addr: target})
@ -319,7 +318,6 @@ func (s ConnectivityState) String() string {
// ClientConn represents a client connection to an RPC server. // ClientConn represents a client connection to an RPC server.
type ClientConn struct { type ClientConn struct {
target string target string
balancer Balancer
authority string authority string
dopts dialOptions dopts dialOptions
@ -328,7 +326,7 @@ type ClientConn struct {
} }
func (cc *ClientConn) lbWatcher() { func (cc *ClientConn) lbWatcher() {
for addrs := range cc.balancer.Notify() { for addrs := range cc.dopts.balancer.Notify() {
var ( var (
add []Address // Addresses need to setup connections. add []Address // Addresses need to setup connections.
del []*addrConn // Connections need to tear down. del []*addrConn // Connections need to tear down.
@ -424,15 +422,14 @@ func (cc *ClientConn) newAddrConn(addr Address, skipWait bool) error {
} }
func (cc *ClientConn) getTransport(ctx context.Context, opts BalancerGetOptions) (transport.ClientTransport, func(), error) { func (cc *ClientConn) getTransport(ctx context.Context, opts BalancerGetOptions) (transport.ClientTransport, func(), error) {
// TODO(zhaoq): Implement fail-fast logic. addr, put, err := cc.dopts.balancer.Get(ctx, opts)
addr, put, err := cc.balancer.Get(ctx, opts)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, toRPCErr(err)
} }
cc.mu.RLock() cc.mu.RLock()
if cc.conns == nil { if cc.conns == nil {
cc.mu.RUnlock() cc.mu.RUnlock()
return nil, nil, ErrClientConnClosing return nil, nil, toRPCErr(ErrClientConnClosing)
} }
ac, ok := cc.conns[addr] ac, ok := cc.conns[addr]
cc.mu.RUnlock() cc.mu.RUnlock()
@ -440,9 +437,9 @@ func (cc *ClientConn) getTransport(ctx context.Context, opts BalancerGetOptions)
if put != nil { if put != nil {
put() put()
} }
return nil, nil, transport.StreamErrorf(codes.Internal, "grpc: failed to find the transport to send the rpc") return nil, nil, Errorf(codes.Internal, "grpc: failed to find the transport to send the rpc")
} }
t, err := ac.wait(ctx) t, err := ac.wait(ctx, !opts.BlockingWait)
if err != nil { if err != nil {
if put != nil { if put != nil {
put() put()
@ -462,7 +459,7 @@ func (cc *ClientConn) Close() error {
conns := cc.conns conns := cc.conns
cc.conns = nil cc.conns = nil
cc.mu.Unlock() cc.mu.Unlock()
cc.balancer.Close() cc.dopts.balancer.Close()
for _, ac := range conns { for _, ac := range conns {
ac.tearDown(ErrClientConnClosing) ac.tearDown(ErrClientConnClosing)
} }
@ -610,7 +607,7 @@ func (ac *addrConn) resetTransport(closeTransport bool) error {
close(ac.ready) close(ac.ready)
ac.ready = nil ac.ready = nil
} }
ac.down = ac.cc.balancer.Up(ac.addr) ac.down = ac.cc.dopts.balancer.Up(ac.addr)
ac.mu.Unlock() ac.mu.Unlock()
return nil return nil
} }
@ -649,8 +646,9 @@ func (ac *addrConn) transportMonitor() {
} }
} }
// wait blocks until i) the new transport is up or ii) ctx is done or iii) ac is closed. // wait blocks until i) the new transport is up or ii) ctx is done or iii) ac is closed or
func (ac *addrConn) wait(ctx context.Context) (transport.ClientTransport, error) { // iv) transport is in TransientFailure and the RPC is fail-fast.
func (ac *addrConn) wait(ctx context.Context, failFast bool) (transport.ClientTransport, error) {
for { for {
ac.mu.Lock() ac.mu.Lock()
switch { switch {
@ -661,6 +659,9 @@ func (ac *addrConn) wait(ctx context.Context) (transport.ClientTransport, error)
ct := ac.transport ct := ac.transport
ac.mu.Unlock() ac.mu.Unlock()
return ct, nil return ct, nil
case ac.state == TransientFailure && failFast:
ac.mu.Unlock()
return nil, Errorf(codes.Unavailable, "grpc: RPC failed fast due to transport failure")
default: default:
ready := ac.ready ready := ac.ready
if ready == nil { if ready == nil {
@ -670,7 +671,7 @@ func (ac *addrConn) wait(ctx context.Context) (transport.ClientTransport, error)
ac.mu.Unlock() ac.mu.Unlock()
select { select {
case <-ctx.Done(): case <-ctx.Done():
return nil, transport.ContextErr(ctx.Err()) return nil, toRPCErr(ctx.Err())
// Wait until the new transport is ready or failed. // Wait until the new transport is ready or failed.
case <-ready: case <-ready:
} }

View File

@ -66,7 +66,7 @@ type PerRPCCredentials interface {
// TODO(zhaoq): Define the set of the qualified keys instead of leaving // TODO(zhaoq): Define the set of the qualified keys instead of leaving
// it as an arbitrary string. // it as an arbitrary string.
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
// RequireTransportSecurity indicates whether the credentails requires // RequireTransportSecurity indicates whether the credentials requires
// transport security. // transport security.
RequireTransportSecurity() bool RequireTransportSecurity() bool
} }
@ -116,7 +116,7 @@ func (t TLSInfo) AuthType() string {
// tlsCreds is the credentials required for authenticating a connection using TLS. // tlsCreds is the credentials required for authenticating a connection using TLS.
type tlsCreds struct { type tlsCreds struct {
// TLS configuration // TLS configuration
config tls.Config config *tls.Config
} }
func (c tlsCreds) Info() ProtocolInfo { func (c tlsCreds) Info() ProtocolInfo {
@ -151,14 +151,16 @@ func (c *tlsCreds) ClientHandshake(addr string, rawConn net.Conn, timeout time.D
errChannel <- timeoutError{} errChannel <- timeoutError{}
}) })
} }
// use local cfg to avoid clobbering ServerName if using multiple endpoints
cfg := *c.config
if c.config.ServerName == "" { if c.config.ServerName == "" {
colonPos := strings.LastIndex(addr, ":") colonPos := strings.LastIndex(addr, ":")
if colonPos == -1 { if colonPos == -1 {
colonPos = len(addr) colonPos = len(addr)
} }
c.config.ServerName = addr[:colonPos] cfg.ServerName = addr[:colonPos]
} }
conn := tls.Client(rawConn, &c.config) conn := tls.Client(rawConn, &cfg)
if timeout == 0 { if timeout == 0 {
err = conn.Handshake() err = conn.Handshake()
} else { } else {
@ -177,7 +179,7 @@ func (c *tlsCreds) ClientHandshake(addr string, rawConn net.Conn, timeout time.D
} }
func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, AuthInfo, error) { func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, AuthInfo, error) {
conn := tls.Server(rawConn, &c.config) conn := tls.Server(rawConn, c.config)
if err := conn.Handshake(); err != nil { if err := conn.Handshake(); err != nil {
rawConn.Close() rawConn.Close()
return nil, nil, err return nil, nil, err
@ -187,7 +189,7 @@ func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, AuthInfo, error)
// NewTLS uses c to construct a TransportCredentials based on TLS. // NewTLS uses c to construct a TransportCredentials based on TLS.
func NewTLS(c *tls.Config) TransportCredentials { func NewTLS(c *tls.Config) TransportCredentials {
tc := &tlsCreds{*c} tc := &tlsCreds{c}
tc.config.NextProtos = alpnProtoStr tc.config.NextProtos = alpnProtoStr
return tc return tc
} }

View File

@ -141,6 +141,8 @@ type callInfo struct {
traceInfo traceInfo // in trace.go traceInfo traceInfo // in trace.go
} }
var defaultCallInfo = callInfo{failFast: true}
// CallOption configures a Call before it starts or extracts information from // CallOption configures a Call before it starts or extracts information from
// a Call after it completes. // a Call after it completes.
type CallOption interface { type CallOption interface {
@ -179,6 +181,19 @@ func Trailer(md *metadata.MD) CallOption {
}) })
} }
// FailFast configures the action to take when an RPC is attempted on broken
// connections or unreachable servers. If failfast is true, the RPC will fail
// immediately. Otherwise, the RPC client will block the call until a
// connection is available (or the call is canceled or times out) and will retry
// the call if it fails due to a transient error. Please refer to
// https://github.com/grpc/grpc/blob/master/doc/fail_fast.md
func FailFast(failFast bool) CallOption {
return beforeCall(func(c *callInfo) error {
c.failFast = failFast
return nil
})
}
// The format of the payload: compressed or not? // The format of the payload: compressed or not?
type payloadFormat uint8 type payloadFormat uint8
@ -319,7 +334,7 @@ type rpcError struct {
desc string desc string
} }
func (e rpcError) Error() string { func (e *rpcError) Error() string {
return fmt.Sprintf("rpc error: code = %d desc = %s", e.code, e.desc) return fmt.Sprintf("rpc error: code = %d desc = %s", e.code, e.desc)
} }
@ -329,7 +344,7 @@ func Code(err error) codes.Code {
if err == nil { if err == nil {
return codes.OK return codes.OK
} }
if e, ok := err.(rpcError); ok { if e, ok := err.(*rpcError); ok {
return e.code return e.code
} }
return codes.Unknown return codes.Unknown
@ -341,7 +356,7 @@ func ErrorDesc(err error) string {
if err == nil { if err == nil {
return "" return ""
} }
if e, ok := err.(rpcError); ok { if e, ok := err.(*rpcError); ok {
return e.desc return e.desc
} }
return err.Error() return err.Error()
@ -353,7 +368,7 @@ func Errorf(c codes.Code, format string, a ...interface{}) error {
if c == codes.OK { if c == codes.OK {
return nil return nil
} }
return rpcError{ return &rpcError{
code: c, code: c,
desc: fmt.Sprintf(format, a...), desc: fmt.Sprintf(format, a...),
} }
@ -362,18 +377,37 @@ func Errorf(c codes.Code, format string, a ...interface{}) error {
// toRPCErr converts an error into a rpcError. // toRPCErr converts an error into a rpcError.
func toRPCErr(err error) error { func toRPCErr(err error) error {
switch e := err.(type) { switch e := err.(type) {
case rpcError: case *rpcError:
return err return err
case transport.StreamError: case transport.StreamError:
return rpcError{ return &rpcError{
code: e.Code, code: e.Code,
desc: e.Desc, desc: e.Desc,
} }
case transport.ConnectionError: case transport.ConnectionError:
return rpcError{ return &rpcError{
code: codes.Internal, code: codes.Internal,
desc: e.Desc, desc: e.Desc,
} }
default:
switch err {
case context.DeadlineExceeded:
return &rpcError{
code: codes.DeadlineExceeded,
desc: err.Error(),
}
case context.Canceled:
return &rpcError{
code: codes.Canceled,
desc: err.Error(),
}
case ErrClientConnClosing:
return &rpcError{
code: codes.FailedPrecondition,
desc: err.Error(),
}
}
} }
return Errorf(codes.Unknown, "%v", err) return Errorf(codes.Unknown, "%v", err)
} }

View File

@ -82,6 +82,7 @@ type service struct {
server interface{} // the server for service methods server interface{} // the server for service methods
md map[string]*MethodDesc md map[string]*MethodDesc
sd map[string]*StreamDesc sd map[string]*StreamDesc
mdata interface{}
} }
// Server is a gRPC server to serve RPC requests. // Server is a gRPC server to serve RPC requests.
@ -231,6 +232,7 @@ func (s *Server) register(sd *ServiceDesc, ss interface{}) {
server: ss, server: ss,
md: make(map[string]*MethodDesc), md: make(map[string]*MethodDesc),
sd: make(map[string]*StreamDesc), sd: make(map[string]*StreamDesc),
mdata: sd.Metadata,
} }
for i := range sd.Methods { for i := range sd.Methods {
d := &sd.Methods[i] d := &sd.Methods[i]
@ -243,6 +245,52 @@ func (s *Server) register(sd *ServiceDesc, ss interface{}) {
s.m[sd.ServiceName] = srv s.m[sd.ServiceName] = srv
} }
// MethodInfo contains the information of an RPC including its method name and type.
type MethodInfo struct {
// Name is the method name only, without the service name or package name.
Name string
// IsClientStream indicates whether the RPC is a client streaming RPC.
IsClientStream bool
// IsServerStream indicates whether the RPC is a server streaming RPC.
IsServerStream bool
}
// ServiceInfo contains unary RPC method info, streaming RPC methid info and metadata for a service.
type ServiceInfo struct {
Methods []MethodInfo
// Metadata is the metadata specified in ServiceDesc when registering service.
Metadata interface{}
}
// GetServiceInfo returns a map from service names to ServiceInfo.
// Service names include the package names, in the form of <package>.<service>.
func (s *Server) GetServiceInfo() map[string]*ServiceInfo {
ret := make(map[string]*ServiceInfo)
for n, srv := range s.m {
methods := make([]MethodInfo, 0, len(srv.md)+len(srv.sd))
for m := range srv.md {
methods = append(methods, MethodInfo{
Name: m,
IsClientStream: false,
IsServerStream: false,
})
}
for m, d := range srv.sd {
methods = append(methods, MethodInfo{
Name: m,
IsClientStream: d.ClientStreams,
IsServerStream: d.ServerStreams,
})
}
ret[n] = &ServiceInfo{
Methods: methods,
Metadata: srv.mdata,
}
}
return ret
}
var ( var (
// ErrServerStopped indicates that the operation is now illegal because of // ErrServerStopped indicates that the operation is now illegal because of
// the server being stopped. // the server being stopped.
@ -272,9 +320,11 @@ func (s *Server) Serve(lis net.Listener) error {
s.lis[lis] = true s.lis[lis] = true
s.mu.Unlock() s.mu.Unlock()
defer func() { defer func() {
lis.Close()
s.mu.Lock() s.mu.Lock()
delete(s.lis, lis) if s.lis != nil && s.lis[lis] {
lis.Close()
delete(s.lis, lis)
}
s.mu.Unlock() s.mu.Unlock()
}() }()
for { for {
@ -529,7 +579,7 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.
} }
reply, appErr := md.Handler(srv.server, stream.Context(), df, s.opts.unaryInt) reply, appErr := md.Handler(srv.server, stream.Context(), df, s.opts.unaryInt)
if appErr != nil { if appErr != nil {
if err, ok := appErr.(rpcError); ok { if err, ok := appErr.(*rpcError); ok {
statusCode = err.code statusCode = err.code
statusDesc = err.desc statusDesc = err.desc
} else { } else {
@ -614,7 +664,7 @@ func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transp
appErr = s.opts.streamInt(srv.server, ss, info, sd.Handler) appErr = s.opts.streamInt(srv.server, ss, info, sd.Handler)
} }
if appErr != nil { if appErr != nil {
if err, ok := appErr.(rpcError); ok { if err, ok := appErr.(*rpcError); ok {
ss.statusCode = err.code ss.statusCode = err.code
ss.statusDesc = err.desc ss.statusDesc = err.desc
} else if err, ok := appErr.(transport.StreamError); ok { } else if err, ok := appErr.(transport.StreamError); ok {

View File

@ -102,16 +102,15 @@ type ClientStream interface {
func NewClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, opts ...CallOption) (ClientStream, error) { func NewClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, opts ...CallOption) (ClientStream, error) {
var ( var (
t transport.ClientTransport t transport.ClientTransport
s *transport.Stream
err error err error
put func() put func()
) )
// TODO(zhaoq): CallOption is omitted. Add support when it is needed. c := defaultCallInfo
gopts := BalancerGetOptions{ for _, o := range opts {
BlockingWait: false, if err := o.before(&c); err != nil {
} return nil, toRPCErr(err)
t, put, err = cc.getTransport(ctx, gopts) }
if err != nil {
return nil, toRPCErr(err)
} }
callHdr := &transport.CallHdr{ callHdr := &transport.CallHdr{
Host: cc.authority, Host: cc.authority,
@ -122,8 +121,9 @@ func NewClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth
callHdr.SendCompress = cc.dopts.cp.Type() callHdr.SendCompress = cc.dopts.cp.Type()
} }
cs := &clientStream{ cs := &clientStream{
opts: opts,
c: c,
desc: desc, desc: desc,
put: put,
codec: cc.dopts.codec, codec: cc.dopts.codec,
cp: cc.dopts.cp, cp: cc.dopts.cp,
dc: cc.dopts.dc, dc: cc.dopts.dc,
@ -142,11 +142,44 @@ func NewClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth
cs.trInfo.tr.LazyLog(&cs.trInfo.firstLine, false) cs.trInfo.tr.LazyLog(&cs.trInfo.firstLine, false)
ctx = trace.NewContext(ctx, cs.trInfo.tr) ctx = trace.NewContext(ctx, cs.trInfo.tr)
} }
s, err := t.NewStream(ctx, callHdr) gopts := BalancerGetOptions{
if err != nil { BlockingWait: !c.failFast,
cs.finish(err)
return nil, toRPCErr(err)
} }
for {
t, put, err = cc.getTransport(ctx, gopts)
if err != nil {
// TODO(zhaoq): Probably revisit the error handling.
if _, ok := err.(*rpcError); ok {
return nil, err
}
if err == errConnClosing {
if c.failFast {
return nil, Errorf(codes.Unavailable, "%v", errConnClosing)
}
continue
}
// All the other errors are treated as Internal errors.
return nil, Errorf(codes.Internal, "%v", err)
}
s, err = t.NewStream(ctx, callHdr)
if err != nil {
if put != nil {
put()
put = nil
}
if _, ok := err.(transport.ConnectionError); ok {
if c.failFast {
cs.finish(err)
return nil, toRPCErr(err)
}
continue
}
return nil, toRPCErr(err)
}
break
}
cs.put = put
cs.t = t cs.t = t
cs.s = s cs.s = s
cs.p = &parser{r: s} cs.p = &parser{r: s}
@ -167,6 +200,8 @@ func NewClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth
// clientStream implements a client side Stream. // clientStream implements a client side Stream.
type clientStream struct { type clientStream struct {
opts []CallOption
c callInfo
t transport.ClientTransport t transport.ClientTransport
s *transport.Stream s *transport.Stream
p *parser p *parser
@ -312,15 +347,18 @@ func (cs *clientStream) closeTransportStream(err error) {
} }
func (cs *clientStream) finish(err error) { func (cs *clientStream) finish(err error) {
if !cs.tracing {
return
}
cs.mu.Lock() cs.mu.Lock()
defer cs.mu.Unlock() defer cs.mu.Unlock()
for _, o := range cs.opts {
o.after(&cs.c)
}
if cs.put != nil { if cs.put != nil {
cs.put() cs.put()
cs.put = nil cs.put = nil
} }
if !cs.tracing {
return
}
if cs.trInfo.tr != nil { if cs.trInfo.tr != nil {
if err == nil || err == io.EOF { if err == nil || err == io.EOF {
cs.trInfo.tr.LazyPrintf("RPC: [OK]") cs.trInfo.tr.LazyPrintf("RPC: [OK]")

View File

@ -312,7 +312,7 @@ func (ht *serverHandlerTransport) HandleStreams(startStream func(*Stream)) {
Addr: ht.RemoteAddr(), Addr: ht.RemoteAddr(),
} }
if req.TLS != nil { if req.TLS != nil {
pr.AuthInfo = credentials.TLSInfo{*req.TLS} pr.AuthInfo = credentials.TLSInfo{State: *req.TLS}
} }
ctx = metadata.NewContext(ctx, ht.headerMD) ctx = metadata.NewContext(ctx, ht.headerMD)
ctx = peer.NewContext(ctx, pr) ctx = peer.NewContext(ctx, pr)

View File

@ -175,7 +175,10 @@ func newHTTP2Client(addr string, opts *ConnectOptions) (_ ClientTransport, err e
return nil, ConnectionErrorf("transport: preface mismatch, wrote %d bytes; want %d", n, len(clientPreface)) return nil, ConnectionErrorf("transport: preface mismatch, wrote %d bytes; want %d", n, len(clientPreface))
} }
if initialWindowSize != defaultWindowSize { if initialWindowSize != defaultWindowSize {
err = t.framer.writeSettings(true, http2.Setting{http2.SettingInitialWindowSize, uint32(initialWindowSize)}) err = t.framer.writeSettings(true, http2.Setting{
ID: http2.SettingInitialWindowSize,
Val: uint32(initialWindowSize),
})
} else { } else {
err = t.framer.writeSettings(true) err = t.framer.writeSettings(true)
} }

View File

@ -100,10 +100,15 @@ func newHTTP2Server(conn net.Conn, maxStreams uint32, authInfo credentials.AuthI
if maxStreams == 0 { if maxStreams == 0 {
maxStreams = math.MaxUint32 maxStreams = math.MaxUint32
} else { } else {
settings = append(settings, http2.Setting{http2.SettingMaxConcurrentStreams, maxStreams}) settings = append(settings, http2.Setting{
ID: http2.SettingMaxConcurrentStreams,
Val: maxStreams,
})
} }
if initialWindowSize != defaultWindowSize { if initialWindowSize != defaultWindowSize {
settings = append(settings, http2.Setting{http2.SettingInitialWindowSize, uint32(initialWindowSize)}) settings = append(settings, http2.Setting{
ID: http2.SettingInitialWindowSize,
Val: uint32(initialWindowSize)})
} }
if err := framer.writeSettings(true, settings...); err != nil { if err := framer.writeSettings(true, settings...); err != nil {
return nil, ConnectionErrorf("transport: %v", err) return nil, ConnectionErrorf("transport: %v", err)

View File

@ -52,7 +52,7 @@ func TestCtlV3Migrate(t *testing.T) {
for i := range epc.procs { for i := range epc.procs {
dataDirs[i] = epc.procs[i].cfg.dataDirPath dataDirs[i] = epc.procs[i].cfg.dataDirPath
} }
if err := epc.Stop(); err != nil { if err := epc.StopAll(); err != nil {
t.Fatalf("error closing etcd processes (%v)", err) t.Fatalf("error closing etcd processes (%v)", err)
} }
@ -74,7 +74,7 @@ func TestCtlV3Migrate(t *testing.T) {
for i := range epc.procs { for i := range epc.procs {
epc.procs[i].cfg.keepDataDir = true epc.procs[i].cfg.keepDataDir = true
} }
if err := epc.Restart(); err != nil { if err := epc.RestartAll(); err != nil {
t.Fatal(err) t.Fatal(err)
} }

View File

@ -0,0 +1,87 @@
// Copyright 2016 The etcd Authors
//
// 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 e2e
import (
"fmt"
"os"
"testing"
"time"
"github.com/coreos/etcd/pkg/fileutil"
"github.com/coreos/etcd/pkg/testutil"
)
// TestReleaseUpgrade ensures that changes to master branch does not affect
// upgrade from latest etcd releases.
func TestReleaseUpgrade(t *testing.T) {
lastReleaseBinary := "../bin/etcd-last-release"
if !fileutil.Exist(lastReleaseBinary) {
t.Skipf("%q does not exist", lastReleaseBinary)
}
defer testutil.AfterTest(t)
copiedCfg := configNoTLS
copiedCfg.execPath = lastReleaseBinary
copiedCfg.snapCount = 3
epc, err := newEtcdProcessCluster(&copiedCfg)
if err != nil {
t.Fatalf("could not start etcd process cluster (%v)", err)
}
defer func() {
if errC := epc.Close(); errC != nil {
t.Fatalf("error closing etcd processes (%v)", errC)
}
}()
os.Setenv("ETCDCTL_API", "3")
defer os.Unsetenv("ETCDCTL_API")
cx := ctlCtx{
t: t,
cfg: configNoTLS,
dialTimeout: 7 * time.Second,
quorum: true,
epc: epc,
}
var kvs []kv
for i := 0; i < 5; i++ {
kvs = append(kvs, kv{key: fmt.Sprintf("foo%d", i), val: "bar"})
}
for i := range kvs {
if err := ctlV3Put(cx, kvs[i].key, kvs[i].val, ""); err != nil {
cx.t.Fatalf("#%d: ctlV3Put error (%v)", i, err)
}
}
for i := range epc.procs {
if err := epc.procs[i].Stop(); err != nil {
t.Fatalf("#%d: error closing etcd process (%v)", i, err)
}
epc.procs[i].cfg.execPath = "../bin/etcd"
epc.procs[i].cfg.keepDataDir = true
if err := epc.procs[i].Restart(); err != nil {
t.Fatalf("error restarting etcd process (%v)", err)
}
for j := range kvs {
if err := ctlV3Get(cx, []string{kvs[j].key}, []kv{kvs[j]}...); err != nil {
cx.t.Fatalf("#%d-%d: ctlV3Get error (%v)", i, j, err)
}
}
}
}

View File

@ -21,6 +21,7 @@ import (
"os" "os"
"strings" "strings"
"github.com/coreos/etcd/etcdserver"
"github.com/coreos/etcd/pkg/expect" "github.com/coreos/etcd/pkg/expect"
"github.com/coreos/etcd/pkg/fileutil" "github.com/coreos/etcd/pkg/fileutil"
) )
@ -122,7 +123,8 @@ type etcdProcess struct {
} }
type etcdProcessConfig struct { type etcdProcessConfig struct {
args []string execPath string
args []string
dataDirPath string dataDirPath string
keepDataDir bool keepDataDir bool
@ -137,12 +139,16 @@ type etcdProcessConfig struct {
} }
type etcdProcessClusterConfig struct { type etcdProcessClusterConfig struct {
execPath string
dataDirPath string dataDirPath string
keepDataDir bool keepDataDir bool
clusterSize int clusterSize int
basePort int basePort int
proxySize int proxySize int
snapCount int // default is 10000
clientTLS clientConnType clientTLS clientConnType
isPeerTLS bool isPeerTLS bool
isPeerAutoTLS bool isPeerAutoTLS bool
@ -175,7 +181,7 @@ func newEtcdProcessCluster(cfg *etcdProcessClusterConfig) (*etcdProcessCluster,
} }
func newEtcdProcess(cfg *etcdProcessConfig) (*etcdProcess, error) { func newEtcdProcess(cfg *etcdProcessConfig) (*etcdProcess, error) {
if !fileutil.Exist("../bin/etcd") { if !fileutil.Exist(cfg.execPath) {
return nil, fmt.Errorf("could not find etcd binary") return nil, fmt.Errorf("could not find etcd binary")
} }
@ -185,7 +191,7 @@ func newEtcdProcess(cfg *etcdProcessConfig) (*etcdProcess, error) {
} }
} }
child, err := spawnCmd(append([]string{"../bin/etcd"}, cfg.args...)) child, err := spawnCmd(append([]string{cfg.execPath}, cfg.args...))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -197,6 +203,13 @@ func (cfg *etcdProcessClusterConfig) etcdProcessConfigs() []*etcdProcessConfig {
cfg.basePort = etcdProcessBasePort cfg.basePort = etcdProcessBasePort
} }
if cfg.execPath == "" {
cfg.execPath = "../bin/etcd"
}
if cfg.snapCount == 0 {
cfg.snapCount = etcdserver.DefaultSnapCount
}
clientScheme := "http" clientScheme := "http"
if cfg.clientTLS == clientTLS { if cfg.clientTLS == clientTLS {
clientScheme = "https" clientScheme = "https"
@ -244,6 +257,7 @@ func (cfg *etcdProcessClusterConfig) etcdProcessConfigs() []*etcdProcessConfig {
"--initial-advertise-peer-urls", purl.String(), "--initial-advertise-peer-urls", purl.String(),
"--initial-cluster-token", cfg.initialToken, "--initial-cluster-token", cfg.initialToken,
"--data-dir", dataDirPath, "--data-dir", dataDirPath,
"--snapshot-count", fmt.Sprintf("%d", cfg.snapCount),
} }
if cfg.forceNewCluster { if cfg.forceNewCluster {
args = append(args, "--force-new-cluster") args = append(args, "--force-new-cluster")
@ -256,6 +270,7 @@ func (cfg *etcdProcessClusterConfig) etcdProcessConfigs() []*etcdProcessConfig {
args = append(args, cfg.tlsArgs()...) args = append(args, cfg.tlsArgs()...)
etcdCfgs[i] = &etcdProcessConfig{ etcdCfgs[i] = &etcdProcessConfig{
execPath: cfg.execPath,
args: args, args: args,
dataDirPath: dataDirPath, dataDirPath: dataDirPath,
keepDataDir: cfg.keepDataDir, keepDataDir: cfg.keepDataDir,
@ -281,6 +296,7 @@ func (cfg *etcdProcessClusterConfig) etcdProcessConfigs() []*etcdProcessConfig {
} }
args = append(args, cfg.tlsArgs()...) args = append(args, cfg.tlsArgs()...)
etcdCfgs[cfg.clusterSize+i] = &etcdProcessConfig{ etcdCfgs[cfg.clusterSize+i] = &etcdProcessConfig{
execPath: cfg.execPath,
args: args, args: args,
dataDirPath: dataDirPath, dataDirPath: dataDirPath,
keepDataDir: cfg.keepDataDir, keepDataDir: cfg.keepDataDir,
@ -351,7 +367,7 @@ func (epc *etcdProcessCluster) Start() (err error) {
return nil return nil
} }
func (epc *etcdProcessCluster) Restart() error { func (epc *etcdProcessCluster) RestartAll() error {
for i := range epc.procs { for i := range epc.procs {
proc, err := newEtcdProcess(epc.procs[i].cfg) proc, err := newEtcdProcess(epc.procs[i].cfg)
if err != nil { if err != nil {
@ -363,7 +379,29 @@ func (epc *etcdProcessCluster) Restart() error {
return epc.Start() return epc.Start()
} }
func (epc *etcdProcessCluster) Stop() (err error) { func (epr *etcdProcess) Restart() error {
proc, err := newEtcdProcess(epr.cfg)
if err != nil {
epr.Stop()
return err
}
*epr = *proc
readyStr := "enabled capabilities for version"
if proc.cfg.isProxy {
readyStr = "httpproxy: endpoints found"
}
if _, err = proc.proc.Expect(readyStr); err != nil {
epr.Stop()
return err
}
close(proc.donec)
return nil
}
func (epc *etcdProcessCluster) StopAll() (err error) {
for _, p := range epc.procs { for _, p := range epc.procs {
if p == nil { if p == nil {
continue continue
@ -380,8 +418,21 @@ func (epc *etcdProcessCluster) Stop() (err error) {
return err return err
} }
func (epr *etcdProcess) Stop() error {
if epr == nil {
return nil
}
if err := epr.proc.Stop(); err != nil {
return err
}
<-epr.donec
return nil
}
func (epc *etcdProcessCluster) Close() error { func (epc *etcdProcessCluster) Close() error {
err := epc.Stop() err := epc.StopAll()
for _, p := range epc.procs { for _, p := range epc.procs {
os.RemoveAll(p.cfg.dataDirPath) os.RemoveAll(p.cfg.dataDirPath)
} }

View File

@ -43,7 +43,7 @@ var (
) )
func init() { func init() {
rootCmd.PersistentFlags().StringSliceVar(&globalFlags.Endpoints, "endpoints", []string{"127.0.0.1:2379", "127.0.0.1:22379", "127.0.0.1:32379"}, "gRPC endpoints") rootCmd.PersistentFlags().StringSliceVar(&globalFlags.Endpoints, "endpoints", []string{"127.0.0.1:2379"}, "gRPC endpoints")
rootCmd.PersistentFlags().StringVarP(&globalFlags.OutputFormat, "write-out", "w", "simple", "set the output format (simple, json, etc..)") rootCmd.PersistentFlags().StringVarP(&globalFlags.OutputFormat, "write-out", "w", "simple", "set the output format (simple, json, etc..)")
rootCmd.PersistentFlags().BoolVar(&globalFlags.IsHex, "hex", false, "print byte strings as hex encoded strings") rootCmd.PersistentFlags().BoolVar(&globalFlags.IsHex, "hex", false, "print byte strings as hex encoded strings")

View File

@ -194,9 +194,16 @@ func startEtcdOrProxyV2() {
// startEtcd launches the etcd server and HTTP handlers for client/server communication. // startEtcd launches the etcd server and HTTP handlers for client/server communication.
func startEtcd(cfg *config) (<-chan struct{}, error) { func startEtcd(cfg *config) (<-chan struct{}, error) {
urlsmap, token, err := getPeerURLsMapAndToken(cfg, "etcd") var (
if err != nil { urlsmap types.URLsMap
return nil, fmt.Errorf("error setting up initial cluster: %v", err) token string
err error
)
if !isMemberInitialized(cfg) {
urlsmap, token, err = getPeerURLsMapAndToken(cfg, "etcd")
if err != nil {
return nil, fmt.Errorf("error setting up initial cluster: %v", err)
}
} }
if cfg.PeerAutoTLS && cfg.peerTLSInfo.Empty() { if cfg.PeerAutoTLS && cfg.peerTLSInfo.Empty() {

30
etcdmain/util.go Normal file
View File

@ -0,0 +1,30 @@
// Copyright 2016 The etcd Authors
//
// 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 etcdmain
import (
"path"
"github.com/coreos/etcd/wal"
)
func isMemberInitialized(cfg *config) bool {
waldir := cfg.WalDir
if waldir == "" {
waldir = path.Join(cfg.Dir, "member", "wal")
}
return wal.Exist(waldir)
}

View File

@ -26,15 +26,15 @@ func TestConvert(t *testing.T) {
e2 := ErrGRPCEmptyKey e2 := ErrGRPCEmptyKey
e3 := ErrEmptyKey e3 := ErrEmptyKey
if e1 != e2 { if e1.Error() != e2.Error() {
t.Fatalf("expected 'true', got %T != %T", e1, e2) t.Fatalf("expected %q == %q", e1.Error(), e2.Error())
} }
if grpc.Code(e1) != e3.(EtcdError).Code() { if grpc.Code(e1) != e3.(EtcdError).Code() {
t.Fatalf("expected them to be equal, got %v / %v", grpc.Code(e1), e3.(EtcdError).Code()) t.Fatalf("expected them to be equal, got %v / %v", grpc.Code(e1), e3.(EtcdError).Code())
} }
if e1 == e3 { if e1.Error() == e3.Error() {
t.Fatalf("expected 'false', got %T == %T", e1, e3) t.Fatalf("expected %q != %q", e1.Error(), e3.Error())
} }
if grpc.Code(e2) != e3.(EtcdError).Code() { if grpc.Code(e2) != e3.(EtcdError).Code() {
t.Fatalf("expected them to be equal, got %v / %v", grpc.Code(e2), e3.(EtcdError).Code()) t.Fatalf("expected them to be equal, got %v / %v", grpc.Code(e2), e3.(EtcdError).Code())

View File

@ -845,7 +845,7 @@ func TestSnapshot(t *testing.T) {
s := raft.NewMemoryStorage() s := raft.NewMemoryStorage()
s.Append([]raftpb.Entry{{Index: 1}}) s.Append([]raftpb.Entry{{Index: 1}})
st := mockstore.NewRecorder() st := mockstore.NewRecorder()
p := mockstorage.NewStorageRecorder("") p := mockstorage.NewStorageRecorderStream("")
srv := &EtcdServer{ srv := &EtcdServer{
Cfg: &ServerConfig{}, Cfg: &ServerConfig{},
r: raftNode{ r: raftNode{
@ -869,7 +869,7 @@ func TestSnapshot(t *testing.T) {
if !reflect.DeepEqual(gaction[1], testutil.Action{Name: "SaveNoCopy"}) { if !reflect.DeepEqual(gaction[1], testutil.Action{Name: "SaveNoCopy"}) {
t.Errorf("action = %s, want SaveNoCopy", gaction[1]) t.Errorf("action = %s, want SaveNoCopy", gaction[1])
} }
gaction = p.Action() gaction, _ = p.Wait(1)
if len(gaction) != 1 { if len(gaction) != 1 {
t.Fatalf("len(action) = %d, want 1", len(gaction)) t.Fatalf("len(action) = %d, want 1", len(gaction))
} }

View File

@ -191,7 +191,7 @@ func TestV3TxnTooManyOps(t *testing.T) {
} }
_, err := kvc.Txn(context.Background(), txn) _, err := kvc.Txn(context.Background(), txn)
if err != rpctypes.ErrGRPCTooManyOps { if !eqErrGRPC(err, rpctypes.ErrGRPCTooManyOps) {
t.Errorf("#%d: err = %v, want %v", i, err, rpctypes.ErrGRPCTooManyOps) t.Errorf("#%d: err = %v, want %v", i, err, rpctypes.ErrGRPCTooManyOps)
} }
} }
@ -257,7 +257,7 @@ func TestV3TxnDuplicateKeys(t *testing.T) {
for i, tt := range tests { for i, tt := range tests {
txn := &pb.TxnRequest{Success: tt.txnSuccess} txn := &pb.TxnRequest{Success: tt.txnSuccess}
_, err := kvc.Txn(context.Background(), txn) _, err := kvc.Txn(context.Background(), txn)
if err != tt.werr { if !eqErrGRPC(err, tt.werr) {
t.Errorf("#%d: err = %v, want %v", i, err, tt.werr) t.Errorf("#%d: err = %v, want %v", i, err, tt.werr)
} }
} }
@ -468,8 +468,8 @@ func TestV3DeleteRange(t *testing.T) {
} }
} }
// TestV3TxnInvaildRange tests txn // TestV3TxnInvalidRange tests that invalid ranges are rejected in txns.
func TestV3TxnInvaildRange(t *testing.T) { func TestV3TxnInvalidRange(t *testing.T) {
defer testutil.AfterTest(t) defer testutil.AfterTest(t)
clus := NewClusterV3(t, &ClusterConfig{Size: 3}) clus := NewClusterV3(t, &ClusterConfig{Size: 3})
defer clus.Terminate(t) defer clus.Terminate(t)
@ -500,14 +500,14 @@ func TestV3TxnInvaildRange(t *testing.T) {
Request: &pb.RequestOp_RequestRange{ Request: &pb.RequestOp_RequestRange{
RequestRange: rreq}}) RequestRange: rreq}})
if _, err := kvc.Txn(context.TODO(), txn); err != rpctypes.ErrGRPCFutureRev { if _, err := kvc.Txn(context.TODO(), txn); !eqErrGRPC(err, rpctypes.ErrGRPCFutureRev) {
t.Errorf("err = %v, want %v", err, rpctypes.ErrGRPCFutureRev) t.Errorf("err = %v, want %v", err, rpctypes.ErrGRPCFutureRev)
} }
// compacted rev // compacted rev
tv, _ := txn.Success[1].Request.(*pb.RequestOp_RequestRange) tv, _ := txn.Success[1].Request.(*pb.RequestOp_RequestRange)
tv.RequestRange.Revision = 1 tv.RequestRange.Revision = 1
if _, err := kvc.Txn(context.TODO(), txn); err != rpctypes.ErrGRPCCompacted { if _, err := kvc.Txn(context.TODO(), txn); !eqErrGRPC(err, rpctypes.ErrGRPCCompacted) {
t.Errorf("err = %v, want %v", err, rpctypes.ErrGRPCCompacted) t.Errorf("err = %v, want %v", err, rpctypes.ErrGRPCCompacted)
} }
} }
@ -525,7 +525,7 @@ func TestV3TooLargeRequest(t *testing.T) {
preq := &pb.PutRequest{Key: []byte("foo"), Value: largeV} preq := &pb.PutRequest{Key: []byte("foo"), Value: largeV}
_, err := kvc.Put(context.Background(), preq) _, err := kvc.Put(context.Background(), preq)
if err != rpctypes.ErrGRPCRequestTooLarge { if !eqErrGRPC(err, rpctypes.ErrGRPCRequestTooLarge) {
t.Errorf("err = %v, want %v", err, rpctypes.ErrGRPCRequestTooLarge) t.Errorf("err = %v, want %v", err, rpctypes.ErrGRPCRequestTooLarge)
} }
} }
@ -572,14 +572,14 @@ func TestV3StorageQuotaAPI(t *testing.T) {
// test small put that fits in quota // test small put that fits in quota
smallbuf := make([]byte, 512) smallbuf := make([]byte, 512)
if _, err := kvc.Put(context.TODO(), &pb.PutRequest{Key: key, Value: smallbuf}); err != nil { if _, err := kvc.Put(context.TODO(), &pb.PutRequest{Key: key, Value: smallbuf}, grpc.FailFast(false)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
// test big put // test big put
bigbuf := make([]byte, 64*1024) bigbuf := make([]byte, 64*1024)
_, err := kvc.Put(context.TODO(), &pb.PutRequest{Key: key, Value: bigbuf}) _, err := kvc.Put(context.TODO(), &pb.PutRequest{Key: key, Value: bigbuf})
if err == nil || err != rpctypes.ErrGRPCNoSpace { if !eqErrGRPC(err, rpctypes.ErrGRPCNoSpace) {
t.Fatalf("big put got %v, expected %v", err, rpctypes.ErrGRPCNoSpace) t.Fatalf("big put got %v, expected %v", err, rpctypes.ErrGRPCNoSpace)
} }
@ -595,7 +595,7 @@ func TestV3StorageQuotaAPI(t *testing.T) {
txnreq := &pb.TxnRequest{} txnreq := &pb.TxnRequest{}
txnreq.Success = append(txnreq.Success, puttxn) txnreq.Success = append(txnreq.Success, puttxn)
_, txnerr := kvc.Txn(context.TODO(), txnreq) _, txnerr := kvc.Txn(context.TODO(), txnreq)
if txnerr == nil || err != rpctypes.ErrGRPCNoSpace { if !eqErrGRPC(txnerr, rpctypes.ErrGRPCNoSpace) {
t.Fatalf("big txn got %v, expected %v", err, rpctypes.ErrGRPCNoSpace) t.Fatalf("big txn got %v, expected %v", err, rpctypes.ErrGRPCNoSpace)
} }
} }
@ -619,7 +619,7 @@ func TestV3StorageQuotaApply(t *testing.T) {
// test small put still works // test small put still works
smallbuf := make([]byte, 1024) smallbuf := make([]byte, 1024)
_, serr := kvc0.Put(context.TODO(), &pb.PutRequest{Key: key, Value: smallbuf}) _, serr := kvc0.Put(context.TODO(), &pb.PutRequest{Key: key, Value: smallbuf}, grpc.FailFast(false))
if serr != nil { if serr != nil {
t.Fatal(serr) t.Fatal(serr)
} }
@ -694,7 +694,7 @@ func TestV3AlarmDeactivate(t *testing.T) {
key := []byte("abc") key := []byte("abc")
smallbuf := make([]byte, 512) smallbuf := make([]byte, 512)
_, err := kvc.Put(context.TODO(), &pb.PutRequest{Key: key, Value: smallbuf}) _, err := kvc.Put(context.TODO(), &pb.PutRequest{Key: key, Value: smallbuf})
if err == nil && err != rpctypes.ErrGRPCNoSpace { if err == nil && !eqErrGRPC(err, rpctypes.ErrGRPCNoSpace) {
t.Fatalf("put got %v, expected %v", err, rpctypes.ErrGRPCNoSpace) t.Fatalf("put got %v, expected %v", err, rpctypes.ErrGRPCNoSpace)
} }
@ -1048,3 +1048,7 @@ func TestGRPCStreamRequireLeader(t *testing.T) {
t.Errorf("err = %v, want nil", err) t.Errorf("err = %v, want nil", err)
} }
} }
func eqErrGRPC(err1 error, err2 error) bool {
return !(err1 == nil && err2 != nil) || err1.Error() == err2.Error()
}

View File

@ -106,7 +106,7 @@ func TestV3LeaseGrantByID(t *testing.T) {
lresp, err = toGRPC(clus.RandClient()).Lease.LeaseGrant( lresp, err = toGRPC(clus.RandClient()).Lease.LeaseGrant(
context.TODO(), context.TODO(),
&pb.LeaseGrantRequest{ID: 1, TTL: 1}) &pb.LeaseGrantRequest{ID: 1, TTL: 1})
if err != rpctypes.ErrGRPCLeaseExist { if !eqErrGRPC(err, rpctypes.ErrGRPCLeaseExist) {
t.Error(err) t.Error(err)
} }
@ -242,7 +242,7 @@ func TestV3PutOnNonExistLease(t *testing.T) {
badLeaseID := int64(0x12345678) badLeaseID := int64(0x12345678)
putr := &pb.PutRequest{Key: []byte("foo"), Value: []byte("bar"), Lease: badLeaseID} putr := &pb.PutRequest{Key: []byte("foo"), Value: []byte("bar"), Lease: badLeaseID}
_, err := toGRPC(clus.RandClient()).KV.Put(ctx, putr) _, err := toGRPC(clus.RandClient()).KV.Put(ctx, putr)
if err != rpctypes.ErrGRPCLeaseNotFound { if !eqErrGRPC(err, rpctypes.ErrGRPCLeaseNotFound) {
t.Errorf("err = %v, want %v", err, rpctypes.ErrGRPCCompacted) t.Errorf("err = %v, want %v", err, rpctypes.ErrGRPCCompacted)
} }
} }
@ -424,7 +424,7 @@ func leaseExist(t *testing.T, clus *ClusterV3, leaseID int64) bool {
return false return false
} }
if err == rpctypes.ErrGRPCLeaseExist { if eqErrGRPC(err, rpctypes.ErrGRPCLeaseExist) {
return true return true
} }
t.Fatalf("unexpecter error %v", err) t.Fatalf("unexpecter error %v", err)

View File

@ -75,7 +75,6 @@ type Progress struct {
func (pr *Progress) resetState(state ProgressStateType) { func (pr *Progress) resetState(state ProgressStateType) {
pr.Paused = false pr.Paused = false
pr.RecentActive = false
pr.PendingSnapshot = 0 pr.PendingSnapshot = 0
pr.State = state pr.State = state
pr.ins.reset() pr.ins.reset()

13
test
View File

@ -57,7 +57,18 @@ function unit_tests {
} }
function integration_tests { function integration_tests {
echo "Running integration tests..." if [ "$RELEASE_TEST" = "y" ]; then
UPGRADE_VER=$(git tag -l | tail -1)
if [ -n "$MANUAL_VER" ]; then
# in case, we need to test against different version
UPGRADE_VER=$MANUAL_VER
fi
echo "Running release upgrade tests with" etcd $UPGRADE_VER
curl -L https://github.com/coreos/etcd/releases/download/$UPGRADE_VER/etcd-$UPGRADE_VER-linux-amd64.tar.gz -o /tmp/etcd-$UPGRADE_VER-linux-amd64.tar.gz
tar xzvf /tmp/etcd-$UPGRADE_VER-linux-amd64.tar.gz -C /tmp/ --strip-components=1
mv /tmp/etcd ./bin/etcd-last-release
fi;
go test -timeout 10m -v -cpu 1,2,4 $@ ${REPO_PATH}/e2e & go test -timeout 10m -v -cpu 1,2,4 $@ ${REPO_PATH}/e2e &
e2epid="$!" e2epid="$!"
go test -timeout 15m -v -cpu 1,2,4 $@ ${REPO_PATH}/integration & go test -timeout 15m -v -cpu 1,2,4 $@ ${REPO_PATH}/integration &

View File

@ -29,7 +29,7 @@ import (
var ( var (
// MinClusterVersion is the min cluster version this etcd binary is compatible with. // MinClusterVersion is the min cluster version this etcd binary is compatible with.
MinClusterVersion = "2.3.0" MinClusterVersion = "2.3.0"
Version = "3.0.2" Version = "3.0.3"
// Git SHA Value will be set during build // Git SHA Value will be set during build
GitSHA = "Not provided (use ./build instead of go build)" GitSHA = "Not provided (use ./build instead of go build)"