diff --git a/cmd/carbonapi/http/render_handler.go b/cmd/carbonapi/http/render_handler.go index 56ae44738..2b98ec9b2 100644 --- a/cmd/carbonapi/http/render_handler.go +++ b/cmd/carbonapi/http/render_handler.go @@ -348,18 +348,18 @@ func renderHandler(w http.ResponseWriter, r *http.Request) { var body []byte returnCode := http.StatusOK - if len(results) == 0 { + if len(results) == 0 || (len(errors) > 0 && config.Config.Upstreams.RequireSuccessAll) { // Obtain error code from the errors // In case we have only "Not Found" errors, result should be 404 // Otherwise it should be 500 returnCode, errMsgs := helper.MergeHttpErrorMap(errors) logger.Debug("error response or no response", zap.Strings("error", errMsgs)) // Allow override status code for 404-not-found replies. - if returnCode == 404 { + if returnCode == http.StatusNotFound { returnCode = config.Config.NotFoundStatusCode } - if returnCode == 400 || returnCode == http.StatusForbidden || returnCode >= 500 { + if returnCode == http.StatusBadRequest || returnCode == http.StatusNotFound || returnCode == http.StatusForbidden || returnCode >= 500 { setError(w, accessLogDetails, strings.Join(errMsgs, ","), returnCode, uid.String()) logAsError = true return diff --git a/cmd/mockbackend/e2etesting.go b/cmd/mockbackend/e2etesting.go index 922246364..e064b23b5 100644 --- a/cmd/mockbackend/e2etesting.go +++ b/cmd/mockbackend/e2etesting.go @@ -313,6 +313,7 @@ func e2eTest(logger *zap.Logger, noapp, breakOnError bool) bool { failed = true logger.Error("test failed", zap.Errors("failures", failures), + zap.String("url", t.URL), zap.String("type", t.Type), zap.String("body", t.Body), ) for _, v := range runningApps { if !v.IsRunning() { diff --git a/cmd/mockbackend/find.go b/cmd/mockbackend/find.go index 2c0c14592..e03fcdb59 100644 --- a/cmd/mockbackend/find.go +++ b/cmd/mockbackend/find.go @@ -72,15 +72,33 @@ func (cfg *listener) findHandler(wr http.ResponseWriter, req *http.Request) { Metrics: []carbonapi_v3_pb.GlobResponse{}, } + returnCode := http.StatusOK if query[0] != "*" { for m := range cfg.Listener.Expressions { globMatches := []carbonapi_v3_pb.GlobMatch{} - for _, metric := range cfg.Expressions[m].Data { - globMatches = append(globMatches, carbonapi_v3_pb.GlobMatch{ - Path: metric.MetricName, - IsLeaf: true, - }) + returnCode = http.StatusNotFound + if response, ok := cfg.Expressions[m]; ok { + if response.ReplyDelayMS > 0 { + delay := time.Duration(response.ReplyDelayMS) * time.Millisecond + time.Sleep(delay) + } + if response.HttpCode == http.StatusNotFound { + returnCode = http.StatusNotFound + } else if response.HttpCode != 0 && response.HttpCode != http.StatusOK { + // return first error + returnCode = response.HttpCode + http.Error(wr, http.StatusText(returnCode), returnCode) + return + } else { + returnCode = http.StatusOK + for _, metric := range response.Data { + globMatches = append(globMatches, carbonapi_v3_pb.GlobMatch{ + Path: metric.MetricName, + IsLeaf: true, + }) + } + } } multiGlobs.Metrics = append(multiGlobs.Metrics, carbonapi_v3_pb.GlobResponse{ @@ -96,8 +114,18 @@ func (cfg *listener) findHandler(wr http.ResponseWriter, req *http.Request) { delay := time.Duration(response.ReplyDelayMS) * time.Millisecond time.Sleep(delay) } - for _, metric := range response.Data { - returnMap[metric.MetricName] = struct{}{} + if response.HttpCode == http.StatusNotFound { + returnCode = http.StatusNotFound + } else if response.HttpCode != 0 && response.HttpCode != http.StatusOK { + // return first error + returnCode = response.HttpCode + http.Error(wr, http.StatusText(returnCode), returnCode) + return + } else { + returnCode = http.StatusOK + for _, metric := range response.Data { + returnMap[metric.MetricName] = struct{}{} + } } } @@ -123,6 +151,12 @@ func (cfg *listener) findHandler(wr http.ResponseWriter, req *http.Request) { }) } + if returnCode == http.StatusNotFound { + // return 404 when no data + http.Error(wr, http.StatusText(returnCode), returnCode) + return + } + logger.Info("will return", zap.Any("response", multiGlobs)) var b []byte diff --git a/cmd/mockbackend/http_common.go b/cmd/mockbackend/http_common.go index 0bb75a912..460c058ec 100644 --- a/cmd/mockbackend/http_common.go +++ b/cmd/mockbackend/http_common.go @@ -7,6 +7,7 @@ import ( ) type Response struct { + HttpCode int `yaml:"httpCode"` // return error if one of expresssions not success ReplyDelayMS int `yaml:"replyDelayMS"` PathExpression string `yaml:"pathExpression"` Data []Metric `yaml:"data"` @@ -86,6 +87,7 @@ func copyResponse(src Response) Response { dst := Response{ PathExpression: src.PathExpression, ReplyDelayMS: src.ReplyDelayMS, + HttpCode: src.HttpCode, Data: make([]Metric, len(src.Data)), } diff --git a/cmd/mockbackend/render.go b/cmd/mockbackend/render.go index 8d9ff469e..bb61fe8e5 100644 --- a/cmd/mockbackend/render.go +++ b/cmd/mockbackend/render.go @@ -97,68 +97,84 @@ func (cfg *listener) renderHandler(wr http.ResponseWriter, req *http.Request) { Expressions: copyMap(cfg.Expressions), } + returnCode := http.StatusOK + for _, target := range targets { - response, ok := newCfg.Expressions[target] - if !ok { - wr.WriteHeader(http.StatusNotFound) - _, _ = wr.Write([]byte("Not found")) - return - } - if response.ReplyDelayMS > 0 { - delay := time.Duration(response.ReplyDelayMS) * time.Millisecond - logger.Info("will add extra delay", - zap.Duration("delay", delay), - ) - time.Sleep(delay) - } - for _, m := range response.Data { - step := m.Step - if step == 0 { - step = 1 - } - startTime := m.StartTime - if startTime == 0 { - startTime = step + if response, ok := newCfg.Expressions[target]; ok { + if response.ReplyDelayMS > 0 { + delay := time.Duration(response.ReplyDelayMS) * time.Millisecond + logger.Info("will add extra delay", + zap.Duration("delay", delay), + ) + time.Sleep(delay) } - isAbsent := make([]bool, 0, len(m.Values)) - protov2Values := make([]float64, 0, len(m.Values)) - for i := range m.Values { - if math.IsNaN(m.Values[i]) { - isAbsent = append(isAbsent, true) - protov2Values = append(protov2Values, 0.0) - } else { - isAbsent = append(isAbsent, false) - protov2Values = append(protov2Values, m.Values[i]) - } - } - fr2 := carbonapi_v2_pb.FetchResponse{ - Name: m.MetricName, - StartTime: int32(startTime), - StopTime: int32(startTime + step*len(protov2Values)), - StepTime: int32(step), - Values: protov2Values, - IsAbsent: isAbsent, + + if response.HttpCode == http.StatusNotFound { + continue } - fr3 := carbonapi_v3_pb.FetchResponse{ - Name: m.MetricName, - PathExpression: target, - ConsolidationFunc: "avg", - StartTime: int64(startTime), - StopTime: int64(startTime + step*len(m.Values)), - StepTime: int64(step), - XFilesFactor: 0, - HighPrecisionTimestamps: false, - Values: m.Values, - RequestStartTime: 1, - RequestStopTime: int64(startTime + step*len(m.Values)), + if response.HttpCode != 0 && response.HttpCode != http.StatusOK { + http.Error(wr, http.StatusText(response.HttpCode), response.HttpCode) + return } - multiv2.Metrics = append(multiv2.Metrics, fr2) - multiv3.Metrics = append(multiv3.Metrics, fr3) + returnCode = http.StatusOK + + for _, m := range response.Data { + step := m.Step + if step == 0 { + step = 1 + } + startTime := m.StartTime + if startTime == 0 { + startTime = step + } + isAbsent := make([]bool, 0, len(m.Values)) + protov2Values := make([]float64, 0, len(m.Values)) + for i := range m.Values { + if math.IsNaN(m.Values[i]) { + isAbsent = append(isAbsent, true) + protov2Values = append(protov2Values, 0.0) + } else { + isAbsent = append(isAbsent, false) + protov2Values = append(protov2Values, m.Values[i]) + } + } + fr2 := carbonapi_v2_pb.FetchResponse{ + Name: m.MetricName, + StartTime: int32(startTime), + StopTime: int32(startTime + step*len(protov2Values)), + StepTime: int32(step), + Values: protov2Values, + IsAbsent: isAbsent, + } + + fr3 := carbonapi_v3_pb.FetchResponse{ + Name: m.MetricName, + PathExpression: target, + ConsolidationFunc: "avg", + StartTime: int64(startTime), + StopTime: int64(startTime + step*len(m.Values)), + StepTime: int64(step), + XFilesFactor: 0, + HighPrecisionTimestamps: false, + Values: m.Values, + RequestStartTime: 1, + RequestStopTime: int64(startTime + step*len(m.Values)), + } + + multiv2.Metrics = append(multiv2.Metrics, fr2) + multiv3.Metrics = append(multiv3.Metrics, fr3) + } } } + if returnCode == http.StatusNotFound { + wr.WriteHeader(http.StatusNotFound) + _, _ = wr.Write([]byte("Not found")) + return + } + if cfg.Listener.ShuffleResults { rand.Shuffle(len(multiv2.Metrics), func(i, j int) { multiv2.Metrics[i], multiv2.Metrics[j] = multiv2.Metrics[j], multiv2.Metrics[i] diff --git a/cmd/mockbackend/testcases/render_error/carbonapi.yaml b/cmd/mockbackend/testcases/render_error/carbonapi.yaml new file mode 100644 index 000000000..d37848832 --- /dev/null +++ b/cmd/mockbackend/testcases/render_error/carbonapi.yaml @@ -0,0 +1,57 @@ +listen: "localhost:8081" +expvar: + enabled: true + pprofEnabled: false + listen: "" +concurency: 1000 +notFoundStatusCode: 200 +cache: + type: "mem" + size_mb: 0 + defaultTimeoutSec: 60 +cpus: 0 +tz: "" +maxBatchSize: 0 +graphite: + host: "" + interval: "60s" + prefix: "carbon.api" + pattern: "{prefix}.{fqdn}" +idleConnections: 10 +pidFile: "" +upstreams: + buckets: 10 + timeouts: + find: "2s" + render: "5s" + connect: "200ms" + concurrencyLimitPerServer: 0 + keepAliveInterval: "30s" + maxIdleConnsPerHost: 100 + backendsv2: + backends: + - + groupName: "mock-001" + protocol: "auto" + lbMethod: "all" + maxTries: 3 + maxBatchSize: 0 + keepAliveInterval: "10s" + concurrencyLimit: 0 + forceAttemptHTTP2: true + maxIdleConnsPerHost: 1000 + timeouts: + find: "3s" + render: "5s" + connect: "200ms" + servers: + - "http://127.0.0.1:9070" + graphite09compat: false +expireDelaySec: 10 +logger: + - logger: "" + file: "stderr" + level: "debug" + encoding: "console" + encodingTime: "iso8601" + encodingDuration: "seconds" diff --git a/cmd/mockbackend/testcases/render_error/render_error.yaml b/cmd/mockbackend/testcases/render_error/render_error.yaml new file mode 100644 index 000000000..a8ece2eda --- /dev/null +++ b/cmd/mockbackend/testcases/render_error/render_error.yaml @@ -0,0 +1,99 @@ +version: "v1" +test: + apps: + - name: "carbonapi" + binary: "./carbonapi" + args: + - "-config" + - "./cmd/mockbackend/testcases/render_error/carbonapi.yaml" + queries: + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=a&format=json" + expectedResponse: + httpCode: 200 + contentType: "application/json" + expectedResults: + - metrics: + - target: "a" + datapoints: [[0,1],[1,2],[2,3],[2,4],[3,5]] + + # empty + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=b&format=json" + expectedResponse: + httpCode: 200 + contentType: "application/json" + + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=a&target=b&format=json" + expectedResponse: + httpCode: 200 + contentType: "application/json" + expectedResults: + - metrics: + - target: "a" + datapoints: [[0,1],[1,2],[2,3],[2,4],[3,5]] + + # timeout + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=c&format=json" + expectedResponse: + httpCode: 503 + contentType: "text/plain; charset=utf-8" + + # 503 + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=d&format=json" + expectedResponse: + httpCode: 503 + contentType: "text/plain; charset=utf-8" + + # partial success + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=a&target=d&format=json" + expectedResponse: + httpCode: 200 + contentType: "application/json" + expectedResults: + - metrics: + - target: "a" + datapoints: [[0,1],[1,2],[2,3],[2,4],[3,5]] + + # partial success + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=divideSeries(a,d)&format=json" + expectedResponse: + httpCode: 200 + contentType: "application/json" + expectedResults: + - metrics: + - target: "divideSeries(a,MISSING)" + datapoints: [[nan,1],[nan,2],[nan,3],[nan,4],[nan,5]] + +listeners: + - address: ":9070" + expressions: + "a": + pathExpression: "a" + data: + - metricName: "a" + values: [0,1,2,2,3] + + # timeout + "c": + pathExpression: "c" + emptyBody: true + httpCode: 404 + replyDelayMS: 7000 + + "d": + pathExpression: "d" + emptyBody: true + httpCode: 503 diff --git a/cmd/mockbackend/testcases/render_error_all/carbonapi.yaml b/cmd/mockbackend/testcases/render_error_all/carbonapi.yaml new file mode 100644 index 000000000..ee71a06a0 --- /dev/null +++ b/cmd/mockbackend/testcases/render_error_all/carbonapi.yaml @@ -0,0 +1,58 @@ +listen: "localhost:8081" +expvar: + enabled: true + pprofEnabled: false + listen: "" +concurency: 1000 +notFoundStatusCode: 200 +cache: + type: "mem" + size_mb: 0 + defaultTimeoutSec: 60 +cpus: 0 +tz: "" +maxBatchSize: 0 +graphite: + host: "" + interval: "60s" + prefix: "carbon.api" + pattern: "{prefix}.{fqdn}" +idleConnections: 10 +pidFile: "" +upstreams: + requireSuccessAll: true + buckets: 10 + timeouts: + find: "2s" + render: "5s" + connect: "200ms" + concurrencyLimitPerServer: 0 + keepAliveInterval: "30s" + maxIdleConnsPerHost: 100 + backendsv2: + backends: + - + groupName: "mock-001" + protocol: "auto" + lbMethod: "all" + maxTries: 3 + maxBatchSize: 0 + keepAliveInterval: "10s" + concurrencyLimit: 0 + forceAttemptHTTP2: true + maxIdleConnsPerHost: 1000 + timeouts: + find: "3s" + render: "5s" + connect: "200ms" + servers: + - "http://127.0.0.1:9070" + graphite09compat: false +expireDelaySec: 10 +logger: + - logger: "" + file: "stderr" + level: "debug" + encoding: "console" + encodingTime: "iso8601" + encodingDuration: "seconds" diff --git a/cmd/mockbackend/testcases/render_error_all/render_error_all.yaml b/cmd/mockbackend/testcases/render_error_all/render_error_all.yaml new file mode 100644 index 000000000..f507261a8 --- /dev/null +++ b/cmd/mockbackend/testcases/render_error_all/render_error_all.yaml @@ -0,0 +1,96 @@ +version: "v1" +test: + apps: + - name: "carbonapi" + binary: "./carbonapi" + args: + - "-config" + - "./cmd/mockbackend/testcases/render_error_all/carbonapi.yaml" + queries: + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=a&format=json" + expectedResponse: + httpCode: 200 + contentType: "application/json" + expectedResults: + - metrics: + - target: "a" + datapoints: [[0,1],[1,2],[2,3],[2,4],[3,5]] + + # empty + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=b&format=json" + expectedResponse: + httpCode: 200 + contentType: "application/json" + + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=a&target=b&format=json" + expectedResponse: + httpCode: 200 + contentType: "application/json" + expectedResults: + - metrics: + - target: "a" + datapoints: [[0,1],[1,2],[2,3],[2,4],[3,5]] + + # timeout + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=c&format=json" + expectedResponse: + httpCode: 503 + contentType: "text/plain; charset=utf-8" + + # 503 + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=d&format=json" + expectedResponse: + httpCode: 503 + contentType: "text/plain; charset=utf-8" + + # partial success + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=a&target=d&format=json" + expectedResponse: + httpCode: 503 + contentType: "text/plain; charset=utf-8" + + # partial success + # TODO: must fail, target d failed + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=divideSeries(a,d)&format=json" + expectedResponse: + httpCode: 200 + contentType: "application/json" + expectedResults: + - metrics: + - target: "divideSeries(a,MISSING)" + datapoints: [[nan,1],[nan,2],[nan,3],[nan,4],[nan,5]] + +listeners: + - address: ":9070" + expressions: + "a": + pathExpression: "a" + data: + - metricName: "a" + values: [0,1,2,2,3] + + # timeout + "c": + pathExpression: "c" + emptyBody: true + httpCode: 404 + replyDelayMS: 7000 + + "d": + pathExpression: "d" + emptyBody: true + httpCode: 503 diff --git a/cmd/mockbackend/testcases/render_error_all_multi/carbonapi.yaml b/cmd/mockbackend/testcases/render_error_all_multi/carbonapi.yaml new file mode 100644 index 000000000..c72de751c --- /dev/null +++ b/cmd/mockbackend/testcases/render_error_all_multi/carbonapi.yaml @@ -0,0 +1,59 @@ +listen: "localhost:8081" +combineMultipleTargetsInOne: true +expvar: + enabled: true + pprofEnabled: false + listen: "" +concurency: 1000 +notFoundStatusCode: 200 +cache: + type: "mem" + size_mb: 0 + defaultTimeoutSec: 60 +cpus: 0 +tz: "" +maxBatchSize: 0 +graphite: + host: "" + interval: "60s" + prefix: "carbon.api" + pattern: "{prefix}.{fqdn}" +idleConnections: 10 +pidFile: "" +upstreams: + requireSuccessAll: true + buckets: 10 + timeouts: + find: "2s" + render: "10s" + connect: "200ms" + concurrencyLimitPerServer: 0 + keepAliveInterval: "30s" + maxIdleConnsPerHost: 100 + backendsv2: + backends: + - + groupName: "mock-001" + protocol: "auto" + lbMethod: "all" + maxTries: 3 + maxBatchSize: 0 + keepAliveInterval: "10s" + concurrencyLimit: 0 + forceAttemptHTTP2: true + maxIdleConnsPerHost: 1000 + timeouts: + find: "3s" + render: "5s" + connect: "200ms" + servers: + - "http://127.0.0.1:9070" + graphite09compat: false +expireDelaySec: 10 +logger: + - logger: "" + file: "stderr" + level: "debug" + encoding: "console" + encodingTime: "iso8601" + encodingDuration: "seconds" diff --git a/cmd/mockbackend/testcases/render_error_all_multi/render_error_all_multi.yaml b/cmd/mockbackend/testcases/render_error_all_multi/render_error_all_multi.yaml new file mode 100644 index 000000000..2d9d15610 --- /dev/null +++ b/cmd/mockbackend/testcases/render_error_all_multi/render_error_all_multi.yaml @@ -0,0 +1,107 @@ +version: "v1" +test: + apps: + - name: "carbonapi" + binary: "./carbonapi" + args: + - "-config" + - "./cmd/mockbackend/testcases/render_error_all_multi/carbonapi.yaml" + queries: + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=a&format=json" + expectedResponse: + httpCode: 200 + contentType: "application/json" + expectedResults: + - metrics: + - target: "a" + datapoints: [[0,1],[1,2],[2,3],[2,4],[3,5]] + + # empty + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=b&format=json" + expectedResponse: + httpCode: 200 + contentType: "application/json" + + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=a&target=b&format=json" + expectedResponse: + httpCode: 200 + contentType: "application/json" + expectedResults: + - metrics: + - target: "a" + datapoints: [[0,1],[1,2],[2,3],[2,4],[3,5]] + + # timeout + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=c&format=json" + expectedResponse: + httpCode: 503 + contentType: "text/plain; charset=utf-8" + + # 503 + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=d&format=json" + expectedResponse: + httpCode: 503 + contentType: "text/plain; charset=utf-8" + + # partial success + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=a&target=d&format=json" + ## TODO: must fail, target d failed + # expectedResponse: + # httpCode: 503 + # contentType: "text/plain; charset=utf-8" + expectedResponse: + httpCode: 200 + contentType: "application/json" + expectedResults: + - metrics: + - target: "a" + datapoints: [[0,1],[1,2],[2,3],[2,4],[3,5]] + + # partial success + ## TODO: must fail, target d failed + - endpoint: "http://127.0.0.1:8081" + type: "GET" + URL: "/render/?target=divideSeries(a,d)&format=json" + expectedResponse: + httpCode: 200 + contentType: "application/json" + expectedResults: + - metrics: + - target: "divideSeries(a,MISSING)" + datapoints: [[nan,1],[nan,2],[nan,3],[nan,4],[nan,5]] + +listeners: + - address: ":9070" + expressions: + "a": + pathExpression: "a" + data: + - metricName: "a" + values: [0,1,2,2,3] + + # timeout + "c": + pathExpression: "b" + emptyBody: true + httpCode: 404 + replyDelayMS: 7000 + data: + - metricName: "c" + values: [0,1,2,2,3] + + "d": + pathExpression: "d" + emptyBody: true + httpCode: 503 diff --git a/zipper/broadcast/broadcast_group.go b/zipper/broadcast/broadcast_group.go index 35efed554..f64b3c9b3 100644 --- a/zipper/broadcast/broadcast_group.go +++ b/zipper/broadcast/broadcast_group.go @@ -607,7 +607,8 @@ func (bg *BroadcastGroup) Find(ctx context.Context, request *protov3.MultiGlobRe if len(result.Response.Metrics) == 0 { nonNotFoundErrors := types.ReturnNonNotFoundError(result.Err) if nonNotFoundErrors != nil { - err := types.ErrFailedToFetch.WithHTTPCode(500) + code := helper.MergeHttpErrorsCode(result.Err) + err := types.ErrFailedToFetch.WithHTTPCode(code) for _, e := range nonNotFoundErrors { err = err.WithCause(e) } @@ -818,10 +819,8 @@ func (bg *BroadcastGroup) tagEverything(ctx context.Context, isTagName bool, que var err merry.Error if result.Err != nil { - err = types.ErrNonFatalErrors - for _, e := range result.Err { - err = err.WithCause(e) - } + code := helper.MergeHttpErrorsCode(result.Err) + err = types.ErrFailedToFetch.WithHTTPCode(code) } return result.Response, err diff --git a/zipper/config/config.go b/zipper/config/config.go index f096d22a2..3abf5ad42 100644 --- a/zipper/config/config.go +++ b/zipper/config/config.go @@ -25,6 +25,7 @@ type Config struct { FallbackMaxBatchSize int `mapstructure:"-"` MaxTries int `mapstructure:"maxTries"` DoMultipleRequestsIfSplit bool `mapstructure:"doMultipleRequestsIfSplit"` + RequireSuccessAll bool `mapstructure:"requireSuccessAll"` // require full success for upstreams queries (for multi-target query) ExpireDelaySec int32 TLDCacheDisabled bool `mapstructure:"tldCacheDisabled"` diff --git a/zipper/helper/requests.go b/zipper/helper/requests.go index 6b047a266..6854493eb 100644 --- a/zipper/helper/requests.go +++ b/zipper/helper/requests.go @@ -98,6 +98,41 @@ func requestError(err error, server string) merry.Error { return types.ErrResponceError.WithValue("server", server) } +func MergeHttpErrorsCode(errors []merry.Error) (returnCode int) { + returnCode = http.StatusNotFound + for _, err := range errors { + c := merry.RootCause(err) + if c == nil { + c = err + } + + code := merry.HTTPCode(err) + if code == http.StatusNotFound { + continue + } else if code == http.StatusInternalServerError && merry.Is(c, parser.ErrInvalidArg) { + // check for invalid args, see applyByNode rewrite function + code = http.StatusBadRequest + } + + if code == http.StatusGatewayTimeout || code == http.StatusBadGateway { + // simplify code, one error type for communications errors, all we can retry + code = http.StatusServiceUnavailable + } + + if code == http.StatusBadRequest { + // The 400 is returned on wrong requests, e.g. non-existent functions + returnCode = code + } else if returnCode == http.StatusNotFound || code == http.StatusForbidden { + // First error or access denied (may be limits or other) + returnCode = code + } else if code != http.StatusServiceUnavailable { + returnCode = code + } + } + + return returnCode +} + func MergeHttpErrors(errors []merry.Error) (int, []string) { returnCode := http.StatusNotFound errMsgs := make([]string, 0)