[Alerting] Forking alert manager API (#32300)

* Alertmanager lotex ruler

* Apply suggestions from code review
This commit is contained in:
Sofia Papagiannaki 2021-03-29 18:18:25 +03:00 committed by GitHub
parent 740c5813d4
commit c4d5a67b38
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 371 additions and 33 deletions

1
go.sum
View File

@ -1417,6 +1417,7 @@ github.com/prometheus/prometheus v1.8.2-0.20210217141258-a6be548dbc17 h1:VN3p3Nb
github.com/prometheus/prometheus v1.8.2-0.20210217141258-a6be548dbc17/go.mod h1:dv3B1syqmkrkmo665MPCU6L8PbTXIiUeg/OEQULLNxA=
github.com/prometheus/statsd_exporter v0.15.0/go.mod h1:Dv8HnkoLQkeEjkIE4/2ndAA7WL1zHKK7WMqFQqu72rw=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/quasilyte/go-ruleguard/dsl/fluent v0.0.0-20201222093424-5d7e62a465d3 h1:eL7x4/zMnlquMxYe7V078BD7MGskZ0daGln+SJCVzuY=
github.com/quasilyte/go-ruleguard/dsl/fluent v0.0.0-20201222093424-5d7e62a465d3/go.mod h1:P7JlQWFT7jDcFZMtUPQbtGzzzxva3rBn6oIF+LPwFcM=
github.com/rafaeljusto/redigomock v0.0.0-20190202135759-257e089e14a1/go.mod h1:JaY6n2sDr+z2WTsXkOmNRUfDy6FN0L6Nk7x06ndm4tY=
github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ=

View File

@ -50,7 +50,11 @@ func (api *API) RegisterAPIEndpoints() {
proxy := &AlertingProxy{
DataProxy: api.DataProxy,
}
api.RegisterAlertmanagerApiEndpoints(AlertmanagerApiMock{log: logger})
api.RegisterAlertmanagerApiEndpoints(NewForkedAM(
api.DatasourceCache,
NewLotexAM(proxy, logger),
AlertmanagerApiMock{log: logger},
))
api.RegisterPrometheusApiEndpoints(NewForkedProm(
api.DatasourceCache,
NewLotexProm(proxy, logger),

View File

@ -21,13 +21,13 @@ type AlertmanagerApiService interface {
RouteCreateSilence(*models.ReqContext, apimodels.SilenceBody) response.Response
RouteDeleteAlertingConfig(*models.ReqContext) response.Response
RouteDeleteSilence(*models.ReqContext) response.Response
RouteGetAMAlertGroups(*models.ReqContext) response.Response
RouteGetAMAlerts(*models.ReqContext) response.Response
RouteGetAlertingConfig(*models.ReqContext) response.Response
RouteGetAmAlertGroups(*models.ReqContext) response.Response
RouteGetAmAlerts(*models.ReqContext) response.Response
RouteGetSilence(*models.ReqContext) response.Response
RouteGetSilences(*models.ReqContext) response.Response
RoutePostAMAlerts(*models.ReqContext, apimodels.PostableAlerts) response.Response
RoutePostAlertingConfig(*models.ReqContext, apimodels.PostableUserConfig) response.Response
RoutePostAmAlerts(*models.ReqContext, apimodels.PostableAlerts) response.Response
}
type AlertmanagerApiBase struct {
@ -39,13 +39,13 @@ func (api *API) RegisterAlertmanagerApiEndpoints(srv AlertmanagerApiService) {
group.Post(toMacaronPath("/alertmanager/{Recipient}/api/v2/silences"), binding.Bind(apimodels.SilenceBody{}), routing.Wrap(srv.RouteCreateSilence))
group.Delete(toMacaronPath("/alertmanager/{Recipient}/config/api/v1/alerts"), routing.Wrap(srv.RouteDeleteAlertingConfig))
group.Delete(toMacaronPath("/alertmanager/{Recipient}/api/v2/silence/{SilenceId}"), routing.Wrap(srv.RouteDeleteSilence))
group.Get(toMacaronPath("/alertmanager/{Recipient}/api/v2/alerts/groups"), routing.Wrap(srv.RouteGetAMAlertGroups))
group.Get(toMacaronPath("/alertmanager/{Recipient}/api/v2/alerts"), routing.Wrap(srv.RouteGetAMAlerts))
group.Get(toMacaronPath("/alertmanager/{Recipient}/config/api/v1/alerts"), routing.Wrap(srv.RouteGetAlertingConfig))
group.Get(toMacaronPath("/alertmanager/{Recipient}/api/v2/alerts/groups"), routing.Wrap(srv.RouteGetAmAlertGroups))
group.Get(toMacaronPath("/alertmanager/{Recipient}/api/v2/alerts"), routing.Wrap(srv.RouteGetAmAlerts))
group.Get(toMacaronPath("/alertmanager/{Recipient}/api/v2/silence/{SilenceId}"), routing.Wrap(srv.RouteGetSilence))
group.Get(toMacaronPath("/alertmanager/{Recipient}/api/v2/silences"), routing.Wrap(srv.RouteGetSilences))
group.Post(toMacaronPath("/alertmanager/{Recipient}/api/v2/alerts"), binding.Bind(apimodels.PostableAlerts{}), routing.Wrap(srv.RoutePostAMAlerts))
group.Post(toMacaronPath("/alertmanager/{Recipient}/config/api/v1/alerts"), binding.Bind(apimodels.PostableUserConfig{}), routing.Wrap(srv.RoutePostAlertingConfig))
group.Post(toMacaronPath("/alertmanager/{Recipient}/api/v2/alerts"), binding.Bind(apimodels.PostableAlerts{}), routing.Wrap(srv.RoutePostAmAlerts))
})
}
@ -70,24 +70,24 @@ func (base AlertmanagerApiBase) RouteDeleteSilence(c *models.ReqContext) respons
return response.Error(http.StatusNotImplemented, "", nil)
}
func (base AlertmanagerApiBase) RouteGetAMAlertGroups(c *models.ReqContext) response.Response {
recipient := c.Params(":Recipient")
base.log.Info("RouteGetAMAlertGroups: ", "Recipient", recipient)
return response.Error(http.StatusNotImplemented, "", nil)
}
func (base AlertmanagerApiBase) RouteGetAMAlerts(c *models.ReqContext) response.Response {
recipient := c.Params(":Recipient")
base.log.Info("RouteGetAMAlerts: ", "Recipient", recipient)
return response.Error(http.StatusNotImplemented, "", nil)
}
func (base AlertmanagerApiBase) RouteGetAlertingConfig(c *models.ReqContext) response.Response {
recipient := c.Params(":Recipient")
base.log.Info("RouteGetAlertingConfig: ", "Recipient", recipient)
return response.Error(http.StatusNotImplemented, "", nil)
}
func (base AlertmanagerApiBase) RouteGetAmAlertGroups(c *models.ReqContext) response.Response {
recipient := c.Params(":Recipient")
base.log.Info("RouteGetAmAlertGroups: ", "Recipient", recipient)
return response.Error(http.StatusNotImplemented, "", nil)
}
func (base AlertmanagerApiBase) RouteGetAmAlerts(c *models.ReqContext) response.Response {
recipient := c.Params(":Recipient")
base.log.Info("RouteGetAmAlerts: ", "Recipient", recipient)
return response.Error(http.StatusNotImplemented, "", nil)
}
func (base AlertmanagerApiBase) RouteGetSilence(c *models.ReqContext) response.Response {
silenceId := c.Params(":SilenceId")
base.log.Info("RouteGetSilence: ", "SilenceId", silenceId)
@ -102,16 +102,16 @@ func (base AlertmanagerApiBase) RouteGetSilences(c *models.ReqContext) response.
return response.Error(http.StatusNotImplemented, "", nil)
}
func (base AlertmanagerApiBase) RoutePostAMAlerts(c *models.ReqContext, body apimodels.PostableAlerts) response.Response {
recipient := c.Params(":Recipient")
base.log.Info("RoutePostAMAlerts: ", "Recipient", recipient)
base.log.Info("RoutePostAMAlerts: ", "body", body)
return response.Error(http.StatusNotImplemented, "", nil)
}
func (base AlertmanagerApiBase) RoutePostAlertingConfig(c *models.ReqContext, body apimodels.PostableUserConfig) response.Response {
recipient := c.Params(":Recipient")
base.log.Info("RoutePostAlertingConfig: ", "Recipient", recipient)
base.log.Info("RoutePostAlertingConfig: ", "body", body)
return response.Error(http.StatusNotImplemented, "", nil)
}
func (base AlertmanagerApiBase) RoutePostAmAlerts(c *models.ReqContext, body apimodels.PostableAlerts) response.Response {
recipient := c.Params(":Recipient")
base.log.Info("RoutePostAmAlerts: ", "Recipient", recipient)
base.log.Info("RoutePostAmAlerts: ", "body", body)
return response.Error(http.StatusNotImplemented, "", nil)
}

View File

@ -593,9 +593,9 @@ func (mock AlertmanagerApiMock) RouteGetAlertingConfig(c *models.ReqContext) res
return response.JSON(http.StatusOK, result)
}
func (mock AlertmanagerApiMock) RouteGetAmAlertGroups(c *models.ReqContext) response.Response {
func (mock AlertmanagerApiMock) RouteGetAMAlertGroups(c *models.ReqContext) response.Response {
recipient := c.Params(":Recipient")
mock.log.Info("RouteGetAmAlertGroups: ", "Recipient", recipient)
mock.log.Info("RouteGetAMAlertGroups: ", "Recipient", recipient)
now := time.Now()
result := apimodels.AlertGroups{
&amv2.AlertGroup{
@ -714,9 +714,9 @@ func (mock AlertmanagerApiMock) RouteGetAmAlertGroups(c *models.ReqContext) resp
return response.JSON(http.StatusOK, result)
}
func (mock AlertmanagerApiMock) RouteGetAmAlerts(c *models.ReqContext) response.Response {
func (mock AlertmanagerApiMock) RouteGetAMAlerts(c *models.ReqContext) response.Response {
recipient := c.Params(":Recipient")
mock.log.Info("RouteGetAmAlerts: ", "Recipient", recipient)
mock.log.Info("RouteGetAMAlerts: ", "Recipient", recipient)
now := time.Now()
result := apimodels.GettableAlerts{
&amv2.GettableAlert{
@ -883,9 +883,9 @@ func (mock AlertmanagerApiMock) RoutePostAlertingConfig(c *models.ReqContext, bo
return response.JSON(http.StatusAccepted, util.DynMap{"message": "configuration created"})
}
func (mock AlertmanagerApiMock) RoutePostAmAlerts(c *models.ReqContext, body apimodels.PostableAlerts) response.Response {
func (mock AlertmanagerApiMock) RoutePostAMAlerts(c *models.ReqContext, body apimodels.PostableAlerts) response.Response {
recipient := c.Params(":Recipient")
mock.log.Info("RoutePostAmAlerts: ", "Recipient", recipient)
mock.log.Info("RoutePostAmAlerts: ", "body", body)
mock.log.Info("RoutePostAMAlerts: ", "Recipient", recipient)
mock.log.Info("RoutePostAMAlerts: ", "body", body)
return response.JSON(http.StatusOK, util.DynMap{"message": "alerts created"})
}

View File

@ -0,0 +1,129 @@
package api
import (
"fmt"
apimodels "github.com/grafana/alerting-api/pkg/api"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/datasources"
)
type ForkedAMSvc struct {
AMSvc, GrafanaSvc AlertmanagerApiService
DatasourceCache datasources.CacheService
}
func NewForkedAM(datasourceCache datasources.CacheService, proxy, grafana AlertmanagerApiService) *ForkedAMSvc {
return &ForkedAMSvc{
AMSvc: proxy,
GrafanaSvc: grafana,
DatasourceCache: datasourceCache,
}
}
func (am *ForkedAMSvc) getService(ctx *models.ReqContext) (AlertmanagerApiService, error) {
t, err := backendType(ctx, am.DatasourceCache)
if err != nil {
return nil, err
}
switch t {
case apimodels.GrafanaBackend:
return am.GrafanaSvc, nil
case apimodels.AlertmanagerBackend:
return am.AMSvc, nil
default:
return nil, fmt.Errorf("unexpected backend type (%v)", t)
}
}
func (am *ForkedAMSvc) RouteCreateSilence(ctx *models.ReqContext, body apimodels.SilenceBody) response.Response {
s, err := am.getService(ctx)
if err != nil {
return response.Error(400, err.Error(), nil)
}
return s.RouteCreateSilence(ctx, body)
}
func (am *ForkedAMSvc) RouteDeleteAlertingConfig(ctx *models.ReqContext) response.Response {
s, err := am.getService(ctx)
if err != nil {
return response.Error(400, err.Error(), nil)
}
return s.RouteDeleteAlertingConfig(ctx)
}
func (am *ForkedAMSvc) RouteDeleteSilence(ctx *models.ReqContext) response.Response {
s, err := am.getService(ctx)
if err != nil {
return response.Error(400, err.Error(), nil)
}
return s.RouteDeleteSilence(ctx)
}
func (am *ForkedAMSvc) RouteGetAlertingConfig(ctx *models.ReqContext) response.Response {
s, err := am.getService(ctx)
if err != nil {
return response.Error(400, err.Error(), nil)
}
return s.RouteGetAlertingConfig(ctx)
}
func (am *ForkedAMSvc) RouteGetAMAlertGroups(ctx *models.ReqContext) response.Response {
s, err := am.getService(ctx)
if err != nil {
return response.Error(400, err.Error(), nil)
}
return s.RouteGetAMAlertGroups(ctx)
}
func (am *ForkedAMSvc) RouteGetAMAlerts(ctx *models.ReqContext) response.Response {
s, err := am.getService(ctx)
if err != nil {
return response.Error(400, err.Error(), nil)
}
return s.RouteGetAMAlerts(ctx)
}
func (am *ForkedAMSvc) RouteGetSilence(ctx *models.ReqContext) response.Response {
s, err := am.getService(ctx)
if err != nil {
return response.Error(400, err.Error(), nil)
}
return s.RouteGetSilence(ctx)
}
func (am *ForkedAMSvc) RouteGetSilences(ctx *models.ReqContext) response.Response {
s, err := am.getService(ctx)
if err != nil {
return response.Error(400, err.Error(), nil)
}
return s.RouteGetSilences(ctx)
}
func (am *ForkedAMSvc) RoutePostAlertingConfig(ctx *models.ReqContext, body apimodels.PostableUserConfig) response.Response {
s, err := am.getService(ctx)
if err != nil {
return response.Error(400, err.Error(), nil)
}
return s.RoutePostAlertingConfig(ctx, body)
}
func (am *ForkedAMSvc) RoutePostAMAlerts(ctx *models.ReqContext, body apimodels.PostableAlerts) response.Response {
s, err := am.getService(ctx)
if err != nil {
return response.Error(400, err.Error(), nil)
}
return s.RoutePostAMAlerts(ctx, body)
}

View File

@ -0,0 +1,170 @@
package api
import (
"encoding/json"
"fmt"
"net/http"
apimodels "github.com/grafana/alerting-api/pkg/api"
"github.com/grafana/grafana/pkg/api/response"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/models"
"gopkg.in/yaml.v3"
)
const (
amSilencesPath = "/api/v2/silences"
amSilencePath = "/api/v2/silence/%s"
amAlertGroupsPath = "/api/v2/alerts/groups"
amAlertsPath = "/api/v2/alerts"
amConfigPath = "/api/v1/alerts"
)
type LotexAM struct {
log log.Logger
*AlertingProxy
}
func NewLotexAM(proxy *AlertingProxy, log log.Logger) *LotexAM {
return &LotexAM{
log: log,
AlertingProxy: proxy,
}
}
func (am *LotexAM) RouteCreateSilence(ctx *models.ReqContext, silenceBody apimodels.SilenceBody) response.Response {
blob, err := json.Marshal(silenceBody)
if err != nil {
return response.Error(500, "Failed marshal silence", err)
}
body, ln := payload(blob)
return am.withReq(
ctx, &http.Request{
Method: "POST",
URL: withPath(*ctx.Req.URL, amSilencesPath),
Body: body,
ContentLength: ln,
},
jsonExtractor(&apimodels.GettableSilence{}),
)
}
func (am *LotexAM) RouteDeleteAlertingConfig(ctx *models.ReqContext) response.Response {
return am.withReq(
ctx, &http.Request{
Method: "DELETE",
URL: withPath(
*ctx.Req.URL,
amConfigPath,
),
},
messageExtractor,
)
}
func (am *LotexAM) RouteDeleteSilence(ctx *models.ReqContext) response.Response {
return am.withReq(
ctx, &http.Request{
Method: "DELETE",
URL: withPath(
*ctx.Req.URL,
fmt.Sprintf(amSilencePath, ctx.Params(":SilenceId")),
),
},
messageExtractor,
)
}
func (am *LotexAM) RouteGetAlertingConfig(ctx *models.ReqContext) response.Response {
return am.withReq(
ctx, &http.Request{
URL: withPath(
*ctx.Req.URL,
amConfigPath,
),
},
jsonExtractor(&apimodels.GettableUserConfig{}),
)
}
func (am *LotexAM) RouteGetAMAlertGroups(ctx *models.ReqContext) response.Response {
return am.withReq(
ctx, &http.Request{
URL: withPath(
*ctx.Req.URL,
amAlertGroupsPath,
),
},
jsonExtractor(&apimodels.AlertGroups{}),
)
}
func (am *LotexAM) RouteGetAMAlerts(ctx *models.ReqContext) response.Response {
return am.withReq(
ctx, &http.Request{
URL: withPath(
*ctx.Req.URL,
amAlertsPath,
),
},
jsonExtractor(&apimodels.GettableAlerts{}),
)
}
func (am *LotexAM) RouteGetSilence(ctx *models.ReqContext) response.Response {
return am.withReq(
ctx, &http.Request{
URL: withPath(
*ctx.Req.URL,
fmt.Sprintf(amSilencePath, ctx.Params(":SilenceId")),
),
},
jsonExtractor(&apimodels.GettableSilence{}),
)
}
func (am *LotexAM) RouteGetSilences(ctx *models.ReqContext) response.Response {
return am.withReq(
ctx, &http.Request{
URL: withPath(
*ctx.Req.URL,
amSilencesPath,
),
},
jsonExtractor(&apimodels.GettableSilences{}),
)
}
func (am *LotexAM) RoutePostAlertingConfig(ctx *models.ReqContext, config apimodels.PostableUserConfig) response.Response {
yml, err := yaml.Marshal(config)
if err != nil {
return response.Error(500, "Failed marshal alert manager configuration ", err)
}
body, ln := payload(yml)
u := withPath(*ctx.Req.URL, amConfigPath)
req := &http.Request{
Method: "POST",
URL: u,
Body: body,
ContentLength: ln,
}
return am.withReq(ctx, req, messageExtractor)
}
func (am *LotexAM) RoutePostAMAlerts(ctx *models.ReqContext, alerts apimodels.PostableAlerts) response.Response {
yml, err := yaml.Marshal(alerts)
if err != nil {
return response.Error(500, "Failed marshal postable alerts", err)
}
body, ln := payload(yml)
u := withPath(*ctx.Req.URL, amAlertsPath)
req := &http.Request{
Method: "POST",
URL: u,
Body: body,
ContentLength: ln,
}
return am.withReq(ctx, req, messageExtractor)
}

View File

@ -0,0 +1,32 @@
@lokiDatasourceID = 32
@prometheusDatasourceID = 875
@grafana = grafana
# unsupported loki backend
GET http://admin:admin@localhost:3000/alertmanager/{{lokiDatasourceID}}/config/api/v1/alerts
###
# unsupported cortex backend
GET http://admin:admin@localhost:3000/alertmanager/{{prometheusDatasourceID}}/config/api/v1/alerts
###
# grafana requests
GET http://admin:admin@localhost:3000/alertmanager/{{grafana}}/config/api/v1/alerts
###
DELETE http://admin:admin@localhost:3000/alertmanager/{{grafana}}/config/api/v1/alerts
###
POST http://admin:admin@localhost:3000/alertmanager/{{grafana}}/config/api/v1/alerts
content-type: application/json
< ./post-user-config.json
###
POST http://admin:admin@localhost:3000/alertmanager/{{grafana}}/api/v2/silences
content-type: application/json
< ./post-silence-data.json
###
GET http://admin:admin@localhost:3000/alertmanager/{{grafana}}/api/v2/silences

View File

@ -49,6 +49,8 @@ func backendType(ctx *models.ReqContext, cache datasources.CacheService) (apimod
switch ds.Type {
case "loki", "prometheus":
return apimodels.LoTexRulerBackend, nil
case "grafana-alertmanager-datasource":
return apimodels.AlertmanagerBackend, nil
default:
return 0, fmt.Errorf("unexpected backend type (%v)", ds.Type)
}