[release-12.1.2] Alerting: Add keepFiringFor and missing_series_evals_to_resolve to file provisioning (#109710)
* Alerting: Add keepFiringFor and missing_series_evals_to_resolve to file provisioning (#109699) * Fix MissingSeriesEvalsToResolve type
This commit is contained in:
@@ -273,7 +273,7 @@ type AlertRuleExport struct {
|
|||||||
NoDataState *NoDataState `json:"noDataState,omitempty" yaml:"noDataState,omitempty" hcl:"no_data_state"`
|
NoDataState *NoDataState `json:"noDataState,omitempty" yaml:"noDataState,omitempty" hcl:"no_data_state"`
|
||||||
ExecErrState *ExecutionErrorState `json:"execErrState,omitempty" yaml:"execErrState,omitempty" hcl:"exec_err_state"`
|
ExecErrState *ExecutionErrorState `json:"execErrState,omitempty" yaml:"execErrState,omitempty" hcl:"exec_err_state"`
|
||||||
For model.Duration `json:"for,omitempty" yaml:"for,omitempty"`
|
For model.Duration `json:"for,omitempty" yaml:"for,omitempty"`
|
||||||
KeepFiringFor model.Duration `json:"keepFiringFor,omitempty" yaml:"keepFiringFor,omitempty" hcl:"keep_firing_for"`
|
KeepFiringFor model.Duration `json:"keepFiringFor,omitempty" yaml:"keepFiringFor,omitempty"`
|
||||||
// ForString and KeepFiringForString are used to:
|
// ForString and KeepFiringForString are used to:
|
||||||
// - Only export the for field for HCL if it is non-zero.
|
// - Only export the for field for HCL if it is non-zero.
|
||||||
// - Format the Prometheus model.Duration type properly for HCL.
|
// - Format the Prometheus model.Duration type properly for HCL.
|
||||||
|
|||||||
@@ -62,21 +62,23 @@ func (ruleGroupV1 *AlertRuleGroupV1) MapToModel() (models.AlertRuleGroupWithFold
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AlertRuleV1 struct {
|
type AlertRuleV1 struct {
|
||||||
UID values.StringValue `json:"uid" yaml:"uid"`
|
UID values.StringValue `json:"uid" yaml:"uid"`
|
||||||
Title values.StringValue `json:"title" yaml:"title"`
|
Title values.StringValue `json:"title" yaml:"title"`
|
||||||
Condition values.StringValue `json:"condition" yaml:"condition"`
|
Condition values.StringValue `json:"condition" yaml:"condition"`
|
||||||
Data []QueryV1 `json:"data" yaml:"data"`
|
Data []QueryV1 `json:"data" yaml:"data"`
|
||||||
DasboardUID values.StringValue `json:"dasboardUid" yaml:"dasboardUid"` // TODO: Grandfathered typo support. TODO: This should be removed in V2.
|
DasboardUID values.StringValue `json:"dasboardUid" yaml:"dasboardUid"` // TODO: Grandfathered typo support. TODO: This should be removed in V2.
|
||||||
DashboardUID values.StringValue `json:"dashboardUid" yaml:"dashboardUid"`
|
DashboardUID values.StringValue `json:"dashboardUid" yaml:"dashboardUid"`
|
||||||
PanelID values.Int64Value `json:"panelId" yaml:"panelId"`
|
PanelID values.Int64Value `json:"panelId" yaml:"panelId"`
|
||||||
NoDataState values.StringValue `json:"noDataState" yaml:"noDataState"`
|
NoDataState values.StringValue `json:"noDataState" yaml:"noDataState"`
|
||||||
ExecErrState values.StringValue `json:"execErrState" yaml:"execErrState"`
|
ExecErrState values.StringValue `json:"execErrState" yaml:"execErrState"`
|
||||||
For values.StringValue `json:"for" yaml:"for"`
|
For values.StringValue `json:"for" yaml:"for"`
|
||||||
Annotations values.StringMapValue `json:"annotations" yaml:"annotations"`
|
KeepFiringFor values.StringValue `json:"keepFiringFor" yaml:"keepFiringFor"`
|
||||||
Labels values.StringMapValue `json:"labels" yaml:"labels"`
|
MissingSeriesEvalsToResolve values.IntValue `json:"missing_series_evals_to_resolve" yaml:"missing_series_evals_to_resolve"`
|
||||||
IsPaused values.BoolValue `json:"isPaused" yaml:"isPaused"`
|
Annotations values.StringMapValue `json:"annotations" yaml:"annotations"`
|
||||||
NotificationSettings *NotificationSettingsV1 `json:"notification_settings" yaml:"notification_settings"`
|
Labels values.StringMapValue `json:"labels" yaml:"labels"`
|
||||||
Record *RecordV1 `json:"record" yaml:"record"`
|
IsPaused values.BoolValue `json:"isPaused" yaml:"isPaused"`
|
||||||
|
NotificationSettings *NotificationSettingsV1 `json:"notification_settings" yaml:"notification_settings"`
|
||||||
|
Record *RecordV1 `json:"record" yaml:"record"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func withFallback(value, fallback string) *string {
|
func withFallback(value, fallback string) *string {
|
||||||
@@ -108,6 +110,24 @@ func (rule *AlertRuleV1) mapToModel(orgID int64) (models.AlertRule, error) {
|
|||||||
}
|
}
|
||||||
alertRule.For = time.Duration(duration)
|
alertRule.For = time.Duration(duration)
|
||||||
|
|
||||||
|
keepFiringForDuration := model.Duration(0)
|
||||||
|
if rule.KeepFiringFor.Value() != "" {
|
||||||
|
var err error
|
||||||
|
keepFiringForDuration, err = model.ParseDuration(rule.KeepFiringFor.Value())
|
||||||
|
if err != nil {
|
||||||
|
return models.AlertRule{}, fmt.Errorf("rule '%s' failed to parse 'keepFiringFor' field: %w", alertRule.Title, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
alertRule.KeepFiringFor = time.Duration(keepFiringForDuration)
|
||||||
|
|
||||||
|
if rule.MissingSeriesEvalsToResolve.Raw != "" {
|
||||||
|
missingSeriesEvalsToResolve := rule.MissingSeriesEvalsToResolve.Value()
|
||||||
|
if missingSeriesEvalsToResolve < 0 {
|
||||||
|
return models.AlertRule{}, fmt.Errorf("rule '%s' failed to parse 'missing_series_evals_to_resolve' field: cannot be negative", alertRule.Title)
|
||||||
|
}
|
||||||
|
alertRule.MissingSeriesEvalsToResolve = &missingSeriesEvalsToResolve
|
||||||
|
}
|
||||||
|
|
||||||
dasboardUID := rule.DasboardUID.Value()
|
dasboardUID := rule.DasboardUID.Value()
|
||||||
dashboardUID := rule.DashboardUID.Value()
|
dashboardUID := rule.DashboardUID.Value()
|
||||||
alertRule.DashboardUID = withFallback(dashboardUID, dasboardUID) // Use correct spelling over supported typo.
|
alertRule.DashboardUID = withFallback(dashboardUID, dasboardUID) // Use correct spelling over supported typo.
|
||||||
|
|||||||
@@ -1019,6 +1019,15 @@ func TestIntegrationExportFileProvision(t *testing.T) {
|
|||||||
require.Equal(t, http.StatusOK, status)
|
require.Equal(t, http.StatusOK, status)
|
||||||
require.Greater(t, len(data), 0)
|
require.Greater(t, len(data), 0)
|
||||||
|
|
||||||
|
t.Run("provisioned alert rules should have proper data", func(t *testing.T) {
|
||||||
|
provisionedRule, status, _ := apiClient.GetProvisioningAlertRule(t, "my_id_1")
|
||||||
|
require.Equal(t, http.StatusOK, status)
|
||||||
|
|
||||||
|
require.Equal(t, model.Duration(time.Second*120), provisionedRule.KeepFiringFor)
|
||||||
|
require.NotNil(t, provisionedRule.MissingSeriesEvalsToResolve)
|
||||||
|
require.Equal(t, 3, *provisionedRule.MissingSeriesEvalsToResolve)
|
||||||
|
})
|
||||||
|
|
||||||
t.Run("exported alert rules should escape $ characters", func(t *testing.T) {
|
t.Run("exported alert rules should escape $ characters", func(t *testing.T) {
|
||||||
// call export endpoint
|
// call export endpoint
|
||||||
status, exportRaw := apiClient.ExportRulesWithStatus(t, &definitions.AlertRulesExportParameters{
|
status, exportRaw := apiClient.ExportRulesWithStatus(t, &definitions.AlertRulesExportParameters{
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ groups:
|
|||||||
execErrState: Alerting
|
execErrState: Alerting
|
||||||
# <duration, required> for how long should the alert fire before alerting
|
# <duration, required> for how long should the alert fire before alerting
|
||||||
for: 60s
|
for: 60s
|
||||||
|
# <duration> for how long the alert should keep firing after condition stops being true
|
||||||
|
keepFiringFor: 120s
|
||||||
|
# <int> number of evaluation intervals required to resolve alert when data is missing
|
||||||
|
missing_series_evals_to_resolve: 3
|
||||||
# <map<string, string>> a map of strings to pass around any data
|
# <map<string, string>> a map of strings to pass around any data
|
||||||
annotations:
|
annotations:
|
||||||
some_key: some_value
|
some_key: some_value
|
||||||
|
|||||||
@@ -1398,6 +1398,45 @@ func (a apiClient) UpdateNamespaceRules(t *testing.T, folder string, body *apimo
|
|||||||
return m, resp.StatusCode, string(b)
|
return m, resp.StatusCode, string(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a apiClient) GetProvisioningAlertRule(t *testing.T, ruleUID string) (apimodels.ProvisionedAlertRule, int, string) {
|
||||||
|
t.Helper()
|
||||||
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/provisioning/alert-rules/%s", a.url, ruleUID), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return sendRequestJSON[apimodels.ProvisionedAlertRule](t, req, http.StatusOK)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a apiClient) GetProvisioningAlertRuleExport(t *testing.T, ruleUID string, params *apimodels.ExportQueryParams) (int, string) {
|
||||||
|
t.Helper()
|
||||||
|
u, err := url.Parse(fmt.Sprintf("%s/api/v1/provisioning/alert-rules/%s/export", a.url, ruleUID))
|
||||||
|
require.NoError(t, err)
|
||||||
|
if params != nil {
|
||||||
|
q := url.Values{}
|
||||||
|
if params.Format != "" {
|
||||||
|
q.Set("format", params.Format)
|
||||||
|
}
|
||||||
|
if params.Download {
|
||||||
|
q.Set("download", "true")
|
||||||
|
}
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
}
|
||||||
|
|
||||||
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer func() {
|
||||||
|
_ = resp.Body.Close()
|
||||||
|
}()
|
||||||
|
b, err := io.ReadAll(resp.Body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
return resp.StatusCode, string(b)
|
||||||
|
}
|
||||||
|
|
||||||
func sendRequestRaw(t *testing.T, req *http.Request) ([]byte, int, error) {
|
func sendRequestRaw(t *testing.T, req *http.Request) ([]byte, int, error) {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
client := &http.Client{}
|
client := &http.Client{}
|
||||||
|
|||||||
Reference in New Issue
Block a user