diff --git a/CHANGELOG.md b/CHANGELOG.md index 423556e013a..7160e572048 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ * `--querier.query-store-after` has been added in it's place. * [FEATURE] Added user sub rings to distribute users to a subset of ingesters. #1947 * `--experimental.distributor.user-subring-size` +* [FEATURE] Added flag `-experimental.ruler.enable-api` to enable the ruler api which implements the Prometheus API `/api/v1/rules` and `/api/v1/alerts` endpoints under the configured `-http.prefix`. #1999 * [ENHANCEMENT] Experimental TSDB: Export TSDB Syncer metrics from Compactor component, they are prefixed with `cortex_compactor_`. #2023 * [ENHANCEMENT] Experimental TSDB: Added dedicated flag `-experimental.tsdb.bucket-store.tenant-sync-concurrency` to configure the maximum number of concurrent tenants for which blocks are synched. #2026 * [ENHANCEMENT] Experimental TSDB: Expose metrics for objstore operations (prefixed with `cortex__thanos_objstore_`, component being one of `ingester`, `querier` and `compactor`). #2027 diff --git a/docs/apis.md b/docs/apis.md index 476fd70ddc4..ba38f8b0e7c 100644 --- a/docs/apis.md +++ b/docs/apis.md @@ -17,6 +17,13 @@ APIs. The encoding is Protobuf over http. Read is on `/api/prom/read` and write is on `/api/prom/push`. +## Alerts & Rules API + +Cortex supports the Prometheus' [alerts](https://prometheus.io/docs/prometheus/latest/querying/api/#alerts) and [rules](https://prometheus.io/docs/prometheus/latest/querying/api/#rules) api endpoints. This is supported in the Ruler service and can be enabled using the `experimental.ruler.enable-api` flag. + +`GET /api/prom/api/v1/rules` - List of alerting and recording rules that are currently loaded + +`GET /api/prom/api/v1/alerts` - List of all active alerts ## Configs API diff --git a/docs/configuration/config-file-reference.md b/docs/configuration/config-file-reference.md index ba8d792c5c2..0feead758c4 100644 --- a/docs/configuration/config-file-reference.md +++ b/docs/configuration/config-file-reference.md @@ -736,6 +736,10 @@ ring: # Period with which to attempt to flush rule groups. # CLI flag: -ruler.flush-period [flushcheckperiod: | default = 1m0s] + +# Enable the ruler api +# CLI flag: -experimental.ruler.enable-api +[enable_api: | default = false] ``` ## `alertmanager_config` diff --git a/pkg/cortex/modules.go b/pkg/cortex/modules.go index f7405df8fb9..d493e905715 100644 --- a/pkg/cortex/modules.go +++ b/pkg/cortex/modules.go @@ -415,9 +415,15 @@ func (t *Cortex) initRuler(cfg *Config) (err error) { cfg.Ruler.Ring.ListenPort = cfg.Server.GRPCListenPort queryable, engine := querier.New(cfg.Querier, t.distributor, t.store) - t.ruler, err = ruler.NewRuler(cfg.Ruler, engine, queryable, t.distributor) + t.ruler, err = ruler.NewRuler(cfg.Ruler, engine, queryable, t.distributor, prometheus.DefaultRegisterer, util.Logger) if err != nil { - return + return err + } + + if cfg.Ruler.EnableAPI { + subrouter := t.server.HTTP.PathPrefix(cfg.HTTPPrefix).Subrouter() + t.ruler.RegisterRoutes(subrouter) + ruler.RegisterRulerServer(t.server.GRPC, t.ruler) } t.server.HTTP.Handle("/ruler_ring", t.ruler) diff --git a/pkg/ruler/api.go b/pkg/ruler/api.go new file mode 100644 index 00000000000..2ac27d83ba9 --- /dev/null +++ b/pkg/ruler/api.go @@ -0,0 +1,252 @@ +package ruler + +import ( + "encoding/json" + "net/http" + "strconv" + "time" + + "github.com/go-kit/kit/log" + "github.com/go-kit/kit/log/level" + "github.com/gorilla/mux" + v1 "github.com/prometheus/client_golang/api/prometheus/v1" + "github.com/prometheus/prometheus/pkg/labels" + "github.com/weaveworks/common/user" + + "github.com/cortexproject/cortex/pkg/ingester/client" + "github.com/cortexproject/cortex/pkg/util" +) + +// RegisterRoutes registers the ruler API HTTP routes with the provided Router. +func (r *Ruler) RegisterRoutes(router *mux.Router) { + for _, route := range []struct { + name, method, path string + handler http.HandlerFunc + }{ + {"get_rules", "GET", "/api/v1/rules", r.rules}, + {"get_alerts", "GET", "/api/v1/alerts", r.alerts}, + } { + level.Debug(util.Logger).Log("msg", "ruler: registering route", "name", route.name, "method", route.method, "path", route.path) + router.Handle(route.path, route.handler).Methods(route.method).Name(route.name) + } +} + +// In order to reimplement the prometheus rules API, a large amount of code was copied over +// This is required because the prometheus api implementation does not pass a context to +// the rule retrieval function. +// https://github.com/prometheus/prometheus/blob/2aacd807b3ec6ddd90ae55f3a42f4cffed561ea9/web/api/v1/api.go#L108 +// https://github.com/prometheus/prometheus/pull/4999 + +type response struct { + Status string `json:"status"` + Data interface{} `json:"data,omitempty"` + ErrorType v1.ErrorType `json:"errorType,omitempty"` + Error string `json:"error,omitempty"` +} + +// AlertDiscovery has info for all active alerts. +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 string `json:"value"` +} + +// 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"` + // 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 { + // State can be "pending", "firing", "inactive". + State string `json:"state"` + 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 string `json:"health"` + LastError string `json:"lastError,omitempty"` + Type v1.RuleType `json:"type"` +} + +type recordingRule struct { + Name string `json:"name"` + Query string `json:"query"` + Labels labels.Labels `json:"labels,omitempty"` + Health string `json:"health"` + LastError string `json:"lastError,omitempty"` + Type v1.RuleType `json:"type"` +} + +func respondError(logger log.Logger, w http.ResponseWriter, msg string) { + b, err := json.Marshal(&response{ + Status: "error", + ErrorType: v1.ErrServer, + Error: msg, + Data: nil, + }) + + if err != nil { + level.Error(logger).Log("msg", "error marshaling json response", "err", err) + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + + w.WriteHeader(http.StatusInternalServerError) + if n, err := w.Write(b); err != nil { + level.Error(logger).Log("msg", "error writing response", "bytesWritten", n, "err", err) + } +} + +func (r *Ruler) rules(w http.ResponseWriter, req *http.Request) { + logger := util.WithContext(req.Context(), util.Logger) + userID, ctx, err := user.ExtractOrgIDFromHTTPRequest(req) + if err != nil { + level.Error(logger).Log("msg", "error extracting org id from context", "err", err) + respondError(logger, w, "no valid org id found") + return + } + + w.Header().Set("Content-Type", "application/json") + rgs, err := r.GetRules(ctx, userID) + + if err != nil { + respondError(logger, w, err.Error()) + return + } + + groups := make([]*RuleGroup, 0, len(rgs)) + + for _, g := range rgs { + grp := RuleGroup{ + Name: g.Name, + File: g.Namespace, + Interval: g.Interval.Seconds(), + Rules: make([]rule, len(g.Rules)), + } + + for i, rl := range g.Rules { + if g.Rules[i].Alert != "" { + alerts := make([]*Alert, 0, len(rl.Alerts)) + for _, a := range rl.Alerts { + alerts = append(alerts, &Alert{ + Labels: client.FromLabelAdaptersToLabels(a.Labels), + Annotations: client.FromLabelAdaptersToLabels(a.Annotations), + State: a.GetState(), + ActiveAt: &a.ActiveAt, + Value: strconv.FormatFloat(a.Value, 'e', -1, 64), + }) + } + grp.Rules[i] = alertingRule{ + State: rl.GetState(), + Name: rl.GetAlert(), + Query: rl.GetExpr(), + Duration: rl.For.Seconds(), + Labels: client.FromLabelAdaptersToLabels(rl.Labels), + Annotations: client.FromLabelAdaptersToLabels(rl.Annotations), + Alerts: alerts, + Health: rl.GetHealth(), + LastError: rl.GetLastError(), + Type: v1.RuleTypeAlerting, + } + } else { + grp.Rules[i] = recordingRule{ + Name: rl.GetRecord(), + Query: rl.GetExpr(), + Labels: client.FromLabelAdaptersToLabels(rl.Labels), + Health: rl.GetHealth(), + LastError: rl.GetLastError(), + Type: v1.RuleTypeRecording, + } + } + } + groups = append(groups, &grp) + } + + b, err := json.Marshal(&response{ + Status: "success", + Data: &RuleDiscovery{RuleGroups: groups}, + }) + if err != nil { + level.Error(logger).Log("msg", "error marshaling json response", "err", err) + respondError(logger, w, "unable to marshal the requested data") + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if n, err := w.Write(b); err != nil { + level.Error(logger).Log("msg", "error writing response", "bytesWritten", n, "err", err) + } +} + +func (r *Ruler) alerts(w http.ResponseWriter, req *http.Request) { + logger := util.WithContext(req.Context(), util.Logger) + userID, ctx, err := user.ExtractOrgIDFromHTTPRequest(req) + if err != nil { + level.Error(logger).Log("msg", "error extracting org id from context", "err", err) + respondError(logger, w, "no valid org id found") + return + } + + w.Header().Set("Content-Type", "application/json") + rgs, err := r.GetRules(ctx, userID) + + if err != nil { + respondError(logger, w, err.Error()) + return + } + + alerts := []*Alert{} + + for _, g := range rgs { + for _, rl := range g.Rules { + if rl.Alert != "" { + for _, a := range rl.Alerts { + alerts = append(alerts, &Alert{ + Labels: client.FromLabelAdaptersToLabels(a.Labels), + Annotations: client.FromLabelAdaptersToLabels(a.Annotations), + State: a.GetState(), + ActiveAt: &a.ActiveAt, + Value: strconv.FormatFloat(a.Value, 'e', -1, 64), + }) + } + } + } + } + + b, err := json.Marshal(&response{ + Status: "success", + Data: &AlertDiscovery{Alerts: alerts}, + }) + if err != nil { + level.Error(logger).Log("msg", "error marshaling json response", "err", err) + respondError(logger, w, "unable to marshal the requested data") + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + if n, err := w.Write(b); err != nil { + level.Error(logger).Log("msg", "error writing response", "bytesWritten", n, "err", err) + } +} diff --git a/pkg/ruler/api_test.go b/pkg/ruler/api_test.go new file mode 100644 index 00000000000..e2448e5d12c --- /dev/null +++ b/pkg/ruler/api_test.go @@ -0,0 +1,110 @@ +package ruler + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "net/http/httptest" + "os" + "testing" + + "github.com/stretchr/testify/require" + "github.com/weaveworks/common/user" +) + +func TestRuler_rules(t *testing.T) { + dir, err := ioutil.TempDir("/tmp", "ruler-tests") + defer os.RemoveAll(dir) + require.NoError(t, err) + + cfg := defaultRulerConfig() + cfg.RulePath = dir + + r := newTestRuler(t, cfg) + defer r.Stop() + + req := httptest.NewRequest("GET", "https://localhost:8080/api/prom/api/v1/rules", nil) + req.Header.Add(user.OrgIDHeaderName, "user1") + w := httptest.NewRecorder() + r.rules(w, req) + + resp := w.Result() + body, _ := ioutil.ReadAll(resp.Body) + + // Check status code and status response + responseJSON := response{} + err = json.Unmarshal(body, &responseJSON) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) + require.Equal(t, responseJSON.Status, "success") + + // Testing the running rules for user1 in the mock store + expectedResponse, _ := json.Marshal(response{ + Status: "success", + Data: &RuleDiscovery{ + RuleGroups: []*RuleGroup{ + { + Name: "group1", + File: "namespace1", + Rules: []rule{ + &recordingRule{ + Name: "UP_RULE", + Query: "up", + Health: "unknown", + Type: "recording", + }, + &alertingRule{ + Name: "UP_ALERT", + Query: "up < 1", + State: "inactive", + Health: "unknown", + Type: "alerting", + Alerts: []*Alert{}, + }, + }, + Interval: 60, + }, + }, + }, + }) + + require.Equal(t, string(expectedResponse), string(body)) +} + +func TestRuler_alerts(t *testing.T) { + dir, err := ioutil.TempDir("/tmp", "ruler-tests") + defer os.RemoveAll(dir) + require.NoError(t, err) + + cfg := defaultRulerConfig() + cfg.RulePath = dir + + r := newTestRuler(t, cfg) + defer r.Stop() + + req := httptest.NewRequest("GET", "https://localhost:8080/api/prom/api/v1/alerts", nil) + req.Header.Add(user.OrgIDHeaderName, "user1") + w := httptest.NewRecorder() + r.alerts(w, req) + + resp := w.Result() + body, _ := ioutil.ReadAll(resp.Body) + + // Check status code and status response + responseJSON := response{} + err = json.Unmarshal(body, &responseJSON) + require.NoError(t, err) + require.Equal(t, http.StatusOK, resp.StatusCode) + require.Equal(t, responseJSON.Status, "success") + + // Currently there is not an easy way to mock firing alerts. The empty + // response case is tested instead. + expectedResponse, _ := json.Marshal(response{ + Status: "success", + Data: &AlertDiscovery{ + Alerts: []*Alert{}, + }, + }) + + require.Equal(t, string(expectedResponse), string(body)) +} diff --git a/pkg/ruler/mapper.go b/pkg/ruler/mapper.go index 3326d2180c1..36d1f60f4b3 100644 --- a/pkg/ruler/mapper.go +++ b/pkg/ruler/mapper.go @@ -4,7 +4,7 @@ import ( "crypto/md5" "sort" - "github.com/cortexproject/cortex/pkg/util" + "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" "github.com/prometheus/prometheus/pkg/rulefmt" "github.com/spf13/afero" @@ -16,13 +16,15 @@ import ( type mapper struct { Path string // Path specifies the directory in which rule files will be mapped. - FS afero.Fs + FS afero.Fs + logger log.Logger } -func newMapper(path string) *mapper { +func newMapper(path string, logger log.Logger) *mapper { return &mapper{ - Path: path, - FS: afero.NewOsFs(), + Path: path, + FS: afero.NewOsFs(), + logger: logger, } } @@ -62,7 +64,7 @@ func (m *mapper) MapRules(user string, ruleConfigs map[string][]rulefmt.RuleGrou if ruleGroups == nil { err = m.FS.Remove(fullFileName) if err != nil { - level.Warn(util.Logger).Log("msg", "unable to remove rule file on disk", "file", fullFileName, "err", err) + level.Warn(m.logger).Log("msg", "unable to remove rule file on disk", "file", fullFileName, "err", err) } anyUpdated = true } @@ -98,7 +100,7 @@ func (m *mapper) writeRuleGroupsIfNewer(groups []rulefmt.RuleGroup, filename str } } - level.Info(util.Logger).Log("msg", "updating rule file", "file", filename) + level.Info(m.logger).Log("msg", "updating rule file", "file", filename) err = afero.WriteFile(m.FS, filename, d, 0777) if err != nil { return false, err diff --git a/pkg/ruler/mapper_test.go b/pkg/ruler/mapper_test.go index 3a1efc790a2..31a972e969c 100644 --- a/pkg/ruler/mapper_test.go +++ b/pkg/ruler/mapper_test.go @@ -1,12 +1,14 @@ package ruler import ( + "os" "testing" - "github.com/stretchr/testify/require" - + "github.com/go-kit/kit/log" + "github.com/go-kit/kit/log/level" "github.com/prometheus/prometheus/pkg/rulefmt" "github.com/spf13/afero" + "github.com/stretchr/testify/require" ) var ( @@ -92,9 +94,12 @@ var ( ) func Test_mapper_MapRules(t *testing.T) { + l := log.NewLogfmtLogger(os.Stdout) + l = level.NewFilter(l, level.AllowInfo()) m := &mapper{ - Path: "/rules", - FS: afero.NewMemMapFs(), + Path: "/rules", + FS: afero.NewMemMapFs(), + logger: l, } t.Run("basic rulegroup", func(t *testing.T) { @@ -238,9 +243,12 @@ var ( ) func Test_mapper_MapRulesMultipleFiles(t *testing.T) { + l := log.NewLogfmtLogger(os.Stdout) + l = level.NewFilter(l, level.AllowInfo()) m := &mapper{ - Path: "/rules", - FS: afero.NewMemMapFs(), + Path: "/rules", + FS: afero.NewMemMapFs(), + logger: l, } t.Run("basic rulegroup", func(t *testing.T) { diff --git a/pkg/ruler/mock_store.go b/pkg/ruler/mock_store.go deleted file mode 100644 index 260d47b19ec..00000000000 --- a/pkg/ruler/mock_store.go +++ /dev/null @@ -1,63 +0,0 @@ -package ruler - -import ( - "context" - "strings" - "sync" - - "github.com/cortexproject/cortex/pkg/ruler/rules" -) - -type mockRuleStore struct { - sync.Mutex - rules map[string]*rules.RuleGroupDesc -} - -func newMockRuleStore() *mockRuleStore { - return &mockRuleStore{ - rules: map[string]*rules.RuleGroupDesc{ - "user1:group1": { - Name: "group1", - Namespace: "namespace1", - User: "user1", - Rules: []*rules.RuleDesc{ - { - Expr: "up", - }, - }, - }, - "user2:group1": { - Name: "group1", - Namespace: "namespace1", - User: "user2", - Rules: []*rules.RuleDesc{ - { - Expr: "up", - }, - }, - }, - }, - } -} - -func (m *mockRuleStore) ListAllRuleGroups(ctx context.Context) (map[string]rules.RuleGroupList, error) { - m.Lock() - defer m.Unlock() - - userGroupMap := map[string]rules.RuleGroupList{} - - for id, rg := range m.rules { - components := strings.Split(id, ":") - if len(components) != 3 { - continue - } - user := components[0] - - if _, exists := userGroupMap[user]; !exists { - userGroupMap[user] = rules.RuleGroupList{} - } - userGroupMap[user] = append(userGroupMap[user], rg) - } - - return userGroupMap, nil -} diff --git a/pkg/ruler/ruler.go b/pkg/ruler/ruler.go index e956502b2ff..dafc1322b04 100644 --- a/pkg/ruler/ruler.go +++ b/pkg/ruler/ruler.go @@ -3,14 +3,26 @@ package ruler import ( native_ctx "context" "flag" + "fmt" "hash/fnv" "net/http" "net/url" + "path/filepath" + "strings" "sync" "time" + "github.com/cortexproject/cortex/pkg/distributor" + "github.com/cortexproject/cortex/pkg/ingester/client" + "github.com/cortexproject/cortex/pkg/ring" + "github.com/cortexproject/cortex/pkg/ruler/rules" + store "github.com/cortexproject/cortex/pkg/ruler/rules" + "github.com/cortexproject/cortex/pkg/util" + "github.com/cortexproject/cortex/pkg/util/flagext" + "github.com/go-kit/kit/log" "github.com/go-kit/kit/log/level" ot "github.com/opentracing/opentracing-go" + "github.com/pkg/errors" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promauto" "github.com/prometheus/prometheus/config" @@ -19,16 +31,10 @@ import ( promRules "github.com/prometheus/prometheus/rules" promStorage "github.com/prometheus/prometheus/storage" "github.com/prometheus/prometheus/util/strutil" + "github.com/weaveworks/common/user" "golang.org/x/net/context" "golang.org/x/net/context/ctxhttp" - - "github.com/cortexproject/cortex/pkg/distributor" - "github.com/cortexproject/cortex/pkg/ring" - "github.com/cortexproject/cortex/pkg/ruler/rules" - store "github.com/cortexproject/cortex/pkg/ruler/rules" - "github.com/cortexproject/cortex/pkg/util" - "github.com/cortexproject/cortex/pkg/util/flagext" - "github.com/weaveworks/common/user" + "google.golang.org/grpc" ) var ( @@ -63,6 +69,8 @@ type Config struct { SearchPendingFor time.Duration Ring RingConfig FlushCheckPeriod time.Duration + + EnableAPI bool `yaml:"enable_api"` } // RegisterFlags adds the flags required to config this to the given FlagSet @@ -92,6 +100,7 @@ func (cfg *Config) RegisterFlags(f *flag.FlagSet) { f.BoolVar(&cfg.EnableSharding, "ruler.enable-sharding", false, "Distribute rule evaluation using ring backend") f.DurationVar(&cfg.FlushCheckPeriod, "ruler.flush-period", 1*time.Minute, "Period with which to attempt to flush rule groups.") f.StringVar(&cfg.RulePath, "ruler.rule-path", "/rules", "file path to store temporary rule files for the prometheus rule managers") + f.BoolVar(&cfg.EnableAPI, "experimental.ruler.enable-api", false, "Enable the ruler api") } // Ruler evaluates rules. @@ -117,10 +126,13 @@ type Ruler struct { done chan struct{} terminated chan struct{} + + registry prometheus.Registerer + logger log.Logger } // NewRuler creates a new ruler from a distributor and chunk store. -func NewRuler(cfg Config, engine *promql.Engine, queryable promStorage.Queryable, d *distributor.Distributor) (*Ruler, error) { +func NewRuler(cfg Config, engine *promql.Engine, queryable promStorage.Queryable, d *distributor.Distributor, reg prometheus.Registerer, logger log.Logger) (*Ruler, error) { ncfg, err := buildNotifierConfig(&cfg) if err != nil { return nil, err @@ -140,10 +152,12 @@ func NewRuler(cfg Config, engine *promql.Engine, queryable promStorage.Queryable notifiers: map[string]*rulerNotifier{}, store: ruleStore, pusher: d, - mapper: newMapper(cfg.RulePath), + mapper: newMapper(cfg.RulePath, logger), userManagers: map[string]*promRules.Manager{}, done: make(chan struct{}), terminated: make(chan struct{}), + registry: reg, + logger: logger, } // If sharding is enabled, create/join a ring to distribute tokens to @@ -164,7 +178,7 @@ func NewRuler(cfg Config, engine *promql.Engine, queryable promStorage.Queryable } go ruler.run() - level.Info(util.Logger).Log("msg", "ruler up and running") + level.Info(logger).Log("msg", "ruler up and running") return ruler, nil } @@ -182,27 +196,27 @@ func (r *Ruler) Stop() { r.notifiersMtx.Unlock() if r.cfg.EnableSharding { - level.Info(util.Logger).Log("msg", "attempting shutdown lifecycle") + level.Info(r.logger).Log("msg", "attempting shutdown lifecycle") r.lifecycler.Shutdown() - level.Info(util.Logger).Log("msg", "shutting down the ring") + level.Info(r.logger).Log("msg", "shutting down the ring") r.ring.Stop() } - level.Info(util.Logger).Log("msg", "stopping user managers") + level.Info(r.logger).Log("msg", "stopping user managers") wg := sync.WaitGroup{} r.userManagerMtx.Lock() for user, manager := range r.userManagers { - level.Debug(util.Logger).Log("msg", "shutting down user manager", "user", user) + level.Debug(r.logger).Log("msg", "shutting down user manager", "user", user) wg.Add(1) go func(manager *promRules.Manager, user string) { manager.Stop() wg.Done() - level.Debug(util.Logger).Log("msg", "user manager shut down", "user", user) + level.Debug(r.logger).Log("msg", "user manager shut down", "user", user) }(manager, user) } wg.Wait() r.userManagerMtx.Unlock() - level.Info(util.Logger).Log("msg", "all user managers stopped") + level.Info(r.logger).Log("msg", "all user managers stopped") } // sendAlerts implements a rules.NotifyFunc for a Notifier. @@ -278,15 +292,15 @@ func (r *Ruler) getOrCreateNotifier(userID string) (*notifier.Manager, error) { func (r *Ruler) ownsRule(hash uint32) (bool, error) { rlrs, err := r.ring.Get(hash, ring.Read, []ring.IngesterDesc{}) if err != nil { - level.Warn(util.Logger).Log("msg", "error reading ring to verify rule group ownership", "err", err) + level.Warn(r.logger).Log("msg", "error reading ring to verify rule group ownership", "err", err) ringCheckErrors.Inc() return false, err } if rlrs.Ingesters[0].Addr == r.lifecycler.Addr { - level.Debug(util.Logger).Log("msg", "rule group owned", "owner_addr", rlrs.Ingesters[0].Addr, "addr", r.lifecycler.Addr) + level.Debug(r.logger).Log("msg", "rule group owned", "owner_addr", rlrs.Ingesters[0].Addr, "addr", r.lifecycler.Addr) return true, nil } - level.Debug(util.Logger).Log("msg", "rule group not owned, address does not match", "owner_addr", rlrs.Ingesters[0].Addr, "addr", r.lifecycler.Addr) + level.Debug(r.logger).Log("msg", "rule group not owned, address does not match", "owner_addr", rlrs.Ingesters[0].Addr, "addr", r.lifecycler.Addr) return false, nil } @@ -309,7 +323,7 @@ func (r *Ruler) ServeHTTP(w http.ResponseWriter, req *http.Request) { w.WriteHeader(http.StatusOK) _, err := w.Write([]byte(unshardedPage)) if err != nil { - level.Error(util.Logger).Log("msg", "unable to serve status page", "err", err) + level.Error(r.logger).Log("msg", "unable to serve status page", "err", err) } } } @@ -336,7 +350,7 @@ func (r *Ruler) loadRules(ctx context.Context) { configs, err := r.store.ListAllRuleGroups(ctx) if err != nil { - level.Error(util.Logger).Log("msg", "unable to poll for rules", "err", err) + level.Error(r.logger).Log("msg", "unable to poll for rules", "err", err) return } @@ -353,13 +367,13 @@ func (r *Ruler) loadRules(ctx context.Context) { ringHasher.Reset() _, err = ringHasher.Write([]byte(id)) if err != nil { - level.Error(util.Logger).Log("msg", "failed to create group for user", "user", user, "namespace", g.Namespace, "group", g.Name, "err", err) + level.Error(r.logger).Log("msg", "failed to create group for user", "user", user, "namespace", g.Namespace, "group", g.Name, "err", err) continue } hash := ringHasher.Sum32() owned, err := r.ownsRule(hash) if err != nil { - level.Error(util.Logger).Log("msg", "unable to verify rule group ownership ownership, will retry on the next poll", "err", err) + level.Error(r.logger).Log("msg", "unable to verify rule group ownership ownership, will retry on the next poll", "err", err) return } if owned { @@ -370,36 +384,7 @@ func (r *Ruler) loadRules(ctx context.Context) { filteredGroups = cfg } - // Map the files to disk and return the file names to be passed to the users manager - update, files, err := r.mapper.MapRules(user, filteredGroups.Formatted()) - if err != nil { - level.Error(util.Logger).Log("msg", "unable to map rule files", "user", user, "err", err) - continue - } - - if update { - configUpdatesTotal.WithLabelValues(user).Inc() - r.userManagerMtx.Lock() - manager, exists := r.userManagers[user] - r.userManagerMtx.Unlock() - if !exists { - manager, err = r.newManager(ctx, user) - if err != nil { - level.Error(util.Logger).Log("msg", "unable to create rule manager", "user", user, "err", err) - continue - } - manager.Run() - - r.userManagerMtx.Lock() - r.userManagers[user] = manager - r.userManagerMtx.Unlock() - } - err = manager.Update(r.cfg.EvaluationInterval, files, nil) - if err != nil { - level.Error(util.Logger).Log("msg", "unable to create rule manager", "user", user, "err", err) - continue - } - } + r.syncManager(ctx, user, filteredGroups) } // Check for deleted users and remove them @@ -409,12 +394,49 @@ func (r *Ruler) loadRules(ctx context.Context) { if _, exists := configs[user]; !exists { go mngr.Stop() delete(r.userManagers, user) - level.Info(util.Logger).Log("msg", "deleting rule manager", "user", user) + level.Info(r.logger).Log("msg", "deleting rule manager", "user", user) } } } +// syncManager maps the rule files to disk, detects any changes and will create/update the +// the users Prometheus Rules Manager. +func (r *Ruler) syncManager(ctx native_ctx.Context, user string, groups store.RuleGroupList) { + // A lock is taken to ensure if syncManager is called concurrently, that each call + // returns after the call map files and check for updates + r.userManagerMtx.Lock() + defer r.userManagerMtx.Unlock() + + // Map the files to disk and return the file names to be passed to the users manager if they + // have been updated + update, files, err := r.mapper.MapRules(user, groups.Formatted()) + if err != nil { + level.Error(r.logger).Log("msg", "unable to map rule files", "user", user, "err", err) + return + } + + if update { + level.Debug(r.logger).Log("msg", "updating rules", "user", "user") + configUpdatesTotal.WithLabelValues(user).Inc() + manager, exists := r.userManagers[user] + if !exists { + manager, err = r.newManager(ctx, user) + if err != nil { + level.Error(r.logger).Log("msg", "unable to create rule manager", "user", user, "err", err) + return + } + manager.Run() + r.userManagers[user] = manager + } + err = manager.Update(r.cfg.EvaluationInterval, files, nil) + if err != nil { + level.Error(r.logger).Log("msg", "unable to update rule manager", "user", user, "err", err) + return + } + } +} + // newManager creates a prometheus rule manager wrapped with a user id // configured storage, appendable, notifier, and instrumentation func (r *Ruler) newManager(ctx context.Context, userID string) (*promRules.Manager, error) { @@ -430,9 +452,9 @@ func (r *Ruler) newManager(ctx context.Context, userID string) (*promRules.Manag } // Wrap registerer with userID and cortex_ prefix - reg := prometheus.WrapRegistererWith(prometheus.Labels{"user": userID}, prometheus.DefaultRegisterer) + reg := prometheus.WrapRegistererWith(prometheus.Labels{"user": userID}, r.registry) reg = prometheus.WrapRegistererWithPrefix("cortex_", reg) - + logger := log.With(r.logger, "user", userID) opts := &promRules.ManagerOptions{ Appendable: tsdb, TSDB: tsdb, @@ -440,8 +462,134 @@ func (r *Ruler) newManager(ctx context.Context, userID string) (*promRules.Manag Context: user.InjectOrgID(ctx, userID), ExternalURL: r.alertURL, NotifyFunc: sendAlerts(notifier, r.alertURL.String()), - Logger: util.Logger, + Logger: logger, Registerer: reg, } return promRules.NewManager(opts), nil } + +// GetRules retrieves the running rules from this ruler and all running rulers in the ring if +// sharding is enabled +func (r *Ruler) GetRules(ctx context.Context, userID string) ([]*rules.RuleGroupDesc, error) { + if r.cfg.EnableSharding { + return r.getShardedRules(ctx, userID) + } + + return r.getLocalRules(userID) +} + +func (r *Ruler) getLocalRules(userID string) ([]*rules.RuleGroupDesc, error) { + var groups []*promRules.Group + r.userManagerMtx.Lock() + if mngr, exists := r.userManagers[userID]; exists { + groups = mngr.RuleGroups() + } + r.userManagerMtx.Unlock() + + groupDescs := make([]*rules.RuleGroupDesc, 0, len(groups)) + prefix := filepath.Join(r.cfg.RulePath, userID) + "/" + + for _, group := range groups { + interval := group.Interval() + groupDesc := &rules.RuleGroupDesc{ + Name: group.Name(), + Namespace: strings.TrimPrefix(group.File(), prefix), + Interval: interval, + User: userID, + } + for _, r := range group.Rules() { + lastError := "" + if r.LastError() != nil { + lastError = r.LastError().Error() + } + + var ruleDesc *rules.RuleDesc + switch rule := r.(type) { + case *promRules.AlertingRule: + rule.ActiveAlerts() + alerts := []*rules.AlertDesc{} + for _, a := range rule.ActiveAlerts() { + alerts = append(alerts, &rules.AlertDesc{ + State: a.State.String(), + Labels: client.FromLabelsToLabelAdapters(a.Labels), + Annotations: client.FromLabelsToLabelAdapters(a.Annotations), + Value: a.Value, + ActiveAt: a.ActiveAt, + FiredAt: a.FiredAt, + ResolvedAt: a.ResolvedAt, + LastSentAt: a.LastSentAt, + ValidUntil: a.ValidUntil, + }) + } + ruleDesc = &rules.RuleDesc{ + State: rule.State().String(), + Alert: rule.Name(), + Alerts: alerts, + Expr: rule.Query().String(), + For: rule.Duration(), + Labels: client.FromLabelsToLabelAdapters(rule.Labels()), + Annotations: client.FromLabelsToLabelAdapters(rule.Annotations()), + Health: string(rule.Health()), + LastError: lastError, + } + case *promRules.RecordingRule: + ruleDesc = &rules.RuleDesc{ + Record: rule.Name(), + Expr: rule.Query().String(), + Labels: client.FromLabelsToLabelAdapters(rule.Labels()), + Health: string(rule.Health()), + LastError: lastError, + } + default: + return nil, errors.Errorf("failed to assert type of rule '%v'", rule.Name()) + } + groupDesc.Rules = append(groupDesc.Rules, ruleDesc) + } + groupDescs = append(groupDescs, groupDesc) + } + return groupDescs, nil +} + +func (r *Ruler) getShardedRules(ctx context.Context, userID string) ([]*rules.RuleGroupDesc, error) { + rulers, err := r.ring.GetAll() + if err != nil { + return nil, err + } + + ctx, err = user.InjectIntoGRPCRequest(ctx) + if err != nil { + return nil, fmt.Errorf("unable to inject user ID into grpc request, %v", err) + } + + rgs := []*rules.RuleGroupDesc{} + + for _, rlr := range rulers.Ingesters { + conn, err := grpc.Dial(rlr.Addr, grpc.WithInsecure()) + if err != nil { + return nil, err + } + cc := NewRulerClient(conn) + newGrps, err := cc.Rules(ctx, nil) + if err != nil { + return nil, fmt.Errorf("unable to retrieve rules from other rulers, %v", err) + } + rgs = append(rgs, newGrps.Groups...) + } + + return rgs, nil +} + +// Rules implements the rules service +func (r *Ruler) Rules(ctx context.Context, in *RulesRequest) (*RulesResponse, error) { + userID, err := user.ExtractOrgID(ctx) + if err != nil { + return nil, fmt.Errorf("no user id found in context") + } + + groupDescs, err := r.getLocalRules(userID) + if err != nil { + return nil, err + } + + return &RulesResponse{Groups: groupDescs}, nil +} diff --git a/pkg/ruler/ruler.pb.go b/pkg/ruler/ruler.pb.go new file mode 100644 index 00000000000..a70d058ece8 --- /dev/null +++ b/pkg/ruler/ruler.pb.go @@ -0,0 +1,657 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: ruler.proto + +package ruler + +import ( + context "context" + fmt "fmt" + rules "github.com/cortexproject/cortex/pkg/ruler/rules" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + grpc "google.golang.org/grpc" + io "io" + math "math" + reflect "reflect" + strings "strings" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package + +type RulesRequest struct { +} + +func (m *RulesRequest) Reset() { *m = RulesRequest{} } +func (*RulesRequest) ProtoMessage() {} +func (*RulesRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_9ecbec0a4cfddea6, []int{0} +} +func (m *RulesRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RulesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RulesRequest.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalTo(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RulesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_RulesRequest.Merge(m, src) +} +func (m *RulesRequest) XXX_Size() int { + return m.Size() +} +func (m *RulesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_RulesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_RulesRequest proto.InternalMessageInfo + +type RulesResponse struct { + Groups []*rules.RuleGroupDesc `protobuf:"bytes,1,rep,name=groups,proto3" json:"groups,omitempty"` +} + +func (m *RulesResponse) Reset() { *m = RulesResponse{} } +func (*RulesResponse) ProtoMessage() {} +func (*RulesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_9ecbec0a4cfddea6, []int{1} +} +func (m *RulesResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *RulesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_RulesResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalTo(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *RulesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_RulesResponse.Merge(m, src) +} +func (m *RulesResponse) XXX_Size() int { + return m.Size() +} +func (m *RulesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_RulesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_RulesResponse proto.InternalMessageInfo + +func (m *RulesResponse) GetGroups() []*rules.RuleGroupDesc { + if m != nil { + return m.Groups + } + return nil +} + +func init() { + proto.RegisterType((*RulesRequest)(nil), "ruler.RulesRequest") + proto.RegisterType((*RulesResponse)(nil), "ruler.RulesResponse") +} + +func init() { proto.RegisterFile("ruler.proto", fileDescriptor_9ecbec0a4cfddea6) } + +var fileDescriptor_9ecbec0a4cfddea6 = []byte{ + // 251 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0x8f, 0x3d, 0x4e, 0xc4, 0x30, + 0x10, 0x85, 0x6d, 0xa1, 0xdd, 0xc2, 0x0b, 0x14, 0x61, 0x0b, 0x94, 0x62, 0x84, 0x52, 0x51, 0x40, + 0x22, 0x2d, 0xdb, 0xa1, 0x6d, 0x10, 0x12, 0x7d, 0x8e, 0x90, 0xc8, 0x98, 0xff, 0x31, 0xfe, 0x91, + 0x28, 0x39, 0x02, 0xc7, 0xe0, 0x28, 0x94, 0x29, 0xb7, 0x24, 0x4e, 0x43, 0xb9, 0x47, 0x40, 0x1e, + 0xa7, 0xc8, 0x36, 0xa3, 0xf9, 0xfc, 0xde, 0xf3, 0xe8, 0x89, 0x85, 0xf1, 0x2f, 0xd2, 0x94, 0xda, + 0xa0, 0xc3, 0x6c, 0x46, 0x90, 0x5f, 0xaa, 0x47, 0xf7, 0xe0, 0x9b, 0xb2, 0xc5, 0xd7, 0x4a, 0xa1, + 0xc2, 0x8a, 0xd4, 0xc6, 0xdf, 0x13, 0x11, 0xd0, 0x96, 0x52, 0xf9, 0xf5, 0xc4, 0xde, 0xa2, 0x71, + 0xf2, 0x43, 0x1b, 0x7c, 0x92, 0xad, 0x1b, 0xa9, 0xd2, 0xcf, 0xaa, 0xa2, 0x9f, 0x69, 0xda, 0x34, + 0x53, 0xb8, 0x38, 0x16, 0x87, 0x75, 0xc4, 0x5a, 0xbe, 0x7b, 0x69, 0x5d, 0xb1, 0x11, 0x47, 0x23, + 0x5b, 0x8d, 0x6f, 0x56, 0x66, 0x17, 0x62, 0xae, 0x0c, 0x7a, 0x6d, 0x4f, 0xf9, 0xd9, 0xc1, 0xf9, + 0x62, 0xb5, 0x2c, 0x53, 0x3c, 0xba, 0xee, 0xa2, 0x70, 0x2b, 0x6d, 0x5b, 0x8f, 0x9e, 0xd5, 0x46, + 0xcc, 0xa2, 0x60, 0xb2, 0x75, 0x5a, 0x6c, 0x76, 0x52, 0xa6, 0x86, 0xd3, 0x2b, 0xf9, 0x72, 0xff, + 0x31, 0x9d, 0x2a, 0xd8, 0xcd, 0xba, 0xeb, 0x81, 0x6d, 0x7b, 0x60, 0xbb, 0x1e, 0xf8, 0x67, 0x00, + 0xfe, 0x1d, 0x80, 0xff, 0x04, 0xe0, 0x5d, 0x00, 0xfe, 0x1b, 0x80, 0xff, 0x05, 0x60, 0xbb, 0x00, + 0xfc, 0x6b, 0x00, 0xd6, 0x0d, 0xc0, 0xb6, 0x03, 0xb0, 0x66, 0x4e, 0x55, 0xae, 0xfe, 0x03, 0x00, + 0x00, 0xff, 0xff, 0xec, 0x23, 0x45, 0x0d, 0x4c, 0x01, 0x00, 0x00, +} + +func (this *RulesRequest) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*RulesRequest) + if !ok { + that2, ok := that.(RulesRequest) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + return true +} +func (this *RulesResponse) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*RulesResponse) + if !ok { + that2, ok := that.(RulesResponse) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if len(this.Groups) != len(that1.Groups) { + return false + } + for i := range this.Groups { + if !this.Groups[i].Equal(that1.Groups[i]) { + return false + } + } + return true +} +func (this *RulesRequest) GoString() string { + if this == nil { + return "nil" + } + s := make([]string, 0, 4) + s = append(s, "&ruler.RulesRequest{") + s = append(s, "}") + return strings.Join(s, "") +} +func (this *RulesResponse) GoString() string { + if this == nil { + return "nil" + } + s := make([]string, 0, 5) + s = append(s, "&ruler.RulesResponse{") + if this.Groups != nil { + s = append(s, "Groups: "+fmt.Sprintf("%#v", this.Groups)+",\n") + } + s = append(s, "}") + return strings.Join(s, "") +} +func valueToGoStringRuler(v interface{}, typ string) string { + rv := reflect.ValueOf(v) + if rv.IsNil() { + return "nil" + } + pv := reflect.Indirect(rv).Interface() + return fmt.Sprintf("func(v %v) *%v { return &v } ( %#v )", typ, typ, pv) +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// RulerClient is the client API for Ruler service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type RulerClient interface { + Rules(ctx context.Context, in *RulesRequest, opts ...grpc.CallOption) (*RulesResponse, error) +} + +type rulerClient struct { + cc *grpc.ClientConn +} + +func NewRulerClient(cc *grpc.ClientConn) RulerClient { + return &rulerClient{cc} +} + +func (c *rulerClient) Rules(ctx context.Context, in *RulesRequest, opts ...grpc.CallOption) (*RulesResponse, error) { + out := new(RulesResponse) + err := c.cc.Invoke(ctx, "/ruler.Ruler/Rules", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// RulerServer is the server API for Ruler service. +type RulerServer interface { + Rules(context.Context, *RulesRequest) (*RulesResponse, error) +} + +func RegisterRulerServer(s *grpc.Server, srv RulerServer) { + s.RegisterService(&_Ruler_serviceDesc, srv) +} + +func _Ruler_Rules_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(RulesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RulerServer).Rules(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/ruler.Ruler/Rules", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RulerServer).Rules(ctx, req.(*RulesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Ruler_serviceDesc = grpc.ServiceDesc{ + ServiceName: "ruler.Ruler", + HandlerType: (*RulerServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Rules", + Handler: _Ruler_Rules_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "ruler.proto", +} + +func (m *RulesRequest) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RulesRequest) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + return i, nil +} + +func (m *RulesResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *RulesResponse) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.Groups) > 0 { + for _, msg := range m.Groups { + dAtA[i] = 0xa + i++ + i = encodeVarintRuler(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func encodeVarintRuler(dAtA []byte, offset int, v uint64) int { + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return offset + 1 +} +func (m *RulesRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *RulesResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Groups) > 0 { + for _, e := range m.Groups { + l = e.Size() + n += 1 + l + sovRuler(uint64(l)) + } + } + return n +} + +func sovRuler(x uint64) (n int) { + for { + n++ + x >>= 7 + if x == 0 { + break + } + } + return n +} +func sozRuler(x uint64) (n int) { + return sovRuler(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (this *RulesRequest) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&RulesRequest{`, + `}`, + }, "") + return s +} +func (this *RulesResponse) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&RulesResponse{`, + `Groups:` + strings.Replace(fmt.Sprintf("%v", this.Groups), "RuleGroupDesc", "rules.RuleGroupDesc", 1) + `,`, + `}`, + }, "") + return s +} +func valueToStringRuler(v interface{}) string { + rv := reflect.ValueOf(v) + if rv.IsNil() { + return "nil" + } + pv := reflect.Indirect(rv).Interface() + return fmt.Sprintf("*%v", pv) +} +func (m *RulesRequest) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRuler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RulesRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RulesRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipRuler(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRuler + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthRuler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *RulesResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRuler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: RulesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: RulesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Groups", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRuler + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRuler + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthRuler + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Groups = append(m.Groups, &rules.RuleGroupDesc{}) + if err := m.Groups[len(m.Groups)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRuler(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRuler + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthRuler + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipRuler(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRuler + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRuler + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + return iNdEx, nil + case 1: + iNdEx += 8 + return iNdEx, nil + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRuler + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthRuler + } + iNdEx += length + if iNdEx < 0 { + return 0, ErrInvalidLengthRuler + } + return iNdEx, nil + case 3: + for { + var innerWire uint64 + var start int = iNdEx + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowRuler + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + innerWire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + innerWireType := int(innerWire & 0x7) + if innerWireType == 4 { + break + } + next, err := skipRuler(dAtA[start:]) + if err != nil { + return 0, err + } + iNdEx = start + next + if iNdEx < 0 { + return 0, ErrInvalidLengthRuler + } + } + return iNdEx, nil + case 4: + return iNdEx, nil + case 5: + iNdEx += 4 + return iNdEx, nil + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + } + panic("unreachable") +} + +var ( + ErrInvalidLengthRuler = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowRuler = fmt.Errorf("proto: integer overflow") +) diff --git a/pkg/ruler/ruler.proto b/pkg/ruler/ruler.proto new file mode 100644 index 00000000000..149924a2d7f --- /dev/null +++ b/pkg/ruler/ruler.proto @@ -0,0 +1,22 @@ +// Ruler Service Representation +// This service is used to retrieve the current state of rules running across +// all Rulers in a cluster. It allows cortex to fully serve the `/api/v1/{rules|alerts}` +// Prometheus API +syntax = "proto3"; +package ruler; + +import "github.com/gogo/protobuf/gogoproto/gogo.proto"; +import "github.com/cortexproject/cortex/pkg/ruler/rules/rules.proto"; + +option (gogoproto.marshaler_all) = true; +option (gogoproto.unmarshaler_all) = true; + +service Ruler { + rpc Rules(RulesRequest) returns (RulesResponse) {}; +} + +message RulesRequest {} + +message RulesResponse { + repeated rules.RuleGroupDesc groups = 1; +} diff --git a/pkg/ruler/ruler_test.go b/pkg/ruler/ruler_test.go index 6ebb8434bc5..7d4660a2bb3 100644 --- a/pkg/ruler/ruler_test.go +++ b/pkg/ruler/ruler_test.go @@ -1,22 +1,29 @@ package ruler import ( + "context" + "io/ioutil" "net/http" "net/http/httptest" + "os" "sync" "testing" "time" + "github.com/go-kit/kit/log" + "github.com/go-kit/kit/log/level" "github.com/stretchr/testify/require" - "github.com/cortexproject/cortex/pkg/querier" "github.com/cortexproject/cortex/pkg/ring" "github.com/cortexproject/cortex/pkg/ring/kv/codec" "github.com/cortexproject/cortex/pkg/ring/kv/consul" + "github.com/cortexproject/cortex/pkg/ruler/rules" "github.com/cortexproject/cortex/pkg/util/flagext" + "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/prometheus/notifier" "github.com/prometheus/prometheus/pkg/labels" "github.com/prometheus/prometheus/promql" + "github.com/prometheus/prometheus/storage" "github.com/stretchr/testify/assert" "github.com/weaveworks/common/user" ) @@ -48,11 +55,21 @@ func newTestRuler(t *testing.T, cfg Config) *Ruler { MaxConcurrent: 20, Timeout: 2 * time.Minute, }) - queryable := querier.NewQueryable(nil, nil, nil, querier.Config{}) - ruler, err := NewRuler(cfg, engine, queryable, nil) + + noopQueryable := storage.QueryableFunc(func(ctx context.Context, mint, maxt int64) (storage.Querier, error) { + return storage.NoopQuerier(), nil + }) + + l := log.NewLogfmtLogger(os.Stdout) + l = level.NewFilter(l, level.AllowInfo()) + ruler, err := NewRuler(cfg, engine, noopQueryable, nil, prometheus.NewRegistry(), l) if err != nil { t.Fatal(err) } + + // Ensure all rules are loaded before usage + ruler.loadRules(context.Background()) + return ruler } @@ -73,6 +90,7 @@ func TestNotifierSendsUserIDHeader(t *testing.T) { cfg.AlertmanagerDiscovery = false r := newTestRuler(t, cfg) + defer r.Stop() n, err := r.getOrCreateNotifier("1") if err != nil { t.Fatal(err) @@ -90,3 +108,44 @@ func TestNotifierSendsUserIDHeader(t *testing.T) { wg.Wait() } + +func TestRuler_Rules(t *testing.T) { + dir, err := ioutil.TempDir("/tmp", "ruler-tests") + defer os.RemoveAll(dir) + + require.NoError(t, err) + + cfg := defaultRulerConfig() + cfg.RulePath = dir + + r := newTestRuler(t, cfg) + defer r.Stop() + + // test user1 + ctx := user.InjectOrgID(context.Background(), "user1") + rls, err := r.Rules(ctx, &RulesRequest{}) + require.NoError(t, err) + require.Len(t, rls.Groups, 1) + rg := rls.Groups[0] + expectedRg := mockRules["user1"][0] + compareRuleGroupDescs(t, rg, expectedRg) + + // test user2 + ctx = user.InjectOrgID(context.Background(), "user2") + rls, err = r.Rules(ctx, &RulesRequest{}) + require.NoError(t, err) + require.Len(t, rls.Groups, 1) + rg = rls.Groups[0] + expectedRg = mockRules["user2"][0] + compareRuleGroupDescs(t, rg, expectedRg) +} + +func compareRuleGroupDescs(t *testing.T, expected, got *rules.RuleGroupDesc) { + require.Equal(t, expected.Name, got.Name) + require.Equal(t, expected.Namespace, got.Namespace) + require.Len(t, got.Rules, len(expected.Rules)) + for i := range got.Rules { + require.Equal(t, expected.Rules[i].Record, got.Rules[i].Record) + require.Equal(t, expected.Rules[i].Alert, got.Rules[i].Alert) + } +} diff --git a/pkg/ruler/rules/compat.go b/pkg/ruler/rules/compat.go index 2d9422ee20d..693157d15d8 100644 --- a/pkg/ruler/rules/compat.go +++ b/pkg/ruler/rules/compat.go @@ -12,11 +12,10 @@ import ( // ToProto transforms a formatted prometheus rulegroup to a rule group protobuf func ToProto(user string, namespace string, rl rulefmt.RuleGroup) *RuleGroupDesc { - dur := time.Duration(rl.Interval) rg := RuleGroupDesc{ Name: rl.Name, Namespace: namespace, - Interval: &dur, + Interval: time.Duration(rl.Interval), Rules: formattedRuleToProto(rl.Rules), User: user, } @@ -26,14 +25,11 @@ func ToProto(user string, namespace string, rl rulefmt.RuleGroup) *RuleGroupDesc func formattedRuleToProto(rls []rulefmt.Rule) []*RuleDesc { rules := make([]*RuleDesc, len(rls)) for i := range rls { - f := time.Duration(rls[i].For) - rules[i] = &RuleDesc{ - Expr: rls[i].Expr, - Record: rls[i].Record, - Alert: rls[i].Alert, - - For: &f, + Expr: rls[i].Expr, + Record: rls[i].Record, + Alert: rls[i].Alert, + For: time.Duration(rls[i].For), Labels: client.FromLabelsToLabelAdapters(labels.FromMap(rls[i].Labels)), Annotations: client.FromLabelsToLabelAdapters(labels.FromMap(rls[i].Annotations)), } @@ -46,19 +42,21 @@ func formattedRuleToProto(rls []rulefmt.Rule) []*RuleDesc { func FromProto(rg *RuleGroupDesc) rulefmt.RuleGroup { formattedRuleGroup := rulefmt.RuleGroup{ Name: rg.GetName(), - Interval: model.Duration(*rg.Interval), + Interval: model.Duration(rg.Interval), Rules: make([]rulefmt.Rule, len(rg.GetRules())), } for i, rl := range rg.GetRules() { - formattedRuleGroup.Rules[i] = rulefmt.Rule{ + newRule := rulefmt.Rule{ Record: rl.GetRecord(), Alert: rl.GetAlert(), Expr: rl.GetExpr(), - For: model.Duration(*rl.GetFor()), Labels: client.FromLabelAdaptersToLabels(rl.Labels).Map(), Annotations: client.FromLabelAdaptersToLabels(rl.Annotations).Map(), + For: model.Duration(rl.GetFor()), } + + formattedRuleGroup.Rules[i] = newRule } return formattedRuleGroup diff --git a/pkg/ruler/rules/rules.pb.go b/pkg/ruler/rules/rules.pb.go index 9219afeb376..b0535365200 100644 --- a/pkg/ruler/rules/rules.pb.go +++ b/pkg/ruler/rules/rules.pb.go @@ -4,6 +4,7 @@ package rules import ( + encoding_binary "encoding/binary" fmt "fmt" _ "github.com/cortexproject/cortex/pkg/ingester/client" github_com_cortexproject_cortex_pkg_ingester_client "github.com/cortexproject/cortex/pkg/ingester/client" @@ -11,6 +12,7 @@ import ( proto "github.com/gogo/protobuf/proto" github_com_gogo_protobuf_types "github.com/gogo/protobuf/types" _ "github.com/golang/protobuf/ptypes/duration" + _ "github.com/golang/protobuf/ptypes/timestamp" io "io" math "math" reflect "reflect" @@ -32,12 +34,12 @@ const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package // RuleGroupDesc is a proto representation of a cortex rule group type RuleGroupDesc struct { - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Namespace string `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` - Interval *time.Duration `protobuf:"bytes,3,opt,name=interval,proto3,stdduration" json:"interval,omitempty"` - Rules []*RuleDesc `protobuf:"bytes,4,rep,name=rules,proto3" json:"rules,omitempty"` - Deleted bool `protobuf:"varint,5,opt,name=deleted,proto3" json:"deleted,omitempty"` - User string `protobuf:"bytes,6,opt,name=user,proto3" json:"user,omitempty"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Namespace string `protobuf:"bytes,2,opt,name=namespace,proto3" json:"namespace,omitempty"` + Interval time.Duration `protobuf:"bytes,3,opt,name=interval,proto3,stdduration" json:"interval"` + Rules []*RuleDesc `protobuf:"bytes,4,rep,name=rules,proto3" json:"rules,omitempty"` + Deleted bool `protobuf:"varint,5,opt,name=deleted,proto3" json:"deleted,omitempty"` + User string `protobuf:"bytes,6,opt,name=user,proto3" json:"user,omitempty"` } func (m *RuleGroupDesc) Reset() { *m = RuleGroupDesc{} } @@ -86,11 +88,11 @@ func (m *RuleGroupDesc) GetNamespace() string { return "" } -func (m *RuleGroupDesc) GetInterval() *time.Duration { +func (m *RuleGroupDesc) GetInterval() time.Duration { if m != nil { return m.Interval } - return nil + return 0 } func (m *RuleGroupDesc) GetRules() []*RuleDesc { @@ -119,9 +121,13 @@ type RuleDesc struct { Expr string `protobuf:"bytes,1,opt,name=expr,proto3" json:"expr,omitempty"` Record string `protobuf:"bytes,2,opt,name=record,proto3" json:"record,omitempty"` Alert string `protobuf:"bytes,3,opt,name=alert,proto3" json:"alert,omitempty"` - For *time.Duration `protobuf:"bytes,4,opt,name=for,proto3,stdduration" json:"for,omitempty"` + For time.Duration `protobuf:"bytes,4,opt,name=for,proto3,stdduration" json:"for"` Labels []github_com_cortexproject_cortex_pkg_ingester_client.LabelAdapter `protobuf:"bytes,5,rep,name=labels,proto3,customtype=github.com/cortexproject/cortex/pkg/ingester/client.LabelAdapter" json:"labels"` Annotations []github_com_cortexproject_cortex_pkg_ingester_client.LabelAdapter `protobuf:"bytes,6,rep,name=annotations,proto3,customtype=github.com/cortexproject/cortex/pkg/ingester/client.LabelAdapter" json:"annotations"` + State string `protobuf:"bytes,7,opt,name=state,proto3" json:"state,omitempty"` + Health string `protobuf:"bytes,8,opt,name=health,proto3" json:"health,omitempty"` + LastError string `protobuf:"bytes,9,opt,name=lastError,proto3" json:"lastError,omitempty"` + Alerts []*AlertDesc `protobuf:"bytes,10,rep,name=alerts,proto3" json:"alerts,omitempty"` } func (m *RuleDesc) Reset() { *m = RuleDesc{} } @@ -177,50 +183,185 @@ func (m *RuleDesc) GetAlert() string { return "" } -func (m *RuleDesc) GetFor() *time.Duration { +func (m *RuleDesc) GetFor() time.Duration { if m != nil { return m.For } + return 0 +} + +func (m *RuleDesc) GetState() string { + if m != nil { + return m.State + } + return "" +} + +func (m *RuleDesc) GetHealth() string { + if m != nil { + return m.Health + } + return "" +} + +func (m *RuleDesc) GetLastError() string { + if m != nil { + return m.LastError + } + return "" +} + +func (m *RuleDesc) GetAlerts() []*AlertDesc { + if m != nil { + return m.Alerts + } return nil } +type AlertDesc struct { + State string `protobuf:"bytes,1,opt,name=state,proto3" json:"state,omitempty"` + Labels []github_com_cortexproject_cortex_pkg_ingester_client.LabelAdapter `protobuf:"bytes,2,rep,name=labels,proto3,customtype=github.com/cortexproject/cortex/pkg/ingester/client.LabelAdapter" json:"labels"` + Annotations []github_com_cortexproject_cortex_pkg_ingester_client.LabelAdapter `protobuf:"bytes,3,rep,name=annotations,proto3,customtype=github.com/cortexproject/cortex/pkg/ingester/client.LabelAdapter" json:"annotations"` + Value float64 `protobuf:"fixed64,4,opt,name=value,proto3" json:"value,omitempty"` + ActiveAt time.Time `protobuf:"bytes,5,opt,name=active_at,json=activeAt,proto3,stdtime" json:"active_at"` + FiredAt time.Time `protobuf:"bytes,6,opt,name=fired_at,json=firedAt,proto3,stdtime" json:"fired_at"` + ResolvedAt time.Time `protobuf:"bytes,7,opt,name=resolved_at,json=resolvedAt,proto3,stdtime" json:"resolved_at"` + LastSentAt time.Time `protobuf:"bytes,8,opt,name=last_sent_at,json=lastSentAt,proto3,stdtime" json:"last_sent_at"` + ValidUntil time.Time `protobuf:"bytes,9,opt,name=valid_until,json=validUntil,proto3,stdtime" json:"valid_until"` +} + +func (m *AlertDesc) Reset() { *m = AlertDesc{} } +func (*AlertDesc) ProtoMessage() {} +func (*AlertDesc) Descriptor() ([]byte, []int) { + return fileDescriptor_8e722d3e922f0937, []int{2} +} +func (m *AlertDesc) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AlertDesc) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AlertDesc.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalTo(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *AlertDesc) XXX_Merge(src proto.Message) { + xxx_messageInfo_AlertDesc.Merge(m, src) +} +func (m *AlertDesc) XXX_Size() int { + return m.Size() +} +func (m *AlertDesc) XXX_DiscardUnknown() { + xxx_messageInfo_AlertDesc.DiscardUnknown(m) +} + +var xxx_messageInfo_AlertDesc proto.InternalMessageInfo + +func (m *AlertDesc) GetState() string { + if m != nil { + return m.State + } + return "" +} + +func (m *AlertDesc) GetValue() float64 { + if m != nil { + return m.Value + } + return 0 +} + +func (m *AlertDesc) GetActiveAt() time.Time { + if m != nil { + return m.ActiveAt + } + return time.Time{} +} + +func (m *AlertDesc) GetFiredAt() time.Time { + if m != nil { + return m.FiredAt + } + return time.Time{} +} + +func (m *AlertDesc) GetResolvedAt() time.Time { + if m != nil { + return m.ResolvedAt + } + return time.Time{} +} + +func (m *AlertDesc) GetLastSentAt() time.Time { + if m != nil { + return m.LastSentAt + } + return time.Time{} +} + +func (m *AlertDesc) GetValidUntil() time.Time { + if m != nil { + return m.ValidUntil + } + return time.Time{} +} + func init() { proto.RegisterType((*RuleGroupDesc)(nil), "rules.RuleGroupDesc") proto.RegisterType((*RuleDesc)(nil), "rules.RuleDesc") + proto.RegisterType((*AlertDesc)(nil), "rules.AlertDesc") } func init() { proto.RegisterFile("rules.proto", fileDescriptor_8e722d3e922f0937) } var fileDescriptor_8e722d3e922f0937 = []byte{ - // 448 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xb4, 0x51, 0x41, 0x8b, 0xd4, 0x30, - 0x14, 0x6e, 0x76, 0x66, 0xea, 0x4c, 0x06, 0x11, 0x83, 0x48, 0x5c, 0x24, 0x53, 0x16, 0x84, 0x5e, - 0x6c, 0x71, 0x3d, 0xee, 0x45, 0x87, 0x05, 0x3d, 0x78, 0x90, 0x1e, 0xbd, 0xa5, 0xed, 0xdb, 0x5a, - 0xcd, 0x36, 0x25, 0x49, 0xc5, 0x8b, 0xe0, 0x4f, 0xf0, 0xe8, 0x4f, 0xf0, 0xa7, 0xec, 0x71, 0xc0, - 0xcb, 0xe2, 0x61, 0x75, 0x3a, 0x17, 0x8f, 0x0b, 0xfe, 0x01, 0x49, 0xd2, 0xba, 0x7b, 0x14, 0xc1, - 0x53, 0xde, 0x97, 0xef, 0xe5, 0xbd, 0xef, 0xfb, 0x82, 0x97, 0xaa, 0x13, 0xa0, 0x93, 0x56, 0x49, - 0x23, 0xc9, 0xcc, 0x81, 0xfd, 0x87, 0x55, 0x6d, 0x5e, 0x77, 0x79, 0x52, 0xc8, 0xd3, 0xb4, 0x92, - 0x95, 0x4c, 0x1d, 0x9b, 0x77, 0x27, 0x0e, 0x39, 0xe0, 0x2a, 0xff, 0x6a, 0x9f, 0x55, 0x52, 0x56, - 0x02, 0xae, 0xba, 0xca, 0x4e, 0x71, 0x53, 0xcb, 0x66, 0xe0, 0x9f, 0x5c, 0x1b, 0x57, 0x48, 0x65, - 0xe0, 0x7d, 0xab, 0xe4, 0x1b, 0x28, 0xcc, 0x80, 0xd2, 0xf6, 0x6d, 0x95, 0xd6, 0x4d, 0x05, 0xda, - 0x80, 0x4a, 0x0b, 0x51, 0x43, 0x33, 0x52, 0x7e, 0xc2, 0xc1, 0x57, 0x84, 0x6f, 0x66, 0x9d, 0x80, - 0x67, 0x4a, 0x76, 0xed, 0x31, 0xe8, 0x82, 0x10, 0x3c, 0x6d, 0xf8, 0x29, 0x50, 0x14, 0xa1, 0x78, - 0x91, 0xb9, 0x9a, 0xdc, 0xc7, 0x0b, 0x7b, 0xea, 0x96, 0x17, 0x40, 0xf7, 0x1c, 0x71, 0x75, 0x41, - 0x8e, 0xf0, 0xbc, 0x6e, 0x0c, 0xa8, 0x77, 0x5c, 0xd0, 0x49, 0x84, 0xe2, 0xe5, 0xe1, 0xbd, 0xc4, - 0x0b, 0x4f, 0x46, 0xe1, 0xc9, 0xf1, 0x20, 0x7c, 0x3d, 0xfd, 0xfc, 0x7d, 0x85, 0xb2, 0x3f, 0x0f, - 0xc8, 0x03, 0xec, 0xa3, 0xa1, 0xd3, 0x68, 0x12, 0x2f, 0x0f, 0x6f, 0x25, 0x3e, 0x35, 0xab, 0xc9, - 0xca, 0xc9, 0x3c, 0x4b, 0x28, 0xbe, 0x51, 0x82, 0x00, 0x03, 0x25, 0x9d, 0x45, 0x28, 0x9e, 0x67, - 0x23, 0xb4, 0x7a, 0x3b, 0x0d, 0x8a, 0x86, 0x5e, 0xaf, 0xad, 0x0f, 0x7e, 0xed, 0xe1, 0xf9, 0x38, - 0xc1, 0x36, 0xd8, 0x5c, 0x46, 0x43, 0xb6, 0x26, 0x77, 0x71, 0xa8, 0xa0, 0x90, 0xaa, 0x1c, 0xdc, - 0x0c, 0x88, 0xdc, 0xc1, 0x33, 0x2e, 0x40, 0x19, 0xe7, 0x63, 0x91, 0x79, 0x40, 0x1e, 0xe1, 0xc9, - 0x89, 0x54, 0x74, 0xfa, 0x77, 0xde, 0x6c, 0x2f, 0xd1, 0x38, 0x14, 0x3c, 0x07, 0xa1, 0xe9, 0xcc, - 0xf9, 0xba, 0x9d, 0x0c, 0xb1, 0xbf, 0xb0, 0xb7, 0x2f, 0x79, 0xad, 0xd6, 0xcf, 0xcf, 0x2e, 0x56, - 0xc1, 0xb7, 0x8b, 0xd5, 0xbf, 0x7c, 0xa2, 0x1f, 0xf3, 0xb4, 0xe4, 0xad, 0x01, 0x95, 0x0d, 0xab, - 0xc8, 0x07, 0xbc, 0xe4, 0x4d, 0x23, 0x8d, 0x53, 0xa3, 0x69, 0xf8, 0xff, 0x37, 0x5f, 0xdf, 0xb7, - 0x3e, 0xda, 0x6c, 0x59, 0x70, 0xbe, 0x65, 0xc1, 0xe5, 0x96, 0xa1, 0x8f, 0x3d, 0x43, 0x5f, 0x7a, - 0x86, 0xce, 0x7a, 0x86, 0x36, 0x3d, 0x43, 0x3f, 0x7a, 0x86, 0x7e, 0xf6, 0x2c, 0xb8, 0xec, 0x19, - 0xfa, 0xb4, 0x63, 0xc1, 0x66, 0xc7, 0x82, 0xf3, 0x1d, 0x0b, 0x5e, 0xf9, 0x0f, 0xce, 0x43, 0x17, - 0xe7, 0xe3, 0xdf, 0x01, 0x00, 0x00, 0xff, 0xff, 0x67, 0x3a, 0xe5, 0x13, 0x36, 0x03, 0x00, 0x00, + // 651 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xc4, 0x54, 0xbf, 0x6f, 0xd4, 0x30, + 0x14, 0x3e, 0xf7, 0x7a, 0xbf, 0x7c, 0x20, 0xc0, 0x42, 0xc8, 0x54, 0xc8, 0x77, 0xaa, 0x84, 0x74, + 0x0b, 0x39, 0xa9, 0x88, 0x89, 0x01, 0xae, 0x6a, 0x81, 0x81, 0x01, 0x05, 0x58, 0x58, 0x2a, 0x5f, + 0xf2, 0x9a, 0x06, 0x7c, 0x71, 0x64, 0x3b, 0x27, 0x16, 0x24, 0x16, 0xf6, 0x8e, 0xfc, 0x09, 0xfc, + 0x29, 0x1d, 0xbb, 0x51, 0x31, 0x14, 0x9a, 0x2e, 0x2c, 0x48, 0xfd, 0x13, 0x90, 0xed, 0xa4, 0x57, + 0xc1, 0xd2, 0x22, 0x01, 0x53, 0xfc, 0xf9, 0xf9, 0x7d, 0xef, 0x7b, 0xcf, 0x9f, 0x83, 0xfb, 0xaa, + 0x10, 0xa0, 0x83, 0x5c, 0x49, 0x23, 0x49, 0xcb, 0x81, 0x95, 0x3b, 0x49, 0x6a, 0x76, 0x8a, 0x69, + 0x10, 0xc9, 0xd9, 0x38, 0x91, 0x89, 0x1c, 0xbb, 0xe8, 0xb4, 0xd8, 0x76, 0xc8, 0x01, 0xb7, 0xf2, + 0x59, 0x2b, 0x2c, 0x91, 0x32, 0x11, 0xb0, 0x38, 0x15, 0x17, 0x8a, 0x9b, 0x54, 0x66, 0x55, 0x7c, + 0xf0, 0x6b, 0xdc, 0xa4, 0x33, 0xd0, 0x86, 0xcf, 0xf2, 0xea, 0xc0, 0xc3, 0x33, 0xf5, 0x22, 0xa9, + 0x0c, 0xbc, 0xcd, 0x95, 0x7c, 0x0d, 0x91, 0xa9, 0xd0, 0x38, 0x7f, 0x93, 0x8c, 0xd3, 0x2c, 0x01, + 0x6d, 0x40, 0x8d, 0x23, 0x91, 0x42, 0x56, 0x87, 0x3c, 0xc3, 0xea, 0x67, 0x84, 0x2f, 0x87, 0x85, + 0x80, 0xc7, 0x4a, 0x16, 0xf9, 0x06, 0xe8, 0x88, 0x10, 0xbc, 0x9c, 0xf1, 0x19, 0x50, 0x34, 0x44, + 0xa3, 0x5e, 0xe8, 0xd6, 0xe4, 0x16, 0xee, 0xd9, 0xaf, 0xce, 0x79, 0x04, 0x74, 0xc9, 0x05, 0x16, + 0x1b, 0xe4, 0x01, 0xee, 0xa6, 0x99, 0x01, 0x35, 0xe7, 0x82, 0x36, 0x87, 0x68, 0xd4, 0x5f, 0xbb, + 0x19, 0x78, 0xe5, 0x41, 0xad, 0x3c, 0xd8, 0xa8, 0x3a, 0x5b, 0xef, 0xee, 0x1d, 0x0e, 0x1a, 0x1f, + 0xbf, 0x0e, 0x50, 0x78, 0x9a, 0x44, 0x6e, 0x63, 0x3f, 0x3f, 0xba, 0x3c, 0x6c, 0x8e, 0xfa, 0x6b, + 0x57, 0x02, 0x3f, 0x5a, 0xab, 0xcb, 0x4a, 0x0a, 0x7d, 0x94, 0x50, 0xdc, 0x89, 0x41, 0x80, 0x81, + 0x98, 0xb6, 0x86, 0x68, 0xd4, 0x0d, 0x6b, 0x68, 0x35, 0x17, 0x1a, 0x14, 0x6d, 0x7b, 0xcd, 0x76, + 0xbd, 0xfa, 0xa3, 0x89, 0xbb, 0x35, 0x83, 0x3d, 0x60, 0x67, 0x53, 0x37, 0x65, 0xd7, 0xe4, 0x06, + 0x6e, 0x2b, 0x88, 0xa4, 0x8a, 0xab, 0x8e, 0x2a, 0x44, 0xae, 0xe3, 0x16, 0x17, 0xa0, 0x8c, 0xeb, + 0xa5, 0x17, 0x7a, 0x40, 0xee, 0xe1, 0xe6, 0xb6, 0x54, 0x74, 0xf9, 0xfc, 0xfd, 0xd9, 0xf3, 0x44, + 0xe3, 0xb6, 0xe0, 0x53, 0x10, 0x9a, 0xb6, 0x5c, 0x6f, 0xd7, 0x82, 0x6a, 0xfc, 0x4f, 0xed, 0xee, + 0x33, 0x9e, 0xaa, 0xf5, 0x27, 0x36, 0xe3, 0xcb, 0xe1, 0xe0, 0x4f, 0x2e, 0xd3, 0xd3, 0x4c, 0x62, + 0x9e, 0x1b, 0x50, 0x61, 0x55, 0x8a, 0xbc, 0xc3, 0x7d, 0x9e, 0x65, 0xd2, 0x38, 0x45, 0x9a, 0xb6, + 0xff, 0x7e, 0xe5, 0xb3, 0xf5, 0xec, 0x00, 0xb5, 0xe1, 0x06, 0x68, 0xc7, 0x0f, 0xd0, 0x01, 0x3b, + 0xee, 0x1d, 0xe0, 0xc2, 0xec, 0xd0, 0xae, 0x1f, 0xb7, 0x47, 0xd6, 0x5b, 0x82, 0x6b, 0xb3, 0xa9, + 0x94, 0x54, 0xb4, 0xe7, 0xbd, 0x75, 0xba, 0x41, 0x46, 0xb8, 0xed, 0xe6, 0xaf, 0x29, 0x76, 0x5d, + 0x5c, 0xad, 0xbc, 0x31, 0xb1, 0x9b, 0xce, 0x1c, 0x55, 0x7c, 0xf5, 0x43, 0x0b, 0xf7, 0x4e, 0x77, + 0x17, 0x1a, 0xd0, 0x59, 0x0d, 0x8b, 0xdb, 0x58, 0xfa, 0x6f, 0xb7, 0xd1, 0xfc, 0xf7, 0xb7, 0x31, + 0xe7, 0xa2, 0x00, 0x67, 0x5d, 0x14, 0x7a, 0x40, 0x26, 0xb8, 0xc7, 0x23, 0x93, 0xce, 0x61, 0x8b, + 0x1b, 0xf7, 0x9a, 0xfa, 0x6b, 0x2b, 0xbf, 0x99, 0xfa, 0x45, 0xfd, 0xbb, 0xf1, 0xae, 0xde, 0x75, + 0xaf, 0xd6, 0xa7, 0x4d, 0x8c, 0x7d, 0xf6, 0xdb, 0xa9, 0x82, 0xd8, 0x32, 0xb4, 0x2f, 0xc0, 0xd0, + 0x71, 0x59, 0x13, 0x43, 0x36, 0x71, 0x5f, 0x81, 0x96, 0x62, 0xee, 0x39, 0x3a, 0x17, 0xe0, 0xc0, + 0x75, 0xe2, 0xc4, 0x90, 0x47, 0xf8, 0x92, 0xf5, 0xcb, 0x96, 0x86, 0xcc, 0x58, 0x9e, 0xee, 0x45, + 0x78, 0x6c, 0xe6, 0x73, 0xc8, 0x8c, 0x97, 0x33, 0xe7, 0x22, 0x8d, 0xb7, 0x8a, 0xcc, 0xa4, 0xc2, + 0x59, 0xf1, 0xdc, 0x34, 0x2e, 0xf1, 0xa5, 0xcd, 0x5b, 0xbf, 0xbf, 0x7f, 0xc4, 0x1a, 0x07, 0x47, + 0xac, 0x71, 0x72, 0xc4, 0xd0, 0xfb, 0x92, 0xa1, 0x4f, 0x25, 0x43, 0x7b, 0x25, 0x43, 0xfb, 0x25, + 0x43, 0xdf, 0x4a, 0x86, 0xbe, 0x97, 0xac, 0x71, 0x52, 0x32, 0xb4, 0x7b, 0xcc, 0x1a, 0xfb, 0xc7, + 0xac, 0x71, 0x70, 0xcc, 0x1a, 0xaf, 0xfc, 0x2f, 0x6e, 0xda, 0x76, 0x65, 0xee, 0xfe, 0x0c, 0x00, + 0x00, 0xff, 0xff, 0x26, 0xdc, 0xc9, 0xc9, 0x5d, 0x06, 0x00, 0x00, } func (this *RuleGroupDesc) Equal(that interface{}) bool { @@ -248,13 +389,7 @@ func (this *RuleGroupDesc) Equal(that interface{}) bool { if this.Namespace != that1.Namespace { return false } - if this.Interval != nil && that1.Interval != nil { - if *this.Interval != *that1.Interval { - return false - } - } else if this.Interval != nil { - return false - } else if that1.Interval != nil { + if this.Interval != that1.Interval { return false } if len(this.Rules) != len(that1.Rules) { @@ -301,13 +436,64 @@ func (this *RuleDesc) Equal(that interface{}) bool { if this.Alert != that1.Alert { return false } - if this.For != nil && that1.For != nil { - if *this.For != *that1.For { + if this.For != that1.For { + return false + } + if len(this.Labels) != len(that1.Labels) { + return false + } + for i := range this.Labels { + if !this.Labels[i].Equal(that1.Labels[i]) { + return false + } + } + if len(this.Annotations) != len(that1.Annotations) { + return false + } + for i := range this.Annotations { + if !this.Annotations[i].Equal(that1.Annotations[i]) { + return false + } + } + if this.State != that1.State { + return false + } + if this.Health != that1.Health { + return false + } + if this.LastError != that1.LastError { + return false + } + if len(this.Alerts) != len(that1.Alerts) { + return false + } + for i := range this.Alerts { + if !this.Alerts[i].Equal(that1.Alerts[i]) { + return false + } + } + return true +} +func (this *AlertDesc) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*AlertDesc) + if !ok { + that2, ok := that.(AlertDesc) + if ok { + that1 = &that2 + } else { return false } - } else if this.For != nil { + } + if that1 == nil { + return this == nil + } else if this == nil { return false - } else if that1.For != nil { + } + if this.State != that1.State { return false } if len(this.Labels) != len(that1.Labels) { @@ -326,6 +512,24 @@ func (this *RuleDesc) Equal(that interface{}) bool { return false } } + if this.Value != that1.Value { + return false + } + if !this.ActiveAt.Equal(that1.ActiveAt) { + return false + } + if !this.FiredAt.Equal(that1.FiredAt) { + return false + } + if !this.ResolvedAt.Equal(that1.ResolvedAt) { + return false + } + if !this.LastSentAt.Equal(that1.LastSentAt) { + return false + } + if !this.ValidUntil.Equal(that1.ValidUntil) { + return false + } return true } func (this *RuleGroupDesc) GoString() string { @@ -349,7 +553,7 @@ func (this *RuleDesc) GoString() string { if this == nil { return "nil" } - s := make([]string, 0, 10) + s := make([]string, 0, 14) s = append(s, "&rules.RuleDesc{") s = append(s, "Expr: "+fmt.Sprintf("%#v", this.Expr)+",\n") s = append(s, "Record: "+fmt.Sprintf("%#v", this.Record)+",\n") @@ -357,6 +561,30 @@ func (this *RuleDesc) GoString() string { s = append(s, "For: "+fmt.Sprintf("%#v", this.For)+",\n") s = append(s, "Labels: "+fmt.Sprintf("%#v", this.Labels)+",\n") s = append(s, "Annotations: "+fmt.Sprintf("%#v", this.Annotations)+",\n") + s = append(s, "State: "+fmt.Sprintf("%#v", this.State)+",\n") + s = append(s, "Health: "+fmt.Sprintf("%#v", this.Health)+",\n") + s = append(s, "LastError: "+fmt.Sprintf("%#v", this.LastError)+",\n") + if this.Alerts != nil { + s = append(s, "Alerts: "+fmt.Sprintf("%#v", this.Alerts)+",\n") + } + s = append(s, "}") + return strings.Join(s, "") +} +func (this *AlertDesc) GoString() string { + if this == nil { + return "nil" + } + s := make([]string, 0, 13) + s = append(s, "&rules.AlertDesc{") + s = append(s, "State: "+fmt.Sprintf("%#v", this.State)+",\n") + s = append(s, "Labels: "+fmt.Sprintf("%#v", this.Labels)+",\n") + s = append(s, "Annotations: "+fmt.Sprintf("%#v", this.Annotations)+",\n") + s = append(s, "Value: "+fmt.Sprintf("%#v", this.Value)+",\n") + s = append(s, "ActiveAt: "+fmt.Sprintf("%#v", this.ActiveAt)+",\n") + s = append(s, "FiredAt: "+fmt.Sprintf("%#v", this.FiredAt)+",\n") + s = append(s, "ResolvedAt: "+fmt.Sprintf("%#v", this.ResolvedAt)+",\n") + s = append(s, "LastSentAt: "+fmt.Sprintf("%#v", this.LastSentAt)+",\n") + s = append(s, "ValidUntil: "+fmt.Sprintf("%#v", this.ValidUntil)+",\n") s = append(s, "}") return strings.Join(s, "") } @@ -395,16 +623,14 @@ func (m *RuleGroupDesc) MarshalTo(dAtA []byte) (int, error) { i = encodeVarintRules(dAtA, i, uint64(len(m.Namespace))) i += copy(dAtA[i:], m.Namespace) } - if m.Interval != nil { - dAtA[i] = 0x1a - i++ - i = encodeVarintRules(dAtA, i, uint64(github_com_gogo_protobuf_types.SizeOfStdDuration(*m.Interval))) - n1, err := github_com_gogo_protobuf_types.StdDurationMarshalTo(*m.Interval, dAtA[i:]) - if err != nil { - return 0, err - } - i += n1 + dAtA[i] = 0x1a + i++ + i = encodeVarintRules(dAtA, i, uint64(github_com_gogo_protobuf_types.SizeOfStdDuration(m.Interval))) + n1, err := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.Interval, dAtA[i:]) + if err != nil { + return 0, err } + i += n1 if len(m.Rules) > 0 { for _, msg := range m.Rules { dAtA[i] = 0x22 @@ -469,16 +695,14 @@ func (m *RuleDesc) MarshalTo(dAtA []byte) (int, error) { i = encodeVarintRules(dAtA, i, uint64(len(m.Alert))) i += copy(dAtA[i:], m.Alert) } - if m.For != nil { - dAtA[i] = 0x22 - i++ - i = encodeVarintRules(dAtA, i, uint64(github_com_gogo_protobuf_types.SizeOfStdDuration(*m.For))) - n2, err := github_com_gogo_protobuf_types.StdDurationMarshalTo(*m.For, dAtA[i:]) - if err != nil { - return 0, err - } - i += n2 + dAtA[i] = 0x22 + i++ + i = encodeVarintRules(dAtA, i, uint64(github_com_gogo_protobuf_types.SizeOfStdDuration(m.For))) + n2, err := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.For, dAtA[i:]) + if err != nil { + return 0, err } + i += n2 if len(m.Labels) > 0 { for _, msg := range m.Labels { dAtA[i] = 0x2a @@ -503,6 +727,130 @@ func (m *RuleDesc) MarshalTo(dAtA []byte) (int, error) { i += n } } + if len(m.State) > 0 { + dAtA[i] = 0x3a + i++ + i = encodeVarintRules(dAtA, i, uint64(len(m.State))) + i += copy(dAtA[i:], m.State) + } + if len(m.Health) > 0 { + dAtA[i] = 0x42 + i++ + i = encodeVarintRules(dAtA, i, uint64(len(m.Health))) + i += copy(dAtA[i:], m.Health) + } + if len(m.LastError) > 0 { + dAtA[i] = 0x4a + i++ + i = encodeVarintRules(dAtA, i, uint64(len(m.LastError))) + i += copy(dAtA[i:], m.LastError) + } + if len(m.Alerts) > 0 { + for _, msg := range m.Alerts { + dAtA[i] = 0x52 + i++ + i = encodeVarintRules(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *AlertDesc) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AlertDesc) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + if len(m.State) > 0 { + dAtA[i] = 0xa + i++ + i = encodeVarintRules(dAtA, i, uint64(len(m.State))) + i += copy(dAtA[i:], m.State) + } + if len(m.Labels) > 0 { + for _, msg := range m.Labels { + dAtA[i] = 0x12 + i++ + i = encodeVarintRules(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + if len(m.Annotations) > 0 { + for _, msg := range m.Annotations { + dAtA[i] = 0x1a + i++ + i = encodeVarintRules(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + if m.Value != 0 { + dAtA[i] = 0x21 + i++ + encoding_binary.LittleEndian.PutUint64(dAtA[i:], uint64(math.Float64bits(float64(m.Value)))) + i += 8 + } + dAtA[i] = 0x2a + i++ + i = encodeVarintRules(dAtA, i, uint64(github_com_gogo_protobuf_types.SizeOfStdTime(m.ActiveAt))) + n3, err := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.ActiveAt, dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + dAtA[i] = 0x32 + i++ + i = encodeVarintRules(dAtA, i, uint64(github_com_gogo_protobuf_types.SizeOfStdTime(m.FiredAt))) + n4, err := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.FiredAt, dAtA[i:]) + if err != nil { + return 0, err + } + i += n4 + dAtA[i] = 0x3a + i++ + i = encodeVarintRules(dAtA, i, uint64(github_com_gogo_protobuf_types.SizeOfStdTime(m.ResolvedAt))) + n5, err := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.ResolvedAt, dAtA[i:]) + if err != nil { + return 0, err + } + i += n5 + dAtA[i] = 0x42 + i++ + i = encodeVarintRules(dAtA, i, uint64(github_com_gogo_protobuf_types.SizeOfStdTime(m.LastSentAt))) + n6, err := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.LastSentAt, dAtA[i:]) + if err != nil { + return 0, err + } + i += n6 + dAtA[i] = 0x4a + i++ + i = encodeVarintRules(dAtA, i, uint64(github_com_gogo_protobuf_types.SizeOfStdTime(m.ValidUntil))) + n7, err := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.ValidUntil, dAtA[i:]) + if err != nil { + return 0, err + } + i += n7 return i, nil } @@ -529,10 +877,8 @@ func (m *RuleGroupDesc) Size() (n int) { if l > 0 { n += 1 + l + sovRules(uint64(l)) } - if m.Interval != nil { - l = github_com_gogo_protobuf_types.SizeOfStdDuration(*m.Interval) - n += 1 + l + sovRules(uint64(l)) - } + l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.Interval) + n += 1 + l + sovRules(uint64(l)) if len(m.Rules) > 0 { for _, e := range m.Rules { l = e.Size() @@ -567,8 +913,49 @@ func (m *RuleDesc) Size() (n int) { if l > 0 { n += 1 + l + sovRules(uint64(l)) } - if m.For != nil { - l = github_com_gogo_protobuf_types.SizeOfStdDuration(*m.For) + l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.For) + n += 1 + l + sovRules(uint64(l)) + if len(m.Labels) > 0 { + for _, e := range m.Labels { + l = e.Size() + n += 1 + l + sovRules(uint64(l)) + } + } + if len(m.Annotations) > 0 { + for _, e := range m.Annotations { + l = e.Size() + n += 1 + l + sovRules(uint64(l)) + } + } + l = len(m.State) + if l > 0 { + n += 1 + l + sovRules(uint64(l)) + } + l = len(m.Health) + if l > 0 { + n += 1 + l + sovRules(uint64(l)) + } + l = len(m.LastError) + if l > 0 { + n += 1 + l + sovRules(uint64(l)) + } + if len(m.Alerts) > 0 { + for _, e := range m.Alerts { + l = e.Size() + n += 1 + l + sovRules(uint64(l)) + } + } + return n +} + +func (m *AlertDesc) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.State) + if l > 0 { n += 1 + l + sovRules(uint64(l)) } if len(m.Labels) > 0 { @@ -583,6 +970,19 @@ func (m *RuleDesc) Size() (n int) { n += 1 + l + sovRules(uint64(l)) } } + if m.Value != 0 { + n += 9 + } + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.ActiveAt) + n += 1 + l + sovRules(uint64(l)) + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.FiredAt) + n += 1 + l + sovRules(uint64(l)) + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.ResolvedAt) + n += 1 + l + sovRules(uint64(l)) + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.LastSentAt) + n += 1 + l + sovRules(uint64(l)) + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.ValidUntil) + n += 1 + l + sovRules(uint64(l)) return n } @@ -606,7 +1006,7 @@ func (this *RuleGroupDesc) String() string { s := strings.Join([]string{`&RuleGroupDesc{`, `Name:` + fmt.Sprintf("%v", this.Name) + `,`, `Namespace:` + fmt.Sprintf("%v", this.Namespace) + `,`, - `Interval:` + strings.Replace(fmt.Sprintf("%v", this.Interval), "Duration", "duration.Duration", 1) + `,`, + `Interval:` + strings.Replace(strings.Replace(this.Interval.String(), "Duration", "duration.Duration", 1), `&`, ``, 1) + `,`, `Rules:` + strings.Replace(fmt.Sprintf("%v", this.Rules), "RuleDesc", "RuleDesc", 1) + `,`, `Deleted:` + fmt.Sprintf("%v", this.Deleted) + `,`, `User:` + fmt.Sprintf("%v", this.User) + `,`, @@ -622,16 +1022,38 @@ func (this *RuleDesc) String() string { `Expr:` + fmt.Sprintf("%v", this.Expr) + `,`, `Record:` + fmt.Sprintf("%v", this.Record) + `,`, `Alert:` + fmt.Sprintf("%v", this.Alert) + `,`, - `For:` + strings.Replace(fmt.Sprintf("%v", this.For), "Duration", "duration.Duration", 1) + `,`, + `For:` + strings.Replace(strings.Replace(this.For.String(), "Duration", "duration.Duration", 1), `&`, ``, 1) + `,`, `Labels:` + fmt.Sprintf("%v", this.Labels) + `,`, `Annotations:` + fmt.Sprintf("%v", this.Annotations) + `,`, + `State:` + fmt.Sprintf("%v", this.State) + `,`, + `Health:` + fmt.Sprintf("%v", this.Health) + `,`, + `LastError:` + fmt.Sprintf("%v", this.LastError) + `,`, + `Alerts:` + strings.Replace(fmt.Sprintf("%v", this.Alerts), "AlertDesc", "AlertDesc", 1) + `,`, `}`, }, "") return s } -func valueToStringRules(v interface{}) string { - rv := reflect.ValueOf(v) - if rv.IsNil() { +func (this *AlertDesc) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&AlertDesc{`, + `State:` + fmt.Sprintf("%v", this.State) + `,`, + `Labels:` + fmt.Sprintf("%v", this.Labels) + `,`, + `Annotations:` + fmt.Sprintf("%v", this.Annotations) + `,`, + `Value:` + fmt.Sprintf("%v", this.Value) + `,`, + `ActiveAt:` + strings.Replace(strings.Replace(this.ActiveAt.String(), "Timestamp", "timestamp.Timestamp", 1), `&`, ``, 1) + `,`, + `FiredAt:` + strings.Replace(strings.Replace(this.FiredAt.String(), "Timestamp", "timestamp.Timestamp", 1), `&`, ``, 1) + `,`, + `ResolvedAt:` + strings.Replace(strings.Replace(this.ResolvedAt.String(), "Timestamp", "timestamp.Timestamp", 1), `&`, ``, 1) + `,`, + `LastSentAt:` + strings.Replace(strings.Replace(this.LastSentAt.String(), "Timestamp", "timestamp.Timestamp", 1), `&`, ``, 1) + `,`, + `ValidUntil:` + strings.Replace(strings.Replace(this.ValidUntil.String(), "Timestamp", "timestamp.Timestamp", 1), `&`, ``, 1) + `,`, + `}`, + }, "") + return s +} +func valueToStringRules(v interface{}) string { + rv := reflect.ValueOf(v) + if rv.IsNil() { return "nil" } pv := reflect.Indirect(rv).Interface() @@ -759,10 +1181,7 @@ func (m *RuleGroupDesc) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.Interval == nil { - m.Interval = new(time.Duration) - } - if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(m.Interval, dAtA[iNdEx:postIndex]); err != nil { + if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.Interval, dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1030,10 +1449,7 @@ func (m *RuleDesc) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if m.For == nil { - m.For = new(time.Duration) - } - if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(m.For, dAtA[iNdEx:postIndex]); err != nil { + if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.For, dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex @@ -1105,6 +1521,465 @@ func (m *RuleDesc) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field State", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRules + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRules + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthRules + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.State = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Health", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRules + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRules + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthRules + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Health = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastError", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRules + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRules + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthRules + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.LastError = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Alerts", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRules + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRules + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthRules + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Alerts = append(m.Alerts, &AlertDesc{}) + if err := m.Alerts[len(m.Alerts)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipRules(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthRules + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthRules + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *AlertDesc) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRules + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AlertDesc: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AlertDesc: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field State", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRules + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthRules + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthRules + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.State = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Labels", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRules + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRules + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthRules + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Labels = append(m.Labels, github_com_cortexproject_cortex_pkg_ingester_client.LabelAdapter{}) + if err := m.Labels[len(m.Labels)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Annotations", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRules + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRules + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthRules + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Annotations = append(m.Annotations, github_com_cortexproject_cortex_pkg_ingester_client.LabelAdapter{}) + if err := m.Annotations[len(m.Annotations)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 1 { + return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) + } + var v uint64 + if (iNdEx + 8) > l { + return io.ErrUnexpectedEOF + } + v = uint64(encoding_binary.LittleEndian.Uint64(dAtA[iNdEx:])) + iNdEx += 8 + m.Value = float64(math.Float64frombits(v)) + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ActiveAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRules + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRules + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthRules + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.ActiveAt, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FiredAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRules + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRules + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthRules + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.FiredAt, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 7: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ResolvedAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRules + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRules + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthRules + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.ResolvedAt, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field LastSentAt", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRules + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRules + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthRules + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.LastSentAt, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidUntil", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowRules + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthRules + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthRules + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.ValidUntil, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipRules(dAtA[iNdEx:]) diff --git a/pkg/ruler/rules/rules.proto b/pkg/ruler/rules/rules.proto index 0936db55fe3..3b842cdc5b8 100644 --- a/pkg/ruler/rules/rules.proto +++ b/pkg/ruler/rules/rules.proto @@ -1,4 +1,3 @@ -// Rule Proto Representations syntax = "proto3"; @@ -8,6 +7,7 @@ option go_package = "rules"; import "github.com/gogo/protobuf/gogoproto/gogo.proto"; import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; import "github.com/cortexproject/cortex/pkg/ingester/client/cortex.proto"; option (gogoproto.marshaler_all) = true; @@ -17,7 +17,8 @@ option (gogoproto.unmarshaler_all) = true; message RuleGroupDesc { string name = 1; string namespace = 2; - google.protobuf.Duration interval = 3 [ (gogoproto.stdduration) = true ]; + google.protobuf.Duration interval = 3 + [(gogoproto.nullable) = false, (gogoproto.stdduration) = true]; repeated RuleDesc rules = 4; bool deleted = 5; string user = 6; @@ -28,17 +29,44 @@ message RuleDesc { string expr = 1; string record = 2; string alert = 3; - google.protobuf.Duration for = 4 [(gogoproto.stdduration) = true]; - repeated cortex.LabelPair labels = 5 [ - (gogoproto.nullable) = false, - (gogoproto.customtype) = - "github.com/cortexproject/cortex/pkg/ingester/client.LabelAdapter" - ]; - ; - repeated cortex.LabelPair annotations = 6 [ - (gogoproto.nullable) = false, - (gogoproto.customtype) = - "github.com/cortexproject/cortex/pkg/ingester/client.LabelAdapter" - ]; - ; + google.protobuf.Duration for = 4 [(gogoproto.nullable) = false,(gogoproto.stdduration) = true]; + repeated cortex.LabelPair labels = 5 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = + "github.com/cortexproject/cortex/pkg/ingester/client.LabelAdapter" + ]; + repeated cortex.LabelPair annotations = 6 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = + "github.com/cortexproject/cortex/pkg/ingester/client.LabelAdapter" + ]; + string state = 7; + string health = 8; + string lastError = 9; + repeated AlertDesc alerts = 10; +} + +message AlertDesc { + string state = 1; + repeated cortex.LabelPair labels = 2 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = + "github.com/cortexproject/cortex/pkg/ingester/client.LabelAdapter" + ]; + repeated cortex.LabelPair annotations = 3 [ + (gogoproto.nullable) = false, + (gogoproto.customtype) = + "github.com/cortexproject/cortex/pkg/ingester/client.LabelAdapter" + ]; + double value = 4; + google.protobuf.Timestamp active_at = 5 + [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + google.protobuf.Timestamp fired_at = 6 + [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + google.protobuf.Timestamp resolved_at = 7 + [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + google.protobuf.Timestamp last_sent_at = 8 + [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; + google.protobuf.Timestamp valid_until = 9 + [(gogoproto.nullable) = false, (gogoproto.stdtime) = true]; } \ No newline at end of file diff --git a/pkg/ruler/storage.go b/pkg/ruler/storage.go index 81ed2c75b5f..536a9989342 100644 --- a/pkg/ruler/storage.go +++ b/pkg/ruler/storage.go @@ -13,7 +13,7 @@ type RuleStoreConfig struct { Type string `yaml:"type"` ConfigDB client.Config - mock *mockRuleStore + mock rules.RuleStore } // RegisterFlags registers flags. diff --git a/pkg/ruler/store_mock_test.go b/pkg/ruler/store_mock_test.go new file mode 100644 index 00000000000..f8b3f9e7ed3 --- /dev/null +++ b/pkg/ruler/store_mock_test.go @@ -0,0 +1,60 @@ +package ruler + +import ( + "context" + "time" + + "github.com/cortexproject/cortex/pkg/ruler/rules" +) + +type mockRuleStore struct { + rules map[string]rules.RuleGroupList +} + +var ( + interval, _ = time.ParseDuration("1m") + mockRules = map[string]rules.RuleGroupList{ + "user1": { + &rules.RuleGroupDesc{ + Name: "group1", + Namespace: "namespace1", + User: "user1", + Rules: []*rules.RuleDesc{ + { + Record: "UP_RULE", + Expr: "up", + }, + { + Alert: "UP_ALERT", + Expr: "up < 1", + }, + }, + Interval: interval, + }, + }, + "user2": { + &rules.RuleGroupDesc{ + Name: "group1", + Namespace: "namespace1", + User: "user2", + Rules: []*rules.RuleDesc{ + { + Record: "UP_RULE", + Expr: "up", + }, + }, + Interval: interval, + }, + }, + } +) + +func newMockRuleStore() *mockRuleStore { + return &mockRuleStore{ + rules: mockRules, + } +} + +func (m *mockRuleStore) ListAllRuleGroups(ctx context.Context) (map[string]rules.RuleGroupList, error) { + return m.rules, nil +}