2021-04-08 19:27:59 +08:00
package alerting
import (
2021-04-16 20:00:07 +08:00
"bytes"
2021-05-05 00:16:28 +08:00
"context"
2021-04-16 20:00:07 +08:00
"encoding/json"
2021-04-08 19:27:59 +08:00
"fmt"
2022-08-10 21:37:51 +08:00
"io"
2021-04-08 19:27:59 +08:00
"net/http"
2021-05-11 16:31:38 +08:00
"regexp"
2021-04-21 22:22:58 +08:00
"strings"
2021-04-08 19:27:59 +08:00
"testing"
2021-04-16 20:00:07 +08:00
"time"
2021-04-08 19:27:59 +08:00
2021-04-21 18:34:42 +08:00
"github.com/prometheus/common/model"
2021-04-16 20:00:07 +08:00
"github.com/stretchr/testify/assert"
2021-04-08 19:27:59 +08:00
"github.com/stretchr/testify/require"
2021-04-16 20:00:07 +08:00
2022-11-08 17:52:07 +08:00
"github.com/grafana/grafana/pkg/models"
2021-04-22 21:06:32 +08:00
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
2021-04-16 20:00:07 +08:00
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
2021-04-21 22:22:58 +08:00
ngstore "github.com/grafana/grafana/pkg/services/ngalert/store"
2022-08-10 17:56:48 +08:00
"github.com/grafana/grafana/pkg/services/org"
2021-04-16 20:00:07 +08:00
"github.com/grafana/grafana/pkg/services/sqlstore"
2022-06-28 20:32:25 +08:00
"github.com/grafana/grafana/pkg/services/user"
2022-05-25 19:43:58 +08:00
"github.com/grafana/grafana/pkg/setting"
2021-04-16 20:00:07 +08:00
"github.com/grafana/grafana/pkg/tests/testinfra"
2021-04-08 19:27:59 +08:00
)
2022-05-05 16:37:26 +08:00
type Response struct {
Message string ` json:"message" `
TraceID string ` json:"traceID" `
}
2021-05-11 16:31:38 +08:00
func TestAMConfigAccess ( t * testing . T ) {
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2021-09-29 22:16:40 +08:00
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
DisableAnonymous : true ,
2022-02-09 17:26:06 +08:00
AppModeProduction : true ,
2021-05-11 16:31:38 +08:00
} )
2021-08-25 21:11:22 +08:00
grafanaListedAddr , store := testinfra . StartGrafana ( t , dir , path )
2021-05-11 16:31:38 +08:00
// Create a users to make authenticated requests
2022-06-28 20:32:25 +08:00
createUser ( t , store , user . CreateUserCommand {
2022-08-10 17:56:48 +08:00
DefaultOrgRole : string ( org . RoleViewer ) ,
2021-08-12 21:04:09 +08:00
Password : "viewer" ,
Login : "viewer" ,
} )
2022-06-28 20:32:25 +08:00
createUser ( t , store , user . CreateUserCommand {
2022-08-10 17:56:48 +08:00
DefaultOrgRole : string ( org . RoleEditor ) ,
2021-08-12 21:04:09 +08:00
Password : "editor" ,
Login : "editor" ,
} )
2022-06-28 20:32:25 +08:00
createUser ( t , store , user . CreateUserCommand {
2022-08-10 17:56:48 +08:00
DefaultOrgRole : string ( org . RoleAdmin ) ,
2021-08-12 21:04:09 +08:00
Password : "admin" ,
Login : "admin" ,
} )
2021-05-11 16:31:38 +08:00
type testCase struct {
desc string
url string
expStatus int
expBody string
}
t . Run ( "when creating alertmanager configuration" , func ( t * testing . T ) {
body := `
{
"alertmanager_config" : {
"route" : {
"receiver" : "grafana-default-email"
} ,
"receivers" : [ {
"name" : "grafana-default-email" ,
"grafana_managed_receiver_configs" : [ {
"uid" : "" ,
"name" : "email receiver" ,
"type" : "email" ,
"isDefault" : true ,
"settings" : {
"addresses" : "<example@email.com>"
}
} ]
} ]
}
}
`
testCases := [ ] testCase {
{
desc : "un-authenticated request should fail" ,
url : "http://%s/api/alertmanager/grafana/config/api/v1/alerts" ,
expStatus : http . StatusUnauthorized ,
2022-05-16 18:45:41 +08:00
expBody : ` { "message":"Unauthorized"} ` ,
2021-05-11 16:31:38 +08:00
} ,
{
desc : "viewer request should fail" ,
url : "http://viewer:viewer@%s/api/alertmanager/grafana/config/api/v1/alerts" ,
expStatus : http . StatusForbidden ,
2022-05-16 18:45:41 +08:00
expBody : ` "title":"Access denied" ` ,
2021-05-11 16:31:38 +08:00
} ,
{
desc : "editor request should succeed" ,
url : "http://editor:editor@%s/api/alertmanager/grafana/config/api/v1/alerts" ,
expStatus : http . StatusAccepted ,
expBody : ` { "message":"configuration created"} ` ,
} ,
{
desc : "admin request should succeed" ,
url : "http://admin:admin@%s/api/alertmanager/grafana/config/api/v1/alerts" ,
expStatus : http . StatusAccepted ,
expBody : ` { "message":"configuration created"} ` ,
} ,
}
for _ , tc := range testCases {
t . Run ( tc . desc , func ( t * testing . T ) {
url := fmt . Sprintf ( tc . url , grafanaListedAddr )
buf := bytes . NewReader ( [ ] byte ( body ) )
// nolint:gosec
resp , err := http . Post ( url , "application/json" , buf )
t . Cleanup ( func ( ) {
require . NoError ( t , resp . Body . Close ( ) )
} )
require . NoError ( t , err )
require . Equal ( t , tc . expStatus , resp . StatusCode )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-05-11 16:31:38 +08:00
require . NoError ( t , err )
2022-05-16 18:45:41 +08:00
require . Contains ( t , string ( b ) , tc . expBody )
2021-05-11 16:31:38 +08:00
} )
}
} )
2021-07-07 21:29:18 +08:00
t . Run ( "when retrieve alertmanager configuration" , func ( t * testing . T ) {
cfgBody := `
{
"template_files" : null ,
"alertmanager_config" : {
"route" : {
"receiver" : "grafana-default-email"
} ,
"templates" : null ,
"receivers" : [ {
"name" : "grafana-default-email" ,
"grafana_managed_receiver_configs" : [ {
"disableResolveMessage" : false ,
"uid" : "" ,
"name" : "email receiver" ,
"type" : "email" ,
"secureFields" : { } ,
"settings" : {
"addresses" : "<example@email.com>"
}
} ]
} ]
}
}
`
testCases := [ ] testCase {
{
desc : "un-authenticated request should fail" ,
url : "http://%s/api/alertmanager/grafana/config/api/v1/alerts" ,
expStatus : http . StatusUnauthorized ,
expBody : ` { "message": "Unauthorized"} ` ,
} ,
{
2022-05-16 18:45:41 +08:00
desc : "viewer request should succeed" ,
2021-07-07 21:29:18 +08:00
url : "http://viewer:viewer@%s/api/alertmanager/grafana/config/api/v1/alerts" ,
2022-05-16 18:45:41 +08:00
expStatus : http . StatusOK ,
expBody : cfgBody ,
2021-07-07 21:29:18 +08:00
} ,
{
desc : "editor request should succeed" ,
url : "http://editor:editor@%s/api/alertmanager/grafana/config/api/v1/alerts" ,
expStatus : http . StatusOK ,
expBody : cfgBody ,
} ,
{
desc : "admin request should succeed" ,
url : "http://admin:admin@%s/api/alertmanager/grafana/config/api/v1/alerts" ,
expStatus : http . StatusOK ,
expBody : cfgBody ,
} ,
}
for _ , tc := range testCases {
t . Run ( tc . desc , func ( t * testing . T ) {
resp , err := http . Get ( fmt . Sprintf ( tc . url , grafanaListedAddr ) )
t . Cleanup ( func ( ) {
require . NoError ( t , resp . Body . Close ( ) )
} )
require . NoError ( t , err )
require . Equal ( t , tc . expStatus , resp . StatusCode )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-07-07 21:29:18 +08:00
if tc . expStatus == http . StatusOK {
re := regexp . MustCompile ( ` "uid":"([\w|-]+)" ` )
b = re . ReplaceAll ( b , [ ] byte ( ` "uid":"" ` ) )
}
require . NoError ( t , err )
require . JSONEq ( t , tc . expBody , string ( b ) )
} )
}
} )
2021-05-11 16:31:38 +08:00
t . Run ( "when creating silence" , func ( t * testing . T ) {
body := `
{
"comment" : "string" ,
"createdBy" : "string" ,
"endsAt" : "2023-03-31T14:17:04.419Z" ,
"matchers" : [
{
"isRegex" : true ,
"name" : "string" ,
"value" : "string"
}
] ,
"startsAt" : "2021-03-31T13:17:04.419Z"
}
`
testCases := [ ] testCase {
{
desc : "un-authenticated request should fail" ,
url : "http://%s/api/alertmanager/grafana/config/api/v2/silences" ,
expStatus : http . StatusUnauthorized ,
2022-05-16 18:45:41 +08:00
expBody : ` { "message":"Unauthorized"} ` ,
2021-05-11 16:31:38 +08:00
} ,
{
desc : "viewer request should fail" ,
url : "http://viewer:viewer@%s/api/alertmanager/grafana/api/v2/silences" ,
expStatus : http . StatusForbidden ,
2022-05-16 18:45:41 +08:00
expBody : ` "title":"Access denied" ` ,
2021-05-11 16:31:38 +08:00
} ,
{
desc : "editor request should succeed" ,
url : "http://editor:editor@%s/api/alertmanager/grafana/api/v2/silences" ,
expStatus : http . StatusAccepted ,
} ,
{
desc : "admin request should succeed" ,
url : "http://admin:admin@%s/api/alertmanager/grafana/api/v2/silences" ,
expStatus : http . StatusAccepted ,
} ,
}
for _ , tc := range testCases {
t . Run ( tc . desc , func ( t * testing . T ) {
url := fmt . Sprintf ( tc . url , grafanaListedAddr )
buf := bytes . NewReader ( [ ] byte ( body ) )
// nolint:gosec
resp , err := http . Post ( url , "application/json" , buf )
t . Cleanup ( func ( ) {
require . NoError ( t , resp . Body . Close ( ) )
} )
require . NoError ( t , err )
require . Equal ( t , tc . expStatus , resp . StatusCode )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-05-11 16:31:38 +08:00
require . NoError ( t , err )
if tc . expStatus == http . StatusAccepted {
2022-10-18 03:43:37 +08:00
response := apimodels . PostSilencesOKBody { }
require . NoError ( t , json . Unmarshal ( b , & response ) )
require . NotEmpty ( t , response . SilenceID )
return
2021-05-11 16:31:38 +08:00
}
2022-05-16 18:45:41 +08:00
require . Contains ( t , string ( b ) , tc . expBody )
2021-05-11 16:31:38 +08:00
} )
}
} )
var blob [ ] byte
t . Run ( "when getting silences" , func ( t * testing . T ) {
testCases := [ ] testCase {
{
desc : "un-authenticated request should fail" ,
url : "http://%s/api/alertmanager/grafana/api/v2/silences" ,
expStatus : http . StatusUnauthorized ,
expBody : ` { "message": "Unauthorized"} ` ,
} ,
{
desc : "viewer request should succeed" ,
url : "http://viewer:viewer@%s/api/alertmanager/grafana/api/v2/silences" ,
expStatus : http . StatusOK ,
} ,
{
desc : "editor request should succeed" ,
url : "http://editor:editor@%s/api/alertmanager/grafana/api/v2/silences" ,
expStatus : http . StatusOK ,
} ,
{
desc : "admin request should succeed" ,
url : "http://admin:admin@%s/api/alertmanager/grafana/api/v2/silences" ,
expStatus : http . StatusOK ,
} ,
}
for _ , tc := range testCases {
t . Run ( tc . desc , func ( t * testing . T ) {
url := fmt . Sprintf ( tc . url , grafanaListedAddr )
// nolint:gosec
resp , err := http . Get ( url )
t . Cleanup ( func ( ) {
require . NoError ( t , resp . Body . Close ( ) )
} )
require . NoError ( t , err )
require . Equal ( t , tc . expStatus , resp . StatusCode )
require . NoError ( t , err )
if tc . expStatus == http . StatusOK {
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-05-11 16:31:38 +08:00
require . NoError ( t , err )
blob = b
}
} )
}
} )
var silences apimodels . GettableSilences
2022-06-15 18:40:41 +08:00
err := json . Unmarshal ( blob , & silences )
2021-05-11 16:31:38 +08:00
require . NoError ( t , err )
assert . Len ( t , silences , 2 )
silenceIDs := make ( [ ] string , 0 , len ( silences ) )
for _ , s := range silences {
silenceIDs = append ( silenceIDs , * s . ID )
}
unconsumedSilenceIdx := 0
t . Run ( "when deleting a silence" , func ( t * testing . T ) {
testCases := [ ] testCase {
{
desc : "un-authenticated request should fail" ,
url : "http://%s/api/alertmanager/grafana/api/v2/silence/%s" ,
expStatus : http . StatusUnauthorized ,
2022-05-16 18:45:41 +08:00
expBody : ` { "message":"Unauthorized"} ` ,
2021-05-11 16:31:38 +08:00
} ,
{
desc : "viewer request should fail" ,
url : "http://viewer:viewer@%s/api/alertmanager/grafana/api/v2/silence/%s" ,
expStatus : http . StatusForbidden ,
2022-05-16 18:45:41 +08:00
expBody : ` "title":"Access denied" ` ,
2021-05-11 16:31:38 +08:00
} ,
{
desc : "editor request should succeed" ,
url : "http://editor:editor@%s/api/alertmanager/grafana/api/v2/silence/%s" ,
expStatus : http . StatusOK ,
2022-05-16 18:45:41 +08:00
expBody : ` { "message":"silence deleted"} ` ,
2021-05-11 16:31:38 +08:00
} ,
{
desc : "admin request should succeed" ,
url : "http://admin:admin@%s/api/alertmanager/grafana/api/v2/silence/%s" ,
expStatus : http . StatusOK ,
2022-05-16 18:45:41 +08:00
expBody : ` { "message":"silence deleted"} ` ,
2021-05-11 16:31:38 +08:00
} ,
}
for _ , tc := range testCases {
t . Run ( tc . desc , func ( t * testing . T ) {
url := fmt . Sprintf ( tc . url , grafanaListedAddr , silenceIDs [ unconsumedSilenceIdx ] )
// Create client
client := & http . Client { }
// Create request
req , err := http . NewRequest ( "DELETE" , url , nil )
if err != nil {
fmt . Println ( err )
return
}
// Fetch Request
resp , err := client . Do ( req )
if err != nil {
return
}
t . Cleanup ( func ( ) {
require . NoError ( t , resp . Body . Close ( ) )
} )
require . NoError ( t , err )
require . Equal ( t , tc . expStatus , resp . StatusCode )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-05-11 16:31:38 +08:00
require . NoError ( t , err )
if tc . expStatus == http . StatusOK {
unconsumedSilenceIdx ++
}
2022-05-16 18:45:41 +08:00
require . Contains ( t , string ( b ) , tc . expBody )
2021-05-11 16:31:38 +08:00
} )
}
} )
}
2021-04-08 19:27:59 +08:00
func TestAlertAndGroupsQuery ( t * testing . T ) {
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2021-09-29 22:16:40 +08:00
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
DisableAnonymous : true ,
2022-02-09 17:26:06 +08:00
AppModeProduction : true ,
2021-04-08 19:27:59 +08:00
} )
2021-04-21 18:34:42 +08:00
2021-08-25 21:11:22 +08:00
grafanaListedAddr , store := testinfra . StartGrafana ( t , dir , path )
2021-04-08 19:27:59 +08:00
2021-05-05 00:16:28 +08:00
// unauthenticated request to get the alerts should fail
2021-04-08 19:27:59 +08:00
{
alertsURL := fmt . Sprintf ( "http://%s/api/alertmanager/grafana/api/v2/alerts" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Get ( alertsURL )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-04-08 19:27:59 +08:00
require . NoError ( t , err )
2021-05-05 00:16:28 +08:00
require . Equal ( t , http . StatusUnauthorized , resp . StatusCode )
require . JSONEq ( t , ` { "message": "Unauthorized"} ` , string ( b ) )
}
// Create a user to make authenticated requests
2022-06-28 20:32:25 +08:00
createUser ( t , store , user . CreateUserCommand {
2022-08-10 17:56:48 +08:00
DefaultOrgRole : string ( org . RoleEditor ) ,
2021-08-12 21:04:09 +08:00
Password : "password" ,
Login : "grafana" ,
} )
2021-05-05 00:16:28 +08:00
2022-06-21 23:39:22 +08:00
apiClient := newAlertingApiClient ( grafanaListedAddr , "grafana" , "password" )
2021-05-05 00:16:28 +08:00
// invalid credentials request to get the alerts should fail
{
alertsURL := fmt . Sprintf ( "http://grafana:invalid@%s/api/alertmanager/grafana/api/v2/alerts" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Get ( alertsURL )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-05-05 00:16:28 +08:00
require . NoError ( t , err )
require . Equal ( t , http . StatusUnauthorized , resp . StatusCode )
2022-04-14 23:54:49 +08:00
var res map [ string ] interface { }
require . NoError ( t , json . Unmarshal ( b , & res ) )
require . Equal ( t , "invalid username or password" , res [ "message" ] )
2021-05-05 00:16:28 +08:00
}
// When there are no alerts available, it returns an empty list.
{
alertsURL := fmt . Sprintf ( "http://grafana:password@%s/api/alertmanager/grafana/api/v2/alerts" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Get ( alertsURL )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-05-05 00:16:28 +08:00
require . NoError ( t , err )
2021-04-08 19:27:59 +08:00
require . Equal ( t , 200 , resp . StatusCode )
require . JSONEq ( t , "[]" , string ( b ) )
}
// When are there no alerts available, it returns an empty list of groups.
{
2021-05-05 00:16:28 +08:00
alertsURL := fmt . Sprintf ( "http://grafana:password@%s/api/alertmanager/grafana/api/v2/alerts/groups" , grafanaListedAddr )
2021-04-08 19:27:59 +08:00
// nolint:gosec
resp , err := http . Get ( alertsURL )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-04-08 19:27:59 +08:00
require . NoError ( t , err )
require . NoError ( t , err )
require . Equal ( t , 200 , resp . StatusCode )
require . JSONEq ( t , "[]" , string ( b ) )
}
2021-04-21 18:34:42 +08:00
// Now, let's test the endpoint with some alerts.
{
// Create the namespace we'll save our alerts to.
2022-06-21 23:39:22 +08:00
apiClient . CreateFolder ( t , "default" , "default" )
2021-04-21 18:34:42 +08:00
}
// Create an alert that will fire as quickly as possible
{
interval , err := model . ParseDuration ( "10s" )
require . NoError ( t , err )
rules := apimodels . PostableRuleGroupConfig {
Name : "arulegroup" ,
Interval : interval ,
Rules : [ ] apimodels . PostableExtendedRuleNode {
{
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
Title : "AlwaysFiring" ,
Condition : "A" ,
Data : [ ] ngmodels . AlertQuery {
{
RefID : "A" ,
RelativeTimeRange : ngmodels . RelativeTimeRange {
From : ngmodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : ngmodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
} ,
2021-04-23 22:52:32 +08:00
DatasourceUID : "-100" ,
2021-04-21 18:34:42 +08:00
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 > 1"
} ` ) ,
} ,
} ,
} ,
} ,
} ,
}
2022-06-21 23:39:22 +08:00
status , _ := apiClient . PostRulesGroup ( t , "default" , & rules )
assert . Equal ( t , http . StatusAccepted , status )
2021-04-21 18:34:42 +08:00
}
// Eventually, we'll get an alert with its state being active.
{
2021-05-05 00:16:28 +08:00
alertsURL := fmt . Sprintf ( "http://grafana:password@%s/api/alertmanager/grafana/api/v2/alerts" , grafanaListedAddr )
2021-04-21 18:34:42 +08:00
// nolint:gosec
require . Eventually ( t , func ( ) bool {
resp , err := http . Get ( alertsURL )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-04-21 18:34:42 +08:00
require . NoError ( t , err )
require . Equal ( t , 200 , resp . StatusCode )
var alerts apimodels . GettableAlerts
err = json . Unmarshal ( b , & alerts )
require . NoError ( t , err )
if len ( alerts ) > 0 {
status := alerts [ 0 ] . Status
return status != nil && status . State != nil && * status . State == "active"
}
return false
} , 18 * time . Second , 2 * time . Second )
}
2021-04-08 19:27:59 +08:00
}
2021-04-16 20:00:07 +08:00
2021-05-20 20:49:33 +08:00
func TestRulerAccess ( t * testing . T ) {
// Setup Grafana and its Database
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2021-09-29 22:16:40 +08:00
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
EnableQuota : true ,
DisableAnonymous : true ,
ViewersCanEdit : true ,
2022-02-09 17:26:06 +08:00
AppModeProduction : true ,
2021-05-20 20:49:33 +08:00
} )
2021-08-25 21:11:22 +08:00
grafanaListedAddr , store := testinfra . StartGrafana ( t , dir , path )
2021-05-20 20:49:33 +08:00
// Create a users to make authenticated requests
2022-06-28 20:32:25 +08:00
createUser ( t , store , user . CreateUserCommand {
2022-08-10 17:56:48 +08:00
DefaultOrgRole : string ( org . RoleViewer ) ,
2021-08-12 21:04:09 +08:00
Password : "viewer" ,
Login : "viewer" ,
} )
2022-06-28 20:32:25 +08:00
createUser ( t , store , user . CreateUserCommand {
2022-08-10 17:56:48 +08:00
DefaultOrgRole : string ( org . RoleEditor ) ,
2021-08-12 21:04:09 +08:00
Password : "editor" ,
Login : "editor" ,
} )
2022-06-28 20:32:25 +08:00
createUser ( t , store , user . CreateUserCommand {
2022-08-10 17:56:48 +08:00
DefaultOrgRole : string ( org . RoleAdmin ) ,
2021-08-12 21:04:09 +08:00
Password : "admin" ,
Login : "admin" ,
} )
2021-05-20 20:49:33 +08:00
2022-06-21 23:39:22 +08:00
client := newAlertingApiClient ( grafanaListedAddr , "editor" , "editor" )
2022-05-16 18:45:41 +08:00
// Create the namespace we'll save our alerts to.
2022-06-21 23:39:22 +08:00
client . CreateFolder ( t , "default" , "default" )
2022-05-16 18:45:41 +08:00
2021-05-20 20:49:33 +08:00
// Now, let's test the access policies.
testCases := [ ] struct {
2022-05-16 18:45:41 +08:00
desc string
2022-06-21 23:39:22 +08:00
client apiClient
2022-05-16 18:45:41 +08:00
expStatus int
expectedMessage string
2021-05-20 20:49:33 +08:00
} {
{
2022-05-16 18:45:41 +08:00
desc : "un-authenticated request should fail" ,
2022-06-21 23:39:22 +08:00
client : newAlertingApiClient ( grafanaListedAddr , "" , "" ) ,
2022-05-16 18:45:41 +08:00
expStatus : http . StatusUnauthorized ,
expectedMessage : ` Unauthorized ` ,
2021-05-20 20:49:33 +08:00
} ,
{
2022-05-16 18:45:41 +08:00
desc : "viewer request should fail" ,
2022-06-21 23:39:22 +08:00
client : newAlertingApiClient ( grafanaListedAddr , "viewer" , "viewer" ) ,
2022-05-16 18:45:41 +08:00
expStatus : http . StatusForbidden ,
2022-06-02 20:14:48 +08:00
expectedMessage : ` You'll need additional permissions to perform this action. Permissions needed: any of alert.rules:write, alert.rules:create, alert.rules:delete ` ,
2021-05-20 20:49:33 +08:00
} ,
{
2022-05-16 18:45:41 +08:00
desc : "editor request should succeed" ,
2022-06-21 23:39:22 +08:00
client : newAlertingApiClient ( grafanaListedAddr , "editor" , "editor" ) ,
2022-05-16 18:45:41 +08:00
expStatus : http . StatusAccepted ,
expectedMessage : ` rule group updated successfully ` ,
2021-05-20 20:49:33 +08:00
} ,
{
2022-05-16 18:45:41 +08:00
desc : "admin request should succeed" ,
2022-06-21 23:39:22 +08:00
client : newAlertingApiClient ( grafanaListedAddr , "admin" , "admin" ) ,
2022-05-16 18:45:41 +08:00
expStatus : http . StatusAccepted ,
expectedMessage : ` rule group updated successfully ` ,
2021-05-20 20:49:33 +08:00
} ,
}
for i , tc := range testCases {
t . Run ( tc . desc , func ( t * testing . T ) {
interval , err := model . ParseDuration ( "1m" )
require . NoError ( t , err )
rules := apimodels . PostableRuleGroupConfig {
Name : "arulegroup" ,
Rules : [ ] apimodels . PostableExtendedRuleNode {
{
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 23:46:26 +08:00
For : & interval ,
2021-05-20 20:49:33 +08:00
Labels : map [ string ] string { "label1" : "val1" } ,
Annotations : map [ string ] string { "annotation1" : "val1" } ,
} ,
// this rule does not explicitly set no data and error states
// therefore it should get the default values
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
Title : fmt . Sprintf ( "AlwaysFiring %d" , i ) ,
Condition : "A" ,
Data : [ ] ngmodels . AlertQuery {
{
RefID : "A" ,
RelativeTimeRange : ngmodels . RelativeTimeRange {
From : ngmodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : ngmodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
} ,
DatasourceUID : "-100" ,
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 > 1"
} ` ) ,
} ,
} ,
} ,
} ,
} ,
}
2022-06-21 23:39:22 +08:00
status , body := tc . client . PostRulesGroup ( t , "default" , & rules )
assert . Equal ( t , tc . expStatus , status )
2022-05-16 18:45:41 +08:00
res := & Response { }
2022-06-21 23:39:22 +08:00
err = json . Unmarshal ( [ ] byte ( body ) , & res )
2022-05-16 18:45:41 +08:00
require . NoError ( t , err )
require . Equal ( t , tc . expectedMessage , res . Message )
2021-05-20 20:49:33 +08:00
} )
}
}
func TestDeleteFolderWithRules ( t * testing . T ) {
// Setup Grafana and its Database
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2021-09-29 22:16:40 +08:00
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
EnableQuota : true ,
DisableAnonymous : true ,
ViewersCanEdit : true ,
2022-02-09 17:26:06 +08:00
AppModeProduction : true ,
2021-05-20 20:49:33 +08:00
} )
2021-08-25 21:11:22 +08:00
grafanaListedAddr , store := testinfra . StartGrafana ( t , dir , path )
2021-05-20 20:49:33 +08:00
2022-06-28 20:32:25 +08:00
createUser ( t , store , user . CreateUserCommand {
2022-08-10 17:56:48 +08:00
DefaultOrgRole : string ( org . RoleViewer ) ,
2021-08-12 21:04:09 +08:00
Password : "viewer" ,
Login : "viewer" ,
} )
2022-06-28 20:32:25 +08:00
createUser ( t , store , user . CreateUserCommand {
2022-08-10 17:56:48 +08:00
DefaultOrgRole : string ( org . RoleEditor ) ,
2021-08-12 21:04:09 +08:00
Password : "editor" ,
Login : "editor" ,
} )
2021-05-20 20:49:33 +08:00
2022-06-21 23:39:22 +08:00
apiClient := newAlertingApiClient ( grafanaListedAddr , "editor" , "editor" )
2022-05-16 18:45:41 +08:00
// Create the namespace we'll save our alerts to.
namespaceUID := "default"
2022-06-21 23:39:22 +08:00
apiClient . CreateFolder ( t , namespaceUID , namespaceUID )
2022-05-16 18:45:41 +08:00
2022-06-21 23:39:22 +08:00
createRule ( t , apiClient , "default" )
2021-05-20 20:49:33 +08:00
// First, let's have an editor create a rule within the folder/namespace.
{
u := fmt . Sprintf ( "http://editor:editor@%s/api/ruler/grafana/api/v1/rules" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Get ( u )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-05-20 20:49:33 +08:00
require . NoError ( t , err )
2021-10-04 23:33:55 +08:00
assert . Equal ( t , 200 , resp . StatusCode )
2021-05-20 20:49:33 +08:00
re := regexp . MustCompile ( ` "uid":"([\w|-]+)" ` )
b = re . ReplaceAll ( b , [ ] byte ( ` "uid":"" ` ) )
re = regexp . MustCompile ( ` "updated":"(\d { 4}-\d { 2}-\d { 2}T\d { 2}:\d { 2}:\d { 2}Z)" ` )
b = re . ReplaceAll ( b , [ ] byte ( ` "updated":"2021-05-19T19:47:55Z" ` ) )
expectedGetRulesResponseBody := fmt . Sprintf ( ` {
"default" : [
{
"name" : "arulegroup" ,
"interval" : "1m" ,
"rules" : [
{
"expr" : "" ,
"for" : "2m" ,
"labels" : {
"label1" : "val1"
} ,
"annotations" : {
"annotation1" : "val1"
} ,
"grafana_alert" : {
"id" : 1 ,
"orgId" : 1 ,
"title" : "rule under folder default" ,
"condition" : "A" ,
"data" : [
{
"refId" : "A" ,
"queryType" : "" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
"datasourceUid" : "-100" ,
"model" : {
"expression" : "2 + 3 > 1" ,
"intervalMs" : 1000 ,
"maxDataPoints" : 43200 ,
"type" : "math"
}
}
] ,
"updated" : "2021-05-19T19:47:55Z" ,
"intervalSeconds" : 60 ,
"version" : 1 ,
"uid" : "" ,
"namespace_uid" : % q ,
"namespace_id" : 1 ,
"rule_group" : "arulegroup" ,
"no_data_state" : "NoData" ,
"exec_err_state" : "Alerting"
}
}
]
}
]
} ` , namespaceUID )
assert . JSONEq ( t , expectedGetRulesResponseBody , string ( b ) )
}
2021-07-22 17:27:13 +08:00
// Next, the editor can not delete the folder because it contains Grafana 8 alerts.
2021-05-20 20:49:33 +08:00
{
u := fmt . Sprintf ( "http://editor:editor@%s/api/folders/%s" , grafanaListedAddr , namespaceUID )
2021-07-22 17:27:13 +08:00
req , err := http . NewRequest ( http . MethodDelete , u , nil )
require . NoError ( t , err )
client := & http . Client { }
resp , err := client . Do ( req )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-07-22 17:27:13 +08:00
require . NoError ( t , err )
require . Equal ( t , http . StatusBadRequest , resp . StatusCode )
require . JSONEq ( t , ` { "message":"folder cannot be deleted: folder contains alert rules"} ` , string ( b ) )
}
// Next, the editor can delete the folder if forceDeleteRules is true.
{
u := fmt . Sprintf ( "http://editor:editor@%s/api/folders/%s?forceDeleteRules=true" , grafanaListedAddr , namespaceUID )
2021-05-20 20:49:33 +08:00
req , err := http . NewRequest ( http . MethodDelete , u , nil )
require . NoError ( t , err )
client := & http . Client { }
resp , err := client . Do ( req )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-11-10 16:42:32 +08:00
_ , err = io . ReadAll ( resp . Body )
2021-05-20 20:49:33 +08:00
require . NoError ( t , err )
require . Equal ( t , 200 , resp . StatusCode )
}
// Finally, we ensure the rules were deleted.
{
u := fmt . Sprintf ( "http://editor:editor@%s/api/ruler/grafana/api/v1/rules" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Get ( u )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-05-20 20:49:33 +08:00
require . NoError ( t , err )
2021-10-04 23:33:55 +08:00
assert . Equal ( t , 200 , resp . StatusCode )
2021-05-20 20:49:33 +08:00
assert . JSONEq ( t , "{}" , string ( b ) )
}
}
2021-04-16 20:00:07 +08:00
func TestAlertRuleCRUD ( t * testing . T ) {
// Setup Grafana and its Database
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2021-09-29 22:16:40 +08:00
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
EnableQuota : true ,
DisableAnonymous : true ,
2022-02-09 17:26:06 +08:00
AppModeProduction : true ,
2021-04-16 20:00:07 +08:00
} )
2021-05-05 00:16:28 +08:00
2021-08-25 21:11:22 +08:00
grafanaListedAddr , store := testinfra . StartGrafana ( t , dir , path )
2021-04-16 20:00:07 +08:00
2022-06-28 20:32:25 +08:00
createUser ( t , store , user . CreateUserCommand {
2022-08-10 17:56:48 +08:00
DefaultOrgRole : string ( org . RoleEditor ) ,
2021-08-12 21:04:09 +08:00
Password : "password" ,
Login : "grafana" ,
} )
2021-05-05 00:16:28 +08:00
2022-06-21 23:39:22 +08:00
apiClient := newAlertingApiClient ( grafanaListedAddr , "grafana" , "password" )
2021-04-16 20:00:07 +08:00
// Create the namespace we'll save our alerts to.
2022-06-21 23:39:22 +08:00
apiClient . CreateFolder ( t , "default" , "default" )
2021-04-16 20:00:07 +08:00
2021-04-21 22:22:58 +08:00
interval , err := model . ParseDuration ( "1m" )
require . NoError ( t , err )
invalidInterval , err := model . ParseDuration ( "1s" )
require . NoError ( t , err )
// Now, let's try to create some invalid alert rules.
{
testCases := [ ] struct {
2022-05-05 16:37:26 +08:00
desc string
rulegroup string
interval model . Duration
rule apimodels . PostableExtendedRuleNode
expectedMessage string
2021-04-21 22:22:58 +08:00
} {
{
desc : "alert rule without queries and expressions" ,
rulegroup : "arulegroup" ,
rule : apimodels . PostableExtendedRuleNode {
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 23:46:26 +08:00
For : & interval ,
2021-04-21 22:22:58 +08:00
Labels : map [ string ] string { "label1" : "val1" } ,
Annotations : map [ string ] string { "annotation1" : "val1" } ,
} ,
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
2021-04-28 16:31:51 +08:00
Title : "AlwaysFiring" ,
Data : [ ] ngmodels . AlertQuery { } ,
2021-04-21 22:22:58 +08:00
} ,
} ,
2022-05-05 16:37:26 +08:00
expectedMessage : "invalid rule specification at index [0]: invalid alert rule: no queries or expressions are found" ,
2021-04-21 22:22:58 +08:00
} ,
{
desc : "alert rule with empty title" ,
rulegroup : "arulegroup" ,
rule : apimodels . PostableExtendedRuleNode {
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 23:46:26 +08:00
For : & interval ,
2021-04-21 22:22:58 +08:00
Labels : map [ string ] string { "label1" : "val1" } ,
Annotations : map [ string ] string { "annotation1" : "val1" } ,
} ,
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
Title : "" ,
Condition : "A" ,
Data : [ ] ngmodels . AlertQuery {
{
RefID : "A" ,
RelativeTimeRange : ngmodels . RelativeTimeRange {
From : ngmodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : ngmodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
} ,
2021-04-23 22:52:32 +08:00
DatasourceUID : "-100" ,
2021-04-21 22:22:58 +08:00
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 > 1"
} ` ) ,
} ,
} ,
} ,
} ,
2022-05-05 16:37:26 +08:00
expectedMessage : "invalid rule specification at index [0]: alert rule title cannot be empty" ,
2021-04-21 22:22:58 +08:00
} ,
{
desc : "alert rule with too long name" ,
rulegroup : "arulegroup" ,
rule : apimodels . PostableExtendedRuleNode {
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 23:46:26 +08:00
For : & interval ,
2021-04-21 22:22:58 +08:00
Labels : map [ string ] string { "label1" : "val1" } ,
Annotations : map [ string ] string { "annotation1" : "val1" } ,
} ,
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
2021-05-05 00:16:28 +08:00
Title : getLongString ( t , ngstore . AlertRuleMaxTitleLength + 1 ) ,
2021-04-21 22:22:58 +08:00
Condition : "A" ,
Data : [ ] ngmodels . AlertQuery {
{
RefID : "A" ,
RelativeTimeRange : ngmodels . RelativeTimeRange {
From : ngmodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : ngmodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
} ,
2021-04-23 22:52:32 +08:00
DatasourceUID : "-100" ,
2021-04-21 22:22:58 +08:00
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 > 1"
} ` ) ,
} ,
} ,
} ,
} ,
2022-05-05 16:37:26 +08:00
expectedMessage : "invalid rule specification at index [0]: alert rule title is too long. Max length is 190" ,
2021-04-21 22:22:58 +08:00
} ,
{
desc : "alert rule with too long rulegroup" ,
2021-05-05 00:16:28 +08:00
rulegroup : getLongString ( t , ngstore . AlertRuleMaxTitleLength + 1 ) ,
2021-04-21 22:22:58 +08:00
rule : apimodels . PostableExtendedRuleNode {
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 23:46:26 +08:00
For : & interval ,
2021-04-21 22:22:58 +08:00
Labels : map [ string ] string { "label1" : "val1" } ,
Annotations : map [ string ] string { "annotation1" : "val1" } ,
} ,
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
Title : "AlwaysFiring" ,
Condition : "A" ,
Data : [ ] ngmodels . AlertQuery {
{
RefID : "A" ,
RelativeTimeRange : ngmodels . RelativeTimeRange {
From : ngmodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : ngmodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
} ,
2021-04-23 22:52:32 +08:00
DatasourceUID : "-100" ,
2021-04-21 22:22:58 +08:00
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 > 1"
} ` ) ,
} ,
} ,
} ,
} ,
2022-05-05 16:37:26 +08:00
expectedMessage : "rule group name is too long. Max length is 190" ,
2021-04-21 22:22:58 +08:00
} ,
{
desc : "alert rule with invalid interval" ,
rulegroup : "arulegroup" ,
interval : invalidInterval ,
rule : apimodels . PostableExtendedRuleNode {
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 23:46:26 +08:00
For : & interval ,
2021-04-21 22:22:58 +08:00
Labels : map [ string ] string { "label1" : "val1" } ,
Annotations : map [ string ] string { "annotation1" : "val1" } ,
} ,
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
Title : "AlwaysFiring" ,
Condition : "A" ,
Data : [ ] ngmodels . AlertQuery {
{
RefID : "A" ,
RelativeTimeRange : ngmodels . RelativeTimeRange {
From : ngmodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : ngmodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
} ,
2021-04-23 22:52:32 +08:00
DatasourceUID : "-100" ,
2021-04-21 22:22:58 +08:00
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 > 1"
} ` ) ,
} ,
} ,
} ,
} ,
2022-05-05 16:37:26 +08:00
expectedMessage : "rule evaluation interval (1 second) should be positive number that is multiple of the base interval of 10 seconds" ,
2021-04-21 22:22:58 +08:00
} ,
2021-04-28 16:31:51 +08:00
{
desc : "alert rule with unknown datasource" ,
rulegroup : "arulegroup" ,
rule : apimodels . PostableExtendedRuleNode {
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 23:46:26 +08:00
For : & interval ,
2021-04-28 16:31:51 +08:00
Labels : map [ string ] string { "label1" : "val1" } ,
Annotations : map [ string ] string { "annotation1" : "val1" } ,
} ,
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
Title : "AlwaysFiring" ,
Condition : "A" ,
Data : [ ] ngmodels . AlertQuery {
{
RefID : "A" ,
RelativeTimeRange : ngmodels . RelativeTimeRange {
From : ngmodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : ngmodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
} ,
DatasourceUID : "unknown" ,
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 > 1"
} ` ) ,
} ,
} ,
} ,
} ,
2022-09-22 03:14:11 +08:00
expectedMessage : "invalid rule specification at index [0]: failed to validate condition of alert rule AlwaysFiring: failed to build query 'A': data source not found" ,
2021-04-28 16:31:51 +08:00
} ,
{
desc : "alert rule with invalid condition" ,
rulegroup : "arulegroup" ,
rule : apimodels . PostableExtendedRuleNode {
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 23:46:26 +08:00
For : & interval ,
2021-04-28 16:31:51 +08:00
Labels : map [ string ] string { "label1" : "val1" } ,
Annotations : map [ string ] string { "annotation1" : "val1" } ,
} ,
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
Title : "AlwaysFiring" ,
Condition : "B" ,
Data : [ ] ngmodels . AlertQuery {
{
RefID : "A" ,
RelativeTimeRange : ngmodels . RelativeTimeRange {
From : ngmodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : ngmodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
} ,
DatasourceUID : "-100" ,
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 > 1"
} ` ) ,
} ,
} ,
} ,
} ,
2022-09-22 03:14:11 +08:00
expectedMessage : "invalid rule specification at index [0]: failed to validate condition of alert rule AlwaysFiring: condition B does not exist, must be one of [A]" ,
2021-04-28 16:31:51 +08:00
} ,
2021-04-21 22:22:58 +08:00
}
for _ , tc := range testCases {
t . Run ( tc . desc , func ( t * testing . T ) {
rules := apimodels . PostableRuleGroupConfig {
Name : tc . rulegroup ,
Interval : tc . interval ,
Rules : [ ] apimodels . PostableExtendedRuleNode {
tc . rule ,
} ,
}
2022-06-21 23:39:22 +08:00
status , body := apiClient . PostRulesGroup ( t , "default" , & rules )
2022-05-05 16:37:26 +08:00
res := & Response { }
2022-06-21 23:39:22 +08:00
err = json . Unmarshal ( [ ] byte ( body ) , & res )
2022-05-05 16:37:26 +08:00
require . NoError ( t , err )
2022-09-22 03:14:11 +08:00
assert . Equal ( t , tc . expectedMessage , res . Message )
2022-05-05 16:37:26 +08:00
2022-06-21 23:39:22 +08:00
assert . Equal ( t , http . StatusBadRequest , status )
2021-04-21 22:22:58 +08:00
} )
}
}
var ruleUID string
var expectedGetNamespaceResponseBody string
2021-04-16 20:00:07 +08:00
// Now, let's create two alerts.
{
rules := apimodels . PostableRuleGroupConfig {
Name : "arulegroup" ,
Rules : [ ] apimodels . PostableExtendedRuleNode {
{
2021-04-21 22:22:58 +08:00
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 23:46:26 +08:00
For : & interval ,
2021-04-21 22:22:58 +08:00
Labels : map [ string ] string { "label1" : "val1" } ,
Annotations : map [ string ] string { "annotation1" : "val1" } ,
} ,
// this rule does not explicitly set no data and error states
// therefore it should get the default values
2021-04-16 20:00:07 +08:00
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
Title : "AlwaysFiring" ,
Condition : "A" ,
Data : [ ] ngmodels . AlertQuery {
{
RefID : "A" ,
RelativeTimeRange : ngmodels . RelativeTimeRange {
From : ngmodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : ngmodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
} ,
2021-04-23 22:52:32 +08:00
DatasourceUID : "-100" ,
2021-04-16 20:00:07 +08:00
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 > 1"
} ` ) ,
} ,
} ,
} ,
} ,
{
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
Title : "AlwaysFiringButSilenced" ,
Condition : "A" ,
Data : [ ] ngmodels . AlertQuery {
{
RefID : "A" ,
RelativeTimeRange : ngmodels . RelativeTimeRange {
From : ngmodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : ngmodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
} ,
2021-04-23 22:52:32 +08:00
DatasourceUID : "-100" ,
2021-04-16 20:00:07 +08:00
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 > 1"
} ` ) ,
} ,
} ,
2021-04-21 22:22:58 +08:00
NoDataState : apimodels . NoDataState ( ngmodels . Alerting ) ,
2021-05-19 01:55:43 +08:00
ExecErrState : apimodels . ExecutionErrorState ( ngmodels . AlertingErrState ) ,
2021-04-16 20:00:07 +08:00
} ,
} ,
} ,
}
2022-06-21 23:39:22 +08:00
status , body := apiClient . PostRulesGroup ( t , "default" , & rules )
assert . Equal ( t , http . StatusAccepted , status )
require . JSONEq ( t , ` { "message":"rule group updated successfully"} ` , body )
2021-04-16 20:00:07 +08:00
}
// With the rules created, let's make sure that rule definition is stored correctly.
{
2021-05-05 00:16:28 +08:00
u := fmt . Sprintf ( "http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default" , grafanaListedAddr )
2021-04-16 20:00:07 +08:00
// nolint:gosec
resp , err := http . Get ( u )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-04-16 20:00:07 +08:00
require . NoError ( t , err )
assert . Equal ( t , resp . StatusCode , 202 )
2021-04-21 22:22:58 +08:00
body , m := rulesNamespaceWithoutVariableValues ( t , b )
generatedUIDs , ok := m [ "default,arulegroup" ]
assert . True ( t , ok )
assert . Equal ( t , 2 , len ( generatedUIDs ) )
// assert that generated UIDs are unique
assert . NotEqual ( t , generatedUIDs [ 0 ] , generatedUIDs [ 1 ] )
// copy result to a variable with a wider scope
// to be used by the next test
ruleUID = generatedUIDs [ 0 ]
expectedGetNamespaceResponseBody = `
{
"default" : [
{
"name" : "arulegroup" ,
"interval" : "1m" ,
"rules" : [
{
"annotations" : {
"annotation1" : "val1"
} ,
"expr" : "" ,
"for" : "1m" ,
"labels" : {
"label1" : "val1"
} ,
"grafana_alert" : {
"id" : 1 ,
2021-05-05 00:16:28 +08:00
"orgId" : 1 ,
2021-04-21 22:22:58 +08:00
"title" : "AlwaysFiring" ,
"condition" : "A" ,
"data" : [
{
"refId" : "A" ,
"queryType" : "" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
2021-04-23 22:52:32 +08:00
"datasourceUid" : "-100" ,
2021-04-21 22:22:58 +08:00
"model" : {
"expression" : "2 + 3 \u003e 1" ,
"intervalMs" : 1000 ,
2021-05-18 00:46:52 +08:00
"maxDataPoints" : 43200 ,
2021-04-21 22:22:58 +08:00
"type" : "math"
}
}
] ,
"updated" : "2021-02-21T01:10:30Z" ,
"intervalSeconds" : 60 ,
"version" : 1 ,
"uid" : "uid" ,
"namespace_uid" : "nsuid" ,
"namespace_id" : 1 ,
"rule_group" : "arulegroup" ,
"no_data_state" : "NoData" ,
"exec_err_state" : "Alerting"
}
} ,
{
"expr" : "" ,
2022-06-30 23:46:26 +08:00
"for" : "0s" ,
2021-04-21 22:22:58 +08:00
"grafana_alert" : {
"id" : 2 ,
2021-05-05 00:16:28 +08:00
"orgId" : 1 ,
2021-04-21 22:22:58 +08:00
"title" : "AlwaysFiringButSilenced" ,
"condition" : "A" ,
"data" : [
{
"refId" : "A" ,
"queryType" : "" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
2021-04-23 22:52:32 +08:00
"datasourceUid" : "-100" ,
2021-04-21 22:22:58 +08:00
"model" : {
"expression" : "2 + 3 \u003e 1" ,
"intervalMs" : 1000 ,
2021-05-18 00:46:52 +08:00
"maxDataPoints" : 43200 ,
2021-04-21 22:22:58 +08:00
"type" : "math"
}
}
] ,
"updated" : "2021-02-21T01:10:30Z" ,
"intervalSeconds" : 60 ,
"version" : 1 ,
"uid" : "uid" ,
"namespace_uid" : "nsuid" ,
"namespace_id" : 1 ,
"rule_group" : "arulegroup" ,
"no_data_state" : "Alerting" ,
2021-05-19 01:55:43 +08:00
"exec_err_state" : "Alerting"
2021-04-21 22:22:58 +08:00
}
}
]
}
]
} `
assert . JSONEq ( t , expectedGetNamespaceResponseBody , body )
2021-04-16 20:00:07 +08:00
}
2021-04-21 22:22:58 +08:00
// try to update by pass an invalid UID
2021-04-16 20:00:07 +08:00
{
2021-04-21 22:22:58 +08:00
interval , err := model . ParseDuration ( "30s" )
2021-04-16 20:00:07 +08:00
require . NoError ( t , err )
2021-04-21 22:22:58 +08:00
rules := apimodels . PostableRuleGroupConfig {
Name : "arulegroup" ,
Rules : [ ] apimodels . PostableExtendedRuleNode {
{
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 23:46:26 +08:00
For : & interval ,
2021-04-21 22:22:58 +08:00
Labels : map [ string ] string {
"label1" : "val42" ,
"foo" : "bar" ,
} ,
Annotations : map [ string ] string {
"annotation1" : "val42" ,
"foo" : "bar" ,
} ,
} ,
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
UID : "unknown" ,
Title : "AlwaysNormal" ,
Condition : "A" ,
Data : [ ] ngmodels . AlertQuery {
{
RefID : "A" ,
RelativeTimeRange : ngmodels . RelativeTimeRange {
From : ngmodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : ngmodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
} ,
2021-04-23 22:52:32 +08:00
DatasourceUID : "-100" ,
2021-04-21 22:22:58 +08:00
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 < 1"
} ` ) ,
} ,
} ,
NoDataState : apimodels . NoDataState ( ngmodels . Alerting ) ,
2021-05-19 01:55:43 +08:00
ExecErrState : apimodels . ExecutionErrorState ( ngmodels . AlertingErrState ) ,
2021-04-21 22:22:58 +08:00
} ,
} ,
} ,
2021-09-03 00:38:42 +08:00
Interval : interval ,
2021-04-21 22:22:58 +08:00
}
2021-04-16 20:00:07 +08:00
2022-06-21 23:39:22 +08:00
status , body := apiClient . PostRulesGroup ( t , "default" , & rules )
assert . Equal ( t , http . StatusNotFound , status )
2022-04-14 23:54:49 +08:00
var res map [ string ] interface { }
2022-06-21 23:39:22 +08:00
assert . NoError ( t , json . Unmarshal ( [ ] byte ( body ) , & res ) )
2022-04-14 23:54:49 +08:00
require . Equal ( t , "failed to update rule group: failed to update rule with UID unknown because could not find alert rule" , res [ "message" ] )
2021-04-16 20:00:07 +08:00
2021-04-21 22:22:58 +08:00
// let's make sure that rule definitions are not affected by the failed POST request.
2022-06-21 23:39:22 +08:00
u := fmt . Sprintf ( "http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default" , grafanaListedAddr )
2021-04-21 22:22:58 +08:00
// nolint:gosec
2022-06-21 23:39:22 +08:00
resp , err := http . Get ( u )
2021-04-21 22:22:58 +08:00
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-04-16 20:00:07 +08:00
require . NoError ( t , err )
2021-04-21 22:22:58 +08:00
assert . Equal ( t , resp . StatusCode , 202 )
body , m := rulesNamespaceWithoutVariableValues ( t , b )
returnedUIDs , ok := m [ "default,arulegroup" ]
assert . True ( t , ok )
assert . Equal ( t , 2 , len ( returnedUIDs ) )
assert . JSONEq ( t , expectedGetNamespaceResponseBody , body )
}
2021-09-03 00:38:42 +08:00
// try to update by pass two rules with conflicting UIDs
{
interval , err := model . ParseDuration ( "30s" )
require . NoError ( t , err )
rules := apimodels . PostableRuleGroupConfig {
Name : "arulegroup" ,
Rules : [ ] apimodels . PostableExtendedRuleNode {
{
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 23:46:26 +08:00
For : & interval ,
2021-09-03 00:38:42 +08:00
Labels : map [ string ] string {
"label1" : "val42" ,
"foo" : "bar" ,
} ,
Annotations : map [ string ] string {
"annotation1" : "val42" ,
"foo" : "bar" ,
} ,
} ,
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
UID : ruleUID ,
Title : "AlwaysNormal" ,
Condition : "A" ,
Data : [ ] ngmodels . AlertQuery {
{
RefID : "A" ,
RelativeTimeRange : ngmodels . RelativeTimeRange {
From : ngmodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : ngmodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
} ,
DatasourceUID : "-100" ,
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 < 1"
} ` ) ,
} ,
} ,
NoDataState : apimodels . NoDataState ( ngmodels . Alerting ) ,
ExecErrState : apimodels . ExecutionErrorState ( ngmodels . AlertingErrState ) ,
} ,
} ,
{
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 23:46:26 +08:00
For : & interval ,
2021-09-03 00:38:42 +08:00
Labels : map [ string ] string {
"label1" : "val42" ,
"foo" : "bar" ,
} ,
Annotations : map [ string ] string {
"annotation1" : "val42" ,
"foo" : "bar" ,
} ,
} ,
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
UID : ruleUID ,
Title : "AlwaysAlerting" ,
Condition : "A" ,
Data : [ ] ngmodels . AlertQuery {
{
RefID : "A" ,
RelativeTimeRange : ngmodels . RelativeTimeRange {
From : ngmodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : ngmodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
} ,
DatasourceUID : "-100" ,
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 > 1"
} ` ) ,
} ,
} ,
NoDataState : apimodels . NoDataState ( ngmodels . Alerting ) ,
ExecErrState : apimodels . ExecutionErrorState ( ngmodels . AlertingErrState ) ,
} ,
} ,
} ,
Interval : interval ,
}
2022-06-21 23:39:22 +08:00
status , body := apiClient . PostRulesGroup ( t , "default" , & rules )
assert . Equal ( t , http . StatusBadRequest , status )
2022-04-14 23:54:49 +08:00
var res map [ string ] interface { }
2022-06-21 23:39:22 +08:00
require . NoError ( t , json . Unmarshal ( [ ] byte ( body ) , & res ) )
2022-04-14 23:54:49 +08:00
require . Equal ( t , fmt . Sprintf ( "rule [1] has UID %s that is already assigned to another rule at index 0" , ruleUID ) , res [ "message" ] )
2021-09-03 00:38:42 +08:00
// let's make sure that rule definitions are not affected by the failed POST request.
2022-06-21 23:39:22 +08:00
u := fmt . Sprintf ( "http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default" , grafanaListedAddr )
2021-09-03 00:38:42 +08:00
// nolint:gosec
2022-06-21 23:39:22 +08:00
resp , err := http . Get ( u )
2021-09-03 00:38:42 +08:00
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-09-03 00:38:42 +08:00
require . NoError ( t , err )
assert . Equal ( t , resp . StatusCode , 202 )
body , m := rulesNamespaceWithoutVariableValues ( t , b )
returnedUIDs , ok := m [ "default,arulegroup" ]
assert . True ( t , ok )
assert . Equal ( t , 2 , len ( returnedUIDs ) )
assert . JSONEq ( t , expectedGetNamespaceResponseBody , body )
}
2021-04-21 22:22:58 +08:00
// update the first rule and completely remove the other
{
2021-06-16 01:55:25 +08:00
forValue , err := model . ParseDuration ( "30s" )
2021-04-21 22:22:58 +08:00
require . NoError ( t , err )
rules := apimodels . PostableRuleGroupConfig {
Name : "arulegroup" ,
Rules : [ ] apimodels . PostableExtendedRuleNode {
{
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 23:46:26 +08:00
For : & forValue ,
2021-04-21 22:22:58 +08:00
Labels : map [ string ] string {
2021-06-16 01:55:25 +08:00
// delete foo label
"label1" : "val1" , // update label value
"label2" : "val2" , // new label
2021-04-21 22:22:58 +08:00
} ,
Annotations : map [ string ] string {
2021-06-16 01:55:25 +08:00
// delete foo annotation
"annotation1" : "val1" , // update annotation value
"annotation2" : "val2" , // new annotation
2021-04-21 22:22:58 +08:00
} ,
} ,
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
UID : ruleUID , // Including the UID in the payload makes the endpoint update the existing rule.
Title : "AlwaysNormal" ,
Condition : "A" ,
Data : [ ] ngmodels . AlertQuery {
{
RefID : "A" ,
RelativeTimeRange : ngmodels . RelativeTimeRange {
From : ngmodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : ngmodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
} ,
2021-04-23 22:52:32 +08:00
DatasourceUID : "-100" ,
2021-04-21 22:22:58 +08:00
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 < 1"
} ` ) ,
} ,
} ,
NoDataState : apimodels . NoDataState ( ngmodels . Alerting ) ,
2021-05-19 01:55:43 +08:00
ExecErrState : apimodels . ExecutionErrorState ( ngmodels . AlertingErrState ) ,
2021-04-21 22:22:58 +08:00
} ,
} ,
} ,
2021-06-16 01:55:25 +08:00
Interval : interval ,
2021-04-21 22:22:58 +08:00
}
2022-06-21 23:39:22 +08:00
status , body := apiClient . PostRulesGroup ( t , "default" , & rules )
assert . Equal ( t , http . StatusAccepted , status )
require . JSONEq ( t , ` { "message":"rule group updated successfully"} ` , body )
2021-04-21 22:22:58 +08:00
2022-06-21 23:39:22 +08:00
// let's make sure that rule definitions are updated correctly.
2021-05-05 00:16:28 +08:00
u := fmt . Sprintf ( "http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default" , grafanaListedAddr )
2021-04-21 22:22:58 +08:00
// nolint:gosec
2022-06-21 23:39:22 +08:00
resp , err := http . Get ( u )
2021-04-21 22:22:58 +08:00
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-04-21 22:22:58 +08:00
require . NoError ( t , err )
assert . Equal ( t , resp . StatusCode , 202 )
body , m := rulesNamespaceWithoutVariableValues ( t , b )
returnedUIDs , ok := m [ "default,arulegroup" ]
assert . True ( t , ok )
assert . Equal ( t , 1 , len ( returnedUIDs ) )
assert . Equal ( t , ruleUID , returnedUIDs [ 0 ] )
assert . JSONEq ( t , `
{
"default" : [
{
"name" : "arulegroup" ,
"interval" : "1m" ,
"rules" : [
{
"annotations" : {
2021-06-16 01:55:25 +08:00
"annotation1" : "val1" ,
"annotation2" : "val2"
2021-04-22 21:06:32 +08:00
} ,
2021-04-21 22:22:58 +08:00
"expr" : "" ,
"for" : "30s" ,
"labels" : {
2021-06-16 01:55:25 +08:00
"label1" : "val1" ,
"label2" : "val2"
2021-04-21 22:22:58 +08:00
} ,
"grafana_alert" : {
"id" : 1 ,
2021-05-05 00:16:28 +08:00
"orgId" : 1 ,
2021-04-21 22:22:58 +08:00
"title" : "AlwaysNormal" ,
"condition" : "A" ,
"data" : [
{
"refId" : "A" ,
"queryType" : "" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
2021-04-23 22:52:32 +08:00
"datasourceUid" : "-100" ,
"model" : {
2021-04-21 22:22:58 +08:00
"expression" : "2 + 3 \u003C 1" ,
"intervalMs" : 1000 ,
2021-05-18 00:46:52 +08:00
"maxDataPoints" : 43200 ,
2021-04-21 22:22:58 +08:00
"type" : "math"
}
}
] ,
"updated" : "2021-02-21T01:10:30Z" ,
"intervalSeconds" : 60 ,
"version" : 2 ,
"uid" : "uid" ,
"namespace_uid" : "nsuid" ,
"namespace_id" : 1 ,
"rule_group" : "arulegroup" ,
"no_data_state" : "Alerting" ,
2021-05-19 01:55:43 +08:00
"exec_err_state" : "Alerting"
2021-04-21 22:22:58 +08:00
}
}
]
}
]
} ` , body )
}
2021-06-16 01:55:25 +08:00
// update the rule; delete labels and annotations
{
forValue , err := model . ParseDuration ( "30s" )
require . NoError ( t , err )
rules := apimodels . PostableRuleGroupConfig {
Name : "arulegroup" ,
Rules : [ ] apimodels . PostableExtendedRuleNode {
{
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 23:46:26 +08:00
For : & forValue ,
2021-06-16 01:55:25 +08:00
} ,
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
UID : ruleUID , // Including the UID in the payload makes the endpoint update the existing rule.
Title : "AlwaysNormal" ,
Condition : "A" ,
Data : [ ] ngmodels . AlertQuery {
{
RefID : "A" ,
RelativeTimeRange : ngmodels . RelativeTimeRange {
From : ngmodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : ngmodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
} ,
DatasourceUID : "-100" ,
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 < 1"
} ` ) ,
} ,
} ,
NoDataState : apimodels . NoDataState ( ngmodels . Alerting ) ,
ExecErrState : apimodels . ExecutionErrorState ( ngmodels . AlertingErrState ) ,
} ,
} ,
} ,
Interval : interval ,
}
2022-06-21 23:39:22 +08:00
status , body := apiClient . PostRulesGroup ( t , "default" , & rules )
assert . Equal ( t , http . StatusAccepted , status )
require . JSONEq ( t , ` { "message":"rule group updated successfully"} ` , body )
2021-06-16 01:55:25 +08:00
2022-06-21 23:39:22 +08:00
// let's make sure that rule definitions are updated correctly.
2021-06-16 01:55:25 +08:00
u := fmt . Sprintf ( "http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default" , grafanaListedAddr )
// nolint:gosec
2022-06-21 23:39:22 +08:00
resp , err := http . Get ( u )
2021-06-16 01:55:25 +08:00
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-06-16 01:55:25 +08:00
require . NoError ( t , err )
assert . Equal ( t , resp . StatusCode , 202 )
body , m := rulesNamespaceWithoutVariableValues ( t , b )
returnedUIDs , ok := m [ "default,arulegroup" ]
assert . True ( t , ok )
assert . Equal ( t , 1 , len ( returnedUIDs ) )
assert . Equal ( t , ruleUID , returnedUIDs [ 0 ] )
assert . JSONEq ( t , `
{
"default" : [
{
"name" : "arulegroup" ,
"interval" : "1m" ,
"rules" : [
{
"expr" : "" ,
"for" : "30s" ,
"grafana_alert" : {
"id" : 1 ,
"orgId" : 1 ,
"title" : "AlwaysNormal" ,
"condition" : "A" ,
"data" : [
{
"refId" : "A" ,
"queryType" : "" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
"datasourceUid" : "-100" ,
"model" : {
"expression" : "2 + 3 \u003C 1" ,
"intervalMs" : 1000 ,
"maxDataPoints" : 43200 ,
"type" : "math"
}
}
] ,
"updated" : "2021-02-21T01:10:30Z" ,
"intervalSeconds" : 60 ,
"version" : 3 ,
"uid" : "uid" ,
"namespace_uid" : "nsuid" ,
"namespace_id" : 1 ,
"rule_group" : "arulegroup" ,
"no_data_state" : "Alerting" ,
"exec_err_state" : "Alerting"
}
}
]
}
]
} ` , body )
}
2022-03-05 05:16:33 +08:00
// update the rule; keep title, condition, no data state, error state, queries and expressions if not provided. should be noop
2021-06-16 01:55:25 +08:00
{
rules := apimodels . PostableRuleGroupConfig {
Name : "arulegroup" ,
Rules : [ ] apimodels . PostableExtendedRuleNode {
{
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
UID : ruleUID , // Including the UID in the payload makes the endpoint update the existing rule.
} ,
} ,
} ,
Interval : interval ,
}
2022-06-21 23:39:22 +08:00
status , body := apiClient . PostRulesGroup ( t , "default" , & rules )
assert . Equal ( t , http . StatusAccepted , status )
require . JSONEq ( t , ` { "message":"no changes detected in the rule group"} ` , body )
2021-06-16 01:55:25 +08:00
2022-06-21 23:39:22 +08:00
// let's make sure that rule definitions are updated correctly.
2021-06-16 01:55:25 +08:00
u := fmt . Sprintf ( "http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default" , grafanaListedAddr )
// nolint:gosec
2022-06-21 23:39:22 +08:00
resp , err := http . Get ( u )
2021-06-16 01:55:25 +08:00
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-06-16 01:55:25 +08:00
require . NoError ( t , err )
assert . Equal ( t , resp . StatusCode , 202 )
body , m := rulesNamespaceWithoutVariableValues ( t , b )
returnedUIDs , ok := m [ "default,arulegroup" ]
assert . True ( t , ok )
assert . Equal ( t , 1 , len ( returnedUIDs ) )
assert . Equal ( t , ruleUID , returnedUIDs [ 0 ] )
assert . JSONEq ( t , `
{
"default" : [
{
"name" : "arulegroup" ,
"interval" : "1m" ,
"rules" : [
{
"expr" : "" ,
2022-02-24 00:30:04 +08:00
"for" : "30s" ,
2021-06-16 01:55:25 +08:00
"grafana_alert" : {
"id" : 1 ,
"orgId" : 1 ,
"title" : "AlwaysNormal" ,
"condition" : "A" ,
"data" : [
{
"refId" : "A" ,
"queryType" : "" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
"datasourceUid" : "-100" ,
"model" : {
"expression" : "2 + 3 \u003C 1" ,
"intervalMs" : 1000 ,
"maxDataPoints" : 43200 ,
"type" : "math"
}
}
] ,
"updated" : "2021-02-21T01:10:30Z" ,
"intervalSeconds" : 60 ,
2022-03-05 05:16:33 +08:00
"version" : 3 ,
2021-06-16 01:55:25 +08:00
"uid" : "uid" ,
"namespace_uid" : "nsuid" ,
"namespace_id" : 1 ,
"rule_group" : "arulegroup" ,
"no_data_state" : "Alerting" ,
"exec_err_state" : "Alerting"
}
}
]
}
]
} ` , body )
}
2021-04-21 22:22:58 +08:00
client := & http . Client { }
// Finally, make sure we can delete it.
{
2022-03-26 00:39:24 +08:00
t . Run ( "succeed if the rule group name does not exists" , func ( t * testing . T ) {
2021-05-05 00:16:28 +08:00
u := fmt . Sprintf ( "http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default/groupnotexist" , grafanaListedAddr )
2021-04-21 22:22:58 +08:00
req , err := http . NewRequest ( http . MethodDelete , u , nil )
require . NoError ( t , err )
resp , err := client . Do ( req )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-04-21 22:22:58 +08:00
require . NoError ( t , err )
2022-03-26 00:39:24 +08:00
require . Equal ( t , http . StatusAccepted , resp . StatusCode )
2022-04-14 23:54:49 +08:00
var res map [ string ] interface { }
require . NoError ( t , json . Unmarshal ( b , & res ) )
require . Equal ( t , "rules deleted" , res [ "message" ] )
2021-04-21 22:22:58 +08:00
} )
t . Run ( "succeed if the rule group name does exist" , func ( t * testing . T ) {
2021-05-05 00:16:28 +08:00
u := fmt . Sprintf ( "http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default/arulegroup" , grafanaListedAddr )
2021-04-21 22:22:58 +08:00
req , err := http . NewRequest ( http . MethodDelete , u , nil )
require . NoError ( t , err )
resp , err := client . Do ( req )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-04-21 22:22:58 +08:00
require . NoError ( t , err )
require . Equal ( t , http . StatusAccepted , resp . StatusCode )
2022-03-26 00:39:24 +08:00
require . JSONEq ( t , ` { "message":"rules deleted"} ` , string ( b ) )
2021-04-21 22:22:58 +08:00
} )
2021-04-16 20:00:07 +08:00
}
2021-05-05 00:16:28 +08:00
}
2021-06-16 00:14:02 +08:00
func TestAlertmanagerStatus ( t * testing . T ) {
// Setup Grafana and its Database
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2021-09-29 22:16:40 +08:00
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
2022-02-09 17:26:06 +08:00
AppModeProduction : true ,
2021-06-16 00:14:02 +08:00
} )
2021-08-25 21:11:22 +08:00
grafanaListedAddr , _ := testinfra . StartGrafana ( t , dir , path )
2021-06-16 00:14:02 +08:00
// Get the Alertmanager current status.
{
alertsURL := fmt . Sprintf ( "http://%s/api/alertmanager/grafana/api/v2/status" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Get ( alertsURL )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-06-16 00:14:02 +08:00
require . NoError ( t , err )
require . Equal ( t , 200 , resp . StatusCode )
require . JSONEq ( t , `
{
"cluster" : {
"peers" : [ ] ,
"status" : "disabled"
} ,
"config" : {
"route" : {
2022-07-12 00:24:43 +08:00
"receiver" : "grafana-default-email" ,
"group_by" : [ "grafana_folder" , "alertname" ]
2021-06-16 00:14:02 +08:00
} ,
"templates" : null ,
"receivers" : [ {
"name" : "grafana-default-email" ,
"grafana_managed_receiver_configs" : [ {
"uid" : "" ,
"name" : "email receiver" ,
"type" : "email" ,
"disableResolveMessage" : false ,
"settings" : {
"addresses" : "\u003cexample@email.com\u003e"
} ,
"secureSettings" : null
} ]
} ]
} ,
"uptime" : null ,
"versionInfo" : {
"branch" : "N/A" ,
"buildDate" : "N/A" ,
"buildUser" : "N/A" ,
"goVersion" : "N/A" ,
"revision" : "N/A" ,
"version" : "N/A"
}
}
` , string ( b ) )
}
}
2021-05-05 00:16:28 +08:00
func TestQuota ( t * testing . T ) {
// Setup Grafana and its Database
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2021-09-29 22:16:40 +08:00
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
EnableQuota : true ,
DisableAnonymous : true ,
2022-02-09 17:26:06 +08:00
AppModeProduction : true ,
2021-05-05 00:16:28 +08:00
} )
2021-08-25 21:11:22 +08:00
grafanaListedAddr , store := testinfra . StartGrafana ( t , dir , path )
2021-05-05 00:16:28 +08:00
// Create a user to make authenticated requests
2022-06-28 20:32:25 +08:00
createUser ( t , store , user . CreateUserCommand {
2022-08-10 17:56:48 +08:00
DefaultOrgRole : string ( org . RoleEditor ) ,
2021-08-12 21:04:09 +08:00
Password : "password" ,
Login : "grafana" ,
} )
2022-06-21 23:39:22 +08:00
apiClient := newAlertingApiClient ( grafanaListedAddr , "grafana" , "password" )
2022-05-16 18:45:41 +08:00
// Create the namespace we'll save our alerts to.
2022-06-21 23:39:22 +08:00
apiClient . CreateFolder ( t , "default" , "default" )
2022-05-16 18:45:41 +08:00
2021-05-05 00:16:28 +08:00
interval , err := model . ParseDuration ( "1m" )
require . NoError ( t , err )
2021-09-03 00:38:42 +08:00
// Create rule under folder1
2022-06-21 23:39:22 +08:00
createRule ( t , apiClient , "default" )
2021-09-03 00:38:42 +08:00
// get the generated rule UID
var ruleUID string
{
u := fmt . Sprintf ( "http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Get ( u )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-09-03 00:38:42 +08:00
require . NoError ( t , err )
assert . Equal ( t , resp . StatusCode , 202 )
_ , m := rulesNamespaceWithoutVariableValues ( t , b )
generatedUIDs , ok := m [ "default,arulegroup" ]
assert . True ( t , ok )
assert . Equal ( t , 1 , len ( generatedUIDs ) )
ruleUID = generatedUIDs [ 0 ]
}
2021-05-05 00:16:28 +08:00
// check quota limits
2021-09-03 00:38:42 +08:00
t . Run ( "when quota limit exceed creating new rule should fail" , func ( t * testing . T ) {
2021-05-05 00:16:28 +08:00
// get existing org quota
2022-11-08 17:52:07 +08:00
query := models . GetOrgQuotaByTargetQuery { OrgId : 1 , Target : "alert_rule" }
err = store . GetOrgQuotaByTarget ( context . Background ( ) , & query )
require . NoError ( t , err )
used := query . Result . Used
limit := query . Result . Limit
// set org quota limit to equal used
orgCmd := models . UpdateOrgQuotaCmd {
OrgId : 1 ,
Target : "alert_rule" ,
Limit : used ,
}
err := store . UpdateOrgQuota ( context . Background ( ) , & orgCmd )
require . NoError ( t , err )
2021-05-05 00:16:28 +08:00
t . Cleanup ( func ( ) {
2022-11-08 17:52:07 +08:00
// reset org quota to original value
orgCmd := models . UpdateOrgQuotaCmd {
OrgId : 1 ,
Target : "alert_rule" ,
Limit : limit ,
}
err := store . UpdateOrgQuota ( context . Background ( ) , & orgCmd )
require . NoError ( t , err )
2021-05-05 00:16:28 +08:00
} )
// try to create an alert rule
rules := apimodels . PostableRuleGroupConfig {
Name : "arulegroup" ,
Interval : interval ,
Rules : [ ] apimodels . PostableExtendedRuleNode {
{
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
Title : "One more alert rule" ,
Condition : "A" ,
Data : [ ] ngmodels . AlertQuery {
{
RefID : "A" ,
RelativeTimeRange : ngmodels . RelativeTimeRange {
From : ngmodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : ngmodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
} ,
2021-09-03 00:38:42 +08:00
DatasourceUID : "-100" ,
2021-05-05 00:16:28 +08:00
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 > 1"
} ` ) ,
} ,
} ,
} ,
} ,
} ,
}
2022-06-21 23:39:22 +08:00
status , body := apiClient . PostRulesGroup ( t , "default" , & rules )
assert . Equal ( t , http . StatusForbidden , status )
2022-04-14 23:54:49 +08:00
var res map [ string ] interface { }
2022-06-21 23:39:22 +08:00
require . NoError ( t , json . Unmarshal ( [ ] byte ( body ) , & res ) )
2022-04-14 23:54:49 +08:00
require . Equal ( t , "quota has been exceeded" , res [ "message" ] )
2021-05-05 00:16:28 +08:00
} )
2021-09-03 00:38:42 +08:00
t . Run ( "when quota limit exceed updating existing rule should succeed" , func ( t * testing . T ) {
// try to create an alert rule
rules := apimodels . PostableRuleGroupConfig {
Name : "arulegroup" ,
Interval : interval ,
Rules : [ ] apimodels . PostableExtendedRuleNode {
{
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
Title : "Updated alert rule" ,
Condition : "A" ,
Data : [ ] ngmodels . AlertQuery {
{
RefID : "A" ,
RelativeTimeRange : ngmodels . RelativeTimeRange {
From : ngmodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : ngmodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
} ,
DatasourceUID : "-100" ,
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 4 > 1"
} ` ) ,
} ,
} ,
UID : ruleUID ,
} ,
} ,
} ,
}
2022-06-21 23:39:22 +08:00
status , body := apiClient . PostRulesGroup ( t , "default" , & rules )
assert . Equal ( t , http . StatusAccepted , status )
require . JSONEq ( t , ` { "message":"rule group updated successfully"} ` , body )
2021-09-03 00:38:42 +08:00
// let's make sure that rule definitions are updated correctly.
2022-06-21 23:39:22 +08:00
u := fmt . Sprintf ( "http://grafana:password@%s/api/ruler/grafana/api/v1/rules/default" , grafanaListedAddr )
2021-09-03 00:38:42 +08:00
// nolint:gosec
2022-06-21 23:39:22 +08:00
resp , err := http . Get ( u )
2021-09-03 00:38:42 +08:00
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-09-03 00:38:42 +08:00
require . NoError ( t , err )
assert . Equal ( t , resp . StatusCode , 202 )
body , m := rulesNamespaceWithoutVariableValues ( t , b )
returnedUIDs , ok := m [ "default,arulegroup" ]
assert . True ( t , ok )
assert . Equal ( t , 1 , len ( returnedUIDs ) )
assert . Equal ( t , ruleUID , returnedUIDs [ 0 ] )
assert . JSONEq ( t , `
{
"default" : [
{
"name" : "arulegroup" ,
"interval" : "1m" ,
"rules" : [
{
"expr" : "" ,
2022-02-24 00:30:04 +08:00
"for" : "2m" ,
2021-09-03 00:38:42 +08:00
"grafana_alert" : {
"id" : 1 ,
"orgId" : 1 ,
"title" : "Updated alert rule" ,
"condition" : "A" ,
"data" : [
{
"refId" : "A" ,
"queryType" : "" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
"datasourceUid" : "-100" ,
"model" : {
"expression" : "2 + 4 \u003E 1" ,
"intervalMs" : 1000 ,
"maxDataPoints" : 43200 ,
"type" : "math"
}
}
] ,
"updated" : "2021-02-21T01:10:30Z" ,
"intervalSeconds" : 60 ,
"version" : 2 ,
"uid" : "uid" ,
"namespace_uid" : "nsuid" ,
"namespace_id" : 1 ,
"rule_group" : "arulegroup" ,
"no_data_state" : "NoData" ,
"exec_err_state" : "Alerting"
}
}
]
}
]
} ` , body )
} )
2021-05-05 00:16:28 +08:00
}
func TestEval ( t * testing . T ) {
// Setup Grafana and its Database
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2021-09-29 22:16:40 +08:00
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
EnableQuota : true ,
DisableAnonymous : true ,
2022-02-09 17:26:06 +08:00
AppModeProduction : true ,
2021-05-05 00:16:28 +08:00
} )
2021-08-25 21:11:22 +08:00
grafanaListedAddr , store := testinfra . StartGrafana ( t , dir , path )
2021-05-05 00:16:28 +08:00
2022-06-28 20:32:25 +08:00
createUser ( t , store , user . CreateUserCommand {
2022-08-10 17:56:48 +08:00
DefaultOrgRole : string ( org . RoleEditor ) ,
2021-08-12 21:04:09 +08:00
Password : "password" ,
Login : "grafana" ,
} )
2022-06-21 23:39:22 +08:00
apiClient := newAlertingApiClient ( grafanaListedAddr , "grafana" , "password" )
2021-05-05 00:16:28 +08:00
// Create the namespace we'll save our alerts to.
2022-06-21 23:39:22 +08:00
apiClient . CreateFolder ( t , "default" , "default" )
2021-04-22 03:44:50 +08:00
// test eval conditions
testCases := [ ] struct {
desc string
payload string
2022-05-25 19:43:58 +08:00
expectedStatusCode func ( ) int
expectedResponse func ( ) string
expectedMessage func ( ) string
2021-04-22 03:44:50 +08:00
} {
{
desc : "alerting condition" ,
payload : `
{
"grafana_condition" : {
"condition" : "A" ,
"data" : [
{
"refId" : "A" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
2021-04-23 22:52:32 +08:00
"datasourceUid" : "-100" ,
2021-04-22 03:44:50 +08:00
"model" : {
"type" : "math" ,
"expression" : "1 < 2"
}
}
] ,
"now" : "2021-04-11T14:38:14Z"
}
}
` ,
2022-05-25 19:43:58 +08:00
expectedMessage : func ( ) string { return "" } ,
expectedStatusCode : func ( ) int { return http . StatusOK } ,
expectedResponse : func ( ) string {
return ` {
2021-04-22 03:44:50 +08:00
"instances" : [
{
"schema" : {
"name" : "evaluation results" ,
"fields" : [
{
"name" : "State" ,
"type" : "string" ,
"typeInfo" : {
"frame" : "string"
}
2021-05-26 16:06:28 +08:00
} ,
{
"name" : "Info" ,
"type" : "string" ,
"typeInfo" : {
"frame" : "string"
}
2021-04-22 03:44:50 +08:00
}
]
} ,
"data" : {
"values" : [
[
"Alerting"
2021-05-26 16:06:28 +08:00
] ,
[
2021-05-28 23:04:20 +08:00
"[ var='A' labels={} value=1 ]"
2021-04-22 03:44:50 +08:00
]
]
}
}
]
2022-05-25 19:43:58 +08:00
} `
} ,
2021-04-22 03:44:50 +08:00
} ,
{
desc : "normal condition" ,
payload : `
{
"grafana_condition" : {
"condition" : "A" ,
"data" : [
{
"refId" : "A" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
2021-04-23 22:52:32 +08:00
"datasourceUid" : "-100" ,
2021-04-22 03:44:50 +08:00
"model" : {
"type" : "math" ,
"expression" : "1 > 2"
}
}
] ,
"now" : "2021-04-11T14:38:14Z"
}
}
` ,
2022-05-25 19:43:58 +08:00
expectedMessage : func ( ) string { return "" } ,
expectedStatusCode : func ( ) int { return http . StatusOK } ,
expectedResponse : func ( ) string {
return ` {
2021-04-22 03:44:50 +08:00
"instances" : [
{
"schema" : {
"name" : "evaluation results" ,
"fields" : [
{
"name" : "State" ,
"type" : "string" ,
"typeInfo" : {
"frame" : "string"
}
2021-05-26 16:06:28 +08:00
} ,
{
"name" : "Info" ,
"type" : "string" ,
"typeInfo" : {
"frame" : "string"
}
2021-04-22 03:44:50 +08:00
}
]
} ,
"data" : {
"values" : [
[
"Normal"
2021-05-26 16:06:28 +08:00
] ,
[
2021-05-28 23:04:20 +08:00
"[ var='A' labels={} value=0 ]"
2021-04-22 03:44:50 +08:00
]
]
}
}
]
2022-05-25 19:43:58 +08:00
} `
} ,
2021-04-22 03:44:50 +08:00
} ,
{
desc : "condition not found in any query or expression" ,
payload : `
{
"grafana_condition" : {
"condition" : "B" ,
"data" : [
{
"refId" : "A" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
2021-04-23 22:52:32 +08:00
"datasourceUid" : "-100" ,
2021-04-22 03:44:50 +08:00
"model" : {
"type" : "math" ,
"expression" : "1 > 2"
}
}
] ,
"now" : "2021-04-11T14:38:14Z"
}
}
` ,
2022-05-25 19:43:58 +08:00
expectedStatusCode : func ( ) int { return http . StatusBadRequest } ,
expectedMessage : func ( ) string {
2022-09-22 03:14:11 +08:00
return "invalid condition: condition B does not exist, must be one of [A]"
2022-05-25 19:43:58 +08:00
} ,
expectedResponse : func ( ) string { return "" } ,
2021-04-22 03:44:50 +08:00
} ,
{
desc : "unknown query datasource" ,
payload : `
{
"grafana_condition" : {
"condition" : "A" ,
"data" : [
{
"refId" : "A" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
2021-04-23 22:52:32 +08:00
"datasourceUid" : "unknown" ,
2021-04-22 03:44:50 +08:00
"model" : {
}
}
] ,
"now" : "2021-04-11T14:38:14Z"
}
}
` ,
2022-05-25 19:43:58 +08:00
expectedStatusCode : func ( ) int {
if setting . IsEnterprise {
return http . StatusUnauthorized
}
return http . StatusBadRequest
} ,
expectedMessage : func ( ) string {
if setting . IsEnterprise {
return "user is not authorized to query one or many data sources used by the rule"
}
2022-09-22 03:14:11 +08:00
return "invalid condition: failed to build query 'A': data source not found"
2022-05-25 19:43:58 +08:00
} ,
expectedResponse : func ( ) string { return "" } ,
2021-04-22 03:44:50 +08:00
} ,
}
for _ , tc := range testCases {
t . Run ( tc . desc , func ( t * testing . T ) {
2021-05-05 00:16:28 +08:00
u := fmt . Sprintf ( "http://grafana:password@%s/api/v1/rule/test/grafana" , grafanaListedAddr )
2021-04-22 03:44:50 +08:00
r := strings . NewReader ( tc . payload )
// nolint:gosec
resp , err := http . Post ( u , "application/json" , r )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-04-22 03:44:50 +08:00
require . NoError ( t , err )
2022-05-05 16:37:26 +08:00
res := Response { }
err = json . Unmarshal ( b , & res )
require . NoError ( t , err )
2021-04-22 03:44:50 +08:00
2022-05-25 19:43:58 +08:00
assert . Equal ( t , tc . expectedStatusCode ( ) , resp . StatusCode )
if tc . expectedResponse ( ) != "" {
require . JSONEq ( t , tc . expectedResponse ( ) , string ( b ) )
2022-05-05 16:37:26 +08:00
}
2022-05-25 19:43:58 +08:00
if tc . expectedMessage ( ) != "" {
assert . Equal ( t , tc . expectedMessage ( ) , res . Message )
2022-05-05 16:37:26 +08:00
}
2021-04-22 03:44:50 +08:00
} )
}
// test eval queries and expressions
testCases = [ ] struct {
desc string
payload string
2022-05-25 19:43:58 +08:00
expectedStatusCode func ( ) int
expectedResponse func ( ) string
expectedMessage func ( ) string
2021-04-22 03:44:50 +08:00
} {
{
desc : "alerting condition" ,
payload : `
{
"data" : [
{
"refId" : "A" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
2021-04-23 22:52:32 +08:00
"datasourceUid" : "-100" ,
2021-04-22 03:44:50 +08:00
"model" : {
"type" : "math" ,
"expression" : "1 < 2"
}
}
] ,
"now" : "2021-04-11T14:38:14Z"
}
` ,
2022-05-25 19:43:58 +08:00
expectedMessage : func ( ) string { return "" } ,
expectedStatusCode : func ( ) int { return http . StatusOK } ,
expectedResponse : func ( ) string {
return ` {
2021-04-22 03:44:50 +08:00
"results" : {
"A" : {
2022-11-04 01:34:27 +08:00
"status" : 200 ,
2021-04-22 03:44:50 +08:00
"frames" : [
{
"schema" : {
"refId" : "A" ,
"fields" : [
{
"name" : "A" ,
"type" : "number" ,
"typeInfo" : {
"frame" : "float64" ,
"nullable" : true
}
}
]
} ,
"data" : {
"values" : [
[
1
]
]
}
}
]
}
}
2022-05-25 19:43:58 +08:00
} `
} ,
2021-04-22 03:44:50 +08:00
} ,
{
desc : "normal condition" ,
payload : `
{
"data" : [
{
"refId" : "A" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
2021-04-23 22:52:32 +08:00
"datasourceUid" : "-100" ,
2021-04-22 03:44:50 +08:00
"model" : {
"type" : "math" ,
"expression" : "1 > 2"
}
}
] ,
"now" : "2021-04-11T14:38:14Z"
}
` ,
2022-05-25 19:43:58 +08:00
expectedMessage : func ( ) string { return "" } ,
expectedStatusCode : func ( ) int { return http . StatusOK } ,
expectedResponse : func ( ) string {
return ` {
2021-04-22 03:44:50 +08:00
"results" : {
"A" : {
2022-11-04 01:34:27 +08:00
"status" : 200 ,
2021-04-22 03:44:50 +08:00
"frames" : [
{
"schema" : {
"refId" : "A" ,
"fields" : [
{
"name" : "A" ,
"type" : "number" ,
"typeInfo" : {
"frame" : "float64" ,
"nullable" : true
}
}
]
} ,
"data" : {
"values" : [
[
0
]
]
}
}
]
}
}
2022-05-25 19:43:58 +08:00
} `
} ,
2021-04-22 03:44:50 +08:00
} ,
{
desc : "unknown query datasource" ,
payload : `
{
"data" : [
{
"refId" : "A" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
2021-04-23 22:52:32 +08:00
"datasourceUid" : "unknown" ,
2021-04-22 03:44:50 +08:00
"model" : {
}
}
] ,
"now" : "2021-04-11T14:38:14Z"
}
` ,
2022-05-25 19:43:58 +08:00
expectedResponse : func ( ) string { return "" } ,
expectedStatusCode : func ( ) int {
if setting . IsEnterprise {
return http . StatusUnauthorized
}
return http . StatusBadRequest
} ,
expectedMessage : func ( ) string {
if setting . IsEnterprise {
return "user is not authorized to query one or many data sources used by the rule"
}
2022-11-02 22:13:39 +08:00
return "Failed to build evaluator for queries and expressions: failed to build query 'A': data source not found"
2022-05-25 19:43:58 +08:00
} ,
2021-04-22 03:44:50 +08:00
} ,
}
for _ , tc := range testCases {
t . Run ( tc . desc , func ( t * testing . T ) {
2021-05-05 00:16:28 +08:00
u := fmt . Sprintf ( "http://grafana:password@%s/api/v1/eval" , grafanaListedAddr )
2021-04-22 03:44:50 +08:00
r := strings . NewReader ( tc . payload )
// nolint:gosec
resp , err := http . Post ( u , "application/json" , r )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 21:37:51 +08:00
b , err := io . ReadAll ( resp . Body )
2021-04-22 03:44:50 +08:00
require . NoError ( t , err )
2022-05-05 16:37:26 +08:00
res := Response { }
err = json . Unmarshal ( b , & res )
require . NoError ( t , err )
2021-04-22 03:44:50 +08:00
2022-05-25 19:43:58 +08:00
assert . Equal ( t , tc . expectedStatusCode ( ) , resp . StatusCode )
if tc . expectedResponse ( ) != "" {
require . JSONEq ( t , tc . expectedResponse ( ) , string ( b ) )
2022-05-05 16:37:26 +08:00
}
2022-05-25 19:43:58 +08:00
if tc . expectedMessage ( ) != "" {
require . Equal ( t , tc . expectedMessage ( ) , res . Message )
2022-05-05 16:37:26 +08:00
}
2021-04-22 03:44:50 +08:00
} )
}
2021-04-16 20:00:07 +08:00
}
// rulesNamespaceWithoutVariableValues takes a apimodels.NamespaceConfigResponse JSON-based input and makes the dynamic fields static e.g. uid, dates, etc.
2021-04-21 22:22:58 +08:00
// it returns a map of the modified rule UIDs with the namespace,rule_group as a key
func rulesNamespaceWithoutVariableValues ( t * testing . T , b [ ] byte ) ( string , map [ string ] [ ] string ) {
2021-04-16 20:00:07 +08:00
t . Helper ( )
var r apimodels . NamespaceConfigResponse
require . NoError ( t , json . Unmarshal ( b , & r ) )
2021-04-21 22:22:58 +08:00
// create a map holding the created rule UIDs per namespace/group
m := make ( map [ string ] [ ] string )
for namespace , nodes := range r {
2021-04-16 20:00:07 +08:00
for _ , node := range nodes {
2021-04-21 22:22:58 +08:00
compositeKey := strings . Join ( [ ] string { namespace , node . Name } , "," )
_ , ok := m [ compositeKey ]
if ! ok {
m [ compositeKey ] = make ( [ ] string , 0 , len ( node . Rules ) )
}
2021-04-16 20:00:07 +08:00
for _ , rule := range node . Rules {
2021-04-21 22:22:58 +08:00
m [ compositeKey ] = append ( m [ compositeKey ] , rule . GrafanaManagedAlert . UID )
2021-04-16 20:00:07 +08:00
rule . GrafanaManagedAlert . UID = "uid"
rule . GrafanaManagedAlert . NamespaceUID = "nsuid"
rule . GrafanaManagedAlert . Updated = time . Date ( 2021 , time . Month ( 2 ) , 21 , 1 , 10 , 30 , 0 , time . UTC )
}
}
}
json , err := json . Marshal ( & r )
require . NoError ( t , err )
2021-04-21 22:22:58 +08:00
return string ( json ) , m
}
2022-06-28 20:32:25 +08:00
func createUser ( t * testing . T , store * sqlstore . SQLStore , cmd user . CreateUserCommand ) int64 {
2021-05-05 00:16:28 +08:00
t . Helper ( )
2022-06-07 21:49:18 +08:00
store . Cfg . AutoAssignOrg = true
store . Cfg . AutoAssignOrgId = 1
2021-08-12 21:04:09 +08:00
u , err := store . CreateUser ( context . Background ( ) , cmd )
require . NoError ( t , err )
2022-06-28 20:32:25 +08:00
return u . ID
2021-08-12 21:04:09 +08:00
}
func createOrg ( t * testing . T , store * sqlstore . SQLStore , name string , userID int64 ) int64 {
org , err := store . CreateOrgWithMember ( name , userID )
require . NoError ( t , err )
return org . Id
2021-05-05 00:16:28 +08:00
}
func getLongString ( t * testing . T , n int ) string {
t . Helper ( )
2021-04-21 22:22:58 +08:00
b := make ( [ ] rune , n )
for i := range b {
b [ i ] = 'a'
}
return string ( b )
2021-04-16 20:00:07 +08:00
}