2021-04-29 19:15:15 +03:00
package alerting
import (
2023-06-15 13:33:42 -04:00
"context"
2023-10-05 15:47:49 -04:00
"embed"
2021-04-29 19:15:15 +03:00
"encoding/json"
"fmt"
2022-08-10 13:37:51 +00:00
"io"
2022-06-22 10:52:46 -04:00
"math/rand"
2021-04-29 19:15:15 +03:00
"net/http"
2023-10-05 15:47:49 -04:00
"path"
"strings"
2021-04-29 19:15:15 +03:00
"testing"
"time"
2023-10-05 15:47:49 -04:00
"github.com/google/go-cmp/cmp"
2023-06-15 13:33:42 -04:00
"github.com/google/uuid"
2022-02-23 11:30:04 -05:00
"github.com/prometheus/common/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
2023-06-15 13:33:42 -04:00
"github.com/grafana/grafana/pkg/expr"
"github.com/grafana/grafana/pkg/services/accesscontrol"
2022-08-18 09:43:45 +02:00
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
2023-06-15 13:33:42 -04:00
"github.com/grafana/grafana/pkg/services/datasources"
datasourceService "github.com/grafana/grafana/pkg/services/datasources/service"
2023-07-21 15:23:01 +01:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
2021-04-29 19:15:15 +03:00
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
2022-08-10 11:56:48 +02:00
"github.com/grafana/grafana/pkg/services/org"
2022-06-28 14:32:25 +02:00
"github.com/grafana/grafana/pkg/services/user"
2023-06-15 13:33:42 -04:00
"github.com/grafana/grafana/pkg/setting"
2021-04-29 19:15:15 +03:00
"github.com/grafana/grafana/pkg/tests/testinfra"
2022-06-22 10:52:46 -04:00
"github.com/grafana/grafana/pkg/util"
2021-04-29 19:15:15 +03:00
)
2023-10-05 15:47:49 -04:00
//go:embed test-data/*.*
var testData embed . FS
2022-12-09 08:11:56 +01:00
func TestIntegrationAlertRulePermissions ( t * testing . T ) {
testinfra . SQLiteIntegrationTest ( t )
2021-04-29 19:15:15 +03:00
// Setup Grafana and its Database
2023-10-05 15:47:49 -04:00
dir , p := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2021-09-29 17:16:40 +03:00
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
DisableAnonymous : true ,
2022-02-09 10:26:06 +01:00
AppModeProduction : true ,
2021-04-29 19:15:15 +03:00
})
2021-08-25 15:11:22 +02:00
2023-10-05 15:47:49 -04:00
grafanaListedAddr , store := testinfra . StartGrafana ( t , dir , p )
2023-07-21 15:23:01 +01:00
permissionsStore := resourcepermissions . NewStore ( store , featuremgmt . WithFeatures ())
2021-04-29 19:15:15 +03:00
2021-05-04 19:16:28 +03:00
// Create a user to make authenticated requests
2022-06-28 14:32:25 +02:00
userID := createUser ( t , store , user . CreateUserCommand {
2022-08-10 11:56:48 +02:00
DefaultOrgRole : string ( org . RoleEditor ),
2021-08-12 16:04:09 +03:00
Password : "password" ,
Login : "grafana" ,
})
2021-05-04 19:16:28 +03:00
2022-06-21 11:39:22 -04:00
apiClient := newAlertingApiClient ( grafanaListedAddr , "grafana" , "password" )
2021-04-29 19:15:15 +03:00
// Create the namespace we'll save our alerts to.
2022-06-21 11:39:22 -04:00
apiClient . CreateFolder ( t , "folder1" , "folder1" )
// Create the namespace we'll save our alerts to.
apiClient . CreateFolder ( t , "folder2" , "folder2" )
2021-04-29 19:15:15 +03:00
2023-10-05 15:47:49 -04:00
postGroupRaw , err := testData . ReadFile ( path . Join ( "test-data" , "rulegroup-1-post.json" ))
require . NoError ( t , err )
var group1 apimodels . PostableRuleGroupConfig
require . NoError ( t , json . Unmarshal ( postGroupRaw , & group1 ))
2021-04-29 19:15:15 +03:00
// Create rule under folder1
2023-10-06 18:11:24 -04:00
_ , status , response := apiClient . PostRulesGroupWithStatus ( t , "folder1" , & group1 )
2023-10-05 15:47:49 -04:00
require . Equalf ( t , http . StatusAccepted , status , response )
postGroupRaw , err = testData . ReadFile ( path . Join ( "test-data" , "rulegroup-2-post.json" ))
require . NoError ( t , err )
var group2 apimodels . PostableRuleGroupConfig
require . NoError ( t , json . Unmarshal ( postGroupRaw , & group2 ))
2021-04-29 19:15:15 +03:00
// Create rule under folder2
2023-10-06 18:11:24 -04:00
_ , status , response = apiClient . PostRulesGroupWithStatus ( t , "folder2" , & group2 )
2023-10-05 15:47:49 -04:00
require . Equalf ( t , http . StatusAccepted , status , response )
2021-04-29 19:15:15 +03:00
// With the rules created, let's make sure that rule definitions are stored.
2023-10-05 15:47:49 -04:00
allRules , status , _ := apiClient . GetAllRulesWithStatus ( t )
require . Equal ( t , http . StatusOK , status )
status , allExportRaw := apiClient . ExportRulesWithStatus ( t , & apimodels . AlertRulesExportParameters {
ExportQueryParams : apimodels . ExportQueryParams { Format : "json" },
})
require . Equal ( t , http . StatusOK , status )
var allExport apimodels . AlertingFileExport
require . NoError ( t , json . Unmarshal ([] byte ( allExportRaw ), & allExport ))
t . Run ( "when user has all permissions" , func ( t * testing . T ) {
t . Run ( "Get all returns all rules" , func ( t * testing . T ) {
var group1 , group2 apimodels . GettableRuleGroupConfig
getGroup1Raw , err := testData . ReadFile ( path . Join ( "test-data" , "rulegroup-1-get.json" ))
2021-04-29 19:15:15 +03:00
require . NoError ( t , err )
2023-10-05 15:47:49 -04:00
require . NoError ( t , json . Unmarshal ( getGroup1Raw , & group1 ))
getGroup2Raw , err := testData . ReadFile ( path . Join ( "test-data" , "rulegroup-2-get.json" ))
require . NoError ( t , err )
require . NoError ( t , json . Unmarshal ( getGroup2Raw , & group2 ))
expected := apimodels . NamespaceConfigResponse {
"folder1" : [] apimodels . GettableRuleGroupConfig {
group1 ,
},
"folder2" : [] apimodels . GettableRuleGroupConfig {
group2 ,
},
}
pathsToIgnore := [] string {
"GrafanaManagedAlert.Updated" ,
"GrafanaManagedAlert.UID" ,
"GrafanaManagedAlert.ID" ,
"GrafanaManagedAlert.Data.Model" ,
"GrafanaManagedAlert.NamespaceUID" ,
"GrafanaManagedAlert.NamespaceID" ,
}
// compare expected and actual and ignore the dynamic fields
diff := cmp . Diff ( expected , allRules , cmp . FilterPath ( func ( path cmp . Path ) bool {
for _ , s := range pathsToIgnore {
if strings . Contains ( path . String (), s ) {
return true
}
}
return false
}, cmp . Ignore ()))
require . Empty ( t , diff )
for _ , rule := range allRules [ "folder1" ][ 0 ]. Rules {
assert . Equal ( t , "folder1" , rule . GrafanaManagedAlert . NamespaceUID )
assert . Equal ( t , int64 ( 1 ), rule . GrafanaManagedAlert . NamespaceID )
}
for _ , rule := range allRules [ "folder2" ][ 0 ]. Rules {
assert . Equal ( t , "folder2" , rule . GrafanaManagedAlert . NamespaceUID )
assert . Equal ( t , int64 ( 2 ), rule . GrafanaManagedAlert . NamespaceID )
}
})
t . Run ( "Get by folder returns groups in folder" , func ( t * testing . T ) {
rules , status , _ := apiClient . GetAllRulesGroupInFolderWithStatus ( t , "folder1" )
require . Equal ( t , http . StatusAccepted , status )
require . Contains ( t , rules , "folder1" )
require . Len ( t , rules [ "folder1" ], 1 )
require . Equal ( t , allRules [ "folder1" ], rules [ "folder1" ])
2021-04-29 19:15:15 +03:00
})
2023-10-05 15:47:49 -04:00
t . Run ( "Get group returns a single group" , func ( t * testing . T ) {
rules := apiClient . GetRulesGroup ( t , "folder2" , allRules [ "folder2" ][ 0 ]. Name )
cmp . Diff ( allRules [ "folder2" ][ 0 ], rules . GettableRuleGroupConfig )
})
t . Run ( "Export returns all rules" , func ( t * testing . T ) {
var group1File , group2File apimodels . AlertingFileExport
getGroup1Raw , err := testData . ReadFile ( path . Join ( "test-data" , "rulegroup-1-export.json" ))
require . NoError ( t , err )
require . NoError ( t , json . Unmarshal ( getGroup1Raw , & group1File ))
getGroup2Raw , err := testData . ReadFile ( path . Join ( "test-data" , "rulegroup-2-export.json" ))
require . NoError ( t , err )
require . NoError ( t , json . Unmarshal ( getGroup2Raw , & group2File ))
group1File . Groups = append ( group1File . Groups , group2File . Groups ... )
expected := group1File
pathsToIgnore := [] string {
"Groups.Rules.UID" ,
"Groups.Folder" ,
}
// compare expected and actual and ignore the dynamic fields
diff := cmp . Diff ( expected , allExport , cmp . FilterPath ( func ( path cmp . Path ) bool {
for _ , s := range pathsToIgnore {
if strings . Contains ( path . String (), s ) {
return true
2021-04-29 19:15:15 +03:00
}
2023-10-05 15:47:49 -04:00
}
return false
}, cmp . Ignore ()))
require . Empty ( t , diff )
require . Equal ( t , "folder1" , allExport . Groups [ 0 ]. Folder )
require . Equal ( t , "folder2" , allExport . Groups [ 1 ]. Folder )
})
t . Run ( "Export from one folder" , func ( t * testing . T ) {
expected := allExport . Groups [ 0 ]
status , exportRaw := apiClient . ExportRulesWithStatus ( t , & apimodels . AlertRulesExportParameters {
ExportQueryParams : apimodels . ExportQueryParams { Format : "json" },
FolderUID : [] string { "folder1" },
})
require . Equal ( t , http . StatusOK , status )
var export apimodels . AlertingFileExport
require . NoError ( t , json . Unmarshal ([] byte ( exportRaw ), & export ))
require . Len ( t , export . Groups , 1 )
require . Equal ( t , expected , export . Groups [ 0 ])
})
t . Run ( "Export from one group" , func ( t * testing . T ) {
expected := allExport . Groups [ 0 ]
status , exportRaw := apiClient . ExportRulesWithStatus ( t , & apimodels . AlertRulesExportParameters {
ExportQueryParams : apimodels . ExportQueryParams { Format : "json" },
FolderUID : [] string { "folder1" },
GroupName : expected . Name ,
})
require . Equal ( t , http . StatusOK , status )
var export apimodels . AlertingFileExport
require . NoError ( t , json . Unmarshal ([] byte ( exportRaw ), & export ))
require . Len ( t , export . Groups , 1 )
require . Equal ( t , expected , export . Groups [ 0 ])
})
t . Run ( "Export single rule" , func ( t * testing . T ) {
expected := allExport . Groups [ 0 ]
expected . Rules = [] apimodels . AlertRuleExport {
expected . Rules [ 0 ],
2021-04-29 19:15:15 +03:00
}
2023-10-05 15:47:49 -04:00
status , exportRaw := apiClient . ExportRulesWithStatus ( t , & apimodels . AlertRulesExportParameters {
ExportQueryParams : apimodels . ExportQueryParams { Format : "json" },
RuleUID : expected . Rules [ 0 ]. UID ,
})
2021-04-29 19:15:15 +03:00
2023-10-05 15:47:49 -04:00
require . Equal ( t , http . StatusOK , status )
var export apimodels . AlertingFileExport
t . Log ( exportRaw )
require . NoError ( t , json . Unmarshal ([] byte ( exportRaw ), & export ))
require . Len ( t , export . Groups , 1 )
require . Equal ( t , expected , export . Groups [ 0 ])
})
})
t . Run ( "when permissions for folder2 removed" , func ( t * testing . T ) {
2021-04-29 19:15:15 +03:00
// remove permissions from folder2
2022-08-10 11:56:48 +02:00
removeFolderPermission ( t , permissionsStore , 1 , userID , org . RoleEditor , "folder2" )
2022-06-27 09:31:49 -04:00
apiClient . ReloadCachedPermissions ( t )
2021-04-29 19:15:15 +03:00
2023-10-05 15:47:49 -04:00
t . Run ( "Get all returns all rules" , func ( t * testing . T ) {
newAll , status , _ := apiClient . GetAllRulesWithStatus ( t )
require . Equal ( t , http . StatusOK , status )
require . NotContains ( t , newAll , "folder2" )
require . Contains ( t , newAll , "folder1" )
2021-04-29 19:15:15 +03:00
})
2023-10-05 15:47:49 -04:00
t . Run ( "Get by folder returns groups in folder" , func ( t * testing . T ) {
_ , status , _ := apiClient . GetAllRulesGroupInFolderWithStatus ( t , "folder2" )
require . Equal ( t , http . StatusForbidden , status )
})
2021-11-08 13:26:08 +00:00
2023-10-05 15:47:49 -04:00
t . Run ( "Get group returns a single group" , func ( t * testing . T ) {
u := fmt . Sprintf ( "%s/api/ruler/grafana/api/v1/rules/folder2/arulegroup" , apiClient . url )
// nolint:gosec
resp , err := http . Get ( u )
2021-11-08 13:26:08 +00:00
require . NoError ( t , err )
2023-10-05 15:47:49 -04:00
defer func () {
_ = resp . Body . Close ()
}()
assert . Equal ( t , http . StatusForbidden , resp . StatusCode )
2021-11-08 13:26:08 +00:00
})
2023-10-05 15:47:49 -04:00
t . Run ( "Export returns all rules" , func ( t * testing . T ) {
status , exportRaw := apiClient . ExportRulesWithStatus ( t , & apimodels . AlertRulesExportParameters {
ExportQueryParams : apimodels . ExportQueryParams { Format : "json" },
})
require . Equal ( t , http . StatusOK , status )
var export apimodels . AlertingFileExport
require . NoError ( t , json . Unmarshal ([] byte ( exportRaw ), & export ))
require . Equal ( t , http . StatusOK , status )
require . Len ( t , export . Groups , 1 )
require . Equal ( t , "folder1" , export . Groups [ 0 ]. Folder )
})
t . Run ( "Export from one folder" , func ( t * testing . T ) {
status , _ := apiClient . ExportRulesWithStatus ( t , & apimodels . AlertRulesExportParameters {
ExportQueryParams : apimodels . ExportQueryParams { Format : "json" },
FolderUID : [] string { "folder2" },
})
assert . Equal ( t , http . StatusUnauthorized , status )
})
t . Run ( "Export from one group" , func ( t * testing . T ) {
status , _ := apiClient . ExportRulesWithStatus ( t , & apimodels . AlertRulesExportParameters {
ExportQueryParams : apimodels . ExportQueryParams { Format : "json" },
FolderUID : [] string { "folder2" },
GroupName : "arulegroup" ,
})
assert . Equal ( t , http . StatusForbidden , status )
})
t . Run ( "Export single rule" , func ( t * testing . T ) {
uid := allRules [ "folder2" ][ 0 ]. Rules [ 0 ]. GrafanaManagedAlert . UID
status , _ := apiClient . ExportRulesWithStatus ( t , & apimodels . AlertRulesExportParameters {
ExportQueryParams : apimodels . ExportQueryParams { Format : "json" },
RuleUID : uid ,
})
require . Equal ( t , http . StatusForbidden , status )
})
t . Run ( "when all permissions are revoked" , func ( t * testing . T ) {
removeFolderPermission ( t , permissionsStore , 1 , userID , org . RoleEditor , "folder1" )
apiClient . ReloadCachedPermissions ( t )
rules , status , _ := apiClient . GetAllRulesWithStatus ( t )
require . Equal ( t , http . StatusOK , status )
require . Empty ( t , rules )
status , _ = apiClient . ExportRulesWithStatus ( t , & apimodels . AlertRulesExportParameters {
ExportQueryParams : apimodels . ExportQueryParams { Format : "json" },
})
require . Equal ( t , http . StatusNotFound , status )
})
})
2021-04-29 19:15:15 +03:00
}
2023-10-06 18:11:24 -04:00
func createRule ( t * testing . T , client apiClient , folder string ) ( apimodels . PostableRuleGroupConfig , string ) {
2021-04-29 19:15:15 +03:00
t . Helper ()
interval , err := model . ParseDuration ( "1m" )
require . NoError ( t , err )
2022-06-30 11:46:26 -04:00
doubleInterval := 2 * interval
2021-04-29 19:15:15 +03:00
rules := apimodels . PostableRuleGroupConfig {
Name : "arulegroup" ,
Interval : interval ,
Rules : [] apimodels . PostableExtendedRuleNode {
{
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 11:46:26 -04:00
For : & doubleInterval ,
2021-04-29 19:15:15 +03: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" ,
2023-03-27 11:55:13 -04:00
Data : [] apimodels . AlertQuery {
2021-04-29 19:15:15 +03:00
{
RefID : "A" ,
2023-03-27 11:55:13 -04:00
RelativeTimeRange : apimodels . RelativeTimeRange {
From : apimodels . Duration ( time . Duration ( 5 ) * time . Hour ),
To : apimodels . Duration ( time . Duration ( 3 ) * time . Hour ),
2021-04-29 19:15:15 +03:00
},
2023-01-31 18:50:10 +01:00
DatasourceUID : expr . DatasourceUID ,
2021-04-29 19:15:15 +03:00
Model : json . RawMessage ( `{
"type": "math",
"expression": "2 + 3 > 1"
}` ),
},
},
},
},
},
}
2023-10-06 18:11:24 -04:00
resp , status , _ := client . PostRulesGroupWithStatus ( t , folder , & rules )
2022-06-21 11:39:22 -04:00
assert . Equal ( t , http . StatusAccepted , status )
2023-10-06 18:11:24 -04:00
require . Len ( t , resp . Created , 1 )
return rules , resp . Created [ 0 ]
2023-10-05 15:47:49 -04:00
}
func TestAlertRulePostExport ( t * testing . T ) {
testinfra . SQLiteIntegrationTest ( t )
// Setup Grafana and its Database
dir , p := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
DisableAnonymous : true ,
AppModeProduction : true ,
})
grafanaListedAddr , store := testinfra . StartGrafana ( t , dir , p )
permissionsStore := resourcepermissions . NewStore ( store , featuremgmt . WithFeatures ())
// Create a user to make authenticated requests
userID := createUser ( t , store , user . CreateUserCommand {
DefaultOrgRole : string ( org . RoleEditor ),
Password : "password" ,
Login : "grafana" ,
})
apiClient := newAlertingApiClient ( grafanaListedAddr , "grafana" , "password" )
// Create the namespace we'll save our alerts to.
apiClient . CreateFolder ( t , "folder1" , "folder1" )
var group1 apimodels . PostableRuleGroupConfig
group1Raw , err := testData . ReadFile ( path . Join ( "test-data" , "rulegroup-1-post.json" ))
require . NoError ( t , err )
require . NoError ( t , json . Unmarshal ( group1Raw , & group1 ))
t . Run ( "should return in export format" , func ( t * testing . T ) {
var expected , actual apimodels . AlertingFileExport
getGroup1Raw , err := testData . ReadFile ( path . Join ( "test-data" , "rulegroup-1-export.json" ))
require . NoError ( t , err )
require . NoError ( t , json . Unmarshal ( getGroup1Raw , & expected ))
status , actualRaw := apiClient . PostRulesExportWithStatus ( t , "folder1" , & group1 , & apimodels . ExportQueryParams {
Download : false ,
Format : "json" ,
})
require . Equal ( t , http . StatusOK , status )
require . NoError ( t , json . Unmarshal ([] byte ( actualRaw ), & actual ))
pathsToIgnore := [] string {
"Groups.Rules.UID" ,
"Groups.Folder" ,
"Data.Model" , // Model is not amended with default values
}
// compare expected and actual and ignore the dynamic fields
diff := cmp . Diff ( expected , actual , cmp . FilterPath ( func ( path cmp . Path ) bool {
for _ , s := range pathsToIgnore {
if strings . Contains ( path . String (), s ) {
return true
}
}
return false
}, cmp . Ignore ()))
require . Empty ( t , diff )
require . Equal ( t , actual . Groups [ 0 ]. Folder , "folder1" )
})
t . Run ( "should return 403 when no access to folder" , func ( t * testing . T ) {
removeFolderPermission ( t , permissionsStore , 1 , userID , org . RoleEditor , "folder1" )
apiClient . ReloadCachedPermissions ( t )
status , _ := apiClient . PostRulesExportWithStatus ( t , "folder1" , & group1 , & apimodels . ExportQueryParams {
Download : false ,
Format : "json" ,
})
require . Equal ( t , http . StatusForbidden , status )
})
2021-04-29 19:15:15 +03:00
}
2021-06-04 20:45:26 +03:00
2022-12-09 08:11:56 +01:00
func TestIntegrationAlertRuleConflictingTitle ( t * testing . T ) {
testinfra . SQLiteIntegrationTest ( t )
2021-06-04 20:45:26 +03:00
// Setup Grafana and its Database
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
2021-09-29 17:16:40 +03:00
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
EnableQuota : true ,
DisableAnonymous : true ,
ViewersCanEdit : true ,
2022-02-09 10:26:06 +01:00
AppModeProduction : true ,
2021-06-04 20:45:26 +03:00
})
2021-08-25 15:11:22 +02:00
grafanaListedAddr , store := testinfra . StartGrafana ( t , dir , path )
2021-06-04 20:45:26 +03:00
// Create user
2022-06-28 14:32:25 +02:00
createUser ( t , store , user . CreateUserCommand {
2022-08-10 11:56:48 +02:00
DefaultOrgRole : string ( org . RoleAdmin ),
2021-08-12 16:04:09 +03:00
Password : "admin" ,
Login : "admin" ,
})
2021-06-04 20:45:26 +03:00
2022-06-21 11:39:22 -04:00
apiClient := newAlertingApiClient ( grafanaListedAddr , "admin" , "admin" )
2022-05-16 03:45:41 -07:00
// Create the namespace we'll save our alerts to.
2022-06-21 11:39:22 -04:00
apiClient . CreateFolder ( t , "folder1" , "folder1" )
2022-05-16 03:45:41 -07:00
// Create the namespace we'll save our alerts to.
2022-06-21 11:39:22 -04:00
apiClient . CreateFolder ( t , "folder2" , "folder2" )
2022-05-16 03:45:41 -07:00
2022-04-14 14:21:36 +02:00
rules := newTestingRuleConfig ( t )
2021-06-04 20:45:26 +03:00
2023-10-06 18:11:24 -04:00
respModel , status , _ := apiClient . PostRulesGroupWithStatus ( t , "folder1" , & rules )
2022-06-21 11:39:22 -04:00
assert . Equal ( t , http . StatusAccepted , status )
2023-10-06 18:11:24 -04:00
require . Len ( t , respModel . Created , len ( rules . Rules ))
2021-06-04 20:45:26 +03:00
2022-04-14 14:21:36 +02:00
// fetch the created rules, so we can get the uid's and trigger
// and update by reusing the uid's
2022-06-21 11:39:22 -04:00
createdRuleGroup := apiClient . GetRulesGroup ( t , "folder1" , rules . Name ). GettableRuleGroupConfig
2022-04-14 14:21:36 +02:00
require . Len ( t , createdRuleGroup . Rules , 2 )
2021-06-04 20:45:26 +03:00
t . Run ( "trying to create alert with same title under same folder should fail" , func ( t * testing . T ) {
2023-06-08 18:51:50 -04:00
rulesWithUID := convertGettableRuleGroupToPostable ( createdRuleGroup )
rulesWithUID . Rules = append ( rulesWithUID . Rules , rules . Rules [ 0 ]) // Create new copy of first rule.
2022-04-14 14:21:36 +02:00
2023-10-06 18:11:24 -04:00
_ , status , body := apiClient . PostRulesGroupWithStatus ( t , "folder1" , & rulesWithUID )
2022-06-21 11:39:22 -04:00
assert . Equal ( t , http . StatusInternalServerError , status )
2022-04-14 17:54:49 +02:00
2023-08-30 08:46:47 -07:00
var res map [ string ] any
2022-06-21 11:39:22 -04:00
require . NoError ( t , json . Unmarshal ([] byte ( body ), & res ))
2022-04-14 17:54:49 +02: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 14:21:36 +02: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 ) {
2023-06-08 18:51:50 -04:00
rulesWithUID := convertGettableRuleGroupToPostable ( createdRuleGroup )
rulesWithUID . Rules [ 1 ]. GrafanaManagedAlert . Title = "AlwaysFiring"
2021-06-04 20:45:26 +03:00
2023-10-06 18:11:24 -04:00
_ , status , body := apiClient . PostRulesGroupWithStatus ( t , "folder1" , & rulesWithUID )
2022-06-21 11:39:22 -04:00
assert . Equal ( t , http . StatusInternalServerError , status )
2022-04-14 17:54:49 +02:00
2023-08-30 08:46:47 -07:00
var res map [ string ] any
2022-06-21 11:39:22 -04:00
require . NoError ( t , json . Unmarshal ([] byte ( body ), & res ))
2022-04-14 17:54:49 +02: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 20:45:26 +03:00
})
t . Run ( "trying to create alert with same title under another folder should succeed" , func ( t * testing . T ) {
2022-04-14 14:21:36 +02:00
rules := newTestingRuleConfig ( t )
2023-10-06 18:11:24 -04:00
resp , status , _ := apiClient . PostRulesGroupWithStatus ( t , "folder2" , & rules )
2022-06-21 11:39:22 -04:00
assert . Equal ( t , http . StatusAccepted , status )
2023-10-06 18:11:24 -04:00
require . Len ( t , resp . Created , len ( rules . Rules ))
2023-06-08 18:51:50 -04:00
})
t . Run ( "trying to swap titles of existing alerts in the same folder should work" , func ( t * testing . T ) {
rulesWithUID := convertGettableRuleGroupToPostable ( createdRuleGroup )
title0 := rulesWithUID . Rules [ 0 ]. GrafanaManagedAlert . Title
title1 := rulesWithUID . Rules [ 1 ]. GrafanaManagedAlert . Title
rulesWithUID . Rules [ 0 ]. GrafanaManagedAlert . Title = title1
rulesWithUID . Rules [ 1 ]. GrafanaManagedAlert . Title = title0
2023-10-06 18:11:24 -04:00
resp , status , _ := apiClient . PostRulesGroupWithStatus ( t , "folder1" , & rulesWithUID )
2023-06-08 18:51:50 -04:00
assert . Equal ( t , http . StatusAccepted , status )
2023-10-06 18:11:24 -04:00
require . Len ( t , resp . Updated , 2 )
2023-06-08 18:51:50 -04:00
})
t . Run ( "trying to update titles of existing alerts in a chain in the same folder should work" , func ( t * testing . T ) {
rulesWithUID := convertGettableRuleGroupToPostable ( createdRuleGroup )
rulesWithUID . Rules [ 0 ]. GrafanaManagedAlert . Title = rulesWithUID . Rules [ 1 ]. GrafanaManagedAlert . Title
rulesWithUID . Rules [ 1 ]. GrafanaManagedAlert . Title = "something new"
2023-10-06 18:11:24 -04:00
resp , status , _ := apiClient . PostRulesGroupWithStatus ( t , "folder1" , & rulesWithUID )
2023-06-08 18:51:50 -04:00
assert . Equal ( t , http . StatusAccepted , status )
2023-10-06 18:11:24 -04:00
require . Len ( t , resp . Updated , len ( rulesWithUID . Rules ))
2021-06-04 20:45:26 +03:00
})
}
2021-10-04 16:33:55 +01:00
2022-12-09 08:11:56 +01:00
func TestIntegrationRulerRulesFilterByDashboard ( t * testing . T ) {
testinfra . SQLiteIntegrationTest ( t )
2021-10-04 16:33:55 +01:00
dir , path := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
EnableFeatureToggles : [] string { "ngalert" },
DisableAnonymous : true ,
2022-02-09 10:26:06 +01:00
AppModeProduction : true ,
2021-10-04 16:33:55 +01:00
})
grafanaListedAddr , store := testinfra . StartGrafana ( t , dir , path )
// Create a user to make authenticated requests
2022-06-28 14:32:25 +02:00
createUser ( t , store , user . CreateUserCommand {
2022-08-10 11:56:48 +02:00
DefaultOrgRole : string ( org . RoleEditor ),
2021-10-04 16:33:55 +01:00
Password : "password" ,
Login : "grafana" ,
})
2022-06-21 11:39:22 -04:00
apiClient := newAlertingApiClient ( grafanaListedAddr , "grafana" , "password" )
2022-05-16 03:45:41 -07:00
dashboardUID := "default"
// Create the namespace under default organisation (orgID = 1) where we'll save our alerts to.
2022-06-21 11:39:22 -04:00
apiClient . CreateFolder ( t , "default" , "default" )
2021-10-04 16:33:55 +01: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 11:46:26 -04:00
For : & interval ,
2021-10-04 16:33:55 +01:00
Labels : map [ string ] string {},
Annotations : map [ string ] string {
"__dashboardUid__" : dashboardUID ,
"__panelId__" : "1" ,
},
},
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
Title : "AlwaysFiring" ,
Condition : "A" ,
2023-03-27 11:55:13 -04:00
Data : [] apimodels . AlertQuery {
2021-10-04 16:33:55 +01:00
{
RefID : "A" ,
2023-03-27 11:55:13 -04:00
RelativeTimeRange : apimodels . RelativeTimeRange {
From : apimodels . Duration ( time . Duration ( 5 ) * time . Hour ),
To : apimodels . Duration ( time . Duration ( 3 ) * time . Hour ),
2021-10-04 16:33:55 +01:00
},
2023-01-31 18:50:10 +01:00
DatasourceUID : expr . DatasourceUID ,
2021-10-04 16:33:55 +01:00
Model : json . RawMessage ( `{
"type": "math",
"expression": "2 + 3 > 1"
}` ),
},
},
},
},
{
GrafanaManagedAlert : & apimodels . PostableGrafanaRule {
Title : "AlwaysFiringButSilenced" ,
Condition : "A" ,
2023-03-27 11:55:13 -04:00
Data : [] apimodels . AlertQuery {
2021-10-04 16:33:55 +01:00
{
RefID : "A" ,
2023-03-27 11:55:13 -04:00
RelativeTimeRange : apimodels . RelativeTimeRange {
From : apimodels . Duration ( time . Duration ( 5 ) * time . Hour ),
To : apimodels . Duration ( time . Duration ( 3 ) * time . Hour ),
2021-10-04 16:33:55 +01:00
},
2023-01-31 18:50:10 +01:00
DatasourceUID : expr . DatasourceUID ,
2021-10-04 16:33:55 +01:00
Model : json . RawMessage ( `{
"type": "math",
"expression": "2 + 3 > 1"
}` ),
},
},
NoDataState : apimodels . NoDataState ( ngmodels . Alerting ),
ExecErrState : apimodels . ExecutionErrorState ( ngmodels . AlertingErrState ),
},
},
},
}
2023-10-06 18:11:24 -04:00
resp , status , _ := apiClient . PostRulesGroupWithStatus ( t , "default" , & rules )
2022-06-21 11:39:22 -04:00
assert . Equal ( t , http . StatusAccepted , status )
2023-10-06 18:11:24 -04:00
require . Len ( t , resp . Created , len ( rules . Rules ))
2021-10-04 16:33:55 +01: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 18:50:10 +01:00
"datasourceUid": "__expr__",
2021-10-04 16:33:55 +01:00
"model": {
"expression": "2 + 3 \u003e 1",
"intervalMs": 1000,
"maxDataPoints": 43200,
"type": "math"
}
}],
"updated": "2021-02-21T01:10:30Z",
"intervalSeconds": 60,
2023-02-01 13:15:03 +01:00
"is_paused": false,
2021-10-04 16:33:55 +01:00
"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 11:46:26 -04:00
"for":"0s",
2021-10-04 16:33:55 +01:00
"grafana_alert": {
"id": 2,
"orgId": 1,
"title": "AlwaysFiringButSilenced",
"condition": "A",
"data": [{
"refId": "A",
"queryType": "",
"relativeTimeRange": {
"from": 18000,
"to": 10800
},
2023-01-31 18:50:10 +01:00
"datasourceUid": "__expr__",
2021-10-04 16:33:55 +01:00
"model": {
"expression": "2 + 3 \u003e 1",
"intervalMs": 1000,
"maxDataPoints": 43200,
"type": "math"
}
}],
"updated": "2021-02-21T01:10:30Z",
"intervalSeconds": 60,
2023-02-01 13:15:03 +01:00
"is_paused": false,
2021-10-04 16:33:55 +01:00
"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 18:50:10 +01:00
"datasourceUid": "__expr__",
2021-10-04 16:33:55 +01:00
"model": {
"expression": "2 + 3 \u003e 1",
"intervalMs": 1000,
"maxDataPoints": 43200,
"type": "math"
}
}],
"updated": "2021-02-21T01:10:30Z",
"intervalSeconds": 60,
2023-02-01 13:15:03 +01:00
"is_paused": false,
2021-10-04 16:33:55 +01:00
"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 13:37:51 +00:00
b , err := io . ReadAll ( resp . Body )
2021-10-04 16:33:55 +01: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 13:37:51 +00:00
b , err := io . ReadAll ( resp . Body )
2021-10-04 16:33:55 +01: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 13:37:51 +00:00
b , err := io . ReadAll ( resp . Body )
2021-10-04 16:33:55 +01: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 13:37:51 +00:00
b , err := io . ReadAll ( resp . Body )
2021-10-04 16:33:55 +01: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 13:37:51 +00:00
b , err := io . ReadAll ( resp . Body )
2021-10-04 16:33:55 +01: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 13:37:51 +00:00
b , err := io . ReadAll ( resp . Body )
2021-10-04 16:33:55 +01:00
require . NoError ( t , err )
2023-08-30 08:46:47 -07:00
var res map [ string ] any
2022-04-14 17:54:49 +02:00
require . NoError ( t , json . Unmarshal ( b , & res ))
require . Equal ( t , `invalid panel_id: strconv.ParseInt: parsing "invalid": invalid syntax` , res [ "message" ])
2021-10-04 16:33:55 +01: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 13:37:51 +00:00
b , err := io . ReadAll ( resp . Body )
2021-10-04 16:33:55 +01:00
require . NoError ( t , err )
2023-08-30 08:46:47 -07:00
var res map [ string ] any
2022-04-14 17:54:49 +02:00
require . NoError ( t , json . Unmarshal ( b , & res ))
require . Equal ( t , "panel_id must be set with dashboard_uid" , res [ "message" ])
2021-10-04 16:33:55 +01:00
}
}
2022-04-14 14:21:36 +02:00
2022-12-09 08:11:56 +01:00
func TestIntegrationRuleGroupSequence ( t * testing . T ) {
testinfra . SQLiteIntegrationTest ( t )
2022-06-22 10:52:46 -04: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 14:32:25 +02:00
createUser ( t , store , user . CreateUserCommand {
2022-08-10 11:56:48 +02:00
DefaultOrgRole : string ( org . RoleEditor ),
2022-06-22 10:52:46 -04: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 ())
2023-10-06 18:11:24 -04:00
_ , status , _ := client . PostRulesGroupWithStatus ( t , folder1Title , & group1 )
2022-06-22 10:52:46 -04:00
require . Equal ( t , http . StatusAccepted , status )
2023-10-06 18:11:24 -04:00
_ , status , _ = client . PostRulesGroupWithStatus ( t , folder1Title , & group2 )
2022-06-22 10:52:46 -04:00
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 )
}
2023-10-06 18:11:24 -04:00
_ , status , _ := client . PostRulesGroupWithStatus ( t , folder1Title , & postableGroup1 )
2022-06-22 10:52:46 -04:00
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 )
}
2023-10-06 18:11:24 -04:00
_ , status , _ := client . PostRulesGroupWithStatus ( t , folder1Title , & postableGroup1 )
2022-06-22 10:52:46 -04:00
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 08:11:56 +01:00
func TestIntegrationRuleUpdate ( t * testing . T ) {
testinfra . SQLiteIntegrationTest ( t )
2022-06-30 11:46:26 -04: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 )
2023-07-21 15:23:01 +01:00
permissionsStore := resourcepermissions . NewStore ( store , featuremgmt . WithFeatures ())
2022-06-30 11:46:26 -04:00
// Create a user to make authenticated requests
2023-06-15 13:33:42 -04:00
userID := createUser ( t , store , user . CreateUserCommand {
2022-08-10 11:56:48 +02:00
DefaultOrgRole : string ( org . RoleEditor ),
2022-06-30 11:46:26 -04:00
Password : "password" ,
Login : "grafana" ,
})
2023-06-15 13:33:42 -04:00
if setting . IsEnterprise {
// add blanket access to data sources.
_ , err := permissionsStore . SetUserResourcePermission ( context . Background (),
1 ,
accesscontrol . User { ID : userID },
resourcepermissions . SetResourcePermissionCommand {
Actions : [] string {
datasources . ActionQuery ,
},
Resource : datasources . ScopeRoot ,
ResourceID : "*" ,
ResourceAttribute : "uid" ,
}, nil )
require . NoError ( t , err )
}
// Create a user to make authenticated requests
createUser ( t , store , user . CreateUserCommand {
DefaultOrgRole : string ( org . RoleAdmin ),
Password : "admin" ,
Login : "admin" ,
})
adminClient := newAlertingApiClient ( grafanaListedAddr , "admin" , "admin" )
2022-06-30 11:46:26 -04:00
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
2023-10-06 18:11:24 -04:00
_ , status , body := client . PostRulesGroupWithStatus ( t , folder1Title , & group )
2022-06-30 11:46:26 -04:00
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
2023-10-06 18:11:24 -04:00
_ , status , body = client . PostRulesGroupWithStatus ( t , folder1Title , & group )
2022-06-30 11:46:26 -04:00
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 )
})
2023-06-15 13:33:42 -04:00
t . Run ( "when data source missing" , func ( t * testing . T ) {
var groupName string
{
ds1 := adminClient . CreateTestDatasource ( t )
group := generateAlertRuleGroup ( 3 , alertRuleGen ( withDatasourceQuery ( ds1 . Body . Datasource . UID )))
2023-10-06 18:11:24 -04:00
_ , status , body := client . PostRulesGroupWithStatus ( t , folder1Title , & group )
2023-06-15 13:33:42 -04:00
require . Equalf ( t , http . StatusAccepted , status , "failed to post rule group. Response: %s" , body )
getGroup := client . GetRulesGroup ( t , folder1Title , group . Name )
group = convertGettableRuleGroupToPostable ( getGroup . GettableRuleGroupConfig )
require . Len ( t , group . Rules , 3 )
adminClient . DeleteDatasource ( t , ds1 . Body . Datasource . UID )
// expire datasource caching
<- time . After ( datasourceService . DefaultCacheTTL + 1 * time . Second ) // TODO delete when TTL could be configured
groupName = group . Name
}
t . Run ( "noop should not fail" , func ( t * testing . T ) {
getGroup := client . GetRulesGroup ( t , folder1Title , groupName )
group := convertGettableRuleGroupToPostable ( getGroup . GettableRuleGroupConfig )
2023-10-06 18:11:24 -04:00
_ , status , body := client . PostRulesGroupWithStatus ( t , folder1Title , & group )
2023-06-15 13:33:42 -04:00
require . Equalf ( t , http . StatusAccepted , status , "failed to post noop rule group. Response: %s" , body )
})
t . Run ( "should not let update rule if it does not fix datasource" , func ( t * testing . T ) {
getGroup := client . GetRulesGroup ( t , folder1Title , groupName )
group := convertGettableRuleGroupToPostable ( getGroup . GettableRuleGroupConfig )
group . Rules [ 0 ]. GrafanaManagedAlert . Title = uuid . NewString ()
2023-10-06 18:11:24 -04:00
resp , status , body := client . PostRulesGroupWithStatus ( t , folder1Title , & group )
2023-06-15 13:33:42 -04:00
if status == http . StatusAccepted {
2023-10-06 18:11:24 -04:00
assert . Len ( t , resp . Deleted , 1 )
2023-06-15 13:33:42 -04:00
getGroup = client . GetRulesGroup ( t , folder1Title , group . Name )
assert . NotEqualf ( t , group . Rules [ 0 ]. GrafanaManagedAlert . Title , getGroup . Rules [ 0 ]. GrafanaManagedAlert . Title , "group was updated" )
}
require . Equalf ( t , http . StatusBadRequest , status , "expected BadRequest. Response: %s" , body )
assert . Contains ( t , body , "data source not found" )
})
t . Run ( "should let delete broken rule" , func ( t * testing . T ) {
getGroup := client . GetRulesGroup ( t , folder1Title , groupName )
group := convertGettableRuleGroupToPostable ( getGroup . GettableRuleGroupConfig )
// remove the last rule.
group . Rules = group . Rules [ 0 : len ( group . Rules ) - 1 ]
2023-10-06 18:11:24 -04:00
resp , status , body := client . PostRulesGroupWithStatus ( t , folder1Title , & group )
2023-06-15 13:33:42 -04:00
require . Equalf ( t , http . StatusAccepted , status , "failed to delete last rule from group. Response: %s" , body )
2023-10-06 18:11:24 -04:00
assert . Len ( t , resp . Deleted , 1 )
2023-06-15 13:33:42 -04:00
getGroup = client . GetRulesGroup ( t , folder1Title , group . Name )
group = convertGettableRuleGroupToPostable ( getGroup . GettableRuleGroupConfig )
require . Len ( t , group . Rules , 2 )
})
t . Run ( "should let fix single rule" , func ( t * testing . T ) {
getGroup := client . GetRulesGroup ( t , folder1Title , groupName )
group := convertGettableRuleGroupToPostable ( getGroup . GettableRuleGroupConfig )
ds2 := adminClient . CreateTestDatasource ( t )
withDatasourceQuery ( ds2 . Body . Datasource . UID )( & group . Rules [ 0 ])
2023-10-06 18:11:24 -04:00
resp , status , body := client . PostRulesGroupWithStatus ( t , folder1Title , & group )
2023-06-15 13:33:42 -04:00
require . Equalf ( t , http . StatusAccepted , status , "failed to post noop rule group. Response: %s" , body )
2023-10-06 18:11:24 -04:00
assert . Len ( t , resp . Deleted , 0 )
assert . Len ( t , resp . Updated , 2 )
assert . Len ( t , resp . Created , 0 )
2023-06-15 13:33:42 -04:00
getGroup = client . GetRulesGroup ( t , folder1Title , group . Name )
group = convertGettableRuleGroupToPostable ( getGroup . GettableRuleGroupConfig )
require . Equal ( t , ds2 . Body . Datasource . UID , group . Rules [ 0 ]. GrafanaManagedAlert . Data [ 0 ]. DatasourceUID )
})
t . Run ( "should let delete group" , func ( t * testing . T ) {
status , body := client . DeleteRulesGroup ( t , folder1Title , groupName )
require . Equalf ( t , http . StatusAccepted , status , "failed to post noop rule group. Response: %s" , body )
})
})
2022-06-30 11:46:26 -04:00
}
2022-04-14 14:21:36 +02: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 11:46:26 -04:00
For : & interval ,
2022-04-14 14:21:36 +02: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" ,
2023-03-27 11:55:13 -04:00
Data : [] apimodels . AlertQuery {
2022-04-14 14:21:36 +02:00
{
RefID : "A" ,
2023-03-27 11:55:13 -04:00
RelativeTimeRange : apimodels . RelativeTimeRange {
From : apimodels . Duration ( time . Duration ( 5 ) * time . Hour ),
To : apimodels . Duration ( time . Duration ( 3 ) * time . Hour ),
2022-04-14 14:21:36 +02:00
},
2023-01-31 18:50:10 +01:00
DatasourceUID : expr . DatasourceUID ,
2022-04-14 14:21:36 +02:00
Model : json . RawMessage ( `{
"type": "math",
"expression": "2 + 3 > 1"
}` ),
},
},
},
}
secondRule := apimodels . PostableExtendedRuleNode {
ApiRuleNode : & apimodels . ApiRuleNode {
2022-06-30 11:46:26 -04:00
For : & interval ,
2022-04-14 14:21:36 +02: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" ,
2023-03-27 11:55:13 -04:00
Data : [] apimodels . AlertQuery {
2022-04-14 14:21:36 +02:00
{
RefID : "A" ,
2023-03-27 11:55:13 -04:00
RelativeTimeRange : apimodels . RelativeTimeRange {
From : apimodels . Duration ( time . Duration ( 5 ) * time . Hour ),
To : apimodels . Duration ( time . Duration ( 3 ) * time . Hour ),
2022-04-14 14:21:36 +02:00
},
2023-01-31 18:50:10 +01:00
DatasourceUID : expr . DatasourceUID ,
2022-04-14 14:21:36 +02:00
Model : json . RawMessage ( `{
"type": "math",
"expression": "2 + 3 > 1"
}` ),
},
},
},
}
return apimodels . PostableRuleGroupConfig {
Name : "arulegroup" ,
Rules : [] apimodels . PostableExtendedRuleNode {
firstRule ,
secondRule ,
},
}
}
2023-02-01 13:15:03 +01:00
func TestIntegrationRulePause ( t * testing . T ) {
testinfra . SQLiteIntegrationTest ( t )
// 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 {
DefaultOrgRole : string ( org . RoleEditor ),
Password : "password" ,
Login : "grafana" ,
})
client := newAlertingApiClient ( grafanaListedAddr , "grafana" , "password" )
folder1Title := "folder1"
client . CreateFolder ( t , util . GenerateShortUID (), folder1Title )
t . Run ( "should create a paused rule if isPaused is true" , func ( t * testing . T ) {
group := generateAlertRuleGroup ( 1 , alertRuleGen ())
expectedIsPaused := true
group . Rules [ 0 ]. GrafanaManagedAlert . IsPaused = & expectedIsPaused
2023-10-06 18:11:24 -04:00
resp , status , body := client . PostRulesGroupWithStatus ( t , folder1Title , & group )
2023-02-01 13:15:03 +01:00
require . Equalf ( t , http . StatusAccepted , status , "failed to post rule group. Response: %s" , body )
2023-10-06 18:11:24 -04:00
require . Len ( t , resp . Created , 1 )
2023-02-01 13:15:03 +01:00
getGroup := client . GetRulesGroup ( t , folder1Title , group . Name )
require . Equalf ( t , http . StatusAccepted , status , "failed to get rule group. Response: %s" , body )
require . Equal ( t , expectedIsPaused , getGroup . Rules [ 0 ]. GrafanaManagedAlert . IsPaused )
})
t . Run ( "should create a unpaused rule if isPaused is false" , func ( t * testing . T ) {
group := generateAlertRuleGroup ( 1 , alertRuleGen ())
expectedIsPaused := false
group . Rules [ 0 ]. GrafanaManagedAlert . IsPaused = & expectedIsPaused
2023-10-06 18:11:24 -04:00
resp , status , body := client . PostRulesGroupWithStatus ( t , folder1Title , & group )
2023-02-01 13:15:03 +01:00
require . Equalf ( t , http . StatusAccepted , status , "failed to post rule group. Response: %s" , body )
2023-10-06 18:11:24 -04:00
require . Len ( t , resp . Created , 1 )
2023-02-01 13:15:03 +01:00
getGroup := client . GetRulesGroup ( t , folder1Title , group . Name )
require . Equalf ( t , http . StatusAccepted , status , "failed to get rule group. Response: %s" , body )
require . Equal ( t , expectedIsPaused , getGroup . Rules [ 0 ]. GrafanaManagedAlert . IsPaused )
})
t . Run ( "should create a unpaused rule if isPaused is not present" , func ( t * testing . T ) {
group := generateAlertRuleGroup ( 1 , alertRuleGen ())
group . Rules [ 0 ]. GrafanaManagedAlert . IsPaused = nil
2023-10-06 18:11:24 -04:00
resp , status , body := client . PostRulesGroupWithStatus ( t , folder1Title , & group )
2023-02-01 13:15:03 +01:00
require . Equalf ( t , http . StatusAccepted , status , "failed to post rule group. Response: %s" , body )
2023-10-06 18:11:24 -04:00
require . Len ( t , resp . Created , 1 )
2023-02-01 13:15:03 +01:00
getGroup := client . GetRulesGroup ( t , folder1Title , group . Name )
require . Equalf ( t , http . StatusAccepted , status , "failed to get rule group. Response: %s" , body )
require . False ( t , getGroup . Rules [ 0 ]. GrafanaManagedAlert . IsPaused )
})
getBooleanPointer := func ( b bool ) * bool { return & b }
testCases := [] struct {
description string
isPausedInDb bool
isPausedInBody * bool
expectedIsPausedInDb bool
}{
{
description : "should pause rule if there is a paused rule in DB and isPaused is true" ,
isPausedInDb : true ,
isPausedInBody : getBooleanPointer ( true ),
expectedIsPausedInDb : true ,
},
{
description : "should unpause rule if there is a paused rule in DB and isPaused is false" ,
isPausedInDb : true ,
isPausedInBody : getBooleanPointer ( false ),
expectedIsPausedInDb : false ,
},
{
description : "should keep rule paused if there is a paused rule in DB and isPaused is not present" ,
isPausedInDb : true ,
isPausedInBody : nil ,
expectedIsPausedInDb : true ,
},
{
description : "should pause rule if there is an unpaused rule in DB and isPaused is true" ,
isPausedInDb : false ,
isPausedInBody : getBooleanPointer ( true ),
expectedIsPausedInDb : true ,
},
{
description : "should unpause rule if there is an unpaused rule in DB and isPaused is false" ,
isPausedInDb : false ,
isPausedInBody : getBooleanPointer ( false ),
expectedIsPausedInDb : false ,
},
{
description : "should keep rule unpaused if there is an unpaused rule in DB and isPaused is not present" ,
isPausedInDb : false ,
isPausedInBody : nil ,
expectedIsPausedInDb : false ,
},
}
for _ , tc := range testCases {
t . Run ( tc . description , func ( t * testing . T ) {
group := generateAlertRuleGroup ( 1 , alertRuleGen ())
group . Rules [ 0 ]. GrafanaManagedAlert . IsPaused = & tc . isPausedInDb
2023-10-06 18:11:24 -04:00
_ , status , body := client . PostRulesGroupWithStatus ( t , folder1Title , & group )
2023-02-01 13:15:03 +01:00
require . Equalf ( t , http . StatusAccepted , status , "failed to post rule group. Response: %s" , body )
getGroup := client . GetRulesGroup ( t , folder1Title , group . Name )
require . Equalf ( t , http . StatusAccepted , status , "failed to get rule group. Response: %s" , body )
group = convertGettableRuleGroupToPostable ( getGroup . GettableRuleGroupConfig )
group . Rules [ 0 ]. GrafanaManagedAlert . IsPaused = tc . isPausedInBody
2023-10-06 18:11:24 -04:00
_ , status , body = client . PostRulesGroupWithStatus ( t , folder1Title , & group )
2023-02-01 13:15:03 +01:00
require . Equalf ( t , http . StatusAccepted , status , "failed to post rule group. Response: %s" , body )
getGroup = client . GetRulesGroup ( t , folder1Title , group . Name )
require . Equal ( t , tc . expectedIsPausedInDb , getGroup . Rules [ 0 ]. GrafanaManagedAlert . IsPaused )
})
}
}