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 }