|
|
@@ -15,38 +15,163 @@
|
|
|
package grpcproxy
|
|
|
|
|
|
import (
|
|
|
+ "fmt"
|
|
|
+ "os"
|
|
|
+ "sync"
|
|
|
+
|
|
|
"github.com/coreos/etcd/clientv3"
|
|
|
+ "github.com/coreos/etcd/clientv3/naming"
|
|
|
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
|
|
|
|
|
"golang.org/x/net/context"
|
|
|
+ "golang.org/x/time/rate"
|
|
|
+ "google.golang.org/grpc"
|
|
|
+ gnaming "google.golang.org/grpc/naming"
|
|
|
)
|
|
|
|
|
|
+// allow maximum 1 retry per second
|
|
|
+const resolveRetryRate = 1
|
|
|
+
|
|
|
type clusterProxy struct {
|
|
|
- client *clientv3.Client
|
|
|
+ clus clientv3.Cluster
|
|
|
+ ctx context.Context
|
|
|
+ gr *naming.GRPCResolver
|
|
|
+
|
|
|
+ // advertise client URL
|
|
|
+ advaddr string
|
|
|
+ prefix string
|
|
|
+
|
|
|
+ umu sync.RWMutex
|
|
|
+ umap map[string]gnaming.Update
|
|
|
+}
|
|
|
+
|
|
|
+// NewClusterProxy takes optional prefix to fetch grpc-proxy member endpoints.
|
|
|
+// The returned channel is closed when there is grpc-proxy endpoint registered
|
|
|
+// and the client's context is canceled so the 'register' loop returns.
|
|
|
+func NewClusterProxy(c *clientv3.Client, advaddr string, prefix string) (pb.ClusterServer, <-chan struct{}) {
|
|
|
+ cp := &clusterProxy{
|
|
|
+ clus: c.Cluster,
|
|
|
+ ctx: c.Ctx(),
|
|
|
+ gr: &naming.GRPCResolver{Client: c},
|
|
|
+
|
|
|
+ advaddr: advaddr,
|
|
|
+ prefix: prefix,
|
|
|
+ umap: make(map[string]gnaming.Update),
|
|
|
+ }
|
|
|
+
|
|
|
+ donec := make(chan struct{})
|
|
|
+ if advaddr != "" && prefix != "" {
|
|
|
+ go func() {
|
|
|
+ defer close(donec)
|
|
|
+ cp.resolve(prefix)
|
|
|
+ }()
|
|
|
+ return cp, donec
|
|
|
+ }
|
|
|
+
|
|
|
+ close(donec)
|
|
|
+ return cp, donec
|
|
|
}
|
|
|
|
|
|
-func NewClusterProxy(c *clientv3.Client) pb.ClusterServer {
|
|
|
- return &clusterProxy{
|
|
|
- client: c,
|
|
|
+func (cp *clusterProxy) resolve(prefix string) {
|
|
|
+ rm := rate.NewLimiter(rate.Limit(resolveRetryRate), resolveRetryRate)
|
|
|
+ for rm.Wait(cp.ctx) == nil {
|
|
|
+ wa, err := cp.gr.Resolve(prefix)
|
|
|
+ if err != nil {
|
|
|
+ plog.Warningf("failed to resolve %q (%v)", prefix, err)
|
|
|
+ continue
|
|
|
+ }
|
|
|
+ cp.monitor(wa)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func (cp *clusterProxy) monitor(wa gnaming.Watcher) {
|
|
|
+ for cp.ctx.Err() == nil {
|
|
|
+ ups, err := wa.Next()
|
|
|
+ if err != nil {
|
|
|
+ plog.Warningf("clusterProxy watcher error (%v)", err)
|
|
|
+ if grpc.ErrorDesc(err) == naming.ErrWatcherClosed.Error() {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ cp.umu.Lock()
|
|
|
+ for i := range ups {
|
|
|
+ switch ups[i].Op {
|
|
|
+ case gnaming.Add:
|
|
|
+ cp.umap[ups[i].Addr] = *ups[i]
|
|
|
+ case gnaming.Delete:
|
|
|
+ delete(cp.umap, ups[i].Addr)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ cp.umu.Unlock()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
func (cp *clusterProxy) MemberAdd(ctx context.Context, r *pb.MemberAddRequest) (*pb.MemberAddResponse, error) {
|
|
|
- conn := cp.client.ActiveConnection()
|
|
|
- return pb.NewClusterClient(conn).MemberAdd(ctx, r)
|
|
|
+ mresp, err := cp.clus.MemberAdd(ctx, r.PeerURLs)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ resp := (pb.MemberAddResponse)(*mresp)
|
|
|
+ return &resp, err
|
|
|
}
|
|
|
|
|
|
func (cp *clusterProxy) MemberRemove(ctx context.Context, r *pb.MemberRemoveRequest) (*pb.MemberRemoveResponse, error) {
|
|
|
- conn := cp.client.ActiveConnection()
|
|
|
- return pb.NewClusterClient(conn).MemberRemove(ctx, r)
|
|
|
+ mresp, err := cp.clus.MemberRemove(ctx, r.ID)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ resp := (pb.MemberRemoveResponse)(*mresp)
|
|
|
+ return &resp, err
|
|
|
}
|
|
|
|
|
|
func (cp *clusterProxy) MemberUpdate(ctx context.Context, r *pb.MemberUpdateRequest) (*pb.MemberUpdateResponse, error) {
|
|
|
- conn := cp.client.ActiveConnection()
|
|
|
- return pb.NewClusterClient(conn).MemberUpdate(ctx, r)
|
|
|
+ mresp, err := cp.clus.MemberUpdate(ctx, r.ID, r.PeerURLs)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ resp := (pb.MemberUpdateResponse)(*mresp)
|
|
|
+ return &resp, err
|
|
|
}
|
|
|
|
|
|
+func (cp *clusterProxy) membersFromUpdates() ([]*pb.Member, error) {
|
|
|
+ cp.umu.RLock()
|
|
|
+ defer cp.umu.RUnlock()
|
|
|
+ mbs := make([]*pb.Member, 0, len(cp.umap))
|
|
|
+ for addr, upt := range cp.umap {
|
|
|
+ m, err := decodeMeta(fmt.Sprint(upt.Metadata))
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ mbs = append(mbs, &pb.Member{Name: m.Name, ClientURLs: []string{addr}})
|
|
|
+ }
|
|
|
+ return mbs, nil
|
|
|
+}
|
|
|
+
|
|
|
+// MemberList wraps member list API with following rules:
|
|
|
+// - If 'advaddr' is not empty and 'prefix' is not empty, return registered member lists via resolver
|
|
|
+// - If 'advaddr' is not empty and 'prefix' is not empty and registered grpc-proxy members haven't been fetched, return the 'advaddr'
|
|
|
+// - If 'advaddr' is not empty and 'prefix' is empty, return 'advaddr' without forcing it to 'register'
|
|
|
+// - If 'advaddr' is empty, forward to member list API
|
|
|
func (cp *clusterProxy) MemberList(ctx context.Context, r *pb.MemberListRequest) (*pb.MemberListResponse, error) {
|
|
|
- conn := cp.client.ActiveConnection()
|
|
|
- return pb.NewClusterClient(conn).MemberList(ctx, r)
|
|
|
+ if cp.advaddr != "" {
|
|
|
+ if cp.prefix != "" {
|
|
|
+ mbs, err := cp.membersFromUpdates()
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ if len(mbs) > 0 {
|
|
|
+ return &pb.MemberListResponse{Members: mbs}, nil
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // prefix is empty or no grpc-proxy members haven't been registered
|
|
|
+ hostname, _ := os.Hostname()
|
|
|
+ return &pb.MemberListResponse{Members: []*pb.Member{{Name: hostname, ClientURLs: []string{cp.advaddr}}}}, nil
|
|
|
+ }
|
|
|
+ mresp, err := cp.clus.MemberList(ctx)
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ resp := (pb.MemberListResponse)(*mresp)
|
|
|
+ return &resp, err
|
|
|
}
|