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  }`