github.com/grafana/pyroscope@v1.18.0/pkg/metastore/client/client.go (about) 1 package metastoreclient 2 3 import ( 4 "context" 5 "fmt" 6 "io" 7 "sync" 8 9 "github.com/go-kit/log" 10 "github.com/go-kit/log/level" 11 "github.com/grafana/dskit/grpcclient" 12 "github.com/grafana/dskit/services" 13 "github.com/hashicorp/go-multierror" 14 "github.com/hashicorp/raft" 15 "google.golang.org/grpc" 16 17 metastorev1 "github.com/grafana/pyroscope/api/gen/proto/go/metastore/v1" 18 "github.com/grafana/pyroscope/pkg/metastore/discovery" 19 "github.com/grafana/pyroscope/pkg/metastore/raftnode/raftnodepb" 20 ) 21 22 // TODO(kolesnikovae): Implement raft leader routing as a grpc load balancer or interceptor. 23 24 type Client struct { 25 service services.Service 26 discovery discovery.Discovery 27 28 mu sync.Mutex 29 leader raft.ServerID 30 servers map[raft.ServerID]*client 31 stopped bool 32 logger log.Logger 33 grpcClientConfig grpcclient.Config 34 dialOpts []grpc.DialOption 35 } 36 37 type client struct { 38 metastorev1.IndexServiceClient 39 metastorev1.CompactionServiceClient 40 metastorev1.MetadataQueryServiceClient 41 metastorev1.TenantServiceClient 42 raftnodepb.RaftNodeServiceClient 43 44 conn io.Closer 45 srv discovery.Server 46 } 47 48 // todo 49 type instance interface { 50 metastorev1.IndexServiceClient 51 metastorev1.MetadataQueryServiceClient 52 metastorev1.TenantServiceClient 53 metastorev1.CompactionServiceClient 54 raftnodepb.RaftNodeServiceClient 55 } 56 57 func New(logger log.Logger, grpcClientConfig grpcclient.Config, d discovery.Discovery, dialOpts ...grpc.DialOption) *Client { 58 var c Client 59 logger = log.With(logger, "component", "metastore-client") 60 c.service = services.NewIdleService(c.starting, c.stopping) 61 c.logger = logger 62 c.grpcClientConfig = grpcClientConfig 63 c.servers = make(map[raft.ServerID]*client) 64 c.discovery = d 65 c.dialOpts = dialOpts 66 67 c.discovery.Subscribe(discovery.UpdateFunc(func(servers []discovery.Server) { 68 c.updateServers(servers) 69 })) 70 return &c 71 } 72 73 func (c *Client) Service() services.Service { return c.service } 74 func (c *Client) starting(context.Context) error { return nil } 75 func (c *Client) stopping(error) error { 76 c.discovery.Close() 77 c.mu.Lock() 78 defer c.mu.Unlock() 79 c.stopped = true 80 var multiErr error 81 for _, srv := range c.servers { 82 err := srv.conn.Close() 83 level.Debug(c.logger).Log("msg", "connection closed", "resolved_address", srv.srv.ResolvedAddress, "raft_address", srv.srv.Raft.Address) 84 if err != nil { 85 multiErr = multierror.Append(multiErr, err) 86 } 87 } 88 c.servers = nil 89 return multiErr 90 } 91 92 func (c *Client) updateServers(servers []discovery.Server) { 93 level.Info(c.logger).Log("msg", "updating servers", "servers", fmt.Sprintf("%+v", servers)) 94 byID := make(map[raft.ServerID][]discovery.Server, len(servers)) 95 for _, srv := range servers { 96 id := stripPort(string(srv.Raft.ID)) 97 byID[id] = append(byID[id], srv) 98 } 99 for k, ss := range byID { 100 if len(ss) > 1 { 101 level.Warn(c.logger).Log("msg", "multiple servers with the same ID", "id", k, "servers", ss) 102 delete(byID, k) 103 } 104 } 105 106 c.mu.Lock() 107 defer c.mu.Unlock() 108 if c.stopped { 109 return 110 } 111 newServers := make(map[raft.ServerID]*client, len(byID)) 112 clientSet := make(map[*client]struct{}) 113 for k, s := range byID { 114 prev, ok := c.servers[k] 115 if ok { 116 if prev.srv == s[0] { 117 newServers[k] = prev 118 clientSet[prev] = struct{}{} 119 level.Debug(c.logger).Log("msg", "server already exists", "id", k, "server", fmt.Sprintf("%+v", s[0])) 120 continue 121 } 122 } 123 cl, err := newClient(s[0], c.grpcClientConfig, c.dialOpts...) 124 if err != nil { 125 level.Error(c.logger).Log("msg", "failed to create client", "err", err) 126 continue 127 } 128 level.Info(c.logger).Log("msg", "new client created", "resolved_address", cl.srv.ResolvedAddress, "raft_address", cl.srv.Raft.Address) 129 newServers[k] = cl 130 clientSet[cl] = struct{}{} 131 } 132 for _, oldClient := range c.servers { 133 if _, ok := clientSet[oldClient]; !ok { 134 err := oldClient.conn.Close() 135 if err != nil { 136 level.Warn(c.logger).Log("msg", "failed to close connection", "err", err) 137 } else { 138 level.Debug(c.logger).Log("msg", "connection closed", "resolved_address", oldClient.srv.ResolvedAddress, "raft_address", oldClient.srv.Raft.Address) 139 } 140 } 141 } 142 c.servers = newServers 143 } 144 145 func newClient(s discovery.Server, config grpcclient.Config, dialOpts ...grpc.DialOption) (*client, error) { 146 address := s.Raft.Address 147 if s.ResolvedAddress != "" { 148 address = raft.ServerAddress(s.ResolvedAddress) 149 } 150 conn, err := dial(string(address), config, dialOpts...) 151 if err != nil { 152 return nil, err 153 } 154 return &client{ 155 IndexServiceClient: metastorev1.NewIndexServiceClient(conn), 156 CompactionServiceClient: metastorev1.NewCompactionServiceClient(conn), 157 MetadataQueryServiceClient: metastorev1.NewMetadataQueryServiceClient(conn), 158 TenantServiceClient: metastorev1.NewTenantServiceClient(conn), 159 RaftNodeServiceClient: raftnodepb.NewRaftNodeServiceClient(conn), 160 conn: conn, 161 srv: s, 162 }, nil 163 } 164 165 func dial(address string, grpcClientConfig grpcclient.Config, dialOpts ...grpc.DialOption) (*grpc.ClientConn, error) { 166 options, err := grpcClientConfig.DialOption(nil, nil, nil) 167 if err != nil { 168 return nil, err 169 } 170 // TODO: https://github.com/grpc/grpc-proto/blob/master/grpc/service_config/service_config.proto 171 options = append(options, grpc.WithDefaultServiceConfig(grpcServiceConfig)) 172 options = append(options, dialOpts...) 173 return grpc.Dial(address, options...) 174 } 175 176 const grpcServiceConfig = `{ 177 "healthCheckConfig": { 178 "serviceName": "pyroscope.metastore" 179 } 180 }`