From dde4485839bc7a3c1f8e9b13d7fb99ae0b097bb0 Mon Sep 17 00:00:00 2001 From: Srikanth Chekuri Date: Fri, 30 Aug 2024 10:34:11 +0530 Subject: [PATCH] chore: add types for alert type, state, and rule data kind (#5804) --- pkg/query-service/app/http_handler.go | 10 +++--- pkg/query-service/rules/alerting.go | 29 +++++++++++++++++ pkg/query-service/rules/api_params.go | 32 ++++++++++++++----- pkg/query-service/rules/db.go | 6 ++-- pkg/query-service/rules/manager.go | 12 +++---- pkg/query-service/rules/threshold_rule.go | 7 ++-- .../rules/threshold_rule_test.go | 2 +- 7 files changed, 71 insertions(+), 27 deletions(-) diff --git a/pkg/query-service/app/http_handler.go b/pkg/query-service/app/http_handler.go index 5d3d9affd5..1210cd4f67 100644 --- a/pkg/query-service/app/http_handler.go +++ b/pkg/query-service/app/http_handler.go @@ -805,7 +805,7 @@ func (aH *APIHandler) getRuleStateHistory(w http.ResponseWriter, r *http.Request continue } filterItems := []v3.FilterItem{} - if rule.AlertType == "LOGS_BASED_ALERT" || rule.AlertType == "TRACES_BASED_ALERT" { + if rule.AlertType == rules.AlertTypeLogs || rule.AlertType == rules.AlertTypeTraces { if rule.RuleCondition.CompositeQuery != nil { if rule.RuleCondition.QueryType() == v3.QueryTypeBuilder { for _, query := range rule.RuleCondition.CompositeQuery.BuilderQueries { @@ -818,9 +818,9 @@ func (aH *APIHandler) getRuleStateHistory(w http.ResponseWriter, r *http.Request } newFilters := common.PrepareFilters(lbls, filterItems) ts := time.Unix(res.Items[idx].UnixMilli/1000, 0) - if rule.AlertType == "LOGS_BASED_ALERT" { + if rule.AlertType == rules.AlertTypeLogs { res.Items[idx].RelatedLogsLink = common.PrepareLinksToLogs(ts, newFilters) - } else if rule.AlertType == "TRACES_BASED_ALERT" { + } else if rule.AlertType == rules.AlertTypeTraces { res.Items[idx].RelatedTracesLink = common.PrepareLinksToTraces(ts, newFilters) } } @@ -854,9 +854,9 @@ func (aH *APIHandler) getRuleStateHistoryTopContributors(w http.ResponseWriter, } ts := time.Unix(params.End/1000, 0) filters := common.PrepareFilters(lbls, nil) - if rule.AlertType == "LOGS_BASED_ALERT" { + if rule.AlertType == rules.AlertTypeLogs { res[idx].RelatedLogsLink = common.PrepareLinksToLogs(ts, filters) - } else if rule.AlertType == "TRACES_BASED_ALERT" { + } else if rule.AlertType == rules.AlertTypeTraces { res[idx].RelatedTracesLink = common.PrepareLinksToTraces(ts, filters) } } diff --git a/pkg/query-service/rules/alerting.go b/pkg/query-service/rules/alerting.go index 36a0ea1b69..7c7fb40ed6 100644 --- a/pkg/query-service/rules/alerting.go +++ b/pkg/query-service/rules/alerting.go @@ -61,6 +61,35 @@ func (s AlertState) String() string { panic(errors.Errorf("unknown alert state: %d", s)) } +func (s AlertState) MarshalJSON() ([]byte, error) { + return json.Marshal(s.String()) +} + +func (s *AlertState) UnmarshalJSON(b []byte) error { + var v interface{} + if err := json.Unmarshal(b, &v); err != nil { + return err + } + switch value := v.(type) { + case string: + switch value { + case "inactive": + *s = StateInactive + case "pending": + *s = StatePending + case "firing": + *s = StateFiring + case "disabled": + *s = StateDisabled + default: + return errors.New("invalid alert state") + } + return nil + default: + return errors.New("invalid alert state") + } +} + type Alert struct { State AlertState diff --git a/pkg/query-service/rules/api_params.go b/pkg/query-service/rules/api_params.go index 74ca041aae..890d464671 100644 --- a/pkg/query-service/rules/api_params.go +++ b/pkg/query-service/rules/api_params.go @@ -16,6 +16,22 @@ import ( yaml "gopkg.in/yaml.v2" ) +type AlertType string + +const ( + AlertTypeMetric AlertType = "METRIC_BASED_ALERT" + AlertTypeTraces AlertType = "TRACES_BASED_ALERT" + AlertTypeLogs AlertType = "LOGS_BASED_ALERT" + AlertTypeExceptions AlertType = "EXCEPTIONS_BASED_ALERT" +) + +type RuleDataKind string + +const ( + RuleDataKindJson RuleDataKind = "json" + RuleDataKindYaml RuleDataKind = "yaml" +) + // this file contains api request and responses to be // served over http @@ -31,12 +47,12 @@ func newApiErrorBadData(err error) *model.ApiError { // PostableRule is used to create alerting rule from HTTP api type PostableRule struct { - AlertName string `yaml:"alert,omitempty" json:"alert,omitempty"` - AlertType string `yaml:"alertType,omitempty" json:"alertType,omitempty"` - Description string `yaml:"description,omitempty" json:"description,omitempty"` - RuleType RuleType `yaml:"ruleType,omitempty" json:"ruleType,omitempty"` - EvalWindow Duration `yaml:"evalWindow,omitempty" json:"evalWindow,omitempty"` - Frequency Duration `yaml:"frequency,omitempty" json:"frequency,omitempty"` + AlertName string `yaml:"alert,omitempty" json:"alert,omitempty"` + AlertType AlertType `yaml:"alertType,omitempty" json:"alertType,omitempty"` + Description string `yaml:"description,omitempty" json:"description,omitempty"` + RuleType RuleType `yaml:"ruleType,omitempty" json:"ruleType,omitempty"` + EvalWindow Duration `yaml:"evalWindow,omitempty" json:"evalWindow,omitempty"` + Frequency Duration `yaml:"frequency,omitempty" json:"frequency,omitempty"` RuleCondition *RuleCondition `yaml:"condition,omitempty" json:"condition,omitempty"` Labels map[string]string `yaml:"labels,omitempty" json:"labels,omitempty"` @@ -234,8 +250,8 @@ type GettableRules struct { // GettableRule has info for an alerting rules. type GettableRule struct { - Id string `json:"id"` - State string `json:"state"` + Id string `json:"id"` + State AlertState `json:"state"` PostableRule CreatedAt *time.Time `json:"createAt"` CreatedBy *string `json:"createBy"` diff --git a/pkg/query-service/rules/db.go b/pkg/query-service/rules/db.go index 37e45f2711..d9a9be195c 100644 --- a/pkg/query-service/rules/db.go +++ b/pkg/query-service/rules/db.go @@ -325,9 +325,9 @@ func (r *ruleDB) GetAlertsInfo(ctx context.Context) (*model.AlertsInfo, error) { continue } alertNames = append(alertNames, rule.AlertName) - if rule.AlertType == "LOGS_BASED_ALERT" { + if rule.AlertType == AlertTypeLogs { alertsInfo.LogsBasedAlerts = alertsInfo.LogsBasedAlerts + 1 - } else if rule.AlertType == "METRIC_BASED_ALERT" { + } else if rule.AlertType == AlertTypeMetric { alertsInfo.MetricBasedAlerts = alertsInfo.MetricBasedAlerts + 1 if rule.RuleCondition != nil && rule.RuleCondition.CompositeQuery != nil { if rule.RuleCondition.CompositeQuery.QueryType == v3.QueryTypeBuilder { @@ -343,7 +343,7 @@ func (r *ruleDB) GetAlertsInfo(ctx context.Context) (*model.AlertsInfo, error) { } } } - } else if rule.AlertType == "TRACES_BASED_ALERT" { + } else if rule.AlertType == AlertTypeTraces { alertsInfo.TracesBasedAlerts = alertsInfo.TracesBasedAlerts + 1 } alertsInfo.TotalAlerts = alertsInfo.TotalAlerts + 1 diff --git a/pkg/query-service/rules/manager.go b/pkg/query-service/rules/manager.go index 95f97753e0..f738fefcc7 100644 --- a/pkg/query-service/rules/manager.go +++ b/pkg/query-service/rules/manager.go @@ -604,10 +604,10 @@ func (m *Manager) ListRuleStates(ctx context.Context) (*GettableRules, error) { // fetch state of rule from memory if rm, ok := m.rules[ruleResponse.Id]; !ok { - ruleResponse.State = StateDisabled.String() + ruleResponse.State = StateDisabled ruleResponse.Disabled = true } else { - ruleResponse.State = rm.State().String() + ruleResponse.State = rm.State() } ruleResponse.CreatedAt = s.CreatedAt ruleResponse.CreatedBy = s.CreatedBy @@ -631,10 +631,10 @@ func (m *Manager) GetRule(ctx context.Context, id string) (*GettableRule, error) r.Id = fmt.Sprintf("%d", s.Id) // fetch state of rule from memory if rm, ok := m.rules[r.Id]; !ok { - r.State = StateDisabled.String() + r.State = StateDisabled r.Disabled = true } else { - r.State = rm.State().String() + r.State = rm.State() } r.CreatedAt = s.CreatedAt r.CreatedBy = s.CreatedBy @@ -740,10 +740,10 @@ func (m *Manager) PatchRule(ctx context.Context, ruleStr string, ruleId string) // fetch state of rule from memory if rm, ok := m.rules[ruleId]; !ok { - response.State = StateDisabled.String() + response.State = StateDisabled response.Disabled = true } else { - response.State = rm.State().String() + response.State = rm.State() } return &response, nil diff --git a/pkg/query-service/rules/threshold_rule.go b/pkg/query-service/rules/threshold_rule.go index 5093e407dc..9bdecbc63d 100644 --- a/pkg/query-service/rules/threshold_rule.go +++ b/pkg/query-service/rules/threshold_rule.go @@ -91,8 +91,7 @@ type ThresholdRule struct { lastTimestampWithDatapoints time.Time // Type of the rule - // One of ["LOGS_BASED_ALERT", "TRACES_BASED_ALERT", "METRIC_BASED_ALERT", "EXCEPTIONS_BASED_ALERT"] - typ string + typ AlertType // querier is used for alerts created before the introduction of new metrics query builder querier interfaces.Querier @@ -975,12 +974,12 @@ func (r *ThresholdRule) Eval(ctx context.Context, ts time.Time, queriers *Querie // Links with timestamps should go in annotations since labels // is used alert grouping, and we want to group alerts with the same // label set, but different timestamps, together. - if r.typ == "TRACES_BASED_ALERT" { + if r.typ == AlertTypeTraces { link := r.prepareLinksToTraces(ts, smpl.MetricOrig) if link != "" && r.hostFromSource() != "" { annotations = append(annotations, labels.Label{Name: "related_traces", Value: fmt.Sprintf("%s/traces-explorer?%s", r.hostFromSource(), link)}) } - } else if r.typ == "LOGS_BASED_ALERT" { + } else if r.typ == AlertTypeLogs { link := r.prepareLinksToLogs(ts, smpl.MetricOrig) if link != "" && r.hostFromSource() != "" { annotations = append(annotations, labels.Label{Name: "related_logs", Value: fmt.Sprintf("%s/logs/logs-explorer?%s", r.hostFromSource(), link)}) diff --git a/pkg/query-service/rules/threshold_rule_test.go b/pkg/query-service/rules/threshold_rule_test.go index 55a831efcd..05bd613900 100644 --- a/pkg/query-service/rules/threshold_rule_test.go +++ b/pkg/query-service/rules/threshold_rule_test.go @@ -674,7 +674,7 @@ func TestNormalizeLabelName(t *testing.T) { func TestPrepareLinksToLogs(t *testing.T) { postableRule := PostableRule{ AlertName: "Tricky Condition Tests", - AlertType: "LOGS_BASED_ALERT", + AlertType: AlertTypeLogs, RuleType: RuleTypeThreshold, EvalWindow: Duration(5 * time.Minute), Frequency: Duration(1 * time.Minute),