From a2dd310a9ff9e1d1b977f85a5a806465a8613ff0 Mon Sep 17 00:00:00 2001 From: David Tsur Date: Sun, 17 Feb 2019 11:51:23 +0200 Subject: [PATCH 1/6] Adding new API end point for rules and alerts --- cmd/thanos/rule.go | 6 + pkg/rule/api/v1.go | 361 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 367 insertions(+) create mode 100644 pkg/rule/api/v1.go diff --git a/cmd/thanos/rule.go b/cmd/thanos/rule.go index 612e1cf93e..348082a2a2 100644 --- a/cmd/thanos/rule.go +++ b/cmd/thanos/rule.go @@ -10,6 +10,7 @@ import ( "net/url" "os" "os/signal" + "path" "path/filepath" "sort" "strconv" @@ -18,6 +19,8 @@ import ( "syscall" "time" + "github.com/improbable-eng/thanos2/pkg/query/api" + "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/improbable-eng/thanos/pkg/alert" @@ -565,6 +568,9 @@ func runRule( ui.NewRuleUI(logger, mgr, alertQueryURL.String(), flagsMap).Register(router.WithPrefix(webRoutePrefix)) + api := v1.NewAPI(logger, mgr) + api.Register(router.WithPrefix(path.Join(webRoutePrefix, "/api/v1")), tracer, logger) + mux := http.NewServeMux() registerMetrics(mux, reg) registerProfile(mux) diff --git a/pkg/rule/api/v1.go b/pkg/rule/api/v1.go new file mode 100644 index 0000000000..d2b251eb5d --- /dev/null +++ b/pkg/rule/api/v1.go @@ -0,0 +1,361 @@ +package v1 + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/NYTimes/gziphandler" + "github.com/improbable-eng/thanos/pkg/tracing" + "github.com/prometheus/client_golang/prometheus" + + "github.com/go-kit/kit/log" + "github.com/go-kit/kit/log/level" + "github.com/json-iterator/go" + "github.com/opentracing/opentracing-go" + "github.com/prometheus/common/route" + "github.com/prometheus/prometheus/pkg/labels" + "github.com/prometheus/prometheus/rules" + "github.com/prometheus/prometheus/storage" +) + +type status string + +const ( + statusSuccess status = "success" + statusError = "error" +) + +type errorType string + +const ( + errorTimeout = "timeout" + errorCanceled = "canceled" + errorExec = "execution" + errorBadData = "bad_data" + errorInternal = "internal" + errorNotFound = "not_found" +) + +var corsHeaders = map[string]string{ + "Access-Control-Allow-Headers": "Accept, Accept-Encoding, Authorization, Content-Type, Origin", + "Access-Control-Allow-Methods": "GET, OPTIONS", + "Access-Control-Allow-Origin": "*", + "Access-Control-Expose-Headers": "Date", +} + +type apiError struct { + typ errorType + err error +} + +func (e *apiError) Error() string { + return fmt.Sprintf("%s: %s", e.typ, e.err) +} + +type response struct { + Status status `json:"status"` + Data interface{} `json:"data,omitempty"` + ErrorType errorType `json:"errorType,omitempty"` + Error string `json:"error,omitempty"` + Warnings []string `json:"warnings,omitempty"` +} + +func (api *API) respondError(w http.ResponseWriter, apiErr *apiError, data interface{}) { + json := jsoniter.ConfigCompatibleWithStandardLibrary + b, err := json.Marshal(&response{ + Status: statusError, + ErrorType: apiErr.typ, + Error: apiErr.err.Error(), + Data: data, + }) + if err != nil { + level.Error(api.logger).Log("msg", "error marshaling json response", "err", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + var code int + switch apiErr.typ { + case errorBadData: + code = http.StatusBadRequest + case errorExec: + code = 422 + case errorCanceled, errorTimeout: + code = http.StatusServiceUnavailable + case errorInternal: + code = http.StatusInternalServerError + case errorNotFound: + code = http.StatusNotFound + default: + code = http.StatusInternalServerError + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(code) + if n, err := w.Write(b); err != nil { + level.Error(api.logger).Log("msg", "error writing response", "bytesWritten", n, "err", err) + } +} + +//type apiFunc func(r *http.Request) apiFuncResult +type apiFunc func(r *http.Request) (interface{}, []error, *apiError) + +// API can register a set of endpoints in a router and handle +// them using the provided storage and query engine. +type API struct { + logger log.Logger + now func() time.Time + rulesRetriever rulesRetriever +} + +// NewAPI returns an initialized API type. +func NewAPI( + logger log.Logger, + rr rulesRetriever, +) *API { + return &API{ + logger: logger, + now: time.Now, + rulesRetriever: rr, + } +} + +// Register the API's endpoints in the given router. +func (api *API) Register(r *route.Router, tracer opentracing.Tracer, logger log.Logger) { + instr := func(name string, f apiFunc) http.HandlerFunc { + hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if data, warnings, err := f(r); err != nil { + respondError(w, err, data) + } else if data != nil { + respond(w, data, warnings) + } else { + w.WriteHeader(http.StatusNoContent) + } + }) + return prometheus.InstrumentHandler(name, tracing.HTTPMiddleware(tracer, name, logger, gziphandler.GzipHandler(hf))) + } + + r.Get("/alerts", instr("alerts", api.alerts)) + r.Get("/rules", instr("rules", api.rules)) + +} + +func (api *API) respond(w http.ResponseWriter, data interface{}, warnings storage.Warnings) { + statusMessage := statusSuccess + var warningStrings []string + for _, warning := range warnings { + warningStrings = append(warningStrings, warning.Error()) + } + json := jsoniter.ConfigCompatibleWithStandardLibrary + b, err := json.Marshal(&response{ + Status: statusMessage, + Data: data, + Warnings: warningStrings, + }) + if err != nil { + level.Error(api.logger).Log("msg", "error marshaling json response", "err", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if n, err := w.Write(b); err != nil { + level.Error(api.logger).Log("msg", "error writing response", "bytesWritten", n, "err", err) + } +} + +type rulesRetriever interface { + RuleGroups() []*rules.Group + AlertingRules() []*rules.AlertingRule +} + +func (api *API) rules(r *http.Request) (interface{}, []error, *apiError) { + ruleGroups := api.rulesRetriever.RuleGroups() + res := &RuleDiscovery{RuleGroups: make([]*RuleGroup, len(ruleGroups))} + for i, grp := range ruleGroups { + apiRuleGroup := &RuleGroup{ + Name: grp.Name(), + File: grp.File(), + Interval: grp.Interval().Seconds(), + Rules: []rule{}, + } + + for _, r := range grp.Rules() { + var enrichedRule rule + + lastError := "" + if r.LastError() != nil { + lastError = r.LastError().Error() + } + + switch rule := r.(type) { + case *rules.AlertingRule: + enrichedRule = alertingRule{ + Name: rule.Name(), + Query: rule.Query().String(), + Duration: rule.Duration().Seconds(), + Labels: rule.Labels(), + Annotations: rule.Annotations(), + Alerts: rulesAlertsToAPIAlerts(rule.ActiveAlerts()), + Health: rule.Health(), + LastError: lastError, + Type: "alerting", + } + case *rules.RecordingRule: + enrichedRule = recordingRule{ + Name: rule.Name(), + Query: rule.Query().String(), + Labels: rule.Labels(), + Health: rule.Health(), + LastError: lastError, + Type: "recording", + } + default: + err := fmt.Errorf("failed to assert type of rule '%v'", rule.Name()) + return nil, nil, &apiError{errorInternal, err} + } + + apiRuleGroup.Rules = append(apiRuleGroup.Rules, enrichedRule) + } + res.RuleGroups[i] = apiRuleGroup + } + return res, nil, nil +} + +func (api *API) alerts(r *http.Request) (interface{}, []error, *apiError) { + alertingRules := api.rulesRetriever.AlertingRules() + alerts := []*Alert{} + + for _, alertingRule := range alertingRules { + alerts = append( + alerts, + rulesAlertsToAPIAlerts(alertingRule.ActiveAlerts())..., + ) + } + + res := &AlertDiscovery{Alerts: alerts} + + return res, nil, nil +} + +type AlertDiscovery struct { + Alerts []*Alert `json:"alerts"` +} + +// Alert has info for an alert. +type Alert struct { + Labels labels.Labels `json:"labels"` + Annotations labels.Labels `json:"annotations"` + State string `json:"state"` + ActiveAt *time.Time `json:"activeAt,omitempty"` + Value float64 `json:"value"` +} + +func rulesAlertsToAPIAlerts(rulesAlerts []*rules.Alert) []*Alert { + apiAlerts := make([]*Alert, len(rulesAlerts)) + for i, ruleAlert := range rulesAlerts { + apiAlerts[i] = &Alert{ + Labels: ruleAlert.Labels, + Annotations: ruleAlert.Annotations, + State: ruleAlert.State.String(), + ActiveAt: &ruleAlert.ActiveAt, + Value: ruleAlert.Value, + } + } + + return apiAlerts +} + +// RuleDiscovery has info for all rules +type RuleDiscovery struct { + RuleGroups []*RuleGroup `json:"groups"` +} + +type apiFuncResult struct { + data interface{} + err *apiError + warnings storage.Warnings + finalizer func() +} + +// RuleGroup has info for rules which are part of a group +type RuleGroup struct { + Name string `json:"name"` + File string `json:"file"` + // In order to preserve rule ordering, while exposing type (alerting or recording) + // specific properties, both alerting and recording rules are exposed in the + // same array. + Rules []rule `json:"rules"` + Interval float64 `json:"interval"` +} + +type rule interface{} + +type alertingRule struct { + Name string `json:"name"` + Query string `json:"query"` + Duration float64 `json:"duration"` + Labels labels.Labels `json:"labels"` + Annotations labels.Labels `json:"annotations"` + Alerts []*Alert `json:"alerts"` + Health rules.RuleHealth `json:"health"` + LastError string `json:"lastError,omitempty"` + Type string `json:"type"` +} + +type recordingRule struct { + Name string `json:"name"` + Query string `json:"query"` + Labels labels.Labels `json:"labels,omitempty"` + Health rules.RuleHealth `json:"health"` + LastError string `json:"lastError,omitempty"` + // Type of a recordingRule is always "recording". + Type string `json:"type"` +} + +func (api *API) options(r *http.Request) (interface{}, []error, *apiError) { + return nil, nil, nil +} + +func respond(w http.ResponseWriter, data interface{}, warnings []error) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + + resp := &response{ + Status: statusSuccess, + Data: data, + } + for _, warn := range warnings { + resp.Warnings = append(resp.Warnings, warn.Error()) + } + _ = json.NewEncoder(w).Encode(resp) +} + +func respondError(w http.ResponseWriter, apiErr *apiError, data interface{}) { + w.Header().Set("Content-Type", "application/json") + + var code int + switch apiErr.typ { + case errorBadData: + code = http.StatusBadRequest + case errorExec: + code = 422 + case errorCanceled, errorTimeout: + code = http.StatusServiceUnavailable + case errorInternal: + code = http.StatusInternalServerError + default: + code = http.StatusInternalServerError + } + w.WriteHeader(code) + + _ = json.NewEncoder(w).Encode(&response{ + Status: statusError, + ErrorType: apiErr.typ, + Error: apiErr.err.Error(), + Data: data, + }) +} From 77e4aea8aca88edbf8f070fd63171e113cda3f4a Mon Sep 17 00:00:00 2001 From: David Tsur Date: Sun, 17 Feb 2019 14:57:08 +0200 Subject: [PATCH 2/6] reusing methods in query api --- benchmark/cmd/thanosbench/resources.go | 5 +- cmd/thanos/rule.go | 3 +- pkg/query/api/v1.go | 120 ++++++++--------- pkg/query/api/v1_test.go | 12 +- pkg/rule/api/v1.go | 175 +------------------------ 5 files changed, 77 insertions(+), 238 deletions(-) diff --git a/benchmark/cmd/thanosbench/resources.go b/benchmark/cmd/thanosbench/resources.go index 063c4e6607..f9da1c7ac0 100644 --- a/benchmark/cmd/thanosbench/resources.go +++ b/benchmark/cmd/thanosbench/resources.go @@ -12,6 +12,7 @@ import ( prom "github.com/prometheus/prometheus/config" "gopkg.in/yaml.v2" appsv1 "k8s.io/api/apps/v1" + "k8s.io/api/core/v1" rbacv1 "k8s.io/api/rbac/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -207,7 +208,7 @@ func createPrometheus(opts *opts, name string, bucket string) *appsv1.StatefulSe Name: name, Namespace: promNamespace, Labels: map[string]string{ - "app": name, + "app": name, "thanos-gossip-member": "true", }, } @@ -370,7 +371,7 @@ func createThanosQuery(opts *opts) (*v1.Service, *v1.Pod) { Name: "thanos-query", Namespace: thanosNamespace, Labels: map[string]string{ - "app": "thanos-query", + "app": "thanos-query", "thanos-gossip-member": "true", }, } diff --git a/cmd/thanos/rule.go b/cmd/thanos/rule.go index 348082a2a2..a3847105b4 100644 --- a/cmd/thanos/rule.go +++ b/cmd/thanos/rule.go @@ -19,8 +19,6 @@ import ( "syscall" "time" - "github.com/improbable-eng/thanos2/pkg/query/api" - "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/improbable-eng/thanos/pkg/alert" @@ -32,6 +30,7 @@ import ( "github.com/improbable-eng/thanos/pkg/extprom" "github.com/improbable-eng/thanos/pkg/objstore/client" "github.com/improbable-eng/thanos/pkg/promclient" + "github.com/improbable-eng/thanos/pkg/rule/api" "github.com/improbable-eng/thanos/pkg/runutil" "github.com/improbable-eng/thanos/pkg/shipper" "github.com/improbable-eng/thanos/pkg/store" diff --git a/pkg/query/api/v1.go b/pkg/query/api/v1.go index cf693dd2fb..02be24c40d 100644 --- a/pkg/query/api/v1.go +++ b/pkg/query/api/v1.go @@ -50,15 +50,15 @@ const ( statusError = "error" ) -type errorType string +type ErrorType string const ( - errorNone errorType = "" + errorNone ErrorType = "" errorTimeout = "timeout" errorCanceled = "canceled" errorExec = "execution" errorBadData = "bad_data" - errorInternal = "internal" + ErrorInternal = "internal" ) var corsHeaders = map[string]string{ @@ -68,19 +68,19 @@ var corsHeaders = map[string]string{ "Access-Control-Expose-Headers": "Date", } -type apiError struct { - typ errorType - err error +type ApiError struct { + Typ ErrorType + Err error } -func (e *apiError) Error() string { - return fmt.Sprintf("%s: %s", e.typ, e.err) +func (e *ApiError) Error() string { + return fmt.Sprintf("%s: %s", e.Typ, e.Err) } type response struct { Status status `json:"status"` Data interface{} `json:"data,omitempty"` - ErrorType errorType `json:"errorType,omitempty"` + ErrorType ErrorType `json:"ErrorType,omitempty"` Error string `json:"error,omitempty"` Warnings []string `json:"warnings,omitempty"` } @@ -92,7 +92,7 @@ func setCORS(w http.ResponseWriter) { } } -type apiFunc func(r *http.Request) (interface{}, []error, *apiError) +type ApiFunc func(r *http.Request) (interface{}, []error, *ApiError) // API can register a set of endpoints in a router and handle // them using the provided storage and query engine. @@ -151,13 +151,13 @@ func NewAPI( // Register the API's endpoints in the given router. func (api *API) Register(r *route.Router, tracer opentracing.Tracer, logger log.Logger) { - instr := func(name string, f apiFunc) http.HandlerFunc { + instr := func(name string, f ApiFunc) http.HandlerFunc { hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { setCORS(w) if data, warnings, err := f(r); err != nil { - respondError(w, err, data) + RespondError(w, err, data) } else if data != nil { - respond(w, data, warnings) + Respond(w, data, warnings) } else { w.WriteHeader(http.StatusNoContent) } @@ -183,7 +183,7 @@ type queryData struct { Warnings []error `json:"warnings,omitempty"` } -func (api *API) parseEnableDedupParam(r *http.Request) (enableDeduplication bool, _ *apiError) { +func (api *API) parseEnableDedupParam(r *http.Request) (enableDeduplication bool, _ *ApiError) { const dedupParam = "dedup" enableDeduplication = true @@ -191,13 +191,13 @@ func (api *API) parseEnableDedupParam(r *http.Request) (enableDeduplication bool var err error enableDeduplication, err = strconv.ParseBool(val) if err != nil { - return false, &apiError{errorBadData, errors.Wrapf(err, "'%s' parameter", dedupParam)} + return false, &ApiError{errorBadData, errors.Wrapf(err, "'%s' parameter", dedupParam)} } } return enableDeduplication, nil } -func (api *API) parseDownsamplingParam(r *http.Request, step time.Duration) (maxSourceResolution time.Duration, _ *apiError) { +func (api *API) parseDownsamplingParam(r *http.Request, step time.Duration) (maxSourceResolution time.Duration, _ *ApiError) { const maxSourceResolutionParam = "max_source_resolution" maxSourceResolution = 0 * time.Second @@ -209,18 +209,18 @@ func (api *API) parseDownsamplingParam(r *http.Request, step time.Duration) (max var err error maxSourceResolution, err = parseDuration(val) if err != nil { - return 0, &apiError{errorBadData, errors.Wrapf(err, "'%s' parameter", maxSourceResolutionParam)} + return 0, &ApiError{errorBadData, errors.Wrapf(err, "'%s' parameter", maxSourceResolutionParam)} } } if maxSourceResolution < 0 { - return 0, &apiError{errorBadData, errors.Errorf("negative '%s' is not accepted. Try a positive integer", maxSourceResolutionParam)} + return 0, &ApiError{errorBadData, errors.Errorf("negative '%s' is not accepted. Try a positive integer", maxSourceResolutionParam)} } return maxSourceResolution, nil } -func (api *API) parsePartialResponseParam(r *http.Request) (enablePartialResponse bool, _ *apiError) { +func (api *API) parsePartialResponseParam(r *http.Request) (enablePartialResponse bool, _ *ApiError) { const partialResponseParam = "partial_response" enablePartialResponse = api.enablePartialResponse @@ -228,23 +228,23 @@ func (api *API) parsePartialResponseParam(r *http.Request) (enablePartialRespons var err error enablePartialResponse, err = strconv.ParseBool(val) if err != nil { - return false, &apiError{errorBadData, errors.Wrapf(err, "'%s' parameter", partialResponseParam)} + return false, &ApiError{errorBadData, errors.Wrapf(err, "'%s' parameter", partialResponseParam)} } } return enablePartialResponse, nil } -func (api *API) options(r *http.Request) (interface{}, []error, *apiError) { +func (api *API) options(r *http.Request) (interface{}, []error, *ApiError) { return nil, nil, nil } -func (api *API) query(r *http.Request) (interface{}, []error, *apiError) { +func (api *API) query(r *http.Request) (interface{}, []error, *ApiError) { var ts time.Time if t := r.FormValue("time"); t != "" { var err error ts, err = parseTime(t) if err != nil { - return nil, nil, &apiError{errorBadData, err} + return nil, nil, &ApiError{errorBadData, err} } } else { ts = api.now() @@ -255,7 +255,7 @@ func (api *API) query(r *http.Request) (interface{}, []error, *apiError) { var cancel context.CancelFunc timeout, err := parseDuration(to) if err != nil { - return nil, nil, &apiError{errorBadData, err} + return nil, nil, &ApiError{errorBadData, err} } ctx, cancel = context.WithTimeout(ctx, timeout) @@ -289,20 +289,20 @@ func (api *API) query(r *http.Request) (interface{}, []error, *apiError) { begin := api.now() qry, err := api.queryEngine.NewInstantQuery(api.queryableCreate(enableDedup, 0, enablePartialResponse, warningReporter), r.FormValue("query"), ts) if err != nil { - return nil, nil, &apiError{errorBadData, err} + return nil, nil, &ApiError{errorBadData, err} } res := qry.Exec(ctx) if res.Err != nil { switch res.Err.(type) { case promql.ErrQueryCanceled: - return nil, nil, &apiError{errorCanceled, res.Err} + return nil, nil, &ApiError{errorCanceled, res.Err} case promql.ErrQueryTimeout: - return nil, nil, &apiError{errorTimeout, res.Err} + return nil, nil, &ApiError{errorTimeout, res.Err} case promql.ErrStorage: - return nil, nil, &apiError{errorInternal, res.Err} + return nil, nil, &ApiError{ErrorInternal, res.Err} } - return nil, nil, &apiError{errorExec, res.Err} + return nil, nil, &ApiError{errorExec, res.Err} } api.instantQueryDuration.Observe(time.Since(begin).Seconds()) @@ -312,35 +312,35 @@ func (api *API) query(r *http.Request) (interface{}, []error, *apiError) { }, warnings, nil } -func (api *API) queryRange(r *http.Request) (interface{}, []error, *apiError) { +func (api *API) queryRange(r *http.Request) (interface{}, []error, *ApiError) { start, err := parseTime(r.FormValue("start")) if err != nil { - return nil, nil, &apiError{errorBadData, err} + return nil, nil, &ApiError{errorBadData, err} } end, err := parseTime(r.FormValue("end")) if err != nil { - return nil, nil, &apiError{errorBadData, err} + return nil, nil, &ApiError{errorBadData, err} } if end.Before(start) { err := errors.New("end timestamp must not be before start time") - return nil, nil, &apiError{errorBadData, err} + return nil, nil, &ApiError{errorBadData, err} } step, err := parseDuration(r.FormValue("step")) if err != nil { - return nil, nil, &apiError{errorBadData, errors.Wrap(err, "param step")} + return nil, nil, &ApiError{errorBadData, errors.Wrap(err, "param step")} } if step <= 0 { err := errors.New("zero or negative query resolution step widths are not accepted. Try a positive integer") - return nil, nil, &apiError{errorBadData, err} + return nil, nil, &ApiError{errorBadData, err} } // For safety, limit the number of returned points per timeseries. // This is sufficient for 60s resolution for a week or 1h resolution for a year. if end.Sub(start)/step > 11000 { err := errors.Errorf("exceeded maximum resolution of 11,000 points per timeseries. Try decreasing the query resolution (?step=XX)") - return nil, nil, &apiError{errorBadData, err} + return nil, nil, &ApiError{errorBadData, err} } ctx := r.Context() @@ -348,7 +348,7 @@ func (api *API) queryRange(r *http.Request) (interface{}, []error, *apiError) { var cancel context.CancelFunc timeout, err := parseDuration(to) if err != nil { - return nil, nil, &apiError{errorBadData, err} + return nil, nil, &ApiError{errorBadData, err} } ctx, cancel = context.WithTimeout(ctx, timeout) @@ -393,18 +393,18 @@ func (api *API) queryRange(r *http.Request) (interface{}, []error, *apiError) { step, ) if err != nil { - return nil, nil, &apiError{errorBadData, err} + return nil, nil, &ApiError{errorBadData, err} } res := qry.Exec(ctx) if res.Err != nil { switch res.Err.(type) { case promql.ErrQueryCanceled: - return nil, nil, &apiError{errorCanceled, res.Err} + return nil, nil, &ApiError{errorCanceled, res.Err} case promql.ErrQueryTimeout: - return nil, nil, &apiError{errorTimeout, res.Err} + return nil, nil, &ApiError{errorTimeout, res.Err} } - return nil, nil, &apiError{errorExec, res.Err} + return nil, nil, &ApiError{errorExec, res.Err} } api.rangeQueryDuration.Observe(time.Since(begin).Seconds()) @@ -414,12 +414,12 @@ func (api *API) queryRange(r *http.Request) (interface{}, []error, *apiError) { }, warnings, nil } -func (api *API) labelValues(r *http.Request) (interface{}, []error, *apiError) { +func (api *API) labelValues(r *http.Request) (interface{}, []error, *ApiError) { ctx := r.Context() name := route.Param(ctx, "name") if !model.LabelNameRE.MatchString(name) { - return nil, nil, &apiError{errorBadData, fmt.Errorf("invalid label name: %q", name)} + return nil, nil, &ApiError{errorBadData, fmt.Errorf("invalid label name: %q", name)} } enablePartialResponse, apiErr := api.parsePartialResponseParam(r) @@ -439,7 +439,7 @@ func (api *API) labelValues(r *http.Request) (interface{}, []error, *apiError) { q, err := api.queryableCreate(true, 0, enablePartialResponse, warningReporter).Querier(ctx, math.MinInt64, math.MaxInt64) if err != nil { - return nil, nil, &apiError{errorExec, err} + return nil, nil, &ApiError{errorExec, err} } defer runutil.CloseWithLogOnErr(api.logger, q, "queryable labelValues") @@ -447,7 +447,7 @@ func (api *API) labelValues(r *http.Request) (interface{}, []error, *apiError) { vals, err := q.LabelValues(name) if err != nil { - return nil, nil, &apiError{errorExec, err} + return nil, nil, &ApiError{errorExec, err} } return vals, warnings, nil @@ -458,13 +458,13 @@ var ( maxTime = time.Unix(math.MaxInt64/1000-62135596801, 999999999) ) -func (api *API) series(r *http.Request) (interface{}, []error, *apiError) { +func (api *API) series(r *http.Request) (interface{}, []error, *ApiError) { if err := r.ParseForm(); err != nil { - return nil, nil, &apiError{errorInternal, errors.Wrap(err, "parse form")} + return nil, nil, &ApiError{ErrorInternal, errors.Wrap(err, "parse form")} } if len(r.Form["match[]"]) == 0 { - return nil, nil, &apiError{errorBadData, fmt.Errorf("no match[] parameter provided")} + return nil, nil, &ApiError{errorBadData, fmt.Errorf("no match[] parameter provided")} } var start time.Time @@ -472,7 +472,7 @@ func (api *API) series(r *http.Request) (interface{}, []error, *apiError) { var err error start, err = parseTime(t) if err != nil { - return nil, nil, &apiError{errorBadData, err} + return nil, nil, &ApiError{errorBadData, err} } } else { start = minTime @@ -483,7 +483,7 @@ func (api *API) series(r *http.Request) (interface{}, []error, *apiError) { var err error end, err = parseTime(t) if err != nil { - return nil, nil, &apiError{errorBadData, err} + return nil, nil, &ApiError{errorBadData, err} } } else { end = maxTime @@ -493,7 +493,7 @@ func (api *API) series(r *http.Request) (interface{}, []error, *apiError) { for _, s := range r.Form["match[]"] { matchers, err := promql.ParseMetricSelector(s) if err != nil { - return nil, nil, &apiError{errorBadData, err} + return nil, nil, &ApiError{errorBadData, err} } matcherSets = append(matcherSets, matchers) } @@ -521,7 +521,7 @@ func (api *API) series(r *http.Request) (interface{}, []error, *apiError) { // TODO(bwplotka): Support downsampling? q, err := api.queryableCreate(enableDedup, 0, enablePartialResponse, warningReporter).Querier(r.Context(), timestamp.FromTime(start), timestamp.FromTime(end)) if err != nil { - return nil, nil, &apiError{errorExec, err} + return nil, nil, &ApiError{errorExec, err} } defer runutil.CloseWithLogOnErr(api.logger, q, "queryable series") @@ -529,7 +529,7 @@ func (api *API) series(r *http.Request) (interface{}, []error, *apiError) { for _, mset := range matcherSets { s, _, err := q.Select(&storage.SelectParams{}, mset...) if err != nil { - return nil, nil, &apiError{errorExec, err} + return nil, nil, &ApiError{errorExec, err} } sets = append(sets, s) } @@ -541,13 +541,13 @@ func (api *API) series(r *http.Request) (interface{}, []error, *apiError) { metrics = append(metrics, set.At().Labels()) } if set.Err() != nil { - return nil, nil, &apiError{errorExec, set.Err()} + return nil, nil, &ApiError{errorExec, set.Err()} } return metrics, warnings, nil } -func respond(w http.ResponseWriter, data interface{}, warnings []error) { +func Respond(w http.ResponseWriter, data interface{}, warnings []error) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) @@ -561,18 +561,18 @@ func respond(w http.ResponseWriter, data interface{}, warnings []error) { _ = json.NewEncoder(w).Encode(resp) } -func respondError(w http.ResponseWriter, apiErr *apiError, data interface{}) { +func RespondError(w http.ResponseWriter, apiErr *ApiError, data interface{}) { w.Header().Set("Content-Type", "application/json") var code int - switch apiErr.typ { + switch apiErr.Typ { case errorBadData: code = http.StatusBadRequest case errorExec: code = 422 case errorCanceled, errorTimeout: code = http.StatusServiceUnavailable - case errorInternal: + case ErrorInternal: code = http.StatusInternalServerError default: code = http.StatusInternalServerError @@ -581,8 +581,8 @@ func respondError(w http.ResponseWriter, apiErr *apiError, data interface{}) { _ = json.NewEncoder(w).Encode(&response{ Status: statusError, - ErrorType: apiErr.typ, - Error: apiErr.err.Error(), + ErrorType: apiErr.Typ, + Error: apiErr.Err.Error(), Data: data, }) } diff --git a/pkg/query/api/v1_test.go b/pkg/query/api/v1_test.go index bcf17907b0..ca5dee2013 100644 --- a/pkg/query/api/v1_test.go +++ b/pkg/query/api/v1_test.go @@ -77,11 +77,11 @@ func TestEndpoints(t *testing.T) { start := time.Unix(0, 0) var tests = []struct { - endpoint apiFunc + endpoint ApiFunc params map[string]string query url.Values response interface{} - errType errorType + errType ErrorType }{ { endpoint: api.query, @@ -425,8 +425,8 @@ func TestEndpoints(t *testing.T) { if test.errType == errorNone { t.Fatalf("Unexpected error: %s", apiErr) } - if test.errType != apiErr.typ { - t.Fatalf("Expected error of type %q but got type %q", test.errType, apiErr.typ) + if test.errType != apiErr.Typ { + t.Fatalf("Expected error of type %q but got type %q", test.errType, apiErr.Typ) } return } @@ -446,7 +446,7 @@ func TestEndpoints(t *testing.T) { func TestRespondSuccess(t *testing.T) { s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - respond(w, "test", nil) + Respond(w, "test", nil) })) defer s.Close() @@ -483,7 +483,7 @@ func TestRespondSuccess(t *testing.T) { func TestRespondError(t *testing.T) { s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - respondError(w, &apiError{errorTimeout, errors.New("message")}, "test") + RespondError(w, &ApiError{errorTimeout, errors.New("message")}, "test") })) defer s.Close() diff --git a/pkg/rule/api/v1.go b/pkg/rule/api/v1.go index d2b251eb5d..a464099889 100644 --- a/pkg/rule/api/v1.go +++ b/pkg/rule/api/v1.go @@ -1,107 +1,22 @@ package v1 import ( - "encoding/json" "fmt" "net/http" "time" "github.com/NYTimes/gziphandler" + qapi "github.com/improbable-eng/thanos/pkg/query/api" "github.com/improbable-eng/thanos/pkg/tracing" "github.com/prometheus/client_golang/prometheus" "github.com/go-kit/kit/log" - "github.com/go-kit/kit/log/level" - "github.com/json-iterator/go" "github.com/opentracing/opentracing-go" "github.com/prometheus/common/route" "github.com/prometheus/prometheus/pkg/labels" "github.com/prometheus/prometheus/rules" - "github.com/prometheus/prometheus/storage" ) -type status string - -const ( - statusSuccess status = "success" - statusError = "error" -) - -type errorType string - -const ( - errorTimeout = "timeout" - errorCanceled = "canceled" - errorExec = "execution" - errorBadData = "bad_data" - errorInternal = "internal" - errorNotFound = "not_found" -) - -var corsHeaders = map[string]string{ - "Access-Control-Allow-Headers": "Accept, Accept-Encoding, Authorization, Content-Type, Origin", - "Access-Control-Allow-Methods": "GET, OPTIONS", - "Access-Control-Allow-Origin": "*", - "Access-Control-Expose-Headers": "Date", -} - -type apiError struct { - typ errorType - err error -} - -func (e *apiError) Error() string { - return fmt.Sprintf("%s: %s", e.typ, e.err) -} - -type response struct { - Status status `json:"status"` - Data interface{} `json:"data,omitempty"` - ErrorType errorType `json:"errorType,omitempty"` - Error string `json:"error,omitempty"` - Warnings []string `json:"warnings,omitempty"` -} - -func (api *API) respondError(w http.ResponseWriter, apiErr *apiError, data interface{}) { - json := jsoniter.ConfigCompatibleWithStandardLibrary - b, err := json.Marshal(&response{ - Status: statusError, - ErrorType: apiErr.typ, - Error: apiErr.err.Error(), - Data: data, - }) - if err != nil { - level.Error(api.logger).Log("msg", "error marshaling json response", "err", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - var code int - switch apiErr.typ { - case errorBadData: - code = http.StatusBadRequest - case errorExec: - code = 422 - case errorCanceled, errorTimeout: - code = http.StatusServiceUnavailable - case errorInternal: - code = http.StatusInternalServerError - case errorNotFound: - code = http.StatusNotFound - default: - code = http.StatusInternalServerError - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(code) - if n, err := w.Write(b); err != nil { - level.Error(api.logger).Log("msg", "error writing response", "bytesWritten", n, "err", err) - } -} - -//type apiFunc func(r *http.Request) apiFuncResult -type apiFunc func(r *http.Request) (interface{}, []error, *apiError) - // API can register a set of endpoints in a router and handle // them using the provided storage and query engine. type API struct { @@ -124,12 +39,12 @@ func NewAPI( // Register the API's endpoints in the given router. func (api *API) Register(r *route.Router, tracer opentracing.Tracer, logger log.Logger) { - instr := func(name string, f apiFunc) http.HandlerFunc { + instr := func(name string, f qapi.ApiFunc) http.HandlerFunc { hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if data, warnings, err := f(r); err != nil { - respondError(w, err, data) + qapi.RespondError(w, err, data) } else if data != nil { - respond(w, data, warnings) + qapi.Respond(w, data, warnings) } else { w.WriteHeader(http.StatusNoContent) } @@ -142,37 +57,12 @@ func (api *API) Register(r *route.Router, tracer opentracing.Tracer, logger log. } -func (api *API) respond(w http.ResponseWriter, data interface{}, warnings storage.Warnings) { - statusMessage := statusSuccess - var warningStrings []string - for _, warning := range warnings { - warningStrings = append(warningStrings, warning.Error()) - } - json := jsoniter.ConfigCompatibleWithStandardLibrary - b, err := json.Marshal(&response{ - Status: statusMessage, - Data: data, - Warnings: warningStrings, - }) - if err != nil { - level.Error(api.logger).Log("msg", "error marshaling json response", "err", err) - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - if n, err := w.Write(b); err != nil { - level.Error(api.logger).Log("msg", "error writing response", "bytesWritten", n, "err", err) - } -} - type rulesRetriever interface { RuleGroups() []*rules.Group AlertingRules() []*rules.AlertingRule } -func (api *API) rules(r *http.Request) (interface{}, []error, *apiError) { +func (api *API) rules(r *http.Request) (interface{}, []error, *qapi.ApiError) { ruleGroups := api.rulesRetriever.RuleGroups() res := &RuleDiscovery{RuleGroups: make([]*RuleGroup, len(ruleGroups))} for i, grp := range ruleGroups { @@ -215,7 +105,7 @@ func (api *API) rules(r *http.Request) (interface{}, []error, *apiError) { } default: err := fmt.Errorf("failed to assert type of rule '%v'", rule.Name()) - return nil, nil, &apiError{errorInternal, err} + return nil, nil, &qapi.ApiError{qapi.ErrorInternal, err} } apiRuleGroup.Rules = append(apiRuleGroup.Rules, enrichedRule) @@ -225,7 +115,7 @@ func (api *API) rules(r *http.Request) (interface{}, []error, *apiError) { return res, nil, nil } -func (api *API) alerts(r *http.Request) (interface{}, []error, *apiError) { +func (api *API) alerts(r *http.Request) (interface{}, []error, *qapi.ApiError) { alertingRules := api.rulesRetriever.AlertingRules() alerts := []*Alert{} @@ -274,13 +164,6 @@ type RuleDiscovery struct { RuleGroups []*RuleGroup `json:"groups"` } -type apiFuncResult struct { - data interface{} - err *apiError - warnings storage.Warnings - finalizer func() -} - // RuleGroup has info for rules which are part of a group type RuleGroup struct { Name string `json:"name"` @@ -315,47 +198,3 @@ type recordingRule struct { // Type of a recordingRule is always "recording". Type string `json:"type"` } - -func (api *API) options(r *http.Request) (interface{}, []error, *apiError) { - return nil, nil, nil -} - -func respond(w http.ResponseWriter, data interface{}, warnings []error) { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - - resp := &response{ - Status: statusSuccess, - Data: data, - } - for _, warn := range warnings { - resp.Warnings = append(resp.Warnings, warn.Error()) - } - _ = json.NewEncoder(w).Encode(resp) -} - -func respondError(w http.ResponseWriter, apiErr *apiError, data interface{}) { - w.Header().Set("Content-Type", "application/json") - - var code int - switch apiErr.typ { - case errorBadData: - code = http.StatusBadRequest - case errorExec: - code = 422 - case errorCanceled, errorTimeout: - code = http.StatusServiceUnavailable - case errorInternal: - code = http.StatusInternalServerError - default: - code = http.StatusInternalServerError - } - w.WriteHeader(code) - - _ = json.NewEncoder(w).Encode(&response{ - Status: statusError, - ErrorType: apiErr.typ, - Error: apiErr.err.Error(), - Data: data, - }) -} From ed90797f452941de069bdc4a2e412fcaee3addc9 Mon Sep 17 00:00:00 2001 From: David Tsur Date: Sun, 17 Feb 2019 23:08:11 +0200 Subject: [PATCH 3/6] Adding unitest for rule API (similar to prometheus rule/alert API end point unitest) --- pkg/rule/api/v1_test.go | 242 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 242 insertions(+) create mode 100644 pkg/rule/api/v1_test.go diff --git a/pkg/rule/api/v1_test.go b/pkg/rule/api/v1_test.go new file mode 100644 index 0000000000..dfd8f49c22 --- /dev/null +++ b/pkg/rule/api/v1_test.go @@ -0,0 +1,242 @@ +package v1 + +import ( + "context" + "encoding/json" + "fmt" + "github.com/go-kit/kit/log" + qapi "github.com/improbable-eng/thanos/pkg/query/api" + "github.com/prometheus/common/route" + "github.com/prometheus/prometheus/pkg/labels" + "github.com/prometheus/prometheus/promql" + "github.com/prometheus/prometheus/rules" + "github.com/prometheus/prometheus/util/testutil" + "net/http" + "net/url" + "reflect" + "testing" + "time" +) + +type rulesRetrieverMock struct { + testing *testing.T +} + +func (m rulesRetrieverMock) RuleGroups() []*rules.Group { + var ar rulesRetrieverMock + arules := ar.AlertingRules() + storage := testutil.NewStorage(m.testing) + defer storage.Close() + + engineOpts := promql.EngineOpts{ + Logger: nil, + Reg: nil, + MaxConcurrent: 10, + MaxSamples: 10, + Timeout: 100 * time.Second, + } + + engine := promql.NewEngine(engineOpts) + opts := &rules.ManagerOptions{ + QueryFunc: rules.EngineQueryFunc(engine, storage), + Appendable: storage, + Context: context.Background(), + Logger: log.NewNopLogger(), + } + + var r []rules.Rule + + for _, alertrule := range arules { + r = append(r, alertrule) + } + + recordingExpr, err := promql.ParseExpr(`vector(1)`) + if err != nil { + m.testing.Fatalf("unable to parse alert expression: %s", err) + } + recordingRule := rules.NewRecordingRule("recording-rule-1", recordingExpr, labels.Labels{}) + r = append(r, recordingRule) + + group := rules.NewGroup("grp", "/path/to/file", time.Second, r, false, opts) + return []*rules.Group{group} +} + +func (m rulesRetrieverMock) AlertingRules() []*rules.AlertingRule { + expr1, err := promql.ParseExpr(`absent(test_metric3) != 1`) + if err != nil { + m.testing.Fatalf("unable to parse alert expression: %s", err) + } + expr2, err := promql.ParseExpr(`up == 1`) + if err != nil { + m.testing.Fatalf("Unable to parse alert expression: %s", err) + } + + rule1 := rules.NewAlertingRule( + "test_metric3", + expr1, + time.Second, + labels.Labels{}, + labels.Labels{}, + true, + log.NewNopLogger(), + ) + rule2 := rules.NewAlertingRule( + "test_metric4", + expr2, + time.Second, + labels.Labels{}, + labels.Labels{}, + true, + log.NewNopLogger(), + ) + var r []*rules.AlertingRule + r = append(r, rule1) + r = append(r, rule2) + return r +} + +func TestEndpoints(t *testing.T) { + suite, err := promql.NewTest(t, ` + load 1m + test_metric1{foo="bar"} 0+100x100 + test_metric1{foo="boo"} 1+0x100 + test_metric2{foo="boo"} 1+0x100 + `) + if err != nil { + t.Fatal(err) + } + defer suite.Close() + + if err := suite.Run(); err != nil { + t.Fatal(err) + } + + var algr rulesRetrieverMock + algr.testing = t + algr.AlertingRules() + algr.RuleGroups() + + t.Run("local", func(t *testing.T) { + var algr rulesRetrieverMock + algr.testing = t + algr.AlertingRules() + algr.RuleGroups() + api := NewAPI(nil, algr) + testEndpoints(t, api) + }) +} + +func testEndpoints(t *testing.T, api *API) { + + type test struct { + endpoint qapi.ApiFunc + params map[string]string + query url.Values + response interface{} + errType qapi.ErrorType + } + var tests = []test{ + { + endpoint: api.rules, + response: &RuleDiscovery{ + RuleGroups: []*RuleGroup{ + { + Name: "grp", + File: "/path/to/file", + Interval: 1, + Rules: []rule{ + alertingRule{ + Name: "test_metric3", + Query: "absent(test_metric3) != 1", + Duration: 1, + Labels: labels.Labels{}, + Annotations: labels.Labels{}, + Alerts: []*Alert{}, + Health: "unknown", + Type: "alerting", + }, + alertingRule{ + Name: "test_metric4", + Query: "up == 1", + Duration: 1, + Labels: labels.Labels{}, + Annotations: labels.Labels{}, + Alerts: []*Alert{}, + Health: "unknown", + Type: "alerting", + }, + recordingRule{ + Name: "recording-rule-1", + Query: "vector(1)", + Labels: labels.Labels{}, + Health: "unknown", + Type: "recording", + }, + }, + }, + }, + }, + }, + } + + methods := func(f qapi.ApiFunc) []string { + return []string{http.MethodGet} + } + + request := func(m string, q url.Values) (*http.Request, error) { + return http.NewRequest(m, fmt.Sprintf("http://example.com?%s", q.Encode()), nil) + } + for i, test := range tests { + for _, method := range methods(test.endpoint) { + // Build a context with the correct request params. + ctx := context.Background() + for p, v := range test.params { + ctx = route.WithParam(ctx, p, v) + } + t.Logf("run %d\t%s\t%q", i, method, test.query.Encode()) + + req, err := request(method, test.query) + if err != nil { + t.Fatal(err) + } + endpoint, errors, apiError := test.endpoint(req.WithContext(ctx)) + + if errors != nil { + t.Fatalf("Unexpected errors: %s", errors) + return + } + assertAPIError(t, apiError) + assertAPIResponse(t, endpoint, test.response) + } + } +} + +func assertAPIError(t *testing.T, got *qapi.ApiError) { + if got != nil { + t.Fatalf("Unexpected error: %s", got) + return + } +} + +func assertAPIResponse(t *testing.T, got interface{}, exp interface{}) { + + respJSON, err := json.Marshal(got) + expectedRespJSON, err := json.Marshal(exp) + fmt.Println(expectedRespJSON) + fmt.Println(respJSON) + if !reflect.DeepEqual(exp, got) { + if err != nil { + t.Fatalf("failed to marshal response as JSON: %v", err.Error()) + } + + if err != nil { + t.Fatalf("failed to marshal expected response as JSON: %v", err.Error()) + } + + t.Fatalf( + "Response does not match, expected:\n%+v\ngot:\n%+v", + string(expectedRespJSON), + string(respJSON), + ) + } +} From e30826dbd1807e96fb54c3bd545877c72b2a41f7 Mon Sep 17 00:00:00 2001 From: David Tsur Date: Sun, 17 Feb 2019 23:17:28 +0200 Subject: [PATCH 4/6] Adding SetCORS --- pkg/query/api/v1.go | 4 ++-- pkg/rule/api/v1.go | 1 + pkg/rule/api/v1_test.go | 18 ++++++++---------- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/pkg/query/api/v1.go b/pkg/query/api/v1.go index 02be24c40d..61c1603b57 100644 --- a/pkg/query/api/v1.go +++ b/pkg/query/api/v1.go @@ -86,7 +86,7 @@ type response struct { } // Enables cross-site script calls. -func setCORS(w http.ResponseWriter) { +func SetCORS(w http.ResponseWriter) { for h, v := range corsHeaders { w.Header().Set(h, v) } @@ -153,7 +153,7 @@ func NewAPI( func (api *API) Register(r *route.Router, tracer opentracing.Tracer, logger log.Logger) { instr := func(name string, f ApiFunc) http.HandlerFunc { hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - setCORS(w) + SetCORS(w) if data, warnings, err := f(r); err != nil { RespondError(w, err, data) } else if data != nil { diff --git a/pkg/rule/api/v1.go b/pkg/rule/api/v1.go index a464099889..8a30d4757d 100644 --- a/pkg/rule/api/v1.go +++ b/pkg/rule/api/v1.go @@ -41,6 +41,7 @@ func NewAPI( func (api *API) Register(r *route.Router, tracer opentracing.Tracer, logger log.Logger) { instr := func(name string, f qapi.ApiFunc) http.HandlerFunc { hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + qapi.SetCORS(w) if data, warnings, err := f(r); err != nil { qapi.RespondError(w, err, data) } else if data != nil { diff --git a/pkg/rule/api/v1_test.go b/pkg/rule/api/v1_test.go index dfd8f49c22..1a4b47854f 100644 --- a/pkg/rule/api/v1_test.go +++ b/pkg/rule/api/v1_test.go @@ -4,6 +4,12 @@ import ( "context" "encoding/json" "fmt" + "net/http" + "net/url" + "reflect" + "testing" + "time" + "github.com/go-kit/kit/log" qapi "github.com/improbable-eng/thanos/pkg/query/api" "github.com/prometheus/common/route" @@ -11,11 +17,6 @@ import ( "github.com/prometheus/prometheus/promql" "github.com/prometheus/prometheus/rules" "github.com/prometheus/prometheus/util/testutil" - "net/http" - "net/url" - "reflect" - "testing" - "time" ) type rulesRetrieverMock struct { @@ -219,16 +220,13 @@ func assertAPIError(t *testing.T, got *qapi.ApiError) { } func assertAPIResponse(t *testing.T, got interface{}, exp interface{}) { - - respJSON, err := json.Marshal(got) - expectedRespJSON, err := json.Marshal(exp) - fmt.Println(expectedRespJSON) - fmt.Println(respJSON) if !reflect.DeepEqual(exp, got) { + respJSON, err := json.Marshal(got) if err != nil { t.Fatalf("failed to marshal response as JSON: %v", err.Error()) } + expectedRespJSON, err := json.Marshal(exp) if err != nil { t.Fatalf("failed to marshal expected response as JSON: %v", err.Error()) } From 46c88e5dea6ec91a41eff6f7dacdea6b4285a778 Mon Sep 17 00:00:00 2001 From: David Tsur Date: Sun, 17 Feb 2019 23:36:49 +0200 Subject: [PATCH 5/6] Removing comments --- pkg/rule/api/v1.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/pkg/rule/api/v1.go b/pkg/rule/api/v1.go index 8a30d4757d..be2dc5abf4 100644 --- a/pkg/rule/api/v1.go +++ b/pkg/rule/api/v1.go @@ -17,15 +17,12 @@ import ( "github.com/prometheus/prometheus/rules" ) -// API can register a set of endpoints in a router and handle -// them using the provided storage and query engine. type API struct { logger log.Logger now func() time.Time rulesRetriever rulesRetriever } -// NewAPI returns an initialized API type. func NewAPI( logger log.Logger, rr rulesRetriever, @@ -37,7 +34,6 @@ func NewAPI( } } -// Register the API's endpoints in the given router. func (api *API) Register(r *route.Router, tracer opentracing.Tracer, logger log.Logger) { instr := func(name string, f qapi.ApiFunc) http.HandlerFunc { hf := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -136,7 +132,6 @@ type AlertDiscovery struct { Alerts []*Alert `json:"alerts"` } -// Alert has info for an alert. type Alert struct { Labels labels.Labels `json:"labels"` Annotations labels.Labels `json:"annotations"` @@ -160,12 +155,10 @@ func rulesAlertsToAPIAlerts(rulesAlerts []*rules.Alert) []*Alert { return apiAlerts } -// RuleDiscovery has info for all rules type RuleDiscovery struct { RuleGroups []*RuleGroup `json:"groups"` } -// RuleGroup has info for rules which are part of a group type RuleGroup struct { Name string `json:"name"` File string `json:"file"` From 69c43abf59ced5e813f111d6617316d53913b608 Mon Sep 17 00:00:00 2001 From: David Tsur Date: Sun, 17 Feb 2019 23:47:25 +0200 Subject: [PATCH 6/6] Related to issue #850 --- pkg/rule/api/v1_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/rule/api/v1_test.go b/pkg/rule/api/v1_test.go index 1a4b47854f..af4034bd8d 100644 --- a/pkg/rule/api/v1_test.go +++ b/pkg/rule/api/v1_test.go @@ -27,7 +27,7 @@ func (m rulesRetrieverMock) RuleGroups() []*rules.Group { var ar rulesRetrieverMock arules := ar.AlertingRules() storage := testutil.NewStorage(m.testing) - defer storage.Close() + //defer storage.Close() engineOpts := promql.EngineOpts{ Logger: nil,