mirror of
https://github.com/grafana/grafana.git
synced 2025-02-14 01:23:32 -06:00
Extricates reusable utilities for different alerting proxy types (#32268)
* backendtype helper * abstracts alertingproxy * updates alerting api dep * prom endpoints
This commit is contained in:
parent
376ed8a381
commit
2179a2658e
2
go.mod
2
go.mod
@ -39,7 +39,7 @@ require (
|
||||
github.com/google/go-cmp v0.5.5
|
||||
github.com/google/uuid v1.2.0
|
||||
github.com/gosimple/slug v1.9.0
|
||||
github.com/grafana/alerting-api v0.0.0-20210323142651-d6515052e2f0
|
||||
github.com/grafana/alerting-api v0.0.0-20210323194814-03a29a4c4c27
|
||||
github.com/grafana/grafana-aws-sdk v0.2.0
|
||||
github.com/grafana/grafana-plugin-model v0.0.0-20190930120109-1fc953a61fb4
|
||||
github.com/grafana/grafana-plugin-sdk-go v0.89.0
|
||||
|
8
go.sum
8
go.sum
@ -797,12 +797,8 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U
|
||||
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
|
||||
github.com/gosimple/slug v1.9.0 h1:r5vDcYrFz9BmfIAMC829un9hq7hKM4cHUrsv36LbEqs=
|
||||
github.com/gosimple/slug v1.9.0/go.mod h1:AMZ+sOVe65uByN3kgEyf9WEBKBCSS+dJjMX9x4vDJbg=
|
||||
github.com/grafana/alerting-api v0.0.0-20210318231719-9499804fc548 h1:KjyaZJhPJ15Ul/+OQr8mbO7kDpU5i7G3r5FGVZKClTQ=
|
||||
github.com/grafana/alerting-api v0.0.0-20210318231719-9499804fc548/go.mod h1:5IppnPguSHcCbVLGCVzVjBvuQZNbYgVJ4KyXXjhCyWY=
|
||||
github.com/grafana/alerting-api v0.0.0-20210323141138-8873de5bf07a h1:OGKDRdmQSXKFJelrJUf9O8Xh0C8u+OQG1NSurcBYpOI=
|
||||
github.com/grafana/alerting-api v0.0.0-20210323141138-8873de5bf07a/go.mod h1:5IppnPguSHcCbVLGCVzVjBvuQZNbYgVJ4KyXXjhCyWY=
|
||||
github.com/grafana/alerting-api v0.0.0-20210323142651-d6515052e2f0 h1:bMYGd71RigZvkLmcdedGdMDJXKJ20luqEQLbqjgAAjI=
|
||||
github.com/grafana/alerting-api v0.0.0-20210323142651-d6515052e2f0/go.mod h1:5IppnPguSHcCbVLGCVzVjBvuQZNbYgVJ4KyXXjhCyWY=
|
||||
github.com/grafana/alerting-api v0.0.0-20210323194814-03a29a4c4c27 h1:DuyuEAHJeI+CMxIyzCVhmHcIeK+sjqberhDUfrgd3PY=
|
||||
github.com/grafana/alerting-api v0.0.0-20210323194814-03a29a4c4c27/go.mod h1:5IppnPguSHcCbVLGCVzVjBvuQZNbYgVJ4KyXXjhCyWY=
|
||||
github.com/grafana/grafana v1.9.2-0.20210308201921-4ce0a49eac03/go.mod h1:AHRRvd4utJGY25J5nW8aL7wZzn/LcJ0z2za9oOp14j4=
|
||||
github.com/grafana/grafana-aws-sdk v0.1.0/go.mod h1:+pPo5U+pX0zWimR7YBc7ASeSQfbRkcTyQYqMiAj7G5U=
|
||||
github.com/grafana/grafana-aws-sdk v0.2.0 h1:UTBBYwye+ad5YUIlwN7TGxLdz1wXN3Ezhl0pseDGRVA=
|
||||
|
@ -45,11 +45,18 @@ type API struct {
|
||||
// RegisterAPIEndpoints registers API handlers
|
||||
func (api *API) RegisterAPIEndpoints() {
|
||||
logger := log.New("ngalert.api")
|
||||
proxy := &AlertingProxy{
|
||||
DataProxy: api.DataProxy,
|
||||
}
|
||||
api.RegisterAlertmanagerApiEndpoints(AlertmanagerApiMock{log: logger})
|
||||
api.RegisterPrometheusApiEndpoints(PrometheusApiMock{log: logger})
|
||||
api.RegisterPrometheusApiEndpoints(NewForkedProm(
|
||||
api.DatasourceCache,
|
||||
NewLotexProm(proxy, logger),
|
||||
PrometheusApiMock{log: logger},
|
||||
))
|
||||
api.RegisterRulerApiEndpoints(NewForkedRuler(
|
||||
api.DatasourceCache,
|
||||
&LotexRuler{DataProxy: api.DataProxy, log: logger},
|
||||
NewLotexRuler(proxy, logger),
|
||||
RulerApiMock{log: logger},
|
||||
))
|
||||
api.RegisterTestingApiEndpoints(TestingApiMock{log: logger})
|
||||
|
@ -2,7 +2,6 @@ package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
"github.com/grafana/grafana/pkg/api/response"
|
||||
@ -24,26 +23,8 @@ func NewForkedRuler(datasourceCache datasources.CacheService, lotex, grafana Rul
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ForkedRuler) backendType(ctx *models.ReqContext) (apimodels.Backend, error) {
|
||||
recipient := ctx.Params("Recipient")
|
||||
if recipient == apimodels.GrafanaBackend.String() {
|
||||
return apimodels.GrafanaBackend, nil
|
||||
}
|
||||
if datasourceID, err := strconv.ParseInt(recipient, 10, 64); err == nil {
|
||||
if ds, err := r.DatasourceCache.GetDatasource(datasourceID, ctx.SignedInUser, ctx.SkipCache); err == nil {
|
||||
switch ds.Type {
|
||||
case "loki", "prometheus":
|
||||
return apimodels.LoTexRulerBackend, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unexpected backend type (%v)", ds.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("unexpected backend type (%v)", recipient)
|
||||
}
|
||||
|
||||
func (r *ForkedRuler) RouteDeleteNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
|
||||
t, err := r.backendType(ctx)
|
||||
t, err := backendType(ctx, r.DatasourceCache)
|
||||
if err != nil {
|
||||
return response.Error(400, err.Error(), nil)
|
||||
}
|
||||
@ -58,7 +39,7 @@ func (r *ForkedRuler) RouteDeleteNamespaceRulesConfig(ctx *models.ReqContext) re
|
||||
}
|
||||
|
||||
func (r *ForkedRuler) RouteDeleteRuleGroupConfig(ctx *models.ReqContext) response.Response {
|
||||
t, err := r.backendType(ctx)
|
||||
t, err := backendType(ctx, r.DatasourceCache)
|
||||
if err != nil {
|
||||
return response.Error(400, err.Error(), nil)
|
||||
}
|
||||
@ -73,7 +54,7 @@ func (r *ForkedRuler) RouteDeleteRuleGroupConfig(ctx *models.ReqContext) respons
|
||||
}
|
||||
|
||||
func (r *ForkedRuler) RouteGetNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
|
||||
t, err := r.backendType(ctx)
|
||||
t, err := backendType(ctx, r.DatasourceCache)
|
||||
if err != nil {
|
||||
return response.Error(400, err.Error(), nil)
|
||||
}
|
||||
@ -88,7 +69,7 @@ func (r *ForkedRuler) RouteGetNamespaceRulesConfig(ctx *models.ReqContext) respo
|
||||
}
|
||||
|
||||
func (r *ForkedRuler) RouteGetRulegGroupConfig(ctx *models.ReqContext) response.Response {
|
||||
t, err := r.backendType(ctx)
|
||||
t, err := backendType(ctx, r.DatasourceCache)
|
||||
if err != nil {
|
||||
return response.Error(400, err.Error(), nil)
|
||||
}
|
||||
@ -103,7 +84,7 @@ func (r *ForkedRuler) RouteGetRulegGroupConfig(ctx *models.ReqContext) response.
|
||||
}
|
||||
|
||||
func (r *ForkedRuler) RouteGetRulesConfig(ctx *models.ReqContext) response.Response {
|
||||
t, err := r.backendType(ctx)
|
||||
t, err := backendType(ctx, r.DatasourceCache)
|
||||
if err != nil {
|
||||
return response.Error(400, err.Error(), nil)
|
||||
}
|
||||
@ -118,7 +99,7 @@ func (r *ForkedRuler) RouteGetRulesConfig(ctx *models.ReqContext) response.Respo
|
||||
}
|
||||
|
||||
func (r *ForkedRuler) RoutePostNameRulesConfig(ctx *models.ReqContext, conf apimodels.RuleGroupConfig) response.Response {
|
||||
backendType, err := r.backendType(ctx)
|
||||
backendType, err := backendType(ctx, r.DatasourceCache)
|
||||
if err != nil {
|
||||
return response.Error(400, err.Error(), nil)
|
||||
}
|
||||
|
55
pkg/services/ngalert/api/forked_prom.go
Normal file
55
pkg/services/ngalert/api/forked_prom.go
Normal file
@ -0,0 +1,55 @@
|
||||
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 ForkedPromSvc struct {
|
||||
ProxySvc, GrafanaSvc PrometheusApiService
|
||||
DatasourceCache datasources.CacheService
|
||||
}
|
||||
|
||||
func NewForkedProm(datasourceCache datasources.CacheService, proxy, grafana PrometheusApiService) *ForkedPromSvc {
|
||||
return &ForkedPromSvc{
|
||||
ProxySvc: proxy,
|
||||
GrafanaSvc: grafana,
|
||||
DatasourceCache: datasourceCache,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ForkedPromSvc) RouteGetAlertStatuses(ctx *models.ReqContext) response.Response {
|
||||
t, err := backendType(ctx, p.DatasourceCache)
|
||||
if err != nil {
|
||||
return response.Error(400, err.Error(), nil)
|
||||
}
|
||||
|
||||
switch t {
|
||||
case apimodels.GrafanaBackend:
|
||||
return p.GrafanaSvc.RouteGetAlertStatuses(ctx)
|
||||
case apimodels.LoTexRulerBackend:
|
||||
return p.ProxySvc.RouteGetAlertStatuses(ctx)
|
||||
default:
|
||||
return response.Error(400, fmt.Sprintf("unexpected backend type (%v)", t), nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *ForkedPromSvc) RouteGetRuleStatuses(ctx *models.ReqContext) response.Response {
|
||||
t, err := backendType(ctx, p.DatasourceCache)
|
||||
if err != nil {
|
||||
return response.Error(400, err.Error(), nil)
|
||||
}
|
||||
|
||||
switch t {
|
||||
case apimodels.GrafanaBackend:
|
||||
return p.GrafanaSvc.RouteGetRuleStatuses(ctx)
|
||||
case apimodels.LoTexRulerBackend:
|
||||
return p.ProxySvc.RouteGetRuleStatuses(ctx)
|
||||
default:
|
||||
return response.Error(400, fmt.Sprintf("unexpected backend type (%v)", t), nil)
|
||||
}
|
||||
}
|
@ -2,7 +2,6 @@ package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
@ -10,96 +9,25 @@ import (
|
||||
"net/url"
|
||||
|
||||
apimodels "github.com/grafana/alerting-api/pkg/api"
|
||||
"gopkg.in/macaron.v1"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"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/datasourceproxy"
|
||||
)
|
||||
|
||||
const legacyRulerPrefix = "/api/prom/rules"
|
||||
|
||||
type LotexRuler struct {
|
||||
DataProxy *datasourceproxy.DatasourceProxyService
|
||||
log log.Logger
|
||||
log log.Logger
|
||||
*AlertingProxy
|
||||
}
|
||||
|
||||
// macaron unsafely asserts the http.ResponseWriter is an http.CloseNotifier, which will panic.
|
||||
// Here we impl it, which will ensure this no longer happens, but neither will we take
|
||||
// advantage cancelling upstream requests when the downstream has closed.
|
||||
// NB: http.CloseNotifier is a deprecated ifc from before the context pkg.
|
||||
type safeMacaronWrapper struct {
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (w *safeMacaronWrapper) CloseNotify() <-chan bool {
|
||||
return make(chan bool)
|
||||
}
|
||||
|
||||
// replacedResponseWriter overwrites the underlying responsewriter used by a *models.ReqContext.
|
||||
// It's ugly because it needs to replace a value behind a few nested pointers.
|
||||
func replacedResponseWriter(ctx *models.ReqContext) (*models.ReqContext, *response.NormalResponse) {
|
||||
resp := response.CreateNormalResponse(make(http.Header), nil, 0)
|
||||
cpy := *ctx
|
||||
cpyMCtx := *cpy.Context
|
||||
cpyMCtx.Resp = macaron.NewResponseWriter(ctx.Req.Method, &safeMacaronWrapper{resp})
|
||||
cpy.Context = &cpyMCtx
|
||||
return &cpy, resp
|
||||
}
|
||||
|
||||
// withReq proxies a different request
|
||||
func (r *LotexRuler) withReq(
|
||||
ctx *models.ReqContext,
|
||||
req *http.Request,
|
||||
extractor func([]byte) (interface{}, error),
|
||||
) response.Response {
|
||||
newCtx, resp := replacedResponseWriter(ctx)
|
||||
newCtx.Req.Request = req
|
||||
r.DataProxy.ProxyDatasourceRequestWithID(newCtx, ctx.ParamsInt64("Recipient"))
|
||||
|
||||
status := resp.Status()
|
||||
if status >= 400 {
|
||||
return response.Error(status, string(resp.Body()), nil)
|
||||
func NewLotexRuler(proxy *AlertingProxy, log log.Logger) *LotexRuler {
|
||||
return &LotexRuler{
|
||||
log: log,
|
||||
AlertingProxy: proxy,
|
||||
}
|
||||
|
||||
t, err := extractor(resp.Body())
|
||||
if err != nil {
|
||||
return response.Error(500, err.Error(), nil)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return response.Error(500, err.Error(), nil)
|
||||
}
|
||||
|
||||
return response.JSON(status, b)
|
||||
}
|
||||
|
||||
func yamlExtractor(v interface{}) func([]byte) (interface{}, error) {
|
||||
return func(b []byte) (interface{}, error) {
|
||||
decoder := yaml.NewDecoder(bytes.NewReader(b))
|
||||
decoder.KnownFields(true)
|
||||
|
||||
err := decoder.Decode(v)
|
||||
|
||||
return v, err
|
||||
}
|
||||
}
|
||||
|
||||
func jsonExtractor(v interface{}) func([]byte) (interface{}, error) {
|
||||
if v == nil {
|
||||
// json unmarshal expects a pointer
|
||||
v = &map[string]interface{}{}
|
||||
}
|
||||
return func(b []byte) (interface{}, error) {
|
||||
return v, json.Unmarshal(b, v)
|
||||
}
|
||||
}
|
||||
|
||||
func messageExtractor(b []byte) (interface{}, error) {
|
||||
return map[string]string{"message": string(b)}, nil
|
||||
}
|
||||
|
||||
func (r *LotexRuler) RouteDeleteNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
|
||||
|
51
pkg/services/ngalert/api/lotex_prom.go
Normal file
51
pkg/services/ngalert/api/lotex_prom.go
Normal file
@ -0,0 +1,51 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
const (
|
||||
promRulesPath = "/prometheus/api/v1/rules"
|
||||
promAlertsPath = "/prometheus/api/v1/alerts"
|
||||
)
|
||||
|
||||
type LotexProm struct {
|
||||
log log.Logger
|
||||
*AlertingProxy
|
||||
}
|
||||
|
||||
func NewLotexProm(proxy *AlertingProxy, log log.Logger) *LotexProm {
|
||||
return &LotexProm{
|
||||
log: log,
|
||||
AlertingProxy: proxy,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *LotexProm) RouteGetAlertStatuses(ctx *models.ReqContext) response.Response {
|
||||
return p.withReq(
|
||||
ctx, &http.Request{
|
||||
URL: withPath(
|
||||
*ctx.Req.URL,
|
||||
promAlertsPath,
|
||||
),
|
||||
},
|
||||
jsonExtractor(&apimodels.AlertResponse{}),
|
||||
)
|
||||
}
|
||||
|
||||
func (p *LotexProm) RouteGetRuleStatuses(ctx *models.ReqContext) response.Response {
|
||||
return p.withReq(
|
||||
ctx, &http.Request{
|
||||
URL: withPath(
|
||||
*ctx.Req.URL,
|
||||
promRulesPath,
|
||||
),
|
||||
},
|
||||
jsonExtractor(&apimodels.RuleResponse{}),
|
||||
)
|
||||
}
|
@ -1,10 +1,21 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-openapi/strfmt"
|
||||
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/datasourceproxy"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"gopkg.in/macaron.v1"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var searchRegex = regexp.MustCompile(`\{(\w+)\}`)
|
||||
@ -27,3 +38,101 @@ func stringPtr(s string) *string {
|
||||
func boolPtr(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
func backendType(ctx *models.ReqContext, cache datasources.CacheService) (apimodels.Backend, error) {
|
||||
recipient := ctx.Params("Recipient")
|
||||
if recipient == apimodels.GrafanaBackend.String() {
|
||||
return apimodels.GrafanaBackend, nil
|
||||
}
|
||||
if datasourceID, err := strconv.ParseInt(recipient, 10, 64); err == nil {
|
||||
if ds, err := cache.GetDatasource(datasourceID, ctx.SignedInUser, ctx.SkipCache); err == nil {
|
||||
switch ds.Type {
|
||||
case "loki", "prometheus":
|
||||
return apimodels.LoTexRulerBackend, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("unexpected backend type (%v)", ds.Type)
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0, fmt.Errorf("unexpected backend type (%v)", recipient)
|
||||
}
|
||||
|
||||
// macaron unsafely asserts the http.ResponseWriter is an http.CloseNotifier, which will panic.
|
||||
// Here we impl it, which will ensure this no longer happens, but neither will we take
|
||||
// advantage cancelling upstream requests when the downstream has closed.
|
||||
// NB: http.CloseNotifier is a deprecated ifc from before the context pkg.
|
||||
type safeMacaronWrapper struct {
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
func (w *safeMacaronWrapper) CloseNotify() <-chan bool {
|
||||
return make(chan bool)
|
||||
}
|
||||
|
||||
// replacedResponseWriter overwrites the underlying responsewriter used by a *models.ReqContext.
|
||||
// It's ugly because it needs to replace a value behind a few nested pointers.
|
||||
func replacedResponseWriter(ctx *models.ReqContext) (*models.ReqContext, *response.NormalResponse) {
|
||||
resp := response.CreateNormalResponse(make(http.Header), nil, 0)
|
||||
cpy := *ctx
|
||||
cpyMCtx := *cpy.Context
|
||||
cpyMCtx.Resp = macaron.NewResponseWriter(ctx.Req.Method, &safeMacaronWrapper{resp})
|
||||
cpy.Context = &cpyMCtx
|
||||
return &cpy, resp
|
||||
}
|
||||
|
||||
type AlertingProxy struct {
|
||||
DataProxy *datasourceproxy.DatasourceProxyService
|
||||
}
|
||||
|
||||
// withReq proxies a different request
|
||||
func (p *AlertingProxy) withReq(
|
||||
ctx *models.ReqContext,
|
||||
req *http.Request,
|
||||
extractor func([]byte) (interface{}, error),
|
||||
) response.Response {
|
||||
newCtx, resp := replacedResponseWriter(ctx)
|
||||
newCtx.Req.Request = req
|
||||
p.DataProxy.ProxyDatasourceRequestWithID(newCtx, ctx.ParamsInt64("Recipient"))
|
||||
|
||||
status := resp.Status()
|
||||
if status >= 400 {
|
||||
return response.Error(status, string(resp.Body()), nil)
|
||||
}
|
||||
|
||||
t, err := extractor(resp.Body())
|
||||
if err != nil {
|
||||
return response.Error(500, err.Error(), nil)
|
||||
}
|
||||
|
||||
b, err := json.Marshal(t)
|
||||
if err != nil {
|
||||
return response.Error(500, err.Error(), nil)
|
||||
}
|
||||
|
||||
return response.JSON(status, b)
|
||||
}
|
||||
|
||||
func yamlExtractor(v interface{}) func([]byte) (interface{}, error) {
|
||||
return func(b []byte) (interface{}, error) {
|
||||
decoder := yaml.NewDecoder(bytes.NewReader(b))
|
||||
decoder.KnownFields(true)
|
||||
|
||||
err := decoder.Decode(v)
|
||||
|
||||
return v, err
|
||||
}
|
||||
}
|
||||
|
||||
func jsonExtractor(v interface{}) func([]byte) (interface{}, error) {
|
||||
if v == nil {
|
||||
// json unmarshal expects a pointer
|
||||
v = &map[string]interface{}{}
|
||||
}
|
||||
return func(b []byte) (interface{}, error) {
|
||||
return v, json.Unmarshal(b, v)
|
||||
}
|
||||
}
|
||||
|
||||
func messageExtractor(b []byte) (interface{}, error) {
|
||||
return map[string]string{"message": string(b)}, nil
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user