mirror of
https://github.com/grafana/grafana.git
synced 2024-11-22 08:56:43 -06:00
[Alerting] Forking LoTex ruler (#32138)
* updates alerting api to master * skeleton for lotex ruler * withPath helper & legacyRulerPrefix const * forked ruler * wires up proxy * safeMacaronWrapper * working proxy * jsonExtractor * lint
This commit is contained in:
parent
c8b59b79c3
commit
93d0f7163f
3
go.mod
3
go.mod
@ -40,7 +40,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-20210316151414-4987b85e57ee
|
||||
github.com/grafana/alerting-api v0.0.0-20210318231719-9499804fc548
|
||||
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.88.0
|
||||
@ -97,6 +97,7 @@ require (
|
||||
gopkg.in/redis.v5 v5.2.9
|
||||
gopkg.in/square/go-jose.v2 v2.5.1
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
|
||||
xorm.io/core v0.7.3
|
||||
xorm.io/xorm v0.8.2
|
||||
)
|
||||
|
4
go.sum
4
go.sum
@ -794,8 +794,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-20210316151414-4987b85e57ee h1:LZo5p1wyV4RbQa28QBJZoS+VtnH1ruh4K4k1fOuHxJs=
|
||||
github.com/grafana/alerting-api v0.0.0-20210316151414-4987b85e57ee/go.mod h1:5IppnPguSHcCbVLGCVzVjBvuQZNbYgVJ4KyXXjhCyWY=
|
||||
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/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=
|
||||
|
@ -34,7 +34,6 @@ type NormalResponse struct {
|
||||
header http.Header
|
||||
errMessage string
|
||||
err error
|
||||
http.ResponseWriter
|
||||
}
|
||||
|
||||
// Write implements http.ResponseWriter
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/datasourceproxy"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/schedule"
|
||||
@ -36,6 +37,7 @@ type API struct {
|
||||
DataService *tsdb.Service
|
||||
Schedule schedule.ScheduleService
|
||||
Store store.Store
|
||||
DataProxy *datasourceproxy.DatasourceProxyService
|
||||
}
|
||||
|
||||
// RegisterAPIEndpoints registers API handlers
|
||||
@ -43,7 +45,10 @@ func (api *API) RegisterAPIEndpoints() {
|
||||
logger := log.New("ngalert.api")
|
||||
api.RegisterAlertmanagerApiEndpoints(AlertmanagerApiMock{log: logger})
|
||||
api.RegisterPrometheusApiEndpoints(PrometheusApiMock{log: logger})
|
||||
api.RegisterRulerApiEndpoints(RulerApiMock{log: logger})
|
||||
api.RegisterRulerApiEndpoints(NewForkedRuler(
|
||||
&LotexRuler{DataProxy: api.DataProxy, log: logger},
|
||||
RulerApiMock{log: logger},
|
||||
))
|
||||
api.RegisterTestingApiEndpoints(TestingApiMock{log: logger})
|
||||
|
||||
// Legacy routes; they will be removed in v8
|
||||
|
107
pkg/services/ngalert/api/fork_ruler.go
Normal file
107
pkg/services/ngalert/api/fork_ruler.go
Normal file
@ -0,0 +1,107 @@
|
||||
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"
|
||||
)
|
||||
|
||||
// ForkedRuler will validate and proxy requests to the correct backend type depending on the datasource.
|
||||
type ForkedRuler struct {
|
||||
LotexRuler, GrafanaRuler RulerApiService
|
||||
}
|
||||
|
||||
func NewForkedRuler(lotex, grafana RulerApiService) *ForkedRuler {
|
||||
return &ForkedRuler{
|
||||
LotexRuler: lotex,
|
||||
GrafanaRuler: grafana,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ForkedRuler) backendType(ctx *models.ReqContext) apimodels.Backend {
|
||||
// TODO: implement, hardcoded for now
|
||||
return apimodels.LoTexRulerBackend
|
||||
}
|
||||
|
||||
func (r *ForkedRuler) RouteDeleteNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
|
||||
switch t := r.backendType(ctx); t {
|
||||
case apimodels.GrafanaBackend:
|
||||
return r.GrafanaRuler.RouteDeleteNamespaceRulesConfig(ctx)
|
||||
case apimodels.LoTexRulerBackend:
|
||||
return r.LotexRuler.RouteDeleteNamespaceRulesConfig(ctx)
|
||||
default:
|
||||
return response.Error(400, fmt.Sprintf("unexpected backend type (%v)", t), nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ForkedRuler) RouteDeleteRuleGroupConfig(ctx *models.ReqContext) response.Response {
|
||||
switch t := r.backendType(ctx); t {
|
||||
case apimodels.GrafanaBackend:
|
||||
return r.GrafanaRuler.RouteDeleteRuleGroupConfig(ctx)
|
||||
case apimodels.LoTexRulerBackend:
|
||||
return r.LotexRuler.RouteDeleteRuleGroupConfig(ctx)
|
||||
default:
|
||||
return response.Error(400, fmt.Sprintf("unexpected backend type (%v)", t), nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ForkedRuler) RouteGetNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
|
||||
switch t := r.backendType(ctx); t {
|
||||
case apimodels.GrafanaBackend:
|
||||
return r.GrafanaRuler.RouteGetNamespaceRulesConfig(ctx)
|
||||
case apimodels.LoTexRulerBackend:
|
||||
return r.LotexRuler.RouteGetNamespaceRulesConfig(ctx)
|
||||
default:
|
||||
return response.Error(400, fmt.Sprintf("unexpected backend type (%v)", t), nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ForkedRuler) RouteGetRulegGroupConfig(ctx *models.ReqContext) response.Response {
|
||||
switch t := r.backendType(ctx); t {
|
||||
case apimodels.GrafanaBackend:
|
||||
return r.GrafanaRuler.RouteGetRulegGroupConfig(ctx)
|
||||
case apimodels.LoTexRulerBackend:
|
||||
return r.LotexRuler.RouteGetRulegGroupConfig(ctx)
|
||||
default:
|
||||
return response.Error(400, fmt.Sprintf("unexpected backend type (%v)", t), nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ForkedRuler) RouteGetRulesConfig(ctx *models.ReqContext) response.Response {
|
||||
switch t := r.backendType(ctx); t {
|
||||
case apimodels.GrafanaBackend:
|
||||
return r.GrafanaRuler.RouteGetRulesConfig(ctx)
|
||||
case apimodels.LoTexRulerBackend:
|
||||
return r.LotexRuler.RouteGetRulesConfig(ctx)
|
||||
default:
|
||||
return response.Error(400, fmt.Sprintf("unexpected backend type (%v)", t), nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (r *ForkedRuler) RoutePostNameRulesConfig(ctx *models.ReqContext, conf apimodels.RuleGroupConfig) response.Response {
|
||||
backendType := r.backendType(ctx)
|
||||
payloadType := conf.Type()
|
||||
|
||||
if backendType != payloadType {
|
||||
return response.Error(
|
||||
400,
|
||||
fmt.Sprintf(
|
||||
"unexpected backend type (%v) vs payload type (%v)",
|
||||
backendType,
|
||||
payloadType,
|
||||
),
|
||||
nil,
|
||||
)
|
||||
}
|
||||
|
||||
switch backendType {
|
||||
case apimodels.GrafanaBackend:
|
||||
return r.GrafanaRuler.RoutePostNameRulesConfig(ctx, conf)
|
||||
case apimodels.LoTexRulerBackend:
|
||||
return r.LotexRuler.RoutePostNameRulesConfig(ctx, conf)
|
||||
default:
|
||||
return response.Error(400, fmt.Sprintf("unexpected backend type (%v)", backendType), nil)
|
||||
}
|
||||
}
|
212
pkg/services/ngalert/api/lotex.go
Normal file
212
pkg/services/ngalert/api/lotex.go
Normal file
@ -0,0 +1,212 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"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
|
||||
}
|
||||
|
||||
// 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("DatasourceId"))
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (r *LotexRuler) RouteDeleteNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
|
||||
return r.withReq(
|
||||
ctx,
|
||||
&http.Request{
|
||||
Method: "DELETE",
|
||||
URL: withPath(
|
||||
*ctx.Req.URL,
|
||||
fmt.Sprintf("/api/prom/rules/%s", ctx.Params("Namespace")),
|
||||
),
|
||||
},
|
||||
messageExtractor,
|
||||
)
|
||||
}
|
||||
|
||||
func (r *LotexRuler) RouteDeleteRuleGroupConfig(ctx *models.ReqContext) response.Response {
|
||||
return r.withReq(
|
||||
ctx,
|
||||
&http.Request{
|
||||
Method: "DELETE",
|
||||
URL: withPath(
|
||||
*ctx.Req.URL,
|
||||
fmt.Sprintf(
|
||||
"%s/%s/%s",
|
||||
legacyRulerPrefix,
|
||||
ctx.Params("Namespace"),
|
||||
ctx.Params("Groupname"),
|
||||
),
|
||||
),
|
||||
},
|
||||
messageExtractor,
|
||||
)
|
||||
}
|
||||
|
||||
func (r *LotexRuler) RouteGetNamespaceRulesConfig(ctx *models.ReqContext) response.Response {
|
||||
return r.withReq(
|
||||
ctx, &http.Request{
|
||||
URL: withPath(
|
||||
*ctx.Req.URL,
|
||||
fmt.Sprintf(
|
||||
"%s/%s",
|
||||
legacyRulerPrefix,
|
||||
ctx.Params("Namespace"),
|
||||
),
|
||||
),
|
||||
},
|
||||
yamlExtractor(apimodels.NamespaceConfigResponse{}),
|
||||
)
|
||||
}
|
||||
|
||||
func (r *LotexRuler) RouteGetRulegGroupConfig(ctx *models.ReqContext) response.Response {
|
||||
return r.withReq(
|
||||
ctx,
|
||||
&http.Request{
|
||||
URL: withPath(
|
||||
*ctx.Req.URL,
|
||||
fmt.Sprintf(
|
||||
"%s/%s/%s",
|
||||
legacyRulerPrefix,
|
||||
ctx.Params("Namespace"),
|
||||
ctx.Params("Groupname"),
|
||||
),
|
||||
),
|
||||
},
|
||||
yamlExtractor(apimodels.RuleGroupConfigResponse{}),
|
||||
)
|
||||
}
|
||||
|
||||
func (r *LotexRuler) RouteGetRulesConfig(ctx *models.ReqContext) response.Response {
|
||||
return r.withReq(
|
||||
ctx,
|
||||
&http.Request{
|
||||
URL: withPath(
|
||||
*ctx.Req.URL,
|
||||
legacyRulerPrefix,
|
||||
),
|
||||
},
|
||||
yamlExtractor(apimodels.NamespaceConfigResponse{}),
|
||||
)
|
||||
}
|
||||
|
||||
func (r *LotexRuler) RoutePostNameRulesConfig(ctx *models.ReqContext, conf apimodels.RuleGroupConfig) response.Response {
|
||||
yml, err := yaml.Marshal(conf)
|
||||
if err != nil {
|
||||
return response.Error(500, "Failed marshal rule group", err)
|
||||
}
|
||||
body, ln := payload(yml)
|
||||
|
||||
ns := ctx.Params("Namespace")
|
||||
|
||||
u := withPath(*ctx.Req.URL, fmt.Sprintf("%s/%s", legacyRulerPrefix, ns))
|
||||
req := &http.Request{
|
||||
Method: "POST",
|
||||
URL: u,
|
||||
Body: body,
|
||||
ContentLength: ln,
|
||||
}
|
||||
return r.withReq(ctx, req, jsonExtractor(nil))
|
||||
}
|
||||
|
||||
func withPath(u url.URL, newPath string) *url.URL {
|
||||
// TODO: handle path escaping
|
||||
u.Path = newPath
|
||||
return &u
|
||||
}
|
||||
|
||||
func payload(b []byte) (io.ReadCloser, int64) {
|
||||
return ioutil.NopCloser(bytes.NewBuffer(b)), int64(len(b))
|
||||
}
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/datasourceproxy"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/schedule"
|
||||
@ -36,11 +37,12 @@ const (
|
||||
|
||||
// AlertNG is the service for evaluating the condition of an alert definition.
|
||||
type AlertNG struct {
|
||||
Cfg *setting.Cfg `inject:""`
|
||||
DatasourceCache datasources.CacheService `inject:""`
|
||||
RouteRegister routing.RouteRegister `inject:""`
|
||||
SQLStore *sqlstore.SQLStore `inject:""`
|
||||
DataService *tsdb.Service `inject:""`
|
||||
Cfg *setting.Cfg `inject:""`
|
||||
DatasourceCache datasources.CacheService `inject:""`
|
||||
RouteRegister routing.RouteRegister `inject:""`
|
||||
SQLStore *sqlstore.SQLStore `inject:""`
|
||||
DataService *tsdb.Service `inject:""`
|
||||
DataProxy *datasourceproxy.DatasourceProxyService `inject:""`
|
||||
Log log.Logger
|
||||
schedule schedule.ScheduleService
|
||||
}
|
||||
@ -73,6 +75,7 @@ func (ng *AlertNG) Init() error {
|
||||
RouteRegister: ng.RouteRegister,
|
||||
DataService: ng.DataService,
|
||||
Schedule: ng.schedule,
|
||||
DataProxy: ng.DataProxy,
|
||||
Store: store}
|
||||
api.RegisterAPIEndpoints()
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user