mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: take datasources as external alertmanagers into consideration (#52534)
This commit is contained in:
committed by
GitHub
parent
5c4aa4a7ac
commit
50ae42130b
@@ -62,6 +62,7 @@ type AlertingStore interface {
|
||||
type API struct {
|
||||
Cfg *setting.Cfg
|
||||
DatasourceCache datasources.CacheService
|
||||
DatasourceService datasources.DataSourceService
|
||||
RouteRegister routing.RouteRegister
|
||||
ExpressionService *expr.Service
|
||||
QuotaService quota.Service
|
||||
@@ -130,6 +131,7 @@ func (api *API) RegisterAPIEndpoints(m *metrics.API) {
|
||||
}), m)
|
||||
api.RegisterConfigurationApiEndpoints(NewConfiguration(
|
||||
&ConfigSrv{
|
||||
datasourceService: api.DatasourceService,
|
||||
store: api.AdminConfigStore,
|
||||
log: logger,
|
||||
alertmanagerProvider: api.AlertsRouter,
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
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/ngalert/store"
|
||||
@@ -16,6 +19,7 @@ import (
|
||||
)
|
||||
|
||||
type ConfigSrv struct {
|
||||
datasourceService datasources.DataSourceService
|
||||
alertmanagerProvider ExternalAlertmanagerProvider
|
||||
store store.AdminConfigurationStore
|
||||
log log.Logger
|
||||
@@ -68,11 +72,17 @@ func (srv ConfigSrv) RoutePostNGalertConfig(c *models.ReqContext, body apimodels
|
||||
|
||||
sendAlertsTo, err := ngmodels.StringToAlertmanagersChoice(string(body.AlertmanagersChoice))
|
||||
if err != nil {
|
||||
return response.Error(400, "Invalid alertmanager choice specified", nil)
|
||||
return response.Error(400, "Invalid alertmanager choice specified", err)
|
||||
}
|
||||
|
||||
if sendAlertsTo == ngmodels.ExternalAlertmanagers && len(body.Alertmanagers) == 0 {
|
||||
return response.Error(400, "At least one Alertmanager must be provided to choose this option", nil)
|
||||
externalAlertmanagers, err := srv.externalAlertmanagers(c.Req.Context(), c.OrgId)
|
||||
if err != nil {
|
||||
return response.Error(500, "Couldn't fetch the external Alertmanagers from datasources", err)
|
||||
}
|
||||
|
||||
if sendAlertsTo == ngmodels.ExternalAlertmanagers &&
|
||||
len(body.Alertmanagers)+len(externalAlertmanagers) < 1 {
|
||||
return response.Error(400, "At least one Alertmanager must be provided or configured as a datasource that handles alerts to choose this option", nil)
|
||||
}
|
||||
|
||||
cfg := &ngmodels.AdminConfiguration{
|
||||
@@ -110,3 +120,25 @@ func (srv ConfigSrv) RouteDeleteNGalertConfig(c *models.ReqContext) response.Res
|
||||
|
||||
return response.JSON(http.StatusOK, util.DynMap{"message": "admin configuration deleted"})
|
||||
}
|
||||
|
||||
// externalAlertmanagers returns the URL of any external alertmanager that is
|
||||
// configured as datasource. The URL does not contain any auth.
|
||||
func (srv ConfigSrv) externalAlertmanagers(ctx context.Context, orgID int64) ([]string, error) {
|
||||
var alertmanagers []string
|
||||
query := &datasources.GetDataSourcesByTypeQuery{
|
||||
OrgId: orgID,
|
||||
Type: datasources.DS_ALERTMANAGER,
|
||||
}
|
||||
err := srv.datasourceService.GetDataSourcesByType(ctx, query)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to fetch datasources for org: %w", err)
|
||||
}
|
||||
for _, ds := range query.Result {
|
||||
if ds.JsonData.Get(apimodels.HandleGrafanaManagedAlerts).MustBool(false) {
|
||||
// we don't need to build the exact URL as we only need
|
||||
// to know if any is set
|
||||
alertmanagers = append(alertmanagers, ds.Uid)
|
||||
}
|
||||
}
|
||||
return alertmanagers, nil
|
||||
}
|
||||
|
||||
117
pkg/services/ngalert/api/api_configuration_test.go
Normal file
117
pkg/services/ngalert/api/api_configuration_test.go
Normal file
@@ -0,0 +1,117 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
fakeDatasources "github.com/grafana/grafana/pkg/services/datasources/fakes"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestExternalAlertmanagerChoice(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
alertmanagerChoice definitions.AlertmanagersChoice
|
||||
alertmanagers []string
|
||||
datasources []*datasources.DataSource
|
||||
statusCode int
|
||||
message string
|
||||
}{
|
||||
{
|
||||
name: "setting the choice to external by passing a plain url should succeed",
|
||||
alertmanagerChoice: definitions.ExternalAlertmanagers,
|
||||
alertmanagers: []string{"http://localhost:9000"},
|
||||
datasources: []*datasources.DataSource{},
|
||||
statusCode: http.StatusCreated,
|
||||
message: "admin configuration updated",
|
||||
},
|
||||
{
|
||||
name: "setting the choice to external by having a enabled external am datasource should succeed",
|
||||
alertmanagerChoice: definitions.ExternalAlertmanagers,
|
||||
alertmanagers: []string{},
|
||||
datasources: []*datasources.DataSource{
|
||||
{
|
||||
OrgId: 1,
|
||||
Type: datasources.DS_ALERTMANAGER,
|
||||
Url: "http://localhost:9000",
|
||||
JsonData: simplejson.NewFromAny(map[string]interface{}{
|
||||
definitions.HandleGrafanaManagedAlerts: true,
|
||||
}),
|
||||
},
|
||||
},
|
||||
statusCode: http.StatusCreated,
|
||||
message: "admin configuration updated",
|
||||
},
|
||||
{
|
||||
name: "setting the choice to external by having a disabled external am datasource should fail",
|
||||
alertmanagerChoice: definitions.ExternalAlertmanagers,
|
||||
alertmanagers: []string{},
|
||||
datasources: []*datasources.DataSource{
|
||||
{
|
||||
OrgId: 1,
|
||||
Type: datasources.DS_ALERTMANAGER,
|
||||
Url: "http://localhost:9000",
|
||||
JsonData: simplejson.NewFromAny(map[string]interface{}{}),
|
||||
},
|
||||
},
|
||||
statusCode: http.StatusBadRequest,
|
||||
message: "At least one Alertmanager must be provided or configured as a datasource that handles alerts to choose this option",
|
||||
},
|
||||
{
|
||||
name: "setting the choice to external and having no am configured should fail",
|
||||
alertmanagerChoice: definitions.ExternalAlertmanagers,
|
||||
alertmanagers: []string{},
|
||||
datasources: []*datasources.DataSource{},
|
||||
statusCode: http.StatusBadRequest,
|
||||
message: "At least one Alertmanager must be provided or configured as a datasource that handles alerts to choose this option",
|
||||
},
|
||||
{
|
||||
name: "setting the choice to all and having no external am configured should succeed",
|
||||
alertmanagerChoice: definitions.AllAlertmanagers,
|
||||
alertmanagers: []string{},
|
||||
datasources: []*datasources.DataSource{},
|
||||
statusCode: http.StatusCreated,
|
||||
message: "admin configuration updated",
|
||||
},
|
||||
{
|
||||
name: "setting the choice to internal should always succeed",
|
||||
alertmanagerChoice: definitions.InternalAlertmanager,
|
||||
alertmanagers: []string{},
|
||||
datasources: []*datasources.DataSource{},
|
||||
statusCode: http.StatusCreated,
|
||||
message: "admin configuration updated",
|
||||
},
|
||||
}
|
||||
ctx := createRequestCtxInOrg(1)
|
||||
ctx.OrgRole = models.ROLE_ADMIN
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
sut := createAPIAdminSut(t, test.datasources)
|
||||
resp := sut.RoutePostNGalertConfig(ctx, definitions.PostableNGalertConfig{
|
||||
Alertmanagers: test.alertmanagers,
|
||||
AlertmanagersChoice: test.alertmanagerChoice,
|
||||
})
|
||||
var res map[string]interface{}
|
||||
err := json.Unmarshal(resp.Body(), &res)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, test.message, res["message"])
|
||||
require.Equal(t, test.statusCode, resp.Status())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createAPIAdminSut(t *testing.T,
|
||||
datasources []*datasources.DataSource) ConfigSrv {
|
||||
return ConfigSrv{
|
||||
datasourceService: &fakeDatasources.FakeDataSourceService{
|
||||
DataSources: datasources,
|
||||
},
|
||||
store: store.NewFakeAdminConfigStore(t),
|
||||
}
|
||||
}
|
||||
@@ -58,9 +58,10 @@ type NGalertConfig struct {
|
||||
type AlertmanagersChoice string
|
||||
|
||||
const (
|
||||
AllAlertmanagers AlertmanagersChoice = "all"
|
||||
InternalAlertmanager AlertmanagersChoice = "internal"
|
||||
ExternalAlertmanagers AlertmanagersChoice = "external"
|
||||
AllAlertmanagers AlertmanagersChoice = "all"
|
||||
InternalAlertmanager AlertmanagersChoice = "internal"
|
||||
ExternalAlertmanagers AlertmanagersChoice = "external"
|
||||
HandleGrafanaManagedAlerts = "handleGrafanaManagedAlerts"
|
||||
)
|
||||
|
||||
// swagger:model
|
||||
|
||||
Reference in New Issue
Block a user