github.com/go-graphite/carbonapi@v0.17.0/zipper/protocols/auto/auto_group.go (about) 1 package auto 2 3 import ( 4 "context" 5 "net/http" 6 "net/url" 7 "time" 8 9 "github.com/ansel1/merry" 10 protov3 "github.com/go-graphite/protocol/carbonapi_v3_pb" 11 "go.uber.org/zap" 12 13 "github.com/go-graphite/carbonapi/internal/dns" 14 "github.com/go-graphite/carbonapi/limiter" 15 "github.com/go-graphite/carbonapi/zipper/broadcast" 16 "github.com/go-graphite/carbonapi/zipper/helper" 17 "github.com/go-graphite/carbonapi/zipper/httpHeaders" 18 "github.com/go-graphite/carbonapi/zipper/metadata" 19 "github.com/go-graphite/carbonapi/zipper/types" 20 ) 21 22 func init() { 23 aliases := []string{"auto"} 24 metadata.Metadata.Lock() 25 for _, name := range aliases { 26 metadata.Metadata.SupportedProtocols[name] = struct{}{} 27 metadata.Metadata.ProtocolInits[name] = New 28 metadata.Metadata.ProtocolInitsWithLimiter[name] = NewWithLimiter 29 } 30 defer metadata.Metadata.Unlock() 31 } 32 33 type capabilityResponse struct { 34 server string 35 protocol string 36 } 37 38 // _internal/capabilities/ 39 func doQuery(ctx context.Context, logger *zap.Logger, groupName string, httpClient *http.Client, limiter limiter.ServerLimiter, server string, request types.Request, resChan chan<- capabilityResponse) { 40 httpQuery := helper.NewHttpQuery(groupName, []string{server}, 1, limiter, httpClient, httpHeaders.ContentTypeCarbonAPIv3PB) 41 rewrite, _ := url.Parse("http://127.0.0.1/_internal/capabilities/") 42 43 res, e := httpQuery.DoQuery(ctx, logger, rewrite.RequestURI(), request) 44 if e != nil || res == nil || res.Response == nil || len(res.Response) == 0 { 45 logger.Info("will assume old protocol") 46 resChan <- capabilityResponse{ 47 server: server, 48 protocol: "protobuf", 49 } 50 return 51 } 52 53 response := protov3.CapabilityResponse{} 54 logger.Debug("response", 55 zap.String("server", res.Server), 56 zap.String("response", string(res.Response)), 57 ) 58 err := response.Unmarshal(res.Response) 59 60 if err != nil { 61 resChan <- capabilityResponse{ 62 server: server, 63 protocol: "protobuf", 64 } 65 return 66 } 67 68 resChan <- capabilityResponse{ 69 server: server, 70 protocol: response.SupportedProtocols[0], 71 } 72 73 } 74 75 type CapabilityResponse struct { 76 ProtoToServers map[string][]string 77 } 78 79 func getBestSupportedProtocol(logger *zap.Logger, servers []string) *CapabilityResponse { 80 response := &CapabilityResponse{ 81 ProtoToServers: make(map[string][]string), 82 } 83 groupName := "capability query" 84 l := limiter.NoopLimiter{} 85 86 httpClient := &http.Client{ 87 Transport: &http.Transport{ 88 DialContext: dns.GetDialContextWithTimeout(200*time.Millisecond, 30*time.Second), 89 }, 90 } 91 92 ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) 93 defer cancel() 94 95 request := types.CapabilityRequestV3{} 96 resCh := make(chan capabilityResponse, len(servers)) 97 98 for _, srv := range servers { 99 go doQuery(ctx, logger, groupName, httpClient, l, srv, request, resCh) 100 } 101 102 answeredServers := make(map[string]struct{}) 103 responseCounts := 0 104 GATHER: 105 for { 106 if responseCounts == len(servers) && len(resCh) == 0 { 107 break GATHER 108 } 109 select { 110 case res := <-resCh: 111 responseCounts++ 112 answeredServers[res.server] = struct{}{} 113 if res.protocol == "" { 114 return nil 115 } 116 p := response.ProtoToServers[res.protocol] 117 response.ProtoToServers[res.protocol] = append(p, res.server) 118 case <-ctx.Done(): 119 noAnswer := make([]string, 0) 120 for _, s := range servers { 121 if _, ok := answeredServers[s]; !ok { 122 noAnswer = append(noAnswer, s) 123 } 124 } 125 logger.Warn("timeout waiting for more responses", 126 zap.Strings("no_answers_from", noAnswer), 127 ) 128 break GATHER 129 } 130 } 131 132 return response 133 } 134 135 type AutoGroup struct { 136 groupName string 137 } 138 139 func NewWithLimiter(logger *zap.Logger, config types.BackendV2, tldCacheDisabled, requireSuccessAll bool, limiter limiter.ServerLimiter) (types.BackendServer, merry.Error) { 140 return nil, merry.New("auto group doesn't support anything useful except for New") 141 } 142 143 func New(logger *zap.Logger, config types.BackendV2, tldCacheDisabled, requireSuccessAll bool) (types.BackendServer, merry.Error) { 144 logger = logger.With(zap.String("type", "autoGroup"), zap.String("name", config.GroupName)) 145 146 if config.ConcurrencyLimit == nil { 147 logger.Error("this behavior changes in 0.14.0, before that there was an implied concurrencyLimit of 100 for the backend. Currently it's required to specify this limit for auto backend as well.") 148 return nil, types.ErrConcurrencyLimitNotSet 149 } 150 151 res := getBestSupportedProtocol(logger, config.Servers) 152 if res == nil { 153 return nil, merry.New("can't query all backend") 154 } 155 156 var backends []types.BackendServer 157 for proto, servers := range res.ProtoToServers { 158 metadata.Metadata.RLock() 159 backendInit, ok := metadata.Metadata.ProtocolInits[proto] 160 metadata.Metadata.RUnlock() 161 if !ok { 162 var protocols []string 163 metadata.Metadata.RLock() 164 for p := range metadata.Metadata.SupportedProtocols { 165 protocols = append(protocols, p) 166 } 167 metadata.Metadata.RUnlock() 168 logger.Error("unknown backend protocol", 169 zap.Any("backend", config), 170 zap.String("requested_protocol", proto), 171 zap.Strings("supported_backends", protocols), 172 ) 173 return nil, merry.New("unknown backend protocol").WithValue("protocol", proto) 174 } 175 176 cfg := config 177 cfg.GroupName = config.GroupName + "_" + proto 178 cfg.Servers = servers 179 c, ePtr := backendInit(logger, cfg, tldCacheDisabled, requireSuccessAll) 180 if ePtr != nil { 181 return nil, ePtr 182 } 183 184 backends = append(backends, c) 185 } 186 187 return broadcast.NewBroadcastGroup(logger, config.GroupName+"_broadcast", config.DoMultipleRequestsIfSplit, backends, 188 600, *config.ConcurrencyLimit, *config.MaxBatchSize, *config.Timeouts, tldCacheDisabled, requireSuccessAll) 189 } 190 191 func (c AutoGroup) MaxMetricsPerRequest() int { 192 return -1 193 } 194 195 func (c AutoGroup) Name() string { 196 return c.groupName 197 } 198 199 func (c AutoGroup) Backends() []string { 200 return nil 201 } 202 203 func (c *AutoGroup) Fetch(ctx context.Context, request *protov3.MultiFetchRequest) (*protov3.MultiFetchResponse, *types.Stats, merry.Error) { 204 return nil, nil, merry.New("auto group doesn't support fetch") 205 } 206 207 func (c *AutoGroup) Find(ctx context.Context, request *protov3.MultiGlobRequest) (*protov3.MultiGlobResponse, *types.Stats, merry.Error) { 208 return nil, nil, merry.New("auto group doesn't support find") 209 } 210 211 func (c *AutoGroup) Info(ctx context.Context, request *protov3.MultiMetricsInfoRequest) (*protov3.ZipperInfoResponse, *types.Stats, merry.Error) { 212 return nil, nil, merry.New("auto group doesn't support info") 213 } 214 215 func (c *AutoGroup) List(ctx context.Context) (*protov3.ListMetricsResponse, *types.Stats, merry.Error) { 216 return nil, nil, merry.New("auto group doesn't support list") 217 } 218 func (c *AutoGroup) Stats(ctx context.Context) (*protov3.MetricDetailsResponse, *types.Stats, merry.Error) { 219 return nil, nil, merry.New("auto group doesn't support stats") 220 } 221 222 func (bg *AutoGroup) TagNames(ctx context.Context, prefix string, exprs []string, limit int64) ([]string, merry.Error) { 223 return nil, merry.New("auto group doesn't support tag names") 224 } 225 226 func (bg *AutoGroup) TagValues(ctx context.Context, tagName, prefix string, exprs []string, limit int64) ([]string, merry.Error) { 227 return nil, merry.New("auto group doesn't support tag values") 228 } 229 230 func (c *AutoGroup) ProbeTLDs(ctx context.Context) ([]string, merry.Error) { 231 return nil, merry.New("auto group doesn't support probing") 232 }