github.com/thanos-io/thanos@v0.32.5/pkg/rules/proxy.go (about)

     1  // Copyright (c) The Thanos Authors.
     2  // Licensed under the Apache License 2.0.
     3  
     4  package rules
     5  
     6  import (
     7  	"context"
     8  	"io"
     9  
    10  	"github.com/go-kit/log"
    11  	"github.com/go-kit/log/level"
    12  	"github.com/pkg/errors"
    13  	"golang.org/x/sync/errgroup"
    14  	"google.golang.org/grpc"
    15  	"google.golang.org/grpc/codes"
    16  	"google.golang.org/grpc/status"
    17  
    18  	"github.com/thanos-io/thanos/pkg/rules/rulespb"
    19  	"github.com/thanos-io/thanos/pkg/store/storepb"
    20  	"github.com/thanos-io/thanos/pkg/tracing"
    21  )
    22  
    23  // Proxy implements rulespb.Rules gRPC that fanouts requests to given rulespb.Rules and deduplication on the way.
    24  type Proxy struct {
    25  	logger log.Logger
    26  	rules  func() []rulespb.RulesClient
    27  }
    28  
    29  func RegisterRulesServer(rulesSrv rulespb.RulesServer) func(*grpc.Server) {
    30  	return func(s *grpc.Server) {
    31  		rulespb.RegisterRulesServer(s, rulesSrv)
    32  	}
    33  }
    34  
    35  // NewProxy returns new rules.Proxy.
    36  func NewProxy(logger log.Logger, rules func() []rulespb.RulesClient) *Proxy {
    37  	return &Proxy{
    38  		logger: logger,
    39  		rules:  rules,
    40  	}
    41  }
    42  
    43  func (s *Proxy) Rules(req *rulespb.RulesRequest, srv rulespb.Rules_RulesServer) error {
    44  	span, ctx := tracing.StartSpan(srv.Context(), "proxy_rules")
    45  	defer span.Finish()
    46  
    47  	var (
    48  		g, gctx  = errgroup.WithContext(ctx)
    49  		respChan = make(chan *rulespb.RuleGroup, 10)
    50  		groups   []*rulespb.RuleGroup
    51  		err      error
    52  	)
    53  
    54  	for _, rulesClient := range s.rules() {
    55  		rs := &rulesStream{
    56  			client:  rulesClient,
    57  			request: req,
    58  			channel: respChan,
    59  			server:  srv,
    60  		}
    61  		g.Go(func() error { return rs.receive(gctx) })
    62  	}
    63  
    64  	go func() {
    65  		_ = g.Wait()
    66  		close(respChan)
    67  	}()
    68  
    69  	for resp := range respChan {
    70  		groups = append(groups, resp)
    71  	}
    72  
    73  	if err := g.Wait(); err != nil {
    74  		level.Error(s.logger).Log("err", err)
    75  		return err
    76  	}
    77  
    78  	for _, g := range groups {
    79  		tracing.DoInSpan(srv.Context(), "send_rules_response", func(_ context.Context) {
    80  			err = srv.Send(rulespb.NewRuleGroupRulesResponse(g))
    81  		})
    82  		if err != nil {
    83  			return status.Error(codes.Unknown, errors.Wrap(err, "send rules response").Error())
    84  		}
    85  	}
    86  
    87  	return nil
    88  }
    89  
    90  type rulesStream struct {
    91  	client  rulespb.RulesClient
    92  	request *rulespb.RulesRequest
    93  	channel chan<- *rulespb.RuleGroup
    94  	server  rulespb.Rules_RulesServer
    95  }
    96  
    97  func (stream *rulesStream) receive(ctx context.Context) error {
    98  	var (
    99  		err   error
   100  		rules rulespb.Rules_RulesClient
   101  	)
   102  
   103  	tracing.DoInSpan(ctx, "receive_stream_request", func(ctx context.Context) {
   104  		rules, err = stream.client.Rules(ctx, stream.request)
   105  	})
   106  
   107  	if err != nil {
   108  		err = errors.Wrapf(err, "fetching rules from rules client %v", stream.client)
   109  
   110  		if stream.request.PartialResponseStrategy == storepb.PartialResponseStrategy_ABORT {
   111  			return err
   112  		}
   113  
   114  		if serr := stream.server.Send(rulespb.NewWarningRulesResponse(err)); serr != nil {
   115  			return serr
   116  		}
   117  		// Not an error if response strategy is warning.
   118  		return nil
   119  	}
   120  
   121  	for {
   122  		rule, err := rules.Recv()
   123  		if err == io.EOF {
   124  			return nil
   125  		}
   126  
   127  		if err != nil {
   128  			// An error happened in Recv(), hence the underlying stream is aborted
   129  			// as per https://github.com/grpc/grpc-go/blob/7f2581f910fc21497091c4109b56d310276fc943/stream.go#L117-L125.
   130  			// We must not continue receiving additional data from it and must return.
   131  			err = errors.Wrapf(err, "receiving rules from rules client %v", stream.client)
   132  
   133  			if stream.request.PartialResponseStrategy == storepb.PartialResponseStrategy_ABORT {
   134  				return err
   135  			}
   136  
   137  			if err := stream.server.Send(rulespb.NewWarningRulesResponse(err)); err != nil {
   138  				return errors.Wrapf(err, "sending rules error to server %v", stream.server)
   139  			}
   140  
   141  			// Return no error if response strategy is warning.
   142  			return nil
   143  		}
   144  
   145  		if w := rule.GetWarning(); w != "" {
   146  			if err := stream.server.Send(rulespb.NewWarningRulesResponse(errors.New(w))); err != nil {
   147  				return errors.Wrapf(err, "sending rules warning to server %v", stream.server)
   148  			}
   149  			// Client stream is not aborted, it is ok to receive additional data.
   150  			continue
   151  		}
   152  
   153  		select {
   154  		case stream.channel <- rule.GetGroup():
   155  		case <-ctx.Done():
   156  			return ctx.Err()
   157  		}
   158  	}
   159  }