github.com/go-graphite/carbonapi@v0.17.0/cmd/mockbackend/find.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"math/rand"
     7  	"net/http"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/ansel1/merry"
    12  	"github.com/go-graphite/protocol/carbonapi_v2_pb"
    13  	"github.com/go-graphite/protocol/carbonapi_v3_pb"
    14  	ogórek "github.com/lomik/og-rek"
    15  	"go.uber.org/zap"
    16  
    17  	"github.com/go-graphite/carbonapi/intervalset"
    18  )
    19  
    20  func (cfg *listener) findHandler(wr http.ResponseWriter, req *http.Request) {
    21  	_ = req.ParseMultipartForm(16 * 1024 * 1024)
    22  	hdrs := make(map[string][]string)
    23  
    24  	for n, v := range req.Header {
    25  		hdrs[n] = v
    26  	}
    27  
    28  	logger := cfg.logger.With(
    29  		zap.String("function", "findHandler"),
    30  		zap.String("method", req.Method),
    31  		zap.String("path", req.URL.Path),
    32  		zap.Any("form", req.Form),
    33  		zap.Any("headers", hdrs),
    34  	)
    35  	logger.Info("got request")
    36  
    37  	if cfg.Code != http.StatusOK {
    38  		wr.WriteHeader(cfg.Code)
    39  		return
    40  	}
    41  
    42  	format, err := getFormat(req)
    43  	if err != nil {
    44  		wr.WriteHeader(http.StatusBadRequest)
    45  		_, _ = wr.Write([]byte(err.Error()))
    46  		return
    47  	}
    48  
    49  	var query []string
    50  
    51  	if format == protoV3Format {
    52  		body, err := io.ReadAll(req.Body)
    53  		if err != nil {
    54  			logger.Error("failed to read request body",
    55  				zap.Error(err),
    56  			)
    57  			http.Error(wr, "Bad request (unsupported format)",
    58  				http.StatusBadRequest)
    59  			return
    60  		}
    61  
    62  		var pv3Request carbonapi_v3_pb.MultiGlobRequest
    63  		_ = pv3Request.Unmarshal(body)
    64  
    65  		query = pv3Request.Metrics
    66  	} else {
    67  		query = req.Form["query"]
    68  	}
    69  
    70  	if len(query) == 0 {
    71  		logger.Error("Bad request (no query)")
    72  		http.Error(wr, "Bad request (no query)",
    73  			http.StatusBadRequest)
    74  		return
    75  	}
    76  
    77  	logger.Info("request details",
    78  		zap.Strings("query", query),
    79  	)
    80  
    81  	multiGlobs := carbonapi_v3_pb.MultiGlobResponse{
    82  		Metrics: []carbonapi_v3_pb.GlobResponse{},
    83  	}
    84  
    85  	if query[0] == "*" {
    86  		returnMap := make(map[string]struct{})
    87  		for m := range cfg.Listener.Expressions {
    88  			response := cfg.Expressions[m]
    89  			if response.ReplyDelayMS > 0 {
    90  				delay := time.Duration(response.ReplyDelayMS) * time.Millisecond
    91  				time.Sleep(delay)
    92  			}
    93  			for _, metric := range response.Data {
    94  				returnMap[metric.MetricName] = struct{}{}
    95  			}
    96  		}
    97  
    98  		globMatches := []carbonapi_v3_pb.GlobMatch{}
    99  		for k := range returnMap {
   100  			metricName := strings.Split(k, ".")
   101  
   102  			globMatches = append(globMatches, carbonapi_v3_pb.GlobMatch{
   103  				Path:   metricName[0],
   104  				IsLeaf: len(metricName) == 1,
   105  			})
   106  		}
   107  		multiGlobs.Metrics = append(multiGlobs.Metrics,
   108  			carbonapi_v3_pb.GlobResponse{
   109  				Name:    "*",
   110  				Matches: globMatches,
   111  			})
   112  	} else {
   113  		for _, m := range query {
   114  			globMatches := []carbonapi_v3_pb.GlobMatch{}
   115  			if response, ok := cfg.Expressions[m]; ok {
   116  				if response.ReplyDelayMS > 0 {
   117  					delay := time.Duration(response.ReplyDelayMS) * time.Millisecond
   118  					time.Sleep(delay)
   119  				}
   120  				if response.Code != 0 && response.Code != http.StatusOK {
   121  					// return first error
   122  					http.Error(wr, http.StatusText(response.Code), response.Code)
   123  					return
   124  				}
   125  
   126  				for _, metric := range cfg.Expressions[m].Data {
   127  					globMatches = append(globMatches, carbonapi_v3_pb.GlobMatch{
   128  						Path:   metric.MetricName,
   129  						IsLeaf: true,
   130  					})
   131  				}
   132  				multiGlobs.Metrics = append(multiGlobs.Metrics,
   133  					carbonapi_v3_pb.GlobResponse{
   134  						Name:    cfg.Expressions[m].PathExpression,
   135  						Matches: globMatches,
   136  					})
   137  			}
   138  		}
   139  	}
   140  
   141  	if cfg.Listener.ShuffleResults {
   142  		rand.Shuffle(len(multiGlobs.Metrics), func(i, j int) {
   143  			multiGlobs.Metrics[i], multiGlobs.Metrics[j] = multiGlobs.Metrics[j], multiGlobs.Metrics[i]
   144  		})
   145  	}
   146  
   147  	logger.Info("will return", zap.Any("response", multiGlobs))
   148  
   149  	var b []byte
   150  	switch format {
   151  	case protoV2Format:
   152  		response := carbonapi_v2_pb.GlobResponse{
   153  			Name:    query[0],
   154  			Matches: make([]carbonapi_v2_pb.GlobMatch, 0),
   155  		}
   156  		for _, metric := range multiGlobs.Metrics {
   157  			if metric.Name == query[0] {
   158  				for _, m := range metric.Matches {
   159  					response.Matches = append(response.Matches,
   160  						carbonapi_v2_pb.GlobMatch{
   161  							Path:   m.Path,
   162  							IsLeaf: m.IsLeaf,
   163  						})
   164  				}
   165  			}
   166  		}
   167  		b, err = response.Marshal()
   168  		format = protoV2Format
   169  	case protoV3Format:
   170  		b, err = multiGlobs.Marshal()
   171  		format = protoV3Format
   172  	case pickleFormat:
   173  		var result []map[string]interface{}
   174  		now := int32(time.Now().Unix() + 60)
   175  		for _, globs := range multiGlobs.Metrics {
   176  			for _, metric := range globs.Matches {
   177  				if strings.HasPrefix(metric.Path, "_tag") {
   178  					continue
   179  				}
   180  				// Tell graphite-web that we have everything
   181  				var mm map[string]interface{}
   182  				// graphite-web 1.0
   183  				interval := &intervalset.IntervalSet{Start: 0, End: now}
   184  				mm = map[string]interface{}{
   185  					"is_leaf":   metric.IsLeaf,
   186  					"path":      metric.Path,
   187  					"intervals": interval,
   188  				}
   189  				result = append(result, mm)
   190  			}
   191  		}
   192  
   193  		p := bytes.NewBuffer(b)
   194  		pEnc := ogórek.NewEncoder(p)
   195  		err = merry.Wrap(pEnc.Encode(result))
   196  		b = p.Bytes()
   197  	}
   198  
   199  	if err != nil {
   200  		logger.Error("failed to marshal", zap.Error(err))
   201  		http.Error(wr, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
   202  		return
   203  	}
   204  
   205  	switch format {
   206  	case jsonFormat:
   207  		wr.Header().Set("Content-Type", contentTypeJSON)
   208  	case protoV3Format, protoV2Format:
   209  		wr.Header().Set("Content-Type", contentTypeProtobuf)
   210  	case pickleFormat:
   211  		wr.Header().Set("Content-Type", contentTypePickle)
   212  	}
   213  	_, _ = wr.Write(b)
   214  }