mirror of https://github.com/grafana/grafana
prometheushacktoberfestmetricsmonitoringalertinggrafanagoinfluxdbmysqlpostgresanalyticsdata-visualizationdashboardbusiness-intelligenceelasticsearch
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1044 lines
30 KiB
1044 lines
30 KiB
package alerting
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/grafana/grafana-plugin-sdk-go/data"
|
|
"github.com/prometheus/common/model"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/grafana/grafana/pkg/api"
|
|
"github.com/grafana/grafana/pkg/expr"
|
|
"github.com/grafana/grafana/pkg/infra/db"
|
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/folder"
|
|
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
|
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
|
"github.com/grafana/grafana/pkg/services/quota"
|
|
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
|
|
"github.com/grafana/grafana/pkg/services/supportbundles/supportbundlestest"
|
|
"github.com/grafana/grafana/pkg/services/user"
|
|
"github.com/grafana/grafana/pkg/services/user/userimpl"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
)
|
|
|
|
const defaultAlertmanagerConfigJSON = `
|
|
{
|
|
"template_files": null,
|
|
"alertmanager_config": {
|
|
"route": {
|
|
"receiver": "grafana-default-email",
|
|
"group_by": ["grafana_folder", "alertname"]
|
|
},
|
|
"receivers": [{
|
|
"name": "grafana-default-email",
|
|
"grafana_managed_receiver_configs": [{
|
|
"uid": "",
|
|
"name": "email receiver",
|
|
"type": "email",
|
|
"disableResolveMessage": false,
|
|
"settings": {
|
|
"addresses": "\u003cexample@email.com\u003e"
|
|
},
|
|
"secureFields": {}
|
|
}]
|
|
}]
|
|
}
|
|
}
|
|
`
|
|
|
|
type Response struct {
|
|
Message string `json:"message"`
|
|
TraceID string `json:"traceID"`
|
|
}
|
|
|
|
func getRequest(t *testing.T, url string, expStatusCode int) *http.Response {
|
|
t.Helper()
|
|
// nolint:gosec
|
|
resp, err := http.Get(url)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
require.NoError(t, resp.Body.Close())
|
|
})
|
|
if expStatusCode != resp.StatusCode {
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
t.Fatal(string(b))
|
|
}
|
|
return resp
|
|
}
|
|
|
|
func postRequest(t *testing.T, url string, body string, expStatusCode int) *http.Response {
|
|
t.Helper()
|
|
buf := bytes.NewReader([]byte(body))
|
|
// nolint:gosec
|
|
resp, err := http.Post(url, "application/json", buf)
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
require.NoError(t, resp.Body.Close())
|
|
})
|
|
if expStatusCode != resp.StatusCode {
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
t.Log(string(b))
|
|
require.Equal(t, expStatusCode, resp.StatusCode)
|
|
}
|
|
return resp
|
|
}
|
|
|
|
func getBody(t *testing.T, body io.ReadCloser) string {
|
|
t.Helper()
|
|
b, err := io.ReadAll(body)
|
|
require.NoError(t, err)
|
|
return string(b)
|
|
}
|
|
|
|
type ruleMutator func(r *apimodels.PostableExtendedRuleNode)
|
|
|
|
func alertRuleGen(mutators ...ruleMutator) func() apimodels.PostableExtendedRuleNode {
|
|
return func() apimodels.PostableExtendedRuleNode {
|
|
forDuration := model.Duration(10 * time.Second)
|
|
rule := apimodels.PostableExtendedRuleNode{
|
|
ApiRuleNode: &apimodels.ApiRuleNode{
|
|
For: &forDuration,
|
|
Labels: map[string]string{"label1": "val1"},
|
|
Annotations: map[string]string{"annotation1": "val1"},
|
|
},
|
|
GrafanaManagedAlert: &apimodels.PostableGrafanaRule{
|
|
Title: fmt.Sprintf("rule-%s", util.GenerateShortUID()),
|
|
Condition: "A",
|
|
Data: []apimodels.AlertQuery{
|
|
{
|
|
RefID: "A",
|
|
RelativeTimeRange: apimodels.RelativeTimeRange{
|
|
From: apimodels.Duration(time.Duration(5) * time.Hour),
|
|
To: apimodels.Duration(time.Duration(3) * time.Hour),
|
|
},
|
|
DatasourceUID: expr.DatasourceUID,
|
|
Model: json.RawMessage(`{
|
|
"type": "math",
|
|
"expression": "2 + 3 > 1"
|
|
}`),
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, mutator := range mutators {
|
|
mutator(&rule)
|
|
}
|
|
return rule
|
|
}
|
|
}
|
|
|
|
func withDatasourceQuery(uid string) func(r *apimodels.PostableExtendedRuleNode) {
|
|
data := []apimodels.AlertQuery{
|
|
{
|
|
RefID: "A",
|
|
RelativeTimeRange: apimodels.RelativeTimeRange{
|
|
From: apimodels.Duration(600 * time.Second),
|
|
To: 0,
|
|
},
|
|
DatasourceUID: uid,
|
|
Model: json.RawMessage(fmt.Sprintf(`{
|
|
"refId": "A",
|
|
"hide": false,
|
|
"datasource": {
|
|
"type": "testdata",
|
|
"uid": "%s"
|
|
},
|
|
"scenarioId": "random_walk",
|
|
"seriesCount": 5,
|
|
"labels": "series=series-$seriesIndex"
|
|
}`, uid)),
|
|
},
|
|
{
|
|
RefID: "B",
|
|
DatasourceUID: expr.DatasourceType,
|
|
Model: json.RawMessage(`{
|
|
"type": "reduce",
|
|
"reducer": "last",
|
|
"expression": "A"
|
|
}`),
|
|
},
|
|
{
|
|
RefID: "C",
|
|
DatasourceUID: expr.DatasourceType,
|
|
Model: json.RawMessage(`{
|
|
"refId": "C",
|
|
"type": "threshold",
|
|
"conditions": [
|
|
{
|
|
"type": "query",
|
|
"evaluator": {
|
|
"params": [
|
|
0
|
|
],
|
|
"type": "gt"
|
|
}
|
|
}
|
|
],
|
|
"expression": "B"
|
|
}`),
|
|
},
|
|
}
|
|
|
|
return func(r *apimodels.PostableExtendedRuleNode) {
|
|
r.GrafanaManagedAlert.Data = data
|
|
r.GrafanaManagedAlert.Condition = "C"
|
|
}
|
|
}
|
|
|
|
func generateAlertRuleGroup(rulesCount int, gen func() apimodels.PostableExtendedRuleNode) apimodels.PostableRuleGroupConfig {
|
|
rules := make([]apimodels.PostableExtendedRuleNode, 0, rulesCount)
|
|
for i := 0; i < rulesCount; i++ {
|
|
rules = append(rules, gen())
|
|
}
|
|
return apimodels.PostableRuleGroupConfig{
|
|
Name: "arulegroup-" + uuid.NewString(),
|
|
Interval: model.Duration(10 * time.Second),
|
|
Rules: rules,
|
|
}
|
|
}
|
|
|
|
func convertGettableRuleGroupToPostable(gettable apimodels.GettableRuleGroupConfig) apimodels.PostableRuleGroupConfig {
|
|
rules := make([]apimodels.PostableExtendedRuleNode, 0, len(gettable.Rules))
|
|
for _, rule := range gettable.Rules {
|
|
rules = append(rules, convertGettableRuleToPostable(rule))
|
|
}
|
|
return apimodels.PostableRuleGroupConfig{
|
|
Name: gettable.Name,
|
|
Interval: gettable.Interval,
|
|
Rules: rules,
|
|
}
|
|
}
|
|
|
|
func convertGettableRuleToPostable(gettable apimodels.GettableExtendedRuleNode) apimodels.PostableExtendedRuleNode {
|
|
return apimodels.PostableExtendedRuleNode{
|
|
ApiRuleNode: gettable.ApiRuleNode,
|
|
GrafanaManagedAlert: convertGettableGrafanaRuleToPostable(gettable.GrafanaManagedAlert),
|
|
}
|
|
}
|
|
|
|
func convertGettableGrafanaRuleToPostable(gettable *apimodels.GettableGrafanaRule) *apimodels.PostableGrafanaRule {
|
|
if gettable == nil {
|
|
return nil
|
|
}
|
|
return &apimodels.PostableGrafanaRule{
|
|
Title: gettable.Title,
|
|
Condition: gettable.Condition,
|
|
Data: gettable.Data,
|
|
UID: gettable.UID,
|
|
NoDataState: gettable.NoDataState,
|
|
ExecErrState: gettable.ExecErrState,
|
|
IsPaused: &gettable.IsPaused,
|
|
NotificationSettings: gettable.NotificationSettings,
|
|
Metadata: gettable.Metadata,
|
|
}
|
|
}
|
|
|
|
type apiClient struct {
|
|
url string
|
|
}
|
|
|
|
type LegacyApiClient struct {
|
|
apiClient
|
|
}
|
|
|
|
func NewAlertingLegacyAPIClient(host, user, pass string) LegacyApiClient {
|
|
cli := newAlertingApiClient(host, user, pass)
|
|
return LegacyApiClient{
|
|
apiClient: cli,
|
|
}
|
|
}
|
|
|
|
func newAlertingApiClient(host, user, pass string) apiClient {
|
|
if len(user) == 0 && len(pass) == 0 {
|
|
return apiClient{url: fmt.Sprintf("http://%s", host)}
|
|
}
|
|
return apiClient{url: fmt.Sprintf("http://%s:%s@%s", user, pass, host)}
|
|
}
|
|
|
|
// ReloadCachedPermissions sends a request to access control API to refresh cached user permissions
|
|
func (a apiClient) ReloadCachedPermissions(t *testing.T) {
|
|
t.Helper()
|
|
|
|
u := fmt.Sprintf("%s/api/access-control/user/permissions?reloadcache=true", a.url)
|
|
// nolint:gosec
|
|
resp, err := http.Get(u)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
require.NoErrorf(t, err, "failed to reload permissions cache")
|
|
require.Equalf(t, http.StatusOK, resp.StatusCode, "failed to reload permissions cache")
|
|
}
|
|
|
|
// AssignReceiverPermission sends a request to access control API to assign permissions to a user, role, or team on a receiver.
|
|
func (a apiClient) AssignReceiverPermission(t *testing.T, receiverUID string, cmd accesscontrol.SetResourcePermissionCommand) (int, string) {
|
|
t.Helper()
|
|
|
|
var assignment string
|
|
var assignTo string
|
|
if cmd.UserID != 0 {
|
|
assignment = "users"
|
|
assignTo = fmt.Sprintf("%d", cmd.UserID)
|
|
} else if cmd.TeamID != 0 {
|
|
assignment = "teams"
|
|
assignTo = fmt.Sprintf("%d", cmd.TeamID)
|
|
} else {
|
|
assignment = "builtInRoles"
|
|
assignTo = cmd.BuiltinRole
|
|
}
|
|
|
|
body := strings.NewReader(fmt.Sprintf(`{"permission": "%s"}`, cmd.Permission))
|
|
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/access-control/receivers/%s/%s/%s", a.url, receiverUID, assignment, assignTo), body)
|
|
require.NoError(t, err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
// CreateFolder creates a folder for storing our alerts, and then refreshes the permission cache to make sure that following requests will be accepted
|
|
func (a apiClient) CreateFolder(t *testing.T, uID string, title string, parentUID ...string) {
|
|
t.Helper()
|
|
cmd := folder.CreateFolderCommand{
|
|
UID: uID,
|
|
Title: title,
|
|
}
|
|
if len(parentUID) > 0 {
|
|
cmd.ParentUID = parentUID[0]
|
|
}
|
|
|
|
blob, err := json.Marshal(cmd)
|
|
require.NoError(t, err)
|
|
|
|
payload := string(blob)
|
|
u := fmt.Sprintf("%s/api/folders", a.url)
|
|
r := strings.NewReader(payload)
|
|
// nolint:gosec
|
|
resp, err := http.Post(u, "application/json", r)
|
|
defer func() {
|
|
require.NoError(t, resp.Body.Close())
|
|
}()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
a.ReloadCachedPermissions(t)
|
|
}
|
|
|
|
func (a apiClient) ReloadAlertingFileProvisioning(t *testing.T) {
|
|
t.Helper()
|
|
|
|
u := fmt.Sprintf("%s/api/admin/provisioning/alerting/reload", a.url)
|
|
// nolint:gosec
|
|
resp, err := http.Post(u, "application/json", nil)
|
|
defer func() {
|
|
require.NoError(t, resp.Body.Close())
|
|
}()
|
|
require.NoError(t, err)
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
}
|
|
|
|
func (a apiClient) GetOrgQuotaLimits(t *testing.T, orgID int64) (int64, int64) {
|
|
t.Helper()
|
|
|
|
u := fmt.Sprintf("%s/api/orgs/%d/quotas", a.url, orgID)
|
|
// nolint:gosec
|
|
resp, err := http.Get(u)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
results := []quota.QuotaDTO{}
|
|
require.NoError(t, json.Unmarshal(b, &results))
|
|
|
|
var limit int64 = 0
|
|
var used int64 = 0
|
|
for _, q := range results {
|
|
if q.Target != string(ngmodels.QuotaTargetSrv) {
|
|
continue
|
|
}
|
|
limit = q.Limit
|
|
used = q.Used
|
|
}
|
|
return limit, used
|
|
}
|
|
|
|
func (a apiClient) UpdateAlertRuleOrgQuota(t *testing.T, orgID int64, limit int64) {
|
|
t.Helper()
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode("a.UpdateQuotaCmd{
|
|
Target: "alert_rule",
|
|
Limit: limit,
|
|
OrgID: orgID,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
u := fmt.Sprintf("%s/api/orgs/%d/quotas/alert_rule", a.url, orgID)
|
|
// nolint:gosec
|
|
client := &http.Client{}
|
|
req, err := http.NewRequest(http.MethodPut, u, &buf)
|
|
require.NoError(t, err)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
assert.Equal(t, http.StatusOK, resp.StatusCode)
|
|
}
|
|
|
|
func (a apiClient) PostConfiguration(t *testing.T, c apimodels.PostableUserConfig) (bool, error) {
|
|
t.Helper()
|
|
|
|
b, err := json.Marshal(c)
|
|
require.NoError(t, err)
|
|
|
|
u := fmt.Sprintf("%s/api/alertmanager/grafana/config/api/v1/alerts", a.url)
|
|
req, err := http.NewRequest(http.MethodPost, u, bytes.NewReader(b))
|
|
require.NoError(t, err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, resp)
|
|
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err = io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
data := struct {
|
|
Message string `json:"message"`
|
|
}{}
|
|
require.NoError(t, json.Unmarshal(b, &data))
|
|
|
|
if resp.StatusCode == http.StatusAccepted {
|
|
return true, nil
|
|
}
|
|
|
|
return false, errors.New(data.Message)
|
|
}
|
|
|
|
func (a apiClient) PostRulesGroupWithStatus(t *testing.T, folder string, group *apimodels.PostableRuleGroupConfig) (apimodels.UpdateRuleGroupResponse, int, string) {
|
|
t.Helper()
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(group)
|
|
require.NoError(t, err)
|
|
|
|
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/%s", a.url, folder)
|
|
// nolint:gosec
|
|
resp, err := http.Post(u, "application/json", &buf)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
var m apimodels.UpdateRuleGroupResponse
|
|
if resp.StatusCode == http.StatusAccepted {
|
|
require.NoError(t, json.Unmarshal(b, &m))
|
|
}
|
|
return m, resp.StatusCode, string(b)
|
|
}
|
|
|
|
func (a apiClient) PostRulesExportWithStatus(t *testing.T, folder string, group *apimodels.PostableRuleGroupConfig, params *apimodels.ExportQueryParams) (int, string) {
|
|
t.Helper()
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(group)
|
|
require.NoError(t, err)
|
|
|
|
u, err := url.Parse(fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/%s/export", a.url, folder))
|
|
require.NoError(t, err)
|
|
|
|
if params != nil {
|
|
q := url.Values{}
|
|
if params.Format != "" {
|
|
q.Set("format", params.Format)
|
|
}
|
|
if params.Download {
|
|
q.Set("download", "true")
|
|
}
|
|
u.RawQuery = q.Encode()
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodPost, u.String(), &buf)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
func (a apiClient) DeleteRulesGroup(t *testing.T, folder string, group string) (int, string) {
|
|
t.Helper()
|
|
|
|
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/%s/%s", a.url, folder, group)
|
|
req, err := http.NewRequest(http.MethodDelete, u, nil)
|
|
require.NoError(t, err)
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
func (a apiClient) PostSilence(t *testing.T, s apimodels.PostableSilence) (apimodels.PostSilencesOKBody, int, string) {
|
|
t.Helper()
|
|
|
|
b, err := json.Marshal(s)
|
|
require.NoError(t, err)
|
|
|
|
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/alertmanager/grafana/api/v2/silences", a.url), bytes.NewReader(b))
|
|
require.NoError(t, err)
|
|
req.Header.Set("Content-Type", "application/json")
|
|
return sendRequest[apimodels.PostSilencesOKBody](t, req, http.StatusAccepted)
|
|
}
|
|
|
|
func (a apiClient) GetSilence(t *testing.T, id string) (apimodels.GettableSilence, int, string) {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/alertmanager/grafana/api/v2/silence/%s", a.url, id), nil)
|
|
require.NoError(t, err)
|
|
return sendRequest[apimodels.GettableSilence](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetSilences(t *testing.T, filters ...string) (apimodels.GettableSilences, int, string) {
|
|
t.Helper()
|
|
|
|
u, err := url.Parse(fmt.Sprintf("%s/api/alertmanager/grafana/api/v2/silences", a.url))
|
|
require.NoError(t, err)
|
|
if len(filters) > 0 {
|
|
u.RawQuery = url.Values{"filter": filters}.Encode()
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.GettableSilences](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) DeleteSilence(t *testing.T, id string) (any, int, string) {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/api/alertmanager/grafana/api/v2/silence/%s", a.url, id), nil)
|
|
require.NoError(t, err)
|
|
|
|
type dynamic struct {
|
|
Message string `json:"message"`
|
|
}
|
|
|
|
return sendRequest[dynamic](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetRulesGroup(t *testing.T, folder string, group string) apimodels.RuleGroupConfigResponse {
|
|
result, status, _ := a.GetRulesGroupWithStatus(t, folder, group)
|
|
require.Equal(t, http.StatusAccepted, status)
|
|
return result
|
|
}
|
|
|
|
func (a apiClient) GetRulesGroupWithStatus(t *testing.T, folder string, group string) (apimodels.RuleGroupConfigResponse, int, []byte) {
|
|
t.Helper()
|
|
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/%s/%s", a.url, folder, group)
|
|
// nolint:gosec
|
|
resp, err := http.Get(u)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
result := apimodels.RuleGroupConfigResponse{}
|
|
|
|
if http.StatusAccepted == resp.StatusCode {
|
|
require.NoError(t, json.Unmarshal(b, &result))
|
|
}
|
|
return result, resp.StatusCode, b
|
|
}
|
|
|
|
func (a apiClient) GetAllRulesGroupInFolderWithStatus(t *testing.T, folder string) (apimodels.NamespaceConfigResponse, int, []byte) {
|
|
t.Helper()
|
|
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules/%s", a.url, folder)
|
|
// nolint:gosec
|
|
resp, err := http.Get(u)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
result := apimodels.NamespaceConfigResponse{}
|
|
if http.StatusAccepted == resp.StatusCode {
|
|
require.NoError(t, json.Unmarshal(b, &result))
|
|
}
|
|
return result, resp.StatusCode, b
|
|
}
|
|
|
|
func (a apiClient) GetAllRulesWithStatus(t *testing.T) (apimodels.NamespaceConfigResponse, int, []byte) {
|
|
t.Helper()
|
|
u := fmt.Sprintf("%s/api/ruler/grafana/api/v1/rules", a.url)
|
|
// nolint:gosec
|
|
resp, err := http.Get(u)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
result := apimodels.NamespaceConfigResponse{}
|
|
if http.StatusOK == resp.StatusCode {
|
|
require.NoError(t, json.Unmarshal(b, &result))
|
|
}
|
|
return result, resp.StatusCode, b
|
|
}
|
|
|
|
func (a apiClient) ExportRulesWithStatus(t *testing.T, params *apimodels.AlertRulesExportParameters) (int, string) {
|
|
t.Helper()
|
|
u, err := url.Parse(fmt.Sprintf("%s/api/ruler/grafana/api/v1/export/rules", a.url))
|
|
require.NoError(t, err)
|
|
if params != nil {
|
|
q := url.Values{}
|
|
if params.Format != "" {
|
|
q.Set("format", params.Format)
|
|
}
|
|
if params.Download {
|
|
q.Set("download", "true")
|
|
}
|
|
if len(params.FolderUID) > 0 {
|
|
for _, s := range params.FolderUID {
|
|
q.Add("folderUid", s)
|
|
}
|
|
}
|
|
if params.GroupName != "" {
|
|
q.Set("group", params.GroupName)
|
|
}
|
|
if params.RuleUID != "" {
|
|
q.Set("ruleUid", params.RuleUID)
|
|
}
|
|
u.RawQuery = q.Encode()
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
func (a apiClient) SubmitRuleForBacktesting(t *testing.T, config apimodels.BacktestConfig) (int, string) {
|
|
t.Helper()
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(config)
|
|
require.NoError(t, err)
|
|
|
|
u := fmt.Sprintf("%s/api/v1/rule/backtest", a.url)
|
|
// nolint:gosec
|
|
resp, err := http.Post(u, "application/json", &buf)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
func (a apiClient) SubmitRuleForTesting(t *testing.T, config apimodels.PostableExtendedRuleNodeExtended) (int, string) {
|
|
t.Helper()
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(config)
|
|
require.NoError(t, err)
|
|
|
|
u := fmt.Sprintf("%s/api/v1/rule/test/grafana", a.url)
|
|
// nolint:gosec
|
|
resp, err := http.Post(u, "application/json", &buf)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
return resp.StatusCode, string(b)
|
|
}
|
|
|
|
func (a apiClient) CreateTestDatasource(t *testing.T) (result api.CreateOrUpdateDatasourceResponse) {
|
|
t.Helper()
|
|
|
|
payload := fmt.Sprintf(`{"name":"TestData-%s","type":"testdata","access":"proxy","isDefault":false}`, uuid.NewString())
|
|
buf := bytes.Buffer{}
|
|
buf.Write([]byte(payload))
|
|
|
|
u := fmt.Sprintf("%s/api/datasources", a.url)
|
|
|
|
// nolint:gosec
|
|
resp, err := http.Post(u, "application/json", &buf)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
if resp.StatusCode != 200 {
|
|
require.Failf(t, "failed to create data source", "API request to create a datasource failed. Status code: %d, response: %s", resp.StatusCode, string(b))
|
|
}
|
|
require.NoError(t, json.Unmarshal([]byte(fmt.Sprintf(`{ "body": %s }`, string(b))), &result))
|
|
return result
|
|
}
|
|
|
|
func (a apiClient) DeleteDatasource(t *testing.T, uid string) {
|
|
t.Helper()
|
|
|
|
u := fmt.Sprintf("%s/api/datasources/uid/%s", a.url, uid)
|
|
|
|
req, err := http.NewRequest(http.MethodDelete, u, nil)
|
|
require.NoError(t, err)
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
b, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
if resp.StatusCode != 200 {
|
|
require.Failf(t, "failed to create data source", "API request to create a datasource failed. Status code: %d, response: %s", resp.StatusCode, string(b))
|
|
}
|
|
}
|
|
|
|
func (a apiClient) GetAllMuteTimingsWithStatus(t *testing.T) (apimodels.MuteTimings, int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/provisioning/mute-timings", a.url), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.MuteTimings](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetMuteTimingByNameWithStatus(t *testing.T, name string) (apimodels.MuteTimeInterval, int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/provisioning/mute-timings/%s", a.url, name), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.MuteTimeInterval](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) CreateMuteTimingWithStatus(t *testing.T, interval apimodels.MuteTimeInterval) (apimodels.MuteTimeInterval, int, string) {
|
|
t.Helper()
|
|
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(interval)
|
|
require.NoError(t, err)
|
|
|
|
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1/provisioning/mute-timings", a.url), &buf)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.MuteTimeInterval](t, req, http.StatusCreated)
|
|
}
|
|
|
|
func (a apiClient) EnsureMuteTiming(t *testing.T, interval apimodels.MuteTimeInterval) {
|
|
t.Helper()
|
|
|
|
_, status, body := a.CreateMuteTimingWithStatus(t, interval)
|
|
require.Equalf(t, http.StatusCreated, status, body)
|
|
}
|
|
|
|
func (a apiClient) UpdateMuteTimingWithStatus(t *testing.T, interval apimodels.MuteTimeInterval) (apimodels.MuteTimeInterval, int, string) {
|
|
t.Helper()
|
|
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(interval)
|
|
require.NoError(t, err)
|
|
|
|
req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/api/v1/provisioning/mute-timings/%s", a.url, interval.Name), &buf)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.MuteTimeInterval](t, req, http.StatusAccepted)
|
|
}
|
|
|
|
func (a apiClient) DeleteMuteTimingWithStatus(t *testing.T, name string) (int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodDelete, fmt.Sprintf("%s/api/v1/provisioning/mute-timings/%s", a.url, name), nil)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
return resp.StatusCode, string(body)
|
|
}
|
|
|
|
func (a apiClient) GetRouteWithStatus(t *testing.T) (apimodels.Route, int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/provisioning/policies", a.url), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.Route](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetRoute(t *testing.T) apimodels.Route {
|
|
t.Helper()
|
|
|
|
route, status, data := a.GetRouteWithStatus(t)
|
|
requireStatusCode(t, http.StatusOK, status, data)
|
|
return route
|
|
}
|
|
|
|
func (a apiClient) UpdateRouteWithStatus(t *testing.T, route apimodels.Route, noProvenance bool) (int, string) {
|
|
t.Helper()
|
|
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(route)
|
|
require.NoError(t, err)
|
|
|
|
req, err := http.NewRequest(http.MethodPut, fmt.Sprintf("%s/api/v1/provisioning/policies", a.url), &buf)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
if noProvenance {
|
|
req.Header.Add("X-Disable-Provenance", "true")
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
return resp.StatusCode, string(body)
|
|
}
|
|
|
|
func (a apiClient) UpdateRoute(t *testing.T, route apimodels.Route, noProvenance bool) {
|
|
t.Helper()
|
|
status, data := a.UpdateRouteWithStatus(t, route, noProvenance)
|
|
requireStatusCode(t, http.StatusAccepted, status, data)
|
|
}
|
|
|
|
func (a apiClient) GetRuleHistoryWithStatus(t *testing.T, ruleUID string) (data.Frame, int, string) {
|
|
t.Helper()
|
|
u, err := url.Parse(fmt.Sprintf("%s/api/v1/rules/history", a.url))
|
|
require.NoError(t, err)
|
|
q := url.Values{}
|
|
q.Set("ruleUID", ruleUID)
|
|
u.RawQuery = q.Encode()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[data.Frame](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetAllTimeIntervalsWithStatus(t *testing.T) ([]apimodels.GettableTimeIntervals, int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/notifications/time-intervals", a.url), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[[]apimodels.GettableTimeIntervals](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetTimeIntervalByNameWithStatus(t *testing.T, name string) (apimodels.GettableTimeIntervals, int, string) {
|
|
t.Helper()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/v1/notifications/time-intervals/%s", a.url, name), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.GettableTimeIntervals](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) CreateReceiverWithStatus(t *testing.T, receiver apimodels.EmbeddedContactPoint) (apimodels.EmbeddedContactPoint, int, string) {
|
|
t.Helper()
|
|
|
|
buf := bytes.Buffer{}
|
|
enc := json.NewEncoder(&buf)
|
|
err := enc.Encode(receiver)
|
|
require.NoError(t, err)
|
|
|
|
req, err := http.NewRequest(http.MethodPost, fmt.Sprintf("%s/api/v1/provisioning/contact-points", a.url), &buf)
|
|
req.Header.Add("Content-Type", "application/json")
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.EmbeddedContactPoint](t, req, http.StatusAccepted)
|
|
}
|
|
|
|
func (a apiClient) EnsureReceiver(t *testing.T, receiver apimodels.EmbeddedContactPoint) {
|
|
t.Helper()
|
|
|
|
_, status, body := a.CreateReceiverWithStatus(t, receiver)
|
|
require.Equalf(t, http.StatusAccepted, status, body)
|
|
}
|
|
|
|
func (a apiClient) ExportReceiver(t *testing.T, name string, format string, decrypt bool) string {
|
|
t.Helper()
|
|
u, err := url.Parse(fmt.Sprintf("%s/api/v1/provisioning/contact-points/export", a.url))
|
|
require.NoError(t, err)
|
|
q := url.Values{}
|
|
q.Set("name", name)
|
|
q.Set("format", format)
|
|
q.Set("decrypt", fmt.Sprintf("%v", decrypt))
|
|
u.RawQuery = q.Encode()
|
|
|
|
req, err := http.NewRequest(http.MethodGet, u.String(), nil)
|
|
require.NoError(t, err)
|
|
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
requireStatusCode(t, http.StatusOK, resp.StatusCode, string(body))
|
|
return string(body)
|
|
}
|
|
|
|
func (a apiClient) ExportReceiverTyped(t *testing.T, name string, decrypt bool) apimodels.ContactPointExport {
|
|
t.Helper()
|
|
|
|
response := a.ExportReceiver(t, name, "json", decrypt)
|
|
|
|
var export apimodels.AlertingFileExport
|
|
require.NoError(t, json.Unmarshal([]byte(response), &export))
|
|
require.Len(t, export.ContactPoints, 1)
|
|
return export.ContactPoints[0]
|
|
}
|
|
|
|
func (a apiClient) GetAlertmanagerConfigWithStatus(t *testing.T) (apimodels.GettableUserConfig, int, string) {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/alertmanager/grafana/config/api/v1/alerts", a.url), nil)
|
|
require.NoError(t, err)
|
|
|
|
return sendRequest[apimodels.GettableUserConfig](t, req, http.StatusOK)
|
|
}
|
|
|
|
func (a apiClient) GetActiveAlertsWithStatus(t *testing.T) (apimodels.AlertGroups, int, string) {
|
|
t.Helper()
|
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/api/alertmanager/grafana/api/v2/alerts/groups", a.url), nil)
|
|
require.NoError(t, err)
|
|
return sendRequest[apimodels.AlertGroups](t, req, http.StatusOK)
|
|
}
|
|
|
|
func sendRequest[T any](t *testing.T, req *http.Request, successStatusCode int) (T, int, string) {
|
|
t.Helper()
|
|
client := &http.Client{}
|
|
resp, err := client.Do(req)
|
|
require.NoError(t, err)
|
|
defer func() {
|
|
_ = resp.Body.Close()
|
|
}()
|
|
body, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
|
|
var result T
|
|
|
|
if resp.StatusCode != successStatusCode {
|
|
return result, resp.StatusCode, string(body)
|
|
}
|
|
|
|
err = json.Unmarshal(body, &result)
|
|
require.NoError(t, err)
|
|
return result, resp.StatusCode, string(body)
|
|
}
|
|
|
|
func requireStatusCode(t *testing.T, expected, actual int, response string) {
|
|
t.Helper()
|
|
require.Equalf(t, expected, actual, "Unexpected status. Response: %s", response)
|
|
}
|
|
|
|
func createUser(t *testing.T, db db.DB, cfg *setting.Cfg, cmd user.CreateUserCommand) int64 {
|
|
t.Helper()
|
|
|
|
cfg.AutoAssignOrg = true
|
|
cfg.AutoAssignOrgId = 1
|
|
|
|
quotaService := quotaimpl.ProvideService(db, cfg)
|
|
orgService, err := orgimpl.ProvideService(db, cfg, quotaService)
|
|
require.NoError(t, err)
|
|
usrSvc, err := userimpl.ProvideService(
|
|
db, orgService, cfg, nil, nil, tracing.InitializeTracerForTest(),
|
|
quotaService, supportbundlestest.NewFakeBundleService(),
|
|
)
|
|
require.NoError(t, err)
|
|
|
|
u, err := usrSvc.Create(context.Background(), &cmd)
|
|
require.NoError(t, err)
|
|
return u.ID
|
|
}
|
|
|