Alerting: Time interval Delete API to check for usages in alert rules (#90500)

* Check if a time interval is used in alert rules before deleting it
* Add time interval to parameters of ListAlertRulesQuery and ListNotificationSettings of DbStore

== Refacorings == 
* refactor isMuteTimeInUse to accept a single route
* update getMuteTiming to not return err
* update delete to get the mute timing from config first
This commit is contained in:
Yuri Tseretyan
2024-07-17 10:53:54 -04:00
committed by GitHub
parent 79df1b1e10
commit 970cafa20f
15 changed files with 492 additions and 130 deletions
+11
View File
@@ -240,6 +240,17 @@ type apiClient struct {
url string
}
type LegacyApiClient struct {
apiClient
}
func NewAlertingLegacyAPIClient(host, user, pass string) LegacyApiClient {
cli := newAlertingApiClient(host, user, pass)
return LegacyApiClient{
apiClient: cli,
}
}
func newAlertingApiClient(host, user, pass string) apiClient {
if len(user) == 0 && len(pass) == 0 {
return apiClient{url: fmt.Sprintf("http://%s", host)}
@@ -0,0 +1,47 @@
{
"template_files": {},
"template_file_provenances": {},
"alertmanager_config": {
"route": {
"receiver": "grafana-default-email",
"routes": [
{
"object_matchers": [
[
"test",
"=",
"test"
]
],
"mute_time_intervals": [
"test-interval"
]
}
]
},
"mute_time_intervals": [
{
"name": "test-interval",
"time_intervals": [
{
"start_time": "06:00",
"end_time": "23:59"
}
]
}
],
"receivers": [
{
"name": "grafana-default-email",
"grafana_managed_receiver_configs": [
{
"type": "email",
"settings": {
"addresses": "<example@email.com>"
}
}
]
}
]
}
}
@@ -0,0 +1,37 @@
{
"name": "Group1",
"interval": "1m",
"rules": [
{
"for": "0",
"labels": {
"label1": "test-label"
},
"annotations": {
"annotation": "test-annotation"
},
"grafana_alert": {
"title": "Rule1",
"condition": "A",
"data": [
{
"refId": "A",
"datasourceUid": "__expr__",
"model": {
"expression": "0 > 0",
"type": "math"
}
}
],
"no_data_state": "NoData",
"exec_err_state": "Alerting",
"notification_settings": {
"receiver": "grafana-default-email",
"mute_time_intervals": [
"test-interval"
]
}
}
}
]
}
@@ -2,8 +2,11 @@ package timeinterval
import (
"context"
"embed"
"encoding/json"
"fmt"
"net/http"
"path"
"testing"
"github.com/prometheus/alertmanager/config"
@@ -24,12 +27,16 @@ import (
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
"github.com/grafana/grafana/pkg/services/ngalert/store"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/tests/api/alerting"
"github.com/grafana/grafana/pkg/tests/apis"
"github.com/grafana/grafana/pkg/tests/testinfra"
"github.com/grafana/grafana/pkg/tests/testsuite"
"github.com/grafana/grafana/pkg/util"
)
//go:embed test-data/*.*
var testData embed.FS
func TestMain(m *testing.M) {
testsuite.Run(m)
}
@@ -626,3 +633,58 @@ func TestIntegrationTimeIntervalListSelector(t *testing.T) {
require.Empty(t, list.Items)
})
}
func TestIntegrationTimeIntervalReferentialIntegrity(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
ctx := context.Background()
helper := getTestHelper(t)
cliCfg := helper.Org1.Admin.NewRestConfig()
legacyCli := alerting.NewAlertingLegacyAPIClient(helper.GetEnv().Server.HTTPServer.Listener.Addr().String(), cliCfg.Username, cliCfg.Password)
// Prepare environment and create notification policy and rule that use time interval
alertmanagerRaw, err := testData.ReadFile(path.Join("test-data", "notification-settings.json"))
require.NoError(t, err)
var amConfig definitions.PostableUserConfig
require.NoError(t, json.Unmarshal(alertmanagerRaw, &amConfig))
success, err := legacyCli.PostConfiguration(t, amConfig)
require.Truef(t, success, "Failed to post Alertmanager configuration: %s", err)
postGroupRaw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1.json"))
require.NoError(t, err)
var ruleGroup definitions.PostableRuleGroupConfig
require.NoError(t, json.Unmarshal(postGroupRaw, &ruleGroup))
folderUID := "test-folder"
legacyCli.CreateFolder(t, folderUID, "TEST")
_, status, data := legacyCli.PostRulesGroupWithStatus(t, folderUID, &ruleGroup)
require.Equalf(t, http.StatusAccepted, status, "Failed to post Rule: %s", data)
adminK8sClient, err := versioned.NewForConfig(cliCfg)
require.NoError(t, err)
adminClient := adminK8sClient.NotificationsV0alpha1().TimeIntervals("default")
intervals, err := adminClient.List(ctx, v1.ListOptions{})
require.NoError(t, err)
require.Len(t, intervals.Items, 1)
intervalToDelete := intervals.Items[0]
t.Run("should fail to delete if time interval is used in rule and routes", func(t *testing.T) {
err := adminClient.Delete(ctx, intervalToDelete.Name, v1.DeleteOptions{})
require.Truef(t, errors.IsConflict(err), "Expected Conflict, got: %s", err)
})
t.Run("should fail to delete if time interval is used in only rule", func(t *testing.T) {
amConfig.AlertmanagerConfig.Route.Routes[0].MuteTimeIntervals = nil
success, err := legacyCli.PostConfiguration(t, amConfig)
require.Truef(t, success, "Failed to post Alertmanager configuration: %s", err)
err = adminClient.Delete(ctx, intervalToDelete.Name, v1.DeleteOptions{})
require.Truef(t, errors.IsConflict(err), "Expected Conflict, got: %s", err)
})
}