├── .gitignore ├── architecture.png ├── examples ├── proto │ ├── hello.proto │ └── hello.pb.go ├── Readme.md ├── zookeeper │ ├── client │ │ └── main.go │ └── server │ │ └── main.go ├── consul │ ├── client │ │ └── main.go │ └── server │ │ └── main.go ├── etcd │ ├── client │ │ ├── random │ │ │ └── main.go │ │ ├── round-robin │ │ │ └── main.go │ │ └── consistent_hash │ │ │ └── main.go │ └── server │ │ └── main.go └── etcd3 │ ├── client │ └── main.go │ └── server │ └── main.go ├── registry ├── registry.go ├── consul │ ├── resolver.go │ ├── registrar.go │ └── watcher.go ├── zookeeper │ ├── resolver.go │ ├── watcher.go │ └── registrar.go ├── etcd3 │ ├── resolver.go │ ├── registrar.go │ └── watcher.go └── etcd │ ├── resolver.go │ ├── registrar.go │ └── watcher.go ├── common └── common.go ├── balancer ├── random.go ├── round_robin.go ├── least_connetion.go ├── consistent_hash.go └── ketama.go ├── README.md ├── go.mod └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | -------------------------------------------------------------------------------- /architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/liyue201/grpc-lb/HEAD/architecture.png -------------------------------------------------------------------------------- /examples/proto/hello.proto: -------------------------------------------------------------------------------- 1 | //protoc --go_out=plugins=grpc:. hello.proto 2 | 3 | syntax = "proto3"; 4 | 5 | package proto; 6 | 7 | message SayReq { 8 | string content = 1; 9 | } 10 | 11 | message SayResp { 12 | string content = 1; 13 | } 14 | 15 | service Test{ 16 | rpc Say(SayReq) returns (SayResp) {} 17 | } 18 | -------------------------------------------------------------------------------- /registry/registry.go: -------------------------------------------------------------------------------- 1 | package registry 2 | 3 | import ( 4 | "google.golang.org/grpc/metadata" 5 | ) 6 | 7 | type ServiceInfo struct { 8 | InstanceId string 9 | Name string 10 | Version string 11 | Address string 12 | Metadata metadata.MD 13 | } 14 | 15 | type Registrar interface { 16 | Register(service *ServiceInfo) error 17 | Unregister(service *ServiceInfo) error 18 | Close() 19 | } 20 | -------------------------------------------------------------------------------- /common/common.go: -------------------------------------------------------------------------------- 1 | package common 2 | 3 | import ( 4 | "google.golang.org/grpc/metadata" 5 | "google.golang.org/grpc/resolver" 6 | "strconv" 7 | ) 8 | 9 | const ( 10 | WeightKey = "weight" 11 | ) 12 | 13 | func GetWeight(addr resolver.Address) int { 14 | if addr.Metadata == nil { 15 | return 1 16 | } 17 | md, ok := addr.Metadata.(*metadata.MD) 18 | if ok { 19 | values := md.Get(WeightKey) 20 | if len(values) > 0 { 21 | weight, err := strconv.Atoi(values[0]) 22 | if err == nil { 23 | return weight 24 | } 25 | } 26 | } 27 | return 1 28 | } 29 | -------------------------------------------------------------------------------- /examples/Readme.md: -------------------------------------------------------------------------------- 1 | install consul 2 | ``` 3 | docker run -d --name consul -p 8500:8500 -e 'CONSUL_LOCAL_CONFIG={"leave_on_terminate": true}' consul agent -server -bootstrap-expect 1 -bind=0.0.0.0 -client=0.0.0.0 4 | ``` 5 | 6 | install zookeeper 7 | ``` 8 | docker run --name zookeeper -p 2189:2181 -d zookeeper:3.4 9 | ``` 10 | 11 | install etcd 12 | ``` 13 | NODE1=10.0.101.68 14 | docker run -d -p 2379:2379 -p 2380:2380 \ 15 | --name etcd \ 16 | quay.io/coreos/etcd:latest \ 17 | /usr/local/bin/etcd --data-dir=/etcd-data --name node1 \ 18 | --initial-advertise-peer-urls http://${NODE1}:2380 \ 19 | --listen-peer-urls http://0.0.0.0:2380 \ 20 | --advertise-client-urls http://${NODE1}:2379 \ 21 | --listen-client-urls http://0.0.0.0:2379 \ 22 | --initial-cluster node1=http://${NODE1}:2380 23 | ``` 24 | -------------------------------------------------------------------------------- /examples/zookeeper/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/liyue201/grpc-lb/balancer" 5 | "github.com/liyue201/grpc-lb/examples/proto" 6 | registry "github.com/liyue201/grpc-lb/registry/zookeeper" 7 | "golang.org/x/net/context" 8 | "google.golang.org/grpc" 9 | "log" 10 | "time" 11 | ) 12 | 13 | func main() { 14 | registry.RegisterResolver("zk", []string{"10.0.101.68:2189"}, "/backend/services", "test", "1.0") 15 | c, err := grpc.Dial("zk:///", grpc.WithInsecure(), grpc.WithBalancerName(balancer.RoundRobin)) 16 | if err != nil { 17 | log.Printf("grpc dial: %s", err) 18 | return 19 | } 20 | defer c.Close() 21 | client := proto.NewTestClient(c) 22 | 23 | for i := 0; i < 5000; i++ { 24 | resp, err := client.Say(context.Background(), &proto.SayReq{Content: "round robin"}) 25 | if err != nil { 26 | log.Println("aa:", err) 27 | time.Sleep(time.Second) 28 | continue 29 | } 30 | time.Sleep(time.Second) 31 | log.Printf(resp.Content) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /examples/consul/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | con_api "github.com/hashicorp/consul/api" 5 | "github.com/liyue201/grpc-lb/balancer" 6 | "github.com/liyue201/grpc-lb/examples/proto" 7 | "github.com/liyue201/grpc-lb/registry/consul" 8 | "golang.org/x/net/context" 9 | "google.golang.org/grpc" 10 | "log" 11 | "time" 12 | ) 13 | 14 | //http://10.0.101.68:8500/v1/agent/services 15 | func main() { 16 | consul.RegisterResolver("consul", &con_api.Config{Address: "http://10.0.101.68:8500"}, "test:1.0") 17 | c, err := grpc.Dial("consul:///", grpc.WithInsecure(), grpc.WithBalancerName(balancer.RoundRobin)) 18 | if err != nil { 19 | log.Printf("grpc dial: %s", err) 20 | return 21 | } 22 | defer c.Close() 23 | 24 | client := proto.NewTestClient(c) 25 | for i := 0; i < 500; i++ { 26 | 27 | resp, err := client.Say(context.Background(), &proto.SayReq{Content: "round robin"}) 28 | if err != nil { 29 | log.Println(err) 30 | time.Sleep(time.Second) 31 | continue 32 | } 33 | time.Sleep(time.Second) 34 | log.Printf(resp.Content) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /examples/etcd/client/random/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | etcd "github.com/coreos/etcd/client" 5 | "github.com/liyue201/grpc-lb/balancer" 6 | "github.com/liyue201/grpc-lb/examples/proto" 7 | registry "github.com/liyue201/grpc-lb/registry/etcd" 8 | "golang.org/x/net/context" 9 | "google.golang.org/grpc" 10 | "log" 11 | "time" 12 | ) 13 | 14 | func main() { 15 | etcdConfg := etcd.Config{ 16 | Endpoints: []string{"http://10.0.101.68:2379"}, 17 | } 18 | registry.RegisterResolver("etcd", etcdConfg, "/backend/services", "test", "1.0") 19 | 20 | c, err := grpc.Dial("etcd:///", grpc.WithInsecure(), grpc.WithBalancerName(balancer.Random)) 21 | if err != nil { 22 | log.Printf("grpc dial: %s", err) 23 | return 24 | } 25 | defer c.Close() 26 | 27 | client := proto.NewTestClient(c) 28 | for i := 0; i < 500; i++ { 29 | 30 | resp, err := client.Say(context.Background(), &proto.SayReq{Content: "round robin"}) 31 | if err != nil { 32 | log.Println(err) 33 | time.Sleep(time.Second) 34 | continue 35 | } 36 | time.Sleep(time.Second) 37 | log.Printf(resp.Content) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/etcd3/client/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | etcd3 "github.com/coreos/etcd/clientv3" 5 | "github.com/liyue201/grpc-lb/balancer" 6 | "github.com/liyue201/grpc-lb/examples/proto" 7 | registry "github.com/liyue201/grpc-lb/registry/etcd3" 8 | "golang.org/x/net/context" 9 | "google.golang.org/grpc" 10 | "log" 11 | "time" 12 | ) 13 | 14 | func main() { 15 | etcdConfg := etcd3.Config{ 16 | Endpoints: []string{"http://10.0.101.68:2379"}, 17 | } 18 | registry.RegisterResolver("etcd3", etcdConfg, "/backend/services", "test", "1.0") 19 | 20 | c, err := grpc.Dial("etcd3:///", grpc.WithInsecure(), grpc.WithBalancerName(balancer.RoundRobin)) 21 | if err != nil { 22 | log.Printf("grpc dial: %s", err) 23 | return 24 | } 25 | defer c.Close() 26 | client := proto.NewTestClient(c) 27 | 28 | for i := 0; i < 500; i++ { 29 | resp, err := client.Say(context.Background(), &proto.SayReq{Content: "round robin"}) 30 | if err != nil { 31 | log.Println("aa:", err) 32 | time.Sleep(time.Second) 33 | continue 34 | } 35 | time.Sleep(time.Second) 36 | log.Printf(resp.Content) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/etcd/client/round-robin/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | etcd "github.com/coreos/etcd/client" 5 | "github.com/liyue201/grpc-lb/balancer" 6 | "github.com/liyue201/grpc-lb/examples/proto" 7 | registry "github.com/liyue201/grpc-lb/registry/etcd" 8 | "golang.org/x/net/context" 9 | "google.golang.org/grpc" 10 | "log" 11 | "time" 12 | ) 13 | 14 | func main() { 15 | etcdConfg := etcd.Config{ 16 | Endpoints: []string{"http://10.0.101.68:2379"}, 17 | } 18 | registry.RegisterResolver("etcd", etcdConfg, "/backend/services", "test", "1.0") 19 | 20 | c, err := grpc.Dial("etcd:///", grpc.WithInsecure(), grpc.WithBalancerName(balancer.RoundRobin)) 21 | if err != nil { 22 | log.Printf("grpc dial: %s", err) 23 | return 24 | } 25 | defer c.Close() 26 | 27 | client := proto.NewTestClient(c) 28 | for i := 0; i < 500; i++ { 29 | 30 | resp, err := client.Say(context.Background(), &proto.SayReq{Content: "round robin"}) 31 | if err != nil { 32 | log.Println(err) 33 | time.Sleep(time.Second) 34 | continue 35 | } 36 | time.Sleep(time.Second) 37 | log.Printf(resp.Content) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /examples/etcd/client/consistent_hash/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | etcd "github.com/coreos/etcd/client" 6 | "github.com/liyue201/grpc-lb/balancer" 7 | "github.com/liyue201/grpc-lb/examples/proto" 8 | registry "github.com/liyue201/grpc-lb/registry/etcd" 9 | "golang.org/x/net/context" 10 | "google.golang.org/grpc" 11 | "log" 12 | "time" 13 | ) 14 | 15 | func main() { 16 | etcdConfg := etcd.Config{ 17 | Endpoints: []string{"http://10.0.101.68:2379"}, 18 | } 19 | balancer.InitConsistentHashBuilder(balancer.DefaultConsistentHashKey) 20 | registry.RegisterResolver("etcd", etcdConfg, "/backend/services", "test", "1.0") 21 | 22 | c, err := grpc.Dial("etcd:///", grpc.WithInsecure(), grpc.WithBalancerName(balancer.ConsistentHash)) 23 | if err != nil { 24 | log.Printf("grpc dial: %s", err) 25 | return 26 | } 27 | defer c.Close() 28 | 29 | client := proto.NewTestClient(c) 30 | for i := 0; i < 100; i++ { 31 | ctx := context.Background() 32 | 33 | hashData := fmt.Sprintf("aaaa %d", i) 34 | resp, err := client.Say(context.WithValue(ctx, balancer.DefaultConsistentHashKey, hashData), 35 | &proto.SayReq{Content: "ketama"}) 36 | if err != nil { 37 | log.Println(err) 38 | time.Sleep(time.Second) 39 | continue 40 | } 41 | log.Printf(resp.Content) 42 | time.Sleep(time.Second) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /registry/consul/resolver.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | con_api "github.com/hashicorp/consul/api" 5 | "google.golang.org/grpc/resolver" 6 | "sync" 7 | ) 8 | 9 | type consulResolver struct { 10 | scheme string 11 | consulConf *con_api.Config 12 | ServiceName string 13 | watcher *ConsulWatcher 14 | cc resolver.ClientConn 15 | wg sync.WaitGroup 16 | } 17 | 18 | func (r *consulResolver) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) { 19 | r.cc = cc 20 | r.watcher = newConsulWatcher(r.ServiceName, r.consulConf) 21 | r.start() 22 | return r, nil 23 | } 24 | 25 | func (r *consulResolver) Scheme() string { 26 | return r.scheme 27 | } 28 | 29 | func (r *consulResolver) start() { 30 | r.wg.Add(1) 31 | go func() { 32 | defer r.wg.Done() 33 | out := r.watcher.Watch() 34 | for addr := range out { 35 | r.cc.UpdateState(resolver.State{Addresses: addr}) 36 | } 37 | }() 38 | } 39 | 40 | func (r *consulResolver) ResolveNow(o resolver.ResolveNowOptions) { 41 | } 42 | 43 | func (r *consulResolver) Close() { 44 | r.watcher.Close() 45 | r.wg.Wait() 46 | } 47 | 48 | func RegisterResolver(scheme string, consulConf *con_api.Config, srvName string) { 49 | resolver.Register(&consulResolver{ 50 | scheme: scheme, 51 | consulConf: consulConf, 52 | ServiceName: srvName, 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /registry/zookeeper/resolver.go: -------------------------------------------------------------------------------- 1 | package zk 2 | 3 | import ( 4 | "google.golang.org/grpc/resolver" 5 | "sync" 6 | ) 7 | 8 | type zkResolver struct { 9 | scheme string 10 | zkServers []string 11 | zkWatchPath string 12 | watcher *Watcher 13 | cc resolver.ClientConn 14 | wg sync.WaitGroup 15 | } 16 | 17 | func (r *zkResolver) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) { 18 | r.cc = cc 19 | var err error 20 | r.watcher, err = newWatcher(r.zkServers, r.zkWatchPath) 21 | if err != nil { 22 | return nil, err 23 | } 24 | r.start() 25 | return r, nil 26 | } 27 | 28 | func (r *zkResolver) Scheme() string { 29 | return r.scheme 30 | } 31 | 32 | func (r *zkResolver) start() { 33 | r.wg.Add(1) 34 | go func() { 35 | defer r.wg.Done() 36 | out := r.watcher.Watch() 37 | if out != nil { 38 | for addr := range out { 39 | r.cc.UpdateState(resolver.State{Addresses: addr}) 40 | } 41 | } 42 | }() 43 | } 44 | 45 | func (r *zkResolver) ResolveNow(o resolver.ResolveNowOptions) { 46 | } 47 | 48 | func (r *zkResolver) Close() { 49 | r.watcher.Close() 50 | r.wg.Wait() 51 | } 52 | 53 | func RegisterResolver(scheme string, zkServers []string, registryDir, srvName, srvVersion string) { 54 | resolver.Register(&zkResolver{ 55 | scheme: scheme, 56 | zkServers: zkServers, 57 | zkWatchPath: registryDir + "/" + srvName + "/" + srvVersion, 58 | }) 59 | } 60 | -------------------------------------------------------------------------------- /registry/etcd3/resolver.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | etcd_cli "github.com/coreos/etcd/clientv3" 5 | "google.golang.org/grpc/resolver" 6 | "sync" 7 | ) 8 | 9 | type etcdResolver struct { 10 | scheme string 11 | etcdConfig etcd_cli.Config 12 | etcdWatchPath string 13 | watcher *Watcher 14 | cc resolver.ClientConn 15 | wg sync.WaitGroup 16 | } 17 | 18 | func (r *etcdResolver) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) { 19 | etcdCli, err := etcd_cli.New(r.etcdConfig) 20 | if err != nil { 21 | return nil, err 22 | } 23 | r.cc = cc 24 | r.watcher = newWatcher(r.etcdWatchPath, etcdCli) 25 | r.start() 26 | return r, nil 27 | } 28 | 29 | func (r *etcdResolver) Scheme() string { 30 | return r.scheme 31 | } 32 | 33 | func (r *etcdResolver) start() { 34 | r.wg.Add(1) 35 | go func() { 36 | defer r.wg.Done() 37 | out := r.watcher.Watch() 38 | for addr := range out { 39 | r.cc.UpdateState(resolver.State{Addresses: addr}) 40 | } 41 | }() 42 | } 43 | 44 | func (r *etcdResolver) ResolveNow(o resolver.ResolveNowOptions) { 45 | } 46 | 47 | func (r *etcdResolver) Close() { 48 | r.watcher.Close() 49 | r.wg.Wait() 50 | } 51 | 52 | func RegisterResolver(scheme string, etcdConfig etcd_cli.Config, registryDir, srvName, srvVersion string) { 53 | resolver.Register(&etcdResolver{ 54 | scheme: scheme, 55 | etcdConfig: etcdConfig, 56 | etcdWatchPath: registryDir + "/" + srvName + "/" + srvVersion, 57 | }) 58 | } 59 | -------------------------------------------------------------------------------- /registry/etcd/resolver.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | etcd_cli "github.com/coreos/etcd/client" 5 | "google.golang.org/grpc/resolver" 6 | "sync" 7 | ) 8 | 9 | type etcdResolver struct { 10 | scheme string 11 | etcdConfig etcd_cli.Config 12 | etcdWatchPath string 13 | watcher *Watcher 14 | target resolver.Target 15 | cc resolver.ClientConn 16 | wg sync.WaitGroup 17 | } 18 | 19 | func (r *etcdResolver) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) { 20 | etcdCli, err := etcd_cli.New(r.etcdConfig) 21 | if err != nil { 22 | return nil, err 23 | } 24 | r.target = target 25 | r.cc = cc 26 | r.watcher = newWatcher(r.etcdWatchPath, etcdCli) 27 | r.start() 28 | return r, nil 29 | } 30 | 31 | func (r *etcdResolver) Scheme() string { 32 | return r.scheme 33 | } 34 | 35 | func (r *etcdResolver) start() { 36 | r.wg.Add(1) 37 | go func() { 38 | defer r.wg.Done() 39 | out := r.watcher.Watch() 40 | for addr := range out { 41 | r.cc.UpdateState(resolver.State{Addresses: addr}) 42 | } 43 | }() 44 | } 45 | 46 | func (r *etcdResolver) ResolveNow(o resolver.ResolveNowOptions) { 47 | } 48 | 49 | func (r *etcdResolver) Close() { 50 | r.watcher.Close() 51 | r.wg.Wait() 52 | } 53 | 54 | func RegisterResolver(scheme string, etcdConfig etcd_cli.Config, registryDir, srvName, srvVersion string) { 55 | resolver.Register(&etcdResolver{ 56 | scheme: scheme, 57 | etcdConfig: etcdConfig, 58 | etcdWatchPath: registryDir + "/" + srvName + "/" + srvVersion, 59 | }) 60 | } 61 | -------------------------------------------------------------------------------- /balancer/random.go: -------------------------------------------------------------------------------- 1 | package balancer 2 | 3 | import ( 4 | "github.com/liyue201/grpc-lb/common" 5 | "google.golang.org/grpc/balancer" 6 | "google.golang.org/grpc/balancer/base" 7 | "google.golang.org/grpc/grpclog" 8 | "math/rand" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | const Random = "random_x" 14 | 15 | // newRandomBuilder creates a new random balancer builder. 16 | func newRandomBuilder() balancer.Builder { 17 | return base.NewBalancerBuilder(Random, &randomPickerBuilder{}, base.Config{HealthCheck: true}) 18 | } 19 | 20 | func init() { 21 | balancer.Register(newRandomBuilder()) 22 | } 23 | 24 | type randomPickerBuilder struct{} 25 | 26 | func (*randomPickerBuilder) Build(buildInfo base.PickerBuildInfo) balancer.Picker { 27 | grpclog.Infof("randomPicker: newPicker called with buildInfo: %v", buildInfo) 28 | if len(buildInfo.ReadySCs) == 0 { 29 | return base.NewErrPicker(balancer.ErrNoSubConnAvailable) 30 | } 31 | var scs []balancer.SubConn 32 | 33 | for subCon, subConnInfo := range buildInfo.ReadySCs { 34 | weight := common.GetWeight(subConnInfo.Address) 35 | for i := 0; i < weight; i++ { 36 | scs = append(scs, subCon) 37 | } 38 | } 39 | return &randomPicker{ 40 | subConns: scs, 41 | rand: rand.New(rand.NewSource(time.Now().Unix())), 42 | } 43 | } 44 | 45 | type randomPicker struct { 46 | subConns []balancer.SubConn 47 | mu sync.Mutex 48 | rand *rand.Rand 49 | } 50 | 51 | func (p *randomPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) { 52 | ret := balancer.PickResult{} 53 | p.mu.Lock() 54 | ret.SubConn = p.subConns[p.rand.Intn(len(p.subConns))] 55 | p.mu.Unlock() 56 | return ret, nil 57 | } 58 | -------------------------------------------------------------------------------- /balancer/round_robin.go: -------------------------------------------------------------------------------- 1 | package balancer 2 | 3 | import ( 4 | "github.com/liyue201/grpc-lb/common" 5 | "google.golang.org/grpc/balancer" 6 | "google.golang.org/grpc/balancer/base" 7 | "google.golang.org/grpc/grpclog" 8 | "math/rand" 9 | "sync" 10 | ) 11 | 12 | const RoundRobin = "round_robin_x" 13 | 14 | // newRoundRobinBuilder creates a new roundrobin balancer builder. 15 | func newRoundRobinBuilder() balancer.Builder { 16 | return base.NewBalancerBuilder(RoundRobin, &roundRobinPickerBuilder{}, base.Config{HealthCheck: true}) 17 | } 18 | 19 | func init() { 20 | balancer.Register(newRoundRobinBuilder()) 21 | } 22 | 23 | type roundRobinPickerBuilder struct{} 24 | 25 | func (*roundRobinPickerBuilder) Build(buildInfo base.PickerBuildInfo) balancer.Picker { 26 | grpclog.Infof("roundrobinPicker: newPicker called with buildInfo: %v", buildInfo) 27 | 28 | if len(buildInfo.ReadySCs) == 0 { 29 | return base.NewErrPicker(balancer.ErrNoSubConnAvailable) 30 | } 31 | var scs []balancer.SubConn 32 | for subConn, subConnInfo := range buildInfo.ReadySCs { 33 | weight := common.GetWeight(subConnInfo.Address) 34 | for i := 0; i < weight; i++ { 35 | scs = append(scs, subConn) 36 | } 37 | } 38 | 39 | return &roundRobinPicker{ 40 | subConns: scs, 41 | next: rand.Intn(len(scs)), 42 | } 43 | } 44 | 45 | type roundRobinPicker struct { 46 | subConns []balancer.SubConn 47 | mu sync.Mutex 48 | next int 49 | } 50 | 51 | func (p *roundRobinPicker) Pick(balancer.PickInfo) (balancer.PickResult, error) { 52 | ret := balancer.PickResult{} 53 | p.mu.Lock() 54 | ret.SubConn = p.subConns[p.next] 55 | p.next = (p.next + 1) % len(p.subConns) 56 | p.mu.Unlock() 57 | return ret, nil 58 | } 59 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # This repo is no longer maintained 2 | 3 | # grpc-lb 4 | 5 | This is a gRPC load balancing library for go. 6 | 7 | ![](/architecture.png) 8 | 9 | ## Feature 10 | - supports Random, RoundRobin, LeastConnection and ConsistentHash strategies. 11 | - supports [etcd](https://github.com/etcd-io/etcd),[consul](https://github.com/consul/consul) and [zookeeper](https://github.com/apache/zookeeper) as a registry. 12 | 13 | ## Example 14 | 15 | ``` go 16 | package main 17 | 18 | import ( 19 | etcd "github.com/coreos/etcd/client" 20 | "github.com/liyue201/grpc-lb/balancer" 21 | "github.com/liyue201/grpc-lb/examples/proto" 22 | registry "github.com/liyue201/grpc-lb/registry/etcd" 23 | "golang.org/x/net/context" 24 | "google.golang.org/grpc" 25 | "log" 26 | "time" 27 | ) 28 | 29 | func main() { 30 | etcdConfg := etcd.Config{ 31 | Endpoints: []string{"http://10.0.101.68:2379"}, 32 | } 33 | registry.RegisterResolver("etcd", etcdConfg, "/backend/services", "test", "1.0") 34 | 35 | c, err := grpc.Dial("etcd:///", grpc.WithInsecure(), grpc.WithBalancerName(balancer.RoundRobin)) 36 | if err != nil { 37 | log.Printf("grpc dial: %s", err) 38 | return 39 | } 40 | defer c.Close() 41 | 42 | client := proto.NewTestClient(c) 43 | for i := 0; i < 500; i++ { 44 | 45 | resp, err := client.Say(context.Background(), &proto.SayReq{Content: "round robin"}) 46 | if err != nil { 47 | log.Println(err) 48 | time.Sleep(time.Second) 49 | continue 50 | } 51 | time.Sleep(time.Second) 52 | log.Printf(resp.Content) 53 | } 54 | } 55 | ``` 56 | see more [examples](/examples) 57 | 58 | 59 | ## Stargazers over time 60 | 61 | [![Stargazers over time](https://starchart.cc/liyue201/grpc-lb.svg)](https://starchart.cc/liyue201/grpc-lb) 62 | 63 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/liyue201/grpc-lb 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/coreos/bbolt v1.3.3 // indirect 7 | github.com/coreos/etcd v3.3.18+incompatible 8 | github.com/coreos/go-semver v0.3.0 // indirect 9 | github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf // indirect 10 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect 11 | github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect 12 | github.com/gogo/protobuf v1.3.1 // indirect 13 | github.com/golang/protobuf v1.4.2 14 | github.com/google/uuid v1.1.1 // indirect 15 | github.com/gorilla/websocket v1.4.1 // indirect 16 | github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 // indirect 17 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect 18 | github.com/grpc-ecosystem/grpc-gateway v1.12.1 // indirect 19 | github.com/hashicorp/consul/api v1.3.0 20 | github.com/jonboulle/clockwork v0.1.0 // indirect 21 | github.com/json-iterator/go v1.1.9 // indirect 22 | github.com/pkg/errors v0.9.1 // indirect 23 | github.com/prometheus/client_golang v1.3.0 // indirect 24 | github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da 25 | github.com/soheilhy/cmux v0.1.4 // indirect 26 | github.com/stretchr/testify v1.5.1 // indirect 27 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect 28 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect 29 | go.etcd.io/bbolt v1.3.3 // indirect 30 | go.uber.org/zap v1.13.0 // indirect 31 | golang.org/x/net v0.0.0-20200707034311-ab3426394381 32 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect 33 | google.golang.org/grpc v1.31.1 34 | google.golang.org/grpc/examples v0.0.0-20200828165940-d8ef479ab79a // indirect 35 | sigs.k8s.io/yaml v1.2.0 // indirect 36 | ) 37 | -------------------------------------------------------------------------------- /balancer/least_connetion.go: -------------------------------------------------------------------------------- 1 | package balancer 2 | 3 | import ( 4 | "google.golang.org/grpc/balancer" 5 | "google.golang.org/grpc/balancer/base" 6 | "google.golang.org/grpc/grpclog" 7 | "math/rand" 8 | "sync" 9 | "sync/atomic" 10 | "time" 11 | ) 12 | 13 | const LeastConnection = "least_connection_x" 14 | 15 | // newLeastConnectionBuilder creates a new leastConnection balancer builder. 16 | func newLeastConnectionBuilder() balancer.Builder { 17 | return base.NewBalancerBuilder(LeastConnection, &leastConnectionPickerBuilder{}, base.Config{HealthCheck: true}) 18 | } 19 | 20 | func init() { 21 | balancer.Register(newLeastConnectionBuilder()) 22 | } 23 | 24 | type leastConnectionPickerBuilder struct{} 25 | 26 | func (*leastConnectionPickerBuilder) Build(buildInfo base.PickerBuildInfo) balancer.Picker { 27 | grpclog.Infof("leastConnectionPicker: newPicker called with buildInfo: %v", buildInfo) 28 | 29 | if len(buildInfo.ReadySCs) == 0 { 30 | return base.NewErrPicker(balancer.ErrNoSubConnAvailable) 31 | } 32 | 33 | var nodes []*Node 34 | for subConn, _ := range buildInfo.ReadySCs { 35 | nodes = append(nodes, &Node{subConn, 0}) 36 | } 37 | 38 | return &leastConnectionPicker{ 39 | nodes: nodes, 40 | rand: rand.New(rand.NewSource(time.Now().Unix())), 41 | } 42 | } 43 | 44 | type Node struct { 45 | balancer.SubConn 46 | inflight int64 47 | } 48 | 49 | type leastConnectionPicker struct { 50 | nodes []*Node 51 | mu sync.Mutex 52 | rand *rand.Rand 53 | } 54 | 55 | func (p *leastConnectionPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) { 56 | ret := balancer.PickResult{} 57 | if len(p.nodes) == 0 { 58 | return ret, balancer.ErrNoSubConnAvailable 59 | } 60 | var node *Node 61 | if len(p.nodes) == 1 { 62 | node = p.nodes[0] 63 | } else { 64 | p.mu.Lock() 65 | a := p.rand.Intn(len(p.nodes)) 66 | b := p.rand.Intn(len(p.nodes)) 67 | p.mu.Unlock() 68 | if a == b { 69 | b = (b + 1) % len(p.nodes) 70 | } 71 | if p.nodes[a].inflight < p.nodes[b].inflight { 72 | node = p.nodes[a] 73 | } else { 74 | node = p.nodes[b] 75 | } 76 | } 77 | atomic.AddInt64(&node.inflight, 1) 78 | 79 | ret.SubConn = node 80 | ret.Done = func(info balancer.DoneInfo) { 81 | atomic.AddInt64(&node.inflight, -1) 82 | } 83 | 84 | return ret, nil 85 | } 86 | -------------------------------------------------------------------------------- /balancer/consistent_hash.go: -------------------------------------------------------------------------------- 1 | package balancer 2 | 3 | import ( 4 | "fmt" 5 | "github.com/liyue201/grpc-lb/common" 6 | "google.golang.org/grpc/balancer" 7 | "google.golang.org/grpc/balancer/base" 8 | "google.golang.org/grpc/grpclog" 9 | ) 10 | 11 | const ConsistentHash = "consistent_hash_x" 12 | 13 | var DefaultConsistentHashKey = "consistent-hash" 14 | 15 | func InitConsistentHashBuilder(consistanceHashKey string) { 16 | balancer.Register(newConsistentHashBuilder(consistanceHashKey)) 17 | } 18 | 19 | // newConsistanceHashBuilder creates a new ConsistanceHash balancer builder. 20 | func newConsistentHashBuilder(consistentHashKey string) balancer.Builder { 21 | return base.NewBalancerBuilder(ConsistentHash, &consistentHashPickerBuilder{consistentHashKey}, base.Config{HealthCheck: true}) 22 | } 23 | 24 | type consistentHashPickerBuilder struct { 25 | consistentHashKey string 26 | } 27 | 28 | func (b *consistentHashPickerBuilder) Build(buildInfo base.PickerBuildInfo) balancer.Picker { 29 | grpclog.Infof("consistentHashPicker: newPicker called with buildInfo: %v", buildInfo) 30 | if len(buildInfo.ReadySCs) == 0 { 31 | return base.NewErrPicker(balancer.ErrNoSubConnAvailable) 32 | } 33 | 34 | picker := &consistentHashPicker{ 35 | subConns: make(map[string]balancer.SubConn), 36 | hash: NewKetama(10, nil), 37 | consistentHashKey: b.consistentHashKey, 38 | } 39 | 40 | for sc, conInfo := range buildInfo.ReadySCs { 41 | weight := common.GetWeight(conInfo.Address) 42 | for i := 0; i < weight; i++ { 43 | node := wrapAddr(conInfo.Address.Addr, i) 44 | picker.hash.Add(node) 45 | picker.subConns[node] = sc 46 | } 47 | } 48 | return picker 49 | } 50 | 51 | type consistentHashPicker struct { 52 | subConns map[string]balancer.SubConn 53 | hash *Ketama 54 | consistentHashKey string 55 | } 56 | 57 | func (p *consistentHashPicker) Pick(info balancer.PickInfo) (balancer.PickResult, error) { 58 | var ret balancer.PickResult 59 | key, ok := info.Ctx.Value(p.consistentHashKey).(string) 60 | if ok { 61 | targetAddr, ok := p.hash.Get(key) 62 | if ok { 63 | ret.SubConn = p.subConns[targetAddr] 64 | } 65 | } 66 | return ret, nil 67 | } 68 | 69 | func wrapAddr(addr string, idx int) string { 70 | return fmt.Sprintf("%s-%d", addr, idx) 71 | } 72 | -------------------------------------------------------------------------------- /examples/zookeeper/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "github.com/liyue201/grpc-lb/common" 7 | "github.com/liyue201/grpc-lb/examples/proto" 8 | "github.com/liyue201/grpc-lb/registry" 9 | "github.com/liyue201/grpc-lb/registry/zookeeper" 10 | "golang.org/x/net/context" 11 | "google.golang.org/grpc" 12 | "google.golang.org/grpc/metadata" 13 | "log" 14 | "net" 15 | "os" 16 | "os/signal" 17 | "sync" 18 | "syscall" 19 | "time" 20 | ) 21 | 22 | var nodeID = flag.String("node", "node1", "node ID") 23 | var port = flag.Int("port", 8080, "listening port") 24 | 25 | type RpcServer struct { 26 | addr string 27 | s *grpc.Server 28 | } 29 | 30 | func NewRpcServer(addr string) *RpcServer { 31 | s := grpc.NewServer() 32 | rs := &RpcServer{ 33 | addr: addr, 34 | s: s, 35 | } 36 | return rs 37 | } 38 | 39 | func (s *RpcServer) Run() { 40 | listener, err := net.Listen("tcp", s.addr) 41 | if err != nil { 42 | log.Printf("failed to listen: %v", err) 43 | return 44 | } 45 | log.Printf("rpc listening on:%s", s.addr) 46 | 47 | proto.RegisterTestServer(s.s, s) 48 | s.s.Serve(listener) 49 | } 50 | 51 | func (s *RpcServer) Stop() { 52 | s.s.GracefulStop() 53 | } 54 | 55 | func (s *RpcServer) Say(ctx context.Context, req *proto.SayReq) (*proto.SayResp, error) { 56 | text := "Hello " + req.Content + ", I am " + *nodeID 57 | log.Println(text) 58 | 59 | return &proto.SayResp{Content: text}, nil 60 | } 61 | 62 | func StartService() { 63 | 64 | service := ®istry.ServiceInfo{ 65 | InstanceId: *nodeID, 66 | Name: "test", 67 | Version: "1.0", 68 | Address: fmt.Sprintf("127.0.0.1:%d", *port), 69 | Metadata: metadata.Pairs(common.WeightKey, "1"), 70 | } 71 | 72 | registrar, err := zk.NewRegistrar( 73 | &zk.Config{ 74 | ZkServers: []string{"10.0.101.68:2189"}, 75 | RegistryDir: "/backend/services", 76 | SessionTimeout: time.Second, 77 | }) 78 | if err != nil { 79 | log.Panic(err) 80 | return 81 | } 82 | server := NewRpcServer(fmt.Sprintf("0.0.0.0:%d", *port)) 83 | wg := sync.WaitGroup{} 84 | 85 | wg.Add(1) 86 | go func() { 87 | server.Run() 88 | wg.Done() 89 | }() 90 | 91 | wg.Add(1) 92 | go func() { 93 | registrar.Register(service) 94 | wg.Done() 95 | }() 96 | 97 | signalChan := make(chan os.Signal, 1) 98 | signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) 99 | <-signalChan 100 | registrar.Unregister(service) 101 | server.Stop() 102 | wg.Wait() 103 | } 104 | 105 | //go run main.go -node node1 -port 28544 106 | //go run main.go -node node2 -port 18562 107 | //go run main.go -node node3 -port 27772 108 | func main() { 109 | flag.Parse() 110 | StartService() 111 | } 112 | -------------------------------------------------------------------------------- /examples/consul/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | capi "github.com/hashicorp/consul/api" 7 | "github.com/liyue201/grpc-lb/common" 8 | "github.com/liyue201/grpc-lb/examples/proto" 9 | "github.com/liyue201/grpc-lb/registry" 10 | "github.com/liyue201/grpc-lb/registry/consul" 11 | "golang.org/x/net/context" 12 | "google.golang.org/grpc" 13 | "google.golang.org/grpc/metadata" 14 | "log" 15 | "net" 16 | "os" 17 | "os/signal" 18 | "sync" 19 | "syscall" 20 | ) 21 | 22 | var nodeID = flag.String("node", "node1", "node ID") 23 | var port = flag.Int("port", 8080, "listening port") 24 | 25 | type RpcServer struct { 26 | addr string 27 | s *grpc.Server 28 | } 29 | 30 | func NewRpcServer(addr string) *RpcServer { 31 | s := grpc.NewServer() 32 | rs := &RpcServer{ 33 | addr: addr, 34 | s: s, 35 | } 36 | return rs 37 | } 38 | 39 | func (s *RpcServer) Run() { 40 | listener, err := net.Listen("tcp", s.addr) 41 | if err != nil { 42 | log.Printf("failed to listen: %v", err) 43 | return 44 | } 45 | log.Printf("rpc listening on:%s", s.addr) 46 | 47 | proto.RegisterTestServer(s.s, s) 48 | s.s.Serve(listener) 49 | } 50 | 51 | func (s *RpcServer) Stop() { 52 | s.s.GracefulStop() 53 | } 54 | 55 | func (s *RpcServer) Say(ctx context.Context, req *proto.SayReq) (*proto.SayResp, error) { 56 | text := "Hello " + req.Content + ", I am " + *nodeID 57 | log.Println(text) 58 | 59 | return &proto.SayResp{Content: text}, nil 60 | } 61 | 62 | func StartService() { 63 | config := &capi.Config{ 64 | Address: "http://10.0.101.68:8500", 65 | } 66 | 67 | service := ®istry.ServiceInfo{ 68 | InstanceId: *nodeID, 69 | Name: "test", 70 | Version: "1.0", 71 | Address: fmt.Sprintf("127.0.0.1:%d", *port), 72 | Metadata: metadata.Pairs(common.WeightKey, "1"), 73 | } 74 | 75 | registry, err := consul.NewRegistrar( 76 | &consul.Config{ 77 | ConsulCfg: config, 78 | Ttl: 5, 79 | }) 80 | if err != nil { 81 | log.Panic(err) 82 | return 83 | } 84 | server := NewRpcServer(fmt.Sprintf("0.0.0.0:%d", *port)) 85 | wg := sync.WaitGroup{} 86 | 87 | wg.Add(1) 88 | go func() { 89 | server.Run() 90 | wg.Done() 91 | }() 92 | wg.Add(1) 93 | go func() { 94 | registry.Register(service) 95 | wg.Done() 96 | }() 97 | 98 | signalChan := make(chan os.Signal, 1) 99 | signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) 100 | <-signalChan 101 | registry.Unregister(service) 102 | server.Stop() 103 | wg.Wait() 104 | } 105 | 106 | //go run main.go -node node1 -port 28544 107 | //go run main.go -node node2 -port 18562 108 | //go run main.go -node node3 -port 27772 109 | func main() { 110 | flag.Parse() 111 | StartService() 112 | } 113 | -------------------------------------------------------------------------------- /examples/etcd3/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | etcdv3 "github.com/coreos/etcd/clientv3" 7 | "github.com/liyue201/grpc-lb/common" 8 | "github.com/liyue201/grpc-lb/examples/proto" 9 | "github.com/liyue201/grpc-lb/registry" 10 | "github.com/liyue201/grpc-lb/registry/etcd3" 11 | "golang.org/x/net/context" 12 | "google.golang.org/grpc" 13 | "google.golang.org/grpc/metadata" 14 | "log" 15 | "net" 16 | "os" 17 | "os/signal" 18 | "sync" 19 | "syscall" 20 | "time" 21 | ) 22 | 23 | var nodeID = flag.String("node", "node1", "node ID") 24 | var port = flag.Int("port", 8080, "listening port") 25 | 26 | type RpcServer struct { 27 | addr string 28 | s *grpc.Server 29 | } 30 | 31 | func NewRpcServer(addr string) *RpcServer { 32 | s := grpc.NewServer() 33 | rs := &RpcServer{ 34 | addr: addr, 35 | s: s, 36 | } 37 | return rs 38 | } 39 | 40 | func (s *RpcServer) Run() { 41 | listener, err := net.Listen("tcp", s.addr) 42 | if err != nil { 43 | log.Printf("failed to listen: %v", err) 44 | return 45 | } 46 | log.Printf("rpc listening on:%s", s.addr) 47 | 48 | proto.RegisterTestServer(s.s, s) 49 | s.s.Serve(listener) 50 | } 51 | 52 | func (s *RpcServer) Stop() { 53 | s.s.GracefulStop() 54 | } 55 | 56 | func (s *RpcServer) Say(ctx context.Context, req *proto.SayReq) (*proto.SayResp, error) { 57 | text := "Hello " + req.Content + ", I am " + *nodeID 58 | log.Println(text) 59 | 60 | return &proto.SayResp{Content: text}, nil 61 | } 62 | 63 | func StartService() { 64 | etcdConfg := etcdv3.Config{ 65 | Endpoints: []string{"http://10.0.101.68:2379"}, 66 | } 67 | 68 | service := ®istry.ServiceInfo{ 69 | InstanceId: *nodeID, 70 | Name: "test", 71 | Version: "1.0", 72 | Address: fmt.Sprintf("127.0.0.1:%d", *port), 73 | Metadata: metadata.Pairs(common.WeightKey, "1"), 74 | } 75 | 76 | registrar, err := etcd.NewRegistrar( 77 | &etcd.Config{ 78 | EtcdConfig: etcdConfg, 79 | RegistryDir: "/backend/services", 80 | Ttl: 10 * time.Second, 81 | }) 82 | if err != nil { 83 | log.Panic(err) 84 | return 85 | } 86 | server := NewRpcServer(fmt.Sprintf("0.0.0.0:%d", *port)) 87 | wg := sync.WaitGroup{} 88 | 89 | wg.Add(1) 90 | go func() { 91 | server.Run() 92 | wg.Done() 93 | }() 94 | 95 | wg.Add(1) 96 | go func() { 97 | registrar.Register(service) 98 | wg.Done() 99 | }() 100 | 101 | signalChan := make(chan os.Signal, 1) 102 | signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) 103 | <-signalChan 104 | registrar.Unregister(service) 105 | server.Stop() 106 | wg.Wait() 107 | } 108 | 109 | //go run main.go -node node1 -port 28544 110 | //go run main.go -node node2 -port 18562 111 | //go run main.go -node node3 -port 27772 112 | func main() { 113 | flag.Parse() 114 | StartService() 115 | } 116 | -------------------------------------------------------------------------------- /examples/etcd/server/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | etcd_cli "github.com/coreos/etcd/client" 7 | "github.com/liyue201/grpc-lb/common" 8 | "github.com/liyue201/grpc-lb/examples/proto" 9 | "github.com/liyue201/grpc-lb/registry" 10 | "github.com/liyue201/grpc-lb/registry/etcd" 11 | "golang.org/x/net/context" 12 | "google.golang.org/grpc" 13 | "google.golang.org/grpc/metadata" 14 | "log" 15 | "net" 16 | "os" 17 | "os/signal" 18 | "sync" 19 | "syscall" 20 | "time" 21 | ) 22 | 23 | var nodeID = flag.String("node", "node1", "node ID") 24 | var port = flag.Int("port", 8080, "listening port") 25 | 26 | type RpcServer struct { 27 | addr string 28 | s *grpc.Server 29 | } 30 | 31 | func NewRpcServer(addr string) *RpcServer { 32 | s := grpc.NewServer() 33 | rs := &RpcServer{ 34 | addr: addr, 35 | s: s, 36 | } 37 | return rs 38 | } 39 | 40 | func (s *RpcServer) Run() { 41 | listener, err := net.Listen("tcp", s.addr) 42 | if err != nil { 43 | log.Printf("failed to listen: %v", err) 44 | return 45 | } 46 | log.Printf("rpc listening on:%s", s.addr) 47 | 48 | proto.RegisterTestServer(s.s, s) 49 | s.s.Serve(listener) 50 | } 51 | 52 | func (s *RpcServer) Stop() { 53 | s.s.GracefulStop() 54 | } 55 | 56 | func (s *RpcServer) Say(ctx context.Context, req *proto.SayReq) (*proto.SayResp, error) { 57 | text := "Hello " + req.Content + ", I am " + *nodeID 58 | log.Println(text) 59 | 60 | return &proto.SayResp{Content: text}, nil 61 | } 62 | 63 | func StartService() { 64 | etcdConfg := etcd_cli.Config{ 65 | Endpoints: []string{"http://10.0.101.68:2379"}, 66 | } 67 | 68 | service := ®istry.ServiceInfo{ 69 | InstanceId: *nodeID, 70 | Name: "test", 71 | Version: "1.0", 72 | Address: fmt.Sprintf("127.0.0.1:%d", *port), 73 | Metadata: metadata.Pairs(common.WeightKey, "1"), 74 | } 75 | 76 | registry, err := etcd.NewRegistrar( 77 | &etcd.Config{ 78 | EtcdConfig: etcdConfg, 79 | RegistryDir: "/backend/services", 80 | Ttl: 10 * time.Second, 81 | }) 82 | if err != nil { 83 | log.Panic(err) 84 | return 85 | } 86 | server := NewRpcServer(fmt.Sprintf("0.0.0.0:%d", *port)) 87 | wg := sync.WaitGroup{} 88 | 89 | wg.Add(1) 90 | go func() { 91 | server.Run() 92 | wg.Done() 93 | }() 94 | 95 | wg.Add(1) 96 | go func() { 97 | registry.Register(service) 98 | wg.Done() 99 | }() 100 | 101 | signalChan := make(chan os.Signal, 1) 102 | signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) 103 | <-signalChan 104 | registry.Unregister(service) 105 | server.Stop() 106 | 107 | wg.Wait() 108 | } 109 | 110 | //go run main.go -node node1 -port 28544 111 | //go run main.go -node node2 -port 18562 112 | //go run main.go -node node3 -port 27772 113 | func main() { 114 | flag.Parse() 115 | StartService() 116 | } 117 | -------------------------------------------------------------------------------- /balancer/ketama.go: -------------------------------------------------------------------------------- 1 | package balancer 2 | 3 | import ( 4 | "hash/fnv" 5 | "sort" 6 | "strconv" 7 | "sync" 8 | ) 9 | 10 | type HashFunc func(data []byte) uint32 11 | 12 | const ( 13 | DefaultReplicas = 10 14 | Salt = "n*@if09g3n" 15 | ) 16 | 17 | func DefaultHash(data []byte) uint32 { 18 | f := fnv.New32() 19 | f.Write(data) 20 | return f.Sum32() 21 | } 22 | 23 | type Ketama struct { 24 | sync.Mutex 25 | hash HashFunc 26 | replicas int 27 | keys []int // Sorted keys 28 | hashMap map[int]string 29 | } 30 | 31 | func NewKetama(replicas int, fn HashFunc) *Ketama { 32 | h := &Ketama{ 33 | replicas: replicas, 34 | hash: fn, 35 | hashMap: make(map[int]string), 36 | } 37 | if h.replicas <= 0 { 38 | h.replicas = DefaultReplicas 39 | } 40 | if h.hash == nil { 41 | h.hash = DefaultHash 42 | } 43 | return h 44 | } 45 | 46 | func (h *Ketama) IsEmpty() bool { 47 | h.Lock() 48 | defer h.Unlock() 49 | 50 | return len(h.keys) == 0 51 | } 52 | 53 | func (h *Ketama) Add(nodes ...string) { 54 | h.Lock() 55 | defer h.Unlock() 56 | 57 | for _, node := range nodes { 58 | for i := 0; i < h.replicas; i++ { 59 | key := int(h.hash([]byte(Salt + strconv.Itoa(i) + node))) 60 | 61 | if _, ok := h.hashMap[key]; !ok { 62 | h.keys = append(h.keys, key) 63 | } 64 | h.hashMap[key] = node 65 | } 66 | } 67 | sort.Ints(h.keys) 68 | } 69 | 70 | func (h *Ketama) Remove(nodes ...string) { 71 | h.Lock() 72 | defer h.Unlock() 73 | 74 | deletedKey := make([]int, 0) 75 | for _, node := range nodes { 76 | for i := 0; i < h.replicas; i++ { 77 | key := int(h.hash([]byte(Salt + strconv.Itoa(i) + node))) 78 | 79 | if _, ok := h.hashMap[key]; ok { 80 | deletedKey = append(deletedKey, key) 81 | delete(h.hashMap, key) 82 | } 83 | } 84 | } 85 | if len(deletedKey) > 0 { 86 | h.deleteKeys(deletedKey) 87 | } 88 | } 89 | 90 | func (h *Ketama) deleteKeys(deletedKeys []int) { 91 | sort.Ints(deletedKeys) 92 | 93 | index := 0 94 | count := 0 95 | for _, key := range deletedKeys { 96 | for ; index < len(h.keys); index++ { 97 | h.keys[index-count] = h.keys[index] 98 | 99 | if key == h.keys[index] { 100 | count++ 101 | index++ 102 | break 103 | } 104 | } 105 | } 106 | 107 | for ; index < len(h.keys); index++ { 108 | h.keys[index-count] = h.keys[index] 109 | } 110 | 111 | h.keys = h.keys[:len(h.keys)-count] 112 | } 113 | 114 | func (h *Ketama) Get(key string) (string, bool) { 115 | if h.IsEmpty() { 116 | return "", false 117 | } 118 | 119 | hash := int(h.hash([]byte(key))) 120 | 121 | h.Lock() 122 | defer h.Unlock() 123 | 124 | idx := sort.Search(len(h.keys), func(i int) bool { 125 | return h.keys[i] >= hash 126 | }) 127 | 128 | if idx == len(h.keys) { 129 | idx = 0 130 | } 131 | str, ok := h.hashMap[h.keys[idx]] 132 | return str, ok 133 | } 134 | -------------------------------------------------------------------------------- /registry/etcd/registrar.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "encoding/json" 5 | etcd_cli "github.com/coreos/etcd/client" 6 | "github.com/liyue201/grpc-lb/registry" 7 | "golang.org/x/net/context" 8 | "google.golang.org/grpc/grpclog" 9 | "sync" 10 | "time" 11 | ) 12 | 13 | type Config struct { 14 | EtcdConfig etcd_cli.Config 15 | RegistryDir string 16 | Ttl time.Duration 17 | } 18 | 19 | type Registrar struct { 20 | sync.RWMutex 21 | conf *Config 22 | keyapi etcd_cli.KeysAPI 23 | canceler map[string]context.CancelFunc 24 | } 25 | 26 | func NewRegistrar(config *Config) (*Registrar, error) { 27 | client, err := etcd_cli.New(config.EtcdConfig) 28 | if err != nil { 29 | return nil, err 30 | } 31 | keyapi := etcd_cli.NewKeysAPI(client) 32 | registry := &Registrar{ 33 | keyapi: keyapi, 34 | conf: config, 35 | canceler: make(map[string]context.CancelFunc), 36 | } 37 | return registry, nil 38 | } 39 | 40 | func (r *Registrar) Register(service *registry.ServiceInfo) error { 41 | val, err := json.Marshal(service) 42 | if err != nil { 43 | return err 44 | } 45 | value := string(val) 46 | ctx, cancel := context.WithCancel(context.Background()) 47 | r.Lock() 48 | r.canceler[service.InstanceId] = cancel 49 | r.Unlock() 50 | 51 | key := r.conf.RegistryDir + "/" + service.Name + "/" + service.Version + "/" + service.InstanceId 52 | 53 | insertFunc := func() error { 54 | _, err := r.keyapi.Get(ctx, key, &etcd_cli.GetOptions{Recursive: true}) 55 | if err != nil { 56 | setopt := &etcd_cli.SetOptions{TTL: r.conf.Ttl, PrevExist: etcd_cli.PrevIgnore} 57 | if _, err := r.keyapi.Set(ctx, key, value, setopt); err != nil { 58 | grpclog.Infof("etcd: set service '%s' ttl to etcd error: %s\n", key, err.Error()) 59 | return err 60 | } 61 | } else { 62 | // refresh set to true for not notifying the watcher 63 | setopt := &etcd_cli.SetOptions{TTL: r.conf.Ttl, PrevExist: etcd_cli.PrevExist, Refresh: true} 64 | if _, err := r.keyapi.Set(ctx, key, "", setopt); err != nil { 65 | grpclog.Infof("etcd: set service '%s' ttl to etcd error: %s\n", key, err.Error()) 66 | return err 67 | } 68 | } 69 | return nil 70 | } 71 | 72 | err = insertFunc() 73 | if err != nil { 74 | return err 75 | } 76 | 77 | ticker := time.NewTicker(r.conf.Ttl / 5) 78 | for { 79 | select { 80 | case <-ticker.C: 81 | insertFunc() 82 | case <-ctx.Done(): 83 | ticker.Stop() 84 | r.keyapi.Delete(context.Background(), key, &etcd_cli.DeleteOptions{Recursive: true}) 85 | return nil 86 | } 87 | } 88 | return nil 89 | } 90 | 91 | func (r *Registrar) Unregister(service *registry.ServiceInfo) error { 92 | r.RLock() 93 | cancel, ok := r.canceler[service.InstanceId] 94 | r.RUnlock() 95 | 96 | if ok { 97 | cancel() 98 | } 99 | return nil 100 | } 101 | 102 | func (r *Registrar) Close() { 103 | 104 | } 105 | -------------------------------------------------------------------------------- /registry/consul/registrar.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | consul "github.com/hashicorp/consul/api" 8 | "github.com/liyue201/grpc-lb/registry" 9 | "google.golang.org/grpc/grpclog" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | type Registrar struct { 15 | sync.RWMutex 16 | client *consul.Client 17 | cfg *Config 18 | canceler map[string]context.CancelFunc 19 | } 20 | 21 | type Config struct { 22 | ConsulCfg *consul.Config 23 | Ttl int //ttl seconds 24 | } 25 | 26 | func NewRegistrar(cfg *Config) (*Registrar, error) { 27 | c, err := consul.NewClient(cfg.ConsulCfg) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return &Registrar{ 32 | canceler: make(map[string]context.CancelFunc), 33 | client: c, 34 | cfg: cfg, 35 | }, nil 36 | } 37 | 38 | func (c *Registrar) Register(service *registry.ServiceInfo) error { 39 | // register service 40 | metadata, err := json.Marshal(service.Metadata) 41 | if err != nil { 42 | return err 43 | } 44 | tags := make([]string, 0) 45 | tags = append(tags, string(metadata)) 46 | 47 | register := func() error { 48 | regis := &consul.AgentServiceRegistration{ 49 | ID: service.InstanceId, 50 | Name: service.Name + ":" + service.Version, 51 | Address: service.Address, 52 | Tags: tags, 53 | Check: &consul.AgentServiceCheck{ 54 | TTL: fmt.Sprintf("%ds", c.cfg.Ttl), 55 | Status: consul.HealthPassing, 56 | DeregisterCriticalServiceAfter: "1m", 57 | }} 58 | err := c.client.Agent().ServiceRegister(regis) 59 | if err != nil { 60 | return fmt.Errorf("register service to consul error: %s\n", err.Error()) 61 | } 62 | return nil 63 | } 64 | 65 | err = register() 66 | if err != nil { 67 | return err 68 | } 69 | ctx, cancel := context.WithCancel(context.Background()) 70 | 71 | c.Lock() 72 | c.canceler[service.InstanceId] = cancel 73 | c.Unlock() 74 | 75 | keepAliveTicker := time.NewTicker(time.Duration(c.cfg.Ttl) * time.Second / 5) 76 | registerTicker := time.NewTicker(time.Minute) 77 | 78 | for { 79 | select { 80 | case <-ctx.Done(): 81 | keepAliveTicker.Stop() 82 | registerTicker.Stop() 83 | c.client.Agent().ServiceDeregister(service.InstanceId) 84 | return nil 85 | case <-keepAliveTicker.C: 86 | err := c.client.Agent().PassTTL("service:"+service.InstanceId, "") 87 | if err != nil { 88 | grpclog.Infof("consul registry check %v.\n", err) 89 | } 90 | case <-registerTicker.C: 91 | err = register() 92 | if err != nil { 93 | grpclog.Infof("consul register service error: %v.\n", err) 94 | } 95 | } 96 | } 97 | 98 | return nil 99 | } 100 | 101 | func (c *Registrar) Unregister(service *registry.ServiceInfo) error { 102 | c.RLock() 103 | cancel, ok := c.canceler[service.InstanceId] 104 | c.RUnlock() 105 | 106 | if ok { 107 | cancel() 108 | } 109 | return nil 110 | } 111 | 112 | func (r *Registrar) Close() { 113 | 114 | } 115 | -------------------------------------------------------------------------------- /registry/consul/watcher.go: -------------------------------------------------------------------------------- 1 | package consul 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "github.com/hashicorp/consul/api" 7 | "github.com/hashicorp/consul/api/watch" 8 | "google.golang.org/grpc/grpclog" 9 | "google.golang.org/grpc/metadata" 10 | "google.golang.org/grpc/resolver" 11 | "sync" 12 | ) 13 | 14 | type ConsulWatcher struct { 15 | sync.RWMutex 16 | consulConf *api.Config 17 | serviceName string 18 | wp *watch.Plan 19 | ctx context.Context 20 | cancel context.CancelFunc 21 | wg sync.WaitGroup 22 | addrs []resolver.Address 23 | addrsChan chan []resolver.Address 24 | } 25 | 26 | func newConsulWatcher(serviceName string, conf *api.Config) *ConsulWatcher { 27 | wp, err := watch.Parse(map[string]interface{}{ 28 | "type": "service", 29 | "service": serviceName, 30 | }) 31 | 32 | if err != nil { 33 | return nil 34 | } 35 | w := &ConsulWatcher{ 36 | serviceName: serviceName, 37 | wp: wp, 38 | consulConf: conf, 39 | addrsChan: make(chan []resolver.Address, 10), 40 | } 41 | wp.Handler = w.handle 42 | 43 | return w 44 | } 45 | 46 | func (w *ConsulWatcher) Close() { 47 | w.wp.Stop() 48 | w.wg.Wait() 49 | close(w.addrsChan) 50 | } 51 | 52 | func (w *ConsulWatcher) Watch() chan []resolver.Address { 53 | w.wg.Add(1) 54 | go func() { 55 | defer w.wg.Done() 56 | w.wp.RunWithConfig(w.consulConf.Address, w.consulConf) 57 | }() 58 | return w.addrsChan 59 | } 60 | 61 | func (w *ConsulWatcher) handle(idx uint64, data interface{}) { 62 | entries, ok := data.([]*api.ServiceEntry) 63 | if !ok { 64 | return 65 | } 66 | if w.wp.IsStopped() { 67 | return 68 | } 69 | addrs := []resolver.Address{} 70 | 71 | for _, e := range entries { 72 | for _, check := range e.Checks { 73 | if check.ServiceID == e.Service.ID { 74 | if check.Status == api.HealthPassing { 75 | md := metadata.MD{} 76 | if len(e.Service.Tags) > 0 { 77 | err := json.Unmarshal([]byte(e.Service.Tags[0]), &md) 78 | if err != nil { 79 | grpclog.Infof("Parse node data error:", err) 80 | } 81 | } 82 | addrs = append(addrs, resolver.Address{Addr: e.Service.Address, Metadata: &md}) 83 | } 84 | break 85 | } 86 | } 87 | } 88 | if !isSameAddrs(w.addrs, addrs) { 89 | w.addrs = addrs 90 | w.addrsChan <- w.cloneAddresses(w.addrs) 91 | } 92 | } 93 | 94 | func (w *ConsulWatcher) cloneAddresses(in []resolver.Address) []resolver.Address { 95 | out := make([]resolver.Address, len(in)) 96 | for i := 0; i < len(in); i++ { 97 | out[i] = in[i] 98 | } 99 | return out 100 | } 101 | 102 | func isSameAddrs(addrs1, addrs2 []resolver.Address) bool { 103 | if len(addrs1) != len(addrs2) { 104 | return false 105 | } 106 | for _, addr1 := range addrs1 { 107 | found := false 108 | for _, addr2 := range addrs2 { 109 | if addr1.Addr == addr2.Addr { 110 | found = true 111 | break 112 | } 113 | } 114 | if !found { 115 | return false 116 | } 117 | } 118 | return true 119 | } 120 | -------------------------------------------------------------------------------- /registry/etcd3/registrar.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | etcd3 "github.com/coreos/etcd/clientv3" 7 | "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" 8 | "github.com/liyue201/grpc-lb/registry" 9 | "golang.org/x/net/context" 10 | "google.golang.org/grpc/grpclog" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | type Registrar struct { 16 | sync.RWMutex 17 | conf *Config 18 | etcd3Client *etcd3.Client 19 | canceler map[string]context.CancelFunc 20 | } 21 | 22 | type Config struct { 23 | EtcdConfig etcd3.Config 24 | RegistryDir string 25 | Ttl time.Duration 26 | } 27 | 28 | func NewRegistrar(conf *Config) (*Registrar, error) { 29 | client, err := etcd3.New(conf.EtcdConfig) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | registry := &Registrar{ 35 | etcd3Client: client, 36 | conf: conf, 37 | canceler: make(map[string]context.CancelFunc), 38 | } 39 | return registry, nil 40 | } 41 | 42 | func (r *Registrar) Register(service *registry.ServiceInfo) error { 43 | val, err := json.Marshal(service) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | key := r.conf.RegistryDir + "/" + service.Name + "/" + service.Version + "/" + service.InstanceId 49 | value := string(val) 50 | ctx, cancel := context.WithCancel(context.Background()) 51 | r.Lock() 52 | r.canceler[service.InstanceId] = cancel 53 | r.Unlock() 54 | 55 | insertFunc := func() error { 56 | resp, err := r.etcd3Client.Grant(ctx, int64(r.conf.Ttl/time.Second)) 57 | if err != nil { 58 | fmt.Printf("[Register] %v\n", err.Error()) 59 | return err 60 | } 61 | _, err = r.etcd3Client.Get(ctx, key) 62 | if err != nil { 63 | if err == rpctypes.ErrKeyNotFound { 64 | if _, err := r.etcd3Client.Put(ctx, key, value, etcd3.WithLease(resp.ID)); err != nil { 65 | grpclog.Infof("grpclb: set key '%s' with ttl to etcd3 failed: %s", key, err.Error()) 66 | } 67 | } else { 68 | grpclog.Infof("grpclb: key '%s' connect to etcd3 failed: %s", key, err.Error()) 69 | } 70 | return err 71 | } else { 72 | // refresh set to true for not notifying the watcher 73 | if _, err := r.etcd3Client.Put(ctx, key, value, etcd3.WithLease(resp.ID)); err != nil { 74 | grpclog.Infof("grpclb: refresh key '%s' with ttl to etcd3 failed: %s", key, err.Error()) 75 | return err 76 | } 77 | } 78 | return nil 79 | } 80 | 81 | err = insertFunc() 82 | if err != nil { 83 | return err 84 | } 85 | 86 | ticker := time.NewTicker(r.conf.Ttl / 5) 87 | for { 88 | select { 89 | case <-ticker.C: 90 | insertFunc() 91 | case <-ctx.Done(): 92 | ticker.Stop() 93 | if _, err := r.etcd3Client.Delete(context.Background(), key); err != nil { 94 | grpclog.Infof("grpclb: deregister '%s' failed: %s", key, err.Error()) 95 | } 96 | return nil 97 | } 98 | } 99 | 100 | return nil 101 | } 102 | 103 | func (r *Registrar) Unregister(service *registry.ServiceInfo) error { 104 | r.RLock() 105 | cancel, ok := r.canceler[service.InstanceId] 106 | r.RUnlock() 107 | 108 | if ok { 109 | cancel() 110 | } 111 | return nil 112 | } 113 | 114 | func (r *Registrar) Close() { 115 | r.etcd3Client.Close() 116 | } 117 | -------------------------------------------------------------------------------- /registry/zookeeper/watcher.go: -------------------------------------------------------------------------------- 1 | package zk 2 | 3 | import ( 4 | "encoding/json" 5 | "github.com/liyue201/grpc-lb/registry" 6 | "github.com/samuel/go-zookeeper/zk" 7 | "google.golang.org/grpc/grpclog" 8 | "google.golang.org/grpc/resolver" 9 | "strings" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | type Watcher struct { 15 | zkServers []string 16 | conn *zk.Conn 17 | path string 18 | addrs []resolver.Address 19 | wg sync.WaitGroup 20 | } 21 | 22 | func newWatcher(zkServers []string, path string) (*Watcher, error) { 23 | w := &Watcher{ 24 | zkServers: zkServers, 25 | path: path, 26 | } 27 | c, _, err := zk.Connect(zkServers, time.Second*15) 28 | if err != nil { 29 | return nil, err 30 | } 31 | w.conn = c 32 | return w, nil 33 | } 34 | 35 | func (w *Watcher) Watch() chan []resolver.Address { 36 | if exist, _, _ := w.conn.Exists(w.path); !exist { 37 | err := w.createPath(w.path) 38 | if err != nil { 39 | grpclog.Infof("Watcher create path error, %v", err) 40 | return nil 41 | } 42 | } 43 | addrChan := make(chan []resolver.Address, 10) 44 | w.wg.Add(1) 45 | go func() { 46 | defer func() { 47 | w.wg.Done() 48 | close(addrChan) 49 | }() 50 | for { 51 | children, _, eventCh, err := w.conn.ChildrenW(w.path) 52 | if err != nil { 53 | grpclog.Errorf("Watcher ChildrenW: %v", err.Error()) 54 | continue 55 | } 56 | 57 | addrs := []resolver.Address{} 58 | for _, child := range children { 59 | data, _, err := w.conn.Get(w.path + "/" + child) 60 | if err != nil { 61 | continue 62 | } 63 | nodeData := registry.ServiceInfo{} 64 | err = json.Unmarshal(data, &nodeData) 65 | if err != nil { 66 | continue 67 | } 68 | addrs = append(addrs, resolver.Address{Addr: nodeData.Address, Metadata: &nodeData.Metadata}) 69 | } 70 | 71 | if !isSameAddrs(w.addrs, addrs) { 72 | w.addrs = addrs 73 | addrChan <- w.cloneAddresses(addrs) 74 | } 75 | for range eventCh { 76 | //do nothing 77 | } 78 | } 79 | }() 80 | return addrChan 81 | } 82 | 83 | func (w *Watcher) createPath(path string) error { 84 | znodes := strings.Split(path, "/") 85 | var onepath string 86 | for _, znode := range znodes { 87 | if len(znode) == 0 { 88 | continue 89 | } 90 | onepath = onepath + "/" + znode 91 | exists, _, _ := w.conn.Exists(onepath) 92 | if exists { 93 | continue 94 | } 95 | err := createtNode(w.conn, onepath) 96 | if err != nil { 97 | return err 98 | } 99 | } 100 | return nil 101 | } 102 | 103 | func (w *Watcher) Close() { 104 | w.conn.Close() 105 | w.wg.Wait() 106 | } 107 | 108 | func (w *Watcher) cloneAddresses(in []resolver.Address) []resolver.Address { 109 | out := make([]resolver.Address, len(in)) 110 | for i := 0; i < len(in); i++ { 111 | out[i] = in[i] 112 | } 113 | return out 114 | } 115 | 116 | func isSameAddrs(addrs1, addrs2 []resolver.Address) bool { 117 | if len(addrs1) != len(addrs2) { 118 | return false 119 | } 120 | for _, addr1 := range addrs1 { 121 | found := false 122 | for _, addr2 := range addrs2 { 123 | if addr1.Addr == addr2.Addr { 124 | found = true 125 | break 126 | } 127 | } 128 | if !found { 129 | return false 130 | } 131 | } 132 | return true 133 | } 134 | -------------------------------------------------------------------------------- /registry/zookeeper/registrar.go: -------------------------------------------------------------------------------- 1 | package zk 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "errors" 7 | "github.com/liyue201/grpc-lb/registry" 8 | "github.com/samuel/go-zookeeper/zk" 9 | "google.golang.org/grpc/grpclog" 10 | "strings" 11 | "sync" 12 | "time" 13 | ) 14 | 15 | type Config struct { 16 | ZkServers []string 17 | RegistryDir string 18 | SessionTimeout time.Duration 19 | } 20 | 21 | type Registrar struct { 22 | sync.RWMutex 23 | conf *Config 24 | conn *zk.Conn 25 | canceler map[string]context.CancelFunc 26 | } 27 | 28 | func NewRegistrar(conf *Config) (*Registrar, error) { 29 | reg := &Registrar{ 30 | conf: conf, 31 | canceler: make(map[string]context.CancelFunc), 32 | } 33 | c, err := connect(conf.ZkServers, conf.SessionTimeout) 34 | if err != nil { 35 | return nil, err 36 | } 37 | reg.conn = c 38 | return reg, nil 39 | } 40 | 41 | func connect(zkServers []string, sessionTimeout time.Duration) (*zk.Conn, error) { 42 | c, event, err := zk.Connect(zkServers, sessionTimeout) 43 | if err != nil { 44 | return nil, err 45 | } 46 | timer := time.NewTimer(time.Second * 10) 47 | for { 48 | select { 49 | case e := <-event: 50 | if e.State == zk.StateConnected { 51 | return c, nil 52 | } 53 | case <-timer.C: 54 | return nil, errors.New("connect zk timeout") 55 | } 56 | } 57 | } 58 | 59 | // create node one by one 60 | // zk not support "mkdir -p" 61 | func (r *Registrar) register(path string, nodeInfo string) error { 62 | znodes := strings.Split(path, "/") 63 | var onepath string 64 | for i, znode := range znodes { 65 | if len(znode) == 0 { 66 | continue 67 | } 68 | onepath = onepath + "/" + znode 69 | exists, _, _ := r.conn.Exists(onepath) 70 | if exists { 71 | continue 72 | } 73 | var err error 74 | if i != len(znodes)-1 { 75 | err = createtNode(r.conn, onepath) 76 | } else { 77 | err = createTemporaryNode(r.conn, onepath, nodeInfo) 78 | } 79 | if err != nil { 80 | return err 81 | } 82 | } 83 | return nil 84 | } 85 | 86 | func (r *Registrar) Register(service *registry.ServiceInfo) error { 87 | path := r.conf.RegistryDir + "/" + service.Name + "/" + service.Version + "/" + service.InstanceId 88 | data, _ := json.Marshal(service) 89 | err := r.register(path, string(data)) 90 | if err != nil { 91 | return err 92 | } 93 | 94 | ctx, cancel := context.WithCancel(context.Background()) 95 | r.Lock() 96 | r.canceler[service.InstanceId] = cancel 97 | r.Unlock() 98 | 99 | r.keepalive(ctx, path, string(data)) 100 | 101 | return err 102 | } 103 | 104 | func (r *Registrar) keepalive(ctx context.Context, path, value string) { 105 | ticker := time.NewTicker(time.Second) 106 | for { 107 | select { 108 | case <-ctx.Done(): 109 | return 110 | case <-ticker.C: 111 | if r.conn.State() != zk.StateHasSession { 112 | err := r.register(path, value) 113 | if err != nil { 114 | grpclog.Errorf("Registrar register error, %v\n", err.Error()) 115 | } 116 | } 117 | } 118 | } 119 | } 120 | 121 | func (r *Registrar) Unregister(service *registry.ServiceInfo) { 122 | r.RLock() 123 | cancel, ok := r.canceler[service.InstanceId] 124 | r.RUnlock() 125 | if ok { 126 | cancel() 127 | } 128 | } 129 | 130 | func (r *Registrar) Close() { 131 | r.conn.Close() 132 | r.conn = nil 133 | } 134 | 135 | // create temporary node 136 | func createTemporaryNode(conn *zk.Conn, path string, nodeInfo string) error { 137 | _, err := conn.Create(path, []byte(nodeInfo), zk.FlagEphemeral, zk.WorldACL(zk.PermAll)) 138 | return err 139 | } 140 | 141 | // create node 142 | func createtNode(conn *zk.Conn, path string) error { 143 | _, err := conn.Create(path, nil, 0, zk.WorldACL(zk.PermAll)) 144 | return err 145 | } 146 | -------------------------------------------------------------------------------- /registry/etcd3/watcher.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "encoding/json" 5 | etcd3 "github.com/coreos/etcd/clientv3" 6 | "github.com/coreos/etcd/mvcc/mvccpb" 7 | "github.com/liyue201/grpc-lb/registry" 8 | "golang.org/x/net/context" 9 | "google.golang.org/grpc/grpclog" 10 | "google.golang.org/grpc/resolver" 11 | "sync" 12 | ) 13 | 14 | type Watcher struct { 15 | key string 16 | client *etcd3.Client 17 | ctx context.Context 18 | cancel context.CancelFunc 19 | wg sync.WaitGroup 20 | addrs []resolver.Address 21 | } 22 | 23 | func (w *Watcher) Close() { 24 | w.cancel() 25 | } 26 | 27 | func newWatcher(key string, cli *etcd3.Client) *Watcher { 28 | ctx, cancel := context.WithCancel(context.Background()) 29 | w := &Watcher{ 30 | key: key, 31 | client: cli, 32 | ctx: ctx, 33 | cancel: cancel, 34 | } 35 | return w 36 | } 37 | 38 | func (w *Watcher) GetAllAddresses() []resolver.Address { 39 | ret := []resolver.Address{} 40 | 41 | resp, err := w.client.Get(w.ctx, w.key, etcd3.WithPrefix()) 42 | if err == nil { 43 | addrs := extractAddrs(resp) 44 | if len(addrs) > 0 { 45 | for _, addr := range addrs { 46 | v := addr 47 | ret = append(ret, resolver.Address{ 48 | Addr: v.Address, 49 | Metadata: &v.Metadata, 50 | }) 51 | } 52 | } 53 | } 54 | return ret 55 | } 56 | 57 | func (w *Watcher) Watch() chan []resolver.Address { 58 | out := make(chan []resolver.Address, 10) 59 | w.wg.Add(1) 60 | go func() { 61 | defer func() { 62 | close(out) 63 | w.wg.Done() 64 | }() 65 | w.addrs = w.GetAllAddresses() 66 | out <- w.cloneAddresses(w.addrs) 67 | 68 | rch := w.client.Watch(w.ctx, w.key, etcd3.WithPrefix()) 69 | for wresp := range rch { 70 | for _, ev := range wresp.Events { 71 | switch ev.Type { 72 | case mvccpb.PUT: 73 | nodeData := registry.ServiceInfo{} 74 | err := json.Unmarshal([]byte(ev.Kv.Value), &nodeData) 75 | if err != nil { 76 | grpclog.Error("Parse node data error:", err) 77 | continue 78 | } 79 | addr := resolver.Address{Addr: nodeData.Address, Metadata: &nodeData.Metadata} 80 | if w.addAddr(addr) { 81 | out <- w.cloneAddresses(w.addrs) 82 | } 83 | case mvccpb.DELETE: 84 | nodeData := registry.ServiceInfo{} 85 | err := json.Unmarshal([]byte(ev.Kv.Value), &nodeData) 86 | if err != nil { 87 | grpclog.Error("Parse node data error:", err) 88 | continue 89 | } 90 | addr := resolver.Address{Addr: nodeData.Address, Metadata: &nodeData.Metadata} 91 | if w.removeAddr(addr) { 92 | out <- w.cloneAddresses(w.addrs) 93 | } 94 | } 95 | } 96 | } 97 | }() 98 | return out 99 | } 100 | 101 | func extractAddrs(resp *etcd3.GetResponse) []registry.ServiceInfo { 102 | addrs := []registry.ServiceInfo{} 103 | 104 | if resp == nil || resp.Kvs == nil { 105 | return addrs 106 | } 107 | 108 | for i := range resp.Kvs { 109 | if v := resp.Kvs[i].Value; v != nil { 110 | nodeData := registry.ServiceInfo{} 111 | err := json.Unmarshal(v, &nodeData) 112 | if err != nil { 113 | grpclog.Info("Parse node data error:", err) 114 | continue 115 | } 116 | addrs = append(addrs, nodeData) 117 | } 118 | } 119 | return addrs 120 | } 121 | 122 | func (w *Watcher) cloneAddresses(in []resolver.Address) []resolver.Address { 123 | out := make([]resolver.Address, len(in)) 124 | for i := 0; i < len(in); i++ { 125 | out[i] = in[i] 126 | } 127 | return out 128 | } 129 | 130 | func (w *Watcher) addAddr(addr resolver.Address) bool { 131 | for _, v := range w.addrs { 132 | if addr.Addr == v.Addr { 133 | return false 134 | } 135 | } 136 | w.addrs = append(w.addrs, addr) 137 | return true 138 | } 139 | 140 | func (w *Watcher) removeAddr(addr resolver.Address) bool { 141 | for i, v := range w.addrs { 142 | if addr.Addr == v.Addr { 143 | w.addrs = append(w.addrs[:i], w.addrs[i+1:]...) 144 | return true 145 | } 146 | } 147 | return false 148 | } 149 | -------------------------------------------------------------------------------- /registry/etcd/watcher.go: -------------------------------------------------------------------------------- 1 | package etcd 2 | 3 | import ( 4 | "encoding/json" 5 | etcd_cli "github.com/coreos/etcd/client" 6 | "github.com/liyue201/grpc-lb/registry" 7 | "golang.org/x/net/context" 8 | "google.golang.org/grpc/grpclog" 9 | "google.golang.org/grpc/resolver" 10 | "sync" 11 | ) 12 | 13 | type Watcher struct { 14 | key string 15 | keyapi etcd_cli.KeysAPI 16 | watcher etcd_cli.Watcher 17 | ctx context.Context 18 | cancel context.CancelFunc 19 | wg sync.WaitGroup 20 | addrs []resolver.Address 21 | } 22 | 23 | func (w *Watcher) Close() { 24 | w.cancel() 25 | w.wg.Wait() 26 | } 27 | 28 | func newWatcher(key string, cli etcd_cli.Client) *Watcher { 29 | 30 | api := etcd_cli.NewKeysAPI(cli) 31 | watcher := api.Watcher(key, &etcd_cli.WatcherOptions{Recursive: true}) 32 | ctx, cancel := context.WithCancel(context.Background()) 33 | 34 | w := &Watcher{ 35 | key: key, 36 | keyapi: api, 37 | watcher: watcher, 38 | ctx: ctx, 39 | cancel: cancel, 40 | } 41 | return w 42 | } 43 | 44 | func (w *Watcher) GetAllAddresses() []resolver.Address { 45 | resp, _ := w.keyapi.Get(w.ctx, w.key, &etcd_cli.GetOptions{Recursive: true}) 46 | addrs := []resolver.Address{} 47 | for _, n := range resp.Node.Nodes { 48 | serviceInfo := registry.ServiceInfo{} 49 | 50 | err := json.Unmarshal([]byte(n.Value), &serviceInfo) 51 | if err != nil { 52 | grpclog.Infof("Parse node data error:", err) 53 | continue 54 | } 55 | addrs = append(addrs, resolver.Address{ 56 | Addr: serviceInfo.Address, 57 | Metadata: &serviceInfo.Metadata, 58 | }) 59 | } 60 | return addrs 61 | } 62 | 63 | func (w *Watcher) Watch() chan []resolver.Address { 64 | out := make(chan []resolver.Address, 10) 65 | w.wg.Add(1) 66 | go func() { 67 | defer func() { 68 | close(out) 69 | w.wg.Done() 70 | }() 71 | 72 | w.addrs = w.GetAllAddresses() 73 | out <- w.cloneAddresses(w.addrs) 74 | 75 | for { 76 | resp, err := w.watcher.Next(w.ctx) 77 | if err != nil { 78 | grpclog.Errorf("etcd Watcher: %s", err.Error()) 79 | if err == context.Canceled { 80 | return 81 | } 82 | continue 83 | } 84 | if resp.Node.Dir { 85 | continue 86 | } 87 | nodeData := registry.ServiceInfo{} 88 | 89 | if resp.Action == "set" || resp.Action == "create" || resp.Action == "update" || 90 | resp.Action == "delete" || resp.Action == "expire" { 91 | err := json.Unmarshal([]byte(resp.Node.Value), &nodeData) 92 | if err != nil { 93 | grpclog.Infof("Parse node data error:", err) 94 | continue 95 | } 96 | addr := resolver.Address{Addr: nodeData.Address, Metadata: &nodeData.Metadata} 97 | changed := false 98 | switch resp.Action { 99 | case "set", "create": 100 | changed = w.addAddr(addr) 101 | case "update": 102 | changed = w.updateAddr(addr) 103 | case "delete", "expire": 104 | changed = w.removeAddr(addr) 105 | } 106 | if changed { 107 | out <- w.cloneAddresses(w.addrs) 108 | } 109 | } 110 | } 111 | }() 112 | return out 113 | } 114 | 115 | func (w *Watcher) cloneAddresses(in []resolver.Address) []resolver.Address { 116 | out := make([]resolver.Address, len(in)) 117 | for i := 0; i < len(in); i++ { 118 | out[i] = in[i] 119 | } 120 | return out 121 | } 122 | 123 | func (w *Watcher) addAddr(addr resolver.Address) bool { 124 | for _, v := range w.addrs { 125 | if addr.Addr == v.Addr { 126 | return false 127 | } 128 | } 129 | w.addrs = append(w.addrs, addr) 130 | return true 131 | } 132 | 133 | func (w *Watcher) removeAddr(addr resolver.Address) bool { 134 | for i, v := range w.addrs { 135 | if addr.Addr == v.Addr { 136 | w.addrs = append(w.addrs[:i], w.addrs[i+1:]...) 137 | return true 138 | } 139 | } 140 | return false 141 | } 142 | 143 | func (w *Watcher) updateAddr(addr resolver.Address) bool { 144 | for i, v := range w.addrs { 145 | if addr.Addr == v.Addr { 146 | w.addrs[i] = addr 147 | return true 148 | } 149 | } 150 | return false 151 | } 152 | -------------------------------------------------------------------------------- /examples/proto/hello.pb.go: -------------------------------------------------------------------------------- 1 | // Code generated by protoc-gen-go. DO NOT EDIT. 2 | // source: hello.proto 3 | 4 | package proto 5 | 6 | import ( 7 | context "context" 8 | fmt "fmt" 9 | proto "github.com/golang/protobuf/proto" 10 | grpc "google.golang.org/grpc" 11 | codes "google.golang.org/grpc/codes" 12 | status "google.golang.org/grpc/status" 13 | math "math" 14 | ) 15 | 16 | // Reference imports to suppress errors if they are not otherwise used. 17 | var _ = proto.Marshal 18 | var _ = fmt.Errorf 19 | var _ = math.Inf 20 | 21 | // This is a compile-time assertion to ensure that this generated file 22 | // is compatible with the proto package it is being compiled against. 23 | // A compilation error at this line likely means your copy of the 24 | // proto package needs to be updated. 25 | const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package 26 | 27 | type SayReq struct { 28 | Content string `protobuf:"bytes,1,opt,name=content,proto3" json:"content,omitempty"` 29 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 30 | XXX_unrecognized []byte `json:"-"` 31 | XXX_sizecache int32 `json:"-"` 32 | } 33 | 34 | func (m *SayReq) Reset() { *m = SayReq{} } 35 | func (m *SayReq) String() string { return proto.CompactTextString(m) } 36 | func (*SayReq) ProtoMessage() {} 37 | func (*SayReq) Descriptor() ([]byte, []int) { 38 | return fileDescriptor_61ef911816e0a8ce, []int{0} 39 | } 40 | 41 | func (m *SayReq) XXX_Unmarshal(b []byte) error { 42 | return xxx_messageInfo_SayReq.Unmarshal(m, b) 43 | } 44 | func (m *SayReq) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 45 | return xxx_messageInfo_SayReq.Marshal(b, m, deterministic) 46 | } 47 | func (m *SayReq) XXX_Merge(src proto.Message) { 48 | xxx_messageInfo_SayReq.Merge(m, src) 49 | } 50 | func (m *SayReq) XXX_Size() int { 51 | return xxx_messageInfo_SayReq.Size(m) 52 | } 53 | func (m *SayReq) XXX_DiscardUnknown() { 54 | xxx_messageInfo_SayReq.DiscardUnknown(m) 55 | } 56 | 57 | var xxx_messageInfo_SayReq proto.InternalMessageInfo 58 | 59 | func (m *SayReq) GetContent() string { 60 | if m != nil { 61 | return m.Content 62 | } 63 | return "" 64 | } 65 | 66 | type SayResp struct { 67 | Content string `protobuf:"bytes,1,opt,name=content,proto3" json:"content,omitempty"` 68 | XXX_NoUnkeyedLiteral struct{} `json:"-"` 69 | XXX_unrecognized []byte `json:"-"` 70 | XXX_sizecache int32 `json:"-"` 71 | } 72 | 73 | func (m *SayResp) Reset() { *m = SayResp{} } 74 | func (m *SayResp) String() string { return proto.CompactTextString(m) } 75 | func (*SayResp) ProtoMessage() {} 76 | func (*SayResp) Descriptor() ([]byte, []int) { 77 | return fileDescriptor_61ef911816e0a8ce, []int{1} 78 | } 79 | 80 | func (m *SayResp) XXX_Unmarshal(b []byte) error { 81 | return xxx_messageInfo_SayResp.Unmarshal(m, b) 82 | } 83 | func (m *SayResp) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { 84 | return xxx_messageInfo_SayResp.Marshal(b, m, deterministic) 85 | } 86 | func (m *SayResp) XXX_Merge(src proto.Message) { 87 | xxx_messageInfo_SayResp.Merge(m, src) 88 | } 89 | func (m *SayResp) XXX_Size() int { 90 | return xxx_messageInfo_SayResp.Size(m) 91 | } 92 | func (m *SayResp) XXX_DiscardUnknown() { 93 | xxx_messageInfo_SayResp.DiscardUnknown(m) 94 | } 95 | 96 | var xxx_messageInfo_SayResp proto.InternalMessageInfo 97 | 98 | func (m *SayResp) GetContent() string { 99 | if m != nil { 100 | return m.Content 101 | } 102 | return "" 103 | } 104 | 105 | func init() { 106 | proto.RegisterType((*SayReq)(nil), "proto.SayReq") 107 | proto.RegisterType((*SayResp)(nil), "proto.SayResp") 108 | } 109 | 110 | func init() { proto.RegisterFile("hello.proto", fileDescriptor_61ef911816e0a8ce) } 111 | 112 | var fileDescriptor_61ef911816e0a8ce = []byte{ 113 | // 116 bytes of a gzipped FileDescriptorProto 114 | 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0xce, 0x48, 0xcd, 0xc9, 115 | 0xc9, 0xd7, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x53, 0x4a, 0x4a, 0x5c, 0x6c, 0xc1, 116 | 0x89, 0x95, 0x41, 0xa9, 0x85, 0x42, 0x12, 0x5c, 0xec, 0xc9, 0xf9, 0x79, 0x25, 0xa9, 0x79, 0x25, 117 | 0x12, 0x8c, 0x0a, 0x8c, 0x1a, 0x9c, 0x41, 0x30, 0xae, 0x92, 0x32, 0x17, 0x3b, 0x58, 0x4d, 0x71, 118 | 0x01, 0x6e, 0x45, 0x46, 0x7a, 0x5c, 0x2c, 0x21, 0xa9, 0xc5, 0x25, 0x42, 0x6a, 0x5c, 0xcc, 0xc1, 119 | 0x89, 0x95, 0x42, 0xbc, 0x10, 0x6b, 0xf4, 0x20, 0x86, 0x4b, 0xf1, 0x21, 0x73, 0x8b, 0x0b, 0x94, 120 | 0x18, 0x92, 0xd8, 0xc0, 0x02, 0xc6, 0x80, 0x00, 0x00, 0x00, 0xff, 0xff, 0x5f, 0x16, 0xd7, 0x0a, 121 | 0x95, 0x00, 0x00, 0x00, 122 | } 123 | 124 | // Reference imports to suppress errors if they are not otherwise used. 125 | var _ context.Context 126 | var _ grpc.ClientConn 127 | 128 | // This is a compile-time assertion to ensure that this generated file 129 | // is compatible with the grpc package it is being compiled against. 130 | const _ = grpc.SupportPackageIsVersion4 131 | 132 | // TestClient is the client API for Test service. 133 | // 134 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. 135 | type TestClient interface { 136 | Say(ctx context.Context, in *SayReq, opts ...grpc.CallOption) (*SayResp, error) 137 | } 138 | 139 | type testClient struct { 140 | cc *grpc.ClientConn 141 | } 142 | 143 | func NewTestClient(cc *grpc.ClientConn) TestClient { 144 | return &testClient{cc} 145 | } 146 | 147 | func (c *testClient) Say(ctx context.Context, in *SayReq, opts ...grpc.CallOption) (*SayResp, error) { 148 | out := new(SayResp) 149 | err := c.cc.Invoke(ctx, "/proto.Test/Say", in, out, opts...) 150 | if err != nil { 151 | return nil, err 152 | } 153 | return out, nil 154 | } 155 | 156 | // TestServer is the server API for Test service. 157 | type TestServer interface { 158 | Say(context.Context, *SayReq) (*SayResp, error) 159 | } 160 | 161 | // UnimplementedTestServer can be embedded to have forward compatible implementations. 162 | type UnimplementedTestServer struct { 163 | } 164 | 165 | func (*UnimplementedTestServer) Say(ctx context.Context, req *SayReq) (*SayResp, error) { 166 | return nil, status.Errorf(codes.Unimplemented, "method Say not implemented") 167 | } 168 | 169 | func RegisterTestServer(s *grpc.Server, srv TestServer) { 170 | s.RegisterService(&_Test_serviceDesc, srv) 171 | } 172 | 173 | func _Test_Say_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { 174 | in := new(SayReq) 175 | if err := dec(in); err != nil { 176 | return nil, err 177 | } 178 | if interceptor == nil { 179 | return srv.(TestServer).Say(ctx, in) 180 | } 181 | info := &grpc.UnaryServerInfo{ 182 | Server: srv, 183 | FullMethod: "/proto.Test/Say", 184 | } 185 | handler := func(ctx context.Context, req interface{}) (interface{}, error) { 186 | return srv.(TestServer).Say(ctx, req.(*SayReq)) 187 | } 188 | return interceptor(ctx, in, info, handler) 189 | } 190 | 191 | var _Test_serviceDesc = grpc.ServiceDesc{ 192 | ServiceName: "proto.Test", 193 | HandlerType: (*TestServer)(nil), 194 | Methods: []grpc.MethodDesc{ 195 | { 196 | MethodName: "Say", 197 | Handler: _Test_Say_Handler, 198 | }, 199 | }, 200 | Streams: []grpc.StreamDesc{}, 201 | Metadata: "hello.proto", 202 | } 203 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 2 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= 3 | cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= 4 | cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= 5 | cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= 6 | cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= 7 | cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= 8 | cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= 9 | cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= 10 | cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= 11 | cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= 12 | cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= 13 | cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= 14 | cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= 15 | cloud.google.com/go v0.63.0/go.mod h1:GmezbQc7T2snqkEXWfZ0sy0VfkB/ivI2DdtJL2DEmlg= 16 | cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= 17 | cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= 18 | cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= 19 | cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= 20 | cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= 21 | cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= 22 | cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= 23 | cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= 24 | cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= 25 | cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= 26 | cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= 27 | cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= 28 | cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= 29 | cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= 30 | cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= 31 | cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= 32 | cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= 33 | dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= 34 | github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= 35 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 36 | github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= 37 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 38 | github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= 39 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 40 | github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= 41 | github.com/antihax/optional v0.0.0-20180407024304-ca021399b1a6/go.mod h1:V8iCPQYkqmusNa815XgQio277wI47sdRh1dUOLdyC6Q= 42 | github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= 43 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= 44 | github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= 45 | github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= 46 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 47 | github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= 48 | github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= 49 | github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= 50 | github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= 51 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= 52 | github.com/cespare/xxhash/v2 v2.1.1 h1:6MnRN8NT7+YBpUIWxHtefFZOKTAPgGjpQSxqLNn0+qY= 53 | github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= 54 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 55 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 56 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 57 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 58 | github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= 59 | github.com/coreos/bbolt v1.3.3 h1:n6AiVyVRKQFNb6mJlwESEvvLoDyiTzXX7ORAUlkeBdY= 60 | github.com/coreos/bbolt v1.3.3/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= 61 | github.com/coreos/etcd v3.3.18+incompatible h1:Zz1aXgDrFFi1nadh58tA9ktt06cmPTwNNP3dXwIq1lE= 62 | github.com/coreos/etcd v3.3.18+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= 63 | github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= 64 | github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= 65 | github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= 66 | github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= 67 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= 68 | github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= 69 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 70 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 71 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 72 | github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= 73 | github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= 74 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 75 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= 76 | github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= 77 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= 78 | github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= 79 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 80 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 81 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 82 | github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= 83 | github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 84 | github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= 85 | github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= 86 | github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= 87 | github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= 88 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 89 | github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= 90 | github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls= 91 | github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= 92 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= 93 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= 94 | github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 95 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7 h1:5ZkaAPbicIKTF2I64qf5Fh8Aa83Q/dnOafMYV0OMwjA= 96 | github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 97 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= 98 | github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= 99 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 100 | github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= 101 | github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= 102 | github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 103 | github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 104 | github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= 105 | github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= 106 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 107 | github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 108 | github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 109 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 110 | github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I= 111 | github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 112 | github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= 113 | github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= 114 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= 115 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= 116 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= 117 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= 118 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= 119 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= 120 | github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= 121 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= 122 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c h1:964Od4U6p2jUkFxvCydnIczKteheJEzHRToSGK3Bnlw= 123 | github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 124 | github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= 125 | github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= 126 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 127 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 128 | github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= 129 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= 130 | github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= 131 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 132 | github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 133 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 134 | github.com/google/go-cmp v0.5.1 h1:JFrFEBb2xKufg6XkJsJr+WbKb4FQlURi5RUcBveYu9k= 135 | github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 136 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 137 | github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= 138 | github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= 139 | github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 140 | github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= 141 | github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 142 | github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 143 | github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 144 | github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 145 | github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= 146 | github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= 147 | github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= 148 | github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= 149 | github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= 150 | github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= 151 | github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= 152 | github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= 153 | github.com/grpc-ecosystem/go-grpc-middleware v1.1.0 h1:THDBEeQ9xZ8JEaCLyLQqXMMdRqNr0QAUJTIkQAUtFjg= 154 | github.com/grpc-ecosystem/go-grpc-middleware v1.1.0/go.mod h1:f5nM7jw/oeRSadq3xCzHAvxcr8HZnzsqU6ILg/0NiiE= 155 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= 156 | github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= 157 | github.com/grpc-ecosystem/grpc-gateway v1.12.1 h1:zCy2xE9ablevUOrUZc3Dl72Dt+ya2FNAvC2yLYMHzi4= 158 | github.com/grpc-ecosystem/grpc-gateway v1.12.1/go.mod h1:8XEsbTttt/W+VvjtQhLACqCisSPWTxCZ7sBRjU6iH9c= 159 | github.com/hashicorp/consul/api v1.3.0 h1:HXNYlRkkM/t+Y/Yhxtwcy02dlYwIaoxzvxPnS+cqy78= 160 | github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= 161 | github.com/hashicorp/consul/sdk v0.3.0 h1:UOxjlb4xVNF93jak1mzzoBatyFju9nrkxpVwIp/QqxQ= 162 | github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= 163 | github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= 164 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= 165 | github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= 166 | github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= 167 | github.com/hashicorp/go-immutable-radix v1.0.0 h1:AKDB1HM5PWEA7i4nhcpwOrO2byshxBjXVn/J/3+z5/0= 168 | github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= 169 | github.com/hashicorp/go-msgpack v0.5.3 h1:zKjpN5BK/P5lMYrLmBHdBULWbJ0XpYR+7NGzqkZzoD4= 170 | github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= 171 | github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= 172 | github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= 173 | github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI= 174 | github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= 175 | github.com/hashicorp/go-sockaddr v1.0.0 h1:GeH6tui99pF4NJgfnhp+L6+FfobzVW3Ah46sLo0ICXs= 176 | github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= 177 | github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= 178 | github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 179 | github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= 180 | github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= 181 | github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= 182 | github.com/hashicorp/golang-lru v0.5.0 h1:CL2msUPvZTLb5O648aiLNJw3hnBxN2+1Jq8rCOH9wdo= 183 | github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 184 | github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= 185 | github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= 186 | github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= 187 | github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= 188 | github.com/hashicorp/memberlist v0.1.3 h1:EmmoJme1matNzb+hMpDuR/0sbJSUisxyqBGG676r31M= 189 | github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= 190 | github.com/hashicorp/serf v0.8.2 h1:YZ7UKsJv+hKjqGVUUbtE3HNj79Eln2oQ75tniF6iPt0= 191 | github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= 192 | github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= 193 | github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= 194 | github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= 195 | github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 196 | github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 197 | github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= 198 | github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= 199 | github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= 200 | github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= 201 | github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= 202 | github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= 203 | github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= 204 | github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= 205 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 206 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= 207 | github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= 208 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= 209 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 210 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 211 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= 212 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 213 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 214 | github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 215 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 216 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 217 | github.com/miekg/dns v1.0.14 h1:9jZdLNd/P4+SfEJ0TNyxYpsK8N4GtfylBLqtbYN1sbA= 218 | github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= 219 | github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= 220 | github.com/mitchellh/go-homedir v1.0.0 h1:vKb8ShqSby24Yrqr/yDYkuFz8d0WUjys40rvnGC8aR0= 221 | github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= 222 | github.com/mitchellh/go-testing-interface v1.0.0 h1:fzU/JVNcaqHQEcVFAKeR41fkiLdIPrefOvVG1VZ96U0= 223 | github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= 224 | github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= 225 | github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= 226 | github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 227 | github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE= 228 | github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= 229 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 230 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 231 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 232 | github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 233 | github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= 234 | github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= 235 | github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= 236 | github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= 237 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= 238 | github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= 239 | github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 240 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 241 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 242 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 243 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 244 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 245 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 246 | github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= 247 | github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 248 | github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= 249 | github.com/prometheus/client_golang v1.3.0 h1:miYCvYqFXtl/J9FIy8eNpBfYthAEFg+Ys0XyUVEcDsc= 250 | github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= 251 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 252 | github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 253 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 254 | github.com/prometheus/client_model v0.1.0 h1:ElTg5tNp4DqfV7UQjDqv2+RJlNzsDtvNAWccbItceIE= 255 | github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= 256 | github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= 257 | github.com/prometheus/common v0.7.0 h1:L+1lyG48J1zAQXA3RBX/nG/B3gjlHq0zTt2tlbJLyCY= 258 | github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= 259 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 260 | github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= 261 | github.com/prometheus/procfs v0.0.8 h1:+fpWZdT24pJBiqJdAwYBjPSk+5YmQzYNPYzQsdzLkt8= 262 | github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= 263 | github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= 264 | github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= 265 | github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= 266 | github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da h1:p3Vo3i64TCLY7gIfzeQaUJ+kppEO5WQG3cL8iE8tGHU= 267 | github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= 268 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= 269 | github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= 270 | github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= 271 | github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4= 272 | github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= 273 | github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= 274 | github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= 275 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 276 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 277 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 278 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 279 | github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= 280 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 281 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 282 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 283 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= 284 | github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= 285 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= 286 | github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= 287 | github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 288 | github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 289 | github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= 290 | go.etcd.io/bbolt v1.3.3 h1:MUGmc65QhB3pIlaQ5bB4LwqSj6GIonVJXpZiaKNyaKk= 291 | go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= 292 | go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= 293 | go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= 294 | go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 295 | go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 296 | go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= 297 | go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= 298 | go.uber.org/atomic v1.5.0 h1:OI5t8sDa1Or+q8AeE+yKeB/SDYioSHAgcVljj9JIETY= 299 | go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= 300 | go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= 301 | go.uber.org/multierr v1.3.0 h1:sFPn2GLc3poCkfrpIXGhBD2X0CMIo4Q/zSULXrj/+uc= 302 | go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= 303 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEaUSmT1ysygQC7qYo7sG4= 304 | go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= 305 | go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= 306 | go.uber.org/zap v1.13.0 h1:nR6NoDBgAf67s68NhaXbsojM+2gxp3S1hWkHDl27pVU= 307 | go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= 308 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 309 | golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 310 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 311 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529 h1:iMGN4xG0cnqj3t+zOM8wUB0BiPKHEwSxEZCvzcbZuvk= 312 | golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 313 | golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 314 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 315 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 h1:psW17arqaxU48Z5kZ0CQnkZWQJsqcURM6tKiBApRjXI= 316 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 317 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 318 | golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 319 | golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= 320 | golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= 321 | golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= 322 | golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 323 | golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 324 | golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= 325 | golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= 326 | golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= 327 | golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 328 | golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= 329 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 330 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= 331 | golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 332 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 333 | golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 334 | golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 335 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de h1:5hukYrvBGR8/eNkX5mdUezrA6JiaEZDtJb9Ei+1LlBs= 336 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 337 | golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= 338 | golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 339 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b h1:Wh+f8QHJXR411sJR8/vRBTZ7YapZaRvUcLFFJhusH0k= 340 | golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= 341 | golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= 342 | golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= 343 | golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= 344 | golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= 345 | golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 346 | golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= 347 | golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 348 | golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4= 349 | golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 350 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 351 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 352 | golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 353 | golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 354 | golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 355 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 356 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 357 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 358 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 359 | golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 360 | golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 361 | golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 362 | golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 363 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 364 | golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 365 | golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 366 | golang.org/x/net v0.0.0-20191002035440-2ec189313ef0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 367 | golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 368 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa h1:F+8P+gmewFQYRk6JoLQLwjBCTu3mcIURZfNkVweuRKA= 369 | golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 370 | golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 371 | golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 372 | golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 373 | golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 374 | golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 375 | golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 376 | golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 377 | golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 378 | golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 379 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 380 | golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= 381 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 382 | golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 383 | golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 384 | golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 385 | golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= 386 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 387 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 388 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 389 | golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 390 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 391 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e h1:vcxGaoTs7kV8m5Np9uUNQin4BrLOthgV7252N8V+FwY= 392 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 393 | golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 394 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208 h1:qwRHBd0NqMbJxfbotnDhm2ByMI1Shq4Y6oRJo21SGJA= 395 | golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 396 | golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 397 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 398 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 399 | golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 400 | golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 401 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 402 | golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 403 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 404 | golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 405 | golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 406 | golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 407 | golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 408 | golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 409 | golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 410 | golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 411 | golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 412 | golang.org/x/sys v0.0.0-20191220142924-d4481acd189f h1:68K/z8GLUxV76xGSqwTWw2gyk/jwn79LUL43rES2g8o= 413 | golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 414 | golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 415 | golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 416 | golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 417 | golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 418 | golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 419 | golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 420 | golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 421 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 422 | golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 423 | golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 424 | golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 425 | golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 426 | golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 427 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642 h1:B6caxRw+hozq68X2MY7jEpZh/cr4/aHLv9xU8Kkadrw= 428 | golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 429 | golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 430 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 431 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 432 | golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 433 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= 434 | golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= 435 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 436 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 437 | golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 438 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= 439 | golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 440 | golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 441 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 442 | golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 443 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 444 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= 445 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 446 | golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 447 | golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 448 | golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 449 | golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 450 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= 451 | golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 452 | golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 453 | golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= 454 | golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 455 | golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 456 | golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 457 | golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 458 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5 h1:hKsoRgsbwY1NafxrwTs+k64bikrLBkAgPir1TNCj3Zs= 459 | golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 460 | golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 461 | golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 462 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 463 | golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 464 | golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 465 | golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 466 | golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 467 | golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 468 | golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 469 | golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 470 | golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 471 | golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 472 | golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 473 | golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 474 | golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= 475 | golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 476 | golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= 477 | golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= 478 | golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 479 | golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 480 | golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 481 | golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= 482 | golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 483 | golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 484 | golang.org/x/tools v0.0.0-20200806022845-90696ccdc692 h1:fsn47thVa7Ar/TMyXYlZgOoT7M4+kRpb+KpSAqRQx1w= 485 | golang.org/x/tools v0.0.0-20200806022845-90696ccdc692/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= 486 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 487 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 488 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= 489 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 490 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 491 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 492 | google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= 493 | google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= 494 | google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 495 | google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= 496 | google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 497 | google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 498 | google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= 499 | google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 500 | google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 501 | google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 502 | google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 503 | google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= 504 | google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 505 | google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= 506 | google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= 507 | google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= 508 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 509 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 510 | google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= 511 | google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= 512 | google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 513 | google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= 514 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= 515 | google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 516 | google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 517 | google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 518 | google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= 519 | google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 520 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= 521 | google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 522 | google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c h1:hrpEMCZ2O7DR5gC1n2AJGVhrwiEjOi35+jxtIuZpTMo= 523 | google.golang.org/genproto v0.0.0-20190927181202-20e1ac93f88c/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= 524 | google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 525 | google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 526 | google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 527 | google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 528 | google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 529 | google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= 530 | google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= 531 | google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 532 | google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 533 | google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 534 | google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 535 | google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 536 | google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 537 | google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 538 | google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= 539 | google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= 540 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= 541 | google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= 542 | google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 543 | google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 544 | google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98 h1:LCO0fg4kb6WwkXQXRQQgUYsFeFb5taTX5WAx5O/Vt28= 545 | google.golang.org/genproto v0.0.0-20200806141610-86f49bd18e98/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= 546 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 547 | google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= 548 | google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= 549 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 550 | google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= 551 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= 552 | google.golang.org/grpc v1.26.0 h1:2dTRdpdFEEhJYQD8EMLB61nnrzSCTbG38PhqdhvOltg= 553 | google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 554 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 555 | google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= 556 | google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= 557 | google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= 558 | google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 559 | google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 560 | google.golang.org/grpc v1.31.1 h1:SfXqXS5hkufcdZ/mHtYCh53P2b+92WQq/DZcKLgsFRs= 561 | google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= 562 | google.golang.org/grpc/examples v0.0.0-20200828165940-d8ef479ab79a h1:7iRJyssym7732TmOPsstcu7CtG53rDKSiL+yhxbN+5Y= 563 | google.golang.org/grpc/examples v0.0.0-20200828165940-d8ef479ab79a/go.mod h1:Lh55/1hxmVHEkOvSIQ2uj0P12QyOCUNyRwnUlSS13hw= 564 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= 565 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= 566 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= 567 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= 568 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= 569 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 570 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 571 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= 572 | google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= 573 | google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= 574 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 575 | gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= 576 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 577 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= 578 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 579 | gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= 580 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 581 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 582 | gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 583 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 584 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 585 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 586 | honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 587 | honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 588 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= 589 | honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= 590 | honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= 591 | honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 592 | honnef.co/go/tools v0.0.1-2020.1.4 h1:UoveltGrhghAA7ePc+e+QYDHXrBps2PqFZiHkGR/xK8= 593 | honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= 594 | rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= 595 | rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= 596 | rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= 597 | sigs.k8s.io/yaml v1.2.0 h1:kr/MCeFWJWTwyaHoR9c8EjH9OumOmoF9YGiZd7lFm/Q= 598 | sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= 599 | --------------------------------------------------------------------------------