2021-04-29 11:15:15 -05:00
package alerting
import (
"encoding/json"
"fmt"
2022-08-10 08:37:51 -05:00
"io"
2022-06-22 09:52:46 -05:00
"math/rand"
2021-04-29 11:15:15 -05:00
"net/http"
"testing"
"time"
2023-01-31 11:50:10 -06:00
"github.com/grafana/grafana/pkg/expr"
2022-02-23 10:30:04 -06:00
"github.com/prometheus/common/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
2022-08-18 02:43:45 -05:00
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
2021-04-29 11:15:15 -05:00
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
2022-08-10 04:56:48 -05:00
"github.com/grafana/grafana/pkg/services/org"
2022-06-28 07:32:25 -05:00
"github.com/grafana/grafana/pkg/services/user"
2021-04-29 11:15:15 -05:00
"github.com/grafana/grafana/pkg/tests/testinfra"
2022-06-22 09:52:46 -05:00
"github.com/grafana/grafana/pkg/util"
2021-04-29 11:15:15 -05:00
)
2022-12-09 01:11:56 -06:00
func TestIntegrationAlertRulePermissions ( t * testing . T ) {
testinfra . SQLiteIntegrationTest ( t )
2021-04-29 11:15:15 -05:00
// Setup Grafana and its Database
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2021-09-29 09:16:40 -05:00
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
DisableAnonymous : true ,
2022-02-09 03:26:06 -06:00
AppModeProduction : true ,
2021-04-29 11:15:15 -05:00
} )
2021-08-25 08:11:22 -05:00
grafanaListedAddr , store := testinfra . StartGrafana ( t , dir , path )
2022-08-18 02:43:45 -05:00
permissionsStore := resourcepermissions . NewStore ( store )
2021-04-29 11:15:15 -05:00
2021-05-04 11:16:28 -05:00
// Create a user to make authenticated requests
2022-06-28 07:32:25 -05:00
userID := createUser ( t , store , user . CreateUserCommand {
2022-08-10 04:56:48 -05:00
DefaultOrgRole : string ( org . RoleEditor ) ,
2021-08-12 08:04:09 -05:00
Password : "password" ,
Login : "grafana" ,
} )
2021-05-04 11:16:28 -05:00
2022-06-21 10:39:22 -05:00
apiClient := newAlertingApiClient ( grafanaListedAddr , "grafana" , "password" )
2021-04-29 11:15:15 -05:00
// Create the namespace we'll save our alerts to.
2022-06-21 10:39:22 -05:00
apiClient . CreateFolder ( t , "folder1" , "folder1" )
// Create the namespace we'll save our alerts to.
apiClient . CreateFolder ( t , "folder2" , "folder2" )
2021-04-29 11:15:15 -05:00
// Create rule under folder1
2022-06-21 10:39:22 -05:00
createRule ( t , apiClient , "folder1" )
2021-04-29 11:15:15 -05:00
// Create rule under folder2
2022-06-21 10:39:22 -05:00
createRule ( t , apiClient , "folder2" )
2021-04-29 11:15:15 -05:00
// With the rules created, let's make sure that rule definitions are stored.
{
2021-05-04 11:16:28 -05:00
u := fmt . Sprintf ( "http://grafana:password@%s/api/ruler/grafana/api/v1/rules" , grafanaListedAddr )
2021-04-29 11:15:15 -05: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 08:37:51 -05:00
b , err := io . ReadAll ( resp . Body )
2021-04-29 11:15:15 -05:00
require . NoError ( t , err )
2021-10-04 10:33:55 -05:00
assert . Equal ( t , resp . StatusCode , 200 )
2021-04-29 11:15:15 -05:00
body , _ := rulesNamespaceWithoutVariableValues ( t , b )
expectedGetNamespaceResponseBody := `
{
"folder1" : [
{
"name" : "arulegroup" ,
"interval" : "1m" ,
"rules" : [
{
"annotations" : {
"annotation1" : "val1"
} ,
"expr" : "" ,
"for" : "2m" ,
"labels" : {
"label1" : "val1"
} ,
"grafana_alert" : {
"id" : 1 ,
2021-05-04 11:16:28 -05:00
"orgId" : 1 ,
2021-04-29 11:15:15 -05:00
"title" : "rule under folder folder1" ,
"condition" : "A" ,
"data" : [
{
"refId" : "A" ,
"queryType" : "" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
2023-01-31 11:50:10 -06:00
"datasourceUid" : "__expr__" ,
2021-04-29 11:15:15 -05:00
"model" : {
"expression" : "2 + 3 \u003E 1" ,
"intervalMs" : 1000 ,
2021-05-17 11:46:52 -05:00
"maxDataPoints" : 43200 ,
2021-04-29 11:15:15 -05: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"
}
}
]
}
] ,
"folder2" : [
{
"name" : "arulegroup" ,
"interval" : "1m" ,
"rules" : [
{
"annotations" : {
"annotation1" : "val1"
} ,
"expr" : "" ,
"for" : "2m" ,
"labels" : {
"label1" : "val1"
} ,
"grafana_alert" : {
"id" : 2 ,
2021-05-04 11:16:28 -05:00
"orgId" : 1 ,
2021-04-29 11:15:15 -05:00
"title" : "rule under folder folder2" ,
"condition" : "A" ,
"data" : [
{
"refId" : "A" ,
"queryType" : "" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
2023-01-31 11:50:10 -06:00
"datasourceUid" : "__expr__" ,
2021-04-29 11:15:15 -05:00
"model" : {
"expression" : "2 + 3 \u003E 1" ,
"intervalMs" : 1000 ,
2021-05-17 11:46:52 -05:00
"maxDataPoints" : 43200 ,
2021-04-29 11:15:15 -05:00
"type" : "math"
}
}
] ,
"updated" : "2021-02-21T01:10:30Z" ,
"intervalSeconds" : 60 ,
"version" : 1 ,
"uid" : "uid" ,
"namespace_uid" : "nsuid" ,
"namespace_id" : 2 ,
"rule_group" : "arulegroup" ,
"no_data_state" : "NoData" ,
"exec_err_state" : "Alerting"
}
}
]
}
]
} `
assert . JSONEq ( t , expectedGetNamespaceResponseBody , body )
// remove permissions from folder2
2022-08-10 04:56:48 -05:00
removeFolderPermission ( t , permissionsStore , 1 , userID , org . RoleEditor , "folder2" )
2022-06-27 08:31:49 -05:00
apiClient . ReloadCachedPermissions ( t )
2021-04-29 11:15:15 -05:00
// make sure that folder2 is not included in the response
// 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 08:37:51 -05:00
b , err = io . ReadAll ( resp . Body )
2021-04-29 11:15:15 -05:00
require . NoError ( t , err )
2021-10-04 10:33:55 -05:00
assert . Equal ( t , resp . StatusCode , 200 )
2021-04-29 11:15:15 -05:00
body , _ = rulesNamespaceWithoutVariableValues ( t , b )
expectedGetNamespaceResponseBody = `
{
"folder1" : [
{
"name" : "arulegroup" ,
"interval" : "1m" ,
"rules" : [
{
"annotations" : {
"annotation1" : "val1"
} ,
"expr" : "" ,
"for" : "2m" ,
"labels" : {
"label1" : "val1"
} ,
"grafana_alert" : {
"id" : 1 ,
2021-05-04 11:16:28 -05:00
"orgId" : 1 ,
2021-04-29 11:15:15 -05:00
"title" : "rule under folder folder1" ,
"condition" : "A" ,
"data" : [
{
"refId" : "A" ,
"queryType" : "" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
2023-01-31 11:50:10 -06:00
"datasourceUid" : "__expr__" ,
2021-04-29 11:15:15 -05:00
"model" : {
"expression" : "2 + 3 \u003E 1" ,
"intervalMs" : 1000 ,
2021-05-17 11:46:52 -05:00
"maxDataPoints" : 43200 ,
2021-04-29 11:15:15 -05: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"
}
}
]
}
]
} `
assert . JSONEq ( t , expectedGetNamespaceResponseBody , body )
}
2021-11-08 07:26:08 -06:00
2022-05-16 05:45:41 -05:00
// Remove permissions from folder1.
2022-08-10 04:56:48 -05:00
removeFolderPermission ( t , permissionsStore , 1 , userID , org . RoleEditor , "folder1" )
2022-06-27 08:31:49 -05:00
apiClient . ReloadCachedPermissions ( t )
2021-11-08 07:26:08 -06:00
{
u := fmt . Sprintf ( "http://grafana:password@%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 08:37:51 -05:00
b , err := io . ReadAll ( resp . Body )
2021-11-08 07:26:08 -06:00
require . NoError ( t , err )
assert . Equal ( t , resp . StatusCode , 200 )
require . JSONEq ( t , ` { } ` , string ( b ) )
}
2021-04-29 11:15:15 -05:00
}
2022-06-21 10:39:22 -05:00
func createRule ( t * testing . T , client apiClient , folder string ) {
2021-04-29 11:15:15 -05:00
t . Helper ( )
interval , err := model . ParseDuration ( "1m" )
require . NoError ( t , err )
2022-06-30 10:46:26 -05:00
doubleInterval := 2 * interval
2021-04-29 11:15:15 -05:00
rules := apimodels . PostableRuleGroupConfig {
Name : "arulegroup" ,
Interval : interval ,
Rules : [ ] apimodels . PostableExtendedRuleNode {
{
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 10:46:26 -05:00
For : & doubleInterval ,
2021-04-29 11:15:15 -05:00
Labels : map [ string ] string { "label1" : "val1" } ,
Annotations : map [ string ] string { "annotation1" : "val1" } ,
} ,
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
Title : fmt . Sprintf ( "rule under folder %s" , folder ) ,
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 ) ,
} ,
2023-01-31 11:50:10 -06:00
DatasourceUID : expr . DatasourceUID ,
2021-04-29 11:15:15 -05:00
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 > 1"
} ` ) ,
} ,
} ,
} ,
} ,
} ,
}
2022-06-21 10:39:22 -05:00
status , body := client . PostRulesGroup ( t , folder , & rules )
assert . Equal ( t , http . StatusAccepted , status )
require . JSONEq ( t , ` { "message":"rule group updated successfully"} ` , body )
2021-04-29 11:15:15 -05:00
}
2021-06-04 12:45:26 -05:00
2022-12-09 01:11:56 -06:00
func TestIntegrationAlertRuleConflictingTitle ( t * testing . T ) {
testinfra . SQLiteIntegrationTest ( t )
2021-06-04 12:45:26 -05:00
// Setup Grafana and its Database
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2021-09-29 09:16:40 -05:00
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
EnableQuota : true ,
DisableAnonymous : true ,
ViewersCanEdit : true ,
2022-02-09 03:26:06 -06:00
AppModeProduction : true ,
2021-06-04 12:45:26 -05:00
} )
2021-08-25 08:11:22 -05:00
grafanaListedAddr , store := testinfra . StartGrafana ( t , dir , path )
2021-06-04 12:45:26 -05:00
// Create user
2022-06-28 07:32:25 -05:00
createUser ( t , store , user . CreateUserCommand {
2022-08-10 04:56:48 -05:00
DefaultOrgRole : string ( org . RoleAdmin ) ,
2021-08-12 08:04:09 -05:00
Password : "admin" ,
Login : "admin" ,
} )
2021-06-04 12:45:26 -05:00
2022-06-21 10:39:22 -05:00
apiClient := newAlertingApiClient ( grafanaListedAddr , "admin" , "admin" )
2022-05-16 05:45:41 -05:00
// Create the namespace we'll save our alerts to.
2022-06-21 10:39:22 -05:00
apiClient . CreateFolder ( t , "folder1" , "folder1" )
2022-05-16 05:45:41 -05:00
// Create the namespace we'll save our alerts to.
2022-06-21 10:39:22 -05:00
apiClient . CreateFolder ( t , "folder2" , "folder2" )
2022-05-16 05:45:41 -05:00
2022-04-14 07:21:36 -05:00
rules := newTestingRuleConfig ( t )
2021-06-04 12:45:26 -05:00
2022-06-21 10:39:22 -05:00
status , body := apiClient . PostRulesGroup ( t , "folder1" , & rules )
assert . Equal ( t , http . StatusAccepted , status )
require . JSONEq ( t , ` { "message":"rule group updated successfully"} ` , body )
2021-06-04 12:45:26 -05:00
2022-04-14 07:21:36 -05:00
// fetch the created rules, so we can get the uid's and trigger
// and update by reusing the uid's
2022-06-21 10:39:22 -05:00
createdRuleGroup := apiClient . GetRulesGroup ( t , "folder1" , rules . Name ) . GettableRuleGroupConfig
2022-04-14 07:21:36 -05:00
require . Len ( t , createdRuleGroup . Rules , 2 )
2021-06-04 12:45:26 -05:00
t . Run ( "trying to create alert with same title under same folder should fail" , func ( t * testing . T ) {
2022-04-14 07:21:36 -05:00
rules := newTestingRuleConfig ( t )
2022-06-21 10:39:22 -05:00
status , body := apiClient . PostRulesGroup ( t , "folder1" , & rules )
assert . Equal ( t , http . StatusInternalServerError , status )
2022-04-14 10:54:49 -05:00
var res map [ string ] interface { }
2022-06-21 10:39:22 -05:00
require . NoError ( t , json . Unmarshal ( [ ] byte ( body ) , & res ) )
2022-04-14 10:54:49 -05:00
require . Equal ( t , "failed to update rule group: failed to add rules: a conflicting alert rule is found: rule title under the same organisation and folder should be unique" , res [ "message" ] )
2022-04-14 07:21:36 -05:00
} )
t . Run ( "trying to update an alert to the title of an existing alert in the same folder should fail" , func ( t * testing . T ) {
rules := newTestingRuleConfig ( t )
rules . Rules [ 0 ] . GrafanaManagedAlert . UID = createdRuleGroup . Rules [ 0 ] . GrafanaManagedAlert . UID
rules . Rules [ 1 ] . GrafanaManagedAlert . UID = createdRuleGroup . Rules [ 1 ] . GrafanaManagedAlert . UID
rules . Rules [ 1 ] . GrafanaManagedAlert . Title = "AlwaysFiring"
2021-06-04 12:45:26 -05:00
2022-06-21 10:39:22 -05:00
status , body := apiClient . PostRulesGroup ( t , "folder1" , & rules )
assert . Equal ( t , http . StatusInternalServerError , status )
2022-04-14 10:54:49 -05:00
var res map [ string ] interface { }
2022-06-21 10:39:22 -05:00
require . NoError ( t , json . Unmarshal ( [ ] byte ( body ) , & res ) )
2022-04-14 10:54:49 -05:00
require . Equal ( t , "failed to update rule group: failed to update rules: a conflicting alert rule is found: rule title under the same organisation and folder should be unique" , res [ "message" ] )
2021-06-04 12:45:26 -05:00
} )
t . Run ( "trying to create alert with same title under another folder should succeed" , func ( t * testing . T ) {
2022-04-14 07:21:36 -05:00
rules := newTestingRuleConfig ( t )
2022-06-21 10:39:22 -05:00
status , body := apiClient . PostRulesGroup ( t , "folder2" , & rules )
assert . Equal ( t , http . StatusAccepted , status )
require . JSONEq ( t , ` { "message":"rule group updated successfully"} ` , body )
2021-06-04 12:45:26 -05:00
} )
}
2021-10-04 10:33:55 -05:00
2022-12-09 01:11:56 -06:00
func TestIntegrationRulerRulesFilterByDashboard ( t * testing . T ) {
testinfra . SQLiteIntegrationTest ( t )
2021-10-04 10:33:55 -05:00
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
EnableFeatureToggles : [ ] string { "ngalert" } ,
DisableAnonymous : true ,
2022-02-09 03:26:06 -06:00
AppModeProduction : true ,
2021-10-04 10:33:55 -05:00
} )
grafanaListedAddr , store := testinfra . StartGrafana ( t , dir , path )
// Create a user to make authenticated requests
2022-06-28 07:32:25 -05:00
createUser ( t , store , user . CreateUserCommand {
2022-08-10 04:56:48 -05:00
DefaultOrgRole : string ( org . RoleEditor ) ,
2021-10-04 10:33:55 -05:00
Password : "password" ,
Login : "grafana" ,
} )
2022-06-21 10:39:22 -05:00
apiClient := newAlertingApiClient ( grafanaListedAddr , "grafana" , "password" )
2022-05-16 05:45:41 -05:00
dashboardUID := "default"
// Create the namespace under default organisation (orgID = 1) where we'll save our alerts to.
2022-06-21 10:39:22 -05:00
apiClient . CreateFolder ( t , "default" , "default" )
2021-10-04 10:33:55 -05:00
interval , err := model . ParseDuration ( "10s" )
require . NoError ( t , err )
// Now, let's create some rules
{
rules := apimodels . PostableRuleGroupConfig {
Name : "anotherrulegroup" ,
Rules : [ ] apimodels . PostableExtendedRuleNode {
{
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 10:46:26 -05:00
For : & interval ,
2021-10-04 10:33:55 -05:00
Labels : map [ string ] string { } ,
Annotations : map [ string ] string {
"__dashboardUid__" : dashboardUID ,
"__panelId__" : "1" ,
} ,
} ,
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 ) ,
} ,
2023-01-31 11:50:10 -06:00
DatasourceUID : expr . DatasourceUID ,
2021-10-04 10:33:55 -05: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 ) ,
} ,
2023-01-31 11:50:10 -06:00
DatasourceUID : expr . DatasourceUID ,
2021-10-04 10:33:55 -05:00
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 > 1"
} ` ) ,
} ,
} ,
NoDataState : apimodels . NoDataState ( ngmodels . Alerting ) ,
ExecErrState : apimodels . ExecutionErrorState ( ngmodels . AlertingErrState ) ,
} ,
} ,
} ,
}
2022-06-21 10:39:22 -05:00
status , body := apiClient . PostRulesGroup ( t , "default" , & rules )
assert . Equal ( t , http . StatusAccepted , status )
require . JSONEq ( t , ` { "message":"rule group updated successfully"} ` , body )
2021-10-04 10:33:55 -05:00
}
expectedAllJSON := fmt . Sprintf ( `
{
"default" : [ {
"name" : "anotherrulegroup" ,
"interval" : "1m" ,
"rules" : [ {
"expr" : "" ,
"for" : "10s" ,
"annotations" : {
"__dashboardUid__" : "%s" ,
"__panelId__" : "1"
} ,
"grafana_alert" : {
"id" : 1 ,
"orgId" : 1 ,
"title" : "AlwaysFiring" ,
"condition" : "A" ,
"data" : [ {
"refId" : "A" ,
"queryType" : "" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
2023-01-31 11:50:10 -06:00
"datasourceUid" : "__expr__" ,
2021-10-04 10:33:55 -05:00
"model" : {
"expression" : "2 + 3 \u003e 1" ,
"intervalMs" : 1000 ,
"maxDataPoints" : 43200 ,
"type" : "math"
}
} ] ,
"updated" : "2021-02-21T01:10:30Z" ,
"intervalSeconds" : 60 ,
"version" : 1 ,
"uid" : "uid" ,
"namespace_uid" : "nsuid" ,
"namespace_id" : 1 ,
"rule_group" : "anotherrulegroup" ,
"no_data_state" : "NoData" ,
"exec_err_state" : "Alerting"
}
} , {
"expr" : "" ,
2022-06-30 10:46:26 -05:00
"for" : "0s" ,
2021-10-04 10:33:55 -05:00
"grafana_alert" : {
"id" : 2 ,
"orgId" : 1 ,
"title" : "AlwaysFiringButSilenced" ,
"condition" : "A" ,
"data" : [ {
"refId" : "A" ,
"queryType" : "" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
2023-01-31 11:50:10 -06:00
"datasourceUid" : "__expr__" ,
2021-10-04 10:33:55 -05:00
"model" : {
"expression" : "2 + 3 \u003e 1" ,
"intervalMs" : 1000 ,
"maxDataPoints" : 43200 ,
"type" : "math"
}
} ] ,
"updated" : "2021-02-21T01:10:30Z" ,
"intervalSeconds" : 60 ,
"version" : 1 ,
"uid" : "uid" ,
"namespace_uid" : "nsuid" ,
"namespace_id" : 1 ,
"rule_group" : "anotherrulegroup" ,
"no_data_state" : "Alerting" ,
"exec_err_state" : "Alerting"
}
} ]
} ]
} ` , dashboardUID )
expectedFilteredByJSON := fmt . Sprintf ( `
{
"default" : [ {
"name" : "anotherrulegroup" ,
"interval" : "1m" ,
"rules" : [ {
"expr" : "" ,
"for" : "10s" ,
"annotations" : {
"__dashboardUid__" : "%s" ,
"__panelId__" : "1"
} ,
"grafana_alert" : {
"id" : 1 ,
"orgId" : 1 ,
"title" : "AlwaysFiring" ,
"condition" : "A" ,
"data" : [ {
"refId" : "A" ,
"queryType" : "" ,
"relativeTimeRange" : {
"from" : 18000 ,
"to" : 10800
} ,
2023-01-31 11:50:10 -06:00
"datasourceUid" : "__expr__" ,
2021-10-04 10:33:55 -05:00
"model" : {
"expression" : "2 + 3 \u003e 1" ,
"intervalMs" : 1000 ,
"maxDataPoints" : 43200 ,
"type" : "math"
}
} ] ,
"updated" : "2021-02-21T01:10:30Z" ,
"intervalSeconds" : 60 ,
"version" : 1 ,
"uid" : "uid" ,
"namespace_uid" : "nsuid" ,
"namespace_id" : 1 ,
"rule_group" : "anotherrulegroup" ,
"no_data_state" : "NoData" ,
"exec_err_state" : "Alerting"
}
} ]
} ]
} ` , dashboardUID )
expectedNoneJSON := ` { } `
// Now, let's see how this looks like.
{
promRulesURL := fmt . Sprintf ( "http://grafana:password@%s/api/ruler/grafana/api/v1/rules" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Get ( promRulesURL )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 08:37:51 -05:00
b , err := io . ReadAll ( resp . Body )
2021-10-04 10:33:55 -05:00
require . NoError ( t , err )
require . Equal ( t , 200 , resp . StatusCode )
body , _ := rulesNamespaceWithoutVariableValues ( t , b )
require . JSONEq ( t , expectedAllJSON , body )
}
// Now, let's check we get the same rule when filtering by dashboard_uid
{
promRulesURL := fmt . Sprintf ( "http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s" , grafanaListedAddr , dashboardUID )
// nolint:gosec
resp , err := http . Get ( promRulesURL )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 08:37:51 -05:00
b , err := io . ReadAll ( resp . Body )
2021-10-04 10:33:55 -05:00
require . NoError ( t , err )
require . Equal ( t , 200 , resp . StatusCode )
body , _ := rulesNamespaceWithoutVariableValues ( t , b )
require . JSONEq ( t , expectedFilteredByJSON , body )
}
// Now, let's check we get no rules when filtering by an unknown dashboard_uid
{
promRulesURL := fmt . Sprintf ( "http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s" , grafanaListedAddr , "abc" )
// nolint:gosec
resp , err := http . Get ( promRulesURL )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 08:37:51 -05:00
b , err := io . ReadAll ( resp . Body )
2021-10-04 10:33:55 -05:00
require . NoError ( t , err )
require . Equal ( t , 200 , resp . StatusCode )
require . JSONEq ( t , expectedNoneJSON , string ( b ) )
}
// Now, let's check we get the same rule when filtering by dashboard_uid and panel_id
{
promRulesURL := fmt . Sprintf ( "http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s&panel_id=1" , grafanaListedAddr , dashboardUID )
// nolint:gosec
resp , err := http . Get ( promRulesURL )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 08:37:51 -05:00
b , err := io . ReadAll ( resp . Body )
2021-10-04 10:33:55 -05:00
require . NoError ( t , err )
require . Equal ( t , 200 , resp . StatusCode )
body , _ := rulesNamespaceWithoutVariableValues ( t , b )
require . JSONEq ( t , expectedFilteredByJSON , body )
}
// Now, let's check we get no rules when filtering by dashboard_uid and unknown panel_id
{
promRulesURL := fmt . Sprintf ( "http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s&panel_id=2" , grafanaListedAddr , dashboardUID )
// nolint:gosec
resp , err := http . Get ( promRulesURL )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
2022-08-10 08:37:51 -05:00
b , err := io . ReadAll ( resp . Body )
2021-10-04 10:33:55 -05:00
require . NoError ( t , err )
require . Equal ( t , 200 , resp . StatusCode )
require . JSONEq ( t , expectedNoneJSON , string ( b ) )
}
// Now, let's check an invalid panel_id returns a 400 Bad Request response
{
promRulesURL := fmt . Sprintf ( "http://grafana:password@%s/api/ruler/grafana/api/v1/rules?dashboard_uid=%s&panel_id=invalid" , grafanaListedAddr , dashboardUID )
// nolint:gosec
resp , err := http . Get ( promRulesURL )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
require . Equal ( t , http . StatusBadRequest , resp . StatusCode )
2022-08-10 08:37:51 -05:00
b , err := io . ReadAll ( resp . Body )
2021-10-04 10:33:55 -05:00
require . NoError ( t , err )
2022-04-14 10:54:49 -05:00
var res map [ string ] interface { }
require . NoError ( t , json . Unmarshal ( b , & res ) )
require . Equal ( t , ` invalid panel_id: strconv.ParseInt: parsing "invalid": invalid syntax ` , res [ "message" ] )
2021-10-04 10:33:55 -05:00
}
// Now, let's check a panel_id without dashboard_uid returns a 400 Bad Request response
{
promRulesURL := fmt . Sprintf ( "http://grafana:password@%s/api/ruler/grafana/api/v1/rules?panel_id=1" , grafanaListedAddr )
// nolint:gosec
resp , err := http . Get ( promRulesURL )
require . NoError ( t , err )
t . Cleanup ( func ( ) {
err := resp . Body . Close ( )
require . NoError ( t , err )
} )
require . Equal ( t , http . StatusBadRequest , resp . StatusCode )
2022-08-10 08:37:51 -05:00
b , err := io . ReadAll ( resp . Body )
2021-10-04 10:33:55 -05:00
require . NoError ( t , err )
2022-04-14 10:54:49 -05:00
var res map [ string ] interface { }
require . NoError ( t , json . Unmarshal ( b , & res ) )
require . Equal ( t , "panel_id must be set with dashboard_uid" , res [ "message" ] )
2021-10-04 10:33:55 -05:00
}
}
2022-04-14 07:21:36 -05:00
2022-12-09 01:11:56 -06:00
func TestIntegrationRuleGroupSequence ( t * testing . T ) {
testinfra . SQLiteIntegrationTest ( t )
2022-06-22 09:52:46 -05:00
// Setup Grafana and its Database
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
DisableAnonymous : true ,
AppModeProduction : true ,
} )
grafanaListedAddr , store := testinfra . StartGrafana ( t , dir , path )
// Create a user to make authenticated requests
2022-06-28 07:32:25 -05:00
createUser ( t , store , user . CreateUserCommand {
2022-08-10 04:56:48 -05:00
DefaultOrgRole : string ( org . RoleEditor ) ,
2022-06-22 09:52:46 -05:00
Password : "password" ,
Login : "grafana" ,
} )
client := newAlertingApiClient ( grafanaListedAddr , "grafana" , "password" )
folder1Title := "folder1"
client . CreateFolder ( t , util . GenerateShortUID ( ) , folder1Title )
group1 := generateAlertRuleGroup ( 5 , alertRuleGen ( ) )
group2 := generateAlertRuleGroup ( 5 , alertRuleGen ( ) )
status , _ := client . PostRulesGroup ( t , folder1Title , & group1 )
require . Equal ( t , http . StatusAccepted , status )
status , _ = client . PostRulesGroup ( t , folder1Title , & group2 )
require . Equal ( t , http . StatusAccepted , status )
t . Run ( "should persist order of the rules in a group" , func ( t * testing . T ) {
group1Get := client . GetRulesGroup ( t , folder1Title , group1 . Name )
assert . Equal ( t , group1 . Name , group1Get . Name )
assert . Equal ( t , group1 . Interval , group1Get . Interval )
assert . Len ( t , group1Get . Rules , len ( group1 . Rules ) )
for i , getRule := range group1Get . Rules {
rule := group1 . Rules [ i ]
assert . Equal ( t , getRule . GrafanaManagedAlert . Title , rule . GrafanaManagedAlert . Title )
assert . NotEmpty ( t , getRule . GrafanaManagedAlert . UID )
}
// now shuffle the rules
postableGroup1 := convertGettableRuleGroupToPostable ( group1Get . GettableRuleGroupConfig )
rand . Shuffle ( len ( postableGroup1 . Rules ) , func ( i , j int ) {
postableGroup1 . Rules [ i ] , postableGroup1 . Rules [ j ] = postableGroup1 . Rules [ j ] , postableGroup1 . Rules [ i ]
} )
expectedUids := make ( [ ] string , 0 , len ( postableGroup1 . Rules ) )
for _ , rule := range postableGroup1 . Rules {
expectedUids = append ( expectedUids , rule . GrafanaManagedAlert . UID )
}
status , _ := client . PostRulesGroup ( t , folder1Title , & postableGroup1 )
require . Equal ( t , http . StatusAccepted , status )
group1Get = client . GetRulesGroup ( t , folder1Title , group1 . Name )
require . Len ( t , group1Get . Rules , len ( postableGroup1 . Rules ) )
actualUids := make ( [ ] string , 0 , len ( group1Get . Rules ) )
for _ , getRule := range group1Get . Rules {
actualUids = append ( actualUids , getRule . GrafanaManagedAlert . UID )
}
assert . Equal ( t , expectedUids , actualUids )
} )
t . Run ( "should be able to move a rule from another group in a specific position" , func ( t * testing . T ) {
group1Get := client . GetRulesGroup ( t , folder1Title , group1 . Name )
group2Get := client . GetRulesGroup ( t , folder1Title , group2 . Name )
movedRule := convertGettableRuleToPostable ( group2Get . Rules [ 3 ] )
// now shuffle the rules
postableGroup1 := convertGettableRuleGroupToPostable ( group1Get . GettableRuleGroupConfig )
postableGroup1 . Rules = append ( append ( append ( [ ] apimodels . PostableExtendedRuleNode { } , postableGroup1 . Rules [ 0 : 1 ] ... ) , movedRule ) , postableGroup1 . Rules [ 2 : ] ... )
expectedUids := make ( [ ] string , 0 , len ( postableGroup1 . Rules ) )
for _ , rule := range postableGroup1 . Rules {
expectedUids = append ( expectedUids , rule . GrafanaManagedAlert . UID )
}
status , _ := client . PostRulesGroup ( t , folder1Title , & postableGroup1 )
require . Equal ( t , http . StatusAccepted , status )
group1Get = client . GetRulesGroup ( t , folder1Title , group1 . Name )
require . Len ( t , group1Get . Rules , len ( postableGroup1 . Rules ) )
actualUids := make ( [ ] string , 0 , len ( group1Get . Rules ) )
for _ , getRule := range group1Get . Rules {
actualUids = append ( actualUids , getRule . GrafanaManagedAlert . UID )
}
assert . Equal ( t , expectedUids , actualUids )
group2Get = client . GetRulesGroup ( t , folder1Title , group2 . Name )
assert . Len ( t , group2Get . Rules , len ( group2 . Rules ) - 1 )
for _ , rule := range group2Get . Rules {
require . NotEqual ( t , movedRule . GrafanaManagedAlert . UID , rule . GrafanaManagedAlert . UID )
}
} )
}
2022-12-09 01:11:56 -06:00
func TestIntegrationRuleUpdate ( t * testing . T ) {
testinfra . SQLiteIntegrationTest ( t )
2022-06-30 10:46:26 -05:00
// Setup Grafana and its Database
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
DisableAnonymous : true ,
AppModeProduction : true ,
} )
grafanaListedAddr , store := testinfra . StartGrafana ( t , dir , path )
// Create a user to make authenticated requests
createUser ( t , store , user . CreateUserCommand {
2022-08-10 04:56:48 -05:00
DefaultOrgRole : string ( org . RoleEditor ) ,
2022-06-30 10:46:26 -05:00
Password : "password" ,
Login : "grafana" ,
} )
client := newAlertingApiClient ( grafanaListedAddr , "grafana" , "password" )
folder1Title := "folder1"
client . CreateFolder ( t , util . GenerateShortUID ( ) , folder1Title )
t . Run ( "should be able to reset 'for' to 0" , func ( t * testing . T ) {
group := generateAlertRuleGroup ( 1 , alertRuleGen ( ) )
expected := model . Duration ( 10 * time . Second )
group . Rules [ 0 ] . ApiRuleNode . For = & expected
status , body := client . PostRulesGroup ( t , folder1Title , & group )
require . Equalf ( t , http . StatusAccepted , status , "failed to post rule group. Response: %s" , body )
getGroup := client . GetRulesGroup ( t , folder1Title , group . Name )
require . Equal ( t , expected , * getGroup . Rules [ 0 ] . ApiRuleNode . For )
group = convertGettableRuleGroupToPostable ( getGroup . GettableRuleGroupConfig )
expected = 0
group . Rules [ 0 ] . ApiRuleNode . For = & expected
status , body = client . PostRulesGroup ( t , folder1Title , & group )
require . Equalf ( t , http . StatusAccepted , status , "failed to post rule group. Response: %s" , body )
getGroup = client . GetRulesGroup ( t , folder1Title , group . Name )
require . Equal ( t , expected , * getGroup . Rules [ 0 ] . ApiRuleNode . For )
} )
}
2022-04-14 07:21:36 -05:00
func newTestingRuleConfig ( t * testing . T ) apimodels . PostableRuleGroupConfig {
interval , err := model . ParseDuration ( "1m" )
require . NoError ( t , err )
firstRule := apimodels . PostableExtendedRuleNode {
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 10:46:26 -05:00
For : & interval ,
2022-04-14 07:21:36 -05: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 : "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 ) ,
} ,
2023-01-31 11:50:10 -06:00
DatasourceUID : expr . DatasourceUID ,
2022-04-14 07:21:36 -05:00
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 > 1"
} ` ) ,
} ,
} ,
} ,
}
secondRule := apimodels . PostableExtendedRuleNode {
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 10:46:26 -05:00
For : & interval ,
2022-04-14 07:21:36 -05: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 : "AlwaysFiring2" ,
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 ) ,
} ,
2023-01-31 11:50:10 -06:00
DatasourceUID : expr . DatasourceUID ,
2022-04-14 07:21:36 -05:00
Model : json . RawMessage ( ` {
"type" : "math" ,
"expression" : "2 + 3 > 1"
} ` ) ,
} ,
} ,
} ,
}
return apimodels . PostableRuleGroupConfig {
Name : "arulegroup" ,
Rules : [ ] apimodels . PostableExtendedRuleNode {
firstRule ,
secondRule ,
} ,
}
}