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  }