diff --git a/pkg/api/dataproxy.go b/pkg/api/dataproxy.go index 3bb2f236129..1bc97eb42ed 100644 --- a/pkg/api/dataproxy.go +++ b/pkg/api/dataproxy.go @@ -1,62 +1,22 @@ package api import ( - "fmt" - "github.com/pkg/errors" - "time" - "github.com/grafana/grafana/pkg/api/pluginproxy" - "github.com/grafana/grafana/pkg/bus" "github.com/grafana/grafana/pkg/metrics" m "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" ) -const HeaderNameNoBackendCache = "X-Grafana-NoCache" - -func (hs *HTTPServer) getDatasourceFromCache(id int64, c *m.ReqContext) (*m.DataSource, error) { - userPermissionsQuery := m.GetDataSourcePermissionsForUserQuery{ - User: c.SignedInUser, - } - if err := bus.Dispatch(&userPermissionsQuery); err != nil { - if err != bus.ErrHandlerNotFound { - return nil, err - } - } else { - permissionType, exists := userPermissionsQuery.Result[id] - if exists && permissionType != m.DsPermissionQuery { - return nil, errors.New("User not allowed to access datasource") - } - } - - nocache := c.Req.Header.Get(HeaderNameNoBackendCache) == "true" - cacheKey := fmt.Sprintf("ds-%d", id) - - if !nocache { - if cached, found := hs.cache.Get(cacheKey); found { - ds := cached.(*m.DataSource) - if ds.OrgId == c.OrgId { - return ds, nil - } - } - } - - query := m.GetDataSourceByIdQuery{Id: id, OrgId: c.OrgId} - if err := bus.Dispatch(&query); err != nil { - return nil, err - } - - hs.cache.Set(cacheKey, query.Result, time.Second*5) - return query.Result, nil -} - func (hs *HTTPServer) ProxyDataSourceRequest(c *m.ReqContext) { c.TimeRequest(metrics.M_DataSource_ProxyReq_Timer) dsId := c.ParamsInt64(":id") - ds, err := hs.getDatasourceFromCache(dsId, c) - + ds, err := hs.DatasourceCache.GetDatasource(dsId, c.SignedInUser, c.SkipCache) if err != nil { + if err == m.ErrDataSourceAccessDenied { + c.JsonApiErr(403, "Access denied to datasource", nil) + return + } c.JsonApiErr(500, "Unable to load datasource meta data", err) return } diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 858b3c5a8c5..ce28e4716ee 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -16,7 +16,6 @@ import ( "github.com/prometheus/client_golang/prometheus/promhttp" - gocache "github.com/patrickmn/go-cache" macaron "gopkg.in/macaron.v1" "github.com/grafana/grafana/pkg/api/live" @@ -28,6 +27,8 @@ import ( "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/plugins" "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/services/cache" + "github.com/grafana/grafana/pkg/services/datasources" "github.com/grafana/grafana/pkg/services/hooks" "github.com/grafana/grafana/pkg/services/rendering" "github.com/grafana/grafana/pkg/setting" @@ -46,19 +47,19 @@ type HTTPServer struct { macaron *macaron.Macaron context context.Context streamManager *live.StreamManager - cache *gocache.Cache httpSrv *http.Server - RouteRegister routing.RouteRegister `inject:""` - Bus bus.Bus `inject:""` - RenderService rendering.Service `inject:""` - Cfg *setting.Cfg `inject:""` - HooksService *hooks.HooksService `inject:""` + RouteRegister routing.RouteRegister `inject:""` + Bus bus.Bus `inject:""` + RenderService rendering.Service `inject:""` + Cfg *setting.Cfg `inject:""` + HooksService *hooks.HooksService `inject:""` + CacheService *cache.CacheService `inject:""` + DatasourceCache datasources.CacheService `inject:""` } func (hs *HTTPServer) Init() error { hs.log = log.New("http.server") - hs.cache = gocache.New(5*time.Minute, 10*time.Minute) hs.streamManager = live.NewStreamManager() hs.macaron = hs.newMacaron() @@ -231,6 +232,7 @@ func (hs *HTTPServer) addMiddlewaresAndStaticRoutes() { m.Use(middleware.ValidateHostHeader(setting.Domain)) } + m.Use(middleware.HandleNoCacheHeader()) m.Use(middleware.AddDefaultResponseHeaders()) } diff --git a/pkg/api/metrics.go b/pkg/api/metrics.go index cb80bd346b8..a6cfe4d09de 100644 --- a/pkg/api/metrics.go +++ b/pkg/api/metrics.go @@ -25,8 +25,11 @@ func (hs *HTTPServer) QueryMetrics(c *m.ReqContext, reqDto dtos.MetricRequest) R return Error(400, "Query missing datasourceId", nil) } - ds, err := hs.getDatasourceFromCache(datasourceId, c) + ds, err := hs.DatasourceCache.GetDatasource(datasourceId, c.SignedInUser, c.SkipCache) if err != nil { + if err == m.ErrDataSourceAccessDenied { + return Error(403, "Access denied to datasource", nil) + } return Error(500, "Unable to load datasource meta data", err) } diff --git a/pkg/cmd/grafana-server/server.go b/pkg/cmd/grafana-server/server.go index 765b8ddf993..a07cb692a68 100644 --- a/pkg/cmd/grafana-server/server.go +++ b/pkg/cmd/grafana-server/server.go @@ -15,9 +15,15 @@ import ( "github.com/grafana/grafana/pkg/api" "github.com/grafana/grafana/pkg/api/routing" "github.com/grafana/grafana/pkg/bus" - _ "github.com/grafana/grafana/pkg/extensions" "github.com/grafana/grafana/pkg/log" "github.com/grafana/grafana/pkg/login" + "github.com/grafana/grafana/pkg/services/cache" + "github.com/grafana/grafana/pkg/setting" + + "github.com/grafana/grafana/pkg/social" + + // self registering services + _ "github.com/grafana/grafana/pkg/extensions" _ "github.com/grafana/grafana/pkg/metrics" "github.com/grafana/grafana/pkg/middleware" _ "github.com/grafana/grafana/pkg/plugins" @@ -72,6 +78,7 @@ func (g *GrafanaServerImpl) Run() error { serviceGraph.Provide(&inject.Object{Value: bus.GetBus()}) serviceGraph.Provide(&inject.Object{Value: g.cfg}) serviceGraph.Provide(&inject.Object{Value: routing.NewRouteRegister(middleware.RequestMetrics, middleware.RequestTracing)}) + serviceGraph.Provide(&inject.Object{Value: cache.New(5*time.Minute, 10*time.Minute)}) // self registered services services := registry.GetServices() diff --git a/pkg/middleware/headers.go b/pkg/middleware/headers.go new file mode 100644 index 00000000000..28c623d74b0 --- /dev/null +++ b/pkg/middleware/headers.go @@ -0,0 +1,14 @@ +package middleware + +import ( + m "github.com/grafana/grafana/pkg/models" + macaron "gopkg.in/macaron.v1" +) + +const HeaderNameNoBackendCache = "X-Grafana-NoCache" + +func HandleNoCacheHeader() macaron.Handler { + return func(ctx *m.ReqContext) { + ctx.SkipCache = ctx.Req.Header.Get(HeaderNameNoBackendCache) == "true" + } +} diff --git a/pkg/middleware/middleware.go b/pkg/middleware/middleware.go index 7b29901c1a3..ace72d998eb 100644 --- a/pkg/middleware/middleware.go +++ b/pkg/middleware/middleware.go @@ -29,6 +29,7 @@ func GetContextHandler() macaron.Handler { Session: session.GetSession(), IsSignedIn: false, AllowAnonymous: false, + SkipCache: false, Logger: log.New("context"), } diff --git a/pkg/models/context.go b/pkg/models/context.go index 262f6550954..8ed7fa61a7d 100644 --- a/pkg/models/context.go +++ b/pkg/models/context.go @@ -20,6 +20,7 @@ type ReqContext struct { IsSignedIn bool IsRenderCall bool AllowAnonymous bool + SkipCache bool Logger log.Logger } diff --git a/pkg/services/cache/cache.go b/pkg/services/cache/cache.go new file mode 100644 index 00000000000..93b2cf76e26 --- /dev/null +++ b/pkg/services/cache/cache.go @@ -0,0 +1,17 @@ +package cache + +import ( + "time" + + gocache "github.com/patrickmn/go-cache" +) + +type CacheService struct { + *gocache.Cache +} + +func New(defaultExpiration, cleanupInterval time.Duration) *CacheService { + return &CacheService{ + Cache: gocache.New(defaultExpiration, cleanupInterval), + } +} diff --git a/pkg/services/datasources/cache.go b/pkg/services/datasources/cache.go new file mode 100644 index 00000000000..c984b4f8743 --- /dev/null +++ b/pkg/services/datasources/cache.go @@ -0,0 +1,49 @@ +package datasources + +import ( + "fmt" + "time" + + "github.com/grafana/grafana/pkg/bus" + m "github.com/grafana/grafana/pkg/models" + "github.com/grafana/grafana/pkg/registry" + "github.com/grafana/grafana/pkg/services/cache" +) + +type CacheService interface { + GetDatasource(datasourceID int64, user *m.SignedInUser, skipCache bool) (*m.DataSource, error) +} + +type CacheServiceImpl struct { + Bus bus.Bus `inject:""` + CacheService *cache.CacheService `inject:""` +} + +func init() { + registry.RegisterService(&CacheServiceImpl{}) +} + +func (dc *CacheServiceImpl) Init() error { + return nil +} + +func (dc *CacheServiceImpl) GetDatasource(datasourceID int64, user *m.SignedInUser, skipCache bool) (*m.DataSource, error) { + cacheKey := fmt.Sprintf("ds-%d", datasourceID) + + if !skipCache { + if cached, found := dc.CacheService.Get(cacheKey); found { + ds := cached.(*m.DataSource) + if ds.OrgId == user.OrgId { + return ds, nil + } + } + } + + query := m.GetDataSourceByIdQuery{Id: datasourceID, OrgId: user.OrgId} + if err := dc.Bus.Dispatch(&query); err != nil { + return nil, err + } + + dc.CacheService.Set(cacheKey, query.Result, time.Second*5) + return query.Result, nil +}