2021-04-29 11:15:15 -05:00
package alerting
import (
2023-06-15 12:33:42 -05:00
"context"
2023-10-05 14:47:49 -05:00
"embed"
2021-04-29 11:15:15 -05:00
"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"
2023-10-05 14:47:49 -05:00
"path"
"strings"
2021-04-29 11:15:15 -05:00
"testing"
"time"
2023-10-05 14:47:49 -05:00
"github.com/google/go-cmp/cmp"
2023-06-15 12:33:42 -05:00
"github.com/google/uuid"
2024-01-04 10:47:13 -06:00
"github.com/grafana/grafana-plugin-sdk-go/data"
2022-02-23 10:30:04 -06:00
"github.com/prometheus/common/model"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
2023-06-15 12:33:42 -05:00
"github.com/grafana/grafana/pkg/expr"
"github.com/grafana/grafana/pkg/services/accesscontrol"
2022-08-18 02:43:45 -05:00
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
2023-06-15 12:33:42 -05:00
"github.com/grafana/grafana/pkg/services/datasources"
datasourceService "github.com/grafana/grafana/pkg/services/datasources/service"
2023-07-21 09:23:01 -05:00
"github.com/grafana/grafana/pkg/services/featuremgmt"
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"
2023-06-15 12:33:42 -05:00
"github.com/grafana/grafana/pkg/setting"
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
)
2023-10-05 14:47:49 -05:00
//go:embed test-data/*.*
var testData embed . FS
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
2023-10-05 14:47:49 -05:00
dir , p := 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
2023-10-05 14:47:49 -05:00
grafanaListedAddr , store := testinfra . StartGrafana ( t , dir , p )
2023-07-21 09:23:01 -05:00
permissionsStore := resourcepermissions . NewStore ( store , featuremgmt . WithFeatures ( ) )
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
2023-10-05 14:47:49 -05: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 11:15:15 -05:00
// Create rule under folder1
2023-10-06 17:11:24 -05:00
_ , status , response := apiClient . PostRulesGroupWithStatus ( t , "folder1" , & group1 )
2023-10-05 14:47:49 -05: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 11:15:15 -05:00
// Create rule under folder2
2023-10-06 17:11:24 -05:00
_ , status , response = apiClient . PostRulesGroupWithStatus ( t , "folder2" , & group2 )
2023-10-05 14:47:49 -05:00
require . Equalf ( t , http . StatusAccepted , status , response )
2021-04-29 11:15:15 -05:00
// With the rules created, let's make sure that rule definitions are stored.
2023-10-05 14:47:49 -05: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 11:15:15 -05:00
require . NoError ( t , err )
2023-10-05 14:47:49 -05: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 )
}
for _ , rule := range allRules [ "folder2" ] [ 0 ] . Rules {
assert . Equal ( t , "folder2" , rule . GrafanaManagedAlert . NamespaceUID )
}
} )
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 11:15:15 -05:00
} )
2023-10-05 14:47:49 -05: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 11:15:15 -05:00
}
2023-10-05 14:47:49 -05: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 11:15:15 -05:00
}
2023-10-05 14:47:49 -05:00
status , exportRaw := apiClient . ExportRulesWithStatus ( t , & apimodels . AlertRulesExportParameters {
ExportQueryParams : apimodels . ExportQueryParams { Format : "json" } ,
RuleUID : expected . Rules [ 0 ] . UID ,
} )
2021-04-29 11:15:15 -05:00
2023-10-05 14:47:49 -05: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 11:15:15 -05:00
// 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
2023-10-05 14:47:49 -05: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 11:15:15 -05:00
} )
2023-10-05 14:47:49 -05: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 07:26:08 -06:00
2023-10-05 14:47:49 -05: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 07:26:08 -06:00
require . NoError ( t , err )
2023-10-05 14:47:49 -05:00
defer func ( ) {
_ = resp . Body . Close ( )
} ( )
assert . Equal ( t , http . StatusForbidden , resp . StatusCode )
2021-11-08 07:26:08 -06:00
} )
2023-10-05 14:47:49 -05: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" } ,
} )
2023-12-07 12:43:58 -06:00
assert . Equal ( t , http . StatusForbidden , status )
2023-10-05 14:47:49 -05:00
} )
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 11:15:15 -05:00
}
2024-01-17 03:07:39 -06:00
func TestIntegrationAlertRuleNestedPermissions ( t * testing . T ) {
testinfra . SQLiteIntegrationTest ( t )
// Setup Grafana and its Database
dir , p := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
EnableFeatureToggles : [ ] string { featuremgmt . FlagNestedFolders } ,
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" )
// Create the namespace we'll save our alerts to.
apiClient . CreateFolder ( t , "folder2" , "folder2" )
// Create a subfolder
apiClient . CreateFolder ( t , "subfolder" , "subfolder" , "folder1" )
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 ) )
// Create rule under folder1
_ , status , response := apiClient . PostRulesGroupWithStatus ( t , "folder1" , & group1 )
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 ) )
// Create rule under folder2
_ , status , response = apiClient . PostRulesGroupWithStatus ( t , "folder2" , & group2 )
require . Equalf ( t , http . StatusAccepted , status , response )
postGroupRaw , err = testData . ReadFile ( path . Join ( "test-data" , "rulegroup-3-post.json" ) )
require . NoError ( t , err )
var group3 apimodels . PostableRuleGroupConfig
require . NoError ( t , json . Unmarshal ( postGroupRaw , & group3 ) )
// Create rule under subfolder
_ , status , response = apiClient . PostRulesGroupWithStatus ( t , "subfolder" , & group3 )
require . Equalf ( t , http . StatusAccepted , status , response )
// With the rules created, let's make sure that rule definitions are stored.
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 , group3 apimodels . GettableRuleGroupConfig
getGroup1Raw , err := testData . ReadFile ( path . Join ( "test-data" , "rulegroup-1-get.json" ) )
require . NoError ( t , err )
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 ) )
getGroup3Raw , err := testData . ReadFile ( path . Join ( "test-data" , "rulegroup-3-get.json" ) )
require . NoError ( t , err )
require . NoError ( t , json . Unmarshal ( getGroup3Raw , & group3 ) )
nestedKey := ngmodels . GetNamespaceKey ( "folder1" , "subfolder" )
expected := apimodels . NamespaceConfigResponse {
"folder1" : [ ] apimodels . GettableRuleGroupConfig {
group1 ,
} ,
"folder2" : [ ] apimodels . GettableRuleGroupConfig {
group2 ,
} ,
nestedKey : [ ] apimodels . GettableRuleGroupConfig {
group3 ,
} ,
}
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 )
}
for _ , rule := range allRules [ "folder2" ] [ 0 ] . Rules {
assert . Equal ( t , "folder2" , rule . GrafanaManagedAlert . NamespaceUID )
}
for _ , rule := range allRules [ nestedKey ] [ 0 ] . Rules {
assert . Equal ( t , "subfolder" , rule . GrafanaManagedAlert . NamespaceUID )
}
} )
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" ] )
} )
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 ( "Get by folder returns groups in folder with nested folder format" , func ( t * testing . T ) {
rules , status , _ := apiClient . GetAllRulesGroupInFolderWithStatus ( t , "subfolder" )
require . Equal ( t , http . StatusAccepted , status )
nestedKey := ngmodels . GetNamespaceKey ( "folder1" , "subfolder" )
require . Contains ( t , rules , nestedKey )
require . Len ( t , rules [ nestedKey ] , 1 )
require . Equal ( t , allRules [ nestedKey ] , rules [ nestedKey ] )
} )
t . Run ( "Export returns all rules" , func ( t * testing . T ) {
var group1File , group2File , group3File 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 ) )
getGroup3Raw , err := testData . ReadFile ( path . Join ( "test-data" , "rulegroup-3-export.json" ) )
require . NoError ( t , err )
require . NoError ( t , json . Unmarshal ( getGroup3Raw , & group3File ) )
group1File . Groups = append ( group1File . Groups , group2File . Groups ... )
group1File . Groups = append ( group1File . Groups , group3File . 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
}
}
return false
} , cmp . Ignore ( ) ) )
require . Empty ( t , diff )
require . Equal ( t , "folder1" , allExport . Groups [ 0 ] . Folder )
require . Equal ( t , "folder2" , allExport . Groups [ 1 ] . Folder )
require . Equal ( t , "subfolder" , allExport . Groups [ 2 ] . 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 a subfolder" , func ( t * testing . T ) {
expected := allExport . Groups [ 2 ]
status , exportRaw := apiClient . ExportRulesWithStatus ( t , & apimodels . AlertRulesExportParameters {
ExportQueryParams : apimodels . ExportQueryParams { Format : "json" } ,
FolderUID : [ ] string { "subfolder" } ,
} )
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 from one group under subfolder" , func ( t * testing . T ) {
expected := allExport . Groups [ 2 ]
status , exportRaw := apiClient . ExportRulesWithStatus ( t , & apimodels . AlertRulesExportParameters {
ExportQueryParams : apimodels . ExportQueryParams { Format : "json" } ,
FolderUID : [ ] string { "subfolder" } ,
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 ] ,
}
status , exportRaw := apiClient . ExportRulesWithStatus ( t , & apimodels . AlertRulesExportParameters {
ExportQueryParams : apimodels . ExportQueryParams { Format : "json" } ,
RuleUID : expected . Rules [ 0 ] . UID ,
} )
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 ) {
// remove permissions for folder2
removeFolderPermission ( t , permissionsStore , 1 , userID , org . RoleEditor , "folder2" )
// remove permissions for subfolder (inherits from folder1)
removeFolderPermission ( t , permissionsStore , 1 , userID , org . RoleEditor , "subfolder" )
apiClient . ReloadCachedPermissions ( t )
t . Run ( "Get all returns all rules" , func ( t * testing . T ) {
newAll , status , _ := apiClient . GetAllRulesWithStatus ( t )
require . Equal ( t , http . StatusOK , status )
require . Contains ( t , newAll , "folder1" )
require . NotContains ( t , newAll , "folder2" )
require . Contains ( t , newAll , ngmodels . GetNamespaceKey ( "folder1" , "subfolder" ) )
} )
t . Run ( "Get by folder returns groups in folder" , func ( t * testing . T ) {
_ , status , _ := apiClient . GetAllRulesGroupInFolderWithStatus ( t , "folder2" )
require . Equal ( t , http . StatusForbidden , status )
} )
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 )
require . NoError ( t , err )
defer func ( ) {
_ = resp . Body . Close ( )
} ( )
assert . Equal ( t , http . StatusForbidden , resp . StatusCode )
} )
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 , 2 )
require . Equal ( t , "folder1" , export . Groups [ 0 ] . Folder )
require . Equal ( t , "subfolder" , export . Groups [ 1 ] . 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 . StatusForbidden , 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 )
} )
} )
}
2023-10-06 17:11:24 -05:00
func createRule ( t * testing . T , client apiClient , folder string ) ( apimodels . PostableRuleGroupConfig , 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" ,
2023-03-27 10:55:13 -05:00
Data : [ ] apimodels . AlertQuery {
2021-04-29 11:15:15 -05:00
{
RefID : "A" ,
2023-03-27 10:55:13 -05:00
RelativeTimeRange : apimodels . RelativeTimeRange {
From : apimodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : apimodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
2021-04-29 11:15:15 -05:00
} ,
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"
} ` ) ,
} ,
} ,
} ,
} ,
} ,
}
2023-10-06 17:11:24 -05:00
resp , status , _ := client . PostRulesGroupWithStatus ( t , folder , & rules )
2022-06-21 10:39:22 -05:00
assert . Equal ( t , http . StatusAccepted , status )
2023-10-06 17:11:24 -05:00
require . Len ( t , resp . Created , 1 )
return rules , resp . Created [ 0 ]
2023-10-05 14:47:49 -05: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 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
2023-10-06 17:11:24 -05:00
respModel , status , _ := apiClient . PostRulesGroupWithStatus ( t , "folder1" , & rules )
2022-06-21 10:39:22 -05:00
assert . Equal ( t , http . StatusAccepted , status )
2023-10-06 17:11:24 -05:00
require . Len ( t , respModel . Created , len ( rules . Rules ) )
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 ) {
2023-06-08 17:51:50 -05:00
rulesWithUID := convertGettableRuleGroupToPostable ( createdRuleGroup )
rulesWithUID . Rules = append ( rulesWithUID . Rules , rules . Rules [ 0 ] ) // Create new copy of first rule.
2022-04-14 07:21:36 -05:00
2023-10-06 17:11:24 -05:00
_ , status , body := apiClient . PostRulesGroupWithStatus ( t , "folder1" , & rulesWithUID )
2022-06-21 10:39:22 -05:00
assert . Equal ( t , http . StatusInternalServerError , status )
2022-04-14 10:54:49 -05:00
2023-08-30 10:46:47 -05:00
var res map [ string ] any
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 ) {
2023-06-08 17:51:50 -05:00
rulesWithUID := convertGettableRuleGroupToPostable ( createdRuleGroup )
rulesWithUID . Rules [ 1 ] . GrafanaManagedAlert . Title = "AlwaysFiring"
2021-06-04 12:45:26 -05:00
2023-10-06 17:11:24 -05:00
_ , status , body := apiClient . PostRulesGroupWithStatus ( t , "folder1" , & rulesWithUID )
2022-06-21 10:39:22 -05:00
assert . Equal ( t , http . StatusInternalServerError , status )
2022-04-14 10:54:49 -05:00
2023-08-30 10:46:47 -05:00
var res map [ string ] any
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 )
2023-10-06 17:11:24 -05:00
resp , status , _ := apiClient . PostRulesGroupWithStatus ( t , "folder2" , & rules )
2022-06-21 10:39:22 -05:00
assert . Equal ( t , http . StatusAccepted , status )
2023-10-06 17:11:24 -05:00
require . Len ( t , resp . Created , len ( rules . Rules ) )
2023-06-08 17:51:50 -05: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 17:11:24 -05:00
resp , status , _ := apiClient . PostRulesGroupWithStatus ( t , "folder1" , & rulesWithUID )
2023-06-08 17:51:50 -05:00
assert . Equal ( t , http . StatusAccepted , status )
2023-10-06 17:11:24 -05:00
require . Len ( t , resp . Updated , 2 )
2023-06-08 17:51:50 -05: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 17:11:24 -05:00
resp , status , _ := apiClient . PostRulesGroupWithStatus ( t , "folder1" , & rulesWithUID )
2023-06-08 17:51:50 -05:00
assert . Equal ( t , http . StatusAccepted , status )
2023-10-06 17:11:24 -05:00
require . Len ( t , resp . Updated , len ( rulesWithUID . Rules ) )
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" ,
2023-03-27 10:55:13 -05:00
Data : [ ] apimodels . AlertQuery {
2021-10-04 10:33:55 -05:00
{
RefID : "A" ,
2023-03-27 10:55:13 -05:00
RelativeTimeRange : apimodels . RelativeTimeRange {
From : apimodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : apimodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
2021-10-04 10:33:55 -05:00
} ,
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" ,
2023-03-27 10:55:13 -05:00
Data : [ ] apimodels . AlertQuery {
2021-10-04 10:33:55 -05:00
{
RefID : "A" ,
2023-03-27 10:55:13 -05:00
RelativeTimeRange : apimodels . RelativeTimeRange {
From : apimodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : apimodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
2021-10-04 10:33:55 -05:00
} ,
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 ) ,
} ,
} ,
} ,
}
2023-10-06 17:11:24 -05:00
resp , status , _ := apiClient . PostRulesGroupWithStatus ( t , "default" , & rules )
2022-06-21 10:39:22 -05:00
assert . Equal ( t , http . StatusAccepted , status )
2023-10-06 17:11:24 -05:00
require . Len ( t , resp . Created , len ( rules . Rules ) )
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 ,
2023-02-01 06:15:03 -06:00
"is_paused" : false ,
2021-10-04 10:33:55 -05:00
"version" : 1 ,
"uid" : "uid" ,
"namespace_uid" : "nsuid" ,
"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 ,
2023-02-01 06:15:03 -06:00
"is_paused" : false ,
2021-10-04 10:33:55 -05:00
"version" : 1 ,
"uid" : "uid" ,
"namespace_uid" : "nsuid" ,
"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 ,
2023-02-01 06:15:03 -06:00
"is_paused" : false ,
2021-10-04 10:33:55 -05:00
"version" : 1 ,
"uid" : "uid" ,
"namespace_uid" : "nsuid" ,
"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 )
2023-08-30 10:46:47 -05:00
var res map [ string ] any
2022-04-14 10:54:49 -05: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 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 )
2023-08-30 10:46:47 -05:00
var res map [ string ] any
2022-04-14 10:54:49 -05:00
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" )
2024-01-17 03:07:39 -06:00
parentFolderUID := util . GenerateShortUID ( )
client . CreateFolder ( t , parentFolderUID , "parent" )
folderUID := util . GenerateShortUID ( )
client . CreateFolder ( t , folderUID , "folder1" , parentFolderUID )
2022-06-22 09:52:46 -05:00
group1 := generateAlertRuleGroup ( 5 , alertRuleGen ( ) )
group2 := generateAlertRuleGroup ( 5 , alertRuleGen ( ) )
2024-01-17 03:07:39 -06:00
_ , status , _ := client . PostRulesGroupWithStatus ( t , folderUID , & group1 )
2022-06-22 09:52:46 -05:00
require . Equal ( t , http . StatusAccepted , status )
2024-01-17 03:07:39 -06:00
_ , status , _ = client . PostRulesGroupWithStatus ( t , folderUID , & group2 )
2022-06-22 09:52:46 -05:00
require . Equal ( t , http . StatusAccepted , status )
t . Run ( "should persist order of the rules in a group" , func ( t * testing . T ) {
2024-01-17 03:07:39 -06:00
group1Get := client . GetRulesGroup ( t , folderUID , group1 . Name )
2022-06-22 09:52:46 -05:00
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 )
}
2024-01-17 03:07:39 -06:00
_ , status , _ := client . PostRulesGroupWithStatus ( t , folderUID , & postableGroup1 )
2022-06-22 09:52:46 -05:00
require . Equal ( t , http . StatusAccepted , status )
2024-01-17 03:07:39 -06:00
group1Get = client . GetRulesGroup ( t , folderUID , group1 . Name )
2022-06-22 09:52:46 -05:00
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 ) {
2024-01-17 03:07:39 -06:00
group1Get := client . GetRulesGroup ( t , folderUID , group1 . Name )
group2Get := client . GetRulesGroup ( t , folderUID , group2 . Name )
2022-06-22 09:52:46 -05:00
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 )
}
2024-01-17 03:07:39 -06:00
_ , status , _ := client . PostRulesGroupWithStatus ( t , folderUID , & postableGroup1 )
2022-06-22 09:52:46 -05:00
require . Equal ( t , http . StatusAccepted , status )
2024-01-17 03:07:39 -06:00
group1Get = client . GetRulesGroup ( t , folderUID , group1 . Name )
2022-06-22 09:52:46 -05:00
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 )
2024-01-17 03:07:39 -06:00
group2Get = client . GetRulesGroup ( t , folderUID , group2 . Name )
2022-06-22 09:52:46 -05:00
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 )
2023-07-21 09:23:01 -05:00
permissionsStore := resourcepermissions . NewStore ( store , featuremgmt . WithFeatures ( ) )
2022-06-30 10:46:26 -05:00
// Create a user to make authenticated requests
2023-06-15 12:33:42 -05:00
userID := 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" ,
} )
2023-06-15 12:33:42 -05: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 10:46:26 -05:00
client := newAlertingApiClient ( grafanaListedAddr , "grafana" , "password" )
2024-01-17 03:07:39 -06:00
folderUID := util . GenerateShortUID ( )
client . CreateFolder ( t , folderUID , "folder1" )
2022-06-30 10:46:26 -05:00
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
2024-01-17 03:07:39 -06:00
_ , status , body := client . PostRulesGroupWithStatus ( t , folderUID , & group )
2022-06-30 10:46:26 -05:00
require . Equalf ( t , http . StatusAccepted , status , "failed to post rule group. Response: %s" , body )
2024-01-17 03:07:39 -06:00
getGroup := client . GetRulesGroup ( t , folderUID , group . Name )
2022-06-30 10:46:26 -05:00
require . Equal ( t , expected , * getGroup . Rules [ 0 ] . ApiRuleNode . For )
group = convertGettableRuleGroupToPostable ( getGroup . GettableRuleGroupConfig )
expected = 0
group . Rules [ 0 ] . ApiRuleNode . For = & expected
2024-01-17 03:07:39 -06:00
_ , status , body = client . PostRulesGroupWithStatus ( t , folderUID , & group )
2022-06-30 10:46:26 -05:00
require . Equalf ( t , http . StatusAccepted , status , "failed to post rule group. Response: %s" , body )
2024-01-17 03:07:39 -06:00
getGroup = client . GetRulesGroup ( t , folderUID , group . Name )
2022-06-30 10:46:26 -05:00
require . Equal ( t , expected , * getGroup . Rules [ 0 ] . ApiRuleNode . For )
} )
2023-06-15 12:33:42 -05: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 ) ) )
2024-01-17 03:07:39 -06:00
_ , status , body := client . PostRulesGroupWithStatus ( t , folderUID , & group )
2023-06-15 12:33:42 -05:00
require . Equalf ( t , http . StatusAccepted , status , "failed to post rule group. Response: %s" , body )
2024-01-17 03:07:39 -06:00
getGroup := client . GetRulesGroup ( t , folderUID , group . Name )
2023-06-15 12:33:42 -05:00
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 ) {
2024-01-17 03:07:39 -06:00
getGroup := client . GetRulesGroup ( t , folderUID , groupName )
2023-06-15 12:33:42 -05:00
group := convertGettableRuleGroupToPostable ( getGroup . GettableRuleGroupConfig )
2024-01-17 03:07:39 -06:00
_ , status , body := client . PostRulesGroupWithStatus ( t , folderUID , & group )
2023-06-15 12:33:42 -05: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 ) {
2024-01-17 03:07:39 -06:00
getGroup := client . GetRulesGroup ( t , folderUID , groupName )
2023-06-15 12:33:42 -05:00
group := convertGettableRuleGroupToPostable ( getGroup . GettableRuleGroupConfig )
group . Rules [ 0 ] . GrafanaManagedAlert . Title = uuid . NewString ( )
2024-01-17 03:07:39 -06:00
resp , status , body := client . PostRulesGroupWithStatus ( t , folderUID , & group )
2023-06-15 12:33:42 -05:00
if status == http . StatusAccepted {
2023-10-06 17:11:24 -05:00
assert . Len ( t , resp . Deleted , 1 )
2024-01-17 03:07:39 -06:00
getGroup = client . GetRulesGroup ( t , folderUID , group . Name )
2023-06-15 12:33:42 -05:00
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 ) {
2024-01-17 03:07:39 -06:00
getGroup := client . GetRulesGroup ( t , folderUID , groupName )
2023-06-15 12:33:42 -05:00
group := convertGettableRuleGroupToPostable ( getGroup . GettableRuleGroupConfig )
// remove the last rule.
group . Rules = group . Rules [ 0 : len ( group . Rules ) - 1 ]
2024-01-17 03:07:39 -06:00
resp , status , body := client . PostRulesGroupWithStatus ( t , folderUID , & group )
2023-06-15 12:33:42 -05:00
require . Equalf ( t , http . StatusAccepted , status , "failed to delete last rule from group. Response: %s" , body )
2023-10-06 17:11:24 -05:00
assert . Len ( t , resp . Deleted , 1 )
2023-06-15 12:33:42 -05:00
2024-01-17 03:07:39 -06:00
getGroup = client . GetRulesGroup ( t , folderUID , group . Name )
2023-06-15 12:33:42 -05:00
group = convertGettableRuleGroupToPostable ( getGroup . GettableRuleGroupConfig )
require . Len ( t , group . Rules , 2 )
} )
t . Run ( "should let fix single rule" , func ( t * testing . T ) {
2024-01-17 03:07:39 -06:00
getGroup := client . GetRulesGroup ( t , folderUID , groupName )
2023-06-15 12:33:42 -05:00
group := convertGettableRuleGroupToPostable ( getGroup . GettableRuleGroupConfig )
ds2 := adminClient . CreateTestDatasource ( t )
withDatasourceQuery ( ds2 . Body . Datasource . UID ) ( & group . Rules [ 0 ] )
2024-01-17 03:07:39 -06:00
resp , status , body := client . PostRulesGroupWithStatus ( t , folderUID , & group )
2023-06-15 12:33:42 -05:00
require . Equalf ( t , http . StatusAccepted , status , "failed to post noop rule group. Response: %s" , body )
2023-10-06 17:11:24 -05:00
assert . Len ( t , resp . Deleted , 0 )
assert . Len ( t , resp . Updated , 2 )
assert . Len ( t , resp . Created , 0 )
2023-06-15 12:33:42 -05:00
2024-01-17 03:07:39 -06:00
getGroup = client . GetRulesGroup ( t , folderUID , group . Name )
2023-06-15 12:33:42 -05:00
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 ) {
2024-01-17 03:07:39 -06:00
status , body := client . DeleteRulesGroup ( t , folderUID , groupName )
2023-06-15 12:33:42 -05:00
require . Equalf ( t , http . StatusAccepted , status , "failed to post noop rule group. Response: %s" , body )
} )
} )
2022-06-30 10:46:26 -05:00
}
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" ,
2023-03-27 10:55:13 -05:00
Data : [ ] apimodels . AlertQuery {
2022-04-14 07:21:36 -05:00
{
RefID : "A" ,
2023-03-27 10:55:13 -05:00
RelativeTimeRange : apimodels . RelativeTimeRange {
From : apimodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : apimodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
2022-04-14 07:21:36 -05:00
} ,
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" ,
2023-03-27 10:55:13 -05:00
Data : [ ] apimodels . AlertQuery {
2022-04-14 07:21:36 -05:00
{
RefID : "A" ,
2023-03-27 10:55:13 -05:00
RelativeTimeRange : apimodels . RelativeTimeRange {
From : apimodels . Duration ( time . Duration ( 5 ) * time . Hour ) ,
To : apimodels . Duration ( time . Duration ( 3 ) * time . Hour ) ,
2022-04-14 07:21:36 -05:00
} ,
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 ,
} ,
}
}
2023-02-01 06:15:03 -06: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" )
2024-01-17 03:07:39 -06:00
folderUID := util . GenerateShortUID ( )
client . CreateFolder ( t , folderUID , "folder1" )
2023-02-01 06:15:03 -06:00
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
2024-01-17 03:07:39 -06:00
resp , status , body := client . PostRulesGroupWithStatus ( t , folderUID , & group )
2023-02-01 06:15:03 -06:00
require . Equalf ( t , http . StatusAccepted , status , "failed to post rule group. Response: %s" , body )
2023-10-06 17:11:24 -05:00
require . Len ( t , resp . Created , 1 )
2024-01-17 03:07:39 -06:00
getGroup := client . GetRulesGroup ( t , folderUID , group . Name )
2023-02-01 06:15:03 -06:00
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
2024-01-17 03:07:39 -06:00
resp , status , body := client . PostRulesGroupWithStatus ( t , folderUID , & group )
2023-02-01 06:15:03 -06:00
require . Equalf ( t , http . StatusAccepted , status , "failed to post rule group. Response: %s" , body )
2023-10-06 17:11:24 -05:00
require . Len ( t , resp . Created , 1 )
2024-01-17 03:07:39 -06:00
getGroup := client . GetRulesGroup ( t , folderUID , group . Name )
2023-02-01 06:15:03 -06:00
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
2024-01-17 03:07:39 -06:00
resp , status , body := client . PostRulesGroupWithStatus ( t , folderUID , & group )
2023-02-01 06:15:03 -06:00
require . Equalf ( t , http . StatusAccepted , status , "failed to post rule group. Response: %s" , body )
2023-10-06 17:11:24 -05:00
require . Len ( t , resp . Created , 1 )
2024-01-17 03:07:39 -06:00
getGroup := client . GetRulesGroup ( t , folderUID , group . Name )
2023-02-01 06:15:03 -06:00
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
2024-01-17 03:07:39 -06:00
_ , status , body := client . PostRulesGroupWithStatus ( t , folderUID , & group )
2023-02-01 06:15:03 -06:00
require . Equalf ( t , http . StatusAccepted , status , "failed to post rule group. Response: %s" , body )
2024-01-17 03:07:39 -06:00
getGroup := client . GetRulesGroup ( t , folderUID , group . Name )
2023-02-01 06:15:03 -06:00
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
2024-01-17 03:07:39 -06:00
_ , status , body = client . PostRulesGroupWithStatus ( t , folderUID , & group )
2023-02-01 06:15:03 -06:00
require . Equalf ( t , http . StatusAccepted , status , "failed to post rule group. Response: %s" , body )
2024-01-17 03:07:39 -06:00
getGroup = client . GetRulesGroup ( t , folderUID , group . Name )
2023-02-01 06:15:03 -06:00
require . Equal ( t , tc . expectedIsPausedInDb , getGroup . Rules [ 0 ] . GrafanaManagedAlert . IsPaused )
} )
}
}
2024-01-04 10:47:13 -06:00
func TestIntegrationHysteresisRule ( t * testing . T ) {
testinfra . SQLiteIntegrationTest ( t )
// Setup Grafana and its Database. Scheduler is set to evaluate every 1 second
dir , p := testinfra . CreateGrafDir ( t , testinfra . GrafanaOpts {
DisableLegacyAlerting : true ,
EnableUnifiedAlerting : true ,
DisableAnonymous : true ,
AppModeProduction : true ,
NGAlertSchedulerBaseInterval : 1 * time . Second ,
EnableFeatureToggles : [ ] string { featuremgmt . FlagConfigurableSchedulerTick , featuremgmt . FlagRecoveryThreshold } ,
} )
grafanaListedAddr , store := testinfra . StartGrafana ( t , dir , p )
// Create a user to make authenticated requests
createUser ( t , store , user . CreateUserCommand {
DefaultOrgRole : string ( org . RoleAdmin ) ,
Password : "password" ,
Login : "grafana" ,
} )
apiClient := newAlertingApiClient ( grafanaListedAddr , "grafana" , "password" )
folder := "hysteresis"
testDs := apiClient . CreateTestDatasource ( t )
apiClient . CreateFolder ( t , folder , folder )
bodyRaw , err := testData . ReadFile ( "test-data/hysteresis_rule.json" )
require . NoError ( t , err )
var postData apimodels . PostableRuleGroupConfig
require . NoError ( t , json . Unmarshal ( bodyRaw , & postData ) )
for _ , rule := range postData . Rules {
for i := range rule . GrafanaManagedAlert . Data {
rule . GrafanaManagedAlert . Data [ i ] . DatasourceUID = strings . ReplaceAll ( rule . GrafanaManagedAlert . Data [ i ] . DatasourceUID , "REPLACE_ME" , testDs . Body . Datasource . UID )
}
}
changes , status , body := apiClient . PostRulesGroupWithStatus ( t , folder , & postData )
require . Equalf ( t , http . StatusAccepted , status , body )
require . Len ( t , changes . Created , 1 )
ruleUid := changes . Created [ 0 ]
var frame data . Frame
require . Eventuallyf ( t , func ( ) bool {
frame , status , body = apiClient . GetRuleHistoryWithStatus ( t , ruleUid )
require . Equalf ( t , http . StatusOK , status , body )
return frame . Rows ( ) > 1
} , 15 * time . Second , 1 * time . Second , "Alert state history expected to have more than one record but got %d. Body: %s" , frame . Rows ( ) , body )
f , _ := frame . FieldByName ( "next" )
alertingIdx := 0
normalIdx := 1
if f . At ( alertingIdx ) . ( string ) != "Alerting" {
alertingIdx = 1
normalIdx = 0
}
assert . Equalf ( t , "Alerting" , f . At ( alertingIdx ) . ( string ) , body )
assert . Equalf ( t , "Normal" , f . At ( normalIdx ) . ( string ) , body )
type HistoryData struct {
Values map [ string ] int64
}
f , _ = frame . FieldByName ( "data" )
var d HistoryData
require . NoErrorf ( t , json . Unmarshal ( [ ] byte ( f . At ( alertingIdx ) . ( string ) ) , & d ) , body )
assert . EqualValuesf ( t , 5 , d . Values [ "B" ] , body )
require . NoErrorf ( t , json . Unmarshal ( [ ] byte ( f . At ( normalIdx ) . ( string ) ) , & d ) , body )
assert . EqualValuesf ( t , 1 , d . Values [ "B" ] , body )
}