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 }