From 16f8359d35b0884c7b796e1b8e2d024e6718cf16 Mon Sep 17 00:00:00 2001 From: Moustafa Baiou Date: Wed, 6 Aug 2025 21:45:48 -0400 Subject: [PATCH] Alerting: Update Alert Rule to use int64 for MissingSeriesEvalsToResolve (#109306) --- .../ngalert/api/api_provisioning_test.go | 10 +++---- .../api/tooling/definitions/cortex-ruler.go | 4 +-- .../definitions/provisioning_alert_rules.go | 4 +-- .../api/validation/api_ruler_validation.go | 4 +-- pkg/services/ngalert/models/alert_rule.go | 4 +-- .../ngalert/models/alert_rule_test.go | 16 +++++----- pkg/services/ngalert/models/testing.go | 6 ++-- pkg/services/ngalert/prom/convert.go | 2 +- pkg/services/ngalert/prom/convert_test.go | 2 +- .../ngalert/schedule/registry_test.go | 4 +-- pkg/services/ngalert/state/manager.go | 4 +-- .../ngalert/state/manager_private_test.go | 2 +- pkg/services/ngalert/store/models.go | 4 +-- .../api/alerting/api_provisioning_test.go | 4 +-- pkg/tests/api/alerting/api_ruler_test.go | 30 +++++++++---------- 15 files changed, 50 insertions(+), 50 deletions(-) diff --git a/pkg/services/ngalert/api/api_provisioning_test.go b/pkg/services/ngalert/api/api_provisioning_test.go index e21f949e4f7..926f3ebcc5b 100644 --- a/pkg/services/ngalert/api/api_provisioning_test.go +++ b/pkg/services/ngalert/api/api_provisioning_test.go @@ -403,7 +403,7 @@ func TestIntegrationProvisioningApi(t *testing.T) { }) t.Run("PUT without MissingSeriesEvalsToResolve clears the field", func(t *testing.T) { - oldValue := util.Pointer(5) + oldValue := util.Pointer[int64](5) sut := createProvisioningSrvSut(t) rc := createTestRequestCtx() rule := createTestAlertRule("rule", 1) @@ -420,8 +420,8 @@ func TestIntegrationProvisioningApi(t *testing.T) { }) t.Run("PUT with MissingSeriesEvalsToResolve updates the value", func(t *testing.T) { - oldValue := util.Pointer(5) - newValue := util.Pointer(10) + oldValue := util.Pointer[int64](5) + newValue := util.Pointer[int64](10) sut := createProvisioningSrvSut(t) rc := createTestRequestCtx() rule := createTestAlertRule("rule", 1) @@ -657,12 +657,12 @@ func TestIntegrationProvisioningApi(t *testing.T) { require.Nil(t, updated.Rules[0].MissingSeriesEvalsToResolve) // Put the same group with a new value - group.Rules[0].MissingSeriesEvalsToResolve = util.Pointer(5) + group.Rules[0].MissingSeriesEvalsToResolve = util.Pointer[int64](5) response = sut.RoutePutAlertRuleGroup(&rc, group, "folder-uid", group.Title) require.Equal(t, 200, response.Status()) updated = deserializeRuleGroup(t, response.Body()) require.NotNil(t, updated.Rules[0].MissingSeriesEvalsToResolve) - require.Equal(t, 5, *updated.Rules[0].MissingSeriesEvalsToResolve) + require.Equal(t, int64(5), *updated.Rules[0].MissingSeriesEvalsToResolve) // Reset the value again group.Rules[0].MissingSeriesEvalsToResolve = nil diff --git a/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go b/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go index 0bb57a9de82..21e6b438ac2 100644 --- a/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go +++ b/pkg/services/ngalert/api/tooling/definitions/cortex-ruler.go @@ -593,7 +593,7 @@ type PostableGrafanaRule struct { // If set to 0, the value is reset to the default. // required: false // example: 3 - MissingSeriesEvalsToResolve *int `json:"missing_series_evals_to_resolve,omitempty" yaml:"missing_series_evals_to_resolve,omitempty"` + MissingSeriesEvalsToResolve *int64 `json:"missing_series_evals_to_resolve,omitempty" yaml:"missing_series_evals_to_resolve,omitempty"` } // swagger:model @@ -616,7 +616,7 @@ type GettableGrafanaRule struct { Record *Record `json:"record,omitempty" yaml:"record,omitempty"` Metadata *AlertRuleMetadata `json:"metadata,omitempty" yaml:"metadata,omitempty"` GUID string `json:"guid" yaml:"guid"` - MissingSeriesEvalsToResolve *int `json:"missing_series_evals_to_resolve,omitempty" yaml:"missing_series_evals_to_resolve,omitempty"` + MissingSeriesEvalsToResolve *int64 `json:"missing_series_evals_to_resolve,omitempty" yaml:"missing_series_evals_to_resolve,omitempty"` } // UserInfo represents user-related information, including a unique identifier and a name. diff --git a/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go b/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go index 5aeb919b4ad..23e036b0b9a 100644 --- a/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go +++ b/pkg/services/ngalert/api/tooling/definitions/provisioning_alert_rules.go @@ -174,7 +174,7 @@ type ProvisionedAlertRule struct { // example: {"metric":"grafana_alerts_ratio", "from":"A"} Record *Record `json:"record"` // example: 2 - MissingSeriesEvalsToResolve *int `json:"missingSeriesEvalsToResolve,omitempty"` + MissingSeriesEvalsToResolve *int64 `json:"missingSeriesEvalsToResolve,omitempty"` } // swagger:route GET /v1/provisioning/folder/{FolderUID}/rule-groups/{Group} provisioning stable RouteGetAlertRuleGroup @@ -284,7 +284,7 @@ type AlertRuleExport struct { IsPaused bool `json:"isPaused" yaml:"isPaused" hcl:"is_paused"` NotificationSettings *AlertRuleNotificationSettingsExport `json:"notification_settings,omitempty" yaml:"notification_settings,omitempty" hcl:"notification_settings,block"` Record *AlertRuleRecordExport `json:"record,omitempty" yaml:"record,omitempty" hcl:"record,block"` - MissingSeriesEvalsToResolve *int `json:"missing_series_evals_to_resolve,omitempty" yaml:"missing_series_evals_to_resolve,omitempty" hcl:"missing_series_evals_to_resolve"` + MissingSeriesEvalsToResolve *int64 `json:"missing_series_evals_to_resolve,omitempty" yaml:"missing_series_evals_to_resolve,omitempty" hcl:"missing_series_evals_to_resolve"` } // AlertQueryExport is the provisioned export of models.AlertQuery. diff --git a/pkg/services/ngalert/api/validation/api_ruler_validation.go b/pkg/services/ngalert/api/validation/api_ruler_validation.go index 01d0b430cef..1dffe249bdd 100644 --- a/pkg/services/ngalert/api/validation/api_ruler_validation.go +++ b/pkg/services/ngalert/api/validation/api_ruler_validation.go @@ -299,10 +299,10 @@ func validateKeepFiringForInterval(ruleNode *apimodels.PostableExtendedRuleNode) // - == 0, returns nil (reset to default) // - == nil && UID == "", returns nil (new rule) // - == nil && UID != "", returns -1 (existing rule) -func validateMissingSeriesEvalsToResolve(ruleNode *apimodels.PostableExtendedRuleNode) (*int, error) { +func validateMissingSeriesEvalsToResolve(ruleNode *apimodels.PostableExtendedRuleNode) (*int64, error) { if ruleNode.GrafanaManagedAlert.MissingSeriesEvalsToResolve == nil { if ruleNode.GrafanaManagedAlert.UID != "" { - return util.Pointer(-1), nil // will be patched later with the real value of the current version of the rule + return util.Pointer[int64](-1), nil // will be patched later with the real value of the current version of the rule } return nil, nil // if it's a new rule, use nil as the default } diff --git a/pkg/services/ngalert/models/alert_rule.go b/pkg/services/ngalert/models/alert_rule.go index b1f415f929d..803bc65ba4f 100644 --- a/pkg/services/ngalert/models/alert_rule.go +++ b/pkg/services/ngalert/models/alert_rule.go @@ -304,7 +304,7 @@ type AlertRule struct { // required before resolving an alert state (a dimension) when data is missing. // If nil, alerts resolve after 2 missing evaluation intervals // (i.e., resolution occurs during the second evaluation where data is absent). - MissingSeriesEvalsToResolve *int + MissingSeriesEvalsToResolve *int64 } type AlertRuleMetadata struct { @@ -598,7 +598,7 @@ func (alertRule *AlertRule) GetGroupKey() AlertRuleGroupKey { // to wait before resolving an alert rule instance when its data is missing. // If not configured, it returns the default value (2), which means the alert // resolves after missing for two evaluation intervals. -func (alertRule *AlertRule) GetMissingSeriesEvalsToResolve() int { +func (alertRule *AlertRule) GetMissingSeriesEvalsToResolve() int64 { if alertRule.MissingSeriesEvalsToResolve == nil { return 2 // default value } diff --git a/pkg/services/ngalert/models/alert_rule_test.go b/pkg/services/ngalert/models/alert_rule_test.go index 329e979ea03..326bd40cb4a 100644 --- a/pkg/services/ngalert/models/alert_rule_test.go +++ b/pkg/services/ngalert/models/alert_rule_test.go @@ -554,8 +554,8 @@ func TestDiff(t *testing.T) { if rule1.MissingSeriesEvalsToResolve != rule2.MissingSeriesEvalsToResolve { diff := diffs.GetDiffsForField("MissingSeriesEvalsToResolve") assert.Len(t, diff, 1) - assert.Equal(t, *rule1.MissingSeriesEvalsToResolve, int(diff[0].Left.Int())) - assert.Equal(t, *rule2.MissingSeriesEvalsToResolve, int(diff[0].Right.Int())) + assert.Equal(t, *rule1.MissingSeriesEvalsToResolve, diff[0].Left.Int()) + assert.Equal(t, *rule2.MissingSeriesEvalsToResolve, diff[0].Right.Int()) difCnt++ } @@ -1001,14 +1001,14 @@ func TestAlertRuleGetMissingSeriesEvalsToResolve(t *testing.T) { t.Run("should return the default 2 if MissingSeriesEvalsToResolve is nil", func(t *testing.T) { rule := RuleGen.GenerateRef() rule.MissingSeriesEvalsToResolve = nil - require.Equal(t, 2, rule.GetMissingSeriesEvalsToResolve()) + require.Equal(t, int64(2), rule.GetMissingSeriesEvalsToResolve()) }) t.Run("should return the correct value", func(t *testing.T) { rule := RuleGen.With( RuleMuts.WithMissingSeriesEvalsToResolve(3), ).GenerateRef() - require.Equal(t, 3, rule.GetMissingSeriesEvalsToResolve()) + require.Equal(t, int64(3), rule.GetMissingSeriesEvalsToResolve()) }) } @@ -1113,7 +1113,7 @@ func TestValidateAlertRule(t *testing.T) { t.Run("missingSeriesEvalsToResolve", func(t *testing.T) { testCases := []struct { name string - missingSeriesEvalsToResolve *int + missingSeriesEvalsToResolve *int64 expectedErrorContains string }{ { @@ -1122,17 +1122,17 @@ func TestValidateAlertRule(t *testing.T) { }, { name: "should reject negative value", - missingSeriesEvalsToResolve: util.Pointer(-1), + missingSeriesEvalsToResolve: util.Pointer[int64](-1), expectedErrorContains: "field `missing_series_evals_to_resolve` must be greater than 0", }, { name: "should reject 0", - missingSeriesEvalsToResolve: util.Pointer(0), + missingSeriesEvalsToResolve: util.Pointer[int64](0), expectedErrorContains: "field `missing_series_evals_to_resolve` must be greater than 0", }, { name: "should accept positive value", - missingSeriesEvalsToResolve: util.Pointer(2), + missingSeriesEvalsToResolve: util.Pointer[int64](2), }, } diff --git a/pkg/services/ngalert/models/testing.go b/pkg/services/ngalert/models/testing.go index aa8e7c8f32d..c9ba4b91348 100644 --- a/pkg/services/ngalert/models/testing.go +++ b/pkg/services/ngalert/models/testing.go @@ -128,7 +128,7 @@ func (g *AlertRuleGenerator) Generate() AlertRule { Labels: labels, NotificationSettings: ns, Metadata: GenerateMetadata(), - MissingSeriesEvalsToResolve: util.Pointer(2), + MissingSeriesEvalsToResolve: util.Pointer[int64](2), } for _, mutator := range g.mutators { @@ -514,12 +514,12 @@ func (a *AlertRuleMutators) WithSameGroup() AlertRuleMutator { } } -func (a *AlertRuleMutators) WithMissingSeriesEvalsToResolve(timesOfInterval int) AlertRuleMutator { +func (a *AlertRuleMutators) WithMissingSeriesEvalsToResolve(timesOfInterval int64) AlertRuleMutator { return func(rule *AlertRule) { if timesOfInterval <= 0 { panic("timesOfInterval must be greater than 0") } - rule.MissingSeriesEvalsToResolve = util.Pointer(timesOfInterval) + rule.MissingSeriesEvalsToResolve = util.Pointer[int64](timesOfInterval) } } diff --git a/pkg/services/ngalert/prom/convert.go b/pkg/services/ngalert/prom/convert.go index 4b8137f347b..485b62dc857 100644 --- a/pkg/services/ngalert/prom/convert.go +++ b/pkg/services/ngalert/prom/convert.go @@ -275,7 +275,7 @@ func (p *Converter) convertRule(orgID int64, namespaceUID string, promGroup Prom // Prometheus resolves alerts as soon as the series disappears. // By setting this value to 1 we ensure that the alert is resolved on the first evaluation // that doesn't have the series. - MissingSeriesEvalsToResolve: util.Pointer(1), + MissingSeriesEvalsToResolve: util.Pointer[int64](1), } if !isRecordingRule { diff --git a/pkg/services/ngalert/prom/convert_test.go b/pkg/services/ngalert/prom/convert_test.go index 5d7b9470d00..4a5d4346b14 100644 --- a/pkg/services/ngalert/prom/convert_test.go +++ b/pkg/services/ngalert/prom/convert_test.go @@ -358,7 +358,7 @@ func TestPrometheusRulesToGrafana(t *testing.T) { require.Equal(t, models.Duration(evalOffset), grafanaRule.Data[0].RelativeTimeRange.To) require.Equal(t, models.Duration(10*time.Minute+evalOffset), grafanaRule.Data[0].RelativeTimeRange.From) - require.Equal(t, util.Pointer(1), grafanaRule.MissingSeriesEvalsToResolve) + require.Equal(t, util.Pointer(int64(1)), grafanaRule.MissingSeriesEvalsToResolve) require.Equal(t, models.OkErrState, grafanaRule.ExecErrState) require.Equal(t, models.OK, grafanaRule.NoDataState) diff --git a/pkg/services/ngalert/schedule/registry_test.go b/pkg/services/ngalert/schedule/registry_test.go index 9d838556ed7..b747830a18c 100644 --- a/pkg/services/ngalert/schedule/registry_test.go +++ b/pkg/services/ngalert/schedule/registry_test.go @@ -214,7 +214,7 @@ func TestRuleWithFolderFingerprint(t *testing.T) { SimplifiedNotificationsSection: false, }, }, - MissingSeriesEvalsToResolve: util.Pointer(2), + MissingSeriesEvalsToResolve: util.Pointer[int64](2), } r2 := &models.AlertRule{ ID: 2, @@ -260,7 +260,7 @@ func TestRuleWithFolderFingerprint(t *testing.T) { SimplifiedQueryAndExpressionsSection: true, }, }, - MissingSeriesEvalsToResolve: util.Pointer(1), + MissingSeriesEvalsToResolve: util.Pointer[int64](1), } excludedFields := map[string]struct{}{ diff --git a/pkg/services/ngalert/state/manager.go b/pkg/services/ngalert/state/manager.go index a228f2bc151..571a5cd1aa7 100644 --- a/pkg/services/ngalert/state/manager.go +++ b/pkg/services/ngalert/state/manager.go @@ -587,13 +587,13 @@ func (st *Manager) processMissingSeriesStates(logger log.Logger, evaluatedAt tim // stateIsStale determines whether the evaluation state is considered stale. // A state is considered stale if the data has been missing for at least missingSeriesEvalsToResolve evaluation intervals. -func stateIsStale(evaluatedAt time.Time, lastEval time.Time, intervalSeconds int64, missingSeriesEvalsToResolve int) bool { +func stateIsStale(evaluatedAt time.Time, lastEval time.Time, intervalSeconds int64, missingSeriesEvalsToResolve int64) bool { // If the last evaluation time equals the current evaluation time, the state is not stale. if evaluatedAt.Equal(lastEval) { return false } - resolveIfMissingDuration := time.Duration(int64(missingSeriesEvalsToResolve)*intervalSeconds) * time.Second + resolveIfMissingDuration := time.Duration(missingSeriesEvalsToResolve*intervalSeconds) * time.Second // timeSinceLastEval >= resolveIfMissingDuration return evaluatedAt.Sub(lastEval) >= resolveIfMissingDuration diff --git a/pkg/services/ngalert/state/manager_private_test.go b/pkg/services/ngalert/state/manager_private_test.go index 471e747d88e..081f4843538 100644 --- a/pkg/services/ngalert/state/manager_private_test.go +++ b/pkg/services/ngalert/state/manager_private_test.go @@ -39,7 +39,7 @@ func TestStateIsStale(t *testing.T) { name string lastEvaluation time.Time expectedResult bool - missingSeriesEvalsToResolve int + missingSeriesEvalsToResolve int64 }{ { name: "false if last evaluation is now", diff --git a/pkg/services/ngalert/store/models.go b/pkg/services/ngalert/store/models.go index 7cbea0c0f86..7f40ccd30ac 100644 --- a/pkg/services/ngalert/store/models.go +++ b/pkg/services/ngalert/store/models.go @@ -30,7 +30,7 @@ type alertRule struct { IsPaused bool NotificationSettings string `xorm:"notification_settings"` Metadata string `xorm:"metadata"` - MissingSeriesEvalsToResolve *int `xorm:"missing_series_evals_to_resolve"` + MissingSeriesEvalsToResolve *int64 `xorm:"missing_series_evals_to_resolve"` } func (a alertRule) TableName() string { @@ -68,7 +68,7 @@ type alertRuleVersion struct { IsPaused bool NotificationSettings string `xorm:"notification_settings"` Metadata string `xorm:"metadata"` - MissingSeriesEvalsToResolve *int `xorm:"missing_series_evals_to_resolve"` + MissingSeriesEvalsToResolve *int64 `xorm:"missing_series_evals_to_resolve"` } // EqualSpec compares two alertRuleVersion objects for equality based on their specifications and returns true if they match. diff --git a/pkg/tests/api/alerting/api_provisioning_test.go b/pkg/tests/api/alerting/api_provisioning_test.go index 54b3e71b61e..15ec7028e04 100644 --- a/pkg/tests/api/alerting/api_provisioning_test.go +++ b/pkg/tests/api/alerting/api_provisioning_test.go @@ -661,7 +661,7 @@ func TestIntegrationProvisioningRules(t *testing.T) { Model: json.RawMessage([]byte(`{"type":"math","expression":"2 + 3 \u003e 1"}`)), }, }, - MissingSeriesEvalsToResolve: util.Pointer(3), + MissingSeriesEvalsToResolve: util.Pointer[int64](3), }, }, } @@ -676,7 +676,7 @@ func TestIntegrationProvisioningRules(t *testing.T) { for _, rule := range result.Rules { require.NotEmpty(t, rule.UID) if rule.UID == "rule3" { - require.Equal(t, 3, *rule.MissingSeriesEvalsToResolve) + require.Equal(t, int64(3), *rule.MissingSeriesEvalsToResolve) } } }) diff --git a/pkg/tests/api/alerting/api_ruler_test.go b/pkg/tests/api/alerting/api_ruler_test.go index da9f0c3441b..e6d022edf6f 100644 --- a/pkg/tests/api/alerting/api_ruler_test.go +++ b/pkg/tests/api/alerting/api_ruler_test.go @@ -1761,42 +1761,42 @@ func TestIntegrationRuleUpdate(t *testing.T) { t.Run("missing_series_evals_to_resolve", func(t *testing.T) { testCases := []struct { name string - initialValue *int - updatedValue *int - expectedValue *int + initialValue *int64 + updatedValue *int64 + expectedValue *int64 expectedStatus int }{ { name: "should be able to set missing_series_evals_to_resolve to 5", initialValue: nil, - updatedValue: util.Pointer(5), - expectedValue: util.Pointer(5), + updatedValue: util.Pointer[int64](5), + expectedValue: util.Pointer[int64](5), expectedStatus: http.StatusAccepted, }, { name: "should be able to update missing_series_evals_to_resolve", - initialValue: util.Pointer(1), - updatedValue: util.Pointer(2), - expectedValue: util.Pointer(2), + initialValue: util.Pointer[int64](1), + updatedValue: util.Pointer[int64](2), + expectedValue: util.Pointer[int64](2), expectedStatus: http.StatusAccepted, }, { name: "should preserve missing_series_evals_to_resolve when it's set nil", - initialValue: util.Pointer(5), + initialValue: util.Pointer[int64](5), updatedValue: nil, - expectedValue: util.Pointer(5), + expectedValue: util.Pointer[int64](5), expectedStatus: http.StatusAccepted, }, { name: "should reject missing_series_evals_to_resolve < 0", - initialValue: util.Pointer(1), - updatedValue: util.Pointer(-1), + initialValue: util.Pointer[int64](1), + updatedValue: util.Pointer[int64](-1), expectedStatus: http.StatusBadRequest, }, { name: "should be able to reset missing_series_evals_to_resolve by setting it to 0", - initialValue: util.Pointer(1), - updatedValue: util.Pointer(0), + initialValue: util.Pointer[int64](1), + updatedValue: util.Pointer[int64](0), expectedValue: nil, expectedStatus: http.StatusAccepted, }, @@ -3474,7 +3474,7 @@ func TestIntegrationAlertRuleCRUD(t *testing.T) { }, NoDataState: apimodels.NoDataState(ngmodels.Alerting), ExecErrState: apimodels.ExecutionErrorState(ngmodels.AlertingErrState), - MissingSeriesEvalsToResolve: util.Pointer(2), // If UID is specified, this field is required + MissingSeriesEvalsToResolve: util.Pointer[int64](2), // If UID is specified, this field is required }, }, },