Alerting: Support UTF-8 (#81512)
This pull request updates our fork of Alertmanager to commit 65bdab0, which is based on commit 5658f8c in Prometheus Alertmanager. It applies the changes from grafana/alerting#155 which removes the overrides for validation of alerts, labels and silences that we had put in place to allow alerts and silences to work for non-Prometheus datasources. However, as this is now supported in Alertmanager with the UTF-8 work, we can use the new upstream functions and remove these overrides. The compat package is a package in Alertmanager that takes care of backwards compatibility when parsing matchers, validating alerts, labels and silences. It has three modes: classic mode, UTF-8 strict mode, fallback mode. These modes are controlled via compat.InitFromFlags. Grafana initializes the compat package without any feature flags, which is the equivalent of fallback mode. Classic and UTF-8 strict mode are used in Mimir. While Grafana Managed Alerts have no need for fallback mode, Grafana can still be used as an interface to manage the configurations of Mimir Alertmanagers and view configurations of Prometheus Alertmanager, and those installations might not have migrated or being running on older versions. Such installations behave as if in classic mode, and Grafana must be able to parse their configurations to interact with them for some period of time. As such, Grafana uses fallback mode until we are ready to drop support for outdated installations of Mimir and the Prometheus Alertmanager.
This commit is contained in:
@@ -12,13 +12,13 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
amv2 "github.com/prometheus/alertmanager/api/v2/models"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/grafana/pkg/expr"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
ngstore "github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
@@ -31,6 +31,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/user/userimpl"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/tests/testinfra"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
type Response struct {
|
||||
@@ -704,7 +706,7 @@ func TestIntegrationDeleteFolderWithRules(t *testing.T) {
|
||||
apiClient := newAlertingApiClient(grafanaListedAddr, "editor", "editor")
|
||||
|
||||
// Create the namespace we'll save our alerts to.
|
||||
namespaceUID := "default"
|
||||
namespaceUID := "default" //nolint:goconst
|
||||
apiClient.CreateFolder(t, namespaceUID, namespaceUID)
|
||||
|
||||
createRule(t, apiClient, "default")
|
||||
@@ -1842,6 +1844,155 @@ func TestIntegrationAlertRuleCRUD(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegrationAlertmanagerCreateSilence(t *testing.T) {
|
||||
testinfra.SQLiteIntegrationTest(t)
|
||||
dir, path := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{
|
||||
DisableLegacyAlerting: true,
|
||||
EnableUnifiedAlerting: true,
|
||||
AppModeProduction: true,
|
||||
})
|
||||
grafanaListedAddr, store := testinfra.StartGrafana(t, dir, path)
|
||||
createUser(t, store, user.CreateUserCommand{
|
||||
DefaultOrgRole: string(org.RoleAdmin),
|
||||
Password: "admin",
|
||||
Login: "admin",
|
||||
})
|
||||
client := newAlertingApiClient(grafanaListedAddr, "admin", "admin")
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
silence apimodels.PostableSilence
|
||||
expErr string
|
||||
}{{
|
||||
name: "can create silence for foo=bar",
|
||||
silence: apimodels.PostableSilence{
|
||||
Silence: amv2.Silence{
|
||||
Comment: util.Pointer("This is a comment"),
|
||||
CreatedBy: util.Pointer("test"),
|
||||
EndsAt: util.Pointer(strfmt.DateTime(time.Now().Add(time.Minute))),
|
||||
Matchers: amv2.Matchers{{
|
||||
IsEqual: util.Pointer(true),
|
||||
IsRegex: util.Pointer(false),
|
||||
Name: util.Pointer("foo"),
|
||||
Value: util.Pointer("bar"),
|
||||
}},
|
||||
StartsAt: util.Pointer(strfmt.DateTime(time.Now())),
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "can create silence for _foo1=bar",
|
||||
silence: apimodels.PostableSilence{
|
||||
Silence: amv2.Silence{
|
||||
Comment: util.Pointer("This is a comment"),
|
||||
CreatedBy: util.Pointer("test"),
|
||||
EndsAt: util.Pointer(strfmt.DateTime(time.Now().Add(time.Minute))),
|
||||
Matchers: amv2.Matchers{{
|
||||
IsEqual: util.Pointer(true),
|
||||
IsRegex: util.Pointer(false),
|
||||
Name: util.Pointer("_foo1"),
|
||||
Value: util.Pointer("bar"),
|
||||
}},
|
||||
StartsAt: util.Pointer(strfmt.DateTime(time.Now())),
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "can create silence for 0foo=bar",
|
||||
silence: apimodels.PostableSilence{
|
||||
Silence: amv2.Silence{
|
||||
Comment: util.Pointer("This is a comment"),
|
||||
CreatedBy: util.Pointer("test"),
|
||||
EndsAt: util.Pointer(strfmt.DateTime(time.Now().Add(time.Minute))),
|
||||
Matchers: amv2.Matchers{{
|
||||
IsEqual: util.Pointer(true),
|
||||
IsRegex: util.Pointer(false),
|
||||
Name: util.Pointer("0foo"),
|
||||
Value: util.Pointer("bar"),
|
||||
}},
|
||||
StartsAt: util.Pointer(strfmt.DateTime(time.Now())),
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "can create silence for foo=🙂bar",
|
||||
silence: apimodels.PostableSilence{
|
||||
Silence: amv2.Silence{
|
||||
Comment: util.Pointer("This is a comment"),
|
||||
CreatedBy: util.Pointer("test"),
|
||||
EndsAt: util.Pointer(strfmt.DateTime(time.Now().Add(time.Minute))),
|
||||
Matchers: amv2.Matchers{{
|
||||
IsEqual: util.Pointer(true),
|
||||
IsRegex: util.Pointer(false),
|
||||
Name: util.Pointer("foo"),
|
||||
Value: util.Pointer("🙂bar"),
|
||||
}},
|
||||
StartsAt: util.Pointer(strfmt.DateTime(time.Now())),
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "can create silence for foo🙂=bar",
|
||||
silence: apimodels.PostableSilence{
|
||||
Silence: amv2.Silence{
|
||||
Comment: util.Pointer("This is a comment"),
|
||||
CreatedBy: util.Pointer("test"),
|
||||
EndsAt: util.Pointer(strfmt.DateTime(time.Now().Add(time.Minute))),
|
||||
Matchers: amv2.Matchers{{
|
||||
IsEqual: util.Pointer(true),
|
||||
IsRegex: util.Pointer(false),
|
||||
Name: util.Pointer("foo🙂"),
|
||||
Value: util.Pointer("bar"),
|
||||
}},
|
||||
StartsAt: util.Pointer(strfmt.DateTime(time.Now())),
|
||||
},
|
||||
},
|
||||
}, {
|
||||
name: "can't create silence for missing label name",
|
||||
silence: apimodels.PostableSilence{
|
||||
Silence: amv2.Silence{
|
||||
Comment: util.Pointer("This is a comment"),
|
||||
CreatedBy: util.Pointer("test"),
|
||||
EndsAt: util.Pointer(strfmt.DateTime(time.Now().Add(time.Minute))),
|
||||
Matchers: amv2.Matchers{{
|
||||
IsEqual: util.Pointer(true),
|
||||
IsRegex: util.Pointer(false),
|
||||
Name: util.Pointer(""),
|
||||
Value: util.Pointer("bar"),
|
||||
}},
|
||||
StartsAt: util.Pointer(strfmt.DateTime(time.Now())),
|
||||
},
|
||||
},
|
||||
expErr: "unable to save silence: silence invalid: invalid label matcher 0: invalid label name \"\": unable to create silence",
|
||||
}, {
|
||||
name: "can't create silence for missing label value",
|
||||
silence: apimodels.PostableSilence{
|
||||
Silence: amv2.Silence{
|
||||
Comment: util.Pointer("This is a comment"),
|
||||
CreatedBy: util.Pointer("test"),
|
||||
EndsAt: util.Pointer(strfmt.DateTime(time.Now().Add(time.Minute))),
|
||||
Matchers: amv2.Matchers{{
|
||||
IsEqual: util.Pointer(true),
|
||||
IsRegex: util.Pointer(false),
|
||||
Name: util.Pointer("foo"),
|
||||
Value: util.Pointer(""),
|
||||
}},
|
||||
StartsAt: util.Pointer(strfmt.DateTime(time.Now())),
|
||||
},
|
||||
},
|
||||
expErr: "unable to save silence: silence invalid: at least one matcher must not match the empty string: unable to create silence",
|
||||
}}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
silenceID, err := client.PostSilence(t, tc.silence)
|
||||
if tc.expErr != "" {
|
||||
require.EqualError(t, err, tc.expErr)
|
||||
require.Empty(t, silenceID)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, silenceID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegrationAlertmanagerStatus(t *testing.T) {
|
||||
testinfra.SQLiteIntegrationTest(t)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user